aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.builds/alpine.yml22
-rw-r--r--.builds/archlinux.yml26
-rw-r--r--.builds/freebsd.yml79
-rw-r--r--.editorconfig12
-rw-r--r--.gitignore9
-rw-r--r--CONTRIBUTING.md356
-rw-r--r--LICENSE20
-rw-r--r--README.md98
-rw-r--r--backend/backend.c291
-rw-r--r--backend/drm/atomic.c273
-rw-r--r--backend/drm/backend.c211
-rw-r--r--backend/drm/drm.c1418
-rw-r--r--backend/drm/legacy.c83
-rw-r--r--backend/drm/properties.c151
-rw-r--r--backend/drm/renderer.c264
-rw-r--r--backend/drm/util.c348
-rw-r--r--backend/headless/backend.c132
-rw-r--r--backend/headless/input_device.c99
-rw-r--r--backend/headless/output.c159
-rw-r--r--backend/libinput/backend.c204
-rw-r--r--backend/libinput/events.c294
-rw-r--r--backend/libinput/keyboard.c83
-rw-r--r--backend/libinput/pointer.c136
-rw-r--r--backend/libinput/switch.c55
-rw-r--r--backend/libinput/tablet_pad.c183
-rw-r--r--backend/libinput/tablet_tool.c371
-rw-r--r--backend/libinput/touch.c97
-rw-r--r--backend/meson.build61
-rw-r--r--backend/multi/backend.c231
-rw-r--r--backend/session/direct-freebsd.c268
-rw-r--r--backend/session/direct-ipc.c269
-rw-r--r--backend/session/direct.c278
-rw-r--r--backend/session/logind.c562
-rw-r--r--backend/session/session.c359
-rw-r--r--backend/wayland/backend.c279
-rw-r--r--backend/wayland/output.c367
-rw-r--r--backend/wayland/wl_seat.c434
-rw-r--r--backend/x11/backend.c314
-rw-r--r--backend/x11/input_device.c244
-rw-r--r--backend/x11/meson.build35
-rw-r--r--backend/x11/output.c236
-rw-r--r--docs/env_vars.md34
-rw-r--r--examples/.gitignore1
-rw-r--r--examples/cat.c2641
-rw-r--r--examples/cat.h13
-rw-r--r--examples/dmabuf-capture.c850
-rw-r--r--examples/foreign-toplevel.c358
-rw-r--r--examples/gamma-control.c195
-rw-r--r--examples/idle-inhibit.c233
-rw-r--r--examples/idle.c197
-rw-r--r--examples/input-inhibitor.c189
-rw-r--r--examples/input-method.c402
-rw-r--r--examples/layer-shell.c653
-rw-r--r--examples/meson.build133
-rw-r--r--examples/multi-pointer.c340
-rw-r--r--examples/output-layout.c295
-rw-r--r--examples/pointer-constraints.c260
-rw-r--r--examples/pointer.c403
-rw-r--r--examples/rotation.c277
-rw-r--r--examples/screencopy.c261
-rw-r--r--examples/screenshot.c237
-rw-r--r--examples/simple.c185
-rw-r--r--examples/tablet.c381
-rw-r--r--examples/text-input.c394
-rw-r--r--examples/toplevel-decoration.c253
-rw-r--r--examples/touch.c286
-rwxr-xr-xglgen.sh92
-rw-r--r--include/backend/drm/drm.h162
-rw-r--r--include/backend/drm/iface.h41
-rw-r--r--include/backend/drm/properties.h72
-rw-r--r--include/backend/drm/renderer.h57
-rw-r--r--include/backend/drm/util.h41
-rw-r--r--include/backend/headless.h40
-rw-r--r--include/backend/libinput.h94
-rw-r--r--include/backend/multi.h23
-rw-r--r--include/backend/session/direct-ipc.h12
-rw-r--r--include/backend/wayland.h92
-rw-r--r--include/backend/x11.h91
-rw-r--r--include/meson.build1
-rw-r--r--include/render/gles2.h108
-rw-r--r--include/rootston/bindings.h9
-rw-r--r--include/rootston/config.h133
-rw-r--r--include/rootston/cursor.h105
-rw-r--r--include/rootston/desktop.h119
-rw-r--r--include/rootston/ini.h93
-rw-r--r--include/rootston/input.h37
-rw-r--r--include/rootston/keyboard.h34
-rw-r--r--include/rootston/layers.h35
-rw-r--r--include/rootston/output.h52
-rw-r--r--include/rootston/seat.h181
-rw-r--r--include/rootston/server.h37
-rw-r--r--include/rootston/switch.h18
-rw-r--r--include/rootston/text_input.h63
-rw-r--r--include/rootston/view.h260
-rw-r--r--include/rootston/virtual_keyboard.h7
-rw-r--r--include/rootston/xcursor.h12
-rw-r--r--include/types/wlr_data_device.h37
-rw-r--r--include/types/wlr_seat.h23
-rw-r--r--include/types/wlr_tablet_v2.h93
-rw-r--r--include/types/wlr_xdg_shell.h48
-rw-r--r--include/types/wlr_xdg_shell_v6.h47
-rw-r--r--include/util/array.h9
-rw-r--r--include/util/shm.h7
-rw-r--r--include/util/signal.h8
-rw-r--r--include/wlr/backend.h70
-rw-r--r--include/wlr/backend/drm.h37
-rw-r--r--include/wlr/backend/headless.h40
-rw-r--r--include/wlr/backend/interface.h32
-rw-r--r--include/wlr/backend/libinput.h27
-rw-r--r--include/wlr/backend/meson.build16
-rw-r--r--include/wlr/backend/multi.h36
-rw-r--r--include/wlr/backend/session.h94
-rw-r--r--include/wlr/backend/session/interface.h22
-rw-r--r--include/wlr/backend/session/meson.build1
-rw-r--r--include/wlr/backend/wayland.h45
-rw-r--r--include/wlr/backend/x11.h20
-rw-r--r--include/wlr/config.h.in16
-rw-r--r--include/wlr/interfaces/meson.build11
-rw-r--r--include/wlr/interfaces/wlr_input_device.h25
-rw-r--r--include/wlr/interfaces/wlr_keyboard.h29
-rw-r--r--include/wlr/interfaces/wlr_output.h52
-rw-r--r--include/wlr/interfaces/wlr_pointer.h22
-rw-r--r--include/wlr/interfaces/wlr_switch.h22
-rw-r--r--include/wlr/interfaces/wlr_tablet_pad.h22
-rw-r--r--include/wlr/interfaces/wlr_tablet_tool.h22
-rw-r--r--include/wlr/interfaces/wlr_touch.h22
-rw-r--r--include/wlr/meson.build26
-rw-r--r--include/wlr/render/dmabuf.h48
-rw-r--r--include/wlr/render/egl.h118
-rw-r--r--include/wlr/render/gles2.h27
-rw-r--r--include/wlr/render/interface.h87
-rw-r--r--include/wlr/render/meson.build9
-rw-r--r--include/wlr/render/wlr_renderer.h121
-rw-r--r--include/wlr/render/wlr_texture.h73
-rw-r--r--include/wlr/types/meson.build49
-rw-r--r--include/wlr/types/wlr_box.h44
-rw-r--r--include/wlr/types/wlr_buffer.h71
-rw-r--r--include/wlr/types/wlr_compositor.h53
-rw-r--r--include/wlr/types/wlr_cursor.h194
-rw-r--r--include/wlr/types/wlr_data_device.h231
-rw-r--r--include/wlr/types/wlr_export_dmabuf_v1.h46
-rw-r--r--include/wlr/types/wlr_foreign_toplevel_management_v1.h120
-rw-r--r--include/wlr/types/wlr_gamma_control.h46
-rw-r--r--include/wlr/types/wlr_gamma_control_v1.h35
-rw-r--r--include/wlr/types/wlr_gtk_primary_selection.h53
-rw-r--r--include/wlr/types/wlr_idle.h71
-rw-r--r--include/wlr/types/wlr_idle_inhibit_v1.h58
-rw-r--r--include/wlr/types/wlr_input_device.h66
-rw-r--r--include/wlr/types/wlr_input_inhibitor.h34
-rw-r--r--include/wlr/types/wlr_input_method_v2.h87
-rw-r--r--include/wlr/types/wlr_keyboard.h115
-rw-r--r--include/wlr/types/wlr_layer_shell_v1.h134
-rw-r--r--include/wlr/types/wlr_linux_dmabuf_v1.h75
-rw-r--r--include/wlr/types/wlr_list.h83
-rw-r--r--include/wlr/types/wlr_matrix.h59
-rw-r--r--include/wlr/types/wlr_output.h278
-rw-r--r--include/wlr/types/wlr_output_damage.h87
-rw-r--r--include/wlr/types/wlr_output_layout.h133
-rw-r--r--include/wlr/types/wlr_pointer.h72
-rw-r--r--include/wlr/types/wlr_pointer_constraints_v1.h102
-rw-r--r--include/wlr/types/wlr_presentation_time.h59
-rw-r--r--include/wlr/types/wlr_primary_selection.h54
-rw-r--r--include/wlr/types/wlr_region.h24
-rw-r--r--include/wlr/types/wlr_screencopy_v1.h55
-rw-r--r--include/wlr/types/wlr_screenshooter.h41
-rw-r--r--include/wlr/types/wlr_seat.h573
-rw-r--r--include/wlr/types/wlr_server_decoration.h78
-rw-r--r--include/wlr/types/wlr_surface.h246
-rw-r--r--include/wlr/types/wlr_switch.h47
-rw-r--r--include/wlr/types/wlr_tablet_pad.h94
-rw-r--r--include/wlr/types/wlr_tablet_tool.h136
-rw-r--r--include/wlr/types/wlr_tablet_v2.h330
-rw-r--r--include/wlr/types/wlr_text_input_v3.h93
-rw-r--r--include/wlr/types/wlr_touch.h58
-rw-r--r--include/wlr/types/wlr_virtual_keyboard_v1.h46
-rw-r--r--include/wlr/types/wlr_wl_shell.h175
-rw-r--r--include/wlr/types/wlr_xcursor_manager.h69
-rw-r--r--include/wlr/types/wlr_xdg_decoration_v1.h69
-rw-r--r--include/wlr/types/wlr_xdg_output_v1.h47
-rw-r--r--include/wlr/types/wlr_xdg_shell.h389
-rw-r--r--include/wlr/types/wlr_xdg_shell_v6.h359
-rw-r--r--include/wlr/util/edges.h28
-rw-r--r--include/wlr/util/log.h64
-rw-r--r--include/wlr/util/meson.build6
-rw-r--r--include/wlr/util/region.h56
-rw-r--r--include/wlr/version.h.in16
-rw-r--r--include/wlr/xcursor.h102
-rw-r--r--include/wlr/xwayland.h260
-rw-r--r--include/xcursor/cursor_data.h554
-rw-r--r--include/xcursor/xcursor.h65
-rw-r--r--include/xwayland/selection.h74
-rw-r--r--include/xwayland/xwm.h158
-rw-r--r--meson.build205
-rw-r--r--meson_options.txt9
-rw-r--r--protocol/gamma-control.xml57
-rw-r--r--protocol/gtk-primary-selection.xml225
-rw-r--r--protocol/idle.xml49
-rw-r--r--protocol/input-method-unstable-v2.xml490
-rw-r--r--protocol/meson.build95
-rw-r--r--protocol/screenshooter.xml16
-rw-r--r--protocol/server-decoration.xml94
-rw-r--r--protocol/text-input-unstable-v3.xml441
-rw-r--r--protocol/virtual-keyboard-unstable-v1.xml113
-rw-r--r--protocol/wlr-export-dmabuf-unstable-v1.xml203
-rw-r--r--protocol/wlr-foreign-toplevel-management-unstable-v1.xml235
-rw-r--r--protocol/wlr-gamma-control-unstable-v1.xml126
-rw-r--r--protocol/wlr-input-inhibitor-unstable-v1.xml67
-rw-r--r--protocol/wlr-layer-shell-unstable-v1.xml285
-rw-r--r--protocol/wlr-screencopy-unstable-v1.xml179
-rw-r--r--render/dmabuf.c10
-rw-r--r--render/egl.c609
-rw-r--r--render/glapi.txt19
-rw-r--r--render/gles2/pixel_format.c78
-rw-r--r--render/gles2/renderer.c655
-rw-r--r--render/gles2/shaders.c88
-rw-r--r--render/gles2/texture.c304
-rw-r--r--render/gles2/util.c38
-rw-r--r--render/meson.build37
-rw-r--r--render/wlr_renderer.c226
-rw-r--r--render/wlr_texture.c71
-rw-r--r--rootston/bindings.c107
-rw-r--r--rootston/config.c670
-rw-r--r--rootston/cursor.c615
-rw-r--r--rootston/desktop.c1101
-rw-r--r--rootston/ini.c195
-rw-r--r--rootston/input.c145
-rw-r--r--rootston/keyboard.c349
-rw-r--r--rootston/layer_shell.c506
-rw-r--r--rootston/main.c81
-rw-r--r--rootston/meson.build30
-rw-r--r--rootston/output.c918
-rw-r--r--rootston/rootston.ini.example63
-rw-r--r--rootston/seat.c1473
-rw-r--r--rootston/switch.c26
-rw-r--r--rootston/text_input.c310
-rw-r--r--rootston/virtual_keyboard.c21
-rw-r--r--rootston/wl_shell.c295
-rw-r--r--rootston/xdg_shell.c565
-rw-r--r--rootston/xdg_shell_v6.c495
-rw-r--r--rootston/xwayland.c351
-rw-r--r--types/data_device/wlr_data_device.c284
-rw-r--r--types/data_device/wlr_data_offer.c222
-rw-r--r--types/data_device/wlr_data_source.c265
-rw-r--r--types/data_device/wlr_drag.c490
-rw-r--r--types/meson.build70
-rw-r--r--types/seat/wlr_seat.c364
-rw-r--r--types/seat/wlr_seat_keyboard.c422
-rw-r--r--types/seat/wlr_seat_pointer.c364
-rw-r--r--types/seat/wlr_seat_touch.c361
-rw-r--r--types/tablet_v2/wlr_tablet_v2.c322
-rw-r--r--types/tablet_v2/wlr_tablet_v2_pad.c697
-rw-r--r--types/tablet_v2/wlr_tablet_v2_tablet.c132
-rw-r--r--types/tablet_v2/wlr_tablet_v2_tool.c834
-rw-r--r--types/wlr_box.c153
-rw-r--r--types/wlr_buffer.c205
-rw-r--r--types/wlr_compositor.c245
-rw-r--r--types/wlr_cursor.c741
-rw-r--r--types/wlr_export_dmabuf_v1.c228
-rw-r--r--types/wlr_foreign_toplevel_management_v1.c578
-rw-r--r--types/wlr_gamma_control.c197
-rw-r--r--types/wlr_gamma_control_v1.c272
-rw-r--r--types/wlr_gtk_primary_selection.c501
-rw-r--r--types/wlr_idle.c248
-rw-r--r--types/wlr_idle_inhibit_v1.c194
-rw-r--r--types/wlr_input_device.c69
-rw-r--r--types/wlr_input_inhibitor.c153
-rw-r--r--types/wlr_input_method_v2.c298
-rw-r--r--types/wlr_keyboard.c237
-rw-r--r--types/wlr_layer_shell_v1.c564
-rw-r--r--types/wlr_linux_dmabuf_v1.c509
-rw-r--r--types/wlr_list.c109
-rw-r--r--types/wlr_matrix.c169
-rw-r--r--types/wlr_output.c921
-rw-r--r--types/wlr_output_damage.c192
-rw-r--r--types/wlr_output_layout.c521
-rw-r--r--types/wlr_pointer.c29
-rw-r--r--types/wlr_pointer_constraints_v1.c372
-rw-r--r--types/wlr_presentation_time.c223
-rw-r--r--types/wlr_primary_selection.c69
-rw-r--r--types/wlr_region.c78
-rw-r--r--types/wlr_screencopy_v1.c349
-rw-r--r--types/wlr_screenshooter.c213
-rw-r--r--types/wlr_server_decoration.c215
-rw-r--r--types/wlr_surface.c1061
-rw-r--r--types/wlr_switch.c22
-rw-r--r--types/wlr_tablet_pad.c29
-rw-r--r--types/wlr_tablet_tool.c29
-rw-r--r--types/wlr_text_input_v3.c337
-rw-r--r--types/wlr_touch.c22
-rw-r--r--types/wlr_virtual_keyboard_v1.c248
-rw-r--r--types/wlr_wl_shell.c731
-rw-r--r--types/wlr_xcursor_manager.c84
-rw-r--r--types/wlr_xdg_decoration_v1.c310
-rw-r--r--types/wlr_xdg_output_v1.c256
-rw-r--r--types/xdg_shell/wlr_xdg_popup.c512
-rw-r--r--types/xdg_shell/wlr_xdg_positioner.c311
-rw-r--r--types/xdg_shell/wlr_xdg_shell.c174
-rw-r--r--types/xdg_shell/wlr_xdg_surface.c651
-rw-r--r--types/xdg_shell/wlr_xdg_toplevel.c558
-rw-r--r--types/xdg_shell_v6/wlr_xdg_popup_v6.c527
-rw-r--r--types/xdg_shell_v6/wlr_xdg_positioner_v6.c234
-rw-r--r--types/xdg_shell_v6/wlr_xdg_shell_v6.c175
-rw-r--r--types/xdg_shell_v6/wlr_xdg_surface_v6.c602
-rw-r--r--types/xdg_shell_v6/wlr_xdg_toplevel_v6.c506
-rw-r--r--util/array.c21
-rw-r--r--util/log.c91
-rw-r--r--util/meson.build12
-rw-r--r--util/region.c250
-rw-r--r--util/shm.c55
-rw-r--r--util/signal.c34
-rw-r--r--wlroots.syms10
-rw-r--r--xcursor/meson.build9
-rw-r--r--xcursor/wlr_xcursor.c350
-rw-r--r--xcursor/xcursor.c983
-rw-r--r--xwayland/meson.build51
-rw-r--r--xwayland/selection/dnd.c339
-rw-r--r--xwayland/selection/incoming.c464
-rw-r--r--xwayland/selection/outgoing.c419
-rw-r--r--xwayland/selection/selection.c317
-rw-r--r--xwayland/sockets.c168
-rw-r--r--xwayland/sockets.h7
-rw-r--r--xwayland/xwayland.c488
-rw-r--r--xwayland/xwm.c1802
323 files changed, 69556 insertions, 0 deletions
diff --git a/.builds/alpine.yml b/.builds/alpine.yml
new file mode 100644
index 00000000..9751dc8a
--- /dev/null
+++ b/.builds/alpine.yml
@@ -0,0 +1,22 @@
+image: alpine/edge
+packages:
+ - eudev-dev
+ - ffmpeg-dev
+ - libcap-dev
+ - libinput-dev
+ - libxkbcommon-dev
+ - mesa-dev
+ - meson
+ - pixman-dev
+ - wayland-dev
+ - wayland-protocols
+ - xcb-util-image-dev
+sources:
+ - https://github.com/swaywm/wlroots
+tasks:
+ - setup: |
+ cd wlroots
+ meson build
+ - build: |
+ cd wlroots
+ ninja -C build
diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml
new file mode 100644
index 00000000..2a4b5fc0
--- /dev/null
+++ b/.builds/archlinux.yml
@@ -0,0 +1,26 @@
+image: archlinux
+packages:
+ - clang
+ - ffmpeg
+ - libcap
+ - libinput
+ - libxkbcommon
+ - mesa
+ - meson
+ - pixman
+ - wayland
+ - wayland-protocols
+ - xcb-util-image
+sources:
+ - https://github.com/swaywm/wlroots
+tasks:
+ - setup: |
+ cd wlroots
+ CC=gcc meson build-gcc
+ CC=clang meson build-clang
+ - gcc: |
+ cd wlroots/build-gcc
+ ninja
+ - clang: |
+ cd wlroots/build-clang
+ ninja
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml
new file mode 100644
index 00000000..7199af85
--- /dev/null
+++ b/.builds/freebsd.yml
@@ -0,0 +1,79 @@
+image: freebsd
+packages:
+- devel/automake
+- multimedia/ffmpeg
+- devel/gmake
+- devel/json-c
+- devel/libtool
+- x11/libxkbcommon
+- textproc/libxslt
+- x11-toolkits/pango
+- devel/pkgconf
+- print/texinfo
+- x11/xcb-util-image
+- x11/xcb-util-wm
+- python36
+- py36-setuptools
+sources:
+- https://github.com/swaywm/wlroots
+tasks:
+- setup: |
+ # Don't build unnecessary stuff
+ echo "OPTIONS_UNSET+= NLS DOCS EXAMPLES LIBWACOM" | sudo tee -a /etc/make.conf
+ # Note: this could probably be set in the FreeBSD base image
+ echo "BATCH=yes" | sudo tee -a /etc/make.conf
+- ports_tree: |
+ # This is ugly, but fetching and extracting the whole ports tree takes a
+ # really-really long time...
+ # First we need a clean tree, and renaming is faster than deleting.
+ sudo mv /usr/ports /usr/ports.orig
+ sudo mkdir /usr/ports
+ # Fetch only needed ports
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/GIDs /usr/ports/GIDs
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/Keywords /usr/ports/Keywords
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/Makefile /usr/ports/Makefile
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/Mk /usr/ports/Mk
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/Templates /usr/ports/Templates
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/Tools /usr/ports/Tools
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/UIDs /usr/ports/UIDs
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/devel/libevdev /usr/ports/devel/libevdev
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/devel/libmtdev /usr/ports/devel/libmtdev
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/devel/libudev-devd /usr/ports/devel/libudev-devd
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/devel/meson /usr/ports/devel/meson
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/devel/ninja /usr/ports/devel/ninja
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/devel/py-evdev /usr/ports/devel/py-evdev
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/devel/py-six /usr/ports/devel/py-six
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/graphics/wayland-protocols /usr/ports/graphics/wayland-protocols
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/graphics/wayland /usr/ports/graphics/wayland
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/lang/python27 /usr/ports/lang/python27
+ sudo svnlite export --force https://svn.FreeBSD.org/ports/head/lang/python36 /usr/ports/lang/python36
+- fixup_libinput: |
+ sudo svnlite export https://github.com/FreeBSDDesktop/freebsd-ports/branches/feature/input/devel/libepoll-shim /usr/ports/devel/libepoll-shim
+ sudo svnlite export https://github.com/FreeBSDDesktop/freebsd-ports/branches/feature/input/x11/libinput /usr/ports/x11/libinput
+ sudo svnlite export https://github.com/FreeBSDDesktop/freebsd-ports/branches/feature/input/devel/evdev-proto /usr/ports/devel/evdev-proto
+ sudo svnlite export https://github.com/FreeBSDDesktop/freebsd-ports/branches/feature/input/devel/py-pyudev /usr/ports/devel/py-pyudev
+- ports_build: |
+ # v4l_compat is a dependency of libinput, but the version in the ports tree
+ # conflicts with the new evdev-proto. It can be safely removed though.
+ sudo pkg remove -fy v4l_compat
+ cd /usr/ports/devel/evdev-proto && sudo make install clean
+ cd /usr/ports/graphics/wayland-protocols/ && sudo make install
+ cd /usr/ports/x11/libinput/ && sudo make install clean
+- fixup_epoll: |
+ cat << 'EOF' | sudo tee /usr/local/libdata/pkgconfig/epoll-shim.pc
+ prefix=/usr/local
+ exec_prefix=\$\{\$prefix\}
+ libdir=${prefix}/lib
+ sharedlibdir=${prefix}/lib
+ includedir=${prefix}/include/libepoll-shim
+ Name: epoll-shim
+ Description: epoll shim implemented using kevent
+ Version: 0
+ Requires:
+ Libs: -L${libdir} -L${sharedlibdir} -lepoll-shim -lthr
+ Cflags: -I${includedir}
+ EOF
+- wlroots: |
+ cd wlroots
+ meson build
+ ninja -C build
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..c74fecda
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,12 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+indent_style = tab
+trim_trailing_whitespace = true
+
+[*.xml]
+indent_style = space
+indent_size = 2
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..be30a5fc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+.clang_complete
+*.o
+*.a
+bin/
+test/
+build/
+wayland-*-protocol.*
+wlr-example.ini
+rootston.ini
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..10e3ac22
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,356 @@
+# Contributing to wlroots
+
+Contributing just involves sending a pull request. You will probably be more
+successful with your contribution if you visit
+[#sway-devel](https://webchat.freenode.net/?channels=sway-devel) on
+irc.freenode.net 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.
+
+## Pull Requests
+
+If you already have your own pull 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 https://github.com/username/wlroots && cd wlroots`
+3. `git remote add upstream https://github.com/swaywm/wlroots`
+
+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 pull request from your feature branch
+
+When you submit your pull 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 pull 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 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 *"Fix #742"* 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 Github
+issues](https://help.github.com/articles/closing-issues-via-commit-messages/),
+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 pull 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 pull request
+description on Github is probably fair game for going into the extended commit
+message as well.
+
+See [here](https://chris.beams.io/posts/git-commit/) 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 pull 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 GitHub 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 pull request when all reviewers approve.
+5. **File** follow-up tickets if appropriate.
+
+## Style Reference
+
+wlroots is written in C with a style similar to the [kernel
+style](https://www.kernel.org/doc/Documentation/process/coding-style.rst), 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 80 columns, but you can go up to 100 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
+
+For functions that are responsible for constructing and destructing an object,
+they should be written as a pair of one of two forms:
+* `init`/`finish`: These initialize/deinitialize a type, but are **NOT**
+responsible for allocating it. They should accept a pointer to some
+pre-allocated memory (e.g. a member of a struct).
+* `create`/`destroy`: These also initialize/deinitialize, but will return a
+pointer to a `malloc`ed chunk of memory, and will `free` it in `destroy`.
+
+A destruction function should always be able to accept a NULL pointer or a
+zeroed value and exit cleanly; this simplifies error handling a lot.
+
+### Error Codes
+
+For functions not returning a value, they should return a (stdbool.h) bool to
+indicated if 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.
+
+### 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 list of resources clients
+created by binding to the global, a destroy signal and a `wl_display` destroy
+listener. Example:
+
+```c
+struct wlr_compositor {
+ struct wl_global *global;
+ struct wl_list resources;
+ …
+
+ 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`, destroy all bound resources
+and then destroy the struct.
+
+### 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);
+}
+```
+
+### 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`).
+Destructor requests should only call `wl_resource_destroy`.
+
+The compositor should not destroy resources on its own.
+
+### 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);
+}
+```
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..b75a8c40
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2017, 2018 Drew DeVault
+Copyright (c) 2014 Jari Vetoniemi
+
+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 00000000..2ebef443
--- /dev/null
+++ b/README.md
@@ -0,0 +1,98 @@
+# wlroots
+
+Pluggable, composable, unopinionated modules for building a
+[Wayland](http://wayland.freedesktop.org/) compositor; or about 50,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](https://github.com/swaywm/wlroots/wiki/Getting-started) to
+get started with wlroots.
+
+wlroots is developed under the direction of the
+[sway](https://github.com/swaywm/sway) project. A variety of wrapper libraries
+[are available](https://github.com/swaywm) for using it with your favorite
+programming language.
+
+## Building
+
+Install dependencies:
+
+* meson
+* wayland
+* wayland-protocols
+* EGL
+* GLESv2
+* libdrm
+* GBM
+* libinput
+* xkbcommon
+* udev
+* pixman
+* systemd (optional, for logind support)
+* elogind (optional, for logind support on systems without systemd)
+* libcap (optional, for capability support)
+
+If you choose to enable X11 support:
+
+* xcb
+* xcb-composite
+* xcb-xfixes
+* xcb-xinput
+* xcb-image
+* xcb-render
+* x11-xcb
+* xcb-errors (optional, for improved error reporting)
+* x11-icccm (optional, for improved Xwayland introspection)
+
+Run these commands:
+
+ meson build
+ ninja -C build
+
+Install like so:
+
+ sudo ninja -C build install
+
+## Running the test compositor
+
+wlroots comes with a test compositor called rootston, which demonstrates the
+features of the library and is used as a testbed for the development of the
+library. It may also be useful as a reference for understanding how to use
+various wlroots features, but it's not considered a production-quality codebase
+and is not designed for daily use.
+
+If you followed the build instructions above the rootston executable can be
+found at `./build/rootston/rootston`. To use it, refer to the example config at
+[./rootston/rootston.ini.example](https://github.com/swaywm/wlroots/blob/master/rootston/rootston.ini.example)
+and place a config file of your own at `rootston.ini` in the working directory
+(or in an arbitrary location via `rootston -C`). Other options are available,
+refer to `rootston -h`.
+
+## Contributing
+
+See [CONTRIBUTING.md](https://github.com/swaywm/wlroots/blob/master/CONTRIBUTING.md).
diff --git a/backend/backend.c b/backend/backend.c
new file mode 100644
index 00000000..b369a135
--- /dev/null
+++ b/backend/backend.c
@@ -0,0 +1,291 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <errno.h>
+#include <libinput.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/backend/drm.h>
+#include <wlr/backend/headless.h>
+#include <wlr/backend/interface.h>
+#include <wlr/backend/libinput.h>
+#include <wlr/backend/multi.h>
+#include <wlr/backend/session.h>
+#include <wlr/backend/wayland.h>
+#include <wlr/config.h>
+#include <wlr/util/log.h>
+#include "backend/multi.h"
+
+#if WLR_HAS_X11_BACKEND
+#include <wlr/backend/x11.h>
+#endif
+
+void wlr_backend_init(struct wlr_backend *backend,
+ const struct wlr_backend_impl *impl) {
+ assert(backend);
+ backend->impl = impl;
+ wl_signal_init(&backend->events.destroy);
+ wl_signal_init(&backend->events.new_input);
+ wl_signal_init(&backend->events.new_output);
+}
+
+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);
+ }
+}
+
+struct wlr_renderer *wlr_backend_get_renderer(struct wlr_backend *backend) {
+ if (backend->impl->get_renderer) {
+ return backend->impl->get_renderer(backend);
+ }
+ return NULL;
+}
+
+struct wlr_session *wlr_backend_get_session(struct wlr_backend *backend) {
+ if (backend->impl->get_session) {
+ return backend->impl->get_session(backend);
+ }
+ return NULL;
+}
+
+clockid_t wlr_backend_get_presentation_clock(struct wlr_backend *backend) {
+ if (backend->impl->get_presentation_clock) {
+ return backend->impl->get_presentation_clock(backend);
+ }
+ return CLOCK_MONOTONIC;
+}
+
+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;
+}
+
+static struct wlr_backend *attempt_wl_backend(struct wl_display *display,
+ wlr_renderer_create_func_t create_renderer_func) {
+ struct wlr_backend *backend = wlr_wl_backend_create(display, NULL, create_renderer_func);
+ 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;
+}
+
+#if WLR_HAS_X11_BACKEND
+static struct wlr_backend *attempt_x11_backend(struct wl_display *display,
+ const char *x11_display, wlr_renderer_create_func_t create_renderer_func) {
+ struct wlr_backend *backend = wlr_x11_backend_create(display, x11_display, create_renderer_func);
+ 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;
+}
+#endif
+
+static struct wlr_backend *attempt_headless_backend(
+ struct wl_display *display, wlr_renderer_create_func_t create_renderer_func) {
+ struct wlr_backend *backend = wlr_headless_backend_create(display, create_renderer_func);
+ 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 wl_display *display,
+ struct wlr_backend *backend, struct wlr_session *session,
+ wlr_renderer_create_func_t create_renderer_func) {
+ int gpus[8];
+ size_t num_gpus = wlr_session_find_gpus(session, 8, gpus);
+ struct wlr_backend *primary_drm = NULL;
+ wlr_log(WLR_INFO, "Found %zu GPUs", num_gpus);
+
+ for (size_t i = 0; i < num_gpus; ++i) {
+ struct wlr_backend *drm = wlr_drm_backend_create(display, session,
+ gpus[i], primary_drm, create_renderer_func);
+ if (!drm) {
+ wlr_log(WLR_ERROR, "Failed to open DRM device %d", gpus[i]);
+ continue;
+ }
+
+ if (!primary_drm) {
+ primary_drm = drm;
+ }
+
+ wlr_multi_backend_add(backend, drm);
+ }
+
+ return primary_drm;
+}
+
+static struct wlr_backend *attempt_backend_by_name(struct wl_display *display,
+ struct wlr_backend *backend, struct wlr_session **session,
+ const char *name, wlr_renderer_create_func_t create_renderer_func) {
+ if (strcmp(name, "wayland") == 0) {
+ return attempt_wl_backend(display, create_renderer_func);
+#if WLR_HAS_X11_BACKEND
+ } else if (strcmp(name, "x11") == 0) {
+ return attempt_x11_backend(display, NULL, create_renderer_func);
+#endif
+ } else if (strcmp(name, "headless") == 0) {
+ return attempt_headless_backend(display, create_renderer_func);
+ } else if (strcmp(name, "drm") == 0 || strcmp(name, "libinput") == 0) {
+ // DRM and libinput need a session
+ if (!*session) {
+ *session = wlr_session_create(display);
+ if (!*session) {
+ wlr_log(WLR_ERROR, "failed to start a session");
+ return NULL;
+ }
+ }
+
+ if (strcmp(name, "libinput") == 0) {
+ return wlr_libinput_backend_create(display, *session);
+ } else {
+ return attempt_drm_backend(display, backend, *session, create_renderer_func);
+ }
+ }
+
+ wlr_log(WLR_ERROR, "unrecognized backend '%s'", name);
+ return NULL;
+}
+
+struct wlr_backend *wlr_backend_autocreate(struct wl_display *display,
+ wlr_renderer_create_func_t create_renderer_func) {
+ struct wlr_backend *backend = wlr_multi_backend_create(display);
+ struct wlr_multi_backend *multi = (struct wlr_multi_backend *)backend;
+ if (!backend) {
+ wlr_log(WLR_ERROR, "could not allocate multibackend");
+ return NULL;
+ }
+
+ char *names = getenv("WLR_BACKENDS");
+ if (names) {
+ names = strdup(names);
+ if (names == NULL) {
+ wlr_log(WLR_ERROR, "allocation failed");
+ wlr_backend_destroy(backend);
+ return NULL;
+ }
+
+ char *saveptr;
+ char *name = strtok_r(names, ",", &saveptr);
+ while (name != NULL) {
+ struct wlr_backend *subbackend = attempt_backend_by_name(display,
+ backend, &multi->session, name, create_renderer_func);
+ if (subbackend == NULL) {
+ wlr_log(WLR_ERROR, "failed to start backend '%s'", name);
+ wlr_session_destroy(multi->session);
+ wlr_backend_destroy(backend);
+ free(names);
+ return NULL;
+ }
+
+ if (!wlr_multi_backend_add(backend, subbackend)) {
+ wlr_log(WLR_ERROR, "failed to add backend '%s'", name);
+ wlr_session_destroy(multi->session);
+ wlr_backend_destroy(backend);
+ free(names);
+ return NULL;
+ }
+
+ name = strtok_r(NULL, ",", &saveptr);
+ }
+
+ free(names);
+ return backend;
+ }
+
+ if (getenv("WAYLAND_DISPLAY") || getenv("_WAYLAND_DISPLAY") ||
+ getenv("WAYLAND_SOCKET")) {
+ struct wlr_backend *wl_backend = attempt_wl_backend(display,
+ create_renderer_func);
+ if (wl_backend) {
+ wlr_multi_backend_add(backend, wl_backend);
+ return backend;
+ }
+ }
+
+#if WLR_HAS_X11_BACKEND
+ const char *x11_display = getenv("DISPLAY");
+ if (x11_display) {
+ struct wlr_backend *x11_backend =
+ attempt_x11_backend(display, x11_display, create_renderer_func);
+ if (x11_backend) {
+ wlr_multi_backend_add(backend, x11_backend);
+ return backend;
+ }
+ }
+#endif
+
+ // Attempt DRM+libinput
+ multi->session = wlr_session_create(display);
+ if (!multi->session) {
+ wlr_log(WLR_ERROR, "Failed to start a DRM session");
+ wlr_backend_destroy(backend);
+ return NULL;
+ }
+
+ struct wlr_backend *libinput = wlr_libinput_backend_create(display,
+ multi->session);
+ if (!libinput) {
+ wlr_log(WLR_ERROR, "Failed to start libinput backend");
+ wlr_session_destroy(multi->session);
+ wlr_backend_destroy(backend);
+ return NULL;
+ }
+ wlr_multi_backend_add(backend, libinput);
+
+ struct wlr_backend *primary_drm = attempt_drm_backend(display, backend,
+ multi->session, create_renderer_func);
+ if (!primary_drm) {
+ wlr_log(WLR_ERROR, "Failed to open any DRM device");
+ wlr_backend_destroy(libinput);
+ wlr_session_destroy(multi->session);
+ wlr_backend_destroy(backend);
+ return NULL;
+ }
+
+ return backend;
+}
diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c
new file mode 100644
index 00000000..fc649d68
--- /dev/null
+++ b/backend/drm/atomic.c
@@ -0,0 +1,273 @@
+#include <gbm.h>
+#include <stdlib.h>
+#include <wlr/util/log.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include "backend/drm/drm.h"
+#include "backend/drm/iface.h"
+#include "backend/drm/util.h"
+
+struct atomic {
+ drmModeAtomicReq *req;
+ int cursor;
+ bool failed;
+};
+
+static void atomic_begin(struct wlr_drm_crtc *crtc, struct atomic *atom) {
+ if (!crtc->atomic) {
+ crtc->atomic = drmModeAtomicAlloc();
+ if (!crtc->atomic) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ atom->failed = true;
+ return;
+ }
+ }
+
+ atom->req = crtc->atomic;
+ atom->cursor = drmModeAtomicGetCursor(atom->req);
+ atom->failed = false;
+}
+
+static bool atomic_end(int drm_fd, struct atomic *atom) {
+ if (atom->failed) {
+ return false;
+ }
+
+ uint32_t flags = DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_NONBLOCK;
+ if (drmModeAtomicCommit(drm_fd, atom->req, flags, NULL)) {
+ wlr_log_errno(WLR_ERROR, "Atomic test failed");
+ drmModeAtomicSetCursor(atom->req, atom->cursor);
+ return false;
+ }
+
+ return true;
+}
+
+static bool atomic_commit(int drm_fd, struct atomic *atom,
+ struct wlr_drm_connector *conn, uint32_t flags, bool modeset) {
+ if (atom->failed) {
+ return false;
+ }
+
+ int ret = drmModeAtomicCommit(drm_fd, atom->req, flags, conn);
+ if (ret) {
+ wlr_log_errno(WLR_ERROR, "%s: Atomic commit failed (%s)",
+ conn->output.name, modeset ? "modeset" : "pageflip");
+
+ // Try to commit without new changes
+ drmModeAtomicSetCursor(atom->req, atom->cursor);
+ if (drmModeAtomicCommit(drm_fd, atom->req, flags, conn)) {
+ wlr_log_errno(WLR_ERROR,
+ "%s: Atomic commit without new changes failed (%s)",
+ conn->output.name, modeset ? "modeset" : "pageflip");
+ }
+ }
+
+ drmModeAtomicSetCursor(atom->req, 0);
+
+ return !ret;
+}
+
+static inline 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;
+ }
+}
+
+static void set_plane_props(struct atomic *atom, struct wlr_drm_plane *plane,
+ uint32_t crtc_id, uint32_t fb_id, bool set_crtc_xy) {
+ uint32_t id = plane->id;
+ const union wlr_drm_plane_props *props = &plane->props;
+
+ // 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)plane->surf.width << 16);
+ atomic_add(atom, id, props->src_h, (uint64_t)plane->surf.height << 16);
+ atomic_add(atom, id, props->crtc_w, plane->surf.width);
+ atomic_add(atom, id, props->crtc_h, plane->surf.height);
+ atomic_add(atom, id, props->fb_id, fb_id);
+ atomic_add(atom, id, props->crtc_id, crtc_id);
+ if (set_crtc_xy) {
+ atomic_add(atom, id, props->crtc_x, 0);
+ atomic_add(atom, id, props->crtc_y, 0);
+ }
+}
+
+static bool atomic_crtc_pageflip(struct wlr_drm_backend *drm,
+ struct wlr_drm_connector *conn,
+ struct wlr_drm_crtc *crtc,
+ uint32_t fb_id, drmModeModeInfo *mode) {
+ if (mode != NULL) {
+ if (crtc->mode_id != 0) {
+ drmModeDestroyPropertyBlob(drm->fd, crtc->mode_id);
+ }
+
+ if (drmModeCreatePropertyBlob(drm->fd, mode, sizeof(*mode),
+ &crtc->mode_id)) {
+ wlr_log_errno(WLR_ERROR, "Unable to create property blob");
+ return false;
+ }
+ }
+
+ uint32_t flags = DRM_MODE_PAGE_FLIP_EVENT;
+ if (mode != NULL) {
+ flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
+ } else {
+ flags |= DRM_MODE_ATOMIC_NONBLOCK;
+ }
+
+ struct atomic atom;
+ atomic_begin(crtc, &atom);
+ atomic_add(&atom, conn->id, conn->props.crtc_id, crtc->id);
+ if (mode != NULL && conn->props.link_status != 0) {
+ atomic_add(&atom, conn->id, conn->props.link_status,
+ DRM_MODE_LINK_STATUS_GOOD);
+ }
+ atomic_add(&atom, crtc->id, crtc->props.mode_id, crtc->mode_id);
+ atomic_add(&atom, crtc->id, crtc->props.active, 1);
+ set_plane_props(&atom, crtc->primary, crtc->id, fb_id, true);
+ return atomic_commit(drm->fd, &atom, conn, flags, mode);
+}
+
+static bool atomic_conn_enable(struct wlr_drm_backend *drm,
+ struct wlr_drm_connector *conn, bool enable) {
+ struct wlr_drm_crtc *crtc = conn->crtc;
+ if (crtc == NULL) {
+ return !enable;
+ }
+
+ struct atomic atom;
+ atomic_begin(crtc, &atom);
+ atomic_add(&atom, crtc->id, crtc->props.active, enable);
+ if (enable) {
+ atomic_add(&atom, conn->id, conn->props.crtc_id, crtc->id);
+ atomic_add(&atom, crtc->id, crtc->props.mode_id, crtc->mode_id);
+ } else {
+ atomic_add(&atom, conn->id, conn->props.crtc_id, 0);
+ atomic_add(&atom, crtc->id, crtc->props.mode_id, 0);
+ }
+ return atomic_commit(drm->fd, &atom, conn, DRM_MODE_ATOMIC_ALLOW_MODESET,
+ true);
+}
+
+bool legacy_crtc_set_cursor(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, struct gbm_bo *bo);
+
+static bool atomic_crtc_set_cursor(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, struct gbm_bo *bo) {
+ if (!crtc || !crtc->cursor) {
+ return true;
+ }
+
+ struct wlr_drm_plane *plane = crtc->cursor;
+ // We can't use atomic operations on fake planes
+ if (plane->id == 0) {
+ return legacy_crtc_set_cursor(drm, crtc, bo);
+ }
+
+ struct atomic atom;
+
+ atomic_begin(crtc, &atom);
+
+ if (bo) {
+ set_plane_props(&atom, plane, crtc->id, get_fb_for_bo(bo), false);
+ } else {
+ atomic_add(&atom, plane->id, plane->props.fb_id, 0);
+ atomic_add(&atom, plane->id, plane->props.crtc_id, 0);
+ }
+
+ return atomic_end(drm->fd, &atom);
+}
+
+bool legacy_crtc_move_cursor(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, int x, int y);
+
+static bool atomic_crtc_move_cursor(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, int x, int y) {
+ if (!crtc || !crtc->cursor) {
+ return true;
+ }
+
+ struct wlr_drm_plane *plane = crtc->cursor;
+ // We can't use atomic operations on fake planes
+ if (plane->id == 0) {
+ return legacy_crtc_move_cursor(drm, crtc, x, y);
+ }
+
+ struct atomic atom;
+
+ atomic_begin(crtc, &atom);
+ atomic_add(&atom, plane->id, plane->props.crtc_x, x);
+ atomic_add(&atom, plane->id, plane->props.crtc_y, y);
+ return atomic_end(drm->fd, &atom);
+}
+
+static bool atomic_crtc_set_gamma(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, size_t size,
+ uint16_t *r, uint16_t *g, uint16_t *b) {
+ // Fallback to legacy gamma interface when gamma properties are not available
+ // (can happen on older Intel GPUs that support gamma but not degamma).
+ // TEMP: This is broken on AMDGPU. Provide a fallback to legacy until they
+ // get it fixed. Ref https://bugs.freedesktop.org/show_bug.cgi?id=107459
+ const char *no_atomic_str = getenv("WLR_DRM_NO_ATOMIC_GAMMA");
+ bool no_atomic = no_atomic_str != NULL && strcmp(no_atomic_str, "1") == 0;
+ if (crtc->props.gamma_lut == 0 || no_atomic) {
+ return legacy_iface.crtc_set_gamma(drm, crtc, size, r, g, b);
+ }
+
+ struct drm_color_lut *gamma = malloc(size * sizeof(struct drm_color_lut));
+ if (gamma == NULL) {
+ wlr_log(WLR_ERROR, "Failed to allocate gamma table");
+ return false;
+ }
+
+ for (size_t i = 0; i < size; i++) {
+ gamma[i].red = r[i];
+ gamma[i].green = g[i];
+ gamma[i].blue = b[i];
+ }
+
+ if (crtc->gamma_lut != 0) {
+ drmModeDestroyPropertyBlob(drm->fd, crtc->gamma_lut);
+ }
+
+ if (drmModeCreatePropertyBlob(drm->fd, gamma,
+ size * sizeof(struct drm_color_lut), &crtc->gamma_lut)) {
+ free(gamma);
+ wlr_log_errno(WLR_ERROR, "Unable to create property blob");
+ return false;
+ }
+ free(gamma);
+
+ struct atomic atom;
+ atomic_begin(crtc, &atom);
+ atomic_add(&atom, crtc->id, crtc->props.gamma_lut, crtc->gamma_lut);
+ return atomic_end(drm->fd, &atom);
+}
+
+static size_t atomic_crtc_get_gamma_size(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc) {
+ if (crtc->props.gamma_lut_size == 0) {
+ return legacy_iface.crtc_get_gamma_size(drm, crtc);
+ }
+
+ 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 (size_t)gamma_lut_size;
+}
+
+const struct wlr_drm_interface atomic_iface = {
+ .conn_enable = atomic_conn_enable,
+ .crtc_pageflip = atomic_crtc_pageflip,
+ .crtc_set_cursor = atomic_crtc_set_cursor,
+ .crtc_move_cursor = atomic_crtc_move_cursor,
+ .crtc_set_gamma = atomic_crtc_set_gamma,
+ .crtc_get_gamma_size = atomic_crtc_get_gamma_size,
+};
diff --git a/backend/drm/backend.c b/backend/drm/backend.c
new file mode 100644
index 00000000..a9082077
--- /dev/null
+++ b/backend/drm/backend.c
@@ -0,0 +1,211 @@
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/backend/interface.h>
+#include <wlr/backend/session.h>
+#include <wlr/interfaces/wlr_output.h>
+#include <wlr/render/egl.h>
+#include <wlr/types/wlr_list.h>
+#include <wlr/util/log.h>
+#include <xf86drm.h>
+#include "backend/drm/drm.h"
+#include "util/signal.h"
+
+struct wlr_drm_backend *get_drm_backend_from_backend(
+ struct wlr_backend *wlr_backend) {
+ assert(wlr_backend_is_drm(wlr_backend));
+ return (struct wlr_drm_backend *)wlr_backend;
+}
+
+static bool backend_start(struct wlr_backend *backend) {
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend);
+ scan_drm_connectors(drm);
+ return true;
+}
+
+static void backend_destroy(struct wlr_backend *backend) {
+ if (!backend) {
+ return;
+ }
+
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend);
+
+ restore_drm_outputs(drm);
+
+ struct wlr_drm_connector *conn, *next;
+ wl_list_for_each_safe(conn, next, &drm->outputs, link) {
+ wlr_output_destroy(&conn->output);
+ }
+
+ wlr_signal_emit_safe(&backend->events.destroy, backend);
+
+ wl_list_remove(&drm->display_destroy.link);
+ wl_list_remove(&drm->session_signal.link);
+ wl_list_remove(&drm->drm_invalidated.link);
+
+ finish_drm_resources(drm);
+ finish_drm_renderer(&drm->renderer);
+ wlr_session_close_file(drm->session, drm->fd);
+ wl_event_source_remove(drm->drm_event);
+ free(drm);
+}
+
+static struct wlr_renderer *backend_get_renderer(
+ struct wlr_backend *backend) {
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend);
+
+ if (drm->parent) {
+ return drm->parent->renderer.wlr_rend;
+ } else {
+ return drm->renderer.wlr_rend;
+ }
+}
+
+static clockid_t backend_get_presentation_clock(struct wlr_backend *backend) {
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend);
+ return drm->clock;
+}
+
+static struct wlr_backend_impl backend_impl = {
+ .start = backend_start,
+ .destroy = backend_destroy,
+ .get_renderer = backend_get_renderer,
+ .get_presentation_clock = backend_get_presentation_clock,
+};
+
+bool wlr_backend_is_drm(struct wlr_backend *b) {
+ return b->impl == &backend_impl;
+}
+
+static void session_signal(struct wl_listener *listener, void *data) {
+ struct wlr_drm_backend *drm =
+ wl_container_of(listener, drm, session_signal);
+ struct wlr_session *session = data;
+
+ if (session->active) {
+ wlr_log(WLR_INFO, "DRM fd resumed");
+ scan_drm_connectors(drm);
+
+ struct wlr_drm_connector *conn;
+ wl_list_for_each(conn, &drm->outputs, link){
+ if (conn->output.enabled) {
+ wlr_output_set_mode(&conn->output, conn->output.current_mode);
+ } else {
+ enable_drm_connector(&conn->output, false);
+ }
+
+ if (!conn->crtc) {
+ continue;
+ }
+
+ struct wlr_drm_plane *plane = conn->crtc->cursor;
+ drm->iface->crtc_set_cursor(drm, conn->crtc,
+ (plane && plane->cursor_enabled) ? plane->cursor_bo : NULL);
+ drm->iface->crtc_move_cursor(drm, conn->crtc, conn->cursor_x,
+ conn->cursor_y);
+
+ if (conn->crtc->gamma_table != NULL) {
+ size_t size = conn->crtc->gamma_table_size;
+ uint16_t *r = conn->crtc->gamma_table;
+ uint16_t *g = conn->crtc->gamma_table + size;
+ uint16_t *b = conn->crtc->gamma_table + 2 * size;
+ drm->iface->crtc_set_gamma(drm, conn->crtc, size, r, g, b);
+ } else {
+ set_drm_connector_gamma(&conn->output, 0, NULL, NULL, NULL);
+ }
+ }
+ } else {
+ wlr_log(WLR_INFO, "DRM fd paused");
+ }
+}
+
+static void drm_invalidated(struct wl_listener *listener, void *data) {
+ struct wlr_drm_backend *drm =
+ wl_container_of(listener, drm, drm_invalidated);
+
+ char *name = drmGetDeviceNameFromFd2(drm->fd);
+ wlr_log(WLR_DEBUG, "%s invalidated", name);
+ free(name);
+
+ scan_drm_connectors(drm);
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_drm_backend *drm =
+ wl_container_of(listener, drm, display_destroy);
+ backend_destroy(&drm->backend);
+}
+
+struct wlr_backend *wlr_drm_backend_create(struct wl_display *display,
+ struct wlr_session *session, int gpu_fd, struct wlr_backend *parent,
+ wlr_renderer_create_func_t create_renderer_func) {
+ assert(display && session && gpu_fd >= 0);
+ assert(!parent || wlr_backend_is_drm(parent));
+
+ char *name = drmGetDeviceNameFromFd2(gpu_fd);
+ drmVersion *version = drmGetVersion(gpu_fd);
+ wlr_log(WLR_INFO, "Initializing DRM backend for %s (%s)", name, version->name);
+ free(name);
+ drmFreeVersion(version);
+
+ struct wlr_drm_backend *drm = calloc(1, sizeof(struct wlr_drm_backend));
+ 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->outputs);
+
+ drm->fd = gpu_fd;
+ if (parent != NULL) {
+ drm->parent = get_drm_backend_from_backend(parent);
+ }
+
+ drm->drm_invalidated.notify = drm_invalidated;
+ wlr_session_signal_add(session, gpu_fd, &drm->drm_invalidated);
+
+ drm->display = display;
+ struct wl_event_loop *event_loop = wl_display_get_event_loop(display);
+
+ drm->drm_event = wl_event_loop_add_fd(event_loop, drm->fd,
+ WL_EVENT_READABLE, handle_drm_event, NULL);
+ if (!drm->drm_event) {
+ wlr_log(WLR_ERROR, "Failed to create DRM event source");
+ goto error_fd;
+ }
+
+ drm->session_signal.notify = session_signal;
+ wl_signal_add(&session->session_signal, &drm->session_signal);
+
+ if (!check_drm_features(drm)) {
+ goto error_event;
+ }
+
+ if (!init_drm_resources(drm)) {
+ goto error_event;
+ }
+
+ if (!init_drm_renderer(drm, &drm->renderer, create_renderer_func)) {
+ wlr_log(WLR_ERROR, "Failed to initialize renderer");
+ goto error_event;
+ }
+
+ drm->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &drm->display_destroy);
+
+ return &drm->backend;
+
+error_event:
+ wl_list_remove(&drm->session_signal.link);
+ wl_event_source_remove(drm->drm_event);
+error_fd:
+ wlr_session_close_file(drm->session, drm->fd);
+ free(drm);
+ return NULL;
+}
diff --git a/backend/drm/drm.c b/backend/drm/drm.c
new file mode 100644
index 00000000..735b7c29
--- /dev/null
+++ b/backend/drm/drm.c
@@ -0,0 +1,1418 @@
+#define _POSIX_C_SOURCE 200112L
+#include <assert.h>
+#include <drm_mode.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <errno.h>
+#include <gbm.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wayland-util.h>
+#include <wlr/backend/interface.h>
+#include <wlr/interfaces/wlr_output.h>
+#include <wlr/render/gles2.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/util/log.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include "backend/drm/drm.h"
+#include "backend/drm/iface.h"
+#include "backend/drm/util.h"
+#include "util/signal.h"
+
+bool check_drm_features(struct wlr_drm_backend *drm) {
+ uint64_t cap;
+ if (drm->parent) {
+ if (drmGetCap(drm->fd, DRM_CAP_PRIME, &cap) ||
+ !(cap & DRM_PRIME_CAP_IMPORT)) {
+ wlr_log(WLR_ERROR,
+ "PRIME import not supported on secondary GPU");
+ return false;
+ }
+
+ 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;
+ }
+
+ const char *no_atomic = getenv("WLR_DRM_NO_ATOMIC");
+ if (no_atomic && strcmp(no_atomic, "1") == 0) {
+ 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;
+ }
+
+ int ret = drmGetCap(drm->fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap);
+ drm->clock = (ret == 0 && cap == 1) ? CLOCK_MONOTONIC : CLOCK_REALTIME;
+
+ return true;
+}
+
+static int cmp_plane(const void *arg1, const void *arg2) {
+ const struct wlr_drm_plane *a = arg1;
+ const struct wlr_drm_plane *b = arg2;
+
+ return (int)a->type - (int)b->type;
+}
+
+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);
+
+ if (plane_res->count_planes == 0) {
+ drmModeFreePlaneResources(plane_res);
+ return true;
+ }
+
+ drm->num_planes = plane_res->count_planes;
+ drm->planes = calloc(drm->num_planes, sizeof(*drm->planes));
+ if (!drm->planes) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ goto error_res;
+ }
+
+ for (size_t i = 0; i < drm->num_planes; ++i) {
+ struct wlr_drm_plane *p = &drm->planes[i];
+
+ drmModePlane *plane = drmModeGetPlane(drm->fd, plane_res->planes[i]);
+ if (!plane) {
+ wlr_log_errno(WLR_ERROR, "Failed to get DRM plane");
+ goto error_planes;
+ }
+
+ p->id = plane->plane_id;
+ p->possible_crtcs = plane->possible_crtcs;
+ uint64_t type;
+
+ if (!get_drm_plane_props(drm->fd, p->id, &p->props) ||
+ !get_drm_prop(drm->fd, p->id, p->props.type, &type)) {
+ drmModeFreePlane(plane);
+ goto error_planes;
+ }
+
+ p->type = type;
+ drm->num_type_planes[type]++;
+
+ drmModeFreePlane(plane);
+ }
+
+ wlr_log(WLR_INFO, "(%zu overlay, %zu primary, %zu cursor)",
+ drm->num_overlay_planes,
+ drm->num_primary_planes,
+ drm->num_cursor_planes);
+
+ qsort(drm->planes, drm->num_planes, sizeof(*drm->planes), cmp_plane);
+
+ drm->overlay_planes = drm->planes;
+ drm->primary_planes = drm->overlay_planes
+ + drm->num_overlay_planes;
+ drm->cursor_planes = drm->primary_planes
+ + drm->num_primary_planes;
+
+ drmModeFreePlaneResources(plane_res);
+ return true;
+
+error_planes:
+ free(drm->planes);
+error_res:
+ 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];
+ crtc->legacy_crtc = drmModeGetCrtc(drm->fd, crtc->id);
+ get_drm_crtc_props(drm->fd, crtc->id, &crtc->props);
+ }
+
+ if (!init_planes(drm)) {
+ goto error_crtcs;
+ }
+
+ drmModeFreeResources(res);
+
+ return true;
+
+error_crtcs:
+ free(drm->crtcs);
+error_res:
+ drmModeFreeResources(res);
+ return false;
+}
+
+void finish_drm_resources(struct wlr_drm_backend *drm) {
+ if (!drm) {
+ return;
+ }
+
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ struct wlr_drm_crtc *crtc = &drm->crtcs[i];
+ drmModeAtomicFree(crtc->atomic);
+ drmModeFreeCrtc(crtc->legacy_crtc);
+ if (crtc->mode_id) {
+ drmModeDestroyPropertyBlob(drm->fd, crtc->mode_id);
+ }
+ if (crtc->gamma_lut) {
+ drmModeDestroyPropertyBlob(drm->fd, crtc->gamma_lut);
+ }
+ free(crtc->gamma_table);
+ }
+ for (size_t i = 0; i < drm->num_planes; ++i) {
+ struct wlr_drm_plane *plane = &drm->planes[i];
+ if (plane->cursor_bo) {
+ gbm_bo_destroy(plane->cursor_bo);
+ }
+ }
+
+ free(drm->crtcs);
+ 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));
+ return (struct wlr_drm_connector *)wlr_output;
+}
+
+static bool drm_connector_make_current(struct wlr_output *output,
+ int *buffer_age) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ return make_drm_surface_current(&conn->crtc->primary->surf, buffer_age);
+}
+
+static bool drm_connector_swap_buffers(struct wlr_output *output,
+ pixman_region32_t *damage) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+ if (!drm->session->active) {
+ return false;
+ }
+
+ struct wlr_drm_crtc *crtc = conn->crtc;
+ if (!crtc) {
+ return false;
+ }
+ struct wlr_drm_plane *plane = crtc->primary;
+
+ struct gbm_bo *bo = swap_drm_surface_buffers(&plane->surf, damage);
+ if (drm->parent) {
+ bo = copy_drm_surface_mgpu(&plane->mgpu_surf, bo);
+ }
+ uint32_t fb_id = get_fb_for_bo(bo);
+
+ if (conn->pageflip_pending) {
+ wlr_log(WLR_ERROR, "Skipping pageflip on output '%s'", conn->output.name);
+ return false;
+ }
+
+ if (!drm->iface->crtc_pageflip(drm, conn, crtc, fb_id, NULL)) {
+ return false;
+ }
+
+ conn->pageflip_pending = true;
+ wlr_output_update_enabled(output, true);
+ return true;
+}
+
+static void fill_empty_gamma_table(size_t size,
+ uint16_t *r, uint16_t *g, uint16_t *b) {
+ for (uint32_t i = 0; i < size; ++i) {
+ uint16_t val = (uint32_t)0xffff * i / (size - 1);
+ r[i] = g[i] = b[i] = val;
+ }
+}
+
+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 = get_drm_backend_from_backend(output->backend);
+
+ if (conn->crtc) {
+ return drm->iface->crtc_get_gamma_size(drm, conn->crtc);
+ }
+
+ return 0;
+}
+
+bool set_drm_connector_gamma(struct wlr_output *output, size_t size,
+ const uint16_t *r, const uint16_t *g, const uint16_t *b) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+
+ if (!conn->crtc) {
+ return false;
+ }
+
+ bool reset = false;
+ if (size == 0) {
+ reset = true;
+ size = drm_connector_get_gamma_size(output);
+ if (size == 0) {
+ return false;
+ }
+ }
+
+ uint16_t *gamma_table = malloc(3 * size * sizeof(uint16_t));
+ if (gamma_table == NULL) {
+ wlr_log(WLR_ERROR, "Failed to allocate gamma table");
+ return false;
+ }
+ uint16_t *_r = gamma_table;
+ uint16_t *_g = gamma_table + size;
+ uint16_t *_b = gamma_table + 2 * size;
+
+ if (reset) {
+ fill_empty_gamma_table(size, _r, _g, _b);
+ } else {
+ memcpy(_r, r, size * sizeof(uint16_t));
+ memcpy(_g, g, size * sizeof(uint16_t));
+ memcpy(_b, b, size * sizeof(uint16_t));
+ }
+
+ bool ok = drm->iface->crtc_set_gamma(drm, conn->crtc, size, _r, _g, _b);
+ if (ok) {
+ wlr_output_update_needs_swap(output);
+
+ free(conn->crtc->gamma_table);
+ conn->crtc->gamma_table = gamma_table;
+ conn->crtc->gamma_table_size = size;
+ } else {
+ free(gamma_table);
+ }
+ return ok;
+}
+
+static bool drm_connector_export_dmabuf(struct wlr_output *output,
+ struct wlr_dmabuf_attributes *attribs) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+
+ if (!drm->session->active) {
+ return false;
+ }
+
+ struct wlr_drm_crtc *crtc = conn->crtc;
+ if (!crtc) {
+ return false;
+ }
+ struct wlr_drm_plane *plane = crtc->primary;
+ struct wlr_drm_surface *surf = &plane->surf;
+
+ return export_drm_bo(surf->back, attribs);
+}
+
+static void drm_connector_start_renderer(struct wlr_drm_connector *conn) {
+ if (conn->state != WLR_DRM_CONN_CONNECTED) {
+ return;
+ }
+
+ wlr_log(WLR_DEBUG, "Starting renderer on output '%s'", conn->output.name);
+
+ struct wlr_drm_backend *drm =
+ get_drm_backend_from_backend(conn->output.backend);
+ struct wlr_drm_crtc *crtc = conn->crtc;
+ if (!crtc) {
+ return;
+ }
+ struct wlr_drm_plane *plane = crtc->primary;
+
+ struct gbm_bo *bo = get_drm_surface_front(
+ drm->parent ? &plane->mgpu_surf : &plane->surf);
+ uint32_t fb_id = get_fb_for_bo(bo);
+
+ struct wlr_drm_mode *mode = (struct wlr_drm_mode *)conn->output.current_mode;
+ if (drm->iface->crtc_pageflip(drm, conn, crtc, fb_id, &mode->drm_mode)) {
+ conn->pageflip_pending = true;
+ wlr_output_update_enabled(&conn->output, true);
+ } else {
+ wl_event_source_timer_update(conn->retry_pageflip,
+ 1000000.0f / conn->output.current_mode->refresh);
+ }
+}
+
+static bool drm_connector_set_mode(struct wlr_output *output,
+ struct wlr_output_mode *mode);
+
+static void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs);
+
+static void attempt_enable_needs_modeset(struct wlr_drm_backend *drm) {
+ // Try to modeset any output that has a desired mode and a CRTC (ie. was
+ // lacking a CRTC on last modeset)
+ struct wlr_drm_connector *conn;
+ wl_list_for_each(conn, &drm->outputs, link) {
+ if (conn->state == WLR_DRM_CONN_NEEDS_MODESET &&
+ conn->crtc != NULL && conn->desired_mode != NULL &&
+ conn->desired_enabled) {
+ drm_connector_set_mode(&conn->output, conn->desired_mode);
+ }
+ }
+}
+
+bool enable_drm_connector(struct wlr_output *output, bool enable) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+ if (conn->state != WLR_DRM_CONN_CONNECTED
+ && conn->state != WLR_DRM_CONN_NEEDS_MODESET) {
+ return false;
+ }
+
+ conn->desired_enabled = enable;
+
+ if (enable && conn->crtc == NULL) {
+ // Maybe we can steal a CRTC from a disabled output
+ realloc_crtcs(drm, NULL);
+ }
+
+ bool ok = drm->iface->conn_enable(drm, conn, enable);
+ if (!ok) {
+ return false;
+ }
+
+ if (enable) {
+ drm_connector_start_renderer(conn);
+ } else {
+ realloc_crtcs(drm, NULL);
+
+ attempt_enable_needs_modeset(drm);
+ }
+
+ wlr_output_update_enabled(&conn->output, enable);
+ return true;
+}
+
+static ssize_t connector_index_from_crtc(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc) {
+ size_t i = 0;
+ struct wlr_drm_connector *conn;
+ wl_list_for_each(conn, &drm->outputs, link) {
+ if (conn->crtc == crtc) {
+ return i;
+ }
+ ++i;
+ }
+ return -1;
+}
+
+static void realloc_planes(struct wlr_drm_backend *drm, const uint32_t *crtc_in,
+ bool *changed_outputs) {
+ wlr_log(WLR_DEBUG, "Reallocating planes");
+
+ // overlay, primary, cursor
+ for (size_t type = 0; type < 3; ++type) {
+ if (drm->num_type_planes[type] == 0) {
+ continue;
+ }
+
+ uint32_t possible[drm->num_type_planes[type] + 1];
+ uint32_t crtc[drm->num_crtcs + 1];
+ uint32_t crtc_res[drm->num_crtcs + 1];
+
+ for (size_t i = 0; i < drm->num_type_planes[type]; ++i) {
+ possible[i] = drm->type_planes[type][i].possible_crtcs;
+ }
+
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ if (crtc_in[i] == UNMATCHED) {
+ crtc[i] = SKIP;
+ } else if (drm->crtcs[i].planes[type]) {
+ crtc[i] = drm->crtcs[i].planes[type]
+ - drm->type_planes[type];
+ } else {
+ crtc[i] = UNMATCHED;
+ }
+ }
+
+ match_obj(drm->num_type_planes[type], possible,
+ drm->num_crtcs, crtc, crtc_res);
+
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ if (crtc_res[i] == UNMATCHED || crtc_res[i] == SKIP) {
+ continue;
+ }
+
+ struct wlr_drm_crtc *c = &drm->crtcs[i];
+ struct wlr_drm_plane **old = &c->planes[type];
+ struct wlr_drm_plane *new = &drm->type_planes[type][crtc_res[i]];
+
+ if (*old != new) {
+ wlr_log(WLR_DEBUG,
+ "Assigning plane %d -> %d (type %zu) to CRTC %d",
+ *old ? (int)(*old)->id : -1,
+ new ? (int)new->id : -1,
+ type,
+ c->id);
+
+ ssize_t conn_idx = connector_index_from_crtc(drm, c);
+ if (conn_idx >= 0) {
+ changed_outputs[conn_idx] = true;
+ }
+ if (*old) {
+ finish_drm_surface(&(*old)->surf);
+ }
+ finish_drm_surface(&new->surf);
+ *old = new;
+ }
+ }
+ }
+}
+
+static void drm_connector_cleanup(struct wlr_drm_connector *conn);
+
+static bool drm_connector_set_mode(struct wlr_output *output,
+ struct wlr_output_mode *mode) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+ if (conn->crtc == NULL) {
+ // Maybe we can steal a CRTC from a disabled output
+ realloc_crtcs(drm, NULL);
+ }
+ if (conn->crtc == NULL) {
+ wlr_log(WLR_ERROR, "Cannot modeset '%s': no CRTC for this connector",
+ conn->output.name);
+ // Save the desired mode for later, when we'll get a proper CRTC
+ conn->desired_mode = mode;
+ return false;
+ }
+
+ wlr_log(WLR_INFO, "Modesetting '%s' with '%ux%u@%u mHz'",
+ conn->output.name, mode->width, mode->height, mode->refresh);
+
+ if (!init_drm_plane_surfaces(conn->crtc->primary, drm,
+ mode->width, mode->height, GBM_FORMAT_XRGB8888)) {
+ wlr_log(WLR_ERROR, "Failed to initialize renderer for plane");
+ return false;
+ }
+
+ conn->state = WLR_DRM_CONN_CONNECTED;
+ conn->desired_mode = NULL;
+ wlr_output_update_mode(&conn->output, mode);
+ wlr_output_update_enabled(&conn->output, true);
+ conn->desired_enabled = true;
+
+ drm_connector_start_renderer(conn);
+
+ // When switching VTs, the mode is not updated but the buffers become
+ // invalid, so we need to manually damage the output here
+ wlr_output_damage_whole(&conn->output);
+
+ return true;
+}
+
+bool 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 false;
+ }
+
+ struct wlr_output_mode *wlr_mode;
+ wl_list_for_each(wlr_mode, &conn->output.modes, link) {
+ struct wlr_drm_mode *mode = (struct wlr_drm_mode *)wlr_mode;
+ if (memcmp(&mode->drm_mode, modeinfo, sizeof(*modeinfo)) == 0) {
+ return true;
+ }
+ }
+
+ struct wlr_drm_mode *mode = calloc(1, sizeof(*mode));
+ if (!mode) {
+ return false;
+ }
+ memcpy(&mode->drm_mode, modeinfo, sizeof(*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);
+
+ wlr_log(WLR_INFO, "Registered custom mode "
+ "%"PRId32"x%"PRId32"@%"PRId32,
+ mode->wlr_mode.width, mode->wlr_mode.height,
+ mode->wlr_mode.refresh);
+ wl_list_insert(&conn->output.modes, &mode->wlr_mode.link);
+ return true;
+}
+
+static void drm_connector_transform(struct wlr_output *output,
+ enum wl_output_transform transform) {
+ output->transform = transform;
+}
+
+static bool drm_connector_set_cursor(struct wlr_output *output,
+ struct wlr_texture *texture, int32_t scale,
+ enum wl_output_transform transform,
+ int32_t hotspot_x, int32_t hotspot_y, bool update_texture) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+
+ struct wlr_drm_crtc *crtc = conn->crtc;
+ if (!crtc) {
+ return false;
+ }
+
+ struct wlr_drm_plane *plane = crtc->cursor;
+ if (!plane) {
+ // We don't have a real cursor plane, so we make a fake one
+ plane = calloc(1, sizeof(*plane));
+ if (!plane) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ return false;
+ }
+ crtc->cursor = plane;
+ }
+
+ if (!plane->surf.gbm) {
+ int ret;
+ uint64_t w, h;
+ ret = drmGetCap(drm->fd, DRM_CAP_CURSOR_WIDTH, &w);
+ w = ret ? 64 : w;
+ ret = drmGetCap(drm->fd, DRM_CAP_CURSOR_HEIGHT, &h);
+ h = ret ? 64 : h;
+
+ struct wlr_drm_renderer *renderer =
+ drm->parent ? &drm->parent->renderer : &drm->renderer;
+
+ if (!init_drm_surface(&plane->surf, renderer, w, h,
+ GBM_FORMAT_ARGB8888, 0)) {
+ wlr_log(WLR_ERROR, "Cannot allocate cursor resources");
+ return false;
+ }
+
+ plane->cursor_bo = gbm_bo_create(drm->renderer.gbm, w, h,
+ GBM_FORMAT_ARGB8888, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE);
+ if (!plane->cursor_bo) {
+ wlr_log_errno(WLR_ERROR, "Failed to create cursor bo");
+ return false;
+ }
+ }
+
+ wlr_matrix_projection(plane->matrix, plane->surf.width,
+ plane->surf.height, output->transform);
+
+ struct wlr_box hotspot = { .x = hotspot_x, .y = hotspot_y };
+ wlr_box_transform(&hotspot, &hotspot,
+ wlr_output_transform_invert(output->transform),
+ plane->surf.width, plane->surf.height);
+
+ if (plane->cursor_hotspot_x != hotspot.x ||
+ plane->cursor_hotspot_y != hotspot.y) {
+ // Update cursor hotspot
+ conn->cursor_x -= hotspot.x - plane->cursor_hotspot_x;
+ conn->cursor_y -= hotspot.y - plane->cursor_hotspot_y;
+ plane->cursor_hotspot_x = hotspot.x;
+ plane->cursor_hotspot_y = hotspot.y;
+
+ if (!drm->iface->crtc_move_cursor(drm, conn->crtc, conn->cursor_x,
+ conn->cursor_y)) {
+ return false;
+ }
+
+ wlr_output_update_needs_swap(output);
+ }
+
+ if (!update_texture) {
+ // Don't update cursor image
+ return true;
+ }
+
+ plane->cursor_enabled = false;
+ if (texture != NULL) {
+ int width, height;
+ wlr_texture_get_size(texture, &width, &height);
+ width = width * output->scale / scale;
+ height = height * output->scale / scale;
+
+ if (width > (int)plane->surf.width || height > (int)plane->surf.height) {
+ wlr_log(WLR_ERROR, "Cursor too large (max %dx%d)",
+ (int)plane->surf.width, (int)plane->surf.height);
+ return false;
+ }
+
+ uint32_t bo_width = gbm_bo_get_width(plane->cursor_bo);
+ uint32_t bo_height = gbm_bo_get_height(plane->cursor_bo);
+
+ uint32_t bo_stride;
+ void *bo_data;
+ if (!gbm_bo_map(plane->cursor_bo, 0, 0, bo_width, bo_height,
+ GBM_BO_TRANSFER_WRITE, &bo_stride, &bo_data)) {
+ wlr_log_errno(WLR_ERROR, "Unable to map buffer");
+ return false;
+ }
+
+ make_drm_surface_current(&plane->surf, NULL);
+
+ struct wlr_renderer *rend = plane->surf.renderer->wlr_rend;
+
+ struct wlr_box cursor_box = { .width = width, .height = height };
+
+ float matrix[9];
+ wlr_matrix_project_box(matrix, &cursor_box, transform, 0, plane->matrix);
+
+ wlr_renderer_begin(rend, plane->surf.width, plane->surf.height);
+ wlr_renderer_clear(rend, (float[]){ 0.0, 0.0, 0.0, 0.0 });
+ wlr_render_texture_with_matrix(rend, texture, matrix, 1.0);
+ wlr_renderer_end(rend);
+
+ wlr_renderer_read_pixels(rend, WL_SHM_FORMAT_ARGB8888, NULL, bo_stride,
+ plane->surf.width, plane->surf.height, 0, 0, 0, 0, bo_data);
+
+ swap_drm_surface_buffers(&plane->surf, NULL);
+
+ gbm_bo_unmap(plane->cursor_bo, bo_data);
+
+ plane->cursor_enabled = true;
+ }
+
+ if (!drm->session->active) {
+ return true; // will be committed when session is resumed
+ }
+
+ struct gbm_bo *bo = plane->cursor_enabled ? plane->cursor_bo : NULL;
+ bool ok = drm->iface->crtc_set_cursor(drm, crtc, bo);
+ if (ok) {
+ wlr_output_update_needs_swap(output);
+ }
+ return ok;
+}
+
+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);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+ if (!conn->crtc) {
+ return false;
+ }
+ struct wlr_drm_plane *plane = conn->crtc->cursor;
+
+ 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);
+
+ if (plane != NULL) {
+ box.x -= plane->cursor_hotspot_x;
+ box.y -= plane->cursor_hotspot_y;
+ }
+
+ conn->cursor_x = box.x;
+ conn->cursor_y = box.y;
+
+ if (!drm->session->active) {
+ return true; // will be committed when session is resumed
+ }
+
+ bool ok = drm->iface->crtc_move_cursor(drm, conn->crtc, box.x, box.y);
+ if (ok) {
+ wlr_output_update_needs_swap(output);
+ }
+ return ok;
+}
+
+static bool drm_connector_schedule_frame(struct wlr_output *output) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+ if (!drm->session->active) {
+ return false;
+ }
+
+ // We need to figure out where we are in the vblank cycle
+ // TODO: try using drmWaitVBlank and fallback to pageflipping
+
+ struct wlr_drm_crtc *crtc = conn->crtc;
+ if (!crtc) {
+ return false;
+ }
+ struct wlr_drm_plane *plane = crtc->primary;
+ struct gbm_bo *bo = plane->surf.back;
+ if (!bo) {
+ // We haven't swapped buffers yet -- can't do a pageflip
+ wlr_output_send_frame(output);
+ return true;
+ }
+ if (drm->parent) {
+ bo = copy_drm_surface_mgpu(&plane->mgpu_surf, bo);
+ }
+ uint32_t fb_id = get_fb_for_bo(bo);
+
+ if (conn->pageflip_pending) {
+ wlr_log(WLR_ERROR, "Skipping pageflip on output '%s'",
+ conn->output.name);
+ return true;
+ }
+
+ if (!drm->iface->crtc_pageflip(drm, conn, crtc, fb_id, NULL)) {
+ return false;
+ }
+
+ conn->pageflip_pending = true;
+ wlr_output_update_enabled(output, true);
+ return true;
+}
+
+static void drm_connector_destroy(struct wlr_output *output) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ drm_connector_cleanup(conn);
+ drmModeFreeCrtc(conn->old_crtc);
+ wl_event_source_remove(conn->retry_pageflip);
+ wl_list_remove(&conn->link);
+ free(conn);
+}
+
+static const struct wlr_output_impl output_impl = {
+ .enable = enable_drm_connector,
+ .set_mode = drm_connector_set_mode,
+ .transform = drm_connector_transform,
+ .set_cursor = drm_connector_set_cursor,
+ .move_cursor = drm_connector_move_cursor,
+ .destroy = drm_connector_destroy,
+ .make_current = drm_connector_make_current,
+ .swap_buffers = drm_connector_swap_buffers,
+ .set_gamma = set_drm_connector_gamma,
+ .get_gamma_size = drm_connector_get_gamma_size,
+ .export_dmabuf = drm_connector_export_dmabuf,
+ .schedule_frame = drm_connector_schedule_frame,
+};
+
+bool wlr_output_is_drm(struct wlr_output *output) {
+ return output->impl == &output_impl;
+}
+
+static int retry_pageflip(void *data) {
+ struct wlr_drm_connector *conn = data;
+ wlr_log(WLR_INFO, "%s: Retrying pageflip", conn->output.name);
+ drm_connector_start_renderer(conn);
+ return 0;
+}
+
+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) {
+ struct wlr_drm_backend *drm =
+ get_drm_backend_from_backend(conn->output.backend);
+ if (conn->crtc == NULL) {
+ return;
+ }
+
+ wlr_log(WLR_DEBUG, "De-allocating CRTC %zu for output '%s'",
+ conn->crtc - drm->crtcs, conn->output.name);
+
+ set_drm_connector_gamma(&conn->output, 0, NULL, NULL, NULL);
+
+ for (size_t type = 0; type < 3; ++type) {
+ struct wlr_drm_plane *plane = conn->crtc->planes[type];
+ if (plane == NULL) {
+ continue;
+ }
+
+ finish_drm_surface(&plane->surf);
+ conn->crtc->planes[type] = NULL;
+ }
+
+ drm->iface->conn_enable(drm, conn, false);
+
+ conn->crtc = NULL;
+}
+
+static void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs) {
+ size_t num_outputs = wl_list_length(&drm->outputs);
+
+ if (changed_outputs == NULL) {
+ changed_outputs = calloc(num_outputs, sizeof(bool));
+ if (changed_outputs == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed");
+ return;
+ }
+ }
+
+ wlr_log(WLR_DEBUG, "Reallocating CRTCs");
+
+ uint32_t crtc[drm->num_crtcs + 1];
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ crtc[i] = UNMATCHED;
+ }
+
+ struct wlr_drm_connector *connectors[num_outputs + 1];
+
+ uint32_t possible_crtc[num_outputs + 1];
+ memset(possible_crtc, 0, sizeof(possible_crtc));
+
+ wlr_log(WLR_DEBUG, "State before reallocation:");
+ ssize_t i = -1;
+ struct wlr_drm_connector *conn;
+ wl_list_for_each(conn, &drm->outputs, link) {
+ i++;
+ connectors[i] = conn;
+
+ wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d desired_enabled=%d",
+ conn->output.name,
+ conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1,
+ conn->state, conn->desired_enabled);
+
+ if (conn->crtc) {
+ crtc[conn->crtc - drm->crtcs] = i;
+ }
+
+ // Only search CRTCs for user-enabled outputs (that are already
+ // connected or in need of a modeset)
+ if ((conn->state == WLR_DRM_CONN_CONNECTED ||
+ conn->state == WLR_DRM_CONN_NEEDS_MODESET) &&
+ conn->desired_enabled) {
+ possible_crtc[i] = conn->possible_crtc;
+ }
+ }
+
+ uint32_t crtc_res[drm->num_crtcs + 1];
+ match_obj(wl_list_length(&drm->outputs), possible_crtc,
+ drm->num_crtcs, crtc, crtc_res);
+
+ bool matched[num_outputs + 1];
+ memset(matched, false, sizeof(matched));
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ if (crtc_res[i] != UNMATCHED) {
+ matched[crtc_res[i]] = true;
+ }
+ }
+
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ // We don't want any of the current monitors to be deactivated
+ if (crtc[i] != UNMATCHED && !matched[crtc[i]] &&
+ connectors[crtc[i]]->desired_enabled) {
+ wlr_log(WLR_DEBUG, "Could not match a CRTC for connected output %d",
+ crtc[i]);
+ return;
+ }
+ }
+
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ if (crtc_res[i] == crtc[i]) {
+ continue;
+ }
+
+ // De-allocate this CRTC on previous output
+ if (crtc[i] != UNMATCHED) {
+ changed_outputs[crtc[i]] = true;
+ dealloc_crtc(connectors[crtc[i]]);
+ }
+
+ // Assign this CRTC to next output
+ if (crtc_res[i] != UNMATCHED) {
+ changed_outputs[crtc_res[i]] = true;
+
+ struct wlr_drm_connector *conn = connectors[crtc_res[i]];
+ dealloc_crtc(conn);
+ conn->crtc = &drm->crtcs[i];
+
+ wlr_log(WLR_DEBUG, "Assigning CRTC %zu to output %d -> %d '%s'",
+ i, crtc[i], crtc_res[i], conn->output.name);
+ }
+ }
+
+ wlr_log(WLR_DEBUG, "State after reallocation:");
+ wl_list_for_each(conn, &drm->outputs, link) {
+ wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d desired_enabled=%d",
+ conn->output.name,
+ conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1,
+ conn->state, conn->desired_enabled);
+ }
+
+ realloc_planes(drm, crtc_res, changed_outputs);
+
+ // We need to reinitialize any plane that has changed
+ i = -1;
+ wl_list_for_each(conn, &drm->outputs, link) {
+ i++;
+ struct wlr_output_mode *mode = conn->output.current_mode;
+
+ if (conn->state != WLR_DRM_CONN_CONNECTED || !changed_outputs[i]
+ || conn->crtc == NULL) {
+ continue;
+ }
+
+ if (!init_drm_plane_surfaces(conn->crtc->primary, drm,
+ mode->width, mode->height, GBM_FORMAT_XRGB8888)) {
+ wlr_log(WLR_ERROR, "Failed to initialize renderer for plane");
+ drm_connector_cleanup(conn);
+ break;
+ }
+
+ drm_connector_start_renderer(conn);
+
+ wlr_output_damage_whole(&conn->output);
+ }
+}
+
+static uint32_t get_possible_crtcs(int fd, drmModeRes *res,
+ drmModeConnector *conn, bool is_mst) {
+ uint32_t ret = 0;
+
+ for (int i = 0; i < conn->count_encoders; ++i) {
+ drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoders[i]);
+ if (!enc) {
+ continue;
+ }
+
+ ret |= enc->possible_crtcs;
+
+ drmModeFreeEncoder(enc);
+ }
+
+ // Sometimes DP MST connectors report no encoders, so we'll loop though
+ // all of the encoders of the MST type instead.
+ // TODO: See if there is a better solution.
+
+ if (!is_mst || ret) {
+ return ret;
+ }
+
+ for (int i = 0; i < res->count_encoders; ++i) {
+ drmModeEncoder *enc = drmModeGetEncoder(fd, res->encoders[i]);
+ if (!enc) {
+ continue;
+ }
+
+ if (enc->encoder_type == DRM_MODE_ENCODER_DPMST) {
+ ret |= enc->possible_crtcs;
+ }
+
+ drmModeFreeEncoder(enc);
+ }
+
+ return ret;
+}
+
+void scan_drm_connectors(struct wlr_drm_backend *drm) {
+ wlr_log(WLR_INFO, "Scanning DRM connectors");
+
+ 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->outputs);
+ // +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) {
+ drmModeConnector *drm_conn = drmModeGetConnector(drm->fd,
+ res->connectors[i]);
+ if (!drm_conn) {
+ wlr_log_errno(WLR_ERROR, "Failed to get DRM connector");
+ continue;
+ }
+ drmModeEncoder *curr_enc = drmModeGetEncoder(drm->fd,
+ drm_conn->encoder_id);
+
+ ssize_t index = -1;
+ struct wlr_drm_connector *c, *wlr_conn = NULL;
+ wl_list_for_each(c, &drm->outputs, link) {
+ index++;
+ if (c->id == drm_conn->connector_id) {
+ wlr_conn = c;
+ break;
+ }
+ }
+
+ if (!wlr_conn) {
+ wlr_conn = calloc(1, sizeof(*wlr_conn));
+ if (!wlr_conn) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ drmModeFreeEncoder(curr_enc);
+ drmModeFreeConnector(drm_conn);
+ continue;
+ }
+ wlr_output_init(&wlr_conn->output, &drm->backend, &output_impl,
+ drm->display);
+
+ struct wl_event_loop *ev = wl_display_get_event_loop(drm->display);
+ wlr_conn->retry_pageflip = wl_event_loop_add_timer(ev, retry_pageflip,
+ wlr_conn);
+
+ wlr_conn->state = WLR_DRM_CONN_DISCONNECTED;
+ wlr_conn->id = drm_conn->connector_id;
+
+ snprintf(wlr_conn->output.name, sizeof(wlr_conn->output.name),
+ "%s-%"PRIu32, conn_get_name(drm_conn->connector_type),
+ drm_conn->connector_type_id);
+
+ if (curr_enc) {
+ wlr_conn->old_crtc = drmModeGetCrtc(drm->fd, curr_enc->crtc_id);
+ }
+
+ wl_list_insert(drm->outputs.prev, &wlr_conn->link);
+ wlr_log(WLR_INFO, "Found connector '%s'", wlr_conn->output.name);
+ } else {
+ seen[index] = true;
+ }
+
+ if (curr_enc) {
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ if (drm->crtcs[i].id == curr_enc->crtc_id) {
+ wlr_conn->crtc = &drm->crtcs[i];
+ break;
+ }
+ }
+ } else {
+ wlr_conn->crtc = NULL;
+ }
+
+ // 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_log(WLR_ERROR, "Failed to get link status for '%s'",
+ wlr_conn->output.name);
+ continue;
+ }
+
+ if (link_status == DRM_MODE_LINK_STATUS_BAD) {
+ // We need to reload our list of modes and force a modeset
+ wlr_log(WLR_INFO, "Bad link for '%s'", wlr_conn->output.name);
+ drm_connector_cleanup(wlr_conn);
+ }
+ }
+
+ if (wlr_conn->state == WLR_DRM_CONN_DISCONNECTED &&
+ drm_conn->connection == DRM_MODE_CONNECTED) {
+ wlr_log(WLR_INFO, "'%s' connected", wlr_conn->output.name);
+ wlr_log(WLR_DEBUG, "Current CRTC: %d",
+ wlr_conn->crtc ? (int)wlr_conn->crtc->id : -1);
+
+ wlr_conn->output.phys_width = drm_conn->mmWidth;
+ wlr_conn->output.phys_height = drm_conn->mmHeight;
+ wlr_log(WLR_INFO, "Physical size: %"PRId32"x%"PRId32,
+ wlr_conn->output.phys_width, wlr_conn->output.phys_height);
+ wlr_conn->output.subpixel = subpixel_map[drm_conn->subpixel];
+
+ get_drm_connector_props(drm->fd, wlr_conn->id, &wlr_conn->props);
+
+ 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->output, edid_len, edid);
+ free(edid);
+
+ wlr_log(WLR_INFO, "Detected modes:");
+
+ for (int i = 0; i < drm_conn->count_modes; ++i) {
+ struct wlr_drm_mode *mode = calloc(1, sizeof(*mode));
+ if (!mode) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ continue;
+ }
+
+ if (drm_conn->modes[i].flags & DRM_MODE_FLAG_INTERLACE) {
+ free(mode);
+ continue;
+ }
+
+ mode->drm_mode = drm_conn->modes[i];
+ mode->wlr_mode.width = mode->drm_mode.hdisplay;
+ mode->wlr_mode.height = mode->drm_mode.vdisplay;
+ mode->wlr_mode.refresh = calculate_refresh_rate(&mode->drm_mode);
+
+ wlr_log(WLR_INFO, " %"PRId32"x%"PRId32"@%"PRId32,
+ mode->wlr_mode.width, mode->wlr_mode.height,
+ mode->wlr_mode.refresh);
+
+ wl_list_insert(&wlr_conn->output.modes, &mode->wlr_mode.link);
+ }
+
+ wlr_conn->possible_crtc = get_possible_crtcs(drm->fd, res, drm_conn,
+ wlr_conn->props.path != 0);
+ if (wlr_conn->possible_crtc == 0) {
+ wlr_log(WLR_ERROR, "No CRTC possible for connector '%s'",
+ wlr_conn->output.name);
+ }
+
+ wlr_output_update_enabled(&wlr_conn->output, wlr_conn->crtc != NULL);
+ wlr_conn->desired_enabled = true;
+
+ wlr_conn->state = WLR_DRM_CONN_NEEDS_MODESET;
+ new_outputs[new_outputs_len++] = wlr_conn;
+ } else if ((wlr_conn->state == WLR_DRM_CONN_CONNECTED ||
+ wlr_conn->state == WLR_DRM_CONN_NEEDS_MODESET) &&
+ drm_conn->connection != DRM_MODE_CONNECTED) {
+ wlr_log(WLR_INFO, "'%s' disconnected", wlr_conn->output.name);
+
+ drm_connector_cleanup(wlr_conn);
+ }
+
+ drmModeFreeEncoder(curr_enc);
+ 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->outputs);
+ wl_list_for_each_reverse_safe(conn, tmp_conn, &drm->outputs, link) {
+ index--;
+ if (index >= seen_len || seen[index]) {
+ continue;
+ }
+
+ wlr_log(WLR_INFO, "'%s' disappeared", conn->output.name);
+ drm_connector_cleanup(conn);
+
+ if (conn->pageflip_pending) {
+ conn->state = WLR_DRM_CONN_DISAPPEARED;
+ } else {
+ wlr_output_destroy(&conn->output);
+ }
+ }
+
+ bool changed_outputs[wl_list_length(&drm->outputs) + 1];
+ memset(changed_outputs, false, sizeof(changed_outputs));
+ for (size_t i = 0; i < new_outputs_len; ++i) {
+ struct wlr_drm_connector *conn = new_outputs[i];
+
+ ssize_t pos = -1;
+ struct wlr_drm_connector *c;
+ wl_list_for_each(c, &drm->outputs, link) {
+ ++pos;
+ if (c == conn) {
+ break;
+ }
+ }
+ assert(pos >= 0);
+
+ changed_outputs[pos] = true;
+ }
+
+ realloc_crtcs(drm, changed_outputs);
+
+ for (size_t i = 0; i < new_outputs_len; ++i) {
+ struct wlr_drm_connector *conn = new_outputs[i];
+
+ wlr_log(WLR_INFO, "Requesting modeset for '%s'",
+ conn->output.name);
+ wlr_signal_emit_safe(&drm->backend.events.new_output,
+ &conn->output);
+ }
+
+ attempt_enable_needs_modeset(drm);
+}
+
+static int mhz_to_nsec(int mhz) {
+ return 1000000000000LL / mhz;
+}
+
+static void page_flip_handler(int fd, unsigned seq,
+ unsigned tv_sec, unsigned tv_usec, void *data) {
+ struct wlr_drm_connector *conn = data;
+ struct wlr_drm_backend *drm =
+ get_drm_backend_from_backend(conn->output.backend);
+
+ conn->pageflip_pending = false;
+
+ if (conn->state == WLR_DRM_CONN_DISAPPEARED) {
+ wlr_output_destroy(&conn->output);
+ return;
+ }
+
+ if (conn->state != WLR_DRM_CONN_CONNECTED || conn->crtc == NULL) {
+ return;
+ }
+
+ post_drm_surface(&conn->crtc->primary->surf);
+ if (drm->parent) {
+ post_drm_surface(&conn->crtc->primary->mgpu_surf);
+ }
+
+ struct timespec present_time = {
+ .tv_sec = tv_sec,
+ .tv_nsec = tv_usec * 1000,
+ };
+ struct wlr_output_event_present present_event = {
+ .when = &present_time,
+ .seq = seq,
+ .refresh = mhz_to_nsec(conn->output.refresh),
+ .flags = WLR_OUTPUT_PRESENT_VSYNC | WLR_OUTPUT_PRESENT_HW_CLOCK |
+ WLR_OUTPUT_PRESENT_HW_COMPLETION,
+ };
+ 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) {
+ drmEventContext event = {
+ .version = 2,
+ .page_flip_handler = page_flip_handler,
+ };
+
+ drmHandleEvent(fd, &event);
+ return 1;
+}
+
+void restore_drm_outputs(struct wlr_drm_backend *drm) {
+ uint64_t to_close = (1L << wl_list_length(&drm->outputs)) - 1;
+
+ struct wlr_drm_connector *conn;
+ wl_list_for_each(conn, &drm->outputs, link) {
+ if (conn->state == WLR_DRM_CONN_CONNECTED) {
+ conn->state = WLR_DRM_CONN_CLEANUP;
+ }
+ }
+
+ time_t timeout = time(NULL) + 5;
+
+ while (to_close && time(NULL) < timeout) {
+ handle_drm_event(drm->fd, 0, NULL);
+ size_t i = 0;
+ struct wlr_drm_connector *conn;
+ wl_list_for_each(conn, &drm->outputs, link) {
+ if (conn->state != WLR_DRM_CONN_CLEANUP || !conn->pageflip_pending) {
+ to_close &= ~(1 << i);
+ }
+ i++;
+ }
+ }
+
+ if (to_close) {
+ wlr_log(WLR_ERROR, "Timed out stopping output renderers");
+ }
+
+ wl_list_for_each(conn, &drm->outputs, link) {
+ drmModeCrtc *crtc = conn->old_crtc;
+ if (!crtc) {
+ continue;
+ }
+
+ drmModeSetCrtc(drm->fd, crtc->crtc_id, crtc->buffer_id, crtc->x, crtc->y,
+ &conn->id, 1, &crtc->mode);
+ }
+}
+
+static void drm_connector_cleanup(struct wlr_drm_connector *conn) {
+ if (!conn) {
+ return;
+ }
+
+ switch (conn->state) {
+ case WLR_DRM_CONN_CONNECTED:
+ case WLR_DRM_CONN_CLEANUP:;
+ struct wlr_drm_crtc *crtc = conn->crtc;
+ if (crtc != NULL) {
+ for (int i = 0; i < 3; ++i) {
+ if (!crtc->planes[i]) {
+ continue;
+ }
+
+ finish_drm_surface(&crtc->planes[i]->surf);
+ finish_drm_surface(&crtc->planes[i]->mgpu_surf);
+ if (crtc->planes[i]->id == 0) {
+ free(crtc->planes[i]);
+ crtc->planes[i] = NULL;
+ }
+ }
+ }
+
+ conn->output.current_mode = NULL;
+ conn->desired_mode = NULL;
+ struct wlr_drm_mode *mode, *tmp;
+ wl_list_for_each_safe(mode, tmp, &conn->output.modes, wlr_mode.link) {
+ wl_list_remove(&mode->wlr_mode.link);
+ free(mode);
+ }
+
+ conn->output.enabled = false;
+ conn->output.width = conn->output.height = conn->output.refresh = 0;
+
+ memset(&conn->output.make, 0, sizeof(conn->output.make));
+ memset(&conn->output.model, 0, sizeof(conn->output.model));
+ memset(&conn->output.serial, 0, sizeof(conn->output.serial));
+
+ if (conn->output.idle_frame != NULL) {
+ wl_event_source_remove(conn->output.idle_frame);
+ conn->output.idle_frame = NULL;
+ }
+ conn->output.needs_swap = false;
+ conn->output.frame_pending = false;
+
+ /* Fallthrough */
+ case WLR_DRM_CONN_NEEDS_MODESET:
+ wlr_log(WLR_INFO, "Emitting destruction signal for '%s'",
+ conn->output.name);
+ dealloc_crtc(conn);
+ conn->possible_crtc = 0;
+ conn->desired_mode = NULL;
+ wlr_signal_emit_safe(&conn->output.events.destroy, &conn->output);
+ break;
+ case WLR_DRM_CONN_DISCONNECTED:
+ break;
+ case WLR_DRM_CONN_DISAPPEARED:
+ return; // don't change state
+ }
+
+ conn->state = WLR_DRM_CONN_DISCONNECTED;
+}
diff --git a/backend/drm/legacy.c b/backend/drm/legacy.c
new file mode 100644
index 00000000..182c7a95
--- /dev/null
+++ b/backend/drm/legacy.c
@@ -0,0 +1,83 @@
+#include <gbm.h>
+#include <wlr/util/log.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include "backend/drm/drm.h"
+#include "backend/drm/iface.h"
+#include "backend/drm/util.h"
+
+static bool legacy_crtc_pageflip(struct wlr_drm_backend *drm,
+ struct wlr_drm_connector *conn, struct wlr_drm_crtc *crtc,
+ uint32_t fb_id, drmModeModeInfo *mode) {
+ if (mode) {
+ if (drmModeSetCrtc(drm->fd, crtc->id, fb_id, 0, 0,
+ &conn->id, 1, mode)) {
+ wlr_log_errno(WLR_ERROR, "%s: Failed to set CRTC", conn->output.name);
+ return false;
+ }
+ }
+
+ if (drmModePageFlip(drm->fd, crtc->id, fb_id, DRM_MODE_PAGE_FLIP_EVENT, conn)) {
+ wlr_log_errno(WLR_ERROR, "%s: Failed to page flip", conn->output.name);
+ return false;
+ }
+
+ return true;
+}
+
+static bool legacy_conn_enable(struct wlr_drm_backend *drm,
+ struct wlr_drm_connector *conn, bool enable) {
+ int ret = drmModeConnectorSetProperty(drm->fd, conn->id, conn->props.dpms,
+ enable ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF);
+ return ret >= 0;
+}
+
+bool legacy_crtc_set_cursor(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, struct gbm_bo *bo) {
+ if (!crtc || !crtc->cursor) {
+ return true;
+ }
+
+ if (!bo) {
+ if (drmModeSetCursor(drm->fd, crtc->id, 0, 0, 0)) {
+ wlr_log_errno(WLR_DEBUG, "Failed to clear hardware cursor");
+ return false;
+ }
+ return true;
+ }
+
+ struct wlr_drm_plane *plane = crtc->cursor;
+
+ if (drmModeSetCursor(drm->fd, crtc->id, gbm_bo_get_handle(bo).u32,
+ plane->surf.width, plane->surf.height)) {
+ wlr_log_errno(WLR_DEBUG, "Failed to set hardware cursor");
+ return false;
+ }
+
+ return true;
+}
+
+bool legacy_crtc_move_cursor(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, int x, int y) {
+ return !drmModeMoveCursor(drm->fd, crtc->id, x, y);
+}
+
+bool legacy_crtc_set_gamma(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, size_t size,
+ uint16_t *r, uint16_t *g, uint16_t *b) {
+ return !drmModeCrtcSetGamma(drm->fd, crtc->id, (uint32_t)size, r, g, b);
+}
+
+size_t legacy_crtc_get_gamma_size(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc) {
+ return (size_t)crtc->legacy_crtc->gamma_size;
+}
+
+const struct wlr_drm_interface legacy_iface = {
+ .conn_enable = legacy_conn_enable,
+ .crtc_pageflip = legacy_crtc_pageflip,
+ .crtc_set_cursor = legacy_crtc_set_cursor,
+ .crtc_move_cursor = legacy_crtc_move_cursor,
+ .crtc_set_gamma = legacy_crtc_set_gamma,
+ .crtc_get_gamma_size = legacy_crtc_get_gamma_size,
+};
diff --git a/backend/drm/properties.c b/backend/drm/properties.c
new file mode 100644
index 00000000..5541d1be
--- /dev/null
+++ b/backend/drm/properties.c
@@ -0,0 +1,151 @@
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <wlr/util/log.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#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) },
+ { "link-status", INDEX(link_status) },
+#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) },
+ { "rotation", INDEX(rotation) },
+ { "scaling mode", INDEX(scaling_mode) },
+#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_ID", INDEX(fb_id) },
+ { "SRC_H", INDEX(src_h) },
+ { "SRC_W", INDEX(src_w) },
+ { "SRC_X", INDEX(src_x) },
+ { "SRC_Y", INDEX(src_y) },
+ { "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;
+}
diff --git a/backend/drm/renderer.c b/backend/drm/renderer.c
new file mode 100644
index 00000000..70b1bcbe
--- /dev/null
+++ b/backend/drm/renderer.c
@@ -0,0 +1,264 @@
+#include <assert.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <gbm.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <wayland-util.h>
+#include <wlr/render/egl.h>
+#include <wlr/render/gles2.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/util/log.h>
+#include "backend/drm/drm.h"
+#include "glapi.h"
+
+#ifndef DRM_FORMAT_MOD_LINEAR
+#define DRM_FORMAT_MOD_LINEAR 0
+#endif
+
+bool init_drm_renderer(struct wlr_drm_backend *drm,
+ struct wlr_drm_renderer *renderer, wlr_renderer_create_func_t create_renderer_func) {
+ renderer->gbm = gbm_create_device(drm->fd);
+ if (!renderer->gbm) {
+ wlr_log(WLR_ERROR, "Failed to create GBM device");
+ return false;
+ }
+
+ if (!create_renderer_func) {
+ create_renderer_func = wlr_renderer_autocreate;
+ }
+
+ static EGLint config_attribs[] = {
+ EGL_RED_SIZE, 1,
+ EGL_GREEN_SIZE, 1,
+ EGL_BLUE_SIZE, 1,
+ EGL_ALPHA_SIZE, 1,
+ EGL_NONE,
+ };
+
+ renderer->wlr_rend = create_renderer_func(&renderer->egl,
+ EGL_PLATFORM_GBM_MESA, renderer->gbm,
+ config_attribs, GBM_FORMAT_ARGB8888);
+
+ if (!renderer->wlr_rend) {
+ wlr_log(WLR_ERROR, "Failed to create EGL/WLR renderer");
+ goto error_gbm;
+ }
+
+ renderer->fd = drm->fd;
+ return true;
+
+error_gbm:
+ gbm_device_destroy(renderer->gbm);
+ return false;
+}
+
+void finish_drm_renderer(struct wlr_drm_renderer *renderer) {
+ if (!renderer) {
+ return;
+ }
+
+ wlr_renderer_destroy(renderer->wlr_rend);
+ wlr_egl_finish(&renderer->egl);
+ gbm_device_destroy(renderer->gbm);
+}
+
+bool init_drm_surface(struct wlr_drm_surface *surf,
+ struct wlr_drm_renderer *renderer, uint32_t width, uint32_t height,
+ uint32_t format, uint32_t flags) {
+ if (surf->width == width && surf->height == height) {
+ return true;
+ }
+
+ surf->renderer = renderer;
+ surf->width = width;
+ surf->height = height;
+
+ if (surf->gbm) {
+ if (surf->front) {
+ gbm_surface_release_buffer(surf->gbm, surf->front);
+ surf->front = NULL;
+ }
+ if (surf->back) {
+ gbm_surface_release_buffer(surf->gbm, surf->back);
+ surf->back = NULL;
+ }
+ gbm_surface_destroy(surf->gbm);
+ }
+ wlr_egl_destroy_surface(&surf->renderer->egl, surf->egl);
+
+ surf->gbm = gbm_surface_create(renderer->gbm, width, height,
+ format, GBM_BO_USE_RENDERING | flags);
+ if (!surf->gbm) {
+ wlr_log_errno(WLR_ERROR, "Failed to create GBM surface");
+ goto error_zero;
+ }
+
+ surf->egl = wlr_egl_create_surface(&renderer->egl, surf->gbm);
+ if (surf->egl == EGL_NO_SURFACE) {
+ wlr_log(WLR_ERROR, "Failed to create EGL surface");
+ goto error_gbm;
+ }
+
+ return true;
+
+error_gbm:
+ gbm_surface_destroy(surf->gbm);
+error_zero:
+ memset(surf, 0, sizeof(*surf));
+ return false;
+}
+
+void finish_drm_surface(struct wlr_drm_surface *surf) {
+ if (!surf || !surf->renderer) {
+ return;
+ }
+
+ if (surf->front) {
+ gbm_surface_release_buffer(surf->gbm, surf->front);
+ }
+ if (surf->back) {
+ gbm_surface_release_buffer(surf->gbm, surf->back);
+ }
+
+ wlr_egl_destroy_surface(&surf->renderer->egl, surf->egl);
+ if (surf->gbm) {
+ gbm_surface_destroy(surf->gbm);
+ }
+
+ memset(surf, 0, sizeof(*surf));
+}
+
+bool make_drm_surface_current(struct wlr_drm_surface *surf,
+ int *buffer_damage) {
+ return wlr_egl_make_current(&surf->renderer->egl, surf->egl, buffer_damage);
+}
+
+struct gbm_bo *swap_drm_surface_buffers(struct wlr_drm_surface *surf,
+ pixman_region32_t *damage) {
+ if (surf->front) {
+ gbm_surface_release_buffer(surf->gbm, surf->front);
+ }
+
+ wlr_egl_swap_buffers(&surf->renderer->egl, surf->egl, damage);
+
+ surf->front = surf->back;
+ surf->back = gbm_surface_lock_front_buffer(surf->gbm);
+ return surf->back;
+}
+
+struct gbm_bo *get_drm_surface_front(struct wlr_drm_surface *surf) {
+ if (surf->front) {
+ return surf->front;
+ }
+
+ make_drm_surface_current(surf, NULL);
+ struct wlr_renderer *renderer = surf->renderer->wlr_rend;
+ wlr_renderer_begin(renderer, surf->width, surf->height);
+ wlr_renderer_clear(renderer, (float[]){ 0.0, 0.0, 0.0, 1.0 });
+ wlr_renderer_end(renderer);
+ return swap_drm_surface_buffers(surf, NULL);
+}
+
+void post_drm_surface(struct wlr_drm_surface *surf) {
+ if (surf->front) {
+ gbm_surface_release_buffer(surf->gbm, surf->front);
+ surf->front = NULL;
+ }
+}
+
+bool export_drm_bo(struct gbm_bo *bo, struct wlr_dmabuf_attributes *attribs) {
+ memset(attribs, 0, sizeof(struct wlr_dmabuf_attributes));
+
+ attribs->n_planes = gbm_bo_get_plane_count(bo);
+ if (attribs->n_planes > WLR_DMABUF_MAX_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);
+
+ for (int i = 0; i < attribs->n_planes; ++i) {
+ attribs->offset[i] = gbm_bo_get_offset(bo, i);
+ attribs->stride[i] = gbm_bo_get_stride_for_plane(bo, i);
+ attribs->fd[i] = gbm_bo_get_fd(bo);
+ if (attribs->fd[i] < 0) {
+ for (int j = 0; j < i; ++j) {
+ close(attribs->fd[j]);
+ }
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void free_tex(struct gbm_bo *bo, void *data) {
+ struct wlr_texture *tex = data;
+ wlr_texture_destroy(tex);
+}
+
+static struct wlr_texture *get_tex_for_bo(struct wlr_drm_renderer *renderer,
+ struct gbm_bo *bo) {
+ struct wlr_texture *tex = gbm_bo_get_user_data(bo);
+ if (tex) {
+ return tex;
+ }
+
+ struct wlr_dmabuf_attributes attribs;
+ if (!export_drm_bo(bo, &attribs)) {
+ return NULL;
+ }
+
+ tex = wlr_texture_from_dmabuf(renderer->wlr_rend, &attribs);
+ if (tex) {
+ gbm_bo_set_user_data(bo, tex, free_tex);
+ }
+
+ return tex;
+}
+
+struct gbm_bo *copy_drm_surface_mgpu(struct wlr_drm_surface *dest,
+ struct gbm_bo *src) {
+ make_drm_surface_current(dest, NULL);
+
+ struct wlr_texture *tex = get_tex_for_bo(dest->renderer, src);
+ assert(tex);
+
+ float mat[9];
+ wlr_matrix_projection(mat, 1, 1, WL_OUTPUT_TRANSFORM_NORMAL);
+
+ struct wlr_renderer *renderer = dest->renderer->wlr_rend;
+ wlr_renderer_begin(renderer, dest->width, dest->height);
+ wlr_renderer_clear(renderer, (float[]){ 0.0, 0.0, 0.0, 1.0 });
+ wlr_render_texture_with_matrix(renderer, tex, mat, 1.0f);
+ wlr_renderer_end(renderer);
+
+ return swap_drm_surface_buffers(dest, NULL);
+}
+
+bool init_drm_plane_surfaces(struct wlr_drm_plane *plane,
+ struct wlr_drm_backend *drm, int32_t width, uint32_t height,
+ uint32_t format) {
+ if (!drm->parent) {
+ return init_drm_surface(&plane->surf, &drm->renderer, width, height,
+ format, GBM_BO_USE_SCANOUT);
+ }
+
+ if (!init_drm_surface(&plane->surf, &drm->parent->renderer,
+ width, height, format, GBM_BO_USE_LINEAR)) {
+ return false;
+ }
+
+ if (!init_drm_surface(&plane->mgpu_surf, &drm->renderer,
+ width, height, format, GBM_BO_USE_SCANOUT)) {
+ finish_drm_surface(&plane->surf);
+ return false;
+ }
+
+ return true;
+}
diff --git a/backend/drm/util.c b/backend/drm/util.c
new file mode 100644
index 00000000..6f2dd5be
--- /dev/null
+++ b/backend/drm/util.c
@@ -0,0 +1,348 @@
+#include <drm_mode.h>
+#include <drm.h>
+#include <gbm.h>
+#include <stdio.h>
+#include <string.h>
+#include <wlr/util/log.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;
+}
+
+// Constructed from http://edid.tv/manufacturer
+static const char *get_manufacturer(uint16_t id) {
+#define ID(a, b, c) ((a & 0x1f) << 10) | ((b & 0x1f) << 5) | (c & 0x1f)
+ switch (id) {
+ case ID('A', 'A', 'A'): return "Avolites Ltd";
+ case ID('A', 'C', 'I'): return "Ancor Communications Inc";
+ case ID('A', 'C', 'R'): return "Acer Technologies";
+ case ID('A', 'D', 'A'): return "Addi-Data GmbH";
+ case ID('A', 'P', 'P'): return "Apple Computer Inc";
+ case ID('A', 'S', 'K'): return "Ask A/S";
+ case ID('A', 'V', 'T'): return "Avtek (Electronics) Pty Ltd";
+ case ID('B', 'N', 'O'): return "Bang & Olufsen";
+ case ID('C', 'M', 'N'): return "Chimei Innolux Corporation";
+ case ID('C', 'M', 'O'): return "Chi Mei Optoelectronics corp.";
+ case ID('C', 'R', 'O'): return "Extraordinary Technologies PTY Limited";
+ case ID('D', 'E', 'L'): return "Dell Inc.";
+ case ID('D', 'G', 'C'): return "Data General Corporation";
+ case ID('D', 'O', 'N'): return "DENON, Ltd.";
+ case ID('E', 'N', 'C'): return "Eizo Nanao Corporation";
+ case ID('E', 'P', 'H'): return "Epiphan Systems Inc.";
+ case ID('E', 'X', 'P'): return "Data Export Corporation";
+ case ID('F', 'N', 'I'): return "Funai Electric Co., Ltd.";
+ case ID('F', 'U', 'S'): return "Fujitsu Siemens Computers GmbH";
+ case ID('G', 'S', 'M'): return "Goldstar Company Ltd";
+ case ID('H', 'I', 'Q'): return "Kaohsiung Opto Electronics Americas, Inc.";
+ case ID('H', 'S', 'D'): return "HannStar Display Corp";
+ case ID('H', 'T', 'C'): return "Hitachi Ltd";
+ case ID('H', 'W', 'P'): return "Hewlett Packard";
+ case ID('I', 'N', 'T'): return "Interphase Corporation";
+ case ID('I', 'N', 'X'): return "Communications Supply Corporation (A division of WESCO)";
+ case ID('I', 'T', 'E'): return "Integrated Tech Express Inc";
+ case ID('I', 'V', 'M'): return "Iiyama North America";
+ case ID('L', 'E', 'N'): return "Lenovo Group Limited";
+ case ID('M', 'A', 'X'): return "Rogen Tech Distribution Inc";
+ case ID('M', 'E', 'G'): return "Abeam Tech Ltd";
+ case ID('M', 'E', 'I'): return "Panasonic Industry Company";
+ case ID('M', 'T', 'C'): return "Mars-Tech Corporation";
+ case ID('M', 'T', 'X'): return "Matrox";
+ case ID('N', 'E', 'C'): return "NEC Corporation";
+ case ID('N', 'E', 'X'): return "Nexgen Mediatech Inc.";
+ case ID('O', 'N', 'K'): return "ONKYO Corporation";
+ case ID('O', 'R', 'N'): return "ORION ELECTRIC CO., LTD.";
+ case ID('O', 'T', 'M'): return "Optoma Corporation";
+ case ID('O', 'V', 'R'): return "Oculus VR, Inc.";
+ case ID('P', 'H', 'L'): return "Philips Consumer Electronics Company";
+ case ID('P', 'I', 'O'): return "Pioneer Electronic Corporation";
+ case ID('P', 'N', 'R'): return "Planar Systems, Inc.";
+ case ID('Q', 'D', 'S'): return "Quanta Display Inc.";
+ case ID('R', 'A', 'T'): return "Rent-A-Tech";
+ case ID('R', 'E', 'N'): return "Renesas Technology Corp.";
+ case ID('S', 'A', 'M'): return "Samsung Electric Company";
+ case ID('S', 'A', 'N'): return "Sanyo Electric Co., Ltd.";
+ case ID('S', 'E', 'C'): return "Seiko Epson Corporation";
+ case ID('S', 'H', 'P'): return "Sharp Corporation";
+ case ID('S', 'I', 'I'): return "Silicon Image, Inc.";
+ case ID('S', 'N', 'Y'): return "Sony";
+ case ID('S', 'T', 'D'): return "STD Computer Inc";
+ case ID('S', 'V', 'S'): return "SVSI";
+ case ID('S', 'Y', 'N'): return "Synaptics Inc";
+ case ID('T', 'C', 'L'): return "Technical Concepts Ltd";
+ case ID('T', 'O', 'P'): return "Orion Communications Co., Ltd.";
+ case ID('T', 'S', 'B'): return "Toshiba America Info Systems Inc";
+ case ID('T', 'S', 'T'): return "Transtream Inc";
+ case ID('U', 'N', 'K'): return "Unknown";
+ case ID('V', 'E', 'S'): return "Vestel Elektronik Sanayi ve Ticaret A. S.";
+ case ID('V', 'I', 'T'): return "Visitech AS";
+ case ID('V', 'I', 'Z'): return "VIZIO, Inc";
+ case ID('V', 'S', 'C'): return "ViewSonic Corporation";
+ case ID('Y', 'M', 'H'): return "Yamaha Corporation";
+ default: return "Unknown";
+ }
+#undef ID
+}
+
+/* See https://en.wikipedia.org/wiki/Extended_Display_Identification_Data for layout of EDID data.
+ * We don't parse the EDID properly. We just expect to receive valid data.
+ */
+void parse_edid(struct wlr_output *restrict output, size_t len, const uint8_t *data) {
+ if (!data || len < 128) {
+ snprintf(output->make, sizeof(output->make), "<Unknown>");
+ snprintf(output->model, sizeof(output->model), "<Unknown>");
+ return;
+ }
+
+ uint16_t id = (data[8] << 8) | data[9];
+ snprintf(output->make, sizeof(output->make), "%s", get_manufacturer(id));
+
+ uint16_t model = data[10] | (data[11] << 8);
+ snprintf(output->model, sizeof(output->model), "0x%04X", model);
+
+ uint32_t serial = data[12] | (data[13] << 8) | (data[14] << 8) | (data[15] << 8);
+ snprintf(output->serial, sizeof(output->serial), "0x%08X", serial);
+
+ for (size_t i = 72; i <= 108; i += 18) {
+ uint16_t flag = (data[i] << 8) | data[i + 1];
+ if (flag == 0 && data[i + 3] == 0xFC) {
+ sprintf(output->model, "%.13s", &data[i + 5]);
+
+ // Monitor names are terminated by newline if they're too short
+ char *nl = strchr(output->model, '\n');
+ if (nl) {
+ *nl = '\0';
+ }
+ } else if (flag == 0 && data[i + 3] == 0xFF) {
+ sprintf(output->serial, "%.13s", &data[i + 5]);
+
+ // Monitor serial numbers are terminated by newline if they're too
+ // short
+ char *nl = strchr(output->serial, '\n');
+ if (nl) {
+ *nl = '\0';
+ }
+ }
+ }
+}
+
+const char *conn_get_name(uint32_t type_id) {
+ switch (type_id) {
+ case DRM_MODE_CONNECTOR_Unknown: return "Unknown";
+ case DRM_MODE_CONNECTOR_VGA: return "VGA";
+ case DRM_MODE_CONNECTOR_DVII: return "DVI-I";
+ case DRM_MODE_CONNECTOR_DVID: return "DVI-D";
+ case DRM_MODE_CONNECTOR_DVIA: return "DVI-A";
+ case DRM_MODE_CONNECTOR_Composite: return "Composite";
+ case DRM_MODE_CONNECTOR_SVIDEO: return "SVIDEO";
+ case DRM_MODE_CONNECTOR_LVDS: return "LVDS";
+ case DRM_MODE_CONNECTOR_Component: return "Component";
+ case DRM_MODE_CONNECTOR_9PinDIN: return "DIN";
+ case DRM_MODE_CONNECTOR_DisplayPort: return "DP";
+ case DRM_MODE_CONNECTOR_HDMIA: return "HDMI-A";
+ case DRM_MODE_CONNECTOR_HDMIB: return "HDMI-B";
+ case DRM_MODE_CONNECTOR_TV: return "TV";
+ case DRM_MODE_CONNECTOR_eDP: return "eDP";
+ case DRM_MODE_CONNECTOR_VIRTUAL: return "Virtual";
+ case DRM_MODE_CONNECTOR_DSI: return "DSI";
+#ifdef DRM_MODE_CONNECTOR_DPI
+ case DRM_MODE_CONNECTOR_DPI: return "DPI";
+#endif
+ default: return "Unknown";
+ }
+}
+
+static void free_fb(struct gbm_bo *bo, void *data) {
+ uint32_t id = (uintptr_t)data;
+
+ if (id) {
+ struct gbm_device *gbm = gbm_bo_get_device(bo);
+ drmModeRmFB(gbm_device_get_fd(gbm), id);
+ }
+}
+
+uint32_t get_fb_for_bo(struct gbm_bo *bo) {
+ uint32_t id = (uintptr_t)gbm_bo_get_user_data(bo);
+ if (id) {
+ return id;
+ }
+
+ struct gbm_device *gbm = gbm_bo_get_device(bo);
+
+ int fd = gbm_device_get_fd(gbm);
+ uint32_t width = gbm_bo_get_width(bo);
+ uint32_t height = gbm_bo_get_height(bo);
+ uint32_t handles[4] = {gbm_bo_get_handle(bo).u32};
+ uint32_t pitches[4] = {gbm_bo_get_stride(bo)};
+ uint32_t offsets[4] = {gbm_bo_get_offset(bo, 0)};
+ uint32_t format = gbm_bo_get_format(bo);
+
+ if (drmModeAddFB2(fd, width, height, format, handles, pitches, offsets, &id, 0)) {
+ wlr_log_errno(WLR_ERROR, "Unable to add DRM framebuffer");
+ }
+
+ gbm_bo_set_user_data(bo, (void *)(uintptr_t)id, free_fb);
+
+ return id;
+}
+
+static inline 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;
+}
diff --git a/backend/headless/backend.c b/backend/headless/backend.c
new file mode 100644
index 00000000..c0fc6022
--- /dev/null
+++ b/backend/headless/backend.c
@@ -0,0 +1,132 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <wlr/interfaces/wlr_input_device.h>
+#include <wlr/interfaces/wlr_output.h>
+#include <wlr/render/egl.h>
+#include <wlr/render/gles2.h>
+#include <wlr/util/log.h>
+#include "backend/headless.h"
+#include "glapi.h"
+#include "util/signal.h"
+
+struct wlr_headless_backend *headless_backend_from_backend(
+ struct wlr_backend *wlr_backend) {
+ assert(wlr_backend_is_headless(wlr_backend));
+ return (struct wlr_headless_backend *)wlr_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_event_source_timer_update(output->frame_timer, output->frame_delay);
+ wlr_output_update_enabled(&output->wlr_output, true);
+ wlr_signal_emit_safe(&backend->backend.events.new_output,
+ &output->wlr_output);
+ }
+
+ struct wlr_headless_input_device *input_device;
+ wl_list_for_each(input_device, &backend->input_devices,
+ wlr_input_device.link) {
+ wlr_signal_emit_safe(&backend->backend.events.new_input,
+ &input_device->wlr_input_device);
+ }
+
+ 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;
+ }
+
+ wl_list_remove(&backend->display_destroy.link);
+
+ struct wlr_headless_output *output, *output_tmp;
+ wl_list_for_each_safe(output, output_tmp, &backend->outputs, link) {
+ wlr_output_destroy(&output->wlr_output);
+ }
+
+ struct wlr_headless_input_device *input_device, *input_device_tmp;
+ wl_list_for_each_safe(input_device, input_device_tmp,
+ &backend->input_devices, wlr_input_device.link) {
+ wlr_input_device_destroy(&input_device->wlr_input_device);
+ }
+
+ wlr_signal_emit_safe(&wlr_backend->events.destroy, backend);
+
+ wlr_renderer_destroy(backend->renderer);
+ wlr_egl_finish(&backend->egl);
+ free(backend);
+}
+
+static struct wlr_renderer *backend_get_renderer(
+ struct wlr_backend *wlr_backend) {
+ struct wlr_headless_backend *backend =
+ headless_backend_from_backend(wlr_backend);
+ return backend->renderer;
+}
+
+static const struct wlr_backend_impl backend_impl = {
+ .start = backend_start,
+ .destroy = backend_destroy,
+ .get_renderer = backend_get_renderer,
+};
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_headless_backend *backend =
+ wl_container_of(listener, backend, display_destroy);
+ backend_destroy(&backend->backend);
+}
+
+struct wlr_backend *wlr_headless_backend_create(struct wl_display *display,
+ wlr_renderer_create_func_t create_renderer_func) {
+ wlr_log(WLR_INFO, "Creating headless backend");
+
+ struct wlr_headless_backend *backend =
+ calloc(1, sizeof(struct wlr_headless_backend));
+ if (!backend) {
+ wlr_log(WLR_ERROR, "Failed to allocate wlr_headless_backend");
+ return NULL;
+ }
+ wlr_backend_init(&backend->backend, &backend_impl);
+ backend->display = display;
+ wl_list_init(&backend->outputs);
+ wl_list_init(&backend->input_devices);
+
+ static const EGLint config_attribs[] = {
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_ALPHA_SIZE, 0,
+ EGL_BLUE_SIZE, 1,
+ EGL_GREEN_SIZE, 1,
+ EGL_RED_SIZE, 1,
+ EGL_NONE,
+ };
+
+ if (!create_renderer_func) {
+ create_renderer_func = wlr_renderer_autocreate;
+ }
+
+ backend->renderer = create_renderer_func(&backend->egl,
+ EGL_PLATFORM_SURFACELESS_MESA, NULL, (EGLint*)config_attribs, 0);
+ if (!backend->renderer) {
+ wlr_log(WLR_ERROR, "Failed to create renderer");
+ free(backend);
+ return NULL;
+ }
+
+ backend->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &backend->display_destroy);
+
+ return &backend->backend;
+}
+
+bool wlr_backend_is_headless(struct wlr_backend *backend) {
+ return backend->impl == &backend_impl;
+}
diff --git a/backend/headless/input_device.c b/backend/headless/input_device.c
new file mode 100644
index 00000000..827c6ee4
--- /dev/null
+++ b/backend/headless/input_device.c
@@ -0,0 +1,99 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <wlr/interfaces/wlr_input_device.h>
+#include <wlr/interfaces/wlr_keyboard.h>
+#include <wlr/interfaces/wlr_pointer.h>
+#include <wlr/interfaces/wlr_tablet_pad.h>
+#include <wlr/interfaces/wlr_tablet_tool.h>
+#include <wlr/interfaces/wlr_touch.h>
+#include <wlr/interfaces/wlr_switch.h>
+#include <wlr/util/log.h>
+#include "backend/headless.h"
+#include "util/signal.h"
+
+static const struct wlr_input_device_impl input_device_impl = { 0 };
+
+bool wlr_input_device_is_headless(struct wlr_input_device *wlr_dev) {
+ return wlr_dev->impl == &input_device_impl;
+}
+
+struct wlr_input_device *wlr_headless_add_input_device(
+ struct wlr_backend *wlr_backend, enum wlr_input_device_type type) {
+ struct wlr_headless_backend *backend =
+ headless_backend_from_backend(wlr_backend);
+
+ struct wlr_headless_input_device *device =
+ calloc(1, sizeof(struct wlr_headless_input_device));
+ if (device == NULL) {
+ return NULL;
+ }
+ device->backend = backend;
+
+ int vendor = 0;
+ int product = 0;
+ const char *name = "headless";
+ struct wlr_input_device *wlr_device = &device->wlr_input_device;
+ wlr_input_device_init(wlr_device, type, &input_device_impl, name, vendor,
+ product);
+
+ switch (type) {
+ case WLR_INPUT_DEVICE_KEYBOARD:
+ wlr_device->keyboard = calloc(1, sizeof(struct wlr_keyboard));
+ if (wlr_device->keyboard == NULL) {
+ wlr_log(WLR_ERROR, "Unable to allocate wlr_keyboard");
+ goto error;
+ }
+ wlr_keyboard_init(wlr_device->keyboard, NULL);
+ break;
+ case WLR_INPUT_DEVICE_POINTER:
+ wlr_device->pointer = calloc(1, sizeof(struct wlr_pointer));
+ if (wlr_device->pointer == NULL) {
+ wlr_log(WLR_ERROR, "Unable to allocate wlr_pointer");
+ goto error;
+ }
+ wlr_pointer_init(wlr_device->pointer, NULL);
+ break;
+ case WLR_INPUT_DEVICE_TOUCH:
+ wlr_device->touch = calloc(1, sizeof(struct wlr_touch));
+ if (wlr_device->touch == NULL) {
+ wlr_log(WLR_ERROR, "Unable to allocate wlr_touch");
+ goto error;
+ }
+ wlr_touch_init(wlr_device->touch, NULL);
+ break;
+ case WLR_INPUT_DEVICE_TABLET_TOOL:
+ wlr_device->tablet = calloc(1, sizeof(struct wlr_tablet));
+ if (wlr_device->tablet == NULL) {
+ wlr_log(WLR_ERROR, "Unable to allocate wlr_tablet");
+ goto error;
+ }
+ wlr_tablet_init(wlr_device->tablet, NULL);
+ break;
+ case WLR_INPUT_DEVICE_TABLET_PAD:
+ wlr_device->tablet_pad = calloc(1, sizeof(struct wlr_tablet_pad));
+ if (wlr_device->tablet_pad == NULL) {
+ wlr_log(WLR_ERROR, "Unable to allocate wlr_tablet_pad");
+ goto error;
+ }
+ wlr_tablet_pad_init(wlr_device->tablet_pad, NULL);
+ break;
+ case WLR_INPUT_DEVICE_SWITCH:
+ wlr_device->lid_switch = calloc(1, sizeof(struct wlr_switch));
+ if (wlr_device->lid_switch == NULL) {
+ wlr_log(WLR_ERROR, "Unable to allocate wlr_switch");
+ goto error;
+ }
+ wlr_switch_init(wlr_device->lid_switch, NULL);
+ }
+
+ wl_list_insert(&backend->input_devices, &wlr_device->link);
+
+ if (backend->started) {
+ wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_device);
+ }
+
+ return wlr_device;
+error:
+ free(device);
+ return NULL;
+}
diff --git a/backend/headless/output.c b/backend/headless/output.c
new file mode 100644
index 00000000..3cb35dce
--- /dev/null
+++ b/backend/headless/output.c
@@ -0,0 +1,159 @@
+#include <assert.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <stdlib.h>
+#include <wlr/interfaces/wlr_output.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/util/log.h>
+#include "backend/headless.h"
+#include "util/signal.h"
+
+static struct wlr_headless_output *headless_output_from_output(
+ struct wlr_output *wlr_output) {
+ assert(wlr_output_is_headless(wlr_output));
+ return (struct wlr_headless_output *)wlr_output;
+}
+
+static EGLSurface egl_create_surface(struct wlr_egl *egl, unsigned int width,
+ unsigned int height) {
+ EGLint attribs[] = {EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE};
+
+ EGLSurface surf = eglCreatePbufferSurface(egl->display, egl->config, attribs);
+ if (surf == EGL_NO_SURFACE) {
+ wlr_log(WLR_ERROR, "Failed to create EGL surface");
+ return EGL_NO_SURFACE;
+ }
+ return surf;
+}
+
+static bool output_set_custom_mode(struct wlr_output *wlr_output, int32_t width,
+ int32_t height, int32_t refresh) {
+ struct wlr_headless_output *output =
+ headless_output_from_output(wlr_output);
+ struct wlr_headless_backend *backend = output->backend;
+
+ if (refresh <= 0) {
+ refresh = HEADLESS_DEFAULT_REFRESH;
+ }
+
+ wlr_egl_destroy_surface(&backend->egl, output->egl_surface);
+
+ output->egl_surface = egl_create_surface(&backend->egl, width, height);
+ if (output->egl_surface == EGL_NO_SURFACE) {
+ wlr_log(WLR_ERROR, "Failed to recreate EGL surface");
+ wlr_output_destroy(wlr_output);
+ return false;
+ }
+
+ output->frame_delay = 1000000 / refresh;
+
+ wlr_output_update_custom_mode(&output->wlr_output, width, height, refresh);
+ return true;
+}
+
+static void output_transform(struct wlr_output *wlr_output,
+ enum wl_output_transform transform) {
+ struct wlr_headless_output *output =
+ headless_output_from_output(wlr_output);
+ output->wlr_output.transform = transform;
+}
+
+static bool output_make_current(struct wlr_output *wlr_output, int *buffer_age) {
+ struct wlr_headless_output *output =
+ headless_output_from_output(wlr_output);
+ return wlr_egl_make_current(&output->backend->egl, output->egl_surface,
+ buffer_age);
+}
+
+static bool output_swap_buffers(struct wlr_output *wlr_output,
+ pixman_region32_t *damage) {
+ // Nothing needs to be done for pbuffers
+ wlr_output_send_present(wlr_output, NULL);
+ 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);
+
+ wlr_egl_destroy_surface(&output->backend->egl, output->egl_surface);
+ free(output);
+}
+
+static const struct wlr_output_impl output_impl = {
+ .set_custom_mode = output_set_custom_mode,
+ .transform = output_transform,
+ .destroy = output_destroy,
+ .make_current = output_make_current,
+ .swap_buffers = output_swap_buffers,
+};
+
+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);
+ wl_event_source_timer_update(output->frame_timer, output->frame_delay);
+ 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(struct wlr_headless_output));
+ if (output == NULL) {
+ wlr_log(WLR_ERROR, "Failed to allocate wlr_headless_output");
+ return NULL;
+ }
+ output->backend = backend;
+ wlr_output_init(&output->wlr_output, &backend->backend, &output_impl,
+ backend->display);
+ struct wlr_output *wlr_output = &output->wlr_output;
+
+ output->egl_surface = egl_create_surface(&backend->egl, width, height);
+ if (output->egl_surface == EGL_NO_SURFACE) {
+ wlr_log(WLR_ERROR, "Failed to create EGL surface");
+ goto error;
+ }
+
+ output_set_custom_mode(wlr_output, width, height, 0);
+ strncpy(wlr_output->make, "headless", sizeof(wlr_output->make));
+ strncpy(wlr_output->model, "headless", sizeof(wlr_output->model));
+ snprintf(wlr_output->name, sizeof(wlr_output->name), "HEADLESS-%d",
+ wl_list_length(&backend->outputs) + 1);
+
+ if (!wlr_egl_make_current(&output->backend->egl, output->egl_surface,
+ NULL)) {
+ goto error;
+ }
+
+ wlr_renderer_begin(backend->renderer, wlr_output->width, wlr_output->height);
+ wlr_renderer_clear(backend->renderer, (float[]){ 1.0, 1.0, 1.0, 1.0 });
+ wlr_renderer_end(backend->renderer);
+
+ struct wl_event_loop *ev = wl_display_get_event_loop(backend->display);
+ output->frame_timer = wl_event_loop_add_timer(ev, signal_frame, output);
+
+ wl_list_insert(&backend->outputs, &output->link);
+
+ if (backend->started) {
+ wl_event_source_timer_update(output->frame_timer, output->frame_delay);
+ wlr_output_update_enabled(wlr_output, true);
+ wlr_signal_emit_safe(&backend->backend.events.new_output, wlr_output);
+ }
+
+ return wlr_output;
+
+error:
+ wlr_output_destroy(&output->wlr_output);
+ return NULL;
+}
diff --git a/backend/libinput/backend.c b/backend/libinput/backend.c
new file mode 100644
index 00000000..8106af00
--- /dev/null
+++ b/backend/libinput/backend.c
@@ -0,0 +1,204 @@
+#include <assert.h>
+#include <libinput.h>
+#include <stdlib.h>
+#include <wlr/backend/interface.h>
+#include <wlr/backend/session.h>
+#include <wlr/util/log.h>
+#include "backend/libinput.h"
+#include "util/signal.h"
+
+static struct wlr_libinput_backend *get_libinput_backend_from_backend(
+ struct wlr_backend *wlr_backend) {
+ assert(wlr_backend_is_libinput(wlr_backend));
+ return (struct wlr_libinput_backend *)wlr_backend;
+}
+
+static int libinput_open_restricted(const char *path,
+ int flags, void *_backend) {
+ struct wlr_libinput_backend *backend = _backend;
+ return wlr_session_open_file(backend->session, path);
+}
+
+static void libinput_close_restricted(int fd, void *_backend) {
+ struct wlr_libinput_backend *backend = _backend;
+ wlr_session_close_file(backend->session, fd);
+}
+
+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;
+ if (libinput_dispatch(backend->libinput_context) != 0) {
+ wlr_log(WLR_ERROR, "Failed to dispatch libinput");
+ // TODO: some kind of abort?
+ 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 void log_libinput(struct libinput *libinput_context,
+ enum libinput_log_priority priority, const char *fmt, va_list args) {
+ _wlr_vlog(WLR_ERROR, 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, "Initializing libinput");
+
+ 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);
+ char *no_devs = getenv("WLR_LIBINPUT_NO_DEVICES");
+ if (no_devs) {
+ if (strcmp(no_devs, "1") != 0) {
+ no_devs = NULL;
+ }
+ }
+ if (!no_devs && backend->wlr_device_lists.length == 0) {
+ handle_libinput_readable(libinput_fd, WL_EVENT_READABLE, backend);
+ if (backend->wlr_device_lists.length == 0) {
+ 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;
+ }
+ }
+
+ struct wl_event_loop *event_loop =
+ wl_display_get_event_loop(backend->display);
+ if (backend->input_event) {
+ wl_event_source_remove(backend->input_event);
+ }
+ backend->input_event = wl_event_loop_add_fd(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);
+
+ for (size_t i = 0; i < backend->wlr_device_lists.length; i++) {
+ struct wl_list *wlr_devices = backend->wlr_device_lists.items[i];
+ struct wlr_input_device *wlr_dev, *next;
+ wl_list_for_each_safe(wlr_dev, next, wlr_devices, link) {
+ wlr_input_device_destroy(wlr_dev);
+ }
+ free(wlr_devices);
+ }
+
+ wlr_signal_emit_safe(&wlr_backend->events.destroy, wlr_backend);
+
+ wl_list_remove(&backend->display_destroy.link);
+ wl_list_remove(&backend->session_signal.link);
+
+ wlr_list_finish(&backend->wlr_device_lists);
+ 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 = data;
+
+ if (!backend->libinput_context) {
+ return;
+ }
+
+ if (session->active) {
+ libinput_resume(backend->libinput_context);
+ } else {
+ libinput_suspend(backend->libinput_context);
+ }
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_libinput_backend *backend =
+ wl_container_of(listener, backend, display_destroy);
+ backend_destroy(&backend->backend);
+}
+
+struct wlr_backend *wlr_libinput_backend_create(struct wl_display *display,
+ struct wlr_session *session) {
+ struct wlr_libinput_backend *backend =
+ calloc(1, sizeof(struct wlr_libinput_backend));
+ if (!backend) {
+ wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno));
+ return NULL;
+ }
+ wlr_backend_init(&backend->backend, &backend_impl);
+
+ if (!wlr_list_init(&backend->wlr_device_lists)) {
+ wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno));
+ goto error_backend;
+ }
+
+ backend->session = session;
+ backend->display = display;
+
+ backend->session_signal.notify = session_signal;
+ wl_signal_add(&session->session_signal, &backend->session_signal);
+
+ backend->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &backend->display_destroy);
+
+ return &backend->backend;
+error_backend:
+ free(backend);
+ return NULL;
+}
+
+struct libinput_device *wlr_libinput_get_device_handle(
+ struct wlr_input_device *wlr_dev) {
+ struct wlr_libinput_input_device *dev =
+ (struct wlr_libinput_input_device *)wlr_dev;
+ return dev->handle;
+}
+
+uint32_t usec_to_msec(uint64_t usec) {
+ return (uint32_t)(usec / 1000);
+}
diff --git a/backend/libinput/events.c b/backend/libinput/events.c
new file mode 100644
index 00000000..a7a6c114
--- /dev/null
+++ b/backend/libinput/events.c
@@ -0,0 +1,294 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <libinput.h>
+#include <stdlib.h>
+#include <wayland-util.h>
+#include <wlr/backend/session.h>
+#include <wlr/interfaces/wlr_input_device.h>
+#include <wlr/util/log.h>
+#include "backend/libinput.h"
+#include "util/signal.h"
+
+struct wlr_libinput_input_device *get_libinput_device_from_device(
+ struct wlr_input_device *wlr_dev) {
+ assert(wlr_input_device_is_libinput(wlr_dev));
+ return (struct wlr_libinput_input_device *)wlr_dev;
+}
+
+struct wlr_input_device *get_appropriate_device(
+ enum wlr_input_device_type desired_type,
+ struct libinput_device *libinput_dev) {
+ struct wl_list *wlr_devices = libinput_device_get_user_data(libinput_dev);
+ if (!wlr_devices) {
+ return NULL;
+ }
+ struct wlr_input_device *dev;
+ wl_list_for_each(dev, wlr_devices, link) {
+ if (dev->type == desired_type) {
+ return dev;
+ }
+ }
+ return NULL;
+}
+
+static void input_device_destroy(struct wlr_input_device *wlr_dev) {
+ struct wlr_libinput_input_device *dev =
+ get_libinput_device_from_device(wlr_dev);
+ libinput_device_unref(dev->handle);
+ wl_list_remove(&dev->wlr_input_device.link);
+ free(dev);
+}
+
+static const struct wlr_input_device_impl input_device_impl = {
+ .destroy = input_device_destroy,
+};
+
+static struct wlr_input_device *allocate_device(
+ struct wlr_libinput_backend *backend,
+ struct libinput_device *libinput_dev, struct wl_list *wlr_devices,
+ enum wlr_input_device_type type) {
+ 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);
+ struct wlr_libinput_input_device *dev =
+ calloc(1, sizeof(struct wlr_libinput_input_device));
+ if (dev == NULL) {
+ return NULL;
+ }
+ struct wlr_input_device *wlr_dev = &dev->wlr_input_device;
+ libinput_device_get_size(libinput_dev,
+ &wlr_dev->width_mm, &wlr_dev->height_mm);
+ const char *output_name = libinput_device_get_output_name(libinput_dev);
+ if (output_name != NULL) {
+ wlr_dev->output_name = strdup(output_name);
+ }
+ wl_list_insert(wlr_devices, &wlr_dev->link);
+ dev->handle = libinput_dev;
+ libinput_device_ref(libinput_dev);
+ wlr_input_device_init(wlr_dev, type, &input_device_impl,
+ name, vendor, product);
+ return wlr_dev;
+}
+
+bool wlr_input_device_is_libinput(struct wlr_input_device *wlr_dev) {
+ return wlr_dev->impl == &input_device_impl;
+}
+
+static void handle_device_added(struct wlr_libinput_backend *backend,
+ struct libinput_device *libinput_dev) {
+ /*
+ * Note: the wlr API exposes only devices with a single capability, because
+ * that meshes better with how Wayland does things and is a bit simpler.
+ * However, libinput devices often have multiple capabilities - in such
+ * cases we have to create several devices.
+ */
+ 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);
+ struct wl_list *wlr_devices = calloc(1, sizeof(struct wl_list));
+ if (!wlr_devices) {
+ wlr_log(WLR_ERROR, "Allocation failed");
+ return;
+ }
+ wl_list_init(wlr_devices);
+ wlr_log(WLR_DEBUG, "Added %s [%d:%d]", name, vendor, product);
+
+ if (libinput_device_has_capability(
+ libinput_dev, LIBINPUT_DEVICE_CAP_KEYBOARD)) {
+ struct wlr_input_device *wlr_dev = allocate_device(backend,
+ libinput_dev, wlr_devices, WLR_INPUT_DEVICE_KEYBOARD);
+ if (!wlr_dev) {
+ goto fail;
+ }
+ wlr_dev->keyboard = create_libinput_keyboard(libinput_dev);
+ if (!wlr_dev->keyboard) {
+ free(wlr_dev);
+ goto fail;
+ }
+ wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev);
+ }
+ if (libinput_device_has_capability(
+ libinput_dev, LIBINPUT_DEVICE_CAP_POINTER)) {
+ struct wlr_input_device *wlr_dev = allocate_device(backend,
+ libinput_dev, wlr_devices, WLR_INPUT_DEVICE_POINTER);
+ if (!wlr_dev) {
+ goto fail;
+ }
+ wlr_dev->pointer = create_libinput_pointer(libinput_dev);
+ if (!wlr_dev->pointer) {
+ free(wlr_dev);
+ goto fail;
+ }
+ wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev);
+ }
+ if (libinput_device_has_capability(
+ libinput_dev, LIBINPUT_DEVICE_CAP_TOUCH)) {
+ struct wlr_input_device *wlr_dev = allocate_device(backend,
+ libinput_dev, wlr_devices, WLR_INPUT_DEVICE_TOUCH);
+ if (!wlr_dev) {
+ goto fail;
+ }
+ wlr_dev->touch = create_libinput_touch(libinput_dev);
+ if (!wlr_dev->touch) {
+ free(wlr_dev);
+ goto fail;
+ }
+ wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev);
+ }
+ if (libinput_device_has_capability(libinput_dev,
+ LIBINPUT_DEVICE_CAP_TABLET_TOOL)) {
+ struct wlr_input_device *wlr_dev = allocate_device(backend,
+ libinput_dev, wlr_devices, WLR_INPUT_DEVICE_TABLET_TOOL);
+ if (!wlr_dev) {
+ goto fail;
+ }
+ wlr_dev->tablet = create_libinput_tablet(libinput_dev);
+ if (!wlr_dev->tablet) {
+ free(wlr_dev);
+ goto fail;
+ }
+ wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev);
+ }
+ if (libinput_device_has_capability(
+ libinput_dev, LIBINPUT_DEVICE_CAP_TABLET_PAD)) {
+ struct wlr_input_device *wlr_dev = allocate_device(backend,
+ libinput_dev, wlr_devices, WLR_INPUT_DEVICE_TABLET_PAD);
+ if (!wlr_dev) {
+ goto fail;
+ }
+ wlr_dev->tablet_pad = create_libinput_tablet_pad(libinput_dev);
+ if (!wlr_dev->tablet_pad) {
+ free(wlr_dev);
+ goto fail;
+ }
+ wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev);
+ }
+ if (libinput_device_has_capability(
+ libinput_dev, LIBINPUT_DEVICE_CAP_GESTURE)) {
+ // TODO
+ }
+ if (libinput_device_has_capability(
+ libinput_dev, LIBINPUT_DEVICE_CAP_SWITCH)) {
+ struct wlr_input_device *wlr_dev = allocate_device(backend,
+ libinput_dev, wlr_devices, WLR_INPUT_DEVICE_SWITCH);
+ if (!wlr_dev) {
+ goto fail;
+ }
+ wlr_dev->lid_switch = create_libinput_switch(libinput_dev);
+ if (!wlr_dev->lid_switch) {
+ free(wlr_dev);
+ goto fail;
+ }
+ wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev);
+ }
+
+ if (!wl_list_empty(wlr_devices)) {
+ libinput_device_set_user_data(libinput_dev, wlr_devices);
+ wlr_list_push(&backend->wlr_device_lists, wlr_devices);
+ } else {
+ free(wlr_devices);
+ }
+ return;
+
+fail:
+ wlr_log(WLR_ERROR, "Could not allocate new device");
+ struct wlr_input_device *dev, *tmp_dev;
+ wl_list_for_each_safe(dev, tmp_dev, wlr_devices, link) {
+ free(dev);
+ }
+ free(wlr_devices);
+}
+
+static void handle_device_removed(struct wlr_libinput_backend *backend,
+ struct libinput_device *libinput_dev) {
+ struct wl_list *wlr_devices = libinput_device_get_user_data(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, "Removing %s [%d:%d]", name, vendor, product);
+ if (!wlr_devices) {
+ return;
+ }
+ struct wlr_input_device *dev, *tmp_dev;
+ wl_list_for_each_safe(dev, tmp_dev, wlr_devices, link) {
+ wlr_input_device_destroy(dev);
+ }
+ for (size_t i = 0; i < backend->wlr_device_lists.length; i++) {
+ if (backend->wlr_device_lists.items[i] == wlr_devices) {
+ wlr_list_del(&backend->wlr_device_lists, i);
+ break;
+ }
+ }
+ free(wlr_devices);
+}
+
+void handle_libinput_event(struct wlr_libinput_backend *backend,
+ struct libinput_event *event) {
+ struct libinput_device *libinput_dev = libinput_event_get_device(event);
+ enum libinput_event_type event_type = libinput_event_get_type(event);
+ switch (event_type) {
+ case LIBINPUT_EVENT_DEVICE_ADDED:
+ handle_device_added(backend, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_DEVICE_REMOVED:
+ handle_device_removed(backend, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_KEYBOARD_KEY:
+ handle_keyboard_key(event, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_POINTER_MOTION:
+ handle_pointer_motion(event, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
+ handle_pointer_motion_abs(event, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_POINTER_BUTTON:
+ handle_pointer_button(event, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_POINTER_AXIS:
+ handle_pointer_axis(event, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_TOUCH_DOWN:
+ handle_touch_down(event, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_TOUCH_UP:
+ handle_touch_up(event, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_TOUCH_MOTION:
+ handle_touch_motion(event, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_TOUCH_CANCEL:
+ handle_touch_cancel(event, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_TOUCH_FRAME:
+ // no-op (at least for now)
+ break;
+ case LIBINPUT_EVENT_TABLET_TOOL_AXIS:
+ handle_tablet_tool_axis(event, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY:
+ handle_tablet_tool_proximity(event, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_TABLET_TOOL_TIP:
+ handle_tablet_tool_tip(event, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_TABLET_TOOL_BUTTON:
+ handle_tablet_tool_button(event, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_TABLET_PAD_BUTTON:
+ handle_tablet_pad_button(event, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_TABLET_PAD_RING:
+ handle_tablet_pad_ring(event, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_TABLET_PAD_STRIP:
+ handle_tablet_pad_strip(event, libinput_dev);
+ break;
+ case LIBINPUT_EVENT_SWITCH_TOGGLE:
+ handle_switch_toggle(event, libinput_dev);
+ break;
+ 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 00000000..93605e77
--- /dev/null
+++ b/backend/libinput/keyboard.c
@@ -0,0 +1,83 @@
+#include <assert.h>
+#include <libinput.h>
+#include <stdlib.h>
+#include <wlr/backend/session.h>
+#include <wlr/interfaces/wlr_keyboard.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/util/log.h>
+#include "backend/libinput.h"
+
+struct wlr_libinput_keyboard {
+ struct wlr_keyboard wlr_keyboard;
+ struct libinput_device *libinput_dev;
+};
+
+static const struct wlr_keyboard_impl impl;
+
+static struct wlr_libinput_keyboard *get_libinput_keyboard_from_keyboard(
+ struct wlr_keyboard *wlr_kb) {
+ assert(wlr_kb->impl == &impl);
+ return (struct wlr_libinput_keyboard *)wlr_kb;
+}
+
+static void keyboard_set_leds(struct wlr_keyboard *wlr_kb, uint32_t leds) {
+ struct wlr_libinput_keyboard *kb =
+ get_libinput_keyboard_from_keyboard(wlr_kb);
+ libinput_device_led_update(kb->libinput_dev, leds);
+}
+
+static void keyboard_destroy(struct wlr_keyboard *wlr_kb) {
+ struct wlr_libinput_keyboard *kb =
+ get_libinput_keyboard_from_keyboard(wlr_kb);
+ libinput_device_unref(kb->libinput_dev);
+ free(kb);
+}
+
+static const struct wlr_keyboard_impl impl = {
+ .destroy = keyboard_destroy,
+ .led_update = keyboard_set_leds
+};
+
+struct wlr_keyboard *create_libinput_keyboard(
+ struct libinput_device *libinput_dev) {
+ struct wlr_libinput_keyboard *kb =
+ calloc(1, sizeof(struct wlr_libinput_keyboard));
+ if (kb == NULL) {
+ return NULL;
+ }
+ kb->libinput_dev = libinput_dev;
+ libinput_device_ref(libinput_dev);
+ libinput_device_led_update(libinput_dev, 0);
+ struct wlr_keyboard *wlr_kb = &kb->wlr_keyboard;
+ wlr_keyboard_init(wlr_kb, &impl);
+ return wlr_kb;
+}
+
+void handle_keyboard_key(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_KEYBOARD, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG,
+ "Got a keyboard event for a device with no keyboards?");
+ return;
+ }
+ struct libinput_event_keyboard *kbevent =
+ libinput_event_get_keyboard_event(event);
+ struct wlr_event_keyboard_key wlr_event = { 0 };
+ wlr_event.time_msec =
+ usec_to_msec(libinput_event_keyboard_get_time_usec(kbevent));
+ wlr_event.keycode = libinput_event_keyboard_get_key(kbevent);
+ enum libinput_key_state state =
+ libinput_event_keyboard_get_key_state(kbevent);
+ switch (state) {
+ case LIBINPUT_KEY_STATE_RELEASED:
+ wlr_event.state = WLR_KEY_RELEASED;
+ break;
+ case LIBINPUT_KEY_STATE_PRESSED:
+ wlr_event.state = WLR_KEY_PRESSED;
+ break;
+ }
+ wlr_event.update_state = true;
+ wlr_keyboard_notify_key(wlr_dev->keyboard, &wlr_event);
+}
diff --git a/backend/libinput/pointer.c b/backend/libinput/pointer.c
new file mode 100644
index 00000000..fb85cddd
--- /dev/null
+++ b/backend/libinput/pointer.c
@@ -0,0 +1,136 @@
+#include <assert.h>
+#include <libinput.h>
+#include <stdlib.h>
+#include <wlr/backend/session.h>
+#include <wlr/interfaces/wlr_pointer.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/util/log.h>
+#include "backend/libinput.h"
+#include "util/signal.h"
+
+struct wlr_pointer *create_libinput_pointer(
+ struct libinput_device *libinput_dev) {
+ assert(libinput_dev);
+ struct wlr_pointer *wlr_pointer = calloc(1, sizeof(struct wlr_pointer));
+ if (!wlr_pointer) {
+ wlr_log(WLR_ERROR, "Unable to allocate wlr_pointer");
+ return NULL;
+ }
+ wlr_pointer_init(wlr_pointer, NULL);
+ return wlr_pointer;
+}
+
+void handle_pointer_motion(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_POINTER, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG, "Got a pointer event for a device with no pointers?");
+ return;
+ }
+ struct libinput_event_pointer *pevent =
+ libinput_event_get_pointer_event(event);
+ struct wlr_event_pointer_motion wlr_event = { 0 };
+ wlr_event.device = wlr_dev;
+ wlr_event.time_msec =
+ usec_to_msec(libinput_event_pointer_get_time_usec(pevent));
+ wlr_event.delta_x = libinput_event_pointer_get_dx(pevent);
+ wlr_event.delta_y = libinput_event_pointer_get_dy(pevent);
+ wlr_signal_emit_safe(&wlr_dev->pointer->events.motion, &wlr_event);
+}
+
+void handle_pointer_motion_abs(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_POINTER, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG, "Got a pointer event for a device with no pointers?");
+ return;
+ }
+ struct libinput_event_pointer *pevent =
+ libinput_event_get_pointer_event(event);
+ struct wlr_event_pointer_motion_absolute wlr_event = { 0 };
+ wlr_event.device = wlr_dev;
+ wlr_event.time_msec =
+ usec_to_msec(libinput_event_pointer_get_time_usec(pevent));
+ wlr_event.x = libinput_event_pointer_get_absolute_x_transformed(pevent, 1);
+ wlr_event.y = libinput_event_pointer_get_absolute_y_transformed(pevent, 1);
+ wlr_signal_emit_safe(&wlr_dev->pointer->events.motion_absolute, &wlr_event);
+}
+
+void handle_pointer_button(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_POINTER, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG, "Got a pointer event for a device with no pointers?");
+ return;
+ }
+ struct libinput_event_pointer *pevent =
+ libinput_event_get_pointer_event(event);
+ struct wlr_event_pointer_button wlr_event = { 0 };
+ wlr_event.device = wlr_dev;
+ wlr_event.time_msec =
+ usec_to_msec(libinput_event_pointer_get_time_usec(pevent));
+ wlr_event.button = libinput_event_pointer_get_button(pevent);
+ switch (libinput_event_pointer_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;
+ }
+ wlr_signal_emit_safe(&wlr_dev->pointer->events.button, &wlr_event);
+}
+
+void handle_pointer_axis(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_POINTER, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG, "Got a pointer event for a device with no pointers?");
+ return;
+ }
+ struct libinput_event_pointer *pevent =
+ libinput_event_get_pointer_event(event);
+ struct wlr_event_pointer_axis wlr_event = { 0 };
+ wlr_event.device = wlr_dev;
+ wlr_event.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 = WLR_AXIS_SOURCE_WHEEL;
+ break;
+ case LIBINPUT_POINTER_AXIS_SOURCE_FINGER:
+ wlr_event.source = WLR_AXIS_SOURCE_FINGER;
+ break;
+ case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS:
+ wlr_event.source = WLR_AXIS_SOURCE_CONTINUOUS;
+ break;
+ case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT:
+ wlr_event.source = WLR_AXIS_SOURCE_WHEEL_TILT;
+ break;
+ }
+ enum libinput_pointer_axis axies[] = {
+ LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
+ LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL,
+ };
+ for (size_t i = 0; i < sizeof(axies) / sizeof(axies[0]); ++i) {
+ if (libinput_event_pointer_has_axis(pevent, axies[i])) {
+ switch (axies[i]) {
+ case LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL:
+ wlr_event.orientation = WLR_AXIS_ORIENTATION_VERTICAL;
+ break;
+ case LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL:
+ wlr_event.orientation = WLR_AXIS_ORIENTATION_HORIZONTAL;
+ break;
+ }
+ wlr_event.delta =
+ libinput_event_pointer_get_axis_value(pevent, axies[i]);
+ wlr_event.delta_discrete =
+ libinput_event_pointer_get_axis_value_discrete(pevent, axies[i]);
+ wlr_signal_emit_safe(&wlr_dev->pointer->events.axis, &wlr_event);
+ }
+ }
+}
diff --git a/backend/libinput/switch.c b/backend/libinput/switch.c
new file mode 100644
index 00000000..393460b0
--- /dev/null
+++ b/backend/libinput/switch.c
@@ -0,0 +1,55 @@
+#include <assert.h>
+#include <libinput.h>
+#include <stdlib.h>
+#include <wlr/backend/session.h>
+#include <wlr/interfaces/wlr_switch.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/util/log.h>
+#include "backend/libinput.h"
+#include "util/signal.h"
+
+struct wlr_switch *create_libinput_switch(
+ struct libinput_device *libinput_dev) {
+ assert(libinput_dev);
+ struct wlr_switch *wlr_switch = calloc(1, sizeof(struct wlr_switch));
+ if (!wlr_switch) {
+ wlr_log(WLR_ERROR, "Unable to allocate wlr_switch");
+ return NULL;
+ }
+ wlr_switch_init(wlr_switch, NULL);
+ wlr_log(WLR_DEBUG, "Created switch for device %s", libinput_device_get_name(libinput_dev));
+ return wlr_switch;
+}
+
+void handle_switch_toggle(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_SWITCH, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG, "Got a switch event for a device with no switch?");
+ return;
+ }
+ struct libinput_event_switch *sevent =
+ libinput_event_get_switch_event (event);
+ struct wlr_event_switch_toggle wlr_event = { 0 };
+ wlr_event.device = wlr_dev;
+ 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;
+ }
+ wlr_event.time_msec =
+ usec_to_msec(libinput_event_switch_get_time_usec(sevent));
+ wlr_signal_emit_safe(&wlr_dev->lid_switch->events.toggle, &wlr_event);
+}
diff --git a/backend/libinput/tablet_pad.c b/backend/libinput/tablet_pad.c
new file mode 100644
index 00000000..b053b9a0
--- /dev/null
+++ b/backend/libinput/tablet_pad.c
@@ -0,0 +1,183 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#include <assert.h>
+#include <string.h>
+#include <libinput.h>
+#include <stdlib.h>
+#include <wlr/backend/session.h>
+#include <wlr/interfaces/wlr_tablet_pad.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/util/log.h>
+#include "backend/libinput.h"
+#include "util/signal.h"
+
+// FIXME: Decide on how to alloc/count here
+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(struct wlr_tablet_pad_group));
+ if (!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(sizeof(unsigned int), group->ring_count);
+ 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(sizeof(unsigned int), group->strip_count);
+ 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(sizeof(unsigned int), group->button_count);
+ 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);
+ wl_list_insert(&pad->groups, &group->link);
+}
+
+struct wlr_tablet_pad *create_libinput_tablet_pad(
+ struct libinput_device *libinput_dev) {
+ assert(libinput_dev);
+ struct wlr_tablet_pad *wlr_tablet_pad =
+ calloc(1, sizeof(struct wlr_tablet_pad));
+ if (!wlr_tablet_pad) {
+ wlr_log(WLR_ERROR, "Unable to allocate wlr_tablet_pad");
+ return NULL;
+ }
+
+ wlr_tablet_pad->button_count =
+ libinput_device_tablet_pad_get_num_buttons(libinput_dev);
+ wlr_tablet_pad->ring_count =
+ libinput_device_tablet_pad_get_num_rings(libinput_dev);
+ wlr_tablet_pad->strip_count =
+ libinput_device_tablet_pad_get_num_strips(libinput_dev);
+
+ wlr_list_init(&wlr_tablet_pad->paths);
+ struct udev_device *udev = libinput_device_get_udev_device(libinput_dev);
+ wlr_list_push(&wlr_tablet_pad->paths, strdup(udev_device_get_syspath(udev)));
+
+ wl_list_init(&wlr_tablet_pad->groups);
+ int groups = libinput_device_tablet_pad_get_num_mode_groups(libinput_dev);
+ for (int i = 0; i < groups; ++i) {
+ add_pad_group_from_libinput(wlr_tablet_pad, libinput_dev, i);
+ }
+
+ wlr_tablet_pad_init(wlr_tablet_pad, NULL);
+ return wlr_tablet_pad;
+}
+
+void handle_tablet_pad_button(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_TABLET_PAD, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG,
+ "Got a tablet pad event for a device with no tablet pad?");
+ return;
+ }
+ struct libinput_event_tablet_pad *pevent =
+ libinput_event_get_tablet_pad_event(event);
+ struct wlr_event_tablet_pad_button wlr_event = { 0 };
+ wlr_event.time_msec =
+ usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent));
+ wlr_event.button = libinput_event_tablet_pad_get_button_number(pevent);
+ wlr_event.mode = libinput_event_tablet_pad_get_mode(pevent);
+ wlr_event.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;
+ }
+ wlr_signal_emit_safe(&wlr_dev->tablet_pad->events.button, &wlr_event);
+}
+
+void handle_tablet_pad_ring(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_TABLET_PAD, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG,
+ "Got a tablet pad event for a device with no tablet pad?");
+ return;
+ }
+ struct libinput_event_tablet_pad *pevent =
+ libinput_event_get_tablet_pad_event(event);
+ struct wlr_event_tablet_pad_ring wlr_event = { 0 };
+ wlr_event.time_msec =
+ usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent));
+ wlr_event.ring = libinput_event_tablet_pad_get_ring_number(pevent);
+ wlr_event.position = libinput_event_tablet_pad_get_ring_position(pevent);
+ wlr_event.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;
+ }
+ wlr_signal_emit_safe(&wlr_dev->tablet_pad->events.ring, &wlr_event);
+}
+
+void handle_tablet_pad_strip(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_TABLET_PAD, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG,
+ "Got a tablet pad event for a device with no tablet pad?");
+ return;
+ }
+ struct libinput_event_tablet_pad *pevent =
+ libinput_event_get_tablet_pad_event(event);
+ struct wlr_event_tablet_pad_strip wlr_event = { 0 };
+ wlr_event.time_msec =
+ usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent));
+ wlr_event.strip = libinput_event_tablet_pad_get_strip_number(pevent);
+ wlr_event.position = libinput_event_tablet_pad_get_strip_position(pevent);
+ wlr_event.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;
+ }
+ wlr_signal_emit_safe(&wlr_dev->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 00000000..4e87b700
--- /dev/null
+++ b/backend/libinput/tablet_tool.c
@@ -0,0 +1,371 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#include <string.h>
+#include <assert.h>
+#include <libinput.h>
+#include <stdlib.h>
+#include <wayland-util.h>
+#include <wlr/backend/session.h>
+#include <wlr/interfaces/wlr_tablet_tool.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/util/log.h>
+#include "backend/libinput.h"
+#include "util/signal.h"
+
+static struct wlr_tablet_impl tablet_impl;
+
+static bool tablet_is_libinput(struct wlr_tablet *tablet) {
+ return tablet->impl == &tablet_impl;
+}
+
+struct wlr_libinput_tablet_tool {
+ struct wlr_tablet_tool wlr_tool;
+
+ struct libinput_tablet_tool *libinput_tool;
+
+ bool unique;
+ // Refcount for destroy + release
+ size_t pad_refs;
+};
+
+// TODO: Maybe this should be a wlr_list? Do we keep it, or want to get rid of
+// it?
+struct tablet_tool_list_elem {
+ struct wl_list link;
+
+ struct wlr_libinput_tablet_tool *tool;
+};
+
+struct wlr_libinput_tablet {
+ struct wlr_tablet wlr_tablet;
+
+ struct wl_list tools; // tablet_tool_list_elem::link
+};
+
+static void destroy_tool(struct wlr_libinput_tablet_tool *tool) {
+ wlr_signal_emit_safe(&tool->wlr_tool.events.destroy, &tool->wlr_tool);
+ libinput_tablet_tool_ref(tool->libinput_tool);
+ libinput_tablet_tool_set_user_data(tool->libinput_tool, NULL);
+ free(tool);
+}
+
+
+static void destroy_tablet(struct wlr_tablet *wlr_tablet) {
+ assert(tablet_is_libinput(wlr_tablet));
+ struct wlr_libinput_tablet *tablet =
+ wl_container_of(wlr_tablet, tablet, wlr_tablet);
+
+ struct tablet_tool_list_elem *pos;
+ struct tablet_tool_list_elem *tmp;
+ wl_list_for_each_safe(pos, tmp, &tablet->tools, link) {
+ struct wlr_libinput_tablet_tool *tool = pos->tool;
+ wl_list_remove(&pos->link);
+ free(pos);
+
+ if (--tool->pad_refs == 0) {
+ destroy_tool(tool);
+ }
+ }
+
+ free(tablet);
+}
+
+static struct wlr_tablet_impl tablet_impl = {
+ .destroy = destroy_tablet,
+};
+
+struct wlr_tablet *create_libinput_tablet(
+ struct libinput_device *libinput_dev) {
+ assert(libinput_dev);
+ struct wlr_libinput_tablet *libinput_tablet =
+ calloc(1, sizeof(struct wlr_libinput_tablet));
+ if (!libinput_tablet) {
+ wlr_log(WLR_ERROR, "Unable to allocate wlr_tablet_tool");
+ return NULL;
+ }
+ struct wlr_tablet *wlr_tablet = &libinput_tablet->wlr_tablet;
+
+ wlr_list_init(&wlr_tablet->paths);
+ struct udev_device *udev = libinput_device_get_udev_device(libinput_dev);
+ wlr_list_push(&wlr_tablet->paths, strdup(udev_device_get_syspath(udev)));
+ wlr_tablet->name = strdup(libinput_device_get_name(libinput_dev));
+ wl_list_init(&libinput_tablet->tools);
+
+ wlr_tablet_init(wlr_tablet, &tablet_impl);
+ return wlr_tablet;
+}
+
+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;
+ }
+
+ assert(false && "UNREACHABLE");
+}
+
+static struct wlr_libinput_tablet_tool *get_wlr_tablet_tool(
+ struct libinput_tablet_tool *tool) {
+ struct wlr_libinput_tablet_tool *ret =
+ libinput_tablet_tool_get_user_data(tool);
+
+ if (ret) {
+ return ret;
+ }
+
+ ret = calloc(1, sizeof(struct wlr_libinput_tablet_tool));
+ if (!ret) {
+ return NULL;
+ }
+
+ ret->libinput_tool = libinput_tablet_tool_ref(tool);
+ ret->wlr_tool.pressure = libinput_tablet_tool_has_pressure(tool);
+ ret->wlr_tool.distance = libinput_tablet_tool_has_distance(tool);
+ ret->wlr_tool.tilt = libinput_tablet_tool_has_tilt(tool);
+ ret->wlr_tool.rotation = libinput_tablet_tool_has_rotation(tool);
+ ret->wlr_tool.slider = libinput_tablet_tool_has_slider(tool);
+ ret->wlr_tool.wheel = libinput_tablet_tool_has_wheel(tool);
+
+ ret->wlr_tool.hardware_serial = libinput_tablet_tool_get_serial(tool);
+ ret->wlr_tool.hardware_wacom = libinput_tablet_tool_get_tool_id(tool);
+ ret->wlr_tool.type = wlr_type_from_libinput_type(
+ libinput_tablet_tool_get_type(tool));
+
+ ret->unique = libinput_tablet_tool_is_unique(tool);
+
+ wl_signal_init(&ret->wlr_tool.events.destroy);
+
+ libinput_tablet_tool_set_user_data(tool, ret);
+ return ret;
+}
+
+static void ensure_tool_reference(struct wlr_libinput_tablet_tool *tool,
+ struct wlr_tablet *wlr_dev) {
+ assert(tablet_is_libinput(wlr_dev));
+ struct wlr_libinput_tablet *tablet =
+ wl_container_of(wlr_dev, tablet, wlr_tablet);
+
+ struct tablet_tool_list_elem *pos;
+ wl_list_for_each(pos, &tablet->tools, link) {
+ if (pos->tool == tool) { // We already have a ref
+ // XXX: We *could* optimize the tool to the front of
+ // the list here, since we will probably get the next
+ // couple of events from the same tool.
+ // BUT the list should always be rather short (probably
+ // single digit amount of tools) so it might be more
+ // work than it saves
+ return;
+ }
+ }
+
+ struct tablet_tool_list_elem *new =
+ calloc(1, sizeof(struct tablet_tool_list_elem));
+ if (!new) {
+ wlr_log(WLR_ERROR, "Failed to allocate memory for tracking tablet tool");
+ return;
+ }
+
+ new->tool = tool;
+ wl_list_insert(&tablet->tools, &new->link);
+ ++tool->pad_refs;
+}
+
+void handle_tablet_tool_axis(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_TABLET_TOOL, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG,
+ "Got a tablet tool event for a device with no tablet tools?");
+ return;
+ }
+ struct libinput_event_tablet_tool *tevent =
+ libinput_event_get_tablet_tool_event(event);
+ struct wlr_event_tablet_tool_axis wlr_event = { 0 };
+ struct wlr_libinput_tablet_tool *tool = get_wlr_tablet_tool(
+ libinput_event_tablet_tool_get_tool(tevent));
+ ensure_tool_reference(tool, wlr_dev->tablet);
+
+ wlr_event.device = wlr_dev;
+ wlr_event.tool = &tool->wlr_tool;
+ wlr_event.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);
+ }
+ wlr_signal_emit_safe(&wlr_dev->tablet->events.axis, &wlr_event);
+}
+
+void handle_tablet_tool_proximity(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_TABLET_TOOL, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG,
+ "Got a tablet tool event for a device with no tablet tools?");
+ return;
+ }
+ struct libinput_event_tablet_tool *tevent =
+ libinput_event_get_tablet_tool_event(event);
+ struct wlr_event_tablet_tool_proximity wlr_event = { 0 };
+ struct wlr_libinput_tablet_tool *tool = get_wlr_tablet_tool(
+ libinput_event_tablet_tool_get_tool(tevent));
+ ensure_tool_reference(tool, wlr_dev->tablet);
+
+ wlr_event.tool = &tool->wlr_tool;
+ wlr_event.device = wlr_dev;
+ wlr_event.time_msec =
+ usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent));
+ 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;
+ }
+ wlr_signal_emit_safe(&wlr_dev->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, libinput_dev);
+ }
+
+ // If the tool is not unique, libinput will not find it again after the
+ // proximity out, so we should destroy it
+ if (!tool->unique &&
+ 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
+ assert(tool->pad_refs == 1);
+ assert(tablet_is_libinput(wlr_dev->tablet));
+ struct wlr_libinput_tablet *tablet =
+ wl_container_of(wlr_dev->tablet, tablet, wlr_tablet);
+ struct tablet_tool_list_elem *pos;
+ struct tablet_tool_list_elem *tmp;
+
+ wl_list_for_each_safe(pos, tmp, &tablet->tools, link) {
+ if (pos->tool == tool) {
+ wl_list_remove(&pos->link);
+ free(pos);
+ break;
+ }
+ }
+
+ destroy_tool(tool);
+ }
+}
+
+void handle_tablet_tool_tip(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_TABLET_TOOL, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG,
+ "Got a tablet tool event for a device with no tablet tools?");
+ return;
+ }
+ handle_tablet_tool_axis(event, libinput_dev);
+ struct libinput_event_tablet_tool *tevent =
+ libinput_event_get_tablet_tool_event(event);
+ struct wlr_event_tablet_tool_tip wlr_event = { 0 };
+ struct wlr_libinput_tablet_tool *tool = get_wlr_tablet_tool(
+ libinput_event_tablet_tool_get_tool(tevent));
+ ensure_tool_reference(tool, wlr_dev->tablet);
+
+ wlr_event.device = wlr_dev;
+ wlr_event.tool = &tool->wlr_tool;
+ wlr_event.time_msec =
+ usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent));
+ 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;
+ }
+ wlr_signal_emit_safe(&wlr_dev->tablet->events.tip, &wlr_event);
+}
+
+void handle_tablet_tool_button(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_TABLET_TOOL, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG,
+ "Got a tablet tool event for a device with no tablet tools?");
+ return;
+ }
+ handle_tablet_tool_axis(event, libinput_dev);
+ struct libinput_event_tablet_tool *tevent =
+ libinput_event_get_tablet_tool_event(event);
+ struct wlr_event_tablet_tool_button wlr_event = { 0 };
+ struct wlr_libinput_tablet_tool *tool = get_wlr_tablet_tool(
+ libinput_event_tablet_tool_get_tool(tevent));
+ ensure_tool_reference(tool, wlr_dev->tablet);
+
+ wlr_event.device = wlr_dev;
+ wlr_event.tool = &tool->wlr_tool;
+ wlr_event.time_msec =
+ usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent));
+ wlr_event.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;
+ }
+ wlr_signal_emit_safe(&wlr_dev->tablet->events.button, &wlr_event);
+}
diff --git a/backend/libinput/touch.c b/backend/libinput/touch.c
new file mode 100644
index 00000000..cb9b0e36
--- /dev/null
+++ b/backend/libinput/touch.c
@@ -0,0 +1,97 @@
+#include <assert.h>
+#include <libinput.h>
+#include <stdlib.h>
+#include <wlr/backend/session.h>
+#include <wlr/interfaces/wlr_touch.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/util/log.h>
+#include "backend/libinput.h"
+#include "util/signal.h"
+
+struct wlr_touch *create_libinput_touch(
+ struct libinput_device *libinput_dev) {
+ assert(libinput_dev);
+ struct wlr_touch *wlr_touch = calloc(1, sizeof(struct wlr_touch));
+ if (!wlr_touch) {
+ wlr_log(WLR_ERROR, "Unable to allocate wlr_touch");
+ return NULL;
+ }
+ wlr_touch_init(wlr_touch, NULL);
+ return wlr_touch;
+}
+
+void handle_touch_down(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_TOUCH, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG, "Got a touch event for a device with no touch?");
+ return;
+ }
+ struct libinput_event_touch *tevent =
+ libinput_event_get_touch_event(event);
+ struct wlr_event_touch_down wlr_event = { 0 };
+ wlr_event.device = wlr_dev;
+ wlr_event.time_msec =
+ usec_to_msec(libinput_event_touch_get_time_usec(tevent));
+ wlr_event.touch_id = libinput_event_touch_get_slot(tevent);
+ wlr_event.x = libinput_event_touch_get_x_transformed(tevent, 1);
+ wlr_event.y = libinput_event_touch_get_y_transformed(tevent, 1);
+ wlr_signal_emit_safe(&wlr_dev->touch->events.down, &wlr_event);
+}
+
+void handle_touch_up(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_TOUCH, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG, "Got a touch event for a device with no touch?");
+ return;
+ }
+ struct libinput_event_touch *tevent =
+ libinput_event_get_touch_event(event);
+ struct wlr_event_touch_up wlr_event = { 0 };
+ wlr_event.device = wlr_dev;
+ wlr_event.time_msec =
+ usec_to_msec(libinput_event_touch_get_time_usec(tevent));
+ wlr_event.touch_id = libinput_event_touch_get_slot(tevent);
+ wlr_signal_emit_safe(&wlr_dev->touch->events.up, &wlr_event);
+}
+
+void handle_touch_motion(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_TOUCH, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG, "Got a touch event for a device with no touch?");
+ return;
+ }
+ struct libinput_event_touch *tevent =
+ libinput_event_get_touch_event(event);
+ struct wlr_event_touch_motion wlr_event = { 0 };
+ wlr_event.device = wlr_dev;
+ wlr_event.time_msec =
+ usec_to_msec(libinput_event_touch_get_time_usec(tevent));
+ wlr_event.touch_id = libinput_event_touch_get_slot(tevent);
+ wlr_event.x = libinput_event_touch_get_x_transformed(tevent, 1);
+ wlr_event.y = libinput_event_touch_get_y_transformed(tevent, 1);
+ wlr_signal_emit_safe(&wlr_dev->touch->events.motion, &wlr_event);
+}
+
+void handle_touch_cancel(struct libinput_event *event,
+ struct libinput_device *libinput_dev) {
+ struct wlr_input_device *wlr_dev =
+ get_appropriate_device(WLR_INPUT_DEVICE_TOUCH, libinput_dev);
+ if (!wlr_dev) {
+ wlr_log(WLR_DEBUG, "Got a touch event for a device with no touch?");
+ return;
+ }
+ struct libinput_event_touch *tevent =
+ libinput_event_get_touch_event(event);
+ struct wlr_event_touch_cancel wlr_event = { 0 };
+ wlr_event.device = wlr_dev;
+ wlr_event.time_msec =
+ usec_to_msec(libinput_event_touch_get_time_usec(tevent));
+ wlr_event.touch_id = libinput_event_touch_get_slot(tevent);
+ wlr_signal_emit_safe(&wlr_dev->touch->events.cancel, &wlr_event);
+}
diff --git a/backend/meson.build b/backend/meson.build
new file mode 100644
index 00000000..bf9b4f83
--- /dev/null
+++ b/backend/meson.build
@@ -0,0 +1,61 @@
+backend_parts = []
+backend_files = files(
+ 'backend.c',
+ 'drm/atomic.c',
+ 'drm/backend.c',
+ 'drm/drm.c',
+ 'drm/legacy.c',
+ 'drm/properties.c',
+ 'drm/renderer.c',
+ 'drm/util.c',
+ 'headless/backend.c',
+ 'headless/input_device.c',
+ 'headless/output.c',
+ 'libinput/backend.c',
+ 'libinput/events.c',
+ 'libinput/keyboard.c',
+ 'libinput/pointer.c',
+ 'libinput/switch.c',
+ 'libinput/tablet_pad.c',
+ 'libinput/tablet_tool.c',
+ 'libinput/touch.c',
+ 'multi/backend.c',
+ 'session/direct-ipc.c',
+ 'session/session.c',
+ 'wayland/backend.c',
+ 'wayland/output.c',
+ 'wayland/wl_seat.c',
+)
+
+backend_deps = [
+ drm,
+ egl,
+ gbm,
+ libinput,
+ pixman,
+ xkbcommon,
+ wayland_server,
+ wlr_protos,
+ wlr_render,
+]
+
+if host_machine.system().startswith('freebsd')
+ backend_files += files('session/direct-freebsd.c')
+else
+ backend_files += files('session/direct.c')
+endif
+
+if logind.found()
+ backend_files += files('session/logind.c')
+ backend_deps += logind
+endif
+
+subdir('x11')
+
+lib_wlr_backend = static_library(
+ 'wlr_backend',
+ backend_files,
+ include_directories: wlr_inc,
+ link_whole: backend_parts,
+ dependencies: backend_deps,
+)
diff --git a/backend/multi/backend.c b/backend/multi/backend.c
new file mode 100644
index 00000000..50851109
--- /dev/null
+++ b/backend/multi/backend.c
@@ -0,0 +1,231 @@
+#define _POSIX_C_SOURCE 200112L
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <time.h>
+#include <wlr/backend/interface.h>
+#include <wlr/backend/session.h>
+#include <wlr/util/log.h>
+#include "backend/multi.h"
+#include "util/signal.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));
+ return (struct wlr_multi_backend *)wlr_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->display_destroy.link);
+
+ struct subbackend_state *sub, *next;
+ wl_list_for_each_safe(sub, next, &backend->backends, link) {
+ wlr_backend_destroy(sub->backend);
+ }
+
+ // Destroy this backend only after removing all sub-backends
+ wlr_signal_emit_safe(&wlr_backend->events.destroy, backend);
+ free(backend);
+}
+
+static struct wlr_renderer *multi_backend_get_renderer(
+ 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) {
+ struct wlr_renderer *rend = wlr_backend_get_renderer(sub->backend);
+ if (rend != NULL) {
+ return rend;
+ }
+ }
+ return NULL;
+}
+
+static struct wlr_session *multi_backend_get_session(
+ struct wlr_backend *_backend) {
+ struct wlr_multi_backend *backend = multi_backend_from_backend(_backend);
+ return backend->session;
+}
+
+static clockid_t multi_backend_get_presentation_clock(
+ 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_presentation_clock) {
+ return wlr_backend_get_presentation_clock(sub->backend);
+ }
+ }
+
+ return CLOCK_MONOTONIC;
+}
+
+struct wlr_backend_impl backend_impl = {
+ .start = multi_backend_start,
+ .destroy = multi_backend_destroy,
+ .get_renderer = multi_backend_get_renderer,
+ .get_session = multi_backend_get_session,
+ .get_presentation_clock = multi_backend_get_presentation_clock,
+};
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_multi_backend *backend =
+ wl_container_of(listener, backend, display_destroy);
+ multi_backend_destroy((struct wlr_backend*)backend);
+}
+
+struct wlr_backend *wlr_multi_backend_create(struct wl_display *display) {
+ struct wlr_multi_backend *backend =
+ calloc(1, sizeof(struct wlr_multi_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->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &backend->display_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);
+ wlr_signal_emit_safe(&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);
+ wlr_signal_emit_safe(&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) {
+ struct wlr_multi_backend *multi = multi_backend_from_backend(_multi);
+
+ if (multi_backend_get_subbackend(multi, backend)) {
+ // already added
+ return true;
+ }
+
+ struct wlr_renderer *multi_renderer =
+ multi_backend_get_renderer(&multi->backend);
+ struct wlr_renderer *backend_renderer = wlr_backend_get_renderer(backend);
+ if (multi_renderer != NULL && backend_renderer != NULL && multi_renderer != backend_renderer) {
+ wlr_log(WLR_ERROR, "Could not add backend: multiple renderers at the "
+ "same time aren't supported");
+ return false;
+ }
+
+ struct subbackend_state *sub = calloc(1, sizeof(struct subbackend_state));
+ if (sub == NULL) {
+ wlr_log(WLR_ERROR, "Could not add backend: allocation failed");
+ return false;
+ }
+ wl_list_insert(&multi->backends, &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;
+
+ wlr_signal_emit_safe(&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) {
+ wlr_signal_emit_safe(&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/session/direct-freebsd.c b/backend/session/direct-freebsd.c
new file mode 100644
index 00000000..342d0d4e
--- /dev/null
+++ b/backend/session/direct-freebsd.c
@@ -0,0 +1,268 @@
+#include <assert.h>
+#include <dev/evdev/input.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/consio.h>
+#include <sys/ioctl.h>
+#include <sys/kbio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/backend/session/interface.h>
+#include <wlr/util/log.h>
+#include <xf86drm.h>
+#include "backend/session/direct-ipc.h"
+#include "util/signal.h"
+
+const struct session_impl session_direct;
+
+struct direct_session {
+ struct wlr_session base;
+ int tty_fd;
+ int old_tty;
+ int old_kbmode;
+ int sock;
+ pid_t child;
+
+ struct wl_event_source *vt_source;
+};
+
+static struct direct_session *direct_session_from_session(
+ struct wlr_session *base) {
+ assert(base->impl == &session_direct);
+ return (struct direct_session *)base;
+}
+
+static int direct_session_open(struct wlr_session *base, const char *path) {
+ struct direct_session *session = direct_session_from_session(base);
+
+ int fd = direct_ipc_open(session->sock, path);
+ if (fd < 0) {
+ wlr_log(WLR_ERROR, "Failed to open %s: %s%s", path, strerror(-fd),
+ fd == -EINVAL ? "; is another display server running?" : "");
+ return fd;
+ }
+
+ return fd;
+}
+
+static void direct_session_close(struct wlr_session *base, int fd) {
+ struct direct_session *session = direct_session_from_session(base);
+
+ int ev;
+ struct drm_version dv = {0};
+ if (ioctl(fd, DRM_IOCTL_VERSION, &dv) == 0) {
+ direct_ipc_dropmaster(session->sock, fd);
+ } else if (ioctl(fd, EVIOCGVERSION, &ev) == 0) {
+ ioctl(fd, EVIOCREVOKE, 0);
+ }
+
+ close(fd);
+}
+
+static bool direct_change_vt(struct wlr_session *base, unsigned vt) {
+ struct direct_session *session = direct_session_from_session(base);
+
+ // Only seat0 has VTs associated with it
+ if (strcmp(session->base.seat, "seat0") != 0) {
+ return true;
+ }
+
+ return ioctl(session->tty_fd, VT_ACTIVATE, (int)vt) == 0;
+}
+
+static void direct_session_destroy(struct wlr_session *base) {
+ struct direct_session *session = direct_session_from_session(base);
+
+ if (strcmp(session->base.seat, "seat0") == 0) {
+ struct vt_mode mode = {
+ .mode = VT_AUTO,
+ };
+
+ errno = 0;
+
+ ioctl(session->tty_fd, KDSKBMODE, session->old_kbmode);
+ ioctl(session->tty_fd, KDSETMODE, KD_TEXT);
+ ioctl(session->tty_fd, VT_SETMODE, &mode);
+
+ ioctl(session->tty_fd, VT_ACTIVATE, session->old_tty);
+
+ if (errno) {
+ wlr_log(WLR_ERROR, "Failed to restore tty");
+ }
+
+ wl_event_source_remove(session->vt_source);
+ close(session->tty_fd);
+ }
+
+ direct_ipc_finish(session->sock, session->child);
+ close(session->sock);
+
+ free(session);
+}
+
+static int vt_handler(int signo, void *data) {
+ struct direct_session *session = data;
+ struct drm_version dv = {0};
+ struct wlr_device *dev;
+
+ if (session->base.active) {
+ session->base.active = false;
+ wlr_signal_emit_safe(&session->base.session_signal, session);
+
+ wl_list_for_each(dev, &session->base.devices, link) {
+ if (ioctl(dev->fd, DRM_IOCTL_VERSION, &dv) == 0) {
+ direct_ipc_dropmaster(session->sock, dev->fd);
+ }
+ }
+
+ ioctl(session->tty_fd, VT_RELDISP, 1);
+ } else {
+ ioctl(session->tty_fd, VT_RELDISP, VT_ACKACQ);
+
+ wl_list_for_each(dev, &session->base.devices, link) {
+ if (ioctl(dev->fd, DRM_IOCTL_VERSION, &dv) == 0) {
+ direct_ipc_setmaster(session->sock, dev->fd);
+ }
+ }
+
+ session->base.active = true;
+ wlr_signal_emit_safe(&session->base.session_signal, session);
+ }
+
+ return 1;
+}
+
+static bool setup_tty(struct direct_session *session, struct wl_display *display) {
+ int fd = -1, tty = -1, tty0_fd = -1, old_tty = 1;
+ if ((tty0_fd = open("/dev/ttyv0", O_RDWR | O_CLOEXEC)) < 0) {
+ wlr_log_errno(WLR_ERROR, "Could not open /dev/ttyv0 to find a free vt");
+ goto error;
+ }
+ if (ioctl(tty0_fd, VT_GETACTIVE, &old_tty) != 0) {
+ wlr_log_errno(WLR_ERROR, "Could not get active vt");
+ goto error;
+ }
+ if (ioctl(tty0_fd, VT_OPENQRY, &tty) != 0) {
+ wlr_log_errno(WLR_ERROR, "Could not find a free vt");
+ goto error;
+ }
+ close(tty0_fd);
+ char tty_path[64];
+ snprintf(tty_path, sizeof(tty_path), "/dev/ttyv%d", tty - 1);
+ wlr_log(WLR_INFO, "Using tty %s", tty_path);
+ fd = open(tty_path, O_RDWR | O_NOCTTY | O_CLOEXEC);
+
+ if (fd == -1) {
+ wlr_log_errno(WLR_ERROR, "Cannot open tty");
+ return false;
+ }
+
+ ioctl(fd, VT_ACTIVATE, tty);
+ ioctl(fd, VT_WAITACTIVE, tty);
+
+ int old_kbmode;
+ if (ioctl(fd, KDGKBMODE, &old_kbmode)) {
+ wlr_log_errno(WLR_ERROR, "Failed to read tty %d keyboard mode", tty);
+ goto error;
+ }
+
+ if (ioctl(fd, KDSKBMODE, K_CODE)) {
+ wlr_log_errno(WLR_ERROR,
+ "Failed to set keyboard mode K_CODE on tty %d", tty);
+ goto error;
+ }
+
+ if (ioctl(fd, KDSETMODE, KD_GRAPHICS)) {
+ wlr_log_errno(WLR_ERROR, "Failed to set graphics mode on tty %d", tty);
+ goto error;
+ }
+
+ struct vt_mode mode = {
+ .mode = VT_PROCESS,
+ .relsig = SIGUSR2,
+ .acqsig = SIGUSR2,
+ .frsig = SIGIO, // has to be set
+ };
+
+ if (ioctl(fd, VT_SETMODE, &mode) < 0) {
+ wlr_log(WLR_ERROR, "Failed to take control of tty %d", tty);
+ goto error;
+ }
+
+ struct wl_event_loop *loop = wl_display_get_event_loop(display);
+ session->vt_source = wl_event_loop_add_signal(loop, SIGUSR2,
+ vt_handler, session);
+ if (!session->vt_source) {
+ goto error;
+ }
+
+ session->base.vtnr = tty;
+ session->tty_fd = fd;
+ session->old_tty = old_tty;
+ session->old_kbmode = old_kbmode;
+
+ return true;
+
+error:
+ // In case we could not get the last active one, drop back to tty 1,
+ // better than hanging in a useless blank console. Otherwise activate the
+ // last active.
+ ioctl(fd, VT_ACTIVATE, old_tty);
+ close(fd);
+ return false;
+}
+
+static struct wlr_session *direct_session_create(struct wl_display *disp) {
+ struct direct_session *session = calloc(1, sizeof(*session));
+ if (!session) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ return NULL;
+ }
+
+ session->sock = direct_ipc_init(&session->child);
+ if (session->sock == -1) {
+ goto error_session;
+ }
+
+ const char *seat = getenv("XDG_SEAT");
+ if (!seat) {
+ seat = "seat0";
+ }
+
+ if (strcmp(seat, "seat0") == 0) {
+ if (!setup_tty(session, disp)) {
+ goto error_ipc;
+ }
+ } else {
+ session->base.vtnr = 0;
+ session->tty_fd = -1;
+ }
+
+ wlr_log(WLR_INFO, "Successfully loaded direct session");
+
+ snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat);
+ session->base.impl = &session_direct;
+ return &session->base;
+
+error_ipc:
+ direct_ipc_finish(session->sock, session->child);
+ close(session->sock);
+error_session:
+ free(session);
+ return NULL;
+}
+
+const struct session_impl session_direct = {
+ .create = direct_session_create,
+ .destroy = direct_session_destroy,
+ .open = direct_session_open,
+ .close = direct_session_close,
+ .change_vt = direct_change_vt,
+};
diff --git a/backend/session/direct-ipc.c b/backend/session/direct-ipc.c
new file mode 100644
index 00000000..2b9634da
--- /dev/null
+++ b/backend/session/direct-ipc.c
@@ -0,0 +1,269 @@
+#define _POSIX_C_SOURCE 200809L
+#ifdef __FreeBSD__
+#define __BSD_VISIBLE 1
+#include <dev/evdev/input.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <wlr/config.h>
+#include <wlr/util/log.h>
+#include <xf86drm.h>
+#ifdef __linux__
+#include <sys/sysmacros.h>
+#include <linux/major.h>
+#endif
+#include "backend/session/direct-ipc.h"
+
+enum { DRM_MAJOR = 226 };
+
+#if WLR_HAS_LIBCAP
+#include <sys/capability.h>
+
+static bool have_permissions(void) {
+ cap_t cap = cap_get_proc();
+ cap_flag_value_t val;
+
+ if (!cap || cap_get_flag(cap, CAP_SYS_ADMIN, CAP_PERMITTED, &val) || val != CAP_SET) {
+ wlr_log(WLR_ERROR, "Do not have CAP_SYS_ADMIN; cannot become DRM master");
+ cap_free(cap);
+ return false;
+ }
+
+ cap_free(cap);
+ return true;
+}
+#else
+static bool have_permissions(void) {
+#ifdef __linux__
+ if (geteuid() != 0) {
+ wlr_log(WLR_ERROR, "Do not have root privileges; cannot become DRM master");
+ return false;
+ }
+#endif
+ return true;
+}
+#endif
+
+static void send_msg(int sock, int fd, void *buf, size_t buf_len) {
+ char control[CMSG_SPACE(sizeof(fd))] = {0};
+ struct iovec iovec = { .iov_base = buf, .iov_len = buf_len };
+ struct msghdr msghdr = {0};
+
+ if (buf) {
+ msghdr.msg_iov = &iovec;
+ msghdr.msg_iovlen = 1;
+ }
+
+ if (fd >= 0) {
+ msghdr.msg_control = &control;
+ msghdr.msg_controllen = sizeof(control);
+
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msghdr);
+ *cmsg = (struct cmsghdr) {
+ .cmsg_level = SOL_SOCKET,
+ .cmsg_type = SCM_RIGHTS,
+ .cmsg_len = CMSG_LEN(sizeof(fd)),
+ };
+ memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
+ }
+
+ ssize_t ret;
+ do {
+ ret = sendmsg(sock, &msghdr, 0);
+ } while (ret < 0 && errno == EINTR);
+}
+
+static ssize_t recv_msg(int sock, int *fd_out, void *buf, size_t buf_len) {
+ char control[CMSG_SPACE(sizeof(*fd_out))] = {0};
+ struct iovec iovec = { .iov_base = buf, .iov_len = buf_len };
+ struct msghdr msghdr = {0};
+
+ if (buf) {
+ msghdr.msg_iov = &iovec;
+ msghdr.msg_iovlen = 1;
+ }
+
+ if (fd_out) {
+ msghdr.msg_control = &control;
+ msghdr.msg_controllen = sizeof(control);
+ }
+
+ ssize_t ret;
+ do {
+ ret = recvmsg(sock, &msghdr, MSG_CMSG_CLOEXEC);
+ } while (ret < 0 && errno == EINTR);
+
+ if (fd_out) {
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msghdr);
+ if (cmsg) {
+ memcpy(fd_out, CMSG_DATA(cmsg), sizeof(*fd_out));
+ } else {
+ *fd_out = -1;
+ }
+ }
+
+ return ret;
+}
+
+enum msg_type {
+ MSG_OPEN,
+ MSG_SETMASTER,
+ MSG_DROPMASTER,
+ MSG_END,
+};
+
+struct msg {
+ enum msg_type type;
+ char path[256];
+};
+
+static void communicate(int sock) {
+ struct msg msg;
+ int drm_fd = -1;
+ bool running = true;
+
+ while (running && recv_msg(sock, &drm_fd, &msg, sizeof(msg)) > 0) {
+ switch (msg.type) {
+ case MSG_OPEN:
+ errno = 0;
+
+ // These are the same flags that logind opens files with
+ int fd = open(msg.path, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+ int ret = errno;
+ if (fd == -1) {
+ goto error;
+ }
+
+#ifndef __FreeBSD__
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ ret = errno;
+ goto error;
+ }
+
+ uint32_t maj = major(st.st_rdev);
+ if (maj != INPUT_MAJOR && maj != DRM_MAJOR) {
+ ret = ENOTSUP;
+ goto error;
+ }
+
+ if (maj == DRM_MAJOR && drmSetMaster(fd)) {
+ ret = errno;
+ }
+#else
+ int ev;
+ struct drm_version dv = {0};
+ if (ioctl(fd, EVIOCGVERSION, &ev) == -1 &&
+ ioctl(fd, DRM_IOCTL_VERSION, &dv) == -1) {
+ ret = ENOTSUP;
+ goto error;
+ }
+
+ if (dv.version_major != 0 && drmSetMaster(fd)) {
+ ret = errno;
+ }
+#endif
+
+error:
+ send_msg(sock, ret ? -1 : fd, &ret, sizeof(ret));
+ if (fd >= 0) {
+ close(fd);
+ }
+
+ break;
+
+ case MSG_SETMASTER:
+ drmSetMaster(drm_fd);
+ close(drm_fd);
+ send_msg(sock, -1, NULL, 0);
+ break;
+
+ case MSG_DROPMASTER:
+ drmDropMaster(drm_fd);
+ close(drm_fd);
+ send_msg(sock, -1, NULL, 0);
+ break;
+
+ case MSG_END:
+ running = false;
+ send_msg(sock, -1, NULL, 0);
+ break;
+ }
+ }
+
+ close(sock);
+}
+
+int direct_ipc_open(int sock, const char *path) {
+ struct msg msg = { .type = MSG_OPEN };
+ snprintf(msg.path, sizeof(msg.path), "%s", path);
+
+ send_msg(sock, -1, &msg, sizeof(msg));
+
+ int fd, err, ret;
+ int retry = 0;
+ do {
+ ret = recv_msg(sock, &fd, &err, sizeof(err));
+ } while (ret == 0 && retry++ < 3);
+
+ return err ? -err : fd;
+}
+
+void direct_ipc_setmaster(int sock, int fd) {
+ struct msg msg = { .type = MSG_SETMASTER };
+
+ send_msg(sock, fd, &msg, sizeof(msg));
+ recv_msg(sock, NULL, NULL, 0);
+}
+
+void direct_ipc_dropmaster(int sock, int fd) {
+ struct msg msg = { .type = MSG_DROPMASTER };
+
+ send_msg(sock, fd, &msg, sizeof(msg));
+ recv_msg(sock, NULL, NULL, 0);
+}
+
+void direct_ipc_finish(int sock, pid_t pid) {
+ struct msg msg = { .type = MSG_END };
+
+ send_msg(sock, -1, &msg, sizeof(msg));
+ recv_msg(sock, NULL, NULL, 0);
+
+ waitpid(pid, NULL, 0);
+}
+
+int direct_ipc_init(pid_t *pid_out) {
+ if (!have_permissions()) {
+ return -1;
+ }
+
+ int sock[2];
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sock) < 0) {
+ wlr_log_errno(WLR_ERROR, "Failed to create socket pair");
+ return -1;
+ }
+
+ pid_t pid = fork();
+ if (pid < 0) {
+ wlr_log_errno(WLR_ERROR, "Fork failed");
+ close(sock[0]);
+ close(sock[1]);
+ return -1;
+ } else if (pid == 0) {
+ close(sock[0]);
+ communicate(sock[1]);
+ _Exit(0);
+ }
+
+ close(sock[1]);
+ *pid_out = pid;
+ return sock[0];
+}
diff --git a/backend/session/direct.c b/backend/session/direct.c
new file mode 100644
index 00000000..0912cd58
--- /dev/null
+++ b/backend/session/direct.c
@@ -0,0 +1,278 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/input.h>
+#include <linux/kd.h>
+#include <linux/major.h>
+#include <linux/vt.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/backend/session/interface.h>
+#include <wlr/util/log.h>
+#include "backend/session/direct-ipc.h"
+#include "util/signal.h"
+
+enum { DRM_MAJOR = 226 };
+
+const struct session_impl session_direct;
+
+struct direct_session {
+ struct wlr_session base;
+ int tty_fd;
+ int old_kbmode;
+ int sock;
+ pid_t child;
+
+ struct wl_event_source *vt_source;
+};
+
+static struct direct_session *direct_session_from_session(
+ struct wlr_session *base) {
+ assert(base->impl == &session_direct);
+ return (struct direct_session *)base;
+}
+
+static int direct_session_open(struct wlr_session *base, const char *path) {
+ struct direct_session *session = direct_session_from_session(base);
+
+ int fd = direct_ipc_open(session->sock, path);
+ if (fd < 0) {
+ wlr_log(WLR_ERROR, "Failed to open %s: %s%s", path, strerror(-fd),
+ fd == -EINVAL ? "; is another display server running?" : "");
+ return fd;
+ }
+
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ close(fd);
+ return -errno;
+ }
+
+ if (major(st.st_rdev) == DRM_MAJOR) {
+ direct_ipc_setmaster(session->sock, fd);
+ }
+
+ return fd;
+}
+
+static void direct_session_close(struct wlr_session *base, int fd) {
+ struct direct_session *session = direct_session_from_session(base);
+
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ wlr_log_errno(WLR_ERROR, "Stat failed");
+ close(fd);
+ return;
+ }
+
+ if (major(st.st_rdev) == DRM_MAJOR) {
+ direct_ipc_dropmaster(session->sock, fd);
+ } else if (major(st.st_rdev) == INPUT_MAJOR) {
+ ioctl(fd, EVIOCREVOKE, 0);
+ }
+
+ close(fd);
+}
+
+static bool direct_change_vt(struct wlr_session *base, unsigned vt) {
+ struct direct_session *session = direct_session_from_session(base);
+
+ // Only seat0 has VTs associated with it
+ if (strcmp(session->base.seat, "seat0") != 0) {
+ return true;
+ }
+
+ return ioctl(session->tty_fd, VT_ACTIVATE, (int)vt) == 0;
+}
+
+static void direct_session_destroy(struct wlr_session *base) {
+ struct direct_session *session = direct_session_from_session(base);
+
+ if (strcmp(session->base.seat, "seat0") == 0) {
+ struct vt_mode mode = {
+ .mode = VT_AUTO,
+ };
+ errno = 0;
+
+ ioctl(session->tty_fd, KDSKBMODE, session->old_kbmode);
+ ioctl(session->tty_fd, KDSETMODE, KD_TEXT);
+ ioctl(session->tty_fd, VT_SETMODE, &mode);
+
+ if (errno) {
+ wlr_log(WLR_ERROR, "Failed to restore tty");
+ }
+
+ wl_event_source_remove(session->vt_source);
+ close(session->tty_fd);
+ }
+
+ direct_ipc_finish(session->sock, session->child);
+ close(session->sock);
+
+ free(session);
+}
+
+static int vt_handler(int signo, void *data) {
+ struct direct_session *session = data;
+
+ if (session->base.active) {
+ session->base.active = false;
+ wlr_signal_emit_safe(&session->base.session_signal, session);
+
+ struct wlr_device *dev;
+ wl_list_for_each(dev, &session->base.devices, link) {
+ if (major(dev->dev) == DRM_MAJOR) {
+ direct_ipc_dropmaster(session->sock,
+ dev->fd);
+ }
+ }
+
+ ioctl(session->tty_fd, VT_RELDISP, 1);
+ } else {
+ ioctl(session->tty_fd, VT_RELDISP, VT_ACKACQ);
+
+ struct wlr_device *dev;
+ wl_list_for_each(dev, &session->base.devices, link) {
+ if (major(dev->dev) == DRM_MAJOR) {
+ direct_ipc_setmaster(session->sock,
+ dev->fd);
+ }
+ }
+
+ session->base.active = true;
+ wlr_signal_emit_safe(&session->base.session_signal, session);
+ }
+
+ return 1;
+}
+
+static bool setup_tty(struct direct_session *session, struct wl_display *display) {
+ int fd = open("/dev/tty", O_RDWR);
+ if (fd == -1) {
+ wlr_log_errno(WLR_ERROR, "Cannot open /dev/tty");
+ return false;
+ }
+
+ struct vt_stat vt_stat;
+ if (ioctl(fd, VT_GETSTATE, &vt_stat)) {
+ wlr_log_errno(WLR_ERROR, "Could not get current tty number");
+ goto error;
+ }
+
+ int tty = vt_stat.v_active;
+ int ret, kd_mode, old_kbmode;
+
+ ret = ioctl(fd, KDGETMODE, &kd_mode);
+ if (ret) {
+ wlr_log_errno(WLR_ERROR, "Failed to get tty mode");
+ goto error;
+ }
+
+ if (kd_mode != KD_TEXT) {
+ wlr_log(WLR_ERROR,
+ "tty already in graphics mode; is another display server running?");
+ goto error;
+ }
+
+ ioctl(fd, VT_ACTIVATE, tty);
+ ioctl(fd, VT_WAITACTIVE, tty);
+
+ if (ioctl(fd, KDGKBMODE, &old_kbmode)) {
+ wlr_log_errno(WLR_ERROR, "Failed to read keyboard mode");
+ goto error;
+ }
+
+ if (ioctl(fd, KDSKBMODE, K_OFF)) {
+ wlr_log_errno(WLR_ERROR, "Failed to set keyboard mode");
+ goto error;
+ }
+
+ if (ioctl(fd, KDSETMODE, KD_GRAPHICS)) {
+ wlr_log_errno(WLR_ERROR, "Failed to set graphics mode on tty");
+ goto error;
+ }
+
+ struct vt_mode mode = {
+ .mode = VT_PROCESS,
+ .relsig = SIGUSR2,
+ .acqsig = SIGUSR2,
+ };
+
+ if (ioctl(fd, VT_SETMODE, &mode) < 0) {
+ wlr_log(WLR_ERROR, "Failed to take control of tty");
+ goto error;
+ }
+
+ struct wl_event_loop *loop = wl_display_get_event_loop(display);
+ session->vt_source = wl_event_loop_add_signal(loop, SIGUSR2,
+ vt_handler, session);
+ if (!session->vt_source) {
+ goto error;
+ }
+
+ session->base.vtnr = tty;
+ session->tty_fd = fd;
+ session->old_kbmode = old_kbmode;
+
+ return true;
+
+error:
+ close(fd);
+ return false;
+}
+
+static struct wlr_session *direct_session_create(struct wl_display *disp) {
+ struct direct_session *session = calloc(1, sizeof(*session));
+ if (!session) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ return NULL;
+ }
+
+ session->sock = direct_ipc_init(&session->child);
+ if (session->sock == -1) {
+ goto error_session;
+ }
+
+ const char *seat = getenv("XDG_SEAT");
+ if (!seat) {
+ seat = "seat0";
+ }
+
+ if (strcmp(seat, "seat0") == 0) {
+ if (!setup_tty(session, disp)) {
+ goto error_ipc;
+ }
+ } else {
+ session->base.vtnr = 0;
+ session->tty_fd = -1;
+ }
+
+ snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat);
+ session->base.impl = &session_direct;
+
+ wlr_log(WLR_INFO, "Successfully loaded direct session");
+ return &session->base;
+
+error_ipc:
+ direct_ipc_finish(session->sock, session->child);
+ close(session->sock);
+error_session:
+ free(session);
+ return NULL;
+}
+
+const struct session_impl session_direct = {
+ .create = direct_session_create,
+ .destroy = direct_session_destroy,
+ .open = direct_session_open,
+ .close = direct_session_close,
+ .change_vt = direct_change_vt,
+};
diff --git a/backend/session/logind.c b/backend/session/logind.c
new file mode 100644
index 00000000..9a1383ce
--- /dev/null
+++ b/backend/session/logind.c
@@ -0,0 +1,562 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/backend/session/interface.h>
+#include <wlr/config.h>
+#include <wlr/util/log.h>
+#include "util/signal.h"
+
+#if WLR_HAS_SYSTEMD
+ #include <systemd/sd-bus.h>
+ #include <systemd/sd-login.h>
+#elif WLR_HAS_ELOGIND
+ #include <elogind/sd-bus.h>
+ #include <elogind/sd-login.h>
+#endif
+
+enum { DRM_MAJOR = 226 };
+
+const struct session_impl session_logind;
+
+struct logind_session {
+ struct wlr_session base;
+
+ sd_bus *bus;
+ struct wl_event_source *event;
+
+ char *id;
+ char *path;
+
+ // specifies whether a drm device was taken
+ // if so, the session will be (de)activated with the drm fd,
+ // otherwise with the dbus PropertiesChanged on "active" signal
+ bool has_drm;
+};
+
+static struct logind_session *logind_session_from_session(
+ struct wlr_session *base) {
+ assert(base->impl == &session_logind);
+ return (struct logind_session *)base;
+}
+
+static int logind_take_device(struct wlr_session *base, const char *path) {
+ struct logind_session *session = logind_session_from_session(base);
+
+ int fd = -1;
+ int ret;
+ sd_bus_message *msg = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ struct stat st;
+ if (stat(path, &st) < 0) {
+ wlr_log(WLR_ERROR, "Failed to stat '%s'", path);
+ return -1;
+ }
+
+ if (major(st.st_rdev) == DRM_MAJOR) {
+ session->has_drm = true;
+ }
+
+ ret = sd_bus_call_method(session->bus, "org.freedesktop.login1",
+ session->path, "org.freedesktop.login1.Session", "TakeDevice",
+ &error, &msg, "uu", major(st.st_rdev), minor(st.st_rdev));
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to take device '%s': %s", path,
+ error.message);
+ goto out;
+ }
+
+ int paused = 0;
+ ret = sd_bus_message_read(msg, "hb", &fd, &paused);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to parse D-Bus response for '%s': %s",
+ path, strerror(-ret));
+ goto out;
+ }
+
+ // The original fd seems to be closed when the message is freed
+ // so we just clone it.
+ fd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
+ if (fd < 0) {
+ wlr_log(WLR_ERROR, "Failed to clone file descriptor for '%s': %s",
+ path, strerror(errno));
+ goto out;
+ }
+
+out:
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(msg);
+ return fd;
+}
+
+static void logind_release_device(struct wlr_session *base, int fd) {
+ struct logind_session *session = logind_session_from_session(base);
+
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ wlr_log(WLR_ERROR, "Failed to stat device '%d': %s", fd,
+ strerror(errno));
+ return;
+ }
+
+ sd_bus_message *msg = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+ int ret = sd_bus_call_method(session->bus, "org.freedesktop.login1",
+ session->path, "org.freedesktop.login1.Session", "ReleaseDevice",
+ &error, &msg, "uu", major(st.st_rdev), minor(st.st_rdev));
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to release device '%d': %s", fd,
+ error.message);
+ }
+
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(msg);
+ close(fd);
+}
+
+static bool logind_change_vt(struct wlr_session *base, unsigned vt) {
+ struct logind_session *session = logind_session_from_session(base);
+
+ // Only seat0 has VTs associated with it
+ if (strcmp(session->base.seat, "seat0") != 0) {
+ return true;
+ }
+
+ int ret;
+ sd_bus_message *msg = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ ret = sd_bus_call_method(session->bus, "org.freedesktop.login1",
+ "/org/freedesktop/login1/seat/self", "org.freedesktop.login1.Seat", "SwitchTo",
+ &error, &msg, "u", (uint32_t)vt);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to change to vt '%d'", vt);
+ }
+
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(msg);
+ return ret >= 0;
+}
+
+static bool find_session_path(struct logind_session *session) {
+ int ret;
+ sd_bus_message *msg = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ ret = sd_bus_call_method(session->bus, "org.freedesktop.login1",
+ "/org/freedesktop/login1", "org.freedesktop.login1.Manager",
+ "GetSession", &error, &msg, "s", session->id);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to get session path: %s", error.message);
+ goto out;
+ }
+
+ const char *path;
+ ret = sd_bus_message_read(msg, "o", &path);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Could not parse session path: %s", error.message);
+ goto out;
+ }
+ session->path = strdup(path);
+
+out:
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(msg);
+
+ return ret >= 0;
+}
+
+static bool session_activate(struct logind_session *session) {
+ int ret;
+ sd_bus_message *msg = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ ret = sd_bus_call_method(session->bus, "org.freedesktop.login1",
+ session->path, "org.freedesktop.login1.Session", "Activate",
+ &error, &msg, "");
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to activate session: %s", error.message);
+ }
+
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(msg);
+ return ret >= 0;
+}
+
+static bool take_control(struct logind_session *session) {
+ int ret;
+ sd_bus_message *msg = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ ret = sd_bus_call_method(session->bus, "org.freedesktop.login1",
+ session->path, "org.freedesktop.login1.Session", "TakeControl",
+ &error, &msg, "b", false);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to take control of session: %s",
+ error.message);
+ }
+
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(msg);
+ return ret >= 0;
+}
+
+static void release_control(struct logind_session *session) {
+ int ret;
+ sd_bus_message *msg = NULL;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ ret = sd_bus_call_method(session->bus, "org.freedesktop.login1",
+ session->path, "org.freedesktop.login1.Session", "ReleaseControl",
+ &error, &msg, "");
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to release control of session: %s",
+ error.message);
+ }
+
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(msg);
+}
+
+static void logind_session_destroy(struct wlr_session *base) {
+ struct logind_session *session = logind_session_from_session(base);
+
+ release_control(session);
+
+ wl_event_source_remove(session->event);
+ sd_bus_unref(session->bus);
+ free(session->id);
+ free(session->path);
+ free(session);
+}
+
+static int session_removed(sd_bus_message *msg, void *userdata,
+ sd_bus_error *ret_error) {
+ wlr_log(WLR_INFO, "SessionRemoved signal received");
+ return 0;
+}
+
+static struct wlr_device *find_device(struct wlr_session *session,
+ dev_t devnum) {
+ struct wlr_device *dev;
+
+ wl_list_for_each(dev, &session->devices, link) {
+ if (dev->dev == devnum) {
+ return dev;
+ }
+ }
+
+ wlr_log(WLR_ERROR, "Tried to use dev_t %lu not opened by session",
+ (unsigned long)devnum);
+ assert(0);
+}
+
+static int pause_device(sd_bus_message *msg, void *userdata,
+ sd_bus_error *ret_error) {
+ struct logind_session *session = userdata;
+ int ret;
+
+ uint32_t major, minor;
+ const char *type;
+ ret = sd_bus_message_read(msg, "uus", &major, &minor, &type);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to parse D-Bus response for PauseDevice: %s",
+ strerror(-ret));
+ goto error;
+ }
+
+ if (major == DRM_MAJOR) {
+ assert(session->has_drm);
+ session->base.active = false;
+ wlr_signal_emit_safe(&session->base.session_signal, session);
+ }
+
+ if (strcmp(type, "pause") == 0) {
+ ret = sd_bus_call_method(session->bus, "org.freedesktop.login1",
+ session->path, "org.freedesktop.login1.Session", "PauseDeviceComplete",
+ ret_error, &msg, "uu", major, minor);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to send PauseDeviceComplete signal: %s",
+ strerror(-ret));
+ }
+ }
+
+error:
+ return 0;
+}
+
+static int resume_device(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) {
+ struct logind_session *session = userdata;
+ int ret;
+
+ int fd;
+ uint32_t major, minor;
+ ret = sd_bus_message_read(msg, "uuh", &major, &minor, &fd);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to parse D-Bus response for ResumeDevice: %s",
+ strerror(-ret));
+ goto error;
+ }
+
+ if (major == DRM_MAJOR) {
+ struct wlr_device *dev = find_device(&session->base, makedev(major, minor));
+ dup2(fd, dev->fd);
+
+ if (!session->base.active) {
+ session->base.active = true;
+ wlr_signal_emit_safe(&session->base.session_signal, session);
+ }
+ }
+
+error:
+ return 0;
+}
+
+static int properties_changed(sd_bus_message *msg, void *userdata,
+ sd_bus_error *ret_error) {
+ struct logind_session *session = userdata;
+ int ret = 0;
+
+ // if we have a drm fd we don't depend on this
+ if (session->has_drm) {
+ return 0;
+ }
+
+ // PropertiesChanged arg 1: interface
+ const char *interface;
+ ret = sd_bus_message_read_basic(msg, 's', &interface); // skip path
+ if (ret < 0) {
+ goto error;
+ }
+
+ if (strcmp(interface, "org.freedesktop.login1.Session") != 0) {
+ // not interesting for us; ignore
+ wlr_log(WLR_DEBUG, "ignoring PropertiesChanged from %s", interface);
+ return 0;
+ }
+
+ // PropertiesChanged arg 2: changed properties with values
+ ret = sd_bus_message_enter_container(msg, 'a', "{sv}");
+ if (ret < 0) {
+ goto error;
+ }
+
+ const char *s;
+ while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) {
+ ret = sd_bus_message_read_basic(msg, 's', &s);
+ if (ret < 0) {
+ goto error;
+ }
+
+ if (strcmp(s, "Active") == 0) {
+ int ret;
+ ret = sd_bus_message_enter_container(msg, 'v', "b");
+ if (ret < 0) {
+ goto error;
+ }
+
+ bool active;
+ ret = sd_bus_message_read_basic(msg, 'b', &active);
+ if (ret < 0) {
+ goto error;
+ }
+
+ if (session->base.active != active) {
+ session->base.active = active;
+ wlr_signal_emit_safe(&session->base.session_signal, session);
+ }
+ return 0;
+ } else {
+ sd_bus_message_skip(msg, "{sv}");
+ }
+
+ ret = sd_bus_message_exit_container(msg);
+ if (ret < 0) {
+ goto error;
+ }
+ }
+
+ if (ret < 0) {
+ goto error;
+ }
+
+ ret = sd_bus_message_exit_container(msg);
+ if (ret < 0) {
+ goto error;
+ }
+
+ // PropertiesChanged arg 3: changed properties without values
+ sd_bus_message_enter_container(msg, 'a', "s");
+ while ((ret = sd_bus_message_read_basic(msg, 's', &s)) > 0) {
+ if (strcmp(s, "Active") == 0) {
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+ bool active;
+ ret = sd_bus_get_property_trivial(session->bus,
+ "org.freedesktop.login1", session->path,
+ "org.freedesktop.login1.Session", "Active", &error,
+ 'b', &active);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to get 'Active' property: %s",
+ error.message);
+ return 0;
+ }
+
+ if (session->base.active != active) {
+ session->base.active = active;
+ wlr_signal_emit_safe(&session->base.session_signal, session);
+ }
+ return 0;
+ }
+ }
+
+ if (ret < 0) {
+ goto error;
+ }
+
+ return 0;
+
+error:
+ wlr_log(WLR_ERROR, "Failed to parse D-Bus PropertiesChanged: %s",
+ strerror(-ret));
+ return 0;
+}
+
+static bool add_signal_matches(struct logind_session *session) {
+ int ret;
+
+ char str[256];
+ const char *fmt = "type='signal',"
+ "sender='org.freedesktop.login1',"
+ "interface='org.freedesktop.login1.%s',"
+ "member='%s',"
+ "path='%s'";
+
+ snprintf(str, sizeof(str), fmt, "Manager", "SessionRemoved",
+ "/org/freedesktop/login1");
+ ret = sd_bus_add_match(session->bus, NULL, str, session_removed, session);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret));
+ return false;
+ }
+
+ snprintf(str, sizeof(str), fmt, "Session", "PauseDevice", session->path);
+ ret = sd_bus_add_match(session->bus, NULL, str, pause_device, session);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret));
+ return false;
+ }
+
+ snprintf(str, sizeof(str), fmt, "Session", "ResumeDevice", session->path);
+ ret = sd_bus_add_match(session->bus, NULL, str, resume_device, session);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret));
+ return false;
+ }
+
+ ret = sd_bus_match_signal(session->bus, NULL, "org.freedesktop.login1",
+ session->path, "org.freedesktop.DBus.Properties", "PropertiesChanged",
+ properties_changed, session);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret));
+ return false;
+ }
+
+ return true;
+}
+
+static int dbus_event(int fd, uint32_t mask, void *data) {
+ sd_bus *bus = data;
+ while (sd_bus_process(bus, NULL) > 0) {
+ // Do nothing.
+ }
+ return 1;
+}
+
+static struct wlr_session *logind_session_create(struct wl_display *disp) {
+ int ret;
+ struct logind_session *session = calloc(1, sizeof(*session));
+ if (!session) {
+ wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno));
+ return NULL;
+ }
+
+ ret = sd_pid_get_session(getpid(), &session->id);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to get session id: %s", strerror(-ret));
+ goto error;
+ }
+
+ char *seat;
+ ret = sd_session_get_seat(session->id, &seat);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to get seat id: %s", strerror(-ret));
+ goto error;
+ }
+ snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat);
+
+ if (strcmp(seat, "seat0") == 0) {
+ ret = sd_session_get_vt(session->id, &session->base.vtnr);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Session not running in virtual terminal");
+ goto error;
+ }
+ }
+ free(seat);
+
+ ret = sd_bus_default_system(&session->bus);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to open D-Bus connection: %s", strerror(-ret));
+ goto error;
+ }
+
+ if (!find_session_path(session)) {
+ sd_bus_unref(session->bus);
+ goto error;
+ }
+
+ if (!add_signal_matches(session)) {
+ goto error_bus;
+ }
+
+ struct wl_event_loop *event_loop = wl_display_get_event_loop(disp);
+ session->event = wl_event_loop_add_fd(event_loop, sd_bus_get_fd(session->bus),
+ WL_EVENT_READABLE, dbus_event, session->bus);
+
+ if (!session_activate(session)) {
+ goto error_bus;
+ }
+
+ if (!take_control(session)) {
+ goto error_bus;
+ }
+
+ wlr_log(WLR_INFO, "Successfully loaded logind session");
+
+ session->base.impl = &session_logind;
+ return &session->base;
+
+error_bus:
+ sd_bus_unref(session->bus);
+ free(session->path);
+
+error:
+ free(session->id);
+ return NULL;
+}
+
+const struct session_impl session_logind = {
+ .create = logind_session_create,
+ .destroy = logind_session_destroy,
+ .open = logind_take_device,
+ .close = logind_release_device,
+ .change_vt = logind_change_vt,
+};
diff --git a/backend/session/session.c b/backend/session/session.c
new file mode 100644
index 00000000..96cde65c
--- /dev/null
+++ b/backend/session/session.c
@@ -0,0 +1,359 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <libudev.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <wayland-server.h>
+#include <wlr/backend/session.h>
+#include <wlr/backend/session/interface.h>
+#include <wlr/config.h>
+#include <wlr/util/log.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include "util/signal.h"
+
+extern const struct session_impl session_logind;
+extern const struct session_impl session_direct;
+
+static const struct session_impl *impls[] = {
+#if WLR_HAS_SYSTEMD || WLR_HAS_ELOGIND
+ &session_logind,
+#endif
+ &session_direct,
+ NULL,
+};
+
+static int 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 *action = udev_device_get_action(udev_dev);
+
+ wlr_log(WLR_DEBUG, "udev event for %s (%s)",
+ udev_device_get_sysname(udev_dev), action);
+
+ if (!action || strcmp(action, "change") != 0) {
+ goto out;
+ }
+
+ 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) {
+ wlr_signal_emit_safe(&dev->signal, session);
+ break;
+ }
+ }
+
+out:
+ udev_device_unref(udev_dev);
+ return 1;
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_session *session =
+ wl_container_of(listener, session, display_destroy);
+ wlr_session_destroy(session);
+}
+
+struct wlr_session *wlr_session_create(struct wl_display *disp) {
+ struct wlr_session *session = NULL;
+
+ const char *env_wlr_session = getenv("WLR_SESSION");
+ if (env_wlr_session) {
+ if (!strcmp(env_wlr_session, "logind") || !strcmp(env_wlr_session, "systemd")) {
+ #if WLR_HAS_SYSTEMD || WLR_HAS_ELOGIND
+ session = session_logind.create(disp);
+ #else
+ wlr_log(WLR_ERROR, "wlroots is not compiled with logind support");
+ #endif
+ } else if (!strcmp(env_wlr_session, "direct")) {
+ session = session_direct.create(disp);
+ } else {
+ wlr_log(WLR_ERROR, "WLR_SESSION has an invalid value: %s", env_wlr_session);
+ }
+ } else {
+ const struct session_impl **iter;
+ for (iter = impls; !session && *iter; ++iter) {
+ session = (*iter)->create(disp);
+ }
+ }
+
+ if (!session) {
+ wlr_log(WLR_ERROR, "Failed to load session backend");
+ return NULL;
+ }
+
+ session->active = true;
+ wl_signal_init(&session->session_signal);
+ wl_signal_init(&session->events.destroy);
+ wl_list_init(&session->devices);
+
+ 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);
+
+ struct wl_event_loop *event_loop = wl_display_get_event_loop(disp);
+ int fd = udev_monitor_get_fd(session->mon);
+
+ session->udev_event = wl_event_loop_add_fd(event_loop, fd,
+ WL_EVENT_READABLE, udev_event, session);
+ if (!session->udev_event) {
+ wlr_log_errno(WLR_ERROR, "Failed to create udev event source");
+ goto error_mon;
+ }
+
+ session->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(disp, &session->display_destroy);
+
+ return session;
+
+error_mon:
+ udev_monitor_unref(session->mon);
+error_udev:
+ udev_unref(session->udev);
+error_session:
+ session->impl->destroy(session);
+ return NULL;
+}
+
+void wlr_session_destroy(struct wlr_session *session) {
+ if (!session) {
+ return;
+ }
+
+ wlr_signal_emit_safe(&session->events.destroy, session);
+ wl_list_remove(&session->display_destroy.link);
+
+ wl_event_source_remove(session->udev_event);
+ udev_monitor_unref(session->mon);
+ udev_unref(session->udev);
+
+ session->impl->destroy(session);
+}
+
+int wlr_session_open_file(struct wlr_session *session, const char *path) {
+ int fd = session->impl->open(session, path);
+ if (fd < 0) {
+ return fd;
+ }
+
+ 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;
+ wl_signal_init(&dev->signal);
+ wl_list_insert(&session->devices, &dev->link);
+
+ return fd;
+
+error:
+ free(dev);
+ return fd;
+}
+
+static struct wlr_device *find_device(struct wlr_session *session, int fd) {
+ struct wlr_device *dev;
+
+ wl_list_for_each(dev, &session->devices, link) {
+ if (dev->fd == fd) {
+ return dev;
+ }
+ }
+
+ wlr_log(WLR_ERROR, "Tried to use fd %d not opened by session", fd);
+ assert(0);
+}
+
+void wlr_session_close_file(struct wlr_session *session, int fd) {
+ struct wlr_device *dev = find_device(session, fd);
+
+ session->impl->close(session, fd);
+ wl_list_remove(&dev->link);
+ free(dev);
+}
+
+void wlr_session_signal_add(struct wlr_session *session, int fd,
+ struct wl_listener *listener) {
+ struct wlr_device *dev = find_device(session, fd);
+
+ wl_signal_add(&dev->signal, listener);
+}
+
+bool wlr_session_change_vt(struct wlr_session *session, unsigned vt) {
+ if (!session) {
+ return false;
+ }
+
+ return session->impl->change_vt(session, vt);
+}
+
+/* Tests if 'path' is KMS compatible by trying to open it.
+ * It leaves the open device in *fd_out it it succeeds.
+ */
+static int open_if_kms(struct wlr_session *restrict session, const char *restrict path) {
+ int fd;
+
+ if (!path) {
+ return -1;
+ }
+
+ fd = wlr_session_open_file(session, path);
+ if (fd < 0) {
+ return -1;
+ }
+
+ drmModeRes *res = drmModeGetResources(fd);
+ if (!res) {
+ goto out_fd;
+ }
+
+ drmModeFreeResources(res);
+ return fd;
+
+out_fd:
+ wlr_session_close_file(session, fd);
+ return -1;
+}
+
+static size_t explicit_find_gpus(struct wlr_session *session,
+ size_t ret_len, int ret[static ret_len], const char *str) {
+ char *gpus = strdup(str);
+ if (!gpus) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ return 0;
+ }
+
+ size_t i = 0;
+ char *save;
+ char *ptr = strtok_r(gpus, ":", &save);
+ do {
+ if (i >= ret_len) {
+ break;
+ }
+
+ ret[i] = open_if_kms(session, ptr);
+ if (ret[i] < 0) {
+ wlr_log(WLR_ERROR, "Unable to open %s as DRM device", ptr);
+ } else {
+ ++i;
+ }
+ } while ((ptr = strtok_r(NULL, ":", &save)));
+
+ free(gpus);
+ return i;
+}
+
+/* Tries to find the primary GPU by checking for the "boot_vga" attribute.
+ * If it's not found, it returns the first valid GPU it finds.
+ */
+size_t wlr_session_find_gpus(struct wlr_session *session,
+ size_t ret_len, int *ret) {
+ const char *explicit = getenv("WLR_DRM_DEVICES");
+ if (explicit) {
+ return explicit_find_gpus(session, ret_len, ret, explicit);
+ }
+
+#ifdef __FreeBSD__
+ // XXX: libudev-devd does not return any GPUs (yet?)
+ return explicit_find_gpus(session, ret_len, ret, "/dev/drm/0");
+#else
+
+ struct udev_enumerate *en = udev_enumerate_new(session->udev);
+ if (!en) {
+ wlr_log(WLR_ERROR, "Failed to create udev enumeration");
+ return -1;
+ }
+
+ udev_enumerate_add_match_subsystem(en, "drm");
+ udev_enumerate_add_match_sysname(en, "card[0-9]*");
+ udev_enumerate_scan_devices(en);
+
+ 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;
+ }
+ }
+
+ int fd = open_if_kms(session, udev_device_get_devnode(dev));
+ if (fd < 0) {
+ udev_device_unref(dev);
+ continue;
+ }
+
+ udev_device_unref(dev);
+
+ ret[i] = fd;
+ if (is_boot_vga) {
+ int tmp = ret[0];
+ ret[0] = ret[i];
+ ret[i] = tmp;
+ }
+
+ ++i;
+ }
+
+ udev_enumerate_unref(en);
+
+ return i;
+#endif
+}
diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c
new file mode 100644
index 00000000..df1bf431
--- /dev/null
+++ b/backend/wayland/backend.c
@@ -0,0 +1,279 @@
+#include <assert.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <wlr/config.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <wayland-server.h>
+
+#include <wlr/backend/interface.h>
+#include <wlr/interfaces/wlr_input_device.h>
+#include <wlr/interfaces/wlr_output.h>
+#include <wlr/render/egl.h>
+#include <wlr/render/gles2.h>
+#include <wlr/util/log.h>
+
+#include "backend/wayland.h"
+#include "util/signal.h"
+#include "xdg-shell-unstable-v6-client-protocol.h"
+
+struct wlr_wl_backend *get_wl_backend_from_backend(struct wlr_backend *backend) {
+ assert(wlr_backend_is_wl(backend));
+ return (struct wlr_wl_backend *)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)) {
+ wl_display_terminate(wl->local_display);
+ return 0;
+ }
+
+ if (mask & WL_EVENT_READABLE) {
+ return wl_display_dispatch(wl->remote_display);
+ }
+ if (mask & WL_EVENT_WRITABLE) {
+ wl_display_flush(wl->remote_display);
+ return 0;
+ }
+ if (mask == 0) {
+ int count = wl_display_dispatch_pending(wl->remote_display);
+ wl_display_flush(wl->remote_display);
+ return count;
+ }
+
+ return 0;
+}
+
+static void xdg_shell_handle_ping(void *data, struct zxdg_shell_v6 *shell,
+ uint32_t serial) {
+ zxdg_shell_v6_pong(shell, serial);
+}
+
+static const struct zxdg_shell_v6_listener xdg_shell_listener = {
+ xdg_shell_handle_ping,
+};
+
+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%d", 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) {
+ wl->seat = wl_registry_bind(registry, name,
+ &wl_seat_interface, 2);
+ wl_seat_add_listener(wl->seat, &seat_listener, wl);
+
+ } else if (strcmp(iface, wl_shm_interface.name) == 0) {
+ wl->shm = wl_registry_bind(registry, name,
+ &wl_shm_interface, 1);
+
+ } else if (strcmp(iface, zxdg_shell_v6_interface.name) == 0) {
+ wl->shell = wl_registry_bind(registry, name,
+ &zxdg_shell_v6_interface, 1);
+ zxdg_shell_v6_add_listener(wl->shell, &xdg_shell_listener, NULL);
+ }
+}
+
+static void registry_global_remove(void *data, struct wl_registry *registry,
+ uint32_t name) {
+ // TODO
+}
+
+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, "Initializating wayland backend");
+
+ wl->started = true;
+
+ if (wl->keyboard) {
+ create_wl_keyboard(wl->keyboard, wl);
+ }
+
+ 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);
+ }
+
+ struct wlr_input_device *input_device, *tmp_input_device;
+ wl_list_for_each_safe(input_device, tmp_input_device, &wl->devices, link) {
+ wlr_input_device_destroy(input_device);
+ }
+
+ wlr_signal_emit_safe(&wl->backend.events.destroy, &wl->backend);
+
+ wl_list_remove(&wl->local_display_destroy.link);
+
+ free(wl->seat_name);
+
+ wl_event_source_remove(wl->remote_display_src);
+
+ wlr_renderer_destroy(wl->renderer);
+ wlr_egl_finish(&wl->egl);
+
+ if (wl->pointer) {
+ wl_pointer_destroy(wl->pointer);
+ }
+ if (wl->seat) {
+ wl_seat_destroy(wl->seat);
+ }
+ if (wl->shm) {
+ wl_shm_destroy(wl->shm);
+ }
+ zxdg_shell_v6_destroy(wl->shell);
+ wl_compositor_destroy(wl->compositor);
+ wl_registry_destroy(wl->registry);
+ wl_display_disconnect(wl->remote_display);
+ free(wl);
+}
+
+static struct wlr_renderer *backend_get_renderer(struct wlr_backend *backend) {
+ struct wlr_wl_backend *wl = get_wl_backend_from_backend(backend);
+ return wl->renderer;
+}
+
+static struct wlr_backend_impl backend_impl = {
+ .start = backend_start,
+ .destroy = backend_destroy,
+ .get_renderer = backend_get_renderer,
+};
+
+bool wlr_backend_is_wl(struct wlr_backend *b) {
+ return b->impl == &backend_impl;
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_wl_backend *wl =
+ wl_container_of(listener, wl, local_display_destroy);
+ backend_destroy(&wl->backend);
+}
+
+struct wlr_backend *wlr_wl_backend_create(struct wl_display *display,
+ const char *remote, wlr_renderer_create_func_t create_renderer_func) {
+ 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->local_display = display;
+ wl_list_init(&wl->devices);
+ wl_list_init(&wl->outputs);
+
+ wl->remote_display = wl_display_connect(remote);
+ if (!wl->remote_display) {
+ wlr_log_errno(WLR_ERROR, "Could not connect to remote display");
+ goto error_wl;
+ }
+
+ 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, &registry_listener, wl);
+ wl_display_dispatch(wl->remote_display);
+ wl_display_roundtrip(wl->remote_display);
+
+ if (!wl->compositor) {
+ wlr_log(WLR_ERROR,
+ "Remote Wayland compositor does not support wl_compositor");
+ goto error_registry;
+ }
+ if (!wl->shell) {
+ wlr_log(WLR_ERROR,
+ "Remote Wayland compositor does not support zxdg_shell_v6");
+ goto error_registry;
+ }
+
+ struct wl_event_loop *loop = wl_display_get_event_loop(wl->local_display);
+ int fd = wl_display_get_fd(wl->remote_display);
+ int events = WL_EVENT_READABLE | WL_EVENT_ERROR | WL_EVENT_HANGUP;
+ wl->remote_display_src = wl_event_loop_add_fd(loop, fd, events,
+ 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);
+
+ static EGLint config_attribs[] = {
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RED_SIZE, 1,
+ EGL_GREEN_SIZE, 1,
+ EGL_BLUE_SIZE, 1,
+ EGL_ALPHA_SIZE, 1,
+ EGL_NONE,
+ };
+
+ if (!create_renderer_func) {
+ create_renderer_func = wlr_renderer_autocreate;
+ }
+
+ wl->renderer = create_renderer_func(&wl->egl, EGL_PLATFORM_WAYLAND_EXT,
+ wl->remote_display, config_attribs, WL_SHM_FORMAT_ARGB8888);
+
+ if (!wl->renderer) {
+ wlr_log(WLR_ERROR, "Could not create renderer");
+ goto error_event;
+ }
+
+ wl->local_display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &wl->local_display_destroy);
+
+ return &wl->backend;
+
+error_event:
+ wl_event_source_remove(wl->remote_display_src);
+error_registry:
+ if (wl->compositor) {
+ wl_compositor_destroy(wl->compositor);
+ }
+ if (wl->shell) {
+ zxdg_shell_v6_destroy(wl->shell);
+ }
+ wl_registry_destroy(wl->registry);
+error_display:
+ wl_display_disconnect(wl->remote_display);
+error_wl:
+ free(wl);
+ return NULL;
+}
diff --git a/backend/wayland/output.c b/backend/wayland/output.c
new file mode 100644
index 00000000..89d5b5c9
--- /dev/null
+++ b/backend/wayland/output.c
@@ -0,0 +1,367 @@
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <wayland-client.h>
+
+#include <wlr/interfaces/wlr_output.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/util/log.h>
+
+#include "backend/wayland.h"
+#include "util/signal.h"
+#include "xdg-shell-unstable-v6-client-protocol.h"
+
+static struct wlr_wl_output *get_wl_output_from_output(
+ struct wlr_output *wlr_output) {
+ assert(wlr_output_is_wl(wlr_output));
+ return (struct wlr_wl_output *)wlr_output;
+}
+
+static struct wl_callback_listener frame_listener;
+
+static void surface_frame_callback(void *data, struct wl_callback *cb,
+ uint32_t time) {
+ struct wlr_wl_output *output = data;
+ assert(output);
+ wl_callback_destroy(cb);
+ output->frame_callback = NULL;
+
+ wlr_output_send_frame(&output->wlr_output);
+}
+
+static struct wl_callback_listener frame_listener = {
+ .done = surface_frame_callback
+};
+
+static bool output_set_custom_mode(struct wlr_output *wlr_output,
+ int32_t width, int32_t height, int32_t refresh) {
+ struct wlr_wl_output *output = get_wl_output_from_output(wlr_output);
+ wl_egl_window_resize(output->egl_window, width, height, 0, 0);
+ wlr_output_update_custom_mode(&output->wlr_output, width, height, 0);
+ return true;
+}
+
+static bool output_make_current(struct wlr_output *wlr_output,
+ int *buffer_age) {
+ struct wlr_wl_output *output =
+ get_wl_output_from_output(wlr_output);
+ return wlr_egl_make_current(&output->backend->egl, output->egl_surface,
+ buffer_age);
+}
+
+static bool output_swap_buffers(struct wlr_output *wlr_output,
+ pixman_region32_t *damage) {
+ struct wlr_wl_output *output =
+ get_wl_output_from_output(wlr_output);
+
+ if (output->frame_callback != NULL) {
+ wlr_log(WLR_ERROR, "Skipping buffer swap");
+ return false;
+ }
+
+ output->frame_callback = wl_surface_frame(output->surface);
+ wl_callback_add_listener(output->frame_callback, &frame_listener, output);
+
+ if (!wlr_egl_swap_buffers(&output->backend->egl,
+ output->egl_surface, damage)) {
+ return false;
+ }
+
+ // TODO: if available, use the presentation-time protocol
+ wlr_output_send_present(wlr_output, NULL);
+ return true;
+}
+
+static void output_transform(struct wlr_output *wlr_output,
+ enum wl_output_transform transform) {
+ struct wlr_wl_output *output = get_wl_output_from_output(wlr_output);
+ output->wlr_output.transform = transform;
+}
+
+static bool output_set_cursor(struct wlr_output *wlr_output,
+ struct wlr_texture *texture, int32_t scale,
+ enum wl_output_transform transform,
+ int32_t hotspot_x, int32_t hotspot_y, bool update_texture) {
+ struct wlr_wl_output *output = get_wl_output_from_output(wlr_output);
+ struct wlr_wl_backend *backend = output->backend;
+
+ struct wlr_box hotspot = { .x = hotspot_x, .y = hotspot_y };
+ wlr_box_transform(&hotspot, &hotspot,
+ wlr_output_transform_invert(wlr_output->transform),
+ output->cursor.width, output->cursor.height);
+
+ // TODO: use output->wlr_output.transform to transform pixels and hotpot
+ output->cursor.hotspot_x = hotspot.x;
+ output->cursor.hotspot_y = hotspot.y;
+
+ if (!update_texture) {
+ // Update hotspot without changing cursor image
+ update_wl_output_cursor(output);
+ return true;
+ }
+
+ if (output->cursor.surface == NULL) {
+ output->cursor.surface =
+ wl_compositor_create_surface(backend->compositor);
+ }
+ struct wl_surface *surface = output->cursor.surface;
+
+ if (texture != NULL) {
+ int width, height;
+ wlr_texture_get_size(texture, &width, &height);
+ width = width * wlr_output->scale / scale;
+ height = height * wlr_output->scale / scale;
+
+ output->cursor.width = width;
+ output->cursor.height = height;
+
+ if (output->cursor.egl_window == NULL) {
+ output->cursor.egl_window =
+ wl_egl_window_create(surface, width, height);
+ }
+ wl_egl_window_resize(output->cursor.egl_window, width, height, 0, 0);
+
+ EGLSurface egl_surface =
+ wlr_egl_create_surface(&backend->egl, output->cursor.egl_window);
+
+ wlr_egl_make_current(&backend->egl, egl_surface, NULL);
+
+ struct wlr_box cursor_box = {
+ .width = width,
+ .height = height,
+ };
+
+ float projection[9];
+ wlr_matrix_projection(projection, width, height, wlr_output->transform);
+
+ float matrix[9];
+ wlr_matrix_project_box(matrix, &cursor_box, transform, 0, projection);
+
+ wlr_renderer_begin(backend->renderer, width, height);
+ wlr_renderer_clear(backend->renderer, (float[]){ 0.0, 0.0, 0.0, 0.0 });
+ wlr_render_texture_with_matrix(backend->renderer, texture, matrix, 1.0);
+ wlr_renderer_end(backend->renderer);
+
+ wlr_egl_swap_buffers(&backend->egl, egl_surface, NULL);
+ wlr_egl_destroy_surface(&backend->egl, egl_surface);
+ } else {
+ wl_surface_attach(surface, NULL, 0, 0);
+ wl_surface_commit(surface);
+ }
+
+ update_wl_output_cursor(output);
+ return true;
+}
+
+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.egl_window != NULL) {
+ wl_egl_window_destroy(output->cursor.egl_window);
+ }
+ if (output->cursor.surface) {
+ wl_surface_destroy(output->cursor.surface);
+ }
+
+ if (output->frame_callback) {
+ wl_callback_destroy(output->frame_callback);
+ }
+
+ wlr_egl_destroy_surface(&output->backend->egl, output->egl_surface);
+ wl_egl_window_destroy(output->egl_window);
+ zxdg_toplevel_v6_destroy(output->xdg_toplevel);
+ zxdg_surface_v6_destroy(output->xdg_surface);
+ wl_surface_destroy(output->surface);
+ free(output);
+}
+
+void update_wl_output_cursor(struct wlr_wl_output *output) {
+ if (output->backend->pointer && output->enter_serial) {
+ wl_pointer_set_cursor(output->backend->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 bool output_schedule_frame(struct wlr_output *wlr_output) {
+ struct wlr_wl_output *output = get_wl_output_from_output(wlr_output);
+
+ if (output->frame_callback != NULL) {
+ wlr_log(WLR_ERROR, "Skipping frame scheduling");
+ return true;
+ }
+
+ output->frame_callback = wl_surface_frame(output->surface);
+ wl_callback_add_listener(output->frame_callback, &frame_listener, output);
+ wl_surface_commit(output->surface);
+ return true;
+}
+
+static const struct wlr_output_impl output_impl = {
+ .set_custom_mode = output_set_custom_mode,
+ .transform = output_transform,
+ .destroy = output_destroy,
+ .make_current = output_make_current,
+ .swap_buffers = output_swap_buffers,
+ .set_cursor = output_set_cursor,
+ .move_cursor = output_move_cursor,
+ .schedule_frame = output_schedule_frame,
+};
+
+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 zxdg_surface_v6 *xdg_surface,
+ uint32_t serial) {
+ struct wlr_wl_output *output = data;
+ assert(output && output->xdg_surface == xdg_surface);
+
+ zxdg_surface_v6_ack_configure(xdg_surface, serial);
+
+ // nothing else?
+}
+
+static struct zxdg_surface_v6_listener xdg_surface_listener = {
+ .configure = xdg_surface_handle_configure,
+};
+
+static void xdg_toplevel_handle_configure(void *data,
+ struct zxdg_toplevel_v6 *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;
+ }
+ // loop over states for maximized etc?
+ wl_egl_window_resize(output->egl_window, width, height, 0, 0);
+ wlr_output_update_custom_mode(&output->wlr_output, width, height, 0);
+}
+
+static void xdg_toplevel_handle_close(void *data,
+ struct zxdg_toplevel_v6 *xdg_toplevel) {
+ struct wlr_wl_output *output = data;
+ assert(output && output->xdg_toplevel == xdg_toplevel);
+
+ wlr_output_destroy((struct wlr_output *)output);
+}
+
+static struct zxdg_toplevel_v6_listener xdg_toplevel_listener = {
+ .configure = xdg_toplevel_handle_configure,
+ .close = xdg_toplevel_handle_close,
+};
+
+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 wlr_wl_output *output;
+ if (!(output = calloc(sizeof(struct wlr_wl_output), 1))) {
+ wlr_log(WLR_ERROR, "Failed to allocate wlr_wl_output");
+ return NULL;
+ }
+ wlr_output_init(&output->wlr_output, &backend->backend, &output_impl,
+ backend->local_display);
+ struct wlr_output *wlr_output = &output->wlr_output;
+
+ wlr_output_update_custom_mode(wlr_output, 1280, 720, 0);
+ strncpy(wlr_output->make, "wayland", sizeof(wlr_output->make));
+ strncpy(wlr_output->model, "wayland", sizeof(wlr_output->model));
+ snprintf(wlr_output->name, sizeof(wlr_output->name), "WL-%d",
+ wl_list_length(&backend->outputs) + 1);
+
+ output->backend = backend;
+
+ output->surface = wl_compositor_create_surface(backend->compositor);
+ if (!output->surface) {
+ wlr_log_errno(WLR_ERROR, "Could not create output surface");
+ goto error;
+ }
+ wl_surface_set_user_data(output->surface, output);
+ output->xdg_surface =
+ zxdg_shell_v6_get_xdg_surface(backend->shell, output->surface);
+ if (!output->xdg_surface) {
+ wlr_log_errno(WLR_ERROR, "Could not get xdg surface");
+ goto error;
+ }
+ output->xdg_toplevel =
+ zxdg_surface_v6_get_toplevel(output->xdg_surface);
+ if (!output->xdg_toplevel) {
+ wlr_log_errno(WLR_ERROR, "Could not get xdg toplevel");
+ goto error;
+ }
+
+ char title[32];
+ if (snprintf(title, sizeof(title), "wlroots - %s", wlr_output->name)) {
+ zxdg_toplevel_v6_set_title(output->xdg_toplevel, title);
+ }
+
+ zxdg_toplevel_v6_set_app_id(output->xdg_toplevel, "wlroots");
+ zxdg_surface_v6_add_listener(output->xdg_surface,
+ &xdg_surface_listener, output);
+ zxdg_toplevel_v6_add_listener(output->xdg_toplevel,
+ &xdg_toplevel_listener, output);
+ wl_surface_commit(output->surface);
+
+ output->egl_window = wl_egl_window_create(output->surface,
+ wlr_output->width, wlr_output->height);
+ output->egl_surface = wlr_egl_create_surface(&backend->egl,
+ output->egl_window);
+
+ wl_display_roundtrip(output->backend->remote_display);
+
+ // start rendering loop per callbacks by rendering first frame
+ if (!wlr_egl_make_current(&output->backend->egl, output->egl_surface,
+ NULL)) {
+ goto error;
+ }
+
+ wlr_renderer_begin(backend->renderer, wlr_output->width, wlr_output->height);
+ wlr_renderer_clear(backend->renderer, (float[]){ 1.0, 1.0, 1.0, 1.0 });
+ wlr_renderer_end(backend->renderer);
+
+ output->frame_callback = wl_surface_frame(output->surface);
+ wl_callback_add_listener(output->frame_callback, &frame_listener, output);
+
+ if (!wlr_egl_swap_buffers(&output->backend->egl, output->egl_surface,
+ NULL)) {
+ goto error;
+ }
+
+ wl_list_insert(&backend->outputs, &output->link);
+ wlr_output_update_enabled(wlr_output, true);
+
+ wlr_signal_emit_safe(&backend->backend.events.new_output, wlr_output);
+
+ if (backend->pointer != NULL) {
+ create_wl_pointer(backend->pointer, output);
+ }
+
+ return wlr_output;
+
+error:
+ wlr_output_destroy(&output->wlr_output);
+ return NULL;
+}
diff --git a/backend/wayland/wl_seat.c b/backend/wayland/wl_seat.c
new file mode 100644
index 00000000..b654197a
--- /dev/null
+++ b/backend/wayland/wl_seat.c
@@ -0,0 +1,434 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <wayland-client.h>
+
+#include <wlr/interfaces/wlr_input_device.h>
+#include <wlr/interfaces/wlr_keyboard.h>
+#include <wlr/interfaces/wlr_output.h>
+#include <wlr/interfaces/wlr_pointer.h>
+#include <wlr/interfaces/wlr_touch.h>
+#include <wlr/util/log.h>
+
+#include "backend/wayland.h"
+#include "util/signal.h"
+
+static struct wlr_wl_pointer *output_get_pointer(struct wlr_wl_output *output) {
+ struct wlr_input_device *wlr_dev;
+ wl_list_for_each(wlr_dev, &output->backend->devices, link) {
+ if (wlr_dev->type != WLR_INPUT_DEVICE_POINTER) {
+ continue;
+ }
+ struct wlr_wl_pointer *pointer = pointer_get_wl(wlr_dev->pointer);
+ 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_backend *backend = data;
+ if (surface == NULL) {
+ return;
+ }
+
+ struct wlr_wl_output *output = wl_surface_get_user_data(surface);
+ assert(output);
+ struct wlr_wl_pointer *pointer = output_get_pointer(output);
+
+ output->enter_serial = serial;
+ backend->current_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_backend *backend = data;
+ if (surface == NULL) {
+ return;
+ }
+
+ struct wlr_wl_output *output = wl_surface_get_user_data(surface);
+ assert(output);
+ output->enter_serial = 0;
+
+ if (backend->current_pointer == NULL ||
+ backend->current_pointer->output != output) {
+ return;
+ }
+
+ backend->current_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_backend *backend = data;
+ struct wlr_wl_pointer *pointer = backend->current_pointer;
+ if (pointer == NULL) {
+ return;
+ }
+
+ struct wlr_output *wlr_output = &pointer->output->wlr_output;
+ struct wlr_event_pointer_motion_absolute event = {
+ .device = &pointer->input_device->wlr_input_device,
+ .time_msec = time,
+ .x = wl_fixed_to_double(sx) / wlr_output->width,
+ .y = wl_fixed_to_double(sy) / wlr_output->height,
+ };
+ wlr_signal_emit_safe(&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_backend *backend = data;
+ struct wlr_wl_pointer *pointer = backend->current_pointer;
+ if (pointer == NULL) {
+ return;
+ }
+
+ struct wlr_event_pointer_button event = {
+ .device = &pointer->input_device->wlr_input_device,
+ .button = button,
+ .state = state,
+ .time_msec = time,
+ };
+ wlr_signal_emit_safe(&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_backend *backend = data;
+ struct wlr_wl_pointer *pointer = backend->current_pointer;
+ if (pointer == NULL) {
+ return;
+ }
+
+ struct wlr_event_pointer_axis event = {
+ .device = &pointer->input_device->wlr_input_device,
+ .delta = wl_fixed_to_double(value),
+ .delta_discrete = pointer->axis_discrete,
+ .orientation = axis,
+ .time_msec = time,
+ .source = pointer->axis_source,
+ };
+ wlr_signal_emit_safe(&pointer->wlr_pointer.events.axis, &event);
+
+ pointer->axis_discrete = 0;
+}
+
+static void pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) {
+ // This space is intentionally left blank
+}
+
+static void pointer_handle_axis_source(void *data,
+ struct wl_pointer *wl_pointer, uint32_t axis_source) {
+ struct wlr_wl_backend *backend = data;
+ struct wlr_wl_pointer *pointer = backend->current_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) {
+ // This space is intentionally left blank
+}
+
+static void pointer_handle_axis_discrete(void *data,
+ struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) {
+ struct wlr_wl_backend *backend = data;
+ struct wlr_wl_pointer *pointer = backend->current_pointer;
+ if (pointer == NULL) {
+ return;
+ }
+
+ pointer->axis_discrete = discrete;
+}
+
+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,
+};
+
+static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t format, int32_t fd, uint32_t size) {
+ // TODO: set keymap
+}
+
+static uint32_t get_current_time_msec() {
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ return now.tv_nsec / 1000;
+}
+
+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_input_device *dev = data;
+
+ uint32_t time = get_current_time_msec();
+
+ uint32_t *keycode_ptr;
+ wl_array_for_each(keycode_ptr, keys) {
+ struct wlr_event_keyboard_key event = {
+ .keycode = *keycode_ptr,
+ .state = WLR_KEY_PRESSED,
+ .time_msec = time,
+ .update_state = false,
+ };
+ wlr_keyboard_notify_key(dev->keyboard, &event);
+ }
+}
+
+static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, struct wl_surface *surface) {
+ struct wlr_input_device *dev = data;
+
+ uint32_t time = get_current_time_msec();
+
+ uint32_t pressed[dev->keyboard->num_keycodes + 1];
+ memcpy(pressed, dev->keyboard->keycodes,
+ dev->keyboard->num_keycodes * sizeof(uint32_t));
+
+ for (size_t i = 0; i < sizeof(pressed)/sizeof(pressed[0]); ++i) {
+ uint32_t keycode = pressed[i];
+
+ struct wlr_event_keyboard_key event = {
+ .keycode = keycode,
+ .state = WLR_KEY_RELEASED,
+ .time_msec = time,
+ .update_state = false,
+ };
+ wlr_keyboard_notify_key(dev->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_input_device *dev = data;
+ assert(dev && dev->keyboard);
+
+ struct wlr_event_keyboard_key wlr_event = {
+ .keycode = key,
+ .state = state,
+ .time_msec = time,
+ .update_state = false,
+ };
+ wlr_keyboard_notify_key(dev->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_input_device *dev = data;
+ assert(dev && dev->keyboard);
+ wlr_keyboard_notify_modifiers(dev->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 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 struct wlr_wl_input_device *get_wl_input_device_from_input_device(
+ struct wlr_input_device *wlr_dev) {
+ assert(wlr_input_device_is_wl(wlr_dev));
+ return (struct wlr_wl_input_device *)wlr_dev;
+}
+
+static void input_device_destroy(struct wlr_input_device *wlr_dev) {
+ struct wlr_wl_input_device *dev =
+ get_wl_input_device_from_input_device(wlr_dev);
+ if (dev->resource) {
+ wl_proxy_destroy(dev->resource);
+ }
+ wl_list_remove(&dev->wlr_input_device.link);
+ free(dev);
+}
+
+static struct wlr_input_device_impl input_device_impl = {
+ .destroy = input_device_destroy,
+};
+
+bool wlr_input_device_is_wl(struct wlr_input_device *dev) {
+ return dev->impl == &input_device_impl;
+}
+
+static struct wlr_wl_input_device *create_wl_input_device(
+ struct wlr_wl_backend *backend, enum wlr_input_device_type type) {
+ struct wlr_wl_input_device *dev =
+ calloc(1, sizeof(struct wlr_wl_input_device));
+ if (dev == NULL) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ return NULL;
+ }
+ dev->backend = backend;
+
+ struct wlr_input_device *wlr_dev = &dev->wlr_input_device;
+
+ unsigned int vendor = 0, product = 0;
+ const char *name = "wayland";
+ wlr_input_device_init(wlr_dev, type, &input_device_impl, name, vendor,
+ product);
+ wl_list_insert(&backend->devices, &wlr_dev->link);
+ return dev;
+}
+
+static struct wlr_pointer_impl pointer_impl;
+
+struct wlr_wl_pointer *pointer_get_wl(struct wlr_pointer *wlr_pointer) {
+ assert(wlr_pointer->impl == &pointer_impl);
+ return (struct wlr_wl_pointer *)wlr_pointer;
+}
+
+static void pointer_destroy(struct wlr_pointer *wlr_pointer) {
+ struct wlr_wl_pointer *pointer = pointer_get_wl(wlr_pointer);
+ wl_list_remove(&pointer->output_destroy.link);
+ free(pointer);
+}
+
+static struct wlr_pointer_impl pointer_impl = {
+ .destroy = pointer_destroy,
+};
+
+static void pointer_handle_output_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_wl_pointer *pointer =
+ wl_container_of(listener, pointer, output_destroy);
+ wlr_input_device_destroy(&pointer->input_device->wlr_input_device);
+}
+
+void create_wl_pointer(struct wl_pointer *wl_pointer, struct wlr_wl_output *output) {
+ struct wlr_wl_backend *backend = output->backend;
+
+ struct wlr_input_device *wlr_dev;
+ wl_list_for_each(wlr_dev, &output->backend->devices, link) {
+ if (wlr_dev->type != WLR_INPUT_DEVICE_POINTER) {
+ continue;
+ }
+ struct wlr_wl_pointer *pointer = pointer_get_wl(wlr_dev->pointer);
+ if (pointer->output == output) {
+ return;
+ }
+ }
+
+ struct wlr_wl_pointer *pointer = calloc(1, sizeof(struct wlr_wl_pointer));
+ if (pointer == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed");
+ return;
+ }
+ pointer->wl_pointer = wl_pointer;
+ pointer->output = output;
+
+ wl_signal_add(&output->wlr_output.events.destroy, &pointer->output_destroy);
+ pointer->output_destroy.notify = pointer_handle_output_destroy;
+
+ struct wlr_wl_input_device *dev =
+ create_wl_input_device(backend, WLR_INPUT_DEVICE_POINTER);
+ if (dev == NULL) {
+ free(pointer);
+ wlr_log(WLR_ERROR, "Allocation failed");
+ return;
+ }
+ pointer->input_device = dev;
+
+ wlr_dev = &dev->wlr_input_device;
+ wlr_dev->pointer = &pointer->wlr_pointer;
+ wlr_dev->output_name = strdup(output->wlr_output.name);
+ wlr_pointer_init(wlr_dev->pointer, &pointer_impl);
+
+ wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev);
+}
+
+void create_wl_keyboard(struct wl_keyboard *wl_keyboard, struct wlr_wl_backend *wl) {
+ struct wlr_wl_input_device *dev =
+ create_wl_input_device(wl, WLR_INPUT_DEVICE_KEYBOARD);
+ if (!dev) {
+ return;
+ }
+
+ struct wlr_input_device *wlr_dev = &dev->wlr_input_device;
+
+ wlr_dev->keyboard = calloc(1, sizeof(*wlr_dev->keyboard));
+ if (!wlr_dev->keyboard) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ free(dev);
+ return;
+ }
+ wlr_keyboard_init(wlr_dev->keyboard, NULL);
+
+ wl_keyboard_add_listener(wl_keyboard, &keyboard_listener, wlr_dev);
+ dev->resource = wl_keyboard;
+ wlr_signal_emit_safe(&wl->backend.events.new_input, wlr_dev);
+}
+
+static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
+ enum wl_seat_capability caps) {
+ struct wlr_wl_backend *backend = data;
+ assert(backend->seat == wl_seat);
+
+ if ((caps & WL_SEAT_CAPABILITY_POINTER)) {
+ wlr_log(WLR_DEBUG, "seat %p offered pointer", (void*) wl_seat);
+
+ struct wl_pointer *wl_pointer = wl_seat_get_pointer(wl_seat);
+ backend->pointer = wl_pointer;
+
+ struct wlr_wl_output *output;
+ wl_list_for_each(output, &backend->outputs, link) {
+ create_wl_pointer(wl_pointer, output);
+ }
+
+ wl_pointer_add_listener(wl_pointer, &pointer_listener, backend);
+ }
+ if ((caps & WL_SEAT_CAPABILITY_KEYBOARD)) {
+ wlr_log(WLR_DEBUG, "seat %p offered keyboard", (void*) wl_seat);
+
+ struct wl_keyboard *wl_keyboard = wl_seat_get_keyboard(wl_seat);
+ backend->keyboard = wl_keyboard;
+
+ if (backend->started) {
+ create_wl_keyboard(wl_keyboard, backend);
+ }
+ }
+}
+
+static void seat_handle_name(void *data, struct wl_seat *wl_seat,
+ const char *name) {
+ struct wlr_wl_backend *backend = data;
+ assert(backend->seat == wl_seat);
+ // Do we need to check if seatName was previously set for name change?
+ free(backend->seat_name);
+ backend->seat_name = strdup(name);
+}
+
+const struct wl_seat_listener seat_listener = {
+ .capabilities = seat_handle_capabilities,
+ .name = seat_handle_name,
+};
diff --git a/backend/x11/backend.c b/backend/x11/backend.c
new file mode 100644
index 00000000..38715631
--- /dev/null
+++ b/backend/x11/backend.c
@@ -0,0 +1,314 @@
+#define _POSIX_C_SOURCE 200112L
+
+#include <assert.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <wlr/config.h>
+
+#include <X11/Xlib-xcb.h>
+#include <wayland-server.h>
+#include <xcb/xcb.h>
+#include <xcb/xfixes.h>
+#include <xcb/xinput.h>
+
+#include <wlr/backend/interface.h>
+#include <wlr/backend/x11.h>
+#include <wlr/interfaces/wlr_input_device.h>
+#include <wlr/interfaces/wlr_keyboard.h>
+#include <wlr/interfaces/wlr_pointer.h>
+#include <wlr/render/egl.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/util/log.h>
+
+#include "backend/x11.h"
+#include "util/signal.h"
+
+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_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) {
+ wlr_output_update_needs_swap(&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);
+ }
+ }
+ 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);
+ }
+ }
+ }
+}
+
+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)) {
+ wl_display_terminate(x11->wl_display);
+ return 0;
+ }
+
+ xcb_generic_event_t *e;
+ while ((e = xcb_poll_for_event(x11->xcb))) {
+ handle_x11_event(x11, e);
+ free(e);
+ }
+
+ return 0;
+}
+
+struct wlr_x11_backend *get_x11_backend_from_backend(
+ struct wlr_backend *wlr_backend) {
+ assert(wlr_backend_is_x11(wlr_backend));
+ return (struct wlr_x11_backend *)wlr_backend;
+}
+
+static bool backend_start(struct wlr_backend *backend) {
+ struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend);
+ x11->started = true;
+
+ wlr_signal_emit_safe(&x11->backend.events.new_input, &x11->keyboard_dev);
+
+ 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_input_device_destroy(&x11->keyboard_dev);
+
+ wlr_signal_emit_safe(&backend->events.destroy, backend);
+
+ if (x11->event_source) {
+ wl_event_source_remove(x11->event_source);
+ }
+ wl_list_remove(&x11->display_destroy.link);
+
+ wlr_renderer_destroy(x11->renderer);
+ wlr_egl_finish(&x11->egl);
+
+ if (x11->xlib_conn) {
+ XCloseDisplay(x11->xlib_conn);
+ }
+ free(x11);
+}
+
+static struct wlr_renderer *backend_get_renderer(
+ struct wlr_backend *backend) {
+ struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend);
+ return x11->renderer;
+}
+
+static const struct wlr_backend_impl backend_impl = {
+ .start = backend_start,
+ .destroy = backend_destroy,
+ .get_renderer = backend_get_renderer,
+};
+
+bool wlr_backend_is_x11(struct wlr_backend *backend) {
+ return backend->impl == &backend_impl;
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_x11_backend *x11 =
+ wl_container_of(listener, x11, display_destroy);
+ backend_destroy(&x11->backend);
+}
+
+struct wlr_backend *wlr_x11_backend_create(struct wl_display *display,
+ const char *x11_display,
+ wlr_renderer_create_func_t create_renderer_func) {
+ struct wlr_x11_backend *x11 = calloc(1, sizeof(*x11));
+ if (!x11) {
+ return NULL;
+ }
+
+ wlr_backend_init(&x11->backend, &backend_impl);
+ x11->wl_display = display;
+ wl_list_init(&x11->outputs);
+
+ x11->xlib_conn = XOpenDisplay(x11_display);
+ if (!x11->xlib_conn) {
+ wlr_log(WLR_ERROR, "Failed to open X connection");
+ goto error_x11;
+ }
+
+ x11->xcb = XGetXCBConnection(x11->xlib_conn);
+ if (!x11->xcb || xcb_connection_has_error(x11->xcb)) {
+ wlr_log(WLR_ERROR, "Failed to open xcb connection");
+ goto error_display;
+ }
+
+ XSetEventQueueOwner(x11->xlib_conn, XCBOwnsEventQueue);
+
+ 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 },
+ };
+
+ 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;
+
+ 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");
+ free(fixes_reply);
+ goto error_display;
+ }
+ free(fixes_reply);
+
+ 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");
+ free(xi_reply);
+ goto error_display;
+ }
+ free(xi_reply);
+
+ int fd = xcb_get_file_descriptor(x11->xcb);
+ struct wl_event_loop *ev = wl_display_get_event_loop(display);
+ uint32_t events = WL_EVENT_READABLE | WL_EVENT_ERROR | WL_EVENT_HANGUP;
+ x11->event_source = wl_event_loop_add_fd(ev, 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 (!create_renderer_func) {
+ create_renderer_func = wlr_renderer_autocreate;
+ }
+
+ static EGLint config_attribs[] = {
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RED_SIZE, 1,
+ EGL_GREEN_SIZE, 1,
+ EGL_BLUE_SIZE, 1,
+ EGL_ALPHA_SIZE, 0,
+ EGL_NONE,
+ };
+
+ x11->renderer = create_renderer_func(&x11->egl, EGL_PLATFORM_X11_KHR,
+ x11->xlib_conn, config_attribs, x11->screen->root_visual);
+
+ if (x11->renderer == NULL) {
+ wlr_log(WLR_ERROR, "Failed to create renderer");
+ goto error_event;
+ }
+
+ wlr_input_device_init(&x11->keyboard_dev, WLR_INPUT_DEVICE_KEYBOARD,
+ &input_device_impl, "X11 keyboard", 0, 0);
+ wlr_keyboard_init(&x11->keyboard, &keyboard_impl);
+ x11->keyboard_dev.keyboard = &x11->keyboard;
+
+ x11->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &x11->display_destroy);
+
+ return &x11->backend;
+
+error_event:
+ wl_event_source_remove(x11->event_source);
+error_display:
+ XCloseDisplay(x11->xlib_conn);
+error_x11:
+ free(x11);
+ return NULL;
+}
diff --git a/backend/x11/input_device.c b/backend/x11/input_device.c
new file mode 100644
index 00000000..a50f478a
--- /dev/null
+++ b/backend/x11/input_device.c
@@ -0,0 +1,244 @@
+#include <stdlib.h>
+
+#include <wlr/config.h>
+
+#ifdef __linux__
+#include <linux/input-event-codes.h>
+#elif __FreeBSD__
+#include <dev/evdev/input-event-codes.h>
+#endif
+
+#include <xcb/xcb.h>
+#include <xcb/xfixes.h>
+#include <xcb/xinput.h>
+
+#include <wlr/interfaces/wlr_input_device.h>
+#include <wlr/interfaces/wlr_keyboard.h>
+#include <wlr/interfaces/wlr_pointer.h>
+#include <wlr/util/log.h>
+
+#include "backend/x11.h"
+#include "util/signal.h"
+
+static void send_key_event(struct wlr_x11_backend *x11, uint32_t key,
+ enum wlr_key_state st, xcb_timestamp_t time) {
+ struct wlr_event_keyboard_key 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 wlr_button_state st, xcb_timestamp_t time) {
+ struct wlr_event_pointer_button ev = {
+ .device = &output->pointer_dev,
+ .time_msec = time,
+ .button = key,
+ .state = st,
+ };
+ wlr_signal_emit_safe(&output->pointer.events.button, &ev);
+}
+
+static void send_axis_event(struct wlr_x11_output *output, int32_t delta,
+ xcb_timestamp_t time) {
+ struct wlr_event_pointer_axis ev = {
+ .device = &output->pointer_dev,
+ .time_msec = time,
+ .source = WLR_AXIS_SOURCE_WHEEL,
+ .orientation = WLR_AXIS_ORIENTATION_VERTICAL,
+ // 15 is a typical value libinput sends for one scroll
+ .delta = delta * 15,
+ .delta_discrete = delta,
+ };
+ wlr_signal_emit_safe(&output->pointer.events.axis, &ev);
+}
+
+static void send_pointer_position_event(struct wlr_x11_output *output,
+ int16_t x, int16_t y, xcb_timestamp_t time) {
+ struct wlr_event_pointer_motion_absolute ev = {
+ .device = &output->pointer_dev,
+ .time_msec = time,
+ .x = (double)x / output->wlr_output.width,
+ .y = (double)y / output->wlr_output.height,
+ };
+ wlr_signal_emit_safe(&output->pointer.events.motion_absolute, &ev);
+}
+
+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;
+
+ 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, WLR_KEY_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, WLR_KEY_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, WLR_BUTTON_PRESSED,
+ ev->time);
+ break;
+ case XCB_BUTTON_INDEX_2:
+ send_button_event(output, BTN_MIDDLE, WLR_BUTTON_PRESSED,
+ ev->time);
+ break;
+ case XCB_BUTTON_INDEX_3:
+ send_button_event(output, BTN_RIGHT, WLR_BUTTON_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, WLR_BUTTON_RELEASED,
+ ev->time);
+ break;
+ case XCB_BUTTON_INDEX_2:
+ send_button_event(output, BTN_MIDDLE, WLR_BUTTON_RELEASED,
+ ev->time);
+ break;
+ case XCB_BUTTON_INDEX_3:
+ send_button_event(output, BTN_RIGHT, WLR_BUTTON_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_ENTER: {
+ xcb_input_enter_event_t *ev = (xcb_input_enter_event_t *)event;
+
+ output = get_x11_output_from_window_id(x11, ev->event);
+ if (!output) {
+ return;
+ }
+
+ if (!output->cursor_hidden) {
+ xcb_xfixes_hide_cursor(x11->xcb, output->win);
+ xcb_flush(x11->xcb);
+ output->cursor_hidden = true;
+ }
+ break;
+ }
+ case XCB_INPUT_LEAVE: {
+ xcb_input_leave_event_t *ev = (xcb_input_leave_event_t *)event;
+
+ output = get_x11_output_from_window_id(x11, ev->event);
+ if (!output) {
+ return;
+ }
+
+ if (output->cursor_hidden) {
+ xcb_xfixes_show_cursor(x11->xcb, output->win);
+ xcb_flush(x11->xcb);
+ output->cursor_hidden = false;
+ }
+ break;
+ }
+ }
+}
+
+static void input_device_destroy(struct wlr_input_device *wlr_device) {
+ // Don't free the input device, it's on the stack
+}
+
+const struct wlr_input_device_impl input_device_impl = {
+ .destroy = input_device_destroy,
+};
+
+static void keyboard_destroy(struct wlr_keyboard *wlr_keyboard) {
+ // Don't free the keyboard, it's on the stack
+}
+
+const struct wlr_keyboard_impl keyboard_impl = {
+ .destroy = keyboard_destroy,
+};
+
+static void pointer_destroy(struct wlr_pointer *wlr_pointer) {
+ // Don't free the pointer, it's on the stack
+}
+
+const struct wlr_pointer_impl pointer_impl = {
+ .destroy = pointer_destroy,
+};
+
+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) {
+ return wlr_dev->impl == &input_device_impl;
+}
diff --git a/backend/x11/meson.build b/backend/x11/meson.build
new file mode 100644
index 00000000..19e873ab
--- /dev/null
+++ b/backend/x11/meson.build
@@ -0,0 +1,35 @@
+x11_libs = []
+x11_required = [
+ 'x11-xcb',
+ 'xcb',
+ 'xcb-xinput',
+ 'xcb-xfixes',
+]
+
+foreach lib : x11_required
+ dep = dependency(lib, required: get_option('x11-backend'))
+ if not dep.found()
+ subdir_done()
+ endif
+
+ x11_libs += dep
+endforeach
+
+lib_wlr_backend_x11 = static_library(
+ 'wlr_backend_x11',
+ files(
+ 'backend.c',
+ 'input_device.c',
+ 'output.c',
+ ),
+ include_directories: wlr_inc,
+ dependencies: [
+ wayland_server,
+ pixman,
+ xkbcommon,
+ x11_libs,
+ ],
+)
+
+backend_parts += lib_wlr_backend_x11
+conf_data.set10('WLR_HAS_X11_BACKEND', true)
diff --git a/backend/x11/output.c b/backend/x11/output.c
new file mode 100644
index 00000000..6f98c590
--- /dev/null
+++ b/backend/x11/output.c
@@ -0,0 +1,236 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <xcb/xcb.h>
+#include <xcb/xinput.h>
+
+#include <wlr/interfaces/wlr_output.h>
+#include <wlr/interfaces/wlr_pointer.h>
+#include <wlr/util/log.h>
+
+#include "backend/x11.h"
+#include "util/signal.h"
+
+static int signal_frame(void *data) {
+ struct wlr_x11_output *output = data;
+ wlr_output_send_frame(&output->wlr_output);
+ wl_event_source_timer_update(output->frame_timer, output->frame_delay);
+ return 0;
+}
+
+static void parse_xcb_setup(struct wlr_output *output,
+ xcb_connection_t *xcb) {
+ const xcb_setup_t *xcb_setup = xcb_get_setup(xcb);
+
+ snprintf(output->make, sizeof(output->make), "%.*s",
+ xcb_setup_vendor_length(xcb_setup),
+ xcb_setup_vendor(xcb_setup));
+ snprintf(output->model, sizeof(output->model), "%"PRIu16".%"PRIu16,
+ xcb_setup->protocol_major_version,
+ xcb_setup->protocol_minor_version);
+}
+
+static struct wlr_x11_output *get_x11_output_from_output(
+ struct wlr_output *wlr_output) {
+ assert(wlr_output_is_x11(wlr_output));
+ return (struct wlr_x11_output *)wlr_output;
+}
+
+static void output_set_refresh(struct wlr_output *wlr_output, int32_t refresh) {
+ struct wlr_x11_output *output = get_x11_output_from_output(wlr_output);
+
+ if (refresh <= 0) {
+ refresh = X11_DEFAULT_REFRESH;
+ }
+
+ wlr_output_update_custom_mode(&output->wlr_output, wlr_output->width,
+ wlr_output->height, refresh);
+
+ output->frame_delay = 1000000 / refresh;
+}
+
+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;
+
+ output_set_refresh(&output->wlr_output, refresh);
+
+ 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;
+ }
+
+ return true;
+}
+
+static void output_transform(struct wlr_output *wlr_output,
+ enum wl_output_transform transform) {
+ struct wlr_x11_output *output = get_x11_output_from_output(wlr_output);
+ output->wlr_output.transform = transform;
+}
+
+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;
+
+ wlr_input_device_destroy(&output->pointer_dev);
+
+ wl_list_remove(&output->link);
+ wl_event_source_remove(output->frame_timer);
+ wlr_egl_destroy_surface(&x11->egl, output->surf);
+ xcb_destroy_window(x11->xcb, output->win);
+ xcb_flush(x11->xcb);
+ free(output);
+}
+
+static bool output_make_current(struct wlr_output *wlr_output,
+ int *buffer_age) {
+ struct wlr_x11_output *output = get_x11_output_from_output(wlr_output);
+ struct wlr_x11_backend *x11 = output->x11;
+
+ return wlr_egl_make_current(&x11->egl, output->surf, buffer_age);
+}
+
+static bool output_swap_buffers(struct wlr_output *wlr_output,
+ pixman_region32_t *damage) {
+ struct wlr_x11_output *output = (struct wlr_x11_output *)wlr_output;
+ struct wlr_x11_backend *x11 = output->x11;
+
+ if (!wlr_egl_swap_buffers(&x11->egl, output->surf, damage)) {
+ return false;
+ }
+
+ wlr_output_send_present(wlr_output, NULL);
+ return true;
+}
+
+static const struct wlr_output_impl output_impl = {
+ .set_custom_mode = output_set_custom_mode,
+ .transform = output_transform,
+ .destroy = output_destroy,
+ .make_current = output_make_current,
+ .swap_buffers = output_swap_buffers,
+};
+
+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(struct wlr_x11_output));
+ if (output == NULL) {
+ return NULL;
+ }
+ output->x11 = x11;
+
+ struct wlr_output *wlr_output = &output->wlr_output;
+ wlr_output_init(wlr_output, &x11->backend, &output_impl, x11->wl_display);
+
+ wlr_output->width = 1024;
+ wlr_output->height = 768;
+
+ output_set_refresh(&output->wlr_output, 0);
+
+ snprintf(wlr_output->name, sizeof(wlr_output->name), "X11-%d",
+ wl_list_length(&x11->outputs) + 1);
+ parse_xcb_setup(wlr_output, x11->xcb);
+
+ uint32_t mask = XCB_CW_EVENT_MASK;
+ uint32_t values[] = {
+ XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY
+ };
+ output->win = xcb_generate_id(x11->xcb);
+ xcb_create_window(x11->xcb, XCB_COPY_FROM_PARENT, output->win,
+ x11->screen->root, 0, 0, wlr_output->width, wlr_output->height, 1,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT, x11->screen->root_visual, mask, values);
+
+ 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_ENTER |
+ XCB_INPUT_XI_EVENT_MASK_LEAVE,
+ };
+ xcb_input_xi_select_events(x11->xcb, output->win, 1, &xinput_mask.head);
+
+ output->surf = wlr_egl_create_surface(&x11->egl, &output->win);
+ if (!output->surf) {
+ wlr_log(WLR_ERROR, "Failed to create EGL surface");
+ free(output);
+ return NULL;
+ }
+
+ 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);
+
+ char title[32];
+ if (snprintf(title, sizeof(title), "wlroots - %s", wlr_output->name)) {
+ xcb_change_property(x11->xcb, XCB_PROP_MODE_REPLACE, output->win,
+ x11->atoms.net_wm_name, x11->atoms.utf8_string, 8,
+ strlen(title), title);
+ }
+
+ xcb_map_window(x11->xcb, output->win);
+ xcb_flush(x11->xcb);
+
+ struct wl_event_loop *ev = wl_display_get_event_loop(x11->wl_display);
+ output->frame_timer = wl_event_loop_add_timer(ev, signal_frame, output);
+
+ wl_list_insert(&x11->outputs, &output->link);
+
+ wl_event_source_timer_update(output->frame_timer, output->frame_delay);
+ wlr_output_update_enabled(wlr_output, true);
+
+ wlr_input_device_init(&output->pointer_dev, WLR_INPUT_DEVICE_POINTER,
+ &input_device_impl, "X11 pointer", 0, 0);
+ wlr_pointer_init(&output->pointer, &pointer_impl);
+ output->pointer_dev.pointer = &output->pointer;
+ output->pointer_dev.output_name = strdup(wlr_output->name);
+
+ wlr_signal_emit_safe(&x11->backend.events.new_output, wlr_output);
+ wlr_signal_emit_safe(&x11->backend.events.new_input, &output->pointer_dev);
+
+ return wlr_output;
+}
+
+void handle_x11_configure_notify(struct wlr_x11_output *output,
+ xcb_configure_notify_event_t *ev) {
+ // ignore events that set an invalid size:
+ if (ev->width > 0 && ev->height > 0) {
+ wlr_output_update_custom_mode(&output->wlr_output, ev->width,
+ ev->height, output->wlr_output.refresh);
+
+ // Move the pointer to its new location
+ update_x11_pointer_position(output, output->x11->time);
+ } else {
+ wlr_log(WLR_DEBUG,
+ "Ignoring X11 configure event for height=%d, width=%d",
+ ev->width, ev->height);
+ }
+}
+
+bool wlr_output_is_x11(struct wlr_output *wlr_output) {
+ return wlr_output->impl == &output_impl;
+}
diff --git a/docs/env_vars.md b/docs/env_vars.md
new file mode 100644
index 00000000..b67d1652
--- /dev/null
+++ b/docs/env_vars.md
@@ -0,0 +1,34 @@
+wlroots reads these environment variables
+
+wlroots specific
+----------------
+* *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_ATOMIC_GAMMA*: set to 1 to use legacy DRM interface for gamma
+ control instead of the atomic interface
+* *WLR_LIBINPUT_NO_DEVICES*: set to 1 to not fail without any input devices
+* *WLR_BACKENDS*: comma-separated list of backends to use (available backends:
+ wayland, x11, headless)
+* *WLR_WL_OUTPUTS*: when using the wayland backend specifies the number of outputs
+* *WLR_X11_OUTPUTS*: when using the X11 backend specifies the number of outputs
+* *WLR_HEADLESS_OUTPUTS*: when using the headless backend specifies the number
+ of outputs
+* *WLR_NO_HARDWARE_CURSORS*: set to 1 to use software cursors instead of
+ hardware cursors
+* *WLR_SESSION*: specifies the wlr\_session to be used (available sessions:
+ logind/systemd, direct)
+
+rootston specific
+------------------
+* *XKB_DEFAULT_RULES*, *XKB_DEFAULT_MODEL*, *XKB_DEFAULT_LAYOUT*,
+ *XKB_DEFAULT_VARIANT*, *XKB_DEFAULT_OPTIONS*: xkb setup
+
+generic
+-------
+* *DISPLAY*: if set probe X11 backend in *wlr_backend_autocreate*
+* *WAYLAND_DISPLAY*, *_WAYLAND_DISPLAY*, *WAYLAND_SOCKET*: if set probe Wayland
+ backend in *wlr_backend_autocreate*
+* *XCURSOR_PATH*: directory where xcursors are located
diff --git a/examples/.gitignore b/examples/.gitignore
new file mode 100644
index 00000000..38582982
--- /dev/null
+++ b/examples/.gitignore
@@ -0,0 +1 @@
+/compositor/protocols
diff --git a/examples/cat.c b/examples/cat.c
new file mode 100644
index 00000000..cc1403d8
--- /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\377<p\001\377J\204"
+ "\001\377I\206\001\377I\206\002\377Q\222\001\377V\233\001\377Q\223\001\377J\224\002\377"
+ "L\225\005\377G\220\001\377C\210\001\377P\227\005\377R\223\004\377N\207\002\377O\216\002"
+ "\377P\221\001\377T\224\001\377^\236\001\377\\\224\001\377X\221\001\377V\223\001\377X"
+ "\230\001\377_\233\001\377m\250\001\377t\257\004\377b\234\001\377Z\225\001\377X\222\001"
+ "\377[\230\002\377k\251\014\377Z\227\002\377[\222\001\377[\217\002\377`\225\002\377d"
+ "\234\001\377c\231\002\377^\223\001\377]\223\001\377^\233\002\377e\246\004\377f\245\002"
+ "\377i\245\001\377e\237\001\377_\227\002\377_\240\003\377\\\234\005\377d\243\024\377"
+ "b\237\024\377m\246\035\377i\246\017\377g\243\003\377h\240\001\377d\230\001\377n\245"
+ "\017\377u\252*\377x\257E\377\201\271V\377t\257;\377a\242\023\377c\244\012\377"
+ "Z\227\002\377c\236\001\377j\240\004\377x\256\033\377}\262\062\377n\244.\377\201\263"
+ "D\377~\263?\377l\245$\377|\265/\377k\246\030\377[\226\002\377Y\225\001\377[\227"
+ "\001\377Y\226\002\377Y\220\001\377d\235\002\377h\241\001\377]\222\001\377Z\222\002\377"
+ "]\232\002\377^\225\001\377j\240\001\377p\247\003\377f\242\002\377Z\231\001\377Z\231\002"
+ "\377\\\232\001\377_\227\002\377d\235\001\377l\246\001\377d\231\001\377a\227\002\377]"
+ "\220\001\377e\234\001\377Z\220\001\377U\214\001\377X\223\001\377]\233\002\377\\\232\002"
+ "\377b\244\010\377N\214\000\377V\230\001\377[\231\001\377[\231\001\377g\247\004\377a"
+ "\240\002\377Y\220\001\377b\237\002\377k\250\002\377g\243\002\377^\224\002\377`\236\001"
+ "\377a\240\001\377d\242\001\377d\235\002\377f\237\001\377h\236\003\377\\\222\001\377h"
+ "\236\005\377m\243\005\377y\257\021\377s\251\015\377l\243\005\377q\252\010\377g\240"
+ "\003\377_\223\001\377h\241\002\377>p\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<K\377|kx\377l^m\377qgu\377\200v\206\377\203z\213\377\215"
+ "\200\221\377\226\210\227\377\270\251\256\377\310\267\267\377\255\232\232"
+ "\377\232\206\214\377\207q}\377\256\235\245\377\262\242\250\377\253\227\240"
+ "\377\256\233\236\377\243\214\213\377\232\204\202\377\211xw\377\217\203\201"
+ "\377\262\247\234\377\244\226\206\377\252\241\223\377\264\257\245\377\253"
+ "\245\232\377\242\231\212\377\217\207p\377wpQ\377ohD\377lg?\377\214\207^\377"
+ "\225\223n\377\227\230r\377\231\232w\377\230\232y\377\232\232}\377\216\212"
+ "n\377\214\207p\377\201zj\377xpd\377l_O\377\203q]\377\221|j\377\236\210~\377"
+ "{aV\377mO?\377x[J\377\206jY\377qP;\377\216vp\377\\A.\377_F\060\377iYN\377"
+ "UE\064\377~sh\377\212\202y\377nef\377{je\377zig\377\205tu\377\202nr\377\211"
+ "uv\377\225\201\200\377\240\217\223\377\234\215\226\377\227\210\223\377\235"
+ "\217\227\377\257\244\253\377\236\220\233\377\203q}\377\245\222\224\377\302"
+ "\267\266\377\266\264\257\377\205\240j\377u\241.\377n\237\016\377v\247\021\377"
+ "n\233\001\377t\243\003\377l\231\002\377c\221\001\377\202\254\030\377\215\256\030\377"
+ "\245\303+\377\200\254\004\377~\253\012\377t\250\004\377n\240\001\377}\246\002\377"
+ "\212\260\001\377\206\256\001\377q\235\002\377r\242\002\377|\252\001\377s\242\002\377"
+ "w\244\002\377v\247\001\377r\250\002\377n\243\001\377m\240\002\377o\245\001\377n\237\001"
+ "\377q\244\001\377s\246\003\377n\235\003\377}\255\016\377w\252\004\377y\251\003\377w"
+ "\250\002\377\215\276\036\377\205\273\033\377t\245\007\377}\253\001\377\177\256\001"
+ "\377b\230\002\377M\177\015\377V\177(\377@F=\377*$*\377\040\023\031\377&\031\032\377"
+ "\027\011\014\377!\030\033\377+&*\377\013\010\012\377\025\016\024\377\071.\071\377\060\037"
+ "-\377\230\211\233\377tjx\377pfr\377|r\200\377\204y\210\377\222\206\225\377"
+ "\230\214\231\377\306\267\273\377\313\274\275\377\245\222\223\377\213u~\377"
+ "\200jw\377\260\235\246\377\271\252\260\377\250\225\235\377\267\245\242\377"
+ "\221|}\377\232\205\206\377\241\217\220\377zmn\377}qk\377\251\243\232\377"
+ "\274\266\260\377\261\255\250\377\256\254\241\377\223\223{\377\225\226}\377"
+ "\234\235\201\377\207\204\\\377\216\216e\377\227\230p\377\226\227q\377\231"
+ "\232w\377\225\231w\377\233\237\202\377\240\242\207\377\232\230|\377\212\204"
+ "j\377bS;\377aQ:\377`M\065\377V=\"\377H-\024\377bH\071\377U<,\377=$\021\377A'"
+ "\024\377nZL\377^C\062\377mUI\377ue\\\377R>,\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"
+ "<a\002\377a\220\022\377[\210\004\377Iq\001\377Hj\030\377N\\@\377ytw\377{pv\377zo"
+ "v\377\233\221\223\377\227\215\213\377ymq\377WKT\377?\065>\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\377<L\002\377AW\002\377"
+ "Xw\007\377\217\235,\377`y\023\377d\215*\377Fg\002\377Hc\002\377@Z\002\377Je\007\377"
+ "`s\067\377nq`\377\214\215\207\377odk\377vkq\377\217\205\206\377\217\206\207"
+ "\377f]^\377L@B\377J@A\377\201zv\377\224\212\201\377\201vi\377\263\251\232"
+ "\377\212wd\377Z@\060\377dM=\377\204ra\377\242\223|\377\230\203i\377\216tZ"
+ "\377\236\210k\377\226~_\377oM)\377jJ\037\377v^\065\377kU-\377oY\062\377ygB\377"
+ "udA\377\215\200f\377~qV\377yiP\377\212\177l\377xjT\377kZA\377vcG\377gQ\060"
+ "\377lS-\377\237\217j\377\250\232y\377\216\177b\377\177lV\377sWA\377tW@\377"
+ "rV;\377v]D\377jL\061\377\222{b\377\245\221~\377\261\241\220\377\242\222\202"
+ "\377\235\217\201\377uka\377L\066+\377nUA\377\261\250\224\377\254\244\226\377"
+ "\200vt\377|sp\377rjh\377sjk\377ohk\377e_e\377XU[\377KIO\377jhi\377~\204w"
+ "\377p\213`\377p\231Q\377j\225\066\377b\226\024\377V\202\002\377^\215\002\377c\215"
+ "\002\377\205\260\011\377y\246\003\377u\240\003\377{\247\002\377e\221\002\377z\245\001"
+ "\377\216\261\002\377\222\264\002\377p\232\001\377o\235\001\377x\246\003\377~\243\001"
+ "\377\212\256\001\377\202\244\002\377|\242\001\377\200\247\002\377\205\254\002\377\203"
+ "\246\002\377\215\253\002\377\207\252\001\377}\241\002\377\203\247\001\377\204\242\003"
+ "\377\212\242\002\377\220\254\002\377\200\240\002\377\205\257\005\377\204\252\005\377"
+ "\204\252\006\377\210\256\004\377h\231\024\377Z\210\002\377Ou\001\377p\224\011\377]"
+ "x\001\377_x\001\377`y\002\377_z\002\377Tn\001\377<W\002\377)I\002\377\065S\002\377Eg\001\377"
+ "Rx\003\377Rw\000\377f\227\004\377Gp\006\377Js\026\377Oy\027\377Bb\000\377Hf\002\377AY\001"
+ "\377B]\003\377Tl#\377Yb@\377{\200p\377wpo\377vlm\377\206{|\377\234\217\217"
+ "\377shl\377[PW\377aW_\377g__\377pkg\377}xs\377\226\216\202\377\251\235\212"
+ "\377q[F\377`E\071\377_J<\377\230\210y\377\222{i\377{^O\377\240\213y\377\274"
+ "\254\233\377\250\221v\377\216rM\377\206lE\377\224~[\377\204pE\377\200nD\377"
+ "}lE\377\211|Z\377\205wZ\377\214\201f\377\217\204g\377\203wX\377\215\203e"
+ "\377\214~\\\377\227\206b\377\210uN\377\236\217l\377\220\177_\377oV@\377j"
+ "Q=\377cH/\377\200eH\377iK.\377mO\062\377\217vZ\377\253\232\204\377\260\240"
+ "\216\377\253\235\214\377\235\212y\377\216\177q\377ZKC\377th[\377\240\223"
+ "\203\377\266\260\237\377\217\207~\377\200vu\377\201zv\377wqo\377khh\377X"
+ "WY\377HGL\377QOU\377[Z_\377vvu\377luc\377c\220K\377]\215\064\377V\212\023\377"
+ "^\225\020\377T\207\001\377l\234\010\377z\245\015\377\202\255\005\377u\235\002\377"
+ "p\226\002\377k\227\001\377t\242\003\377}\252\001\377\214\261\002\377\205\253\001\377"
+ "p\234\002\377s\244\005\377}\257\007\377\207\253\007\377\204\244\002\377y\232\001\377"
+ "\205\241\010\377\210\253\002\377\221\265\003\377\210\253\002\377\226\260\006\377\202"
+ "\244\001\377\200\244\002\377\201\241\002\377\204\235\004\377\215\243\016\377\224\263"
+ "\016\377\205\255\004\377x\236\003\377x\233\003\377\202\255\002\377\203\252\001\377_"
+ "\211\001\377S\201\002\377Z\205\002\377j\223\002\377c}\002\377f\201\001\377g\200\001\377"
+ "g\206\002\377Yx\001\377Lm\001\377Jp\003\377Ks\002\377R}\004\377:_\001\377c\221\033\377W"
+ "\203\027\377Pu\020\377q\232<\377?m\010\377\066\\\004\377Nq\017\377Lj\007\377So\015"
+ "\377k\202\065\377i|I\377x{l\377pnj\377xrq\377\207\177}\377\236\223\221\377"
+ "ukl\377^SZ\377ZQX\377kfj\377e^a\377\177xs\377\203yp\377\261\251\234\377\244"
+ "\227\201\377mXA\377}k^\377|jb\377\217~s\377nXJ\377\213yi\377\262\244\220"
+ "\377\257\237\204\377\235\206d\377\213qN\377\216sM\377\240\216j\377\227\205"
+ "a\377\224\204`\377\202sO\377\204tS\377\204vT\377\225\210h\377\211|\\\377"
+ "\223\211j\377\223\212n\377\233\215s\377\207vZ\377\206rQ\377\216zY\377v`?"
+ "\377hR\060\377cJ*\377u[;\377\210oQ\377\227\200e\377\251\230\202\377\256\240"
+ "\216\377\247\231\207\377\230\207v\377\222\203v\377S@\062\377I\064\"\377\202"
+ "q\\\377\247\235\213\377\215\203v\377phc\377voj\377nfd\377]VU\377ZUY\377h"
+ "fl\377c`g\377ROX\377a`e\377xwx\377XdI\377c\236H\377I|\015\377Q\211\007\377U"
+ "\217\011\377S\211\001\377n\237\003\377o\234\002\377t\235\001\377x\236\002\377r\231\002"
+ "\377g\222\002\377m\230\003\377\204\254\001\377\201\247\002\377|\245\001\377r\242\002"
+ "\377k\237\016\377p\253%\377\201\250\025\377\201\244\001\377p\224\002\377\201\231"
+ "\007\377\225\257\004\377\226\260\003\377\217\263\001\377\232\267\012\377w\230\001\377"
+ "z\235\002\377\203\246\002\377\203\241\003\377\230\263\027\377\225\261\015\377\212"
+ "\254\001\377y\232\002\377|\242\002\377\177\254\002\377}\250\002\377X\204\002\377Hy\001"
+ "\377T\202\003\377Jp\004\377`\177\002\377m\222\003\377e\215\002\377b\212\002\377X~\002\377"
+ "Sz\003\377Ry\001\377Fc\002\377\062J\002\377\064J\002\377>Z\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<W!\377Ck\035\377J~\034\377h\230\060"
+ "\377Iv\002\377X\215\006\377V\207\006\377c\222\001\377z\234\010\377i\226\013\377\225"
+ "\271\066\377x\236\000\377`\216\002\377o\240\001\377k\232\010\377o\237\002\377o\236"
+ "\003\377y\253-\377|\246\023\377\200\260\033\377j\231\001\377}\252\005\377p\241\001"
+ "\377u\242\010\377\221\273R\377g\235(\377\246\303`\377\215\246\002\377\221\256"
+ "\001\377u\231\002\377\213\251\005\377\264\315W\377\210\253\002\377y\234\000\377\221"
+ "\276@\377\205\256\013\377\222\272\016\377\226\274\022\377\230\276\025\377\220"
+ "\257\001\377\224\261\003\377\217\252\002\377\243\276\030\377\324\344\321\377x\244"
+ "\066\377d\226\021\377X\212\004\377Q\202\003\377\\\220\004\377e\222\014\377_\221\003"
+ "\377]\216\001\377J|\002\377O\200\002\377`\225\007\377R{\004\377Ej\001\377U|\003\377[\207"
+ "\005\377f\233\002\377P}\013\377V~\004\377\\\204\006\377Kl\001\377Y\204\002\377u\235\025"
+ "\377w\235\027\377X\207\013\377S\204\010\377^\215\027\377E`\033\377Rh\064\377my"
+ "_\377\224\224\215\377\237\237\232\377\225\225\220\377\203|x\377}vv\377wr"
+ "t\377gaf\377ojl\377\224\215\217\377\263\260\254\377\227\223\213\377\207\205"
+ "x\377\223\215{\377\177x]\377zoM\377\226\217m\377\225\215m\377\251\237\202"
+ "\377\250\237\204\377\262\252\224\377\230\213r\377\177oY\377p]O\377jWN\377"
+ "YC\067\377kXL\377T?\063\377eSG\377jXH\377{h[\377veW\377l\\M\377i\\M\377k\\"
+ "J\377zhT\377\235\214y\377\260\241\215\377\254\235\211\377\266\251\225\377"
+ "\257\244\220\377\260\250\224\377\214}d\377\214~c\377\221\204n\377xlY\377"
+ "\226\222\207\377\230\227\220\377\222\225\212\377\213\231\200\377\177\227"
+ "r\377etX\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<T\006\377:M\013\377S\\\070\377UVF\377GC?\377VJQ\377xk"
+ "s\377ujp\377]RZ\377#\027\035\377\062*.\377;\065\067\377ONJ\377ute\377\217\213"
+ "q\377\214\210h\377\233\231x\377\207\210a\377\204\207[\377\200\205W\377\234"
+ "\232w\377\220\214i\377\227\220n\377\203zV\377\226\221q\377\231\224x\377\226"
+ "\221y\377\243\240\214\377\236\237\212\377\224\225\202\377\217\217\177\377"
+ "\223\224\206\377\230\231\217\377\221\220\213\377lfe\377JCE\377\060)+\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\377<m\022\377O\206"
+ "\031\377M\205\012\377N\203\003\377R\202\030\377c\222%\377P\177\000\377P\177\002\377"
+ "^\217\025\377c\224!\377f\226\003\377Ov\002\377h\234\003\377R\206\002\377e\231\016\377"
+ "R\200\004\377q\236\011\377\202\260'\377\226\277\070\377\212\270C\377l\234\036"
+ "\377n\236\025\377t\241\003\377q\242\013\377y\253\011\377\211\265\004\377m\237\022"
+ "\377\206\264D\377p\244\021\377\203\267E\377\225\276?\377\201\256#\377t\245"
+ "\007\377\246\257\062\377\232\260,\377\232\272\066\377\177\243\"\377}\235\017\377"
+ "\232\300H\377n\234\001\377|\237\005\377\207\243\000\377\266\306/\377\231\275-\377"
+ "\205\256\026\377x\233\004\377\206\253\006\377\215\271\063\377S\213\003\377O\213\003"
+ "\377G}\002\377Z\210\001\377U\204\002\377Z\215\003\377]\217\013\377b\217\004\377l\231"
+ "\007\377c\217\003\377\\\215\003\377c\224\017\377a\220\010\377i\230\014\377b\222\000"
+ "\377b\222\002\377j\231\001\377\230\274\011\377p\222\002\377p\223\002\377v\230\002\377"
+ "{\233\005\377e}\002\377fx\002\377\215\241\006\377~\222\006\377z\245\004\377n\225\002\377"
+ "s\214\031\377\206\257:\377_\205\002\377^}\004\377q\216\035\377s\215\032\377g\177"
+ "\014\377>J\001\377<I\001\377Wg!\377JW&\377nqQ\377\207\205s\377zsg\377\215\205"
+ "}\377\205}s\377\237\222\201\377\254\237\210\377\244\226y\377\224\204g\377"
+ "xhJ\377vqF\377pq?\377}{S\377}{Z\377\210\207f\377\211\212g\377\207\206`\377"
+ "\206\204Z\377\206\204X\377\226\224m\377\213\212d\377\204\202c\377\211\212"
+ "l\377\206\206m\377\221\223}\377\220\225~\377\230\242\213\377\227\231\217"
+ "\377\201~x\377\214\207~\377\214\205x\377~wl\377}yo\377mji\377\203\234f\377"
+ "y\242X\377\062L%\377\034<\015\377Fn!\377&F\003\377\071`\010\377[\217!\377K\205\007"
+ "\377Fz\002\377_\223\015\377f\235!\377L\200\000\377P\202\002\377W\207\002\377Z\211"
+ "\022\377f\225\025\377f\220\003\377R\177\002\377j\234\003\377^\215\002\377b\222\016\377"
+ "{\244\005\377\206\262=\377g\236\007\377\206\265\061\377a\225\016\377Z\212\002\377"
+ "g\223\003\377{\250\002\377y\246\017\377\222\271\007\377\200\253\006\377j\230\020\377"
+ "\206\263\066\377\257\321n\377e\230\014\377i\233\002\377q\236\003\377i\236\007\377"
+ "p\235\002\377\215\262\004\377x\234\003\377q\227\000\377\253\274H\377\215\263\062\377"
+ "u\246\024\377\201\245\005\377\246\270\030\377\237\257\003\377\240\273\026\377\214"
+ "\265\033\377\202\245\010\377\236\301\033\377\205\266(\377Y\227\002\377K\207\003"
+ "\377Y\223\003\377f\220\012\377\\\207\002\377^\214\002\377]\217\002\377`\221\004\377"
+ "[\215\002\377Y\213\002\377p\234\022\377l\225\024\377c\213\007\377g\221\005\377a\215"
+ "\002\377c\217\002\377g\222\002\377\210\256\003\377s\232\001\377s\227\002\377t\223\002\377"
+ "t\220\004\377\202\225\005\377j}\002\377\211\227\004\377\177\224\007\377j\203\005\377"
+ "q\215)\377y\242*\377i\236\003\377i\226\007\377d\213\006\377a\205\005\377Qq\001\377"
+ "Ni\001\377`v\006\377z\212\031\377dn\006\377mo\016\377]`\037\377ggB\377po_\377\177"
+ "|t\377voi\377\220\206z\377\222\204q\377\202rX\377\207yW\377|tA\377\203\177"
+ "D\377\200uG\377xmG\377rkH\377}y\\\377\213\212k\377\214\212h\377}|S\377\212"
+ "\210`\377\231\230r\377\212\210c\377\213\207g\377\220\217r\377\215\215u\377"
+ "\220\222}\377\223\226\202\377\232\242\214\377\221\221\211\377vqi\377ysh\377"
+ "\205~q\377wti\377TSN\377^eS\377c\217E\377r\235R\377\065U\033\377Nv\060\377&"
+ "K\001\377:e\006\377Gy\014\377>x\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\377<k\004\377"
+ "@r\014\377>o\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"
+ "<l\004\377:j\010\377\063e\001\377O\205\002\377F~\002\377N\205\003\377J~\004\377L\202\003"
+ "\377Z\222\002\377Z\216\003\377l\233\006\377u\232\022\377O\177\000\377O\201\003\377U"
+ "\203\002\377\206\257\007\377j\231\010\377d\234\036\377r\243\023\377\216\265\004\377"
+ "X\205\001\377a\222\031\377\217\261\026\377\177\240\016\377}\243\001\377n\227\003\377"
+ "w\243\010\377\205\255\001\377x\242\002\377y\246\005\377o\237\003\377l\233\002\377v\242"
+ "\005\377k\232\004\377r\241\002\377|\251\004\377n\231\005\377\212\264\012\377\235\271"
+ "\016\377\200\243\012\377\177\247\016\377z\244\027\377\215\272/\377u\251\014\377"
+ "\211\277H\377\212\274?\377\211\273A\377}\260$\377\213\270-\377v\234\015\377"
+ "~\235\014\377\207\247\006\377Pz\002\377\\\201\010\377]\210\003\377L\177\004\377X\216"
+ "\006\377Q}\002\377^\206\007\377\207\231\025\377d\214\002\377X|\004\377S}\005\377Ao\001\377"
+ "R\202\004\377b\216\003\377y\235\002\377`\203\001\377b\212\003\377\210\264\011\377o\232"
+ "\013\377x\243\007\377d\225\023\377\244\314\207\377\245\316\177\377\256\324{\377"
+ "\250\313c\377\205\244'\377\232\256'\377w\234\040\377s\235\037\377h\221\016\377"
+ "w\243\017\377d\220\003\377x\247\027\377\213\265@\377\177\256\063\377w\255*\377"
+ "\\\231\015\377\\\225\011\377]\215\007\377Sv\007\377Vx\006\377i\212\022\377Y|\011\377"
+ "ct+\377\300\265\244\377\253\235\217\377\234\213\204\377\237\215\205\377\236"
+ "\214\203\377\220~s\377o]T\377fVD\377\200qW\377\242\221t\377\245\223y\377"
+ "\232\207q\377SLD\377\025\022\007\377N=\062\377kTH\377iO;\377oVC\377iO=\377L\064"
+ "(\377?\060'\377\063+!\377oyR\377M[\061\377L\\/\377Y}\060\377Jq\"\377Q\211\040"
+ "\377W\217*\377c\234*\377I\204\013\377@q\001\377Ao\011\377\063W\001\377M\203\003\377"
+ "N\210\002\377S\220\004\377Q\215\005\377Fz\005\377P\205\003\377S\210\002\377u\241\006\377"
+ "b\211\002\377d\212\007\377P\202\012\377R\205\004\377Y\212\003\377h\226\004\377g\223"
+ "\015\377G\201\006\377x\253\020\377~\255\003\377a\226\012\377u\250\013\377y\247\031"
+ "\377t\234\003\377v\234\001\377j\230\020\377\177\257\005\377\177\242\013\377\215\262"
+ "\004\377q\242\013\377p\247\015\377n\234\002\377\221\257\004\377\202\252\011\377\177"
+ "\250\001\377\232\274\003\377\211\251\003\377n\236\012\377\227\274\060\377\214\257"
+ "\061\377\210\251\003\377~\233\001\377s\224\003\377\202\254\033\377\204\264/\377\201"
+ "\251\002\377|\240\006\377\201\261\010\377t\243\003\377r\237\001\377\206\243\023\377"
+ "\217\247\011\377Mp\002\377\\{\003\377Y~\002\377Hl\001\377Vw\003\377Ow\002\377Y\205\003\377"
+ "k\221\002\377h\227\004\377T\200\003\377o\220\030\377Hs\002\377R\177\003\377c\224\003\377"
+ "m\236\010\377Y\207\003\377e\224\004\377\214\266\002\377\204\257\004\377p\241\024\377"
+ "t\237'\377\203\237\026\377\230\247\034\377\233\260(\377\202\255K\377\255\325"
+ "\204\377\241\315w\377t\242\066\377g\232!\377v\247\063\377d\233\027\377m\245"
+ "!\377m\235\"\377j\233\026\377a\222\016\377Z\214\004\377d\230\017\377`\232\007\377"
+ "X\216\002\377Ry\003\377f\203\014\377No\007\377Sn\013\377m\201(\377\261\247\233\377"
+ "\264\247\237\377\245\226\223\377\224\204\200\377\230\210\200\377\223\206"
+ "q\377\206\201]\377lgF\377kaH\377\224\203n\377\254\235\206\377\246\224\202"
+ "\377\\QL\377'\040\020\377dPE\377v]P\377|bP\377tT@\377iI\067\377cF\067\377WB\063"
+ "\377F\064$\377iqD\377F^\035\377Qs!\377Qz\022\377Hx\011\377=l\006\377c\222\061\377"
+ "c\234'\377J{\013\377Q\204\017\377M\204\011\377S\213\005\377W\221\002\377N\211\002"
+ "\377X\224\002\377S\213\001\377H}\002\377Y\223\003\377`\227\003\377m\237\011\377a\225"
+ "\031\377V\213\016\377y\253:\377x\253\031\377e\231\005\377v\246\015\377n\241\"\377"
+ "w\246'\377v\247\006\377{\254\005\377\\\214\003\377\211\257\004\377y\244\011\377\215"
+ "\261\003\377\202\251\006\377p\230\003\377{\251\004\377s\237\006\377\217\265\016\377"
+ "p\233\002\377a\230\014\377x\241\005\377\234\267\004\377\223\262\002\377}\247\004\377"
+ "\214\257\003\377\177\243\020\377g\222\001\377v\231\000\377\222\251\003\377\217\262"
+ "\023\377\226\271\064\377u\233\012\377p\233\012\377~\256$\377\242\310P\377\202"
+ "\242\002\377\203\246\005\377\211\246\032\377m\226\001\377z\243\001\377\215\253\004\377"
+ "Ul\003\377^|\003\377P{\003\377U~\004\377j\216\005\377c\212\006\377f\215\005\377h\227\006"
+ "\377o\246\004\377_\217\005\377`\213\006\377Js\001\377X\207\002\377b\224\003\377b\220"
+ "\003\377X\213\002\377j\233\004\377\214\265\004\377p\233\003\377h\220\006\377~\231\012"
+ "\377b\202\012\377j\220\007\377h\227\035\377n\245-\377f\235\025\377a\234\016\377"
+ "\213\302O\377\213\276R\377n\237&\377Z\204\011\377e\210%\377s\236\061\377p\231"
+ "\036\377p\243\031\377o\247\024\377`\235\011\377]\234\014\377K\205\003\377T\212\002"
+ "\377a\221\002\377b\202\005\377c\204\004\377Y~\023\377\234\242\202\377\235\224\216"
+ "\377\222\206\204\377\220\210{\377\220\230l\377\210\220a\377\230\235s\377"
+ "\231\233s\377\233\234s\377\247\246\200\377\252\251\203\377\237\243|\377r"
+ "\177Y\377Vj\067\377s\202G\377|\206G\377{\177=\377rs\061\377`]\030\377[W\034\377"
+ "`Z+\377cc\066\377h|\070\377a\213&\377K\177\006\377I~\005\377Dz\003\377H~\010\377\207"
+ "\263<\377T\203\021\377Q\207\023\377Z\232\023\377b\233\015\377[\220\002\377P\211"
+ "\004\377:p\002\377R\212\004\377V\214\005\377b\236\023\377Z\217\002\377p\243\004\377X\222"
+ "\011\377Z\222\013\377P\204\000\377\245\310^\377m\237\022\377m\233\011\377u\240"
+ "\005\377{\246\027\377y\252\004\377v\253\010\377{\250\005\377\200\250\004\377Uu\002\377"
+ "u\243\027\377~\250\003\377{\237\015\377y\247\002\377q\237\007\377\206\250\006\377\203"
+ "\255$\377x\250\023\377s\244\024\377\262\307\006\377\225\271\015\377\201\251#\377"
+ "u\236\000\377\177\245\004\377n\230\004\377i\226\004\377q\245\017\377\177\256\035\377"
+ "y\243\005\377~\255\006\377\204\256,\377\201\247$\377s\227\010\377\253\324u\377"
+ "\203\251\014\377\206\255#\377\214\255\030\377z\237\002\377\205\246\002\377\206"
+ "\244\002\377bs\005\377i\216\003\377Dt\004\377V|\004\377Yh\002\377V_\002\377hv\003\377Pf\002"
+ "\377m\241\003\377X\220\004\377e\227\005\377\210\263\007\377t\245\016\377q\245\002\377"
+ "g\235\002\377Y\220\004\377\177\255\004\377}\245\030\377s\237.\377g\221\007\377r\231"
+ "$\377^\216\013\377V\205\005\377u\232\014\377f\232\010\377W\214\001\377V\206\000\377"
+ "Z\217\000\377u\253\061\377z\257\071\377[\224\006\377_\224\011\377q\242\035\377q\243"
+ "\026\377\\\225\001\377\\\233\005\377X\224\006\377_\232\015\377\\\231\010\377S\214"
+ "\004\377\\\226\006\377b\231\011\377Z\216\006\377Y\217\005\377t\226.\377t\220-\377"
+ "t\220+\377\202\217]\377kwJ\377stX\377ieK\377iaJ\377\200r\\\377\231\205n\377"
+ "\242\216t\377\222za\377S?\066\377]H<\377\220tZ\377\211nQ\377|a>\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<m\002\377J~\004\377U\211\002\377u\236.\377Rp\005\377e\234\025\377\214\272d"
+ "\377N\177\000\377\227\261\061\377\274\317\215\377a\222\016\377q\237\005\377h\223"
+ "\002\377\216\271\034\377b\222\015\377V\214\014\377m\241\007\377\200\244\003\377\204"
+ "\251\003\377o\235\003\377v\242\004\377p\231\003\377v\241\002\377c\221\003\377\207\254"
+ "\006\377\231\254\006\377\277\306\017\377~\242\006\377Uw\002\377{\245\004\377d\221\004"
+ "\377k\223\003\377o\225\002\377k\224\004\377i\224\004\377{\237\002\377\203\244\001\377"
+ "\204\246\003\377q\230\004\377w\242\002\377p\223\003\377\177\236\003\377\222\252\002\377"
+ "\205\240\001\377\201\255\022\377\212\263\030\377\201\251\035\377\204\256(\377"
+ "p\222\002\377z\224\001\377S{\003\377S\206\002\377U\205\004\377Y\211\006\377[\213\005\377"
+ "`\220\003\377\\}\003\377`\200\003\377p\235\003\377}\242\015\377R\204\010\377T\216\004"
+ "\377]\222\004\377o\237\002\377n\221\015\377\201\251\012\377\224\273\006\377c\220"
+ "\012\377]\213\002\377q\235\014\377^\217\004\377b\223\004\377]\221\003\377U\210\002\377"
+ "`\220\010\377U{\000\377f\201\005\377}\226\024\377n\231\004\377e\224\007\377Y\215\016"
+ "\377b\225\023\377z\246\032\377Z\210\005\377\\\215\013\377f\220\022\377i\225\034"
+ "\377^\215\020\377^\220\016\377\\\215\006\377]\221\005\377X\214\003\377L|\002\377S\204"
+ "\002\377V\206\006\377g\221#\377Lx\015\377[\210\037\377s\232\071\377w\220@\377o\215"
+ "\065\377X\177\032\377]\213\040\377_\216!\377b\215\"\377h\216)\377j\206\062\377"
+ "\232\251s\377\224\233k\377\204rB\377\223uJ\377\224sG\377\253\217@\377z~\035"
+ "\377^|\021\377Ej\000\377i\214\061\377R{\023\377[\215\004\377\213\271N\377Y\216\026"
+ "\377f\237*\377`\234)\377J\202\003\377T\211\016\377a\225\006\377i\227\004\377f\223"
+ "\003\377Op\001\377U\214\002\377g\230\032\377\211\251<\377k\234\006\377P\203\006\377"
+ "b\216\040\377n\236.\377\216\266\070\377\231\271@\377\177\256\004\377P{\020\377"
+ "^\223\010\377\212\265\033\377_\221\001\377T\215\016\377y\245\014\377\261\302\004"
+ "\377x\243\005\377u\240\003\377~\252\005\377h\222\004\377c\222\003\377\200\241\003\377"
+ "\256\275\006\377\202\240\004\377\216\245\007\377i\220\002\377f\215\002\377v\240\004\377"
+ "f\214\004\377k\221\003\377m\230\003\377f\220\001\377m\226\005\377\177\243\004\377\206"
+ "\246\006\377\177\243\002\377v\235\004\377n\225\002\377y\240\004\377\223\254\002\377\207"
+ "\235\004\377i\217\001\377~\243\024\377\233\273D\377\201\247\026\377{\244\005\377"
+ "\200\252\025\377\206\226\017\377Fp\002\377Dq\002\377K|\003\377N|\002\377b\204\005\377"
+ "Y{\012\377Sv\007\377W|\005\377s\240\026\377{\237\004\377a\201\005\377H}\004\377P\207"
+ "\002\377b\227\003\377o\243\004\377]\220\003\377p\235\004\377M~\002\377X\211\004\377S\203"
+ "\003\377h\226\004\377\\\217\003\377J\200\003\377X\214\003\377R\204\002\377d\220\003\377"
+ "b\211\002\377l\221\002\377l\231\005\377K\177\002\377Z\213\002\377\210\252\013\377i\220"
+ "\020\377`\220\021\377d\227$\377_\230\034\377d\235\017\377W\213\004\377V\205\003\377"
+ "U\206\005\377Lx\003\377i\224\007\377p\227\004\377\216\247#\377\245\262<\377\231\256"
+ ",\377\214\247\033\377\203\247\023\377\201\251\030\377\204\255\035\377\223\265"
+ "\064\377\217\256\071\377\205\241\066\377v\223(\377|\233,\377j\217\017\377`\213"
+ "\011\377g\215!\377d\207\"\377i\210#\377h\207!\377g\211!\377q\220\015\377y\233"
+ "\007\377Iq\011\377\\|\037\377}\253\066\377j\232\025\377l\233\010\377\177\255C\377"
+ "]\227\020\377\\\224\016\377Fu\001\377Cv\004\377g\232\016\377U\203\003\377S\202\003\377"
+ "^\214\004\377t\245\007\377_\233\010\377k\240\024\377g\235\021\377^\222\004\377Z\205"
+ "\004\377W\202\003\377\201\261?\377t\236\025\377\203\254\023\377o\236\040\377`\224"
+ "\007\377U\211\011\377\217\273\013\377a\223\001\377b\240\034\377t\234\005\377\224\256"
+ "\004\377p\234\002\377\207\253\006\377\202\251\004\377m\231\003\377\220\257\005\377\246"
+ "\274\005\377b\214\002\377d\221\003\377\210\243\011\377}\233\002\377\240\277\"\377"
+ "e\216\001\377\210\253\006\377o\224\002\377f\223\002\377~\251\017\377n\223\011\377{"
+ "\240\001\377\230\262\027\377\177\242\004\377v\227\004\377}\237\005\377\212\252\006\377"
+ "r\221\003\377s\226\002\377r\235\002\377\236\270<\377\216\236:\377\257\313j\377"
+ "t\235\000\377|\245\017\377\222\276\037\377T\205\003\377R\204\002\377K\177\002\377D"
+ "r\001\377[v\021\377d}\017\377\210\202\031\377x\233\060\377m\234\001\377M\202\002\377"
+ "W\214\004\377W\215\022\377J\201\003\377g\236\003\377c\230\003\377z\252\003\377^\221"
+ "\003\377V\214\003\377M\202\002\377Qw\002\377^\207\003\377c\217\003\377`\220\003\377X\214"
+ "\003\377_\224\000\377b\217\005\377c\213\002\377f\226\002\377P\204\003\377]\217\002\377"
+ "p\235\003\377\223\255\004\377~\236\006\377f\235\010\377m\241\035\377d\226\007\377["
+ "\210\002\377T{\006\377U\177\006\377Z\207\004\377]\216\002\377^\216\003\377R~\003\377X\202"
+ "\010\377\200\235.\377\250\260e\377\230\245a\377\216\246a\377\220\250a\377"
+ "\240\250r\377\231\224b\377\206wC\377\230\210R\377\222\217L\377\227\214X\377"
+ "\206\201L\377^m\061\377as\071\377\211\226M\377\226\240P\377l\204&\377T~\027"
+ "\377[\214\016\377r\235\002\377x\234\021\377\220\256@\377\202\254M\377\\\221\023"
+ "\377s\243\020\377f\232(\377_\226\022\377Dn\003\377[\204\000\377u\244'\377S|\001\377"
+ "Kq\002\377?e\002\377J\204\004\377J\210\006\377T\214\011\377\177\260\031\377u\245\003"
+ "\377Qz\004\377Hs\002\377V\203\006\377k\232\023\377z\247\024\377f\231\016\377Y\213"
+ "\010\377W\207\004\377P\202\004\377~\256\006\377[\220\006\377^\227\026\377\230\265\003"
+ "\377j\222\004\377u\242\002\377\200\240\014\377\212\260\004\377\221\265\004\377s\231"
+ "\004\377\201\245\004\377Y\206\002\377q\227\005\377\207\251\003\377}\232\004\377\224\261"
+ "\003\377\213\254\005\377y\236\002\377g\221\002\377s\241\005\377\201\250\004\377d\215"
+ "\002\377q\233\004\377\221\260\016\377x\226\003\377z\225\006\377~\241\006\377x\243\003"
+ "\377h\227\002\377{\250\025\377\200\251\021\377\203\257\027\377~\240\032\377{\231"
+ "\035\377\206\253&\377z\241\001\377x\250\017\377U\210\004\377Y\207\003\377Z\204\002"
+ "\377bg\004\377\202t\005\377{\200\004\377h\210&\377S\210\005\377T\216\003\377T\212\005"
+ "\377V\207\003\377X\205\004\377S\204\005\377g\241\002\377]\226\003\377e\232\003\377i\231"
+ "\002\377K\177\003\377V\215\013\377Z\201\012\377^\213\006\377`\220\007\377\\\217\003\377"
+ "[\223\003\377~\250?\377b\222\010\377h\230\005\377R\210\003\377]\224\003\377d\234\002"
+ "\377r\251\004\377k\237\001\377d\233\004\377c\232\015\377_\224\005\377T\206\002\377["
+ "\204\004\377Zz\014\377_\201\020\377h\223\026\377Lz\004\377@b\003\377\070Y\003\377(I\002"
+ "\377lw!\377}\203\060\377\223\226L\377\273\265|\377y\206%\377Sm\026\377Wq.\377"
+ "d|>\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\377<m\000\377\177\254R\377x\252S\377c\217\020"
+ "\377a\223\005\377Et\002\377f\210\021\377p\231\007\377r\246\003\377\203\256\014\377"
+ "X\205\003\377U\205\002\377c\226\016\377Co\004\377h\216\004\377\205\256\004\377_\217"
+ "\004\377\202\264\032\377~\244\005\377a\217\006\377\203\251\007\377\224\252\015\377"
+ "u\245\002\377^\215\003\377\204\253\003\377o\231\012\377\\\210\002\377\202\245\005\377"
+ "}\241\005\377\222\263\004\377\210\243\003\377n\220\002\377|\240\006\377x\224\004\377"
+ "u\231\004\377\217\255\010\377q\226\002\377{\242\010\377}\240\000\377\217\256\001\377"
+ "\202\234\010\377\215\267\005\377\207\262\004\377r\231\010\377p\230\002\377o\232\007"
+ "\377z\252\024\377\202\255\035\377\202\256\005\377\206\263\021\377\203\254\005\377"
+ "\177\243\002\377l|\005\377Q\200\003\377X\177\005\377_\205\004\377g\234\006\377^\230\006"
+ "\377S\212\001\377i\234\004\377d\226\003\377_u\006\377i\220\017\377O\201\004\377[\222"
+ "\003\377^\224\003\377B{\002\377h\236\003\377Lz\001\377d\226\011\377Y\203\003\377[\215"
+ "\000\377P\201\001\377{\245\061\377{\237\071\377Sy\010\377Gw\002\377a\230\025\377c\231"
+ "\002\377X\215\003\377\\\223\005\377^\226\015\377T\213\001\377b\230\020\377\\\220\005"
+ "\377V\212\010\377e\234\011\377b\231\015\377n\236\031\377c\220\032\377Y\214\017"
+ "\377a\227\012\377]\216\002\377v\246\017\377i\225\020\377e\212\025\377d\202\031\377"
+ "\236\271c\377\207\261I\377r\231%\377a\177\024\377v\215!\377dy\000\377m\214\022"
+ "\377X\202\007\377V\206\004\377\\\220\011\377M~\004\377Gv\004\377Jv\007\377U\206\023\377"
+ "y\251&\377\205\252\063\377Oe\013\377Ni\016\377n\224\003\377t\236\"\377x\243D\377"
+ "c\230\001\377L\200\013\377y\244\040\377G{\007\377Bp\003\377Q~\034\377b\223\061\377"
+ "\063W\002\377Ap\017\377J\203\016\377x\252H\377h\237;\377L}\014\377u\230\011\377"
+ "Uu\002\377\\{\005\377g\220\010\377t\240\001\377\233\271;\377\223\261\"\377a\230"
+ "\005\377g\226\025\377R|\004\377S}\002\377l\224\004\377\205\257\003\377h\227\023\377\224"
+ "\267\012\377z\244\010\377t\237\006\377\236\265\005\377o\233\002\377v\244\011\377i"
+ "\230\010\377|\243\002\377h\223\004\377f\221\003\377v\236\003\377s\233\002\377\213\260"
+ "\003\377p\225\004\377u\234\003\377}\244\002\377w\233\003\377d\215\003\377n\230\001\377"
+ "o\230\003\377v\240\020\377w\241\007\377\203\257\026\377\200\254\031\377\177\250"
+ "\006\377\221\266\003\377z\217\004\377v\224\002\377s\226\001\377w\241\017\377\200\253"
+ "\026\377u\244\004\377s\244\004\377x\244\003\377u\232\002\377X\210\003\377`\212\007\377"
+ "}\210\023\377d{\003\377\\\206\003\377Gv\002\377C}\002\377V\214\003\377m\235\006\377v\222"
+ "\011\377Z\210\003\377O\206\004\377k\242\006\377Z\221\002\377T\206\004\377`\223\003\377"
+ "Jy\002\377Pz\002\377_\213\003\377a\222\003\377Z\212\004\377{\233\030\377T|\010\377t\241"
+ "\067\377L\200\001\377W\220\014\377g\237\007\377\\\221\002\377a\213\011\377V\211\002"
+ "\377a\226\013\377\204\260/\377q\240\020\377s\243\022\377v\244)\377{\254@\377"
+ "d\232#\377K\200\032\377Ev\037\377a\213\060\377j\227*\377\\\214\026\377Q\201\010"
+ "\377M}\004\377p\246/\377\224\277[\377u\245)\377g\230\016\377e\225\011\377\177"
+ "\250*\377j\231\013\377^\216\003\377`\223\003\377W\207\004\377Do\004\377Q\200\005\377"
+ "Ku\002\377h\230\027\377\225\275F\377\246\311_\377\203\237B\377j\212!\377d\201"
+ "\021\377w\241\017\377l\237\022\377\226\267N\377[\213\000\377U\204\014\377m\240"
+ "\021\377@j\005\377Ov\011\377a\211\034\377Y\200\040\377>a\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 00000000..46dc50f6
--- /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/dmabuf-capture.c b/examples/dmabuf-capture.c
new file mode 100644
index 00000000..ebbe0a70
--- /dev/null
+++ b/examples/dmabuf-capture.c
@@ -0,0 +1,850 @@
+#define _POSIX_C_SOURCE 199309L
+#include <libavformat/avformat.h>
+#include <libavutil/display.h>
+#include <libavutil/hwcontext_drm.h>
+#include <libavutil/pixdesc.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <drm_fourcc.h>
+#include "wlr-export-dmabuf-unstable-v1-client-protocol.h"
+
+struct wayland_output {
+ struct wl_list link;
+ uint32_t id;
+ struct wl_output *output;
+ char *make;
+ char *model;
+ int width;
+ int height;
+ AVRational framerate;
+};
+
+struct fifo_buffer {
+ AVFrame **queued_frames;
+ int num_queued_frames;
+ int max_queued_frames;
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+ pthread_mutex_t cond_lock;
+};
+
+struct capture_context {
+ AVClass *class; /* For pretty logging */
+ struct wl_display *display;
+ struct wl_registry *registry;
+ struct zwlr_export_dmabuf_manager_v1 *export_manager;
+
+ struct wl_list output_list;
+
+ /* Target */
+ struct wl_output *target_output;
+ bool with_cursor;
+
+ /* Main frame callback */
+ struct zwlr_export_dmabuf_frame_v1 *frame_callback;
+
+ /* If something happens during capture */
+ int err;
+ bool quit;
+
+ /* FFmpeg specific parts */
+ pthread_t vid_thread;
+ AVFrame *current_frame;
+ AVFormatContext *avf;
+ AVCodecContext *avctx;
+ AVBufferRef *drm_device_ref;
+ AVBufferRef *drm_frames_ref;
+ AVBufferRef *mapped_device_ref;
+ AVBufferRef *mapped_frames_ref;
+
+ /* Sync stuff */
+ struct fifo_buffer vid_frames;
+
+ int64_t start_pts;
+
+ /* Config */
+ enum AVPixelFormat software_format;
+ enum AVHWDeviceType hw_device_type;
+ AVDictionary *encoder_opts;
+ int is_software_encoder;
+ char *hardware_device;
+ char *out_filename;
+ char *encoder_name;
+ float out_bitrate;
+};
+
+static int init_fifo(struct fifo_buffer *buf, int max_queued_frames) {
+ pthread_mutex_init(&buf->lock, NULL);
+ pthread_cond_init(&buf->cond, NULL);
+ pthread_mutex_init(&buf->cond_lock, NULL);
+ buf->num_queued_frames = 0;
+ buf->max_queued_frames = max_queued_frames;
+ buf->queued_frames = av_mallocz(buf->max_queued_frames * sizeof(AVFrame));
+ return !buf->queued_frames ? AVERROR(ENOMEM) : 0;
+}
+
+static int get_fifo_size(struct fifo_buffer *buf) {
+ pthread_mutex_lock(&buf->lock);
+ int ret = buf->num_queued_frames;
+ pthread_mutex_unlock(&buf->lock);
+ return ret;
+}
+
+static int push_to_fifo(struct fifo_buffer *buf, AVFrame *f) {
+ int ret;
+ pthread_mutex_lock(&buf->lock);
+ if ((buf->num_queued_frames + 1) > buf->max_queued_frames) {
+ av_frame_free(&f);
+ ret = 1;
+ } else {
+ buf->queued_frames[buf->num_queued_frames++] = f;
+ ret = 0;
+ }
+ pthread_mutex_unlock(&buf->lock);
+ pthread_cond_signal(&buf->cond);
+ return ret;
+}
+
+static AVFrame *pop_from_fifo(struct fifo_buffer *buf) {
+ pthread_mutex_lock(&buf->lock);
+
+ if (!buf->num_queued_frames) {
+ pthread_mutex_unlock(&buf->lock);
+ pthread_cond_wait(&buf->cond, &buf->cond_lock);
+ pthread_mutex_lock(&buf->lock);
+ }
+
+ AVFrame *rf = buf->queued_frames[0];
+ for (int i = 1; i < buf->num_queued_frames; i++) {
+ buf->queued_frames[i - 1] = buf->queued_frames[i];
+ }
+ buf->num_queued_frames--;
+ buf->queued_frames[buf->num_queued_frames] = NULL;
+
+ pthread_mutex_unlock(&buf->lock);
+ return rf;
+}
+
+static void free_fifo(struct fifo_buffer *buf) {
+ pthread_mutex_lock(&buf->lock);
+ if (buf->num_queued_frames) {
+ for (int i = 0; i < buf->num_queued_frames; i++) {
+ av_frame_free(&buf->queued_frames[i]);
+ }
+ }
+ av_freep(&buf->queued_frames);
+ pthread_mutex_unlock(&buf->lock);
+}
+
+static void output_handle_geometry(void *data, struct wl_output *wl_output,
+ int32_t x, int32_t y, int32_t phys_width, int32_t phys_height,
+ int32_t subpixel, const char *make, const char *model,
+ int32_t transform) {
+ struct wayland_output *output = data;
+ output->make = av_strdup(make);
+ output->model = av_strdup(model);
+}
+
+static void output_handle_mode(void *data, struct wl_output *wl_output,
+ uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
+ if (flags & WL_OUTPUT_MODE_CURRENT) {
+ struct wayland_output *output = data;
+ output->width = width;
+ output->height = height;
+ output->framerate = (AVRational){ refresh, 1000 };
+ }
+}
+
+static void output_handle_done(void* data, struct wl_output *wl_output) {
+ /* Nothing to do */
+}
+
+static void output_handle_scale(void* data, struct wl_output *wl_output,
+ int32_t factor) {
+ /* Nothing to do */
+}
+
+static const struct wl_output_listener output_listener = {
+ .geometry = output_handle_geometry,
+ .mode = output_handle_mode,
+ .done = output_handle_done,
+ .scale = output_handle_scale,
+};
+
+static void registry_handle_add(void *data, struct wl_registry *reg,
+ uint32_t id, const char *interface, uint32_t ver) {
+ struct capture_context *ctx = data;
+
+ if (!strcmp(interface, wl_output_interface.name)) {
+ struct wayland_output *output = av_mallocz(sizeof(*output));
+
+ output->id = id;
+ output->output = wl_registry_bind(reg, id, &wl_output_interface, 1);
+
+ wl_output_add_listener(output->output, &output_listener, output);
+ wl_list_insert(&ctx->output_list, &output->link);
+ }
+
+ if (!strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name)) {
+ ctx->export_manager = wl_registry_bind(reg, id,
+ &zwlr_export_dmabuf_manager_v1_interface, 1);
+ }
+}
+
+static void remove_output(struct wayland_output *out) {
+ wl_list_remove(&out->link);
+ av_free(out->make);
+ av_free(out->model);
+ av_free(out);
+}
+
+static struct wayland_output *find_output(struct capture_context *ctx,
+ struct wl_output *out, uint32_t id) {
+ struct wayland_output *output, *tmp;
+ wl_list_for_each_safe(output, tmp, &ctx->output_list, link) {
+ if ((output->output == out) || (output->id == id)) {
+ return output;
+ }
+ }
+ return NULL;
+}
+
+static void registry_handle_remove(void *data, struct wl_registry *reg,
+ uint32_t id) {
+ remove_output(find_output((struct capture_context *)data, NULL, id));
+}
+
+static const struct wl_registry_listener registry_listener = {
+ .global = registry_handle_add,
+ .global_remove = registry_handle_remove,
+};
+
+static void frame_free(void *opaque, uint8_t *data) {
+ AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)data;
+
+ for (int i = 0; i < desc->nb_objects; ++i) {
+ close(desc->objects[i].fd);
+ }
+
+ zwlr_export_dmabuf_frame_v1_destroy(opaque);
+
+ av_free(data);
+}
+
+static void frame_start(void *data, struct zwlr_export_dmabuf_frame_v1 *frame,
+ uint32_t width, uint32_t height, uint32_t offset_x, uint32_t offset_y,
+ uint32_t buffer_flags, uint32_t flags, uint32_t format,
+ uint32_t mod_high, uint32_t mod_low, uint32_t num_objects) {
+ struct capture_context *ctx = data;
+ int err = 0;
+
+ /* Allocate DRM specific struct */
+ AVDRMFrameDescriptor *desc = av_mallocz(sizeof(*desc));
+ if (!desc) {
+ err = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ desc->nb_objects = num_objects;
+ desc->objects[0].format_modifier = ((uint64_t)mod_high << 32) | mod_low;
+
+ desc->nb_layers = 1;
+ desc->layers[0].format = format;
+
+ /* Allocate a frame */
+ AVFrame *f = av_frame_alloc();
+ if (!f) {
+ err = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ /* Set base frame properties */
+ ctx->current_frame = f;
+ f->width = width;
+ f->height = height;
+ f->format = AV_PIX_FMT_DRM_PRIME;
+
+ /* Set the frame data to the DRM specific struct */
+ f->buf[0] = av_buffer_create((uint8_t*)desc, sizeof(*desc),
+ &frame_free, frame, 0);
+ if (!f->buf[0]) {
+ err = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ f->data[0] = (uint8_t*)desc;
+
+ return;
+
+fail:
+ ctx->err = err;
+ frame_free(frame, (uint8_t *)desc);
+}
+
+static void frame_object(void *data, struct zwlr_export_dmabuf_frame_v1 *frame,
+ uint32_t index, int32_t fd, uint32_t size, uint32_t offset,
+ uint32_t stride, uint32_t plane_index) {
+ struct capture_context *ctx = data;
+ AVFrame *f = ctx->current_frame;
+ AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)f->data[0];
+
+ desc->objects[index].fd = fd;
+ desc->objects[index].size = size;
+
+ desc->layers[0].planes[plane_index].object_index = index;
+ desc->layers[0].planes[plane_index].offset = offset;
+ desc->layers[0].planes[plane_index].pitch = stride;
+}
+
+static enum AVPixelFormat drm_fmt_to_pixfmt(uint32_t fmt) {
+ switch (fmt) {
+ case DRM_FORMAT_NV12: return AV_PIX_FMT_NV12;
+ case DRM_FORMAT_ARGB8888: return AV_PIX_FMT_BGRA;
+ case DRM_FORMAT_XRGB8888: return AV_PIX_FMT_BGR0;
+ case DRM_FORMAT_ABGR8888: return AV_PIX_FMT_RGBA;
+ case DRM_FORMAT_XBGR8888: return AV_PIX_FMT_RGB0;
+ case DRM_FORMAT_RGBA8888: return AV_PIX_FMT_ABGR;
+ case DRM_FORMAT_RGBX8888: return AV_PIX_FMT_0BGR;
+ case DRM_FORMAT_BGRA8888: return AV_PIX_FMT_ARGB;
+ case DRM_FORMAT_BGRX8888: return AV_PIX_FMT_0RGB;
+ default: return AV_PIX_FMT_NONE;
+ };
+}
+
+static int attach_drm_frames_ref(struct capture_context *ctx, AVFrame *f,
+ enum AVPixelFormat sw_format) {
+ int err = 0;
+ AVHWFramesContext *hwfc;
+
+ if (ctx->drm_frames_ref) {
+ hwfc = (AVHWFramesContext*)ctx->drm_frames_ref->data;
+ if (hwfc->width == f->width && hwfc->height == f->height &&
+ hwfc->sw_format == sw_format) {
+ goto attach;
+ }
+ av_buffer_unref(&ctx->drm_frames_ref);
+ }
+
+ ctx->drm_frames_ref = av_hwframe_ctx_alloc(ctx->drm_device_ref);
+ if (!ctx->drm_frames_ref) {
+ err = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ hwfc = (AVHWFramesContext*)ctx->drm_frames_ref->data;
+
+ hwfc->format = f->format;
+ hwfc->sw_format = sw_format;
+ hwfc->width = f->width;
+ hwfc->height = f->height;
+
+ err = av_hwframe_ctx_init(ctx->drm_frames_ref);
+ if (err) {
+ av_log(ctx, AV_LOG_ERROR, "AVHWFramesContext init failed: %s!\n",
+ av_err2str(err));
+ goto fail;
+ }
+
+attach:
+ /* Set frame hardware context referencce */
+ f->hw_frames_ctx = av_buffer_ref(ctx->drm_frames_ref);
+ if (!f->hw_frames_ctx) {
+ err = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ av_buffer_unref(&ctx->drm_frames_ref);
+ return err;
+}
+
+static void register_cb(struct capture_context *ctx);
+
+static void frame_ready(void *data, struct zwlr_export_dmabuf_frame_v1 *frame,
+ uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) {
+ struct capture_context *ctx = data;
+ AVFrame *f = ctx->current_frame;
+ AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)f->data[0];
+ enum AVPixelFormat pix_fmt = drm_fmt_to_pixfmt(desc->layers[0].format);
+ int err = 0;
+
+ /* Timestamp, nanoseconds timebase */
+ f->pts = ((((uint64_t)tv_sec_hi) << 32) | tv_sec_lo) * 1000000000 + tv_nsec;
+
+ if (!ctx->start_pts) {
+ ctx->start_pts = f->pts;
+ }
+
+ f->pts = av_rescale_q(f->pts - ctx->start_pts, (AVRational){ 1, 1000000000 },
+ ctx->avctx->time_base);
+
+ /* Attach the hardware frame context to the frame */
+ err = attach_drm_frames_ref(ctx, f, pix_fmt);
+ if (err) {
+ goto end;
+ }
+
+ /* TODO: support multiplane stuff */
+ desc->layers[0].nb_planes = av_pix_fmt_count_planes(pix_fmt);
+
+ AVFrame *mapped_frame = av_frame_alloc();
+ if (!mapped_frame) {
+ err = AVERROR(ENOMEM);
+ goto end;
+ }
+
+ AVHWFramesContext *mapped_hwfc;
+ mapped_hwfc = (AVHWFramesContext *)ctx->mapped_frames_ref->data;
+ mapped_frame->format = mapped_hwfc->format;
+ mapped_frame->pts = f->pts;
+
+ /* Set frame hardware context referencce */
+ mapped_frame->hw_frames_ctx = av_buffer_ref(ctx->mapped_frames_ref);
+ if (!mapped_frame->hw_frames_ctx) {
+ err = AVERROR(ENOMEM);
+ goto end;
+ }
+
+ err = av_hwframe_map(mapped_frame, f, 0);
+ if (err) {
+ av_log(ctx, AV_LOG_ERROR, "Error mapping: %s!\n", av_err2str(err));
+ goto end;
+ }
+
+ if (push_to_fifo(&ctx->vid_frames, mapped_frame)) {
+ av_log(ctx, AV_LOG_WARNING, "Dropped frame!\n");
+ }
+
+ if (!ctx->quit && !ctx->err) {
+ register_cb(ctx);
+ }
+
+end:
+ ctx->err = err;
+ av_frame_free(&ctx->current_frame);
+}
+
+static void frame_cancel(void *data, struct zwlr_export_dmabuf_frame_v1 *frame,
+ uint32_t reason) {
+ struct capture_context *ctx = data;
+ av_log(ctx, AV_LOG_WARNING, "Frame cancelled!\n");
+ av_frame_free(&ctx->current_frame);
+ if (reason == ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT) {
+ av_log(ctx, AV_LOG_ERROR, "Permanent failure, exiting\n");
+ ctx->err = true;
+ } else {
+ register_cb(ctx);
+ }
+}
+
+static const struct zwlr_export_dmabuf_frame_v1_listener frame_listener = {
+ .frame = frame_start,
+ .object = frame_object,
+ .ready = frame_ready,
+ .cancel = frame_cancel,
+};
+
+static void register_cb(struct capture_context *ctx) {
+ ctx->frame_callback = zwlr_export_dmabuf_manager_v1_capture_output(
+ ctx->export_manager, ctx->with_cursor, ctx->target_output);
+
+ zwlr_export_dmabuf_frame_v1_add_listener(ctx->frame_callback,
+ &frame_listener, ctx);
+}
+
+void *vid_encode_thread(void *arg) {
+ int err = 0;
+ struct capture_context *ctx = arg;
+
+ do {
+ AVFrame *f = NULL;
+ if (get_fifo_size(&ctx->vid_frames) || !ctx->quit) {
+ f = pop_from_fifo(&ctx->vid_frames);
+ }
+
+ if (ctx->is_software_encoder && f) {
+ AVFrame *soft_frame = av_frame_alloc();
+ av_hwframe_transfer_data(soft_frame, f, 0);
+ soft_frame->pts = f->pts;
+ av_frame_free(&f);
+ f = soft_frame;
+ }
+
+ err = avcodec_send_frame(ctx->avctx, f);
+
+ av_frame_free(&f);
+
+ if (err) {
+ av_log(ctx, AV_LOG_ERROR, "Error encoding: %s!\n", av_err2str(err));
+ goto end;
+ }
+
+ while (1) {
+ AVPacket pkt;
+ av_init_packet(&pkt);
+
+ int ret = avcodec_receive_packet(ctx->avctx, &pkt);
+ if (ret == AVERROR(EAGAIN)) {
+ break;
+ } else if (ret == AVERROR_EOF) {
+ av_log(ctx, AV_LOG_INFO, "Encoder flushed!\n");
+ goto end;
+ } else if (ret) {
+ av_log(ctx, AV_LOG_ERROR, "Error encoding: %s!\n",
+ av_err2str(ret));
+ err = ret;
+ goto end;
+ }
+
+ pkt.stream_index = 0;
+ err = av_interleaved_write_frame(ctx->avf, &pkt);
+
+ av_packet_unref(&pkt);
+
+ if (err) {
+ av_log(ctx, AV_LOG_ERROR, "Writing packet fail: %s!\n",
+ av_err2str(err));
+ goto end;
+ }
+ };
+
+ av_log(ctx, AV_LOG_INFO, "Encoded frame %i (%i in queue)\n",
+ ctx->avctx->frame_number, get_fifo_size(&ctx->vid_frames));
+
+ } while (!ctx->err);
+
+end:
+ if (!ctx->err) {
+ ctx->err = err;
+ }
+ return NULL;
+}
+
+static int init_lavu_hwcontext(struct capture_context *ctx) {
+ /* DRM hwcontext */
+ ctx->drm_device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_DRM);
+ if (!ctx->drm_device_ref)
+ return AVERROR(ENOMEM);
+
+ AVHWDeviceContext *ref_data = (AVHWDeviceContext*)ctx->drm_device_ref->data;
+ AVDRMDeviceContext *hwctx = ref_data->hwctx;
+
+ /* We don't need a device (we don't even know it and can't open it) */
+ hwctx->fd = -1;
+
+ av_hwdevice_ctx_init(ctx->drm_device_ref);
+
+ /* Mapped hwcontext */
+ int err = av_hwdevice_ctx_create(&ctx->mapped_device_ref,
+ ctx->hw_device_type, ctx->hardware_device, NULL, 0);
+ if (err < 0) {
+ av_log(ctx, AV_LOG_ERROR, "Failed to create a hardware device: %s\n",
+ av_err2str(err));
+ return err;
+ }
+
+ return 0;
+}
+
+static int set_hwframe_ctx(struct capture_context *ctx,
+ AVBufferRef *hw_device_ctx) {
+ AVHWFramesContext *frames_ctx = NULL;
+ int err = 0;
+
+ if (!(ctx->mapped_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx))) {
+ return AVERROR(ENOMEM);
+ }
+
+ AVHWFramesConstraints *cst =
+ av_hwdevice_get_hwframe_constraints(ctx->mapped_device_ref, NULL);
+ if (!cst) {
+ av_log(ctx, AV_LOG_ERROR, "Failed to get hw device constraints!\n");
+ av_buffer_unref(&ctx->mapped_frames_ref);
+ return AVERROR(ENOMEM);
+ }
+
+ frames_ctx = (AVHWFramesContext *)(ctx->mapped_frames_ref->data);
+ frames_ctx->format = cst->valid_hw_formats[0];
+ frames_ctx->sw_format = ctx->avctx->pix_fmt;
+ frames_ctx->width = ctx->avctx->width;
+ frames_ctx->height = ctx->avctx->height;
+
+ av_hwframe_constraints_free(&cst);
+
+ if ((err = av_hwframe_ctx_init(ctx->mapped_frames_ref))) {
+ av_log(ctx, AV_LOG_ERROR, "Failed to initialize hw frame context: %s!\n",
+ av_err2str(err));
+ av_buffer_unref(&ctx->mapped_frames_ref);
+ return err;
+ }
+
+ if (!ctx->is_software_encoder) {
+ ctx->avctx->pix_fmt = frames_ctx->format;
+ ctx->avctx->hw_frames_ctx = av_buffer_ref(ctx->mapped_frames_ref);
+ if (!ctx->avctx->hw_frames_ctx) {
+ av_buffer_unref(&ctx->mapped_frames_ref);
+ err = AVERROR(ENOMEM);
+ }
+ }
+
+ return err;
+}
+
+static int init_encoding(struct capture_context *ctx) {
+ int err;
+
+ /* lavf init */
+ err = avformat_alloc_output_context2(&ctx->avf, NULL,
+ NULL, ctx->out_filename);
+ if (err) {
+ av_log(ctx, AV_LOG_ERROR, "Unable to init lavf context!\n");
+ return err;
+ }
+
+ AVStream *st = avformat_new_stream(ctx->avf, NULL);
+ if (!st) {
+ av_log(ctx, AV_LOG_ERROR, "Unable to alloc stream!\n");
+ return 1;
+ }
+
+ /* Find encoder */
+ AVCodec *out_codec = avcodec_find_encoder_by_name(ctx->encoder_name);
+ if (!out_codec) {
+ av_log(ctx, AV_LOG_ERROR, "Codec not found (not compiled in lavc?)!\n");
+ return AVERROR(EINVAL);
+ }
+ ctx->avf->oformat->video_codec = out_codec->id;
+ ctx->is_software_encoder = !(out_codec->capabilities & AV_CODEC_CAP_HARDWARE);
+
+ ctx->avctx = avcodec_alloc_context3(out_codec);
+ if (!ctx->avctx)
+ return 1;
+
+ ctx->avctx->opaque = ctx;
+ ctx->avctx->bit_rate = (int)ctx->out_bitrate*1000000.0f;
+ ctx->avctx->pix_fmt = ctx->software_format;
+ ctx->avctx->time_base = (AVRational){ 1, 1000 };
+ ctx->avctx->compression_level = 7;
+ ctx->avctx->width = find_output(ctx, ctx->target_output, 0)->width;
+ ctx->avctx->height = find_output(ctx, ctx->target_output, 0)->height;
+
+ if (ctx->avf->oformat->flags & AVFMT_GLOBALHEADER) {
+ ctx->avctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
+ }
+
+ st->id = 0;
+ st->time_base = ctx->avctx->time_base;
+ st->avg_frame_rate = find_output(ctx, ctx->target_output, 0)->framerate;
+
+ /* Init hw frames context */
+ err = set_hwframe_ctx(ctx, ctx->mapped_device_ref);
+ if (err) {
+ return err;
+ }
+
+ err = avcodec_open2(ctx->avctx, out_codec, &ctx->encoder_opts);
+ if (err) {
+ av_log(ctx, AV_LOG_ERROR, "Cannot open encoder: %s!\n",
+ av_err2str(err));
+ return err;
+ }
+
+ if (avcodec_parameters_from_context(st->codecpar, ctx->avctx) < 0) {
+ av_log(ctx, AV_LOG_ERROR, "Couldn't copy codec params: %s!\n",
+ av_err2str(err));
+ return err;
+ }
+
+ /* Debug print */
+ av_dump_format(ctx->avf, 0, ctx->out_filename, 1);
+
+ /* Open for writing */
+ err = avio_open(&ctx->avf->pb, ctx->out_filename, AVIO_FLAG_WRITE);
+ if (err) {
+ av_log(ctx, AV_LOG_ERROR, "Couldn't open %s: %s!\n", ctx->out_filename,
+ av_err2str(err));
+ return err;
+ }
+
+ err = avformat_write_header(ctx->avf, NULL);
+ if (err) {
+ av_log(ctx, AV_LOG_ERROR, "Couldn't write header: %s!\n", av_err2str(err));
+ return err;
+ }
+
+ return err;
+}
+
+struct capture_context *q_ctx = NULL;
+
+void on_quit_signal(int signo) {
+ printf("\r");
+ av_log(q_ctx, AV_LOG_WARNING, "Quitting!\n");
+ q_ctx->quit = true;
+}
+
+static int main_loop(struct capture_context *ctx) {
+ int err;
+
+ q_ctx = ctx;
+
+ if (signal(SIGINT, on_quit_signal) == SIG_ERR) {
+ av_log(ctx, AV_LOG_ERROR, "Unable to install signal handler!\n");
+ return AVERROR(EINVAL);
+ }
+
+ err = init_lavu_hwcontext(ctx);
+ if (err) {
+ return err;
+ }
+
+ err = init_encoding(ctx);
+ if (err) {
+ return err;
+ }
+
+ /* Start video encoding thread */
+ err = init_fifo(&ctx->vid_frames, 16);
+ if (err) {
+ return err;
+ }
+ pthread_create(&ctx->vid_thread, NULL, vid_encode_thread, ctx);
+
+ /* Start the frame callback */
+ register_cb(ctx);
+
+ /* Run capture */
+ while (wl_display_dispatch(ctx->display) != -1 && !ctx->err && !ctx->quit);
+
+ /* Join with encoder thread */
+ pthread_join(ctx->vid_thread, NULL);
+
+ err = av_write_trailer(ctx->avf);
+ if (err) {
+ av_log(ctx, AV_LOG_ERROR, "Error writing trailer: %s!\n",
+ av_err2str(err));
+ return err;
+ }
+
+ av_log(ctx, AV_LOG_INFO, "Wrote trailer!\n");
+
+ return ctx->err;
+}
+
+static int init(struct capture_context *ctx) {
+ ctx->display = wl_display_connect(NULL);
+ if (!ctx->display) {
+ av_log(ctx, AV_LOG_ERROR, "Failed to connect to display!\n");
+ return AVERROR(EINVAL);
+ }
+
+ wl_list_init(&ctx->output_list);
+
+ ctx->registry = wl_display_get_registry(ctx->display);
+ wl_registry_add_listener(ctx->registry, &registry_listener, ctx);
+
+ wl_display_roundtrip(ctx->display);
+ wl_display_dispatch(ctx->display);
+
+ if (!ctx->export_manager) {
+ av_log(ctx, AV_LOG_ERROR, "Compositor doesn't support %s!\n",
+ zwlr_export_dmabuf_manager_v1_interface.name);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void uninit(struct capture_context *ctx);
+
+int main(int argc, char *argv[]) {
+ int err;
+ struct capture_context ctx = { 0 };
+ ctx.class = &((AVClass) {
+ .class_name = "dmabuf-capture",
+ .item_name = av_default_item_name,
+ .version = LIBAVUTIL_VERSION_INT,
+ });
+
+ err = init(&ctx);
+ if (err) {
+ goto end;
+ }
+
+ struct wayland_output *o, *tmp_o;
+ wl_list_for_each_reverse_safe(o, tmp_o, &ctx.output_list, link) {
+ printf("Capturable output: %s Model: %s: ID: %i\n",
+ o->make, o->model, o->id);
+ }
+
+ if (argc != 8) {
+ printf("Invalid number of arguments! Usage and example:\n"
+ "./dmabuf-capture <source id> <hardware device type> <device> "
+ "<encoder name> <pixel format> <bitrate in Mbps> <file path>\n"
+ "./dmabuf-capture 0 vaapi /dev/dri/renderD129 libx264 nv12 12 "
+ "dmabuf_recording_01.mkv\n");
+ return 1;
+ }
+
+ const int o_id = strtol(argv[1], NULL, 10);
+ o = find_output(&ctx, NULL, o_id);
+ if (!o) {
+ printf("Unable to find output with ID %i!\n", o_id);
+ return 1;
+ }
+
+ ctx.target_output = o->output;
+ ctx.with_cursor = true;
+ ctx.hw_device_type = av_hwdevice_find_type_by_name(argv[2]);
+ ctx.hardware_device = argv[3];
+
+ ctx.encoder_name = argv[4];
+ ctx.software_format = av_get_pix_fmt(argv[5]);
+ ctx.out_bitrate = strtof(argv[6], NULL);
+ ctx.out_filename = argv[7];
+
+ av_dict_set(&ctx.encoder_opts, "preset", "veryfast", 0);
+
+ err = main_loop(&ctx);
+ if (err) {
+ goto end;
+ }
+
+end:
+ uninit(&ctx);
+ return err;
+}
+
+static void uninit(struct capture_context *ctx) {
+ struct wayland_output *output, *tmp_o;
+ wl_list_for_each_safe(output, tmp_o, &ctx->output_list, link) {
+ remove_output(output);
+ }
+
+ if (ctx->export_manager) {
+ zwlr_export_dmabuf_manager_v1_destroy(ctx->export_manager);
+ }
+
+ free_fifo(&ctx->vid_frames);
+
+ av_buffer_unref(&ctx->drm_frames_ref);
+ av_buffer_unref(&ctx->drm_device_ref);
+ av_buffer_unref(&ctx->mapped_frames_ref);
+ av_buffer_unref(&ctx->mapped_device_ref);
+
+ av_dict_free(&ctx->encoder_opts);
+
+ avcodec_close(ctx->avctx);
+ if (ctx->avf) {
+ avio_closep(&ctx->avf->pb);
+ }
+ avformat_free_context(ctx->avf);
+}
diff --git a/examples/foreign-toplevel.c b/examples/foreign-toplevel.c
new file mode 100644
index 00000000..40ddbb09
--- /dev/null
+++ b/examples/foreign-toplevel.c
@@ -0,0 +1,358 @@
+#define _POSIX_C_SOURCE 200809L
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-client.h>
+#include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h"
+
+/**
+ * Usage:
+ * 1. foreign-toplevel
+ * Prints a list of opened toplevels
+ * 2. foreign-toplevel -f <id>
+ * Focus the toplevel with the given id
+ * 3. foreign-toplevel -a <id>
+ * Maximize the toplevel with the given id
+ * 4. foreign-toplevel -u <id>
+ * Unmaximize the toplevel with the given id
+ * 5. foreign-toplevel -i <id>
+ * Minimize the toplevel with the given id
+ * 6. foreign-toplevel -r <id>
+ * Restore(unminimize) the toplevel with the given id
+ * 7. foreign-toplevel -c <id>
+ * Close the toplevel with the given id
+ * 8. foreign-toplevel -m
+ * Continuously print changes to the list of opened toplevels.
+ * Can be used together with some of the previous options.
+ */
+
+enum toplevel_state_field {
+ TOPLEVEL_STATE_MAXIMIZED = 1,
+ TOPLEVEL_STATE_MINIMIZED = 2,
+ TOPLEVEL_STATE_ACTIVATED = 4,
+ TOPLEVEL_STATE_INVALID = 8,
+};
+
+struct toplevel_state {
+ char *title;
+ char *app_id;
+
+ uint32_t state;
+};
+
+static void copy_state(struct toplevel_state *current,
+ struct toplevel_state *pending) {
+ if (current->title && pending->title) {
+ free(current->title);
+ }
+ if (current->app_id && pending->app_id) {
+ free(current->app_id);
+ }
+
+ if (pending->title) {
+ current->title = pending->title;
+ pending->title = NULL;
+ }
+ if (pending->app_id) {
+ current->app_id = pending->app_id;
+ pending->app_id = NULL;
+ }
+
+ if (!(pending->state & TOPLEVEL_STATE_INVALID)) {
+ current->state = pending->state;
+ }
+
+ pending->state = TOPLEVEL_STATE_INVALID;
+}
+
+static uint32_t global_id = 0;
+struct toplevel_v1 {
+ struct wl_list link;
+ struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel;
+
+ uint32_t id;
+ struct toplevel_state current, pending;
+};
+
+static void print_toplevel(struct toplevel_v1 *toplevel, bool print_endl) {
+ printf("-> %d. title=%s app_id=%s", toplevel->id,
+ toplevel->current.title ?: "(nil)",
+ toplevel->current.app_id ?: "(nil)");
+
+ if (print_endl) {
+ printf("\n");
+ }
+}
+
+static void print_toplevel_state(struct toplevel_v1 *toplevel, bool print_endl) {
+ if (toplevel->current.state & TOPLEVEL_STATE_MAXIMIZED) {
+ printf(" maximized");
+ } else {
+ printf(" unmaximized");
+ }
+ if (toplevel->current.state & TOPLEVEL_STATE_MINIMIZED) {
+ printf(" minimized");
+ } else {
+ printf(" unminimized");
+ }
+ if (toplevel->current.state & TOPLEVEL_STATE_ACTIVATED) {
+ printf(" active");
+ } else {
+ printf(" inactive");
+ }
+
+ if (print_endl) {
+ printf("\n");
+ }
+}
+
+static void toplevel_handle_title(void *data,
+ struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel,
+ const char *title) {
+ struct toplevel_v1 *toplevel = data;
+ free(toplevel->pending.title);
+ toplevel->pending.title = strdup(title);
+}
+
+static void toplevel_handle_app_id(void *data,
+ struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel,
+ const char *app_id) {
+ struct toplevel_v1 *toplevel = data;
+ free(toplevel->pending.app_id);
+ toplevel->pending.app_id = strdup(app_id);
+}
+
+static void toplevel_handle_output_enter(void *data,
+ struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel,
+ struct wl_output *output) {
+ struct toplevel_v1 *toplevel = data;
+ print_toplevel(toplevel, false);
+ printf(" enter output %u\n",
+ (uint32_t)(size_t)wl_output_get_user_data(output));
+}
+
+static void toplevel_handle_output_leave(void *data,
+ struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel,
+ struct wl_output *output) {
+ struct toplevel_v1 *toplevel = data;
+ print_toplevel(toplevel, false);
+ printf(" leave output %u\n",
+ (uint32_t)(size_t)wl_output_get_user_data(output));
+}
+
+static uint32_t array_to_state(struct wl_array *array) {
+ uint32_t state = 0;
+ uint32_t *entry;
+ wl_array_for_each(entry, array) {
+ if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED)
+ state |= TOPLEVEL_STATE_MAXIMIZED;
+ if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED)
+ state |= TOPLEVEL_STATE_MINIMIZED;
+ if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED)
+ state |= TOPLEVEL_STATE_ACTIVATED;
+ }
+
+ return state;
+}
+
+static void toplevel_handle_state(void *data,
+ struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel,
+ struct wl_array *state) {
+ struct toplevel_v1 *toplevel = data;
+ toplevel->pending.state = array_to_state(state);
+}
+
+static void toplevel_handle_done(void *data,
+ struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel) {
+ struct toplevel_v1 *toplevel = data;
+ bool state_changed = toplevel->current.state != toplevel->pending.state;
+
+ copy_state(&toplevel->current, &toplevel->pending);
+
+ print_toplevel(toplevel, !state_changed);
+ if (state_changed) {
+ print_toplevel_state(toplevel, true);
+ }
+}
+
+static void toplevel_handle_closed(void *data,
+ struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel) {
+ struct toplevel_v1 *toplevel = data;
+ print_toplevel(toplevel, false);
+ printf(" closed\n");
+
+ zwlr_foreign_toplevel_handle_v1_destroy(zwlr_toplevel);
+}
+
+static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_impl = {
+ .title = toplevel_handle_title,
+ .app_id = toplevel_handle_app_id,
+ .output_enter = toplevel_handle_output_enter,
+ .output_leave = toplevel_handle_output_leave,
+ .state = toplevel_handle_state,
+ .done = toplevel_handle_done,
+ .closed = toplevel_handle_closed,
+};
+
+static struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager = NULL;
+static struct wl_list toplevel_list;
+
+static void toplevel_manager_handle_toplevel(void *data,
+ struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager,
+ struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel) {
+ struct toplevel_v1 *toplevel = calloc(1, sizeof(struct toplevel_v1));
+ if (!toplevel) {
+ fprintf(stderr, "Failed to allocate memory for toplevel\n");
+ return;
+ }
+
+ toplevel->id = global_id++;
+ toplevel->zwlr_toplevel = zwlr_toplevel;
+ wl_list_insert(&toplevel_list, &toplevel->link);
+
+ zwlr_foreign_toplevel_handle_v1_add_listener(zwlr_toplevel, &toplevel_impl,
+ toplevel);
+}
+
+static void toplevel_manager_handle_finished(void *data,
+ struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager) {
+ zwlr_foreign_toplevel_manager_v1_destroy(toplevel_manager);
+}
+
+static const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_impl = {
+ .toplevel = toplevel_manager_handle_toplevel,
+ .finished = toplevel_manager_handle_finished,
+};
+
+struct wl_seat *seat = NULL;
+static void handle_global(void *data, struct wl_registry *registry,
+ uint32_t name, const char *interface, uint32_t version) {
+ if (strcmp(interface, wl_output_interface.name) == 0) {
+ struct wl_output *output = wl_registry_bind(registry, name,
+ &wl_output_interface, version);
+ wl_output_set_user_data(output, (void*)(size_t)name); // assign some ID to the output
+ } else if (strcmp(interface,
+ zwlr_foreign_toplevel_manager_v1_interface.name) == 0) {
+ toplevel_manager = wl_registry_bind(registry, name,
+ &zwlr_foreign_toplevel_manager_v1_interface, 1);
+
+ wl_list_init(&toplevel_list);
+ zwlr_foreign_toplevel_manager_v1_add_listener(toplevel_manager,
+ &toplevel_manager_impl, NULL);
+ } else if (strcmp(interface, wl_seat_interface.name) == 0 && seat == NULL) {
+ seat = wl_registry_bind(registry, name, &wl_seat_interface, version);
+ }
+}
+
+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,
+};
+
+/* return NULL when id == -1
+ * exit if the given ID cannot be found in the list of toplevels */
+static struct toplevel_v1 *toplevel_by_id_or_bail(int32_t id) {
+ if (id == -1) {
+ return NULL;
+ }
+
+ struct toplevel_v1 *toplevel;
+ wl_list_for_each(toplevel, &toplevel_list, link) {
+ if (toplevel->id == (uint32_t)id) {
+ return toplevel;
+ }
+ }
+
+ fprintf(stderr, "No toplevel with the given id: %d\n", id);
+ exit(EXIT_FAILURE);
+}
+
+int main(int argc, char **argv) {
+ int focus_id = -1, close_id = -1;
+ int maximize_id = -1, unmaximize_id = -1;
+ int minimize_id = -1, restore_id = -1;
+ int one_shot = 1;
+ int c;
+
+ // TODO maybe print usage with -h?
+ while ((c = getopt(argc, argv, "f:a:u:i:r:c:m")) != -1) {
+ switch (c) {
+ case 'f':
+ focus_id = atoi(optarg);
+ break;
+ case 'a':
+ maximize_id = atoi(optarg);
+ break;
+ case 'u':
+ unmaximize_id = atoi(optarg);
+ break;
+ case 'i':
+ minimize_id = atoi(optarg);
+ break;
+ case 'r':
+ restore_id = atoi(optarg);
+ break;
+ case 'c':
+ close_id = atoi(optarg);
+ break;
+ case 'm':
+ one_shot = 0;
+ break;
+ }
+ }
+
+ struct wl_display *display = wl_display_connect(NULL);
+ if (display == NULL) {
+ fprintf(stderr, "Failed to create display\n");
+ return EXIT_FAILURE;
+ }
+
+ struct wl_registry *registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, &registry_listener, NULL);
+ wl_display_dispatch(display);
+ wl_display_roundtrip(display);
+
+ if (toplevel_manager == NULL) {
+ fprintf(stderr, "wlr-foreign-toplevel not available\n");
+ return EXIT_FAILURE;
+ }
+ wl_display_roundtrip(display); // load list of toplevels
+ wl_display_roundtrip(display); // load toplevel details
+
+ struct toplevel_v1 *toplevel;
+ if ((toplevel = toplevel_by_id_or_bail(focus_id))) {
+ zwlr_foreign_toplevel_handle_v1_activate(toplevel->zwlr_toplevel, seat);
+ }
+ if ((toplevel = toplevel_by_id_or_bail(maximize_id))) {
+ zwlr_foreign_toplevel_handle_v1_set_maximized(toplevel->zwlr_toplevel);
+ }
+ if ((toplevel = toplevel_by_id_or_bail(unmaximize_id))) {
+ zwlr_foreign_toplevel_handle_v1_unset_maximized(toplevel->zwlr_toplevel);
+ }
+ if ((toplevel = toplevel_by_id_or_bail(minimize_id))) {
+ zwlr_foreign_toplevel_handle_v1_set_minimized(toplevel->zwlr_toplevel);
+ }
+ if ((toplevel = toplevel_by_id_or_bail(restore_id))) {
+ zwlr_foreign_toplevel_handle_v1_unset_minimized(toplevel->zwlr_toplevel);
+ }
+ if ((toplevel = toplevel_by_id_or_bail(close_id))) {
+ zwlr_foreign_toplevel_handle_v1_close(toplevel->zwlr_toplevel);
+ }
+
+ wl_display_flush(display);
+
+ if (one_shot == 0) {
+ while (wl_display_dispatch(display) != -1) {
+ // This space intentionally left blank
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/examples/gamma-control.c b/examples/gamma-control.c
new file mode 100644
index 00000000..a060b883
--- /dev/null
+++ b/examples/gamma-control.c
@@ -0,0 +1,195 @@
+#define _POSIX_C_SOURCE 200809L
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <wayland-client-protocol.h>
+#include <wayland-client.h>
+#include "wlr-gamma-control-unstable-v1-client-protocol.h"
+
+struct output {
+ struct wl_output *wl_output;
+ struct zwlr_gamma_control_v1 *gamma_control;
+ uint32_t ramp_size;
+ int table_fd;
+ uint16_t *table;
+ struct wl_list link;
+};
+
+static struct wl_list outputs;
+static struct zwlr_gamma_control_manager_v1 *gamma_control_manager = NULL;
+
+static int create_anonymous_file(off_t size) {
+ char template[] = "/tmp/wlroots-shared-XXXXXX";
+ int fd = mkstemp(template);
+ if (fd < 0) {
+ return -1;
+ }
+
+ int ret;
+ do {
+ errno = 0;
+ ret = ftruncate(fd, size);
+ } while (errno == EINTR);
+ if (ret < 0) {
+ close(fd);
+ return -1;
+ }
+
+ unlink(template);
+ return fd;
+}
+
+static int create_gamma_table(uint32_t ramp_size, uint16_t **table) {
+ size_t table_size = ramp_size * 3 * sizeof(uint16_t);
+ int fd = create_anonymous_file(table_size);
+ if (fd < 0) {
+ fprintf(stderr, "failed to create anonymous file\n");
+ return -1;
+ }
+
+ void *data =
+ mmap(NULL, table_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (data == MAP_FAILED) {
+ fprintf(stderr, "failed to mmap()\n");
+ close(fd);
+ return -1;
+ }
+
+ *table = data;
+ return fd;
+}
+
+static void gamma_control_handle_gamma_size(void *data,
+ struct zwlr_gamma_control_v1 *gamma_control, uint32_t ramp_size) {
+ struct output *output = data;
+ output->ramp_size = ramp_size;
+ output->table_fd = create_gamma_table(ramp_size, &output->table);
+ if (output->table_fd < 0) {
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void gamma_control_handle_failed(void *data,
+ struct zwlr_gamma_control_v1 *gamma_control) {
+ fprintf(stderr, "failed to set gamma table\n");
+ exit(EXIT_FAILURE);
+}
+
+static const struct zwlr_gamma_control_v1_listener gamma_control_listener = {
+ .gamma_size = gamma_control_handle_gamma_size,
+ .failed = gamma_control_handle_failed,
+};
+
+static void registry_handle_global(void *data, struct wl_registry *registry,
+ uint32_t name, const char *interface, uint32_t version) {
+ if (strcmp(interface, wl_output_interface.name) == 0) {
+ struct output *output = calloc(1, sizeof(struct output));
+ output->wl_output = wl_registry_bind(registry, name,
+ &wl_output_interface, 1);
+ wl_list_insert(&outputs, &output->link);
+ } else if (strcmp(interface,
+ zwlr_gamma_control_manager_v1_interface.name) == 0) {
+ gamma_control_manager = wl_registry_bind(registry, name,
+ &zwlr_gamma_control_manager_v1_interface, 1);
+ }
+}
+
+static void registry_handle_global_remove(void *data,
+ struct wl_registry *registry, uint32_t name) {
+ // Who cares?
+}
+
+static const struct wl_registry_listener registry_listener = {
+ .global = registry_handle_global,
+ .global_remove = registry_handle_global_remove,
+};
+
+static void fill_gamma_table(uint16_t *table, uint32_t ramp_size,
+ double contrast, double brightness, double gamma) {
+ uint16_t *r = table;
+ uint16_t *g = table + ramp_size;
+ uint16_t *b = table + 2 * ramp_size;
+ for (uint32_t i = 0; i < ramp_size; ++i) {
+ double val = (double)i / (ramp_size - 1);
+ val = contrast * pow(val, 1.0 / gamma) + (brightness - 1);
+ if (val > 1.0) {
+ val = 1.0;
+ } else if (val < 0.0) {
+ val = 0.0;
+ }
+ r[i] = g[i] = b[i] = (uint16_t)(UINT16_MAX * val);
+ }
+}
+
+static const char usage[] = "usage: gamma-control [options]\n"
+ " -h show this help message\n"
+ " -c <value> set contrast (default: 1)\n"
+ " -b <value> set brightness (default: 1)\n"
+ " -g <value> set gamma (default: 1)\n";
+
+int main(int argc, char *argv[]) {
+ wl_list_init(&outputs);
+
+ double contrast = 1, brightness = 1, gamma = 1;
+ int opt;
+ while ((opt = getopt(argc, argv, "hc:b:g:")) != -1) {
+ switch (opt) {
+ case 'c':
+ contrast = strtod(optarg, NULL);
+ break;
+ case 'b':
+ brightness = strtod(optarg, NULL);
+ break;
+ case 'g':
+ gamma = strtod(optarg, NULL);
+ break;
+ case 'h':
+ default:
+ fprintf(stderr, usage);
+ return opt == 'h' ? EXIT_SUCCESS : EXIT_FAILURE;
+ }
+ }
+
+ struct wl_display *display = wl_display_connect(NULL);
+ if (display == NULL) {
+ fprintf(stderr, "failed to create display\n");
+ return -1;
+ }
+
+ struct wl_registry *registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, &registry_listener, NULL);
+ wl_display_dispatch(display);
+ wl_display_roundtrip(display);
+
+ if (gamma_control_manager == NULL) {
+ fprintf(stderr,
+ "compositor doesn't support wlr-gamma-control-unstable-v1\n");
+ return EXIT_FAILURE;
+ }
+
+ struct output *output;
+ wl_list_for_each(output, &outputs, link) {
+ output->gamma_control = zwlr_gamma_control_manager_v1_get_gamma_control(
+ gamma_control_manager, output->wl_output);
+ zwlr_gamma_control_v1_add_listener(output->gamma_control,
+ &gamma_control_listener, output);
+ }
+ wl_display_roundtrip(display);
+
+ wl_list_for_each(output, &outputs, link) {
+ fill_gamma_table(output->table, output->ramp_size,
+ contrast, brightness, gamma);
+ zwlr_gamma_control_v1_set_gamma(output->gamma_control,
+ output->table_fd);
+ }
+
+ while (wl_display_dispatch(display) != -1) {
+ // This space is intentionnally left blank
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/examples/idle-inhibit.c b/examples/idle-inhibit.c
new file mode 100644
index 00000000..48c812e8
--- /dev/null
+++ b/examples/idle-inhibit.c
@@ -0,0 +1,233 @@
+#include <GLES2/gl2.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-client.h>
+#include <wayland-egl.h>
+#include <wlr/render/egl.h>
+#include "idle-inhibit-unstable-v1-client-protocol.h"
+#include "xdg-shell-client-protocol.h"
+
+#ifdef __linux__
+#include <linux/input-event-codes.h>
+#elif __FreeBSD__
+#include <dev/evdev/input-event-codes.h>
+#endif
+
+/**
+ * Usage: idle-inhibit
+ * Creates a xdg-toplevel using the idle-inhibit protocol.
+ * It will be solid green, when it has an idle inhibitor, and solid yellow if
+ * it does not.
+ * Left click with a pointer will toggle this state. (Touch is not supported
+ * for now).
+ */
+
+static int width = 500, height = 300;
+
+static struct wl_compositor *compositor = NULL;
+static struct wl_seat *seat = NULL;
+static struct xdg_wm_base *wm_base = NULL;
+static struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = NULL;
+static struct zwp_idle_inhibitor_v1 *idle_inhibitor = NULL;
+
+struct wlr_egl egl;
+struct wl_egl_window *egl_window;
+struct wlr_egl_surface *egl_surface;
+
+static void draw(void) {
+ eglMakeCurrent(egl.display, egl_surface, egl_surface, egl.context);
+
+ float color[] = {1.0, 1.0, 0.0, 1.0};
+ if (idle_inhibitor) {
+ color[0] = 0.0;
+ }
+
+ glViewport(0, 0, width, height);
+ glClearColor(color[0], color[1], color[2], 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ eglSwapBuffers(egl.display, egl_surface);
+}
+
+static void pointer_handle_button(void *data, struct wl_pointer *pointer, uint32_t serial,
+ uint32_t time, uint32_t button, uint32_t state_w) {
+ struct wl_surface *surface = data;
+
+ if (button == BTN_LEFT && state_w == WL_POINTER_BUTTON_STATE_PRESSED) {
+ if (idle_inhibitor) {
+ zwp_idle_inhibitor_v1_destroy(idle_inhibitor);
+ idle_inhibitor = NULL;
+ } else {
+ idle_inhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(
+ idle_inhibit_manager, surface);
+ }
+ }
+
+ draw();
+}
+
+static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer,
+ uint32_t serial, struct wl_surface *surface,
+ wl_fixed_t surface_x, wl_fixed_t surface_y) {
+ // This space intentionally left blank
+}
+
+static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer,
+ uint32_t serial, struct wl_surface *surface) {
+ // This space intentionally left blank
+}
+
+static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer,
+ uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
+ // This space intentionally left blank
+}
+
+static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer,
+ uint32_t time, uint32_t axis, wl_fixed_t value) {
+ // This space intentionally left blank
+}
+
+static void pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) {
+ // This space intentionally left blank
+}
+
+static void pointer_handle_axis_source(void *data,
+ struct wl_pointer *wl_pointer, uint32_t axis_source) {
+ // This space intentionally left blank
+}
+
+static void pointer_handle_axis_stop(void *data,
+ struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) {
+ // This space intentionally left blank
+}
+
+static void pointer_handle_axis_discrete(void *data,
+ struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) {
+ // This space intentionally left blank
+}
+
+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,
+};
+
+static void xdg_surface_handle_configure(void *data,
+ struct xdg_surface *xdg_surface, uint32_t serial) {
+ xdg_surface_ack_configure(xdg_surface, serial);
+ wl_egl_window_resize(egl_window, width, height, 0, 0);
+ draw();
+}
+
+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) {
+ width = w;
+ height = h;
+}
+
+static void xdg_toplevel_handle_close(void *data,
+ struct xdg_toplevel *xdg_toplevel) {
+ exit(EXIT_SUCCESS);
+}
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+ .configure = xdg_toplevel_handle_configure,
+ .close = xdg_toplevel_handle_close,
+};
+
+static void handle_global(void *data, struct wl_registry *registry,
+ uint32_t name, const char *interface, uint32_t version) {
+ if (strcmp(interface, "wl_compositor") == 0) {
+ compositor = wl_registry_bind(registry, name,
+ &wl_compositor_interface, 1);
+ } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
+ wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
+ } else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) {
+ idle_inhibit_manager = wl_registry_bind(registry, name,
+ &zwp_idle_inhibit_manager_v1_interface, 1);
+ } else if (strcmp(interface, wl_seat_interface.name) == 0) {
+ seat = wl_registry_bind(registry, name, &wl_seat_interface, version);
+ }
+}
+
+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,
+};
+
+int main(int argc, char **argv) {
+ struct wl_display *display = wl_display_connect(NULL);
+ if (display == NULL) {
+ fprintf(stderr, "Failed to create display\n");
+ return EXIT_FAILURE;
+ }
+
+ struct wl_registry *registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, &registry_listener, NULL);
+ wl_display_dispatch(display);
+ wl_display_roundtrip(display);
+
+ if (compositor == NULL) {
+ fprintf(stderr, "wl-compositor not available\n");
+ return EXIT_FAILURE;
+ }
+ if (wm_base == NULL) {
+ fprintf(stderr, "xdg-shell not available\n");
+ return EXIT_FAILURE;
+ }
+ if (idle_inhibit_manager == NULL) {
+ fprintf(stderr, "idle-inhibit not available\n");
+ return EXIT_FAILURE;
+ }
+
+ wlr_egl_init(&egl, EGL_PLATFORM_WAYLAND_EXT, display, NULL,
+ WL_SHM_FORMAT_ARGB8888);
+
+ struct wl_surface *surface = wl_compositor_create_surface(compositor);
+ struct xdg_surface *xdg_surface =
+ xdg_wm_base_get_xdg_surface(wm_base, surface);
+ struct xdg_toplevel *xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
+
+ idle_inhibitor =
+ zwp_idle_inhibit_manager_v1_create_inhibitor(idle_inhibit_manager,
+ surface);
+
+ struct wl_pointer *pointer = wl_seat_get_pointer(seat);
+ wl_pointer_add_listener(pointer, &pointer_listener, surface);
+
+
+ xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
+ xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL);
+
+ wl_surface_commit(surface);
+
+ egl_window = wl_egl_window_create(surface, width, height);
+ egl_surface = wlr_egl_create_surface(&egl, egl_window);
+
+ wl_display_roundtrip(display);
+
+ draw();
+
+ while (wl_display_dispatch(display) != -1) {
+ // This space intentionally left blank
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/examples/idle.c b/examples/idle.c
new file mode 100644
index 00000000..a6a4f5c0
--- /dev/null
+++ b/examples/idle.c
@@ -0,0 +1,197 @@
+#include <getopt.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wayland-client-protocol.h>
+#include <wayland-client.h>
+#include "idle-client-protocol.h"
+
+static struct org_kde_kwin_idle *idle_manager = NULL;
+static struct wl_seat *seat = NULL;
+static uint32_t timeout = 0, simulate_activity_timeout = 0, close_timeout = 0;
+static int run = 1;
+
+struct thread_args {
+ struct wl_display *display;
+ struct org_kde_kwin_idle_timeout *timer;
+};
+
+static void handle_global(void *data, struct wl_registry *registry,
+ uint32_t name, const char *interface, uint32_t version) {
+ fprintf(stdout, "interfaces found: %s\n", interface);
+ if (strcmp(interface, "org_kde_kwin_idle") == 0) {
+ idle_manager = wl_registry_bind(registry, name, &org_kde_kwin_idle_interface, 1);
+ }
+ else if (strcmp(interface, "wl_seat") == 0) {
+ seat = wl_registry_bind(registry, name, &wl_seat_interface, 1);
+ }
+}
+
+static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {
+ //TODO
+}
+
+static const struct wl_registry_listener registry_listener = {
+ .global = handle_global,
+ .global_remove = handle_global_remove,
+};
+
+static void handle_idle(void* data, struct org_kde_kwin_idle_timeout *timer) {
+ fprintf(stdout, "idle state\n");
+}
+
+static void handle_resume(void* data, struct org_kde_kwin_idle_timeout *timer) {
+ fprintf(stdout, "active state\n");
+}
+
+static const struct org_kde_kwin_idle_timeout_listener idle_timer_listener = {
+ .idle = handle_idle,
+ .resumed = handle_resume,
+};
+
+int parse_args(int argc, char *argv[]) {
+ int c;
+ while ((c = getopt(argc, argv, "c:hs:t:")) != -1) {
+ switch(c)
+ {
+ case 'c':
+ close_timeout = strtoul(optarg, NULL, 10);
+ break;
+ case 's':
+ simulate_activity_timeout = strtoul(optarg, NULL, 10);
+ break;
+ case 't':
+ timeout = strtoul(optarg, NULL, 10);
+ break;
+ case 'h':
+ case '?':
+ printf("Usage: %s [OPTIONS]\n", argv[0]);
+ printf(" -t seconds\t\t\tidle timeout in seconds\n");
+ printf(" -s seconds optional\t\tsimulate user activity after x seconds\n");
+ printf(" -c seconds optional\t\tclose program after x seconds\n");
+ printf(" -h\t\t\t\tthis help menu\n");
+ return 1;
+ default:
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void *simulate_activity(void *data) {
+ sleep(simulate_activity_timeout);
+ fprintf(stdout, "simulate user activity\n");
+ struct thread_args *arg = data;
+ org_kde_kwin_idle_timeout_simulate_user_activity(arg->timer);
+ wl_display_roundtrip(arg->display);
+ return NULL;
+}
+
+void *close_program(void *data) {
+ sleep(close_timeout);
+ struct thread_args *arg = data;
+ org_kde_kwin_idle_timeout_release(arg->timer);
+ wl_display_roundtrip(arg->display);
+ fprintf(stdout, "close program\n");
+ run = 0;
+ return NULL;
+}
+
+void *main_loop(void *data) {
+ struct wl_display *display = data;
+ while (wl_display_dispatch(display) != -1) {
+ ;
+ }
+ return NULL;
+}
+
+int main(int argc, char *argv[]) {
+ if (parse_args(argc, argv) != 0) {
+ return -1;
+ }
+ if (timeout == 0) {
+ printf("idle timeout 0 is invalid\n");
+ return -1;
+ }
+
+ struct wl_display *display = wl_display_connect(NULL);
+ if (display == NULL) {
+ fprintf(stderr, "failed to create display\n");
+ return -1;
+ }
+
+ struct wl_registry *registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, &registry_listener, NULL);
+ wl_display_dispatch(display);
+ wl_display_roundtrip(display);
+ free(registry);
+
+ if (idle_manager == NULL) {
+ fprintf(stderr, "display doesn't support idle protocol\n");
+ return -1;
+ }
+ if (seat== NULL) {
+ fprintf(stderr, "seat error\n");
+ return -1;
+ }
+ struct org_kde_kwin_idle_timeout *timer =
+ org_kde_kwin_idle_get_idle_timeout(idle_manager, seat, timeout * 1000);
+
+ if (timer == NULL) {
+ fprintf(stderr, "Could not create idle_timeout\n");
+ return -1;
+ }
+
+ pthread_t t1, t2, t3;
+ struct thread_args arg = {
+ .timer = timer,
+ .display = display,
+ };
+
+ bool create_t1 = (simulate_activity_timeout != 0) &&
+ (simulate_activity_timeout < close_timeout);
+
+ if (create_t1) {
+ if (pthread_create(&t1, NULL, &simulate_activity, (void *)&arg) != 0) {
+ return -1;
+ }
+ }
+
+ bool create_t2 = (close_timeout != 0);
+
+ if (create_t2) {
+ if (pthread_create(&t2, NULL, &close_program, (void *)&arg) != 0) {
+ if (create_t1) {
+ pthread_cancel(t1);
+ }
+ return -1;
+ }
+ }
+
+ org_kde_kwin_idle_timeout_add_listener(timer, &idle_timer_listener, timer);
+ fprintf(stdout, "waiting\n");
+
+ if (pthread_create(&t3, NULL, &main_loop, (void *)display) != 0) {
+ if (create_t1) {
+ pthread_cancel(t1);
+ }
+ if (create_t2) {
+ pthread_cancel(t2);
+ }
+ return -1;
+ }
+
+ if (create_t1) {
+ pthread_join(t1, NULL);
+ }
+ if (create_t2) {
+ pthread_join(t2, NULL);
+ }
+ pthread_cancel(t3);
+
+ wl_display_disconnect(display);
+ return 0;
+}
diff --git a/examples/input-inhibitor.c b/examples/input-inhibitor.c
new file mode 100644
index 00000000..25e46c73
--- /dev/null
+++ b/examples/input-inhibitor.c
@@ -0,0 +1,189 @@
+#include <GLES2/gl2.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <wayland-client.h>
+#include <wayland-egl.h>
+#include <wlr/render/egl.h>
+#include "wlr-input-inhibitor-unstable-v1-client-protocol.h"
+#include "xdg-shell-client-protocol.h"
+
+static int width = 500, height = 300;
+static int keys = 0;
+
+static struct wl_compositor *compositor = NULL;
+static struct wl_seat *seat = NULL;
+static struct xdg_wm_base *wm_base = NULL;
+static struct zwlr_input_inhibit_manager_v1 *input_inhibit_manager = NULL;
+static struct zwlr_input_inhibitor_v1 *input_inhibitor = NULL;
+
+struct wlr_egl egl;
+struct wl_egl_window *egl_window;
+struct wlr_egl_surface *egl_surface;
+
+static void render_frame(void) {
+ eglMakeCurrent(egl.display, egl_surface, egl_surface, egl.context);
+
+ glViewport(0, 0, width, height);
+ if (keys) {
+ glClearColor(1.0, 1.0, 1.0, 1.0);
+ } else {
+ glClearColor(0.8, 0.4, 1.0, 1.0);
+ }
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ eglSwapBuffers(egl.display, egl_surface);
+}
+
+static void xdg_surface_handle_configure(void *data,
+ struct xdg_surface *xdg_surface, uint32_t serial) {
+ xdg_surface_ack_configure(xdg_surface, serial);
+ wl_egl_window_resize(egl_window, width, height, 0, 0);
+ render_frame();
+}
+
+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) {
+ width = w;
+ height = h;
+}
+
+static void xdg_toplevel_handle_close(void *data,
+ struct xdg_toplevel *xdg_toplevel) {
+ exit(0);
+}
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+ .configure = xdg_toplevel_handle_configure,
+ .close = xdg_toplevel_handle_close,
+};
+
+static void wl_keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t format, int32_t fd, uint32_t size) {
+}
+static void wl_keyboard_enter(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, struct wl_surface *surface, struct wl_array *keys) {
+}
+static void wl_keyboard_leave(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, struct wl_surface *surface) {
+}
+static void wl_keyboard_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) {
+}
+static void wl_keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
+ int32_t rate, int32_t delay) {
+}
+
+static void wl_keyboard_key(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
+ if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
+ ++keys;
+ } else {
+ --keys;
+ }
+ render_frame();
+}
+
+static struct wl_keyboard_listener keyboard_listener = {
+ .keymap = wl_keyboard_keymap,
+ .enter = wl_keyboard_enter,
+ .leave = wl_keyboard_leave,
+ .key = wl_keyboard_key,
+ .modifiers = wl_keyboard_modifiers,
+ .repeat_info = wl_keyboard_repeat_info,
+};
+
+static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
+ enum wl_seat_capability caps) {
+ if ((caps & WL_SEAT_CAPABILITY_KEYBOARD)) {
+ struct wl_keyboard *keyboard = wl_seat_get_keyboard(wl_seat);
+ wl_keyboard_add_listener(keyboard, &keyboard_listener, NULL);
+ }
+}
+
+static void seat_handle_name(void *data, struct wl_seat *wl_seat,
+ const char *name) {
+ // Who cares
+}
+
+const struct wl_seat_listener seat_listener = {
+ .capabilities = seat_handle_capabilities,
+ .name = seat_handle_name,
+};
+
+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, 1);
+ } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
+ wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
+ } else if (strcmp(interface, zwlr_input_inhibit_manager_v1_interface.name) == 0) {
+ input_inhibit_manager = wl_registry_bind(registry, name,
+ &zwlr_input_inhibit_manager_v1_interface, 1);
+ } else if (strcmp(interface, wl_seat_interface.name) == 0) {
+ seat = wl_registry_bind(registry, name, &wl_seat_interface, version);
+ wl_seat_add_listener(seat, &seat_listener, seat);
+ }
+}
+
+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,
+};
+
+int main(int argc, char **argv) {
+ struct wl_display *display = wl_display_connect(NULL);
+ assert(display);
+ struct wl_registry *registry = wl_display_get_registry(display);
+ assert(registry);
+ wl_registry_add_listener(registry, &registry_listener, NULL);
+ wl_display_dispatch(display);
+ wl_display_roundtrip(display);
+ assert(compositor && seat && wm_base && input_inhibit_manager);
+
+ input_inhibitor = zwlr_input_inhibit_manager_v1_get_inhibitor(
+ input_inhibit_manager);
+ assert(input_inhibitor);
+
+ wlr_egl_init(&egl, EGL_PLATFORM_WAYLAND_EXT, display, NULL,
+ WL_SHM_FORMAT_ARGB8888);
+
+ struct wl_surface *surface = wl_compositor_create_surface(compositor);
+ assert(surface);
+ struct xdg_surface *xdg_surface =
+ xdg_wm_base_get_xdg_surface(wm_base, surface);
+ assert(xdg_surface);
+ struct xdg_toplevel *xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
+ assert(xdg_toplevel);
+
+ xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
+ xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL);
+
+ wl_surface_commit(surface);
+
+ egl_window = wl_egl_window_create(surface, width, height);
+ egl_surface = wlr_egl_create_surface(&egl, egl_window);
+
+ wl_display_roundtrip(display);
+
+ render_frame();
+
+ while (wl_display_dispatch(display) != -1) {
+ // This space intentionally left blank
+ }
+
+ return 0;
+}
diff --git a/examples/input-method.c b/examples/input-method.c
new file mode 100644
index 00000000..4e375306
--- /dev/null
+++ b/examples/input-method.c
@@ -0,0 +1,402 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/timerfd.h>
+#include <unistd.h>
+#include <wayland-client.h>
+#include <wayland-egl.h>
+#include "input-method-unstable-v2-client-protocol.h"
+#include "text-input-unstable-v3-client-protocol.h"
+#include "xdg-shell-client-protocol.h"
+
+const char usage[] = "Usage: input-method [seconds]\n\
+\n\
+Creates an input method using the input-method protocol.\n\
+\n\
+Whenever a text input is activated, this program sends a few sequences of\n\
+commands and checks the validity of the responses, relying on returned\n\
+surrounding text.\n\
+\n\
+The \"seconds\" argument is optional and defines the maximum delay between\n\
+stages.";
+
+struct input_method_state {
+ enum zwp_text_input_v3_change_cause change_cause;
+ struct {
+ enum zwp_text_input_v3_content_hint hint;
+ enum zwp_text_input_v3_content_purpose purpose;
+ } content_type;
+ struct {
+ char *text;
+ uint32_t cursor;
+ uint32_t anchor;
+ } surrounding;
+};
+
+static int sleeptime = 0;
+
+static struct wl_display *display = NULL;
+static struct wl_compositor *compositor = NULL;
+static struct wl_seat *seat = NULL;
+static struct zwp_input_method_manager_v2 *input_method_manager = NULL;
+static struct zwp_input_method_v2 *input_method = NULL;
+
+struct input_method_state pending;
+struct input_method_state current;
+
+static uint32_t serial = 0;
+bool active = false;
+bool pending_active = false;
+bool unavailable = false;
+bool running = false;
+
+uint32_t update_stage = 0;
+
+int timer_fd = 0;
+
+static void print_state_diff(struct input_method_state previous,
+ struct input_method_state future) {
+ if (previous.content_type.hint != future.content_type.hint) {
+ char *strs[] = { "COMPLETION", "SPELLCHECK", "AUTO_CAPITALIZATION",
+ "LOWERCASE", "UPPERCASE", "TITLECASE", "HIDDEN_TEXT",
+ "SENSITIVE_DATA", "LATIN", "MULTILINE"};
+ printf("content_type.hint:");
+ uint32_t hint = future.content_type.hint;
+ if (!hint) {
+ printf(" NONE");
+ }
+ for (unsigned i = 0; i < sizeof(strs) / sizeof(*strs); i++) {
+ if (hint & 1 << i) {
+ printf(" %s", strs[i]);
+ }
+ }
+ printf("\n");
+ }
+ if (previous.content_type.purpose != future.content_type.purpose) {
+ char *strs[] = { "NORMAL", "ALPHA", "DIGITS", "NUMBER", "PHONE", "URL",
+ "EMAIL", "NAME", "PASSWORD", "PIN", "DATE", "TIME", "DATETIME",
+ "TERMINAL" };
+ printf("content_type.purpose: %s\n", strs[future.content_type.purpose]);
+ }
+ if (!!previous.surrounding.text != !!future.surrounding.text
+ || (previous.surrounding.text && future.surrounding.text
+ && strcmp(previous.surrounding.text, future.surrounding.text) != 0)
+ || previous.surrounding.anchor != future.surrounding.anchor
+ || previous.surrounding.cursor != future.surrounding.cursor) {
+ char *text = future.surrounding.text;
+ if (!text) {
+ printf("Removed surrounding text\n");
+ } else {
+ printf("Surrounding text: %s\n", text);
+ uint32_t anchor = future.surrounding.anchor;
+ uint32_t cursor = future.surrounding.cursor;
+ if (cursor == anchor) {
+ char *temp = strndup(text, cursor);
+ printf("Cursor after %d: %s\n", cursor, temp);
+ free(temp);
+ } else {
+ if (cursor > anchor) {
+ uint32_t tmp = anchor;
+ anchor = cursor;
+ cursor = tmp;
+ }
+ char *temp = strndup(&text[cursor], anchor - cursor);
+ printf("Selection: %s\n", temp);
+ free(temp);
+ }
+ }
+ }
+ if (previous.change_cause != future.change_cause) {
+ char *strs[] = { "INPUT_METHOD", "OTHER" };
+ printf("Change cause: %s\n", strs[future.change_cause]);
+ }
+}
+
+static void handle_content_type(void *data,
+ struct zwp_input_method_v2 *zwp_input_method_v2,
+ uint32_t hint, uint32_t purpose) {
+ pending.content_type.hint = hint;
+ pending.content_type.purpose = purpose;
+}
+
+static void handle_surrounding_text(void *data,
+ struct zwp_input_method_v2 *zwp_input_method_v2,
+ const char *text, uint32_t cursor, uint32_t anchor) {
+ free(pending.surrounding.text);
+ pending.surrounding.text = strdup(text);
+ pending.surrounding.cursor = cursor;
+ pending.surrounding.anchor = anchor;
+}
+
+static void handle_text_change_cause(void *data,
+ struct zwp_input_method_v2 *zwp_input_method_v2,
+ uint32_t cause) {
+ pending.change_cause = cause;
+}
+
+static void handle_activate(void *data,
+ struct zwp_input_method_v2 *zwp_input_method_v2) {
+ pending_active = true;
+}
+
+static void handle_deactivate(void *data,
+ struct zwp_input_method_v2 *zwp_input_method_v2) {
+ pending_active = false;
+}
+
+static void handle_unavailable(void *data,
+ struct zwp_input_method_v2 *zwp_input_method_v2) {
+ printf("IM disappeared\n");
+ zwp_input_method_v2_destroy(zwp_input_method_v2);
+ input_method = NULL;
+ running = false;
+}
+
+static void im_activate(void *data,
+ struct zwp_input_method_v2 *id) {
+ update_stage = 0;
+}
+
+static void timer_arm(unsigned seconds) {
+ printf("Timer armed\n");
+ struct itimerspec spec = {
+ .it_interval = {0},
+ .it_value = {
+ .tv_sec = seconds,
+ .tv_nsec = 0
+ }
+ };
+ if (timerfd_settime(timer_fd, 0, &spec, NULL)) {
+ fprintf(stderr, "Failed to arm timer: %s\n", strerror(errno));
+ }
+}
+
+static void do_updates() {
+ printf("Update %d\n", update_stage);
+ switch (update_stage) {
+ case 0:
+ // TODO: remember initial surrounding text
+ zwp_input_method_v2_set_preedit_string(input_method, "Preedit", 2, 4);
+ zwp_input_method_v2_commit(input_method, serial);
+ // don't expect an answer, preedit doesn't change anything visible
+ timer_arm(sleeptime);
+ update_stage++;
+ return;
+ case 1:
+ zwp_input_method_v2_set_preedit_string(input_method, "Præedit2", strlen("Pr"), strlen("Præed"));
+ zwp_input_method_v2_commit_string(input_method, "_Commit_");
+ zwp_input_method_v2_commit(input_method, serial);
+ update_stage++;
+ break;
+ case 2:
+ if (strcmp(current.surrounding.text, "_Commit_") != 0) {
+ return;
+ }
+ zwp_input_method_v2_commit_string(input_method, "_CommitNoPreed_");
+ zwp_input_method_v2_commit(input_method, serial);
+ timer_arm(sleeptime);
+ update_stage++;
+ break;
+ case 3:
+ if (strcmp(current.surrounding.text, "_Commit__CommitNoPreed_") != 0) {
+ return;
+ }
+ zwp_input_method_v2_commit_string(input_method, "_WaitNo_");
+ zwp_input_method_v2_delete_surrounding_text(input_method, strlen("_CommitNoPreed_"), 0);
+ zwp_input_method_v2_commit(input_method, serial);
+ update_stage++;
+ break;
+ case 4:
+ if (strcmp(current.surrounding.text, "_Commit__WaitNo_") != 0) {
+ return;
+ }
+ zwp_input_method_v2_set_preedit_string(input_method, "PreedWithDel", strlen("Preed"), strlen("Preed"));
+ zwp_input_method_v2_delete_surrounding_text(input_method, strlen("_WaitNo_"), 0);
+ zwp_input_method_v2_commit(input_method, serial);
+ update_stage++;
+ break;
+ case 5:
+ if (strcmp(current.surrounding.text, "_Commit_") != 0) {
+ return;
+ }
+ zwp_input_method_v2_delete_surrounding_text(input_method, strlen("mit_"), 0);
+ zwp_input_method_v2_commit(input_method, serial);
+ update_stage++;
+ break;
+ case 6:
+ if (strcmp(current.surrounding.text, "_Com") != 0) {
+ printf("Failed\n");
+ }
+ update_stage++;
+ break;
+ default:
+ printf("Submitted everything\n");
+ return;
+ };
+}
+
+static void handle_timer() {
+ printf("Timer dispatched at %d\n", update_stage);
+ do_updates();
+}
+
+static void im_deactivate(void *data,
+ struct zwp_input_method_v2 *context) {
+ // No special action needed
+}
+
+static void handle_done(void *data,
+ struct zwp_input_method_v2 *zwp_input_method_v2) {
+ bool prev_active = active;
+ serial++;
+ printf("Handle serial %d\n", serial);
+ if (active != pending_active) {
+ printf("Now %s\n", pending_active ? "active" : "inactive");
+ }
+ if (pending_active) {
+ print_state_diff(current, pending);
+ }
+ active = pending_active;
+ free(current.surrounding.text);
+ struct input_method_state default_state = {0};
+ current = pending;
+ pending = default_state;
+ if (active && !prev_active) {
+ im_activate(data, zwp_input_method_v2);
+ } else if (!active && prev_active) {
+ im_deactivate(data, zwp_input_method_v2);
+ }
+
+ do_updates();
+}
+
+static const struct zwp_input_method_v2_listener im_listener = {
+ .activate = handle_activate,
+ .deactivate = handle_deactivate,
+ .surrounding_text = handle_surrounding_text,
+ .text_change_cause = handle_text_change_cause,
+ .content_type = handle_content_type,
+ .done = handle_done,
+ .unavailable = handle_unavailable,
+};
+
+static void handle_global(void *data, struct wl_registry *registry,
+ uint32_t name, const char *interface, uint32_t version) {
+ if (strcmp(interface, "wl_compositor") == 0) {
+ compositor = wl_registry_bind(registry, name,
+ &wl_compositor_interface, 1);
+ } else if (strcmp(interface, zwp_input_method_manager_v2_interface.name) == 0) {
+ input_method_manager = wl_registry_bind(registry, name,
+ &zwp_input_method_manager_v2_interface, 1);
+ } else if (strcmp(interface, wl_seat_interface.name) == 0) {
+ seat = wl_registry_bind(registry, name, &wl_seat_interface, version);
+ }
+}
+
+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,
+};
+
+int main(int argc, char **argv) {
+ if (argc > 1) {
+ if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) {
+ printf(usage);
+ return 0;
+ }
+ sleeptime = atoi(argv[1]);
+ }
+ display = wl_display_connect(NULL);
+ if (display == NULL) {
+ fprintf(stderr, "Failed to create display\n");
+ return EXIT_FAILURE;
+ }
+
+ struct wl_registry *registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, &registry_listener, NULL);
+ wl_display_dispatch(display);
+ wl_display_roundtrip(display);
+
+ if (compositor == NULL) {
+ fprintf(stderr, "wl-compositor not available\n");
+ return EXIT_FAILURE;
+ }
+ if (input_method_manager == NULL) {
+ fprintf(stderr, "input-method not available\n");
+ return EXIT_FAILURE;
+ }
+ if (seat == NULL) {
+ fprintf(stderr, "seat not available\n");
+ return EXIT_FAILURE;
+ }
+
+ input_method = zwp_input_method_manager_v2_get_input_method(
+ input_method_manager, seat);
+ running = true;
+ zwp_input_method_v2_add_listener(input_method, &im_listener, NULL);
+
+ int display_fd = wl_display_get_fd(display);
+ timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
+ if (timer_fd < 0) {
+ fprintf(stderr, "Failed to start timer\n");
+ return EXIT_FAILURE;
+ }
+ int epoll = epoll_create1(EPOLL_CLOEXEC);
+ if (epoll < 0) {
+ fprintf(stderr, "Failed to start epoll\n");
+ return EXIT_FAILURE;
+ }
+
+ struct epoll_event epoll_display = {
+ .events = EPOLLIN | EPOLLOUT,
+ .data = {.fd = display_fd},
+ };
+ if (epoll_ctl(epoll, EPOLL_CTL_ADD, display_fd, &epoll_display)) {
+ fprintf(stderr, "Failed to epoll display\n");
+ return EXIT_FAILURE;
+ }
+
+ wl_display_roundtrip(display); // timer may be armed here
+
+ struct epoll_event epoll_timer = {
+ .events = EPOLLIN,
+ .data = {.fd = timer_fd},
+ };
+ if (epoll_ctl(epoll, EPOLL_CTL_ADD, timer_fd, &epoll_timer)) {
+ fprintf(stderr, "Failed to epoll timer\n");
+ return EXIT_FAILURE;
+ }
+
+ timer_arm(2);
+
+ struct epoll_event caught;
+ while (epoll_wait(epoll, &caught, 1, -1)) {
+ if (!running) {
+ printf("Exiting\n");
+ return EXIT_SUCCESS;
+ }
+ if (caught.data.fd == display_fd) {
+ if (wl_display_dispatch(display) == -1) {
+ break;
+ }
+ } else if (caught.data.fd == timer_fd) {
+ uint64_t expirations;
+ ssize_t n = read(timer_fd, &expirations, sizeof(expirations));
+ assert(n >= 0);
+ handle_timer();
+ } else {
+ printf("Unknown source\n");
+ }
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/examples/layer-shell.c b/examples/layer-shell.c
new file mode 100644
index 00000000..77b2f6c3
--- /dev/null
+++ b/examples/layer-shell.c
@@ -0,0 +1,653 @@
+#define _POSIX_C_SOURCE 200112L
+#ifdef __linux__
+#include <linux/input-event-codes.h>
+#elif __FreeBSD__
+#include <dev/evdev/input-event-codes.h>
+#endif
+#include <assert.h>
+#include <GLES2/gl2.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <wayland-client.h>
+#include <wayland-cursor.h>
+#include <wayland-egl.h>
+#include <wlr/render/egl.h>
+#include <wlr/util/log.h>
+#include "wlr-layer-shell-unstable-v1-client-protocol.h"
+#include "xdg-shell-client-protocol.h"
+
+static struct wl_display *display;
+static struct wl_compositor *compositor;
+static struct wl_seat *seat;
+static struct wl_shm *shm;
+static struct wl_pointer *pointer;
+static struct wl_keyboard *keyboard;
+static struct xdg_wm_base *xdg_wm_base;
+static struct zwlr_layer_shell_v1 *layer_shell;
+
+struct zwlr_layer_surface_v1 *layer_surface;
+static struct wl_output *wl_output;
+
+struct wl_surface *wl_surface;
+struct wlr_egl egl;
+struct wl_egl_window *egl_window;
+struct wlr_egl_surface *egl_surface;
+struct wl_callback *frame_callback;
+
+static uint32_t output = UINT32_MAX;
+struct xdg_popup *popup;
+struct wl_surface *popup_wl_surface;
+struct wl_egl_window *popup_egl_window;
+static uint32_t popup_width = 256, popup_height = 256;
+struct wlr_egl_surface *popup_egl_surface;
+struct wl_callback *popup_frame_callback;
+float popup_alpha = 1.0, popup_red = 0.5f;
+
+static uint32_t layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND;
+static uint32_t anchor = 0;
+static uint32_t width = 256, height = 256;
+static int32_t margin_top = 0;
+static double alpha = 1.0;
+static bool run_display = true;
+static bool animate = false;
+static bool keyboard_interactive = false;
+static double frame = 0;
+static int cur_x = -1, cur_y = -1;
+static int buttons = 0;
+
+struct wl_cursor_image *cursor_image;
+struct wl_cursor_image *popup_cursor_image;
+struct wl_surface *cursor_surface, *input_surface;
+
+static struct {
+ struct timespec last_frame;
+ float color[3];
+ int dec;
+} demo;
+
+static void draw(void);
+static void draw_popup(void);
+
+static void surface_frame_callback(
+ void *data, struct wl_callback *cb, uint32_t time) {
+ wl_callback_destroy(cb);
+ frame_callback = NULL;
+ draw();
+}
+
+static struct wl_callback_listener frame_listener = {
+ .done = surface_frame_callback
+};
+
+static void popup_surface_frame_callback(
+ void *data, struct wl_callback *cb, uint32_t time) {
+ wl_callback_destroy(cb);
+ popup_frame_callback = NULL;
+ if (popup) {
+ draw_popup();
+ }
+}
+
+static struct wl_callback_listener popup_frame_listener = {
+ .done = popup_surface_frame_callback
+};
+
+static void draw(void) {
+ eglMakeCurrent(egl.display, egl_surface, egl_surface, egl.context);
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+
+ long ms = (ts.tv_sec - demo.last_frame.tv_sec) * 1000 +
+ (ts.tv_nsec - demo.last_frame.tv_nsec) / 1000000;
+ int inc = (demo.dec + 1) % 3;
+
+ if (!buttons) {
+ demo.color[inc] += ms / 2000.0f;
+ demo.color[demo.dec] -= ms / 2000.0f;
+
+ if (demo.color[demo.dec] < 0.0f) {
+ demo.color[inc] = 1.0f;
+ demo.color[demo.dec] = 0.0f;
+ demo.dec = inc;
+ }
+ }
+
+ if (animate) {
+ frame += ms / 50.0;
+ int32_t old_top = margin_top;
+ margin_top = -(20 - ((int)frame % 20));
+ if (old_top != margin_top) {
+ zwlr_layer_surface_v1_set_margin(layer_surface,
+ margin_top, 0, 0, 0);
+ wl_surface_commit(wl_surface);
+ }
+ }
+
+ glViewport(0, 0, width, height);
+ if (buttons) {
+ glClearColor(1, 1, 1, alpha);
+ } else {
+ glClearColor(demo.color[0], demo.color[1], demo.color[2], alpha);
+ }
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ if (cur_x != -1 && cur_y != -1) {
+ glEnable(GL_SCISSOR_TEST);
+ glScissor(cur_x, height - cur_y, 5, 5);
+ glClearColor(0, 0, 0, 1);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glDisable(GL_SCISSOR_TEST);
+ }
+
+ frame_callback = wl_surface_frame(wl_surface);
+ wl_callback_add_listener(frame_callback, &frame_listener, NULL);
+
+ eglSwapBuffers(egl.display, egl_surface);
+
+ demo.last_frame = ts;
+}
+
+static void draw_popup(void) {
+ static float alpha_mod = -0.01;
+
+ eglMakeCurrent(egl.display, popup_egl_surface, popup_egl_surface, egl.context);
+ glViewport(0, 0, popup_width, popup_height);
+ glClearColor(popup_red, 0.5f, 0.5f, popup_alpha);
+ popup_alpha += alpha_mod;
+ if (popup_alpha < 0.01 || popup_alpha >= 1.0f) {
+ alpha_mod *= -1.0;
+ }
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ popup_frame_callback = wl_surface_frame(popup_wl_surface);
+ assert(popup_frame_callback);
+ wl_callback_add_listener(popup_frame_callback, &popup_frame_listener, NULL);
+ eglSwapBuffers(egl.display, popup_egl_surface);
+ wl_surface_commit(popup_wl_surface);
+}
+
+static void xdg_surface_handle_configure(void *data,
+ struct xdg_surface *xdg_surface, uint32_t serial) {
+ xdg_surface_ack_configure(xdg_surface, serial);
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+ .configure = xdg_surface_handle_configure,
+};
+
+static void xdg_popup_configure(void *data, struct xdg_popup *xdg_popup,
+ int32_t x, int32_t y, int32_t width, int32_t height) {
+ wlr_log(WLR_DEBUG, "Popup configured %dx%d@%d,%d",
+ width, height, x, y);
+ popup_width = width;
+ popup_height = height;
+ if (popup_egl_window) {
+ wl_egl_window_resize(popup_egl_window, width, height, 0, 0);
+ }
+}
+
+static void popup_destroy(void) {
+ wlr_egl_destroy_surface(&egl, popup_egl_surface);
+ wl_egl_window_destroy(popup_egl_window);
+ xdg_popup_destroy(popup);
+ wl_surface_destroy(popup_wl_surface);
+ popup_wl_surface = NULL;
+ popup = NULL;
+ popup_egl_window = NULL;
+}
+
+static void xdg_popup_done(void *data, struct xdg_popup *xdg_popup) {
+ wlr_log(WLR_DEBUG, "Popup done");
+ popup_destroy();
+}
+
+static const struct xdg_popup_listener xdg_popup_listener = {
+ .configure = xdg_popup_configure,
+ .popup_done = xdg_popup_done,
+};
+
+static void create_popup(uint32_t serial) {
+ if (popup) {
+ return;
+ }
+ struct wl_surface *surface = wl_compositor_create_surface(compositor);
+ assert(xdg_wm_base && surface);
+ struct xdg_surface *xdg_surface =
+ xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
+ struct xdg_positioner *xdg_positioner =
+ xdg_wm_base_create_positioner(xdg_wm_base);
+ assert(xdg_surface && xdg_positioner);
+
+ xdg_positioner_set_size(xdg_positioner, popup_width, popup_height);
+ xdg_positioner_set_offset(xdg_positioner, 0, 0);
+ xdg_positioner_set_anchor_rect(xdg_positioner, cur_x, cur_y, 1, 1);
+ xdg_positioner_set_anchor(xdg_positioner, XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT);
+
+ popup = xdg_surface_get_popup(xdg_surface, NULL, xdg_positioner);
+ xdg_popup_grab(popup, seat, serial);
+
+ assert(popup);
+
+ zwlr_layer_surface_v1_get_popup(layer_surface, popup);
+
+ xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
+ xdg_popup_add_listener(popup, &xdg_popup_listener, NULL);
+
+ wl_surface_commit(surface);
+ wl_display_roundtrip(display);
+
+ xdg_positioner_destroy(xdg_positioner);
+
+ popup_wl_surface = surface;
+ popup_egl_window = wl_egl_window_create(surface, popup_width, popup_height);
+ assert(popup_egl_window);
+ popup_egl_surface = wlr_egl_create_surface(&egl, popup_egl_window);
+ assert(popup_egl_surface);
+ draw_popup();
+}
+
+static void layer_surface_configure(void *data,
+ struct zwlr_layer_surface_v1 *surface,
+ uint32_t serial, uint32_t w, uint32_t h) {
+ width = w;
+ height = h;
+ if (egl_window) {
+ wl_egl_window_resize(egl_window, width, height, 0, 0);
+ }
+ zwlr_layer_surface_v1_ack_configure(surface, serial);
+}
+
+static void layer_surface_closed(void *data,
+ struct zwlr_layer_surface_v1 *surface) {
+ wlr_egl_destroy_surface(&egl, egl_surface);
+ wl_egl_window_destroy(egl_window);
+ zwlr_layer_surface_v1_destroy(surface);
+ wl_surface_destroy(wl_surface);
+ run_display = false;
+}
+
+struct zwlr_layer_surface_v1_listener layer_surface_listener = {
+ .configure = layer_surface_configure,
+ .closed = layer_surface_closed,
+};
+
+static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
+ uint32_t serial, struct wl_surface *surface,
+ wl_fixed_t surface_x, wl_fixed_t surface_y) {
+ struct wl_cursor_image *image;
+ if (surface == popup_wl_surface) {
+ image = popup_cursor_image;
+ } else {
+ image = cursor_image;
+ }
+ wl_surface_attach(cursor_surface,
+ wl_cursor_image_get_buffer(image), 0, 0);
+ wl_surface_damage(cursor_surface, 1, 0,
+ image->width, image->height);
+ wl_surface_commit(cursor_surface);
+ wl_pointer_set_cursor(wl_pointer, serial, cursor_surface,
+ image->hotspot_x, image->hotspot_y);
+ input_surface = surface;
+}
+
+static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
+ uint32_t serial, struct wl_surface *surface) {
+ cur_x = cur_y = -1;
+ buttons = 0;
+}
+
+static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
+ uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
+ cur_x = wl_fixed_to_int(surface_x);
+ cur_y = wl_fixed_to_int(surface_y);
+}
+
+static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
+ uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {
+ if (input_surface == wl_surface) {
+ if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
+ if (button == BTN_RIGHT) {
+ if (popup_wl_surface) {
+ popup_destroy();
+ } else {
+ create_popup(serial);
+ }
+ } else {
+ buttons++;
+ }
+ } else {
+ if (button != BTN_RIGHT) {
+ buttons--;
+ }
+ }
+ } else if (input_surface == popup_wl_surface) {
+ if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
+ if (button == BTN_LEFT && popup_red <= 0.9f) {
+ popup_red += 0.1;
+ } else if (button == BTN_RIGHT && popup_red >= 0.1f) {
+ popup_red -= 0.1;
+ }
+ }
+ } else {
+ assert(false && "Unknown surface");
+ }
+}
+
+static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
+ uint32_t time, uint32_t axis, wl_fixed_t value) {
+ // Who cares
+}
+
+static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) {
+ // Who cares
+}
+
+static void wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer,
+ uint32_t axis_source) {
+ // Who cares
+}
+
+static void wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer,
+ uint32_t time, uint32_t axis) {
+ // Who cares
+}
+
+static void wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer,
+ uint32_t axis, int32_t discrete) {
+ // Who cares
+}
+
+struct wl_pointer_listener pointer_listener = {
+ .enter = wl_pointer_enter,
+ .leave = wl_pointer_leave,
+ .motion = wl_pointer_motion,
+ .button = wl_pointer_button,
+ .axis = wl_pointer_axis,
+ .frame = wl_pointer_frame,
+ .axis_source = wl_pointer_axis_source,
+ .axis_stop = wl_pointer_axis_stop,
+ .axis_discrete = wl_pointer_axis_discrete,
+};
+
+static void wl_keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t format, int32_t fd, uint32_t size) {
+ // Who cares
+}
+
+static void wl_keyboard_enter(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, struct wl_surface *surface, struct wl_array *keys) {
+ wlr_log(WLR_DEBUG, "Keyboard enter");
+}
+
+static void wl_keyboard_leave(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, struct wl_surface *surface) {
+ wlr_log(WLR_DEBUG, "Keyboard leave");
+}
+
+static void wl_keyboard_key(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
+ wlr_log(WLR_DEBUG, "Key event: %d %d", key, state);
+}
+
+static void wl_keyboard_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) {
+ // Who cares
+}
+
+static void wl_keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
+ int32_t rate, int32_t delay) {
+ // Who cares
+}
+
+static struct wl_keyboard_listener keyboard_listener = {
+ .keymap = wl_keyboard_keymap,
+ .enter = wl_keyboard_enter,
+ .leave = wl_keyboard_leave,
+ .key = wl_keyboard_key,
+ .modifiers = wl_keyboard_modifiers,
+ .repeat_info = wl_keyboard_repeat_info,
+};
+
+static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
+ enum wl_seat_capability caps) {
+ if ((caps & WL_SEAT_CAPABILITY_POINTER)) {
+ pointer = wl_seat_get_pointer(wl_seat);
+ wl_pointer_add_listener(pointer, &pointer_listener, NULL);
+ }
+ if ((caps & WL_SEAT_CAPABILITY_KEYBOARD)) {
+ keyboard = wl_seat_get_keyboard(wl_seat);
+ wl_keyboard_add_listener(keyboard, &keyboard_listener, NULL);
+ }
+}
+
+static void seat_handle_name(void *data, struct wl_seat *wl_seat,
+ const char *name) {
+ // Who cares
+}
+
+const struct wl_seat_listener seat_listener = {
+ .capabilities = seat_handle_capabilities,
+ .name = seat_handle_name,
+};
+
+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, 1);
+ } else if (strcmp(interface, wl_shm_interface.name) == 0) {
+ shm = wl_registry_bind(registry, name,
+ &wl_shm_interface, 1);
+ } else if (strcmp(interface, "wl_output") == 0) {
+ if (output != UINT32_MAX) {
+ if (!wl_output) {
+ wl_output = wl_registry_bind(registry, name,
+ &wl_output_interface, 1);
+ } else {
+ output--;
+ }
+ }
+ } else if (strcmp(interface, wl_seat_interface.name) == 0) {
+ seat = wl_registry_bind(registry, name,
+ &wl_seat_interface, 1);
+ wl_seat_add_listener(seat, &seat_listener, NULL);
+ } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
+ layer_shell = wl_registry_bind(
+ registry, name, &zwlr_layer_shell_v1_interface, 1);
+ } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
+ xdg_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,
+};
+
+int main(int argc, char **argv) {
+ wlr_log_init(WLR_DEBUG, NULL);
+ char *namespace = "wlroots";
+ int exclusive_zone = 0;
+ int32_t margin_right = 0, margin_bottom = 0, margin_left = 0;
+ bool found;
+ int c;
+ while ((c = getopt(argc, argv, "knw:h:o:l:a:x:m:t:")) != -1) {
+ switch (c) {
+ case 'o':
+ output = atoi(optarg);
+ break;
+ case 'w':
+ width = atoi(optarg);
+ break;
+ case 'h':
+ height = atoi(optarg);
+ break;
+ case 'x':
+ exclusive_zone = atoi(optarg);
+ break;
+ case 'l': {
+ struct {
+ char *name;
+ enum zwlr_layer_shell_v1_layer value;
+ } layers[] = {
+ { "background", ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND },
+ { "bottom", ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM },
+ { "top", ZWLR_LAYER_SHELL_V1_LAYER_TOP },
+ { "overlay", ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY },
+ };
+ found = false;
+ for (size_t i = 0; i < sizeof(layers) / sizeof(layers[0]); ++i) {
+ if (strcmp(optarg, layers[i].name) == 0) {
+ layer = layers[i].value;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ fprintf(stderr, "invalid layer %s\n", optarg);
+ return 1;
+ }
+ break;
+ }
+ case 'a': {
+ struct {
+ char *name;
+ uint32_t value;
+ } anchors[] = {
+ { "top", ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP },
+ { "bottom", ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM },
+ { "left", ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT },
+ { "right", ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT },
+ };
+ found = false;
+ for (size_t i = 0; i < sizeof(anchors) / sizeof(anchors[0]); ++i) {
+ if (strcmp(optarg, anchors[i].name) == 0) {
+ anchor |= anchors[i].value;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ fprintf(stderr, "invalid anchor %s\n", optarg);
+ return 1;
+ }
+ break;
+ }
+ case 't':
+ alpha = atof(optarg);
+ break;
+ case 'm': {
+ char *endptr = optarg;
+ margin_top = strtol(endptr, &endptr, 10);
+ assert(*endptr == ',');
+ margin_right = strtol(endptr + 1, &endptr, 10);
+ assert(*endptr == ',');
+ margin_bottom = strtol(endptr + 1, &endptr, 10);
+ assert(*endptr == ',');
+ margin_left = strtol(endptr + 1, &endptr, 10);
+ assert(!*endptr);
+ break;
+ }
+ case 'n':
+ animate = true;
+ break;
+ case 'k':
+ keyboard_interactive = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ display = wl_display_connect(NULL);
+ if (display == NULL) {
+ fprintf(stderr, "Failed to create display\n");
+ return 1;
+ }
+
+ struct wl_registry *registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, &registry_listener, NULL);
+ wl_display_roundtrip(display);
+
+ if (compositor == NULL) {
+ fprintf(stderr, "wl_compositor not available\n");
+ return 1;
+ }
+ if (shm == NULL) {
+ fprintf(stderr, "wl_shm not available\n");
+ return 1;
+ }
+ if (layer_shell == NULL) {
+ fprintf(stderr, "layer_shell not available\n");
+ return 1;
+ }
+
+ struct wl_cursor_theme *cursor_theme =
+ wl_cursor_theme_load(NULL, 16, shm);
+ assert(cursor_theme);
+ struct wl_cursor *cursor =
+ wl_cursor_theme_get_cursor(cursor_theme, "crosshair");
+ if (cursor == NULL) {
+ cursor = wl_cursor_theme_get_cursor(cursor_theme, "left_ptr");
+ }
+ assert(cursor);
+ cursor_image = cursor->images[0];
+
+ cursor = wl_cursor_theme_get_cursor(cursor_theme, "tcross");
+ if (cursor == NULL) {
+ cursor = wl_cursor_theme_get_cursor(cursor_theme, "left_ptr");
+ }
+ assert(cursor);
+ popup_cursor_image = cursor->images[0];
+
+ cursor_surface = wl_compositor_create_surface(compositor);
+ assert(cursor_surface);
+
+ EGLint attribs[] = { EGL_ALPHA_SIZE, 8, EGL_NONE };
+ wlr_egl_init(&egl, EGL_PLATFORM_WAYLAND_EXT, display,
+ attribs, WL_SHM_FORMAT_ARGB8888);
+
+ wl_surface = wl_compositor_create_surface(compositor);
+ assert(wl_surface);
+
+ layer_surface = zwlr_layer_shell_v1_get_layer_surface(layer_shell,
+ wl_surface, wl_output, layer, namespace);
+ assert(layer_surface);
+ zwlr_layer_surface_v1_set_size(layer_surface, width, height);
+ zwlr_layer_surface_v1_set_anchor(layer_surface, anchor);
+ zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, exclusive_zone);
+ zwlr_layer_surface_v1_set_margin(layer_surface,
+ margin_top, margin_right, margin_bottom, margin_left);
+ zwlr_layer_surface_v1_set_keyboard_interactivity(
+ layer_surface, keyboard_interactive);
+ zwlr_layer_surface_v1_add_listener(layer_surface,
+ &layer_surface_listener, layer_surface);
+ wl_surface_commit(wl_surface);
+ wl_display_roundtrip(display);
+
+ egl_window = wl_egl_window_create(wl_surface, width, height);
+ assert(egl_window);
+ egl_surface = wlr_egl_create_surface(&egl, egl_window);
+ assert(egl_surface);
+
+ wl_display_roundtrip(display);
+ draw();
+
+ while (wl_display_dispatch(display) != -1 && run_display) {
+ // This space intentionally left blank
+ }
+
+ wl_cursor_theme_destroy(cursor_theme);
+ return 0;
+}
diff --git a/examples/meson.build b/examples/meson.build
new file mode 100644
index 00000000..589a7326
--- /dev/null
+++ b/examples/meson.build
@@ -0,0 +1,133 @@
+threads = dependency('threads')
+wayland_cursor = dependency('wayland-cursor')
+libpng = dependency('libpng', required: false)
+# These versions correspond to ffmpeg 4.0
+libavutil = dependency('libavutil', version: '>=56.14.100', required: false)
+libavcodec = dependency('libavcodec', version: '>=58.18.100', required: false)
+libavformat = dependency('libavformat', version: '>=58.12.100', required: false)
+
+# epoll is a separate library in FreeBSD
+if host_machine.system() == 'freebsd'
+ libepoll = [dependency('epoll-shim')]
+else
+ libepoll = []
+endif
+
+# Small hack until https://github.com/mesonbuild/meson/pull/3386/ is merged
+foreach dep : ['libpng', 'libavutil', 'libavcodec', 'libavformat']
+ if not get_variable(dep).found()
+ set_variable(dep, disabler())
+ endif
+endforeach
+
+if not cc.has_header('libavutil/hwcontext_drm.h', dependencies: libavutil)
+ libavutil = disabler()
+endif
+
+examples = {
+ 'simple': {
+ 'src': 'simple.c',
+ 'dep': [wlroots],
+ },
+ 'pointer': {
+ 'src': 'pointer.c',
+ 'dep': [wlroots],
+ },
+ 'touch': {
+ 'src': ['touch.c', 'cat.c'],
+ 'dep': [wlroots],
+ },
+ 'tablet': {
+ 'src': 'tablet.c',
+ 'dep': [wlroots],
+ },
+ 'rotation': {
+ 'src': ['rotation.c', 'cat.c'],
+ 'dep': [wlroots],
+ },
+ 'multi-pointer': {
+ 'src': 'multi-pointer.c',
+ 'dep': [wlroots],
+ },
+ 'output-layout': {
+ 'src': ['output-layout.c', 'cat.c'],
+ 'dep': [wlroots],
+ },
+ 'screenshot': {
+ 'src': 'screenshot.c',
+ 'dep': [wayland_client, wlr_protos, rt],
+ },
+ 'idle': {
+ 'src': 'idle.c',
+ 'dep': [wayland_client, wlr_protos, threads],
+ },
+ 'idle-inhibit': {
+ 'src': 'idle-inhibit.c',
+ 'dep': [wayland_client, wlr_protos, wlroots],
+ },
+ 'layer-shell': {
+ 'src': 'layer-shell.c',
+ 'dep': [wayland_client, wayland_cursor, wlr_protos, wlroots],
+ },
+ 'input-inhibitor': {
+ 'src': 'input-inhibitor.c',
+ 'dep': [wayland_client, wayland_cursor, wlr_protos, wlroots],
+ },
+ 'gamma-control': {
+ 'src': 'gamma-control.c',
+ 'dep': [wayland_client, wayland_cursor, wlr_protos, math],
+ },
+ 'pointer-constraints': {
+ 'src': 'pointer-constraints.c',
+ 'dep': [wayland_client, wlr_protos, wlroots],
+ },
+ 'dmabuf-capture': {
+ 'src': 'dmabuf-capture.c',
+ 'dep': [
+ libavcodec,
+ libavformat,
+ libavutil,
+ drm.partial_dependency(compile_args: true), # <drm_fourcc.h>
+ threads,
+ wayland_client,
+ wlr_protos,
+ ],
+ },
+ 'screencopy': {
+ 'src': 'screencopy.c',
+ 'dep': [libpng, wayland_client, wlr_protos, rt],
+ },
+ 'toplevel-decoration': {
+ 'src': 'toplevel-decoration.c',
+ 'dep': [wayland_client, wlr_protos, wlroots],
+ },
+ 'input-method': {
+ 'src': 'input-method.c',
+ 'dep': [wayland_client, wlr_protos] + libepoll,
+ },
+ 'text-input': {
+ 'src': 'text-input.c',
+ 'dep': [wayland_cursor, wayland_client, wlr_protos, wlroots],
+ },
+ 'foreign-toplevel': {
+ 'src': 'foreign-toplevel.c',
+ 'dep': [wayland_client, wlr_protos, wlroots],
+ },
+}
+
+foreach name, info : examples
+ all_dep_found = true
+ foreach d : info.get('dep')
+ all_dep_found = all_dep_found and d.found()
+ endforeach
+ if all_dep_found
+ executable(
+ name,
+ info.get('src'),
+ dependencies: info.get('dep'),
+ build_by_default: get_option('examples'),
+ )
+ else
+ warning('Dependencies not satisfied for ' + name)
+ endif
+endforeach
diff --git a/examples/multi-pointer.c b/examples/multi-pointer.c
new file mode 100644
index 00000000..49670c39
--- /dev/null
+++ b/examples/multi-pointer.c
@@ -0,0 +1,340 @@
+#define _POSIX_C_SOURCE 200112L
+#include <assert.h>
+#include <GLES2/gl2.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <wayland-server-protocol.h>
+#include <wayland-server.h>
+#include <wlr/backend.h>
+#include <wlr/backend/session.h>
+#include <wlr/render/gles2.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/types/wlr_keyboard.h>
+#include <wlr/types/wlr_list.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/util/log.h>
+#include <wlr/xcursor.h>
+#include <xkbcommon/xkbcommon.h>
+
+struct sample_state {
+ struct wl_display *display;
+ struct wlr_xcursor *xcursor;
+ float default_color[4];
+ float clear_color[4];
+ struct wlr_output_layout *layout;
+ struct wl_list cursors; // sample_cursor::link
+ struct wl_list pointers; // sample_pointer::link
+ struct wl_list outputs; // sample_output::link
+ struct timespec last_frame;
+ struct wl_listener new_output;
+ struct wl_listener new_input;
+};
+
+struct sample_cursor {
+ struct sample_state *sample;
+ struct wlr_input_device *device;
+ struct wlr_cursor *cursor;
+ struct wl_list link;
+
+ struct wl_listener cursor_motion;
+ struct wl_listener cursor_motion_absolute;
+ struct wl_listener cursor_button;
+ struct wl_listener cursor_axis;
+ struct wl_listener destroy;
+};
+
+struct sample_pointer {
+ struct wlr_input_device *device;
+ struct wl_list link;
+};
+
+struct sample_output {
+ struct sample_state *sample;
+ struct wlr_output *output;
+ struct wl_listener frame;
+ struct wl_listener destroy;
+ struct wl_list link;
+};
+
+struct sample_keyboard {
+ struct sample_state *sample;
+ struct wlr_input_device *device;
+ struct wl_listener key;
+ struct wl_listener destroy;
+};
+
+void configure_cursor(struct wlr_cursor *cursor, struct wlr_input_device *device,
+ struct sample_state *sample) {
+ struct sample_output *output;
+ wlr_log(WLR_ERROR, "Configuring cursor %p for device %p", cursor, device);
+
+ // reset mappings
+ wlr_cursor_map_to_output(cursor, NULL);
+ wlr_cursor_detach_input_device(cursor, device);
+ wlr_cursor_map_input_to_output(cursor, device, NULL);
+
+ wlr_cursor_attach_input_device(cursor, device);
+
+ // configure device to output mappings
+ wl_list_for_each(output, &sample->outputs, link) {
+ wlr_cursor_map_to_output(cursor, output->output);
+
+ wlr_cursor_map_input_to_output(cursor, device, output->output);
+ }
+}
+
+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;
+
+ wlr_output_make_current(wlr_output, NULL);
+
+ glClearColor(sample->clear_color[0], sample->clear_color[1],
+ sample->clear_color[2], sample->clear_color[3]);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ wlr_output_swap_buffers(wlr_output, NULL, NULL);
+}
+
+static void handle_cursor_motion(struct wl_listener *listener, void *data) {
+ struct sample_cursor *cursor =
+ wl_container_of(listener, cursor, cursor_motion);
+ struct wlr_event_pointer_motion *event = data;
+ wlr_cursor_move(cursor->cursor, event->device, event->delta_x,
+ event->delta_y);
+}
+
+static void handle_cursor_motion_absolute(struct wl_listener *listener,
+ void *data) {
+ struct sample_cursor *cursor =
+ wl_container_of(listener, cursor, cursor_motion_absolute);
+ struct wlr_event_pointer_motion_absolute *event = data;
+ wlr_cursor_warp_absolute(cursor->cursor, event->device, event->x, event->y);
+}
+
+static void cursor_destroy(struct sample_cursor *cursor) {
+ wl_list_remove(&cursor->link);
+ wl_list_remove(&cursor->cursor_motion.link);
+ wl_list_remove(&cursor->cursor_motion_absolute.link);
+ wlr_cursor_destroy(cursor->cursor);
+ free(cursor);
+}
+
+void input_remove_notify(struct wl_listener *listener, void *data) {
+ struct wlr_input_device *device = data;
+ struct sample_cursor *sample_cursor = wl_container_of(listener, sample_cursor, destroy);
+ struct sample_state *sample = sample_cursor->sample;
+ struct sample_cursor *cursor;
+ wl_list_for_each(cursor, &sample->cursors, link) {
+ if (cursor->device == device) {
+ cursor_destroy(cursor);
+ break;
+ }
+ }
+ struct sample_pointer *pointer;
+ wl_list_for_each(pointer, &sample->pointers, link) {
+ if (pointer->device == device) {
+ free(pointer);
+ break;
+ }
+ }
+}
+
+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;
+ wl_list_remove(&sample_output->frame.link);
+ wl_list_remove(&sample_output->destroy.link);
+ wl_list_remove(&sample_output->link);
+ free(sample_output);
+
+ struct sample_cursor *cursor;
+ wl_list_for_each(cursor, &sample->cursors, link) {
+ configure_cursor(cursor->cursor, cursor->device, sample);
+ }
+}
+
+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);
+ struct sample_output *sample_output = calloc(1, sizeof(struct sample_output));
+ if (!wl_list_empty(&output->modes)) {
+ struct wlr_output_mode *mode = wl_container_of(output->modes.prev, mode, link);
+ wlr_output_set_mode(output, mode);
+ }
+ 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;
+
+ wlr_output_layout_add_auto(sample->layout, output);
+
+
+ struct sample_cursor *cursor;
+ wl_list_for_each(cursor, &sample->cursors, link) {
+ configure_cursor(cursor->cursor, cursor->device, sample);
+
+ struct wlr_xcursor_image *image = sample->xcursor->images[0];
+ wlr_cursor_set_image(cursor->cursor, image->buffer, image->width * 4,
+ image->width, image->height, image->hotspot_x, image->hotspot_y, 0);
+
+ wlr_cursor_warp(cursor->cursor, NULL, cursor->cursor->x,
+ cursor->cursor->y);
+ }
+ wl_list_insert(&sample->outputs, &sample_output->link);
+}
+
+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_event_keyboard_key *event = data;
+ uint32_t keycode = event->keycode + 8;
+ const xkb_keysym_t *syms;
+ int nsyms = xkb_state_key_get_syms(keyboard->device->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);
+ }
+ }
+}
+
+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);
+}
+
+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(struct sample_keyboard));
+ keyboard->device = device;
+ keyboard->sample = sample;
+ wl_signal_add(&device->events.destroy, &keyboard->destroy);
+ keyboard->destroy.notify = keyboard_destroy_notify;
+ wl_signal_add(&device->keyboard->events.key, &keyboard->key);
+ keyboard->key.notify = keyboard_key_notify;
+ struct xkb_rule_names rules = { 0 };
+ rules.rules = getenv("XKB_DEFAULT_RULES");
+ rules.model = getenv("XKB_DEFAULT_MODEL");
+ rules.layout = getenv("XKB_DEFAULT_LAYOUT");
+ rules.variant = getenv("XKB_DEFAULT_VARIANT");
+ rules.options = getenv("XKB_DEFAULT_OPTIONS");
+ 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_map_new_from_names(context, &rules,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+ if (!keymap) {
+ wlr_log(WLR_ERROR, "Failed to create XKB keymap");
+ exit(1);
+ }
+ wlr_keyboard_set_keymap(device->keyboard, keymap);
+ xkb_keymap_unref(keymap);
+ xkb_context_unref(context);
+ break;
+ case WLR_INPUT_DEVICE_POINTER:;
+ struct sample_cursor *cursor = calloc(1, sizeof(struct sample_cursor));
+ struct sample_pointer *pointer = calloc(1, sizeof(struct sample_pointer));
+ pointer->device = device;
+ cursor->sample = sample;
+ cursor->device = device;
+
+ cursor->cursor = wlr_cursor_create();
+
+ wlr_cursor_attach_output_layout(cursor->cursor, sample->layout);
+
+ wl_signal_add(&cursor->cursor->events.motion, &cursor->cursor_motion);
+ cursor->cursor_motion.notify = handle_cursor_motion;
+ wl_signal_add(&cursor->cursor->events.motion_absolute,
+ &cursor->cursor_motion_absolute);
+ cursor->cursor_motion_absolute.notify = handle_cursor_motion_absolute;
+
+ wlr_cursor_attach_input_device(cursor->cursor, device);
+ configure_cursor(cursor->cursor, device, sample);
+
+ struct wlr_xcursor_image *image = sample->xcursor->images[0];
+ wlr_cursor_set_image(cursor->cursor, image->buffer, image->width * 4,
+ image->width, image->height, image->hotspot_x, image->hotspot_y, 0);
+
+ wl_list_insert(&sample->cursors, &cursor->link);
+ wl_list_insert(&sample->pointers, &pointer->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 = {
+ .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(display, NULL);
+ if (!wlr) {
+ exit(1);
+ }
+ wl_list_init(&state.cursors);
+ wl_list_init(&state.pointers);
+ wl_list_init(&state.outputs);
+
+ state.layout = wlr_output_layout_create();
+
+ 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);
+
+ struct wlr_xcursor_theme *theme = wlr_xcursor_theme_load("default", 16);
+ if (!theme) {
+ wlr_log(WLR_ERROR, "Failed to load cursor theme");
+ return 1;
+ }
+ state.xcursor = wlr_xcursor_theme_get_cursor(theme, "left_ptr");
+ if (!state.xcursor) {
+ wlr_log(WLR_ERROR, "Failed to load left_ptr cursor");
+ return 1;
+ }
+
+ 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);
+
+ struct sample_cursor *cursor, *tmp_cursor;
+ wl_list_for_each_safe(cursor, tmp_cursor, &state.cursors, link) {
+ cursor_destroy(cursor);
+ }
+
+ struct sample_pointer *pointer, *tmp_pointer;
+ wl_list_for_each_safe(pointer, tmp_pointer, &state.pointers, link) {
+ free(pointer);
+ }
+
+ wlr_xcursor_theme_destroy(theme);
+ wlr_output_layout_destroy(state.layout);
+}
diff --git a/examples/output-layout.c b/examples/output-layout.c
new file mode 100644
index 00000000..440b3188
--- /dev/null
+++ b/examples/output-layout.c
@@ -0,0 +1,295 @@
+#define _POSIX_C_SOURCE 200112L
+#include <GLES2/gl2.h>
+#include <limits.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+#include <wayland-server-protocol.h>
+#include <wayland-server.h>
+#include <wlr/backend.h>
+#include <wlr/backend/session.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_keyboard.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/util/log.h>
+#include <xkbcommon/xkbcommon.h>
+#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_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_input_device *device;
+ 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;
+}
+
+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 timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+
+ struct wlr_output *wlr_output = output->output;
+
+ wlr_output_make_current(wlr_output, NULL);
+ wlr_renderer_begin(sample->renderer, wlr_output->width, wlr_output->height);
+ wlr_renderer_clear(sample->renderer, (float[]){0.25f, 0.25f, 0.25f, 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_texture(sample->renderer, sample->cat_texture,
+ wlr_output->transform_matrix, local_x, local_y, 1.0f);
+ }
+
+ wlr_renderer_end(sample->renderer);
+ wlr_output_swap_buffers(wlr_output, NULL, NULL);
+}
+
+static void update_velocities(struct sample_state *sample,
+ float x_diff, float y_diff) {
+ sample->x_vel += x_diff;
+ sample->y_vel += y_diff;
+}
+
+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);
+}
+
+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);
+ struct sample_output *sample_output = calloc(1, sizeof(struct sample_output));
+ if (!wl_list_empty(&output->modes)) {
+ struct wlr_output_mode *mode = wl_container_of(output->modes.prev, mode, link);
+ wlr_output_set_mode(output, mode);
+ }
+ 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;
+}
+
+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_event_keyboard_key *event = data;
+ uint32_t keycode = event->keycode + 8;
+ const xkb_keysym_t *syms;
+ int nsyms = xkb_state_key_get_syms(keyboard->device->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 == WLR_KEY_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;
+ }
+ }
+ }
+}
+
+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);
+}
+
+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(struct sample_keyboard));
+ keyboard->device = device;
+ keyboard->sample = sample;
+ wl_signal_add(&device->events.destroy, &keyboard->destroy);
+ keyboard->destroy.notify = keyboard_destroy_notify;
+ wl_signal_add(&device->keyboard->events.key, &keyboard->key);
+ keyboard->key.notify = keyboard_key_notify;
+ struct xkb_rule_names rules = { 0 };
+ rules.rules = getenv("XKB_DEFAULT_RULES");
+ rules.model = getenv("XKB_DEFAULT_MODEL");
+ rules.layout = getenv("XKB_DEFAULT_LAYOUT");
+ rules.variant = getenv("XKB_DEFAULT_VARIANT");
+ rules.options = getenv("XKB_DEFAULT_OPTIONS");
+ 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_map_new_from_names(context, &rules,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+ if (!keymap) {
+ wlr_log(WLR_ERROR, "Failed to create XKB keymap");
+ exit(1);
+ }
+ wlr_keyboard_set_keymap(device->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();
+ clock_gettime(CLOCK_MONOTONIC, &state.ts_last);
+
+ struct wlr_backend *wlr = wlr_backend_autocreate(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_backend_get_renderer(wlr);
+ state.cat_texture = wlr_texture_from_pixels(state.renderer,
+ WL_SHM_FORMAT_ABGR8888, cat_tex.width * 4, cat_tex.width, cat_tex.height,
+ cat_tex.pixel_data);
+
+ 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);
+ wlr_output_layout_destroy(state.layout);
+}
diff --git a/examples/pointer-constraints.c b/examples/pointer-constraints.c
new file mode 100644
index 00000000..1df9f6ce
--- /dev/null
+++ b/examples/pointer-constraints.c
@@ -0,0 +1,260 @@
+#include <GLES2/gl2.h>
+#include <linux/input-event-codes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-client.h>
+#include <wayland-egl.h>
+#include <wlr/render/egl.h>
+#include "xdg-shell-client-protocol.h"
+#include "pointer-constraints-unstable-v1-client-protocol.h"
+
+static int width = 512, height = 512;
+
+static struct wl_compositor *compositor = NULL;
+static struct wl_seat *seat = NULL;
+static struct xdg_wm_base *wm_base = NULL;
+static struct zwp_pointer_constraints_v1 *pointer_constraints = NULL;
+
+struct wlr_egl egl;
+struct wl_egl_window *egl_window;
+struct wlr_egl_surface *egl_surface;
+struct zwp_locked_pointer_v1* locked_pointer;
+struct zwp_confined_pointer_v1* confined_pointer;
+
+enum {
+ REGION_TYPE_NONE,
+ REGION_TYPE_DISJOINT,
+ REGION_TYPE_JOINT,
+ REGION_TYPE_MAX
+} region_type = REGION_TYPE_NONE;
+
+struct wl_region *regions[3];
+
+static void draw(void) {
+ eglMakeCurrent(egl.display, egl_surface, egl_surface, egl.context);
+
+ float color[] = {1.0, 1.0, 0.0, 1.0};
+
+ glViewport(0, 0, width, height);
+ glClearColor(color[0], color[1], color[2], 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ eglSwapBuffers(egl.display, egl_surface);
+}
+
+static void pointer_handle_button(void *data, struct wl_pointer *pointer,
+ uint32_t serial, uint32_t time, uint32_t button, uint32_t state_w) {
+ struct wl_surface *surface = data;
+
+ if (button == BTN_LEFT && state_w == WL_POINTER_BUTTON_STATE_PRESSED) {
+ region_type = (region_type + 1) % REGION_TYPE_MAX;
+
+ if (locked_pointer) {
+ zwp_locked_pointer_v1_set_region(locked_pointer,
+ regions[region_type]);
+ } else if (confined_pointer) {
+ zwp_confined_pointer_v1_set_region(confined_pointer,
+ regions[region_type]);
+ }
+
+ wl_surface_commit(surface);
+ }
+}
+
+static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer,
+ uint32_t serial, struct wl_surface *surface,
+ wl_fixed_t surface_x, wl_fixed_t surface_y) {
+ // This space intentionally left blank
+}
+
+static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer,
+ uint32_t serial, struct wl_surface *surface) {
+ // This space intentionally left blank
+}
+
+static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer,
+ uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
+ // This space intentionally left blank
+}
+
+static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer,
+ uint32_t time, uint32_t axis, wl_fixed_t value) {
+ // This space intentionally left blank
+}
+
+static void pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) {
+ // This space intentionally left blank
+}
+
+static void pointer_handle_axis_source(void *data,
+ struct wl_pointer *wl_pointer, uint32_t axis_source) {
+ // This space intentionally left blank
+}
+
+static void pointer_handle_axis_stop(void *data,
+ struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) {
+ // This space intentionally left blank
+}
+
+static void pointer_handle_axis_discrete(void *data,
+ struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) {
+ // This space intentionally left blank
+}
+
+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,
+};
+
+static void xdg_surface_handle_configure(void *data,
+ struct xdg_surface *xdg_surface, uint32_t serial) {
+ xdg_surface_ack_configure(xdg_surface, serial);
+ wl_egl_window_resize(egl_window, width, height, 0, 0);
+ draw();
+}
+
+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) {
+ width = w;
+ height = h;
+}
+
+static void xdg_toplevel_handle_close(void *data,
+ struct xdg_toplevel *xdg_toplevel) {
+ exit(EXIT_SUCCESS);
+}
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+ .configure = xdg_toplevel_handle_configure,
+ .close = xdg_toplevel_handle_close,
+};
+
+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, 1);
+ } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
+ wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
+ } else if (strcmp(interface, wl_seat_interface.name) == 0) {
+ seat = wl_registry_bind(registry, name, &wl_seat_interface, version);
+ } else if (strcmp(interface,
+ zwp_pointer_constraints_v1_interface.name) == 0) {
+ pointer_constraints = wl_registry_bind(registry, name,
+ &zwp_pointer_constraints_v1_interface, version);
+ }
+}
+
+static const struct wl_registry_listener registry_listener = {
+ .global = handle_global,
+ .global_remove = NULL,
+};
+
+int main(int argc, char **argv) {
+ if (argc != 4) {
+ goto invalid_args;
+ }
+
+ bool lock;
+ if (strcmp(argv[1], "lock") == 0) {
+ lock = true;
+ } else if (strcmp(argv[1], "confine") == 0) {
+ lock = false;
+ } else {
+ goto invalid_args;
+ }
+
+ enum zwp_pointer_constraints_v1_lifetime lifetime;
+ if (strcmp(argv[2], "oneshot") == 0) {
+ lifetime = ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT;
+ } else if (strcmp(argv[2], "persistent") == 0) {
+ lifetime = ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT;
+ } else {
+ goto invalid_args;
+ }
+
+ if (strcmp(argv[3], "no-region") == 0) {
+ region_type = REGION_TYPE_NONE;
+ } else if (strcmp(argv[3], "disjoint-region") == 0) {
+ region_type = REGION_TYPE_DISJOINT;
+ } else if (strcmp(argv[3], "joint-region") == 0) {
+ region_type = REGION_TYPE_JOINT;
+ }
+
+ struct wl_display *display = wl_display_connect(NULL);
+
+ struct wl_registry *registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, &registry_listener, NULL);
+ wl_display_dispatch(display);
+ wl_display_roundtrip(display);
+
+ struct wl_region *disjoint_region = wl_compositor_create_region(compositor);
+ wl_region_add(disjoint_region, 0, 0, 255, 256);
+ wl_region_add(disjoint_region, 257, 0, 255, 256);
+ regions[REGION_TYPE_DISJOINT] = disjoint_region;
+
+ struct wl_region *joint_region = wl_compositor_create_region(compositor);
+ wl_region_add(joint_region, 0, 0, 256, 256);
+ wl_region_add(joint_region, 256, 0, 256, 256);
+ wl_region_add(joint_region, 256, 256, 256, 256);
+ regions[REGION_TYPE_JOINT] = joint_region;
+
+ wlr_egl_init(&egl, EGL_PLATFORM_WAYLAND_EXT, display, NULL,
+ WL_SHM_FORMAT_ARGB8888);
+
+ struct wl_surface *surface = wl_compositor_create_surface(compositor);
+ struct xdg_surface *xdg_surface =
+ xdg_wm_base_get_xdg_surface(wm_base, 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);
+
+ struct wl_pointer *pointer = wl_seat_get_pointer(seat);
+ wl_pointer_add_listener(pointer, &pointer_listener, surface);
+
+ if (lock) {
+ locked_pointer = zwp_pointer_constraints_v1_lock_pointer(
+ pointer_constraints, surface, pointer,
+ regions[region_type], lifetime);
+
+ zwp_locked_pointer_v1_set_cursor_position_hint(locked_pointer,
+ wl_fixed_from_int(128), wl_fixed_from_int(128));
+ } else {
+ confined_pointer = zwp_pointer_constraints_v1_confine_pointer(
+ pointer_constraints, surface, pointer,
+ regions[region_type], lifetime);
+ }
+
+ wl_surface_commit(surface);
+
+ egl_window = wl_egl_window_create(surface, width, height);
+ egl_surface = wlr_egl_create_surface(&egl, egl_window);
+
+ wl_display_roundtrip(display);
+
+ draw();
+
+ while (wl_display_dispatch(display) != -1) {}
+
+ return EXIT_SUCCESS;
+
+invalid_args:
+ fprintf(stderr, "pointer-constraints <lock | confine> "
+ "<oneshot | persistent> "
+ "<no-region | disjoint-rejoin | joint-region>\n");
+ return EXIT_FAILURE;
+}
diff --git a/examples/pointer.c b/examples/pointer.c
new file mode 100644
index 00000000..cc58c223
--- /dev/null
+++ b/examples/pointer.c
@@ -0,0 +1,403 @@
+#define _POSIX_C_SOURCE 200112L
+#include <assert.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <wayland-server-protocol.h>
+#include <wayland-server.h>
+#include <wlr/backend.h>
+#include <wlr/backend/session.h>
+#include <wlr/render/gles2.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/types/wlr_keyboard.h>
+#include <wlr/types/wlr_list.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_xcursor_manager.h>
+#include <wlr/util/log.h>
+#include <xkbcommon/xkbcommon.h>
+
+struct sample_state {
+ struct wl_display *display;
+ struct compositor_state *compositor;
+ 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_input_device *device;
+ 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);
+}
+
+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 = wlr_backend_get_renderer(wlr_output->backend);
+ assert(renderer);
+
+ wlr_output_make_current(wlr_output, NULL);
+ wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height);
+ wlr_renderer_clear(renderer, state->clear_color);
+ wlr_output_swap_buffers(wlr_output, NULL, NULL);
+ wlr_renderer_end(renderer);
+}
+
+static void handle_cursor_motion(struct wl_listener *listener, void *data) {
+ struct sample_state *sample =
+ wl_container_of(listener, sample, cursor_motion);
+ struct wlr_event_pointer_motion *event = data;
+ wlr_cursor_move(sample->cursor, event->device, 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_event_pointer_motion_absolute *event = data;
+
+ sample->cur_x = event->x;
+ sample->cur_y = event->y;
+
+ wlr_cursor_warp_absolute(sample->cursor, event->device, 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_event_pointer_button *event = data;
+
+ float (*color)[4];
+ if (event->state == WLR_BUTTON_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_event_pointer_axis *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_event_touch_up *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->device);
+}
+
+static void handle_touch_down(struct wl_listener *listener, void *data) {
+ struct sample_state *sample = wl_container_of(listener, sample, touch_down);
+ struct wlr_event_touch_down *event = data;
+ struct touch_point *point = calloc(1, sizeof(struct touch_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->device);
+}
+
+static void handle_touch_motion(struct wl_listener *listener, void *data) {
+ struct sample_state *sample =
+ wl_container_of(listener, sample, touch_motion);
+ struct wlr_event_touch_motion *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->device);
+}
+
+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_event_tablet_tool_axis *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->device, event->x, event->y);
+ }
+}
+
+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_event_keyboard_key *event = data;
+ uint32_t keycode = event->keycode + 8;
+ const xkb_keysym_t *syms;
+ int nsyms = xkb_state_key_get_syms(keyboard->device->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);
+ }
+ }
+}
+
+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);
+}
+
+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);
+ struct sample_output *sample_output = calloc(1, sizeof(struct sample_output));
+ if (!wl_list_empty(&output->modes)) {
+ struct wlr_output_mode *mode = wl_container_of(output->modes.prev, mode, link);
+ wlr_output_set_mode(output, mode);
+ }
+ 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);
+
+ wlr_xcursor_manager_load(sample->xcursor_manager, output->scale);
+ wlr_xcursor_manager_set_cursor_image(sample->xcursor_manager, "left_ptr",
+ sample->cursor);
+}
+
+
+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);
+}
+
+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_TOOL:
+ wlr_cursor_attach_input_device(state->cursor, device);
+ break;
+
+ case WLR_INPUT_DEVICE_KEYBOARD:;
+ struct sample_keyboard *keyboard = calloc(1, sizeof(struct sample_keyboard));
+ keyboard->device = device;
+ keyboard->state = state;
+ wl_signal_add(&device->events.destroy, &keyboard->destroy);
+ keyboard->destroy.notify = keyboard_destroy_notify;
+ wl_signal_add(&device->keyboard->events.key, &keyboard->key);
+ keyboard->key.notify = keyboard_key_notify;
+ struct xkb_rule_names rules = { 0 };
+ rules.rules = getenv("XKB_DEFAULT_RULES");
+ rules.model = getenv("XKB_DEFAULT_MODEL");
+ rules.layout = getenv("XKB_DEFAULT_LAYOUT");
+ rules.variant = getenv("XKB_DEFAULT_VARIANT");
+ rules.options = getenv("XKB_DEFAULT_OPTIONS");
+ 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_map_new_from_names(context, &rules,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+ if (!keymap) {
+ wlr_log(WLR_ERROR, "Failed to create XKB keymap");
+ exit(1);
+ }
+ wlr_keyboard_set_keymap(device->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(display, NULL);
+ if (!wlr) {
+ exit(1);
+ }
+ state.cursor = wlr_cursor_create();
+ state.layout = wlr_output_layout_create();
+ 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("default", 24);
+ if (!state.xcursor_manager) {
+ wlr_log(WLR_ERROR, "Failed to load left_ptr cursor");
+ return 1;
+ }
+
+ wlr_xcursor_manager_set_cursor_image(state.xcursor_manager, "left_ptr",
+ state.cursor);
+
+ 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);
+ wlr_output_layout_destroy(state.layout);
+}
diff --git a/examples/rotation.c b/examples/rotation.c
new file mode 100644
index 00000000..7cf5727b
--- /dev/null
+++ b/examples/rotation.c
@@ -0,0 +1,277 @@
+#define _POSIX_C_SOURCE 200112L
+#include <GLES2/gl2.h>
+#include <getopt.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+#include <wayland-server-protocol.h>
+#include <wayland-server.h>
+#include <wlr/backend.h>
+#include <wlr/backend/session.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_keyboard.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/util/log.h>
+#include <xkbcommon/xkbcommon.h>
+#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_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_input_device *device;
+ 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);
+
+ wlr_output_make_current(wlr_output, NULL);
+ wlr_renderer_begin(sample->renderer, wlr_output->width, wlr_output->height);
+ wlr_renderer_clear(sample->renderer, (float[]){0.25f, 0.25f, 0.25f, 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_texture(sample->renderer, sample->cat_texture,
+ wlr_output->transform_matrix, x, y, 1.0f);
+ }
+ }
+
+ wlr_renderer_end(sample->renderer);
+ wlr_output_swap_buffers(wlr_output, NULL, NULL);
+
+ 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;
+ }
+}
+
+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);
+}
+
+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);
+ struct sample_output *sample_output = calloc(1, sizeof(struct sample_output));
+ if (!wl_list_empty(&output->modes)) {
+ struct wlr_output_mode *mode = wl_container_of(output->modes.prev, mode, link);
+ wlr_output_set_mode(output, mode);
+ }
+ sample_output->x_offs = sample_output->y_offs = 0;
+ sample_output->x_vel = sample_output->y_vel = 128;
+
+ wlr_output_set_transform(output, sample->transform);
+ 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);
+}
+
+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_event_keyboard_key *event = data;
+ uint32_t keycode = event->keycode + 8;
+ const xkb_keysym_t *syms;
+ int nsyms = xkb_state_key_get_syms(keyboard->device->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 == WLR_KEY_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;
+ }
+ }
+ }
+}
+
+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);
+}
+
+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(struct sample_keyboard));
+ keyboard->device = device;
+ keyboard->sample = sample;
+ wl_signal_add(&device->events.destroy, &keyboard->destroy);
+ keyboard->destroy.notify = keyboard_destroy_notify;
+ wl_signal_add(&device->keyboard->events.key, &keyboard->key);
+ keyboard->key.notify = keyboard_key_notify;
+ struct xkb_rule_names rules = { 0 };
+ rules.rules = getenv("XKB_DEFAULT_RULES");
+ rules.model = getenv("XKB_DEFAULT_MODEL");
+ rules.layout = getenv("XKB_DEFAULT_LAYOUT");
+ rules.variant = getenv("XKB_DEFAULT_VARIANT");
+ rules.options = getenv("XKB_DEFAULT_OPTIONS");
+ 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_map_new_from_names(context, &rules,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+ if (!keymap) {
+ wlr_log(WLR_ERROR, "Failed to create XKB keymap");
+ exit(1);
+ }
+ wlr_keyboard_set_keymap(device->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:
+ break;
+ }
+ }
+ 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(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_backend_get_renderer(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,
+ WL_SHM_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);
+ }
+
+ 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/screencopy.c b/examples/screencopy.c
new file mode 100644
index 00000000..82edcb9c
--- /dev/null
+++ b/examples/screencopy.c
@@ -0,0 +1,261 @@
+/*
+ * Copyright © 2008 Kristian Høgsberg
+ *
+ * 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.
+ */
+
+#define _POSIX_C_SOURCE 200112L
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <png.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <wayland-client-protocol.h>
+#include "wlr-screencopy-unstable-v1-client-protocol.h"
+
+struct format {
+ enum wl_shm_format wl_format;
+ bool is_bgr;
+};
+
+static struct wl_shm *shm = NULL;
+static struct zwlr_screencopy_manager_v1 *screencopy_manager = NULL;
+static struct wl_output *output = NULL;
+
+static struct {
+ struct wl_buffer *wl_buffer;
+ void *data;
+ enum wl_shm_format format;
+ int width, height, stride;
+ bool y_invert;
+} buffer;
+bool buffer_copy_done = false;
+
+// wl_shm_format describes little-endian formats, libpng uses big-endian
+// formats (so Wayland's ABGR is libpng's RGBA).
+static const struct format formats[] = {
+ {WL_SHM_FORMAT_XRGB8888, true},
+ {WL_SHM_FORMAT_ARGB8888, true},
+ {WL_SHM_FORMAT_XBGR8888, false},
+ {WL_SHM_FORMAT_ABGR8888, false},
+};
+
+static struct wl_buffer *create_shm_buffer(enum wl_shm_format fmt,
+ int width, int height, int stride, void **data_out) {
+ int size = stride * height;
+
+ const char shm_name[] = "/wlroots-screencopy";
+ int fd = shm_open(shm_name, O_RDWR | O_CREAT | O_EXCL, 0);
+ if (fd < 0) {
+ fprintf(stderr, "shm_open failed\n");
+ return NULL;
+ }
+ shm_unlink(shm_name);
+
+ int ret;
+ while ((ret = ftruncate(fd, size)) == EINTR) {
+ // No-op
+ }
+ if (ret < 0) {
+ close(fd);
+ fprintf(stderr, "ftruncate failed\n");
+ return NULL;
+ }
+
+ void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (data == MAP_FAILED) {
+ fprintf(stderr, "mmap failed: %m\n");
+ close(fd);
+ return NULL;
+ }
+
+ struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
+ close(fd);
+ struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
+ stride, fmt);
+ wl_shm_pool_destroy(pool);
+
+ *data_out = data;
+ return buffer;
+}
+
+static void frame_handle_buffer(void *data,
+ struct zwlr_screencopy_frame_v1 *frame, uint32_t format,
+ uint32_t width, uint32_t height, uint32_t stride) {
+ buffer.format = format;
+ buffer.width = width;
+ buffer.height = height;
+ buffer.stride = stride;
+ buffer.wl_buffer =
+ create_shm_buffer(format, width, height, stride, &buffer.data);
+ if (buffer.wl_buffer == NULL) {
+ fprintf(stderr, "failed to create buffer\n");
+ exit(EXIT_FAILURE);
+ }
+
+ zwlr_screencopy_frame_v1_copy(frame, buffer.wl_buffer);
+}
+
+static void frame_handle_flags(void *data,
+ struct zwlr_screencopy_frame_v1 *frame, uint32_t flags) {
+ buffer.y_invert = flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT;
+}
+
+static void frame_handle_ready(void *data,
+ struct zwlr_screencopy_frame_v1 *frame, uint32_t tv_sec_hi,
+ uint32_t tv_sec_lo, uint32_t tv_nsec) {
+ buffer_copy_done = true;
+}
+
+static void frame_handle_failed(void *data,
+ struct zwlr_screencopy_frame_v1 *frame) {
+ fprintf(stderr, "failed to copy frame\n");
+ exit(EXIT_FAILURE);
+}
+
+static const struct zwlr_screencopy_frame_v1_listener frame_listener = {
+ .buffer = frame_handle_buffer,
+ .flags = frame_handle_flags,
+ .ready = frame_handle_ready,
+ .failed = frame_handle_failed,
+};
+
+static void handle_global(void *data, struct wl_registry *registry,
+ uint32_t name, const char *interface, uint32_t version) {
+ if (strcmp(interface, wl_output_interface.name) == 0 && output == NULL) {
+ output = wl_registry_bind(registry, name, &wl_output_interface, 1);
+ } else if (strcmp(interface, wl_shm_interface.name) == 0) {
+ shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
+ } else if (strcmp(interface,
+ zwlr_screencopy_manager_v1_interface.name) == 0) {
+ screencopy_manager = wl_registry_bind(registry, name,
+ &zwlr_screencopy_manager_v1_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 write_image(char *filename, enum wl_shm_format wl_fmt, int width,
+ int height, int stride, bool y_invert, png_bytep data) {
+ const struct format *fmt = NULL;
+ for (size_t i = 0; i < sizeof(formats) / sizeof(formats[0]); ++i) {
+ if (formats[i].wl_format == wl_fmt) {
+ fmt = &formats[i];
+ break;
+ }
+ }
+ if (fmt == NULL) {
+ fprintf(stderr, "unsupported format %"PRIu32"\n", wl_fmt);
+ exit(EXIT_FAILURE);
+ }
+
+ FILE *f = fopen(filename, "wb");
+ if (f == NULL) {
+ fprintf(stderr, "failed to open output file\n");
+ exit(EXIT_FAILURE);
+ }
+
+ png_structp png =
+ png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ png_infop info = png_create_info_struct(png);
+
+ png_init_io(png, f);
+
+ png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGBA,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+
+ if (fmt->is_bgr) {
+ png_set_bgr(png);
+ }
+
+ png_write_info(png, info);
+
+ for (size_t i = 0; i < (size_t)height; ++i) {
+ png_bytep row;
+ if (y_invert) {
+ row = data + (height - i - 1) * stride;
+ } else {
+ row = data + i * stride;
+ }
+ png_write_row(png, row);
+ }
+
+ png_write_end(png, NULL);
+
+ png_destroy_write_struct(&png, &info);
+
+ fclose(f);
+}
+
+int main(int argc, char *argv[]) {
+ struct wl_display * display = wl_display_connect(NULL);
+ if (display == NULL) {
+ fprintf(stderr, "failed to create display: %m\n");
+ return EXIT_FAILURE;
+ }
+
+ struct wl_registry *registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, &registry_listener, NULL);
+ wl_display_dispatch(display);
+ wl_display_roundtrip(display);
+
+ if (shm == NULL) {
+ fprintf(stderr, "compositor is missing wl_shm\n");
+ return EXIT_FAILURE;
+ }
+ if (screencopy_manager == NULL) {
+ fprintf(stderr, "compositor doesn't support wlr-screencopy-unstable-v1\n");
+ return EXIT_FAILURE;
+ }
+ if (output == NULL) {
+ fprintf(stderr, "no output available\n");
+ return EXIT_FAILURE;
+ }
+
+ struct zwlr_screencopy_frame_v1 *frame =
+ zwlr_screencopy_manager_v1_capture_output(screencopy_manager, 0, output);
+ zwlr_screencopy_frame_v1_add_listener(frame, &frame_listener, NULL);
+
+ while (!buffer_copy_done && wl_display_dispatch(display) != -1) {
+ // This space is intentionally left blank
+ }
+
+ write_image("wayland-screenshot.png", buffer.format, buffer.width,
+ buffer.height, buffer.stride, buffer.y_invert, buffer.data);
+ wl_buffer_destroy(buffer.wl_buffer);
+
+ return EXIT_SUCCESS;
+}
diff --git a/examples/screenshot.c b/examples/screenshot.c
new file mode 100644
index 00000000..914f3994
--- /dev/null
+++ b/examples/screenshot.c
@@ -0,0 +1,237 @@
+/*
+ * Copyright © 2008 Kristian Høgsberg
+ *
+ * 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.
+ */
+
+#define _POSIX_C_SOURCE 200112L
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <wayland-client.h>
+#include "screenshooter-client-protocol.h"
+
+static struct wl_shm *shm = NULL;
+static struct orbital_screenshooter *screenshooter = NULL;
+static struct wl_list output_list;
+static bool buffer_copy_done;
+
+struct screenshooter_output {
+ struct wl_output *output;
+ int width, height;
+ struct wl_list link;
+};
+
+static void output_handle_geometry(void *data, struct wl_output *wl_output,
+ int x, int y, int physical_width, int physical_height, int subpixel,
+ const char *make, const char *model, int transform) {
+ // No-op
+}
+
+static void output_handle_mode(void *data, struct wl_output *wl_output,
+ uint32_t flags, int width, int height, int refresh) {
+ struct screenshooter_output *output = wl_output_get_user_data(wl_output);
+
+ if (wl_output == output->output && (flags & WL_OUTPUT_MODE_CURRENT)) {
+ output->width = width;
+ output->height = height;
+ }
+}
+
+static void output_handle_done(void *data, struct wl_output *wl_output) {
+ // No-op
+}
+
+static const struct wl_output_listener output_listener = {
+ .geometry = output_handle_geometry,
+ .mode = output_handle_mode,
+ .done = output_handle_done,
+};
+
+static void screenshot_done(void *data, struct orbital_screenshot *screenshot) {
+ buffer_copy_done = true;
+}
+
+static const struct orbital_screenshot_listener screenshot_listener = {
+ .done = screenshot_done,
+};
+
+static void handle_global(void *data, struct wl_registry *registry,
+ uint32_t name, const char *interface, uint32_t version) {
+ static struct screenshooter_output *output;
+
+ if (strcmp(interface, "wl_output") == 0) {
+ output = calloc(1, sizeof(*output));
+ output->output = wl_registry_bind(registry, name, &wl_output_interface,
+ 1);
+ wl_list_insert(&output_list, &output->link);
+ wl_output_add_listener(output->output, &output_listener, output);
+ } else if (strcmp(interface, "wl_shm") == 0) {
+ shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
+ } else if (strcmp(interface, "orbital_screenshooter") == 0) {
+ screenshooter = wl_registry_bind(registry, name,
+ &orbital_screenshooter_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 struct wl_buffer *create_shm_buffer(int width, int height,
+ void **data_out) {
+ int stride = width * 4;
+ int size = stride * height;
+
+ const char shm_name[] = "/wlroots-screenshot";
+ int fd = shm_open(shm_name, O_RDWR | O_CREAT | O_EXCL, 0);
+ if (fd < 0) {
+ fprintf(stderr, "shm_open failed\n");
+ return NULL;
+ }
+ shm_unlink(shm_name);
+
+ int ret;
+ while ((ret = ftruncate(fd, size)) == EINTR) {
+ // No-op
+ }
+ if (ret < 0) {
+ close(fd);
+ fprintf(stderr, "ftruncate failed\n");
+ return NULL;
+ }
+
+ void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (data == MAP_FAILED) {
+ fprintf(stderr, "mmap failed: %m\n");
+ close(fd);
+ return NULL;
+ }
+
+ struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
+ close(fd);
+ struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
+ stride, WL_SHM_FORMAT_XRGB8888);
+ wl_shm_pool_destroy(pool);
+
+ *data_out = data;
+
+ return buffer;
+}
+
+static void write_image(const char *filename, int width, int height,
+ void *data) {
+ char size[10 + 1 + 10 + 2 + 1]; // int32_t are max 10 digits
+ sprintf(size, "%dx%d+0", width, height);
+
+ int fd[2];
+ if (pipe(fd) != 0) {
+ fprintf(stderr, "cannot create pipe: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ pid_t child = fork();
+ if (child < 0) {
+ fprintf(stderr, "fork() failed\n");
+ exit(EXIT_FAILURE);
+ } else if (child != 0) {
+ close(fd[0]);
+ if (write(fd[1], data, 4 * width * height) < 0) {
+ fprintf(stderr, "write() failed: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ close(fd[1]);
+ waitpid(child, NULL, 0);
+ } else {
+ close(fd[1]);
+ if (dup2(fd[0], 0) != 0) {
+ fprintf(stderr, "cannot dup the pipe\n");
+ exit(EXIT_FAILURE);
+ }
+ close(fd[0]);
+ // We requested WL_SHM_FORMAT_XRGB8888 in little endian, so that's BGRA
+ // in big endian.
+ execlp("convert", "convert", "-depth", "8", "-size", size, "bgra:-",
+ "-alpha", "opaque", filename, NULL);
+ fprintf(stderr, "cannot execute convert\n");
+ exit(EXIT_FAILURE);
+ }
+}
+
+int main(int argc, char *argv[]) {
+ struct wl_display * display = wl_display_connect(NULL);
+ if (display == NULL) {
+ fprintf(stderr, "failed to create display: %m\n");
+ return -1;
+ }
+
+ wl_list_init(&output_list);
+ struct wl_registry *registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, &registry_listener, NULL);
+ wl_display_dispatch(display);
+ wl_display_roundtrip(display);
+
+ if (screenshooter == NULL) {
+ fprintf(stderr, "display doesn't support screenshooter\n");
+ return -1;
+ }
+
+ int i = 0;
+ struct screenshooter_output *output;
+ wl_list_for_each(output, &output_list, link) {
+ void *data = NULL;
+ struct wl_buffer *buffer =
+ create_shm_buffer(output->width, output->height, &data);
+ if (buffer == NULL) {
+ return -1;
+ }
+ struct orbital_screenshot *screenshot = orbital_screenshooter_shoot(
+ screenshooter, output->output, buffer);
+ orbital_screenshot_add_listener(screenshot, &screenshot_listener,
+ screenshot);
+ buffer_copy_done = false;
+ while (!buffer_copy_done) {
+ wl_display_roundtrip(display);
+ }
+
+ char filename[24 + 10]; // int32_t are max 10 digits
+ snprintf(filename, sizeof(filename), "wayland-screenshot-%d.png", i);
+
+ write_image(filename, output->width, output->height, data);
+ wl_buffer_destroy(buffer);
+ ++i;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/examples/simple.c b/examples/simple.c
new file mode 100644
index 00000000..e1c10906
--- /dev/null
+++ b/examples/simple.c
@@ -0,0 +1,185 @@
+#define _POSIX_C_SOURCE 200112L
+#include <GLES2/gl2.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wlr/backend.h>
+#include <wlr/backend/session.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/util/log.h>
+#include <xkbcommon/xkbcommon.h>
+
+struct sample_state {
+ struct wl_display *display;
+ struct wl_listener new_output;
+ struct wl_listener new_input;
+ struct timespec last_frame;
+ float color[3];
+ 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_input_device *device;
+ struct wl_listener key;
+ struct wl_listener destroy;
+};
+
+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);
+
+ 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;
+ }
+
+ wlr_output_make_current(sample_output->output, NULL);
+
+ glClearColor(sample->color[0], sample->color[1], sample->color[2], 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ wlr_output_swap_buffers(sample_output->output, NULL, NULL);
+ sample->last_frame = now;
+}
+
+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);
+}
+
+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);
+ struct sample_output *sample_output =
+ calloc(1, sizeof(struct sample_output));
+ if (!wl_list_empty(&output->modes)) {
+ struct wlr_output_mode *mode =
+ wl_container_of(output->modes.prev, mode, link);
+ wlr_output_set_mode(output, mode);
+ }
+ 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;
+}
+
+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_event_keyboard_key *event = data;
+ uint32_t keycode = event->keycode + 8;
+ const xkb_keysym_t *syms;
+ int nsyms = xkb_state_key_get_syms(keyboard->device->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);
+ }
+ }
+}
+
+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);
+}
+
+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(struct sample_keyboard));
+ keyboard->device = device;
+ keyboard->sample = sample;
+ wl_signal_add(&device->events.destroy, &keyboard->destroy);
+ keyboard->destroy.notify = keyboard_destroy_notify;
+ wl_signal_add(&device->keyboard->events.key, &keyboard->key);
+ keyboard->key.notify = keyboard_key_notify;
+ struct xkb_rule_names rules = { 0 };
+ rules.rules = getenv("XKB_DEFAULT_RULES");
+ rules.model = getenv("XKB_DEFAULT_MODEL");
+ rules.layout = getenv("XKB_DEFAULT_LAYOUT");
+ rules.variant = getenv("XKB_DEFAULT_VARIANT");
+ rules.options = getenv("XKB_DEFAULT_OPTIONS");
+ 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_map_new_from_names(context, &rules,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+ if (!keymap) {
+ wlr_log(WLR_ERROR, "Failed to create XKB keymap");
+ exit(1);
+ }
+ wlr_keyboard_set_keymap(device->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 },
+ .dec = 0,
+ .last_frame = { 0 },
+ .display = display
+ };
+ struct wlr_backend *backend = wlr_backend_autocreate(display, NULL);
+ if (!backend) {
+ exit(1);
+ }
+ 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 00000000..fad30d52
--- /dev/null
+++ b/examples/tablet.c
@@ -0,0 +1,381 @@
+#define _XOPEN_SOURCE 600
+#include <GLES2/gl2.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <wayland-server-protocol.h>
+#include <wayland-server.h>
+#include <wlr/backend.h>
+#include <wlr/backend/session.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_tablet_pad.h>
+#include <wlr/types/wlr_tablet_tool.h>
+#include <wlr/util/log.h>
+#include <xkbcommon/xkbcommon.h>
+
+struct sample_state {
+ struct wl_display *display;
+ struct wlr_renderer *renderer;
+ 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_input_device *device;
+ 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_input_device *device;
+ 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_input_device *device;
+ 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);
+
+ wlr_output_make_current(wlr_output, NULL);
+ wlr_renderer_begin(sample->renderer, wlr_output->width, wlr_output->height);
+ wlr_renderer_clear(sample->renderer, (float[]){0.25f, 0.25f, 0.25f, 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;
+ const struct wlr_box box = {
+ .x = left, .y = top,
+ .width = pad_width, .height = pad_height,
+ };
+ wlr_render_rect(sample->renderer, &box, sample->pad_color,
+ wlr_output->transform_matrix);
+
+ 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),
+ };
+ float matrix[9];
+ wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL,
+ sample->ring, wlr_output->transform_matrix);
+ wlr_render_quad_with_matrix(sample->renderer, tool_color, matrix);
+
+ box.x += sample->x_tilt;
+ box.y += sample->y_tilt;
+ box.width /= 2;
+ box.height /= 2;
+ wlr_render_rect(sample->renderer, &box, tool_color,
+ wlr_output->transform_matrix);
+ }
+
+ wlr_renderer_end(sample->renderer);
+ wlr_output_swap_buffers(wlr_output, NULL, NULL);
+ 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_event_tablet_tool_axis *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_event_tablet_tool_proximity *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_event_tablet_tool_button *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_event_tablet_pad_button *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_event_tablet_pad_ring *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);
+}
+
+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);
+}
+
+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);
+ struct sample_output *sample_output = calloc(1, sizeof(struct sample_output));
+ if (!wl_list_empty(&output->modes)) {
+ struct wlr_output_mode *mode = wl_container_of(output->modes.prev, mode, link);
+ wlr_output_set_mode(output, mode);
+ }
+ 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;
+}
+
+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_event_keyboard_key *event = data;
+ uint32_t keycode = event->keycode + 8;
+ const xkb_keysym_t *syms;
+ int nsyms = xkb_state_key_get_syms(keyboard->device->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);
+ }
+ }
+}
+
+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);
+}
+
+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(struct sample_keyboard));
+ keyboard->device = device;
+ keyboard->sample = sample;
+ wl_signal_add(&device->events.destroy, &keyboard->destroy);
+ keyboard->destroy.notify = keyboard_destroy_notify;
+ wl_signal_add(&device->keyboard->events.key, &keyboard->key);
+ keyboard->key.notify = keyboard_key_notify;
+ struct xkb_rule_names rules = { 0 };
+ rules.rules = getenv("XKB_DEFAULT_RULES");
+ rules.model = getenv("XKB_DEFAULT_MODEL");
+ rules.layout = getenv("XKB_DEFAULT_LAYOUT");
+ rules.variant = getenv("XKB_DEFAULT_VARIANT");
+ rules.options = getenv("XKB_DEFAULT_OPTIONS");
+ 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_map_new_from_names(context, &rules,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+ if (!keymap) {
+ wlr_log(WLR_ERROR, "Failed to create XKB keymap");
+ exit(1);
+ }
+ wlr_keyboard_set_keymap(device->keyboard, keymap);
+ xkb_keymap_unref(keymap);
+ xkb_context_unref(context);
+ break;
+ case WLR_INPUT_DEVICE_TABLET_PAD:;
+ struct tablet_pad_state *pstate = calloc(sizeof(struct tablet_pad_state), 1);
+ pstate->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(&device->tablet_pad->events.button, &pstate->button);
+ pstate->ring.notify = tablet_pad_ring_notify;
+ wl_signal_add(&device->tablet_pad->events.ring, &pstate->ring);
+ wl_list_insert(&sample->tablet_pads, &pstate->link);
+ break;
+ case WLR_INPUT_DEVICE_TABLET_TOOL:
+ sample->width_mm = device->width_mm == 0 ?
+ 20 : device->width_mm;
+ sample->height_mm = device->height_mm == 0 ?
+ 10 : device->height_mm;
+
+ struct tablet_tool_state *tstate = calloc(sizeof(struct tablet_tool_state), 1);
+ tstate->device = device;
+ 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(&device->tablet->events.axis, &tstate->axis);
+ tstate->proximity.notify = tablet_tool_proximity_notify;
+ wl_signal_add(&device->tablet->events.proximity, &tstate->proximity);
+ tstate->button.notify = tablet_tool_button_notify;
+ wl_signal_add(&device->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(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_backend_get_renderer(wlr);
+ if (!state.renderer) {
+ wlr_log(WLR_ERROR, "Could not start compositor, OOM");
+ exit(EXIT_FAILURE);
+ }
+ 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/text-input.c b/examples/text-input.c
new file mode 100644
index 00000000..3ccd99a0
--- /dev/null
+++ b/examples/text-input.c
@@ -0,0 +1,394 @@
+#define _POSIX_C_SOURCE 200809L
+#include <GLES2/gl2.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wayland-client.h>
+#include <wayland-egl.h>
+#include <wlr/render/egl.h>
+#include "text-input-unstable-v3-client-protocol.h"
+#include "xdg-shell-client-protocol.h"
+
+const char usage[] = "Usage: text-input [seconds [width height]]\n\
+\n\
+Creates a xdg-toplevel using the text-input protocol.\n\
+It will be solid black when it has no text input focus, yellow when it\n\
+has focus, and red when it was notified that the focus moved away\n\
+but still didn't give up the text input ability.\n\
+\n\
+The \"seconds\" argument is optional and defines the delay between getting\n\
+notified of lost focus and releasing text input.\n\
+\n\
+The \"width\" and \"height\" arguments define the window shape.\n\
+\n\
+The console will print the internal state of the text field:\n\
+- the text in the 1st line\n\
+- \".\" under each preedit character\n\
+- \"_\" under each selected preedit character\n\
+- \"|\" at the cursor position if there are no selected characters in the\n\
+preedit.\n\
+\n\
+The cursor positions may be inaccurate, especially in presence of zero-width\n\
+characters or non-monospaced fonts.\n";
+
+struct text_input_state {
+ char *commit;
+ struct {
+ char *text;
+ int32_t cursor_begin;
+ int32_t cursor_end;
+ } preedit;
+ struct {
+ uint32_t after_length;
+ uint32_t before_length;
+ } delete_surrounding;
+};
+
+static struct text_input_state pending = {0};
+static struct text_input_state current = {0};
+static bool entered = false;
+static uint32_t serial;
+static char *buffer; // text buffer
+// cursor is not present, there's no way to move it outside of preedit
+
+static int sleeptime = 0;
+static int width = 100, height = 200;
+static int enabled = 0;
+
+static struct wl_display *display = NULL;
+static struct wl_compositor *compositor = NULL;
+static struct wl_seat *seat = NULL;
+static struct xdg_wm_base *wm_base = NULL;
+static struct zwp_text_input_manager_v3 *text_input_manager = NULL;
+static struct zwp_text_input_v3 *text_input = NULL;
+
+struct wlr_egl egl;
+struct wl_egl_window *egl_window;
+struct wlr_egl_surface *egl_surface;
+
+static void draw(void) {
+ eglMakeCurrent(egl.display, egl_surface, egl_surface, egl.context);
+
+ float color[] = {1.0, 1.0, 0.0, 1.0};
+ color[0] = enabled * 1.0;
+ color[1] = entered * 1.0;
+
+ glViewport(0, 0, width, height);
+ glClearColor(color[0], color[1], color[2], 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ eglSwapBuffers(egl.display, egl_surface);
+}
+
+static size_t utf8_strlen(char *str) {
+ size_t cp_count = 0;
+ for (; *str != '\0'; str++) {
+ if ((*str & 0xc0) != 0x80) {
+ cp_count++;
+ }
+ }
+ return cp_count;
+}
+
+static size_t utf8_offset(char *utf8_str, size_t byte_offset) {
+ size_t cp_count = 0;
+ for (char *c = utf8_str; c < utf8_str + byte_offset; c++) {
+ if ((*c & 0xc0) != 0x80) {
+ cp_count++;
+ }
+ }
+ return cp_count;
+}
+
+// TODO: would be nicer to have this text display inside the window
+static void show_status() {
+ printf("State %d:", serial);
+ if (!enabled) {
+ printf(" disabled");
+ }
+
+ char *preedit_text = current.preedit.text;
+ if (!preedit_text) {
+ preedit_text = "";
+ }
+
+ printf("\n");
+ printf("%s", buffer);
+ printf("%s\n", preedit_text);
+
+ // Positioning of the cursor requires UTF8 offsets to match monospaced
+ // glyphs
+ for (unsigned i = 0; i < utf8_strlen(buffer); i++) {
+ printf(" ");
+ }
+ char *cursor_mark = calloc(utf8_strlen(preedit_text) + 2, sizeof(char));
+ for (unsigned i = 0; i < utf8_strlen(preedit_text); i++) {
+ cursor_mark[i] = '.';
+ }
+ if (current.preedit.cursor_begin == -1
+ && current.preedit.cursor_end == -1) {
+ goto end;
+ }
+ if (current.preedit.cursor_begin == -1
+ || current.preedit.cursor_end == -1) {
+ printf("Only one cursor side is defined: %d to %d\n",
+ current.preedit.cursor_begin, current.preedit.cursor_end);
+ goto end;
+ }
+
+ if ((unsigned)current.preedit.cursor_begin > strlen(preedit_text)
+ || (unsigned)current.preedit.cursor_begin > strlen(preedit_text)) {
+ printf("Cursor out of bounds\n");
+ goto end;
+ }
+
+ if (current.preedit.cursor_begin == current.preedit.cursor_end) {
+ cursor_mark[utf8_offset(preedit_text, current.preedit.cursor_begin)]
+ = '|';
+ goto print;
+ }
+
+ if (current.preedit.cursor_begin > current.preedit.cursor_end) {
+ printf("End cursor is before start cursor\n");
+ goto end;
+ }
+
+ // negative offsets already checked before
+ for (unsigned i = utf8_offset(preedit_text, current.preedit.cursor_begin);
+ i < utf8_offset(preedit_text, current.preedit.cursor_end); i++) {
+ cursor_mark[i] = '_';
+ }
+print:
+ printf("%s\n", cursor_mark);
+end:
+ free(cursor_mark);
+}
+
+static void commit(struct zwp_text_input_v3 *text_input) {
+ zwp_text_input_v3_commit(text_input);
+ serial++;
+}
+
+static void send_status_update(struct zwp_text_input_v3 *text_input) {
+ zwp_text_input_v3_set_surrounding_text(text_input, buffer, strlen(buffer), strlen(buffer));
+ zwp_text_input_v3_set_text_change_cause(text_input, ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD);
+ commit(text_input);
+}
+
+static void text_input_handle_enter(void *data,
+ struct zwp_text_input_v3 *zwp_text_input_v3,
+ struct wl_surface *surface) {
+ entered = true;
+ zwp_text_input_v3_enable(zwp_text_input_v3);
+ commit(zwp_text_input_v3);
+ enabled = true;
+ draw();
+ show_status();
+}
+
+static void text_input_handle_leave(void *data,
+ struct zwp_text_input_v3 *zwp_text_input_v3,
+ struct wl_surface *surface) {
+ entered = false;
+ draw();
+ wl_display_roundtrip(display);
+ sleep(sleeptime);
+ zwp_text_input_v3_disable(zwp_text_input_v3);
+ commit(zwp_text_input_v3);
+ enabled = false;
+ draw();
+ show_status();
+}
+
+static void text_input_commit_string(void *data,
+ struct zwp_text_input_v3 *zwp_text_input_v3,
+ const char *text) {
+ free(pending.commit);
+ pending.commit = strdup(text);
+}
+
+static void text_input_delete_surrounding_text(void *data,
+ struct zwp_text_input_v3 *zwp_text_input_v3,
+ uint32_t before_length, uint32_t after_length) {
+ pending.delete_surrounding.before_length = before_length;
+ pending.delete_surrounding.after_length = after_length;
+}
+
+static void text_input_preedit_string(void *data,
+ struct zwp_text_input_v3 *zwp_text_input_v3,
+ const char *text, int32_t cursor_begin, int32_t cursor_end) {
+ free(pending.preedit.text);
+ pending.preedit.text = strdup(text);
+ pending.preedit.cursor_begin = cursor_begin;
+ pending.preedit.cursor_end = cursor_end;
+}
+
+static void text_input_handle_done(void *data,
+ struct zwp_text_input_v3 *zwp_text_input_v3,
+ uint32_t incoming_serial) {
+ if (serial != incoming_serial) {
+ fprintf(stderr, "Received serial %d while expecting %d\n", incoming_serial, serial);
+ return;
+ }
+ free(current.preedit.text);
+ free(current.commit);
+ current = pending;
+ struct text_input_state empty = {0};
+ pending = empty;
+
+ if (current.delete_surrounding.after_length + current.delete_surrounding.before_length > 0) {
+ // cursor is always after committed text, after_length != 0 will never happen
+ unsigned delete_before = current.delete_surrounding.before_length;
+ if (delete_before > strlen(buffer)) {
+ delete_before = strlen(buffer);
+ }
+ buffer[strlen(buffer) - delete_before] = '\0';
+ }
+
+ char *commit_string = current.commit;
+ if (!commit_string) {
+ commit_string = "";
+ }
+ char *old_buffer = buffer;
+ buffer = calloc(strlen(buffer) + strlen(commit_string) + 1, sizeof(char)); // realloc may fail anyway
+ strcpy(buffer, old_buffer);
+ free(old_buffer);
+ strcat(buffer, commit_string);
+
+ send_status_update(zwp_text_input_v3);
+ show_status();
+}
+
+static const struct zwp_text_input_v3_listener text_input_listener = {
+ .enter = text_input_handle_enter,
+ .leave = text_input_handle_leave,
+ .commit_string = text_input_commit_string,
+ .delete_surrounding_text = text_input_delete_surrounding_text,
+ .preedit_string = text_input_preedit_string,
+ .done = text_input_handle_done,
+};
+
+static void xdg_surface_handle_configure(void *data,
+ struct xdg_surface *xdg_surface, uint32_t serial) {
+ xdg_surface_ack_configure(xdg_surface, serial);
+ wl_egl_window_resize(egl_window, width, height, 0, 0);
+ draw();
+}
+
+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) {
+ width = w;
+ height = h;
+}
+
+static void xdg_toplevel_handle_close(void *data,
+ struct xdg_toplevel *xdg_toplevel) {
+ exit(EXIT_SUCCESS);
+}
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+ .configure = xdg_toplevel_handle_configure,
+ .close = xdg_toplevel_handle_close,
+};
+
+static void handle_global(void *data, struct wl_registry *registry,
+ uint32_t name, const char *interface, uint32_t version) {
+ if (strcmp(interface, "wl_compositor") == 0) {
+ compositor = wl_registry_bind(registry, name,
+ &wl_compositor_interface, 1);
+ } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
+ wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
+ } else if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) {
+ text_input_manager = wl_registry_bind(registry, name,
+ &zwp_text_input_manager_v3_interface, 1);
+ } else if (strcmp(interface, wl_seat_interface.name) == 0) {
+ seat = wl_registry_bind(registry, name, &wl_seat_interface, version);
+ }
+}
+
+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,
+};
+
+int main(int argc, char **argv) {
+ if (argc > 1) {
+ if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) {
+ printf(usage);
+ return 0;
+ }
+ sleeptime = atoi(argv[1]);
+ if (argc > 3) {
+ width = atoi(argv[2]);
+ height = atoi(argv[3]);
+ }
+ }
+
+ buffer = calloc(1, sizeof(char));
+
+ display = wl_display_connect(NULL);
+ if (display == NULL) {
+ fprintf(stderr, "Failed to create display\n");
+ return EXIT_FAILURE;
+ }
+
+ struct wl_registry *registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, &registry_listener, NULL);
+ wl_display_dispatch(display);
+ wl_display_roundtrip(display);
+
+ if (compositor == NULL) {
+ fprintf(stderr, "wl-compositor not available\n");
+ return EXIT_FAILURE;
+ }
+ if (wm_base == NULL) {
+ fprintf(stderr, "xdg-shell not available\n");
+ return EXIT_FAILURE;
+ }
+ if (text_input_manager == NULL) {
+ fprintf(stderr, "text-input not available\n");
+ return EXIT_FAILURE;
+ }
+
+ text_input = zwp_text_input_manager_v3_get_text_input(text_input_manager, seat);
+
+ zwp_text_input_v3_add_listener(text_input, &text_input_listener, NULL);
+
+
+ wlr_egl_init(&egl, EGL_PLATFORM_WAYLAND_EXT, display, NULL,
+ WL_SHM_FORMAT_ARGB8888);
+
+ struct wl_surface *surface = wl_compositor_create_surface(compositor);
+ struct xdg_surface *xdg_surface =
+ xdg_wm_base_get_xdg_surface(wm_base, 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);
+
+ wl_surface_commit(surface);
+
+ egl_window = wl_egl_window_create(surface, width, height);
+ egl_surface = wlr_egl_create_surface(&egl, egl_window);
+
+ wl_display_roundtrip(display);
+
+ draw();
+
+ while (wl_display_dispatch(display) != -1) {
+ // This space intentionally left blank
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/examples/toplevel-decoration.c b/examples/toplevel-decoration.c
new file mode 100644
index 00000000..e930c417
--- /dev/null
+++ b/examples/toplevel-decoration.c
@@ -0,0 +1,253 @@
+#include <GLES2/gl2.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-client.h>
+#include <wayland-egl.h>
+#include <wlr/render/egl.h>
+#include "xdg-shell-client-protocol.h"
+#include "xdg-decoration-unstable-v1-client-protocol.h"
+
+/**
+ * Usage: toplevel-decoration [mode]
+ * Creates an xdg-toplevel supporting decoration negotiation. If `mode` is
+ * specified, the client will prefer this decoration mode.
+ */
+
+static int width = 500, height = 300;
+
+static struct wl_compositor *compositor = NULL;
+static struct xdg_wm_base *wm_base = NULL;
+static struct zxdg_decoration_manager_v1 *decoration_manager = NULL;
+
+struct wlr_egl egl;
+struct wl_egl_window *egl_window;
+struct wlr_egl_surface *egl_surface;
+
+struct zxdg_toplevel_decoration_v1 *decoration;
+enum zxdg_toplevel_decoration_v1_mode client_preferred_mode, current_mode;
+
+static const char *get_mode_name(enum zxdg_toplevel_decoration_v1_mode mode) {
+ switch (mode) {
+ case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:
+ return "client-side decorations";
+ case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:
+ return "server-side decorations";
+ }
+ abort();
+}
+
+static void request_preferred_mode(void) {
+ enum zxdg_toplevel_decoration_v1_mode mode = client_preferred_mode;
+ if (mode == 0) {
+ printf("Requesting compositor preferred mode\n");
+ zxdg_toplevel_decoration_v1_unset_mode(decoration);
+ return;
+ }
+ if (mode == current_mode) {
+ return;
+ }
+
+ printf("Requesting %s\n", get_mode_name(mode));
+ zxdg_toplevel_decoration_v1_set_mode(decoration, mode);
+}
+
+static void draw(void) {
+ eglMakeCurrent(egl.display, egl_surface, egl_surface, egl.context);
+
+ float color[] = {1.0, 1.0, 0.0, 1.0};
+ if (current_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE) {
+ color[0] = 0.0;
+ }
+
+ glViewport(0, 0, width, height);
+ glClearColor(color[0], color[1], color[2], 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ eglSwapBuffers(egl.display, egl_surface);
+}
+
+static void xdg_surface_handle_configure(void *data,
+ struct xdg_surface *xdg_surface, uint32_t serial) {
+ xdg_surface_ack_configure(xdg_surface, serial);
+ wl_egl_window_resize(egl_window, width, height, 0, 0);
+ draw();
+}
+
+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) {
+ width = w;
+ height = h;
+}
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+ .configure = xdg_toplevel_handle_configure,
+};
+
+static void decoration_handle_configure(void *data,
+ struct zxdg_toplevel_decoration_v1 *decoration,
+ enum zxdg_toplevel_decoration_v1_mode mode) {
+ printf("Using %s\n", get_mode_name(mode));
+ current_mode = mode;
+}
+
+static const struct zxdg_toplevel_decoration_v1_listener decoration_listener = {
+ .configure = decoration_handle_configure,
+};
+
+static void pointer_handle_enter(void *data, struct wl_pointer *pointer,
+ uint32_t serial, struct wl_surface *surface,
+ wl_fixed_t sx, wl_fixed_t sy) {
+ // No-op
+}
+
+static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
+ uint32_t serial, struct wl_surface *surface) {
+ // No-op
+}
+
+static void pointer_handle_motion(void *data, struct wl_pointer *pointer,
+ uint32_t time, wl_fixed_t sx, wl_fixed_t sy) {
+ // No-op
+}
+
+static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer,
+ uint32_t serial, uint32_t time, uint32_t button,
+ enum wl_pointer_button_state state) {
+ if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
+ // Toggle mode
+ if (client_preferred_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE) {
+ client_preferred_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
+ } else {
+ client_preferred_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
+ }
+ request_preferred_mode();
+ }
+}
+
+static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer,
+ uint32_t time, enum wl_pointer_axis axis, wl_fixed_t value) {
+ // No-op
+}
+
+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,
+};
+
+static void seat_handle_capabilities(void *data, struct wl_seat *seat,
+ enum wl_seat_capability caps) {
+ if (caps & WL_SEAT_CAPABILITY_POINTER) {
+ struct wl_pointer *pointer = wl_seat_get_pointer(seat);
+ wl_pointer_add_listener(pointer, &pointer_listener, NULL);
+ }
+}
+
+static const struct wl_seat_listener seat_listener = {
+ .capabilities = seat_handle_capabilities,
+};
+
+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,
+ 1);
+ } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
+ wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
+ } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {
+ decoration_manager = wl_registry_bind(registry, name,
+ &zxdg_decoration_manager_v1_interface, 1);
+ } else if (strcmp(interface, wl_seat_interface.name) == 0) {
+ struct wl_seat *seat =
+ wl_registry_bind(registry, name, &wl_seat_interface, 1);
+ wl_seat_add_listener(seat, &seat_listener, NULL);
+ }
+}
+
+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,
+};
+
+int main(int argc, char **argv) {
+ if (argc == 2) {
+ char *mode = argv[1];
+ if (strcmp(mode, "client") == 0) {
+ client_preferred_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
+ } else if (strcmp(mode, "server") == 0) {
+ client_preferred_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
+ } else {
+ fprintf(stderr, "Invalid decoration mode\n");
+ return EXIT_FAILURE;
+ }
+ }
+
+ struct wl_display *display = wl_display_connect(NULL);
+ if (display == NULL) {
+ fprintf(stderr, "Failed to create display\n");
+ return EXIT_FAILURE;
+ }
+
+ struct wl_registry *registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, &registry_listener, NULL);
+ wl_display_dispatch(display);
+ wl_display_roundtrip(display);
+
+ if (compositor == NULL) {
+ fprintf(stderr, "wl-compositor not available\n");
+ return EXIT_FAILURE;
+ }
+ if (wm_base == NULL) {
+ fprintf(stderr, "xdg-shell not available\n");
+ return EXIT_FAILURE;
+ }
+ if (decoration_manager == NULL) {
+ fprintf(stderr, "xdg-decoration not available\n");
+ return EXIT_FAILURE;
+ }
+
+ wlr_egl_init(&egl, EGL_PLATFORM_WAYLAND_EXT, display, NULL,
+ WL_SHM_FORMAT_ARGB8888);
+
+ struct wl_surface *surface = wl_compositor_create_surface(compositor);
+ struct xdg_surface *xdg_surface =
+ xdg_wm_base_get_xdg_surface(wm_base, surface);
+ struct xdg_toplevel *xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
+ decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(
+ decoration_manager, xdg_toplevel);
+
+ wl_display_roundtrip(display);
+ request_preferred_mode();
+
+ xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
+ xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL);
+ zxdg_toplevel_decoration_v1_add_listener(decoration, &decoration_listener,
+ NULL);
+ wl_surface_commit(surface);
+
+ egl_window = wl_egl_window_create(surface, width, height);
+ egl_surface = wlr_egl_create_surface(&egl, egl_window);
+
+ wl_display_roundtrip(display);
+
+ draw();
+
+ while (wl_display_dispatch(display) != -1) {
+ // This space is intentionally left blank
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/examples/touch.c b/examples/touch.c
new file mode 100644
index 00000000..9ed20a28
--- /dev/null
+++ b/examples/touch.c
@@ -0,0 +1,286 @@
+#define _POSIX_C_SOURCE 200112L
+#include <GLES2/gl2.h>
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <wayland-server-protocol.h>
+#include <wayland-server.h>
+#include <wlr/backend.h>
+#include <wlr/backend/session.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_list.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/util/log.h>
+#include <xkbcommon/xkbcommon.h>
+#include "cat.h"
+
+struct sample_state {
+ struct wl_display *display;
+ struct wlr_renderer *renderer;
+ 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_input_device *device;
+ struct wl_listener destroy;
+ struct wl_listener down;
+ struct wl_listener up;
+ struct wl_listener motion;
+ 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_input_device *device;
+ 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);
+
+ wlr_output_make_current(wlr_output, NULL);
+ wlr_renderer_begin(sample->renderer, wlr_output->width, wlr_output->height);
+ wlr_renderer_clear(sample->renderer, (float[]){0.25f, 0.25f, 0.25f, 1});
+
+ int tex_width, tex_height;
+ wlr_texture_get_size(sample->cat_texture, &tex_width, &tex_height);
+
+ struct touch_point *p;
+ wl_list_for_each(p, &sample->touch_points, link) {
+ int x = (int)(p->x * width) - tex_width / 2;
+ int y = (int)(p->y * height) - tex_height / 2;
+ wlr_render_texture(sample->renderer, sample->cat_texture,
+ wlr_output->transform_matrix, x, y, 1.0f);
+ }
+
+ wlr_renderer_end(sample->renderer);
+ wlr_output_swap_buffers(wlr_output, NULL, NULL);
+ sample->last_frame = now;
+}
+
+static void touch_down_notify(struct wl_listener *listener, void *data) {
+ struct wlr_event_touch_motion *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(struct touch_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_event_touch_up *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_event_touch_motion *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_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);
+ free(tstate);
+}
+
+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);
+}
+
+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);
+ struct sample_output *sample_output = calloc(1, sizeof(struct sample_output));
+ if (!wl_list_empty(&output->modes)) {
+ struct wlr_output_mode *mode = wl_container_of(output->modes.prev, mode, link);
+ wlr_output_set_mode(output, mode);
+ }
+ 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;
+}
+
+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_event_keyboard_key *event = data;
+ uint32_t keycode = event->keycode + 8;
+ const xkb_keysym_t *syms;
+ int nsyms = xkb_state_key_get_syms(keyboard->device->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);
+ }
+ }
+}
+
+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);
+}
+
+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(struct sample_keyboard));
+ keyboard->device = device;
+ keyboard->sample = sample;
+ wl_signal_add(&device->events.destroy, &keyboard->destroy);
+ keyboard->destroy.notify = keyboard_destroy_notify;
+ wl_signal_add(&device->keyboard->events.key, &keyboard->key);
+ keyboard->key.notify = keyboard_key_notify;
+ struct xkb_rule_names rules = { 0 };
+ rules.rules = getenv("XKB_DEFAULT_RULES");
+ rules.model = getenv("XKB_DEFAULT_MODEL");
+ rules.layout = getenv("XKB_DEFAULT_LAYOUT");
+ rules.variant = getenv("XKB_DEFAULT_VARIANT");
+ rules.options = getenv("XKB_DEFAULT_OPTIONS");
+ 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_map_new_from_names(context, &rules,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+ if (!keymap) {
+ wlr_log(WLR_ERROR, "Failed to create XKB keymap");
+ exit(1);
+ }
+ wlr_keyboard_set_keymap(device->keyboard, keymap);
+ xkb_keymap_unref(keymap);
+ xkb_context_unref(context);
+ break;
+ case WLR_INPUT_DEVICE_TOUCH:;
+ struct touch_state *tstate = calloc(sizeof(struct touch_state), 1);
+ tstate->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(&device->touch->events.down, &tstate->down);
+ tstate->motion.notify = touch_motion_notify;
+ wl_signal_add(&device->touch->events.motion, &tstate->motion);
+ tstate->up.notify = touch_up_notify;
+ wl_signal_add(&device->touch->events.up, &tstate->up);
+ 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(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_backend_get_renderer(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,
+ WL_SHM_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);
+ }
+
+ 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/glgen.sh b/glgen.sh
new file mode 100755
index 00000000..fb3bb3c6
--- /dev/null
+++ b/glgen.sh
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+# Generates a simple GL/EGL extension function loader.
+#
+# The input is a .txt file, with each function to load on its own line.
+# If a line starts with a -, it is optional, and will not cause the loader
+# to fail if it can't load the function. You'll need to check if that function
+# is NULL before using it.
+
+if [ $# -ne 2 ]; then
+ exit 1
+fi
+
+SPEC=$1
+OUTDIR=$2
+
+BASE=$(basename "$SPEC" .txt)
+INCLUDE_GUARD=$(printf %s_%s_H "$OUTDIR" "$BASE" | tr -c [:alnum:] _ | tr [:lower:] [:upper:])
+
+DECL=""
+DEFN=""
+LOADER=""
+
+DECL_FMT='extern %s %s;'
+DEFN_FMT='%s %s;'
+LOADER_FMT='%s = (%s)eglGetProcAddress("%s");'
+CHECK_FMT='if (!%s) {
+ wlr_log(WLR_ERROR, "Unable to load %s");
+ return false;
+}'
+
+while read -r COMMAND; do
+ OPTIONAL=0
+ FUNC_PTR_FMT='PFN%sPROC'
+
+ case $COMMAND in
+ -*)
+ OPTIONAL=1
+ ;;
+ esac
+
+ case $COMMAND in
+ *WL)
+ FUNC_PTR_FMT='PFN%s'
+ ;;
+ esac
+
+ COMMAND=${COMMAND#-}
+ FUNC_PTR=$(printf "$FUNC_PTR_FMT" "$COMMAND" | tr [:lower:] [:upper:])
+
+ DECL="$DECL$(printf "\n$DECL_FMT" "$FUNC_PTR" "$COMMAND")"
+ DEFN="$DEFN$(printf "\n$DEFN_FMT" "$FUNC_PTR" "$COMMAND")"
+ LOADER="$LOADER$(printf "\n$LOADER_FMT" "$COMMAND" "$FUNC_PTR" "$COMMAND")"
+
+ if [ $OPTIONAL -eq 0 ]; then
+ LOADER="$LOADER$(printf "\n$CHECK_FMT" "$COMMAND" "$COMMAND")"
+ fi
+done < "$SPEC"
+
+cat > "$OUTDIR/$BASE.h" << EOF
+#ifndef $INCLUDE_GUARD
+#define $INCLUDE_GUARD
+
+#include <stdbool.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+bool load_$BASE(void);
+$DECL
+
+#endif
+EOF
+
+cat > "$OUTDIR/$BASE.c" << EOF
+#include <wlr/util/log.h>
+#include "$BASE.h"
+$DEFN
+
+bool load_$BASE(void) {
+ static bool done = false;
+ if (done) {
+ return true;
+ }
+$LOADER
+
+ done = true;
+ return true;
+}
+EOF
diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h
new file mode 100644
index 00000000..de5212d3
--- /dev/null
+++ b/include/backend/drm/drm.h
@@ -0,0 +1,162 @@
+#ifndef BACKEND_DRM_DRM_H
+#define BACKEND_DRM_DRM_H
+
+#include <EGL/egl.h>
+#include <gbm.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wayland-util.h>
+#include <wlr/backend/drm.h>
+#include <wlr/backend/session.h>
+#include <wlr/render/egl.h>
+#include <xf86drmMode.h>
+#include "iface.h"
+#include "properties.h"
+#include "renderer.h"
+
+struct wlr_drm_plane {
+ uint32_t type;
+ uint32_t id;
+
+ uint32_t possible_crtcs;
+
+ struct wlr_drm_surface surf;
+ struct wlr_drm_surface mgpu_surf;
+
+ // Only used by cursor
+ float matrix[9];
+ struct gbm_bo *cursor_bo;
+ bool cursor_enabled;
+ int32_t cursor_hotspot_x, cursor_hotspot_y;
+
+ union wlr_drm_plane_props props;
+};
+
+struct wlr_drm_crtc {
+ uint32_t id;
+
+ // Atomic modesetting only
+ uint32_t mode_id;
+ uint32_t gamma_lut;
+ drmModeAtomicReq *atomic;
+
+ // Legacy only
+ drmModeCrtc *legacy_crtc;
+
+ union {
+ struct {
+ struct wlr_drm_plane *overlay;
+ struct wlr_drm_plane *primary;
+ struct wlr_drm_plane *cursor;
+ };
+ struct wlr_drm_plane *planes[3];
+ };
+
+ union wlr_drm_crtc_props props;
+
+ struct wl_list connectors;
+
+ uint16_t *gamma_table;
+ size_t gamma_table_size;
+};
+
+struct wlr_drm_backend {
+ struct wlr_backend backend;
+
+ struct wlr_drm_backend *parent;
+ const struct wlr_drm_interface *iface;
+ clockid_t clock;
+
+ int fd;
+
+ size_t num_crtcs;
+ struct wlr_drm_crtc *crtcs;
+ size_t num_planes;
+ struct wlr_drm_plane *planes;
+
+ union {
+ struct {
+ size_t num_overlay_planes;
+ size_t num_primary_planes;
+ size_t num_cursor_planes;
+ };
+ size_t num_type_planes[3];
+ };
+
+ union {
+ struct {
+ struct wlr_drm_plane *overlay_planes;
+ struct wlr_drm_plane *primary_planes;
+ struct wlr_drm_plane *cursor_planes;
+ };
+ struct wlr_drm_plane *type_planes[3];
+ };
+
+ struct wl_display *display;
+ struct wl_event_source *drm_event;
+
+ struct wl_listener display_destroy;
+ struct wl_listener session_signal;
+ struct wl_listener drm_invalidated;
+
+ struct wl_list outputs;
+
+ struct wlr_drm_renderer renderer;
+ struct wlr_session *session;
+};
+
+enum wlr_drm_connector_state {
+ // Connector is available but no output is plugged in
+ WLR_DRM_CONN_DISCONNECTED,
+ // An output just has been plugged in and is waiting for a modeset
+ WLR_DRM_CONN_NEEDS_MODESET,
+ WLR_DRM_CONN_CLEANUP,
+ WLR_DRM_CONN_CONNECTED,
+ // Connector disappeared, waiting for being destroyed on next page-flip
+ WLR_DRM_CONN_DISAPPEARED,
+};
+
+struct wlr_drm_mode {
+ struct wlr_output_mode wlr_mode;
+ drmModeModeInfo drm_mode;
+};
+
+struct wlr_drm_connector {
+ struct wlr_output output;
+
+ enum wlr_drm_connector_state state;
+ struct wlr_output_mode *desired_mode;
+ bool desired_enabled;
+ uint32_t id;
+
+ struct wlr_drm_crtc *crtc;
+ uint32_t possible_crtc;
+
+ union wlr_drm_connector_props props;
+
+ uint32_t width, height;
+ int32_t cursor_x, cursor_y;
+
+ drmModeCrtc *old_crtc;
+
+ bool pageflip_pending;
+ struct wl_event_source *retry_pageflip;
+ struct wl_list link;
+};
+
+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 restore_drm_outputs(struct wlr_drm_backend *drm);
+void scan_drm_connectors(struct wlr_drm_backend *state);
+int handle_drm_event(int fd, uint32_t mask, void *data);
+bool enable_drm_connector(struct wlr_output *output, bool enable);
+bool set_drm_connector_gamma(struct wlr_output *output, size_t size,
+ const uint16_t *r, const uint16_t *g, const uint16_t *b);
+
+#endif
diff --git a/include/backend/drm/iface.h b/include/backend/drm/iface.h
new file mode 100644
index 00000000..5308b136
--- /dev/null
+++ b/include/backend/drm/iface.h
@@ -0,0 +1,41 @@
+#ifndef BACKEND_DRM_IFACE_H
+#define BACKEND_DRM_IFACE_H
+
+#include <gbm.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+struct wlr_drm_backend;
+struct wlr_drm_connector;
+struct wlr_drm_crtc;
+
+// Used to provide atomic or legacy DRM functions
+struct wlr_drm_interface {
+ // Enable or disable DPMS for connector
+ bool (*conn_enable)(struct wlr_drm_backend *drm,
+ struct wlr_drm_connector *conn, bool enable);
+ // Pageflip on crtc. If mode is non-NULL perform a full modeset using it.
+ bool (*crtc_pageflip)(struct wlr_drm_backend *drm,
+ struct wlr_drm_connector *conn, struct wlr_drm_crtc *crtc,
+ uint32_t fb_id, drmModeModeInfo *mode);
+ // Enable the cursor buffer on crtc. Set bo to NULL to disable
+ bool (*crtc_set_cursor)(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, struct gbm_bo *bo);
+ // Move the cursor on crtc
+ bool (*crtc_move_cursor)(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, int x, int y);
+ // Set the gamma lut on crtc
+ bool (*crtc_set_gamma)(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, size_t size,
+ uint16_t *r, uint16_t *g, uint16_t *b);
+ // Get the gamma lut size of a crtc
+ size_t (*crtc_get_gamma_size)(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc);
+};
+
+extern const struct wlr_drm_interface atomic_iface;
+extern const struct wlr_drm_interface legacy_iface;
+
+#endif
diff --git a/include/backend/drm/properties.h b/include/backend/drm/properties.h
new file mode 100644
index 00000000..b4d43bdd
--- /dev/null
+++ b/include/backend/drm/properties.h
@@ -0,0 +1,72 @@
+#ifndef BACKEND_DRM_PROPERTIES_H
+#define BACKEND_DRM_PROPERTIES_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/*
+ * These types contain the property ids for several DRM objects.
+ * See https://01.org/linuxgraphics/gfx-docs/drm/gpu/drm-kms.html#kms-properties
+ * for more details.
+ */
+
+union wlr_drm_connector_props {
+ struct {
+ uint32_t edid;
+ uint32_t dpms;
+ uint32_t link_status; // not guaranteed to exist
+ uint32_t path;
+
+ // 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 rotation;
+ uint32_t scaling_mode;
+
+ // atomic-modesetting only
+
+ uint32_t active;
+ uint32_t mode_id;
+ uint32_t gamma_lut;
+ uint32_t gamma_lut_size;
+ };
+ uint32_t props[6];
+};
+
+union wlr_drm_plane_props {
+ struct {
+ uint32_t type;
+ uint32_t rotation; // 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 props[12];
+};
+
+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);
+
+#endif
diff --git a/include/backend/drm/renderer.h b/include/backend/drm/renderer.h
new file mode 100644
index 00000000..575758de
--- /dev/null
+++ b/include/backend/drm/renderer.h
@@ -0,0 +1,57 @@
+#ifndef BACKEND_DRM_RENDERER_H
+#define BACKEND_DRM_RENDERER_H
+
+#include <EGL/egl.h>
+#include <gbm.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <wlr/backend.h>
+#include <wlr/render/wlr_renderer.h>
+
+struct wlr_drm_backend;
+struct wlr_drm_plane;
+
+struct wlr_drm_renderer {
+ int fd;
+ struct gbm_device *gbm;
+ struct wlr_egl egl;
+
+ struct wlr_renderer *wlr_rend;
+};
+
+struct wlr_drm_surface {
+ struct wlr_drm_renderer *renderer;
+
+ uint32_t width;
+ uint32_t height;
+
+ struct gbm_surface *gbm;
+ EGLSurface egl;
+
+ struct gbm_bo *front;
+ struct gbm_bo *back;
+};
+
+bool init_drm_renderer(struct wlr_drm_backend *drm,
+ struct wlr_drm_renderer *renderer, wlr_renderer_create_func_t create_render);
+void finish_drm_renderer(struct wlr_drm_renderer *renderer);
+
+bool init_drm_surface(struct wlr_drm_surface *surf,
+ struct wlr_drm_renderer *renderer, uint32_t width, uint32_t height,
+ uint32_t format, uint32_t flags);
+
+bool init_drm_plane_surfaces(struct wlr_drm_plane *plane,
+ struct wlr_drm_backend *drm, int32_t width, uint32_t height,
+ uint32_t format);
+
+void finish_drm_surface(struct wlr_drm_surface *surf);
+bool make_drm_surface_current(struct wlr_drm_surface *surf, int *buffer_age);
+struct gbm_bo *swap_drm_surface_buffers(struct wlr_drm_surface *surf,
+ pixman_region32_t *damage);
+struct gbm_bo *get_drm_surface_front(struct wlr_drm_surface *surf);
+void post_drm_surface(struct wlr_drm_surface *surf);
+struct gbm_bo *copy_drm_surface_mgpu(struct wlr_drm_surface *dest,
+ struct gbm_bo *src);
+bool export_drm_bo(struct gbm_bo *bo, struct wlr_dmabuf_attributes *attribs);
+
+#endif
diff --git a/include/backend/drm/util.h b/include/backend/drm/util.h
new file mode 100644
index 00000000..e159d716
--- /dev/null
+++ b/include/backend/drm/util.h
@@ -0,0 +1,41 @@
+#ifndef BACKEND_DRM_UTIL_H
+#define BACKEND_DRM_UTIL_H
+
+#include <stdint.h>
+#include <wlr/types/wlr_output.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+// Calculates a more accurate refresh rate (mHz) than what mode itself provides
+int32_t calculate_refresh_rate(const drmModeModeInfo *mode);
+// Populates the make/model/phys_{width,height} of output from the edid data
+void parse_edid(struct wlr_output *restrict output, size_t len,
+ const uint8_t *data);
+// Returns the string representation of a DRM output type
+const char *conn_get_name(uint32_t type_id);
+// Returns the DRM framebuffer id for a gbm_bo
+uint32_t get_fb_for_bo(struct gbm_bo *bo);
+
+// 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 00000000..35280862
--- /dev/null
+++ b/include/backend/headless.h
@@ -0,0 +1,40 @@
+#ifndef BACKEND_HEADLESS_H
+#define BACKEND_HEADLESS_H
+
+#include <wlr/backend/headless.h>
+#include <wlr/backend/interface.h>
+
+#define HEADLESS_DEFAULT_REFRESH (60 * 1000) // 60 Hz
+
+struct wlr_headless_backend {
+ struct wlr_backend backend;
+ struct wlr_egl egl;
+ struct wlr_renderer *renderer;
+ struct wl_display *display;
+ struct wl_list outputs;
+ struct wl_list input_devices;
+ struct wl_listener display_destroy;
+ bool started;
+};
+
+struct wlr_headless_output {
+ struct wlr_output wlr_output;
+
+ struct wlr_headless_backend *backend;
+ struct wl_list link;
+
+ void *egl_surface;
+ struct wl_event_source *frame_timer;
+ int frame_delay; // ms
+};
+
+struct wlr_headless_input_device {
+ struct wlr_input_device wlr_input_device;
+
+ struct wlr_headless_backend *backend;
+};
+
+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 00000000..f4886956
--- /dev/null
+++ b/include/backend/libinput.h
@@ -0,0 +1,94 @@
+#ifndef BACKEND_LIBINPUT_H
+#define BACKEND_LIBINPUT_H
+
+#include <libinput.h>
+#include <wayland-server-core.h>
+#include <wlr/backend/interface.h>
+#include <wlr/backend/libinput.h>
+#include <wlr/interfaces/wlr_input_device.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_list.h>
+
+struct wlr_libinput_backend {
+ struct wlr_backend backend;
+
+ struct wlr_session *session;
+ struct wl_display *display;
+
+ struct libinput *libinput_context;
+ struct wl_event_source *input_event;
+
+ struct wl_listener display_destroy;
+ struct wl_listener session_signal;
+
+ struct wlr_list wlr_device_lists; // list of struct wl_list
+};
+
+struct wlr_libinput_input_device {
+ struct wlr_input_device wlr_input_device;
+
+ struct libinput_device *handle;
+};
+
+uint32_t usec_to_msec(uint64_t usec);
+
+void handle_libinput_event(struct wlr_libinput_backend *state,
+ struct libinput_event *event);
+
+struct wlr_input_device *get_appropriate_device(
+ enum wlr_input_device_type desired_type,
+ struct libinput_device *device);
+
+struct wlr_keyboard *create_libinput_keyboard(
+ struct libinput_device *device);
+void handle_keyboard_key(struct libinput_event *event,
+ struct libinput_device *device);
+
+struct wlr_pointer *create_libinput_pointer(
+ struct libinput_device *device);
+void handle_pointer_motion(struct libinput_event *event,
+ struct libinput_device *device);
+void handle_pointer_motion_abs(struct libinput_event *event,
+ struct libinput_device *device);
+void handle_pointer_button(struct libinput_event *event,
+ struct libinput_device *device);
+void handle_pointer_axis(struct libinput_event *event,
+ struct libinput_device *device);
+
+struct wlr_switch *create_libinput_switch(
+ struct libinput_device *device);
+void handle_switch_toggle(struct libinput_event *event,
+ struct libinput_device *device);
+
+struct wlr_touch *create_libinput_touch(
+ struct libinput_device *device);
+void handle_touch_down(struct libinput_event *event,
+ struct libinput_device *device);
+void handle_touch_up(struct libinput_event *event,
+ struct libinput_device *device);
+void handle_touch_motion(struct libinput_event *event,
+ struct libinput_device *device);
+void handle_touch_cancel(struct libinput_event *event,
+ struct libinput_device *device);
+
+struct wlr_tablet *create_libinput_tablet(
+ struct libinput_device *device);
+void handle_tablet_tool_axis(struct libinput_event *event,
+ struct libinput_device *device);
+void handle_tablet_tool_proximity(struct libinput_event *event,
+ struct libinput_device *device);
+void handle_tablet_tool_tip(struct libinput_event *event,
+ struct libinput_device *device);
+void handle_tablet_tool_button(struct libinput_event *event,
+ struct libinput_device *device);
+
+struct wlr_tablet_pad *create_libinput_tablet_pad(
+ struct libinput_device *device);
+void handle_tablet_pad_button(struct libinput_event *event,
+ struct libinput_device *device);
+void handle_tablet_pad_ring(struct libinput_event *event,
+ struct libinput_device *device);
+void handle_tablet_pad_strip(struct libinput_event *event,
+ struct libinput_device *device);
+
+#endif
diff --git a/include/backend/multi.h b/include/backend/multi.h
new file mode 100644
index 00000000..2f5f1bd4
--- /dev/null
+++ b/include/backend/multi.h
@@ -0,0 +1,23 @@
+#ifndef BACKEND_MULTI_H
+#define BACKEND_MULTI_H
+
+#include <wayland-util.h>
+#include <wlr/backend/interface.h>
+#include <wlr/backend/multi.h>
+#include <wlr/backend/session.h>
+
+struct wlr_multi_backend {
+ struct wlr_backend backend;
+ struct wlr_session *session;
+
+ struct wl_list backends;
+
+ struct wl_listener display_destroy;
+
+ struct {
+ struct wl_signal backend_add;
+ struct wl_signal backend_remove;
+ } events;
+};
+
+#endif
diff --git a/include/backend/session/direct-ipc.h b/include/backend/session/direct-ipc.h
new file mode 100644
index 00000000..c660b58f
--- /dev/null
+++ b/include/backend/session/direct-ipc.h
@@ -0,0 +1,12 @@
+#ifndef BACKEND_SESSION_DIRECT_IPC_H
+#define BACKEND_SESSION_DIRECT_IPC_H
+
+#include <sys/types.h>
+
+int direct_ipc_open(int sock, const char *path);
+void direct_ipc_setmaster(int sock, int fd);
+void direct_ipc_dropmaster(int sock, int fd);
+void direct_ipc_finish(int sock, pid_t pid);
+int direct_ipc_init(pid_t *pid_out);
+
+#endif
diff --git a/include/backend/wayland.h b/include/backend/wayland.h
new file mode 100644
index 00000000..dbc309ca
--- /dev/null
+++ b/include/backend/wayland.h
@@ -0,0 +1,92 @@
+#ifndef BACKEND_WAYLAND_H
+#define BACKEND_WAYLAND_H
+
+#include <stdbool.h>
+
+#include <wayland-client.h>
+#include <wayland-egl.h>
+#include <wayland-server.h>
+#include <wayland-util.h>
+
+#include <wlr/backend/wayland.h>
+#include <wlr/render/egl.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_box.h>
+
+struct wlr_wl_backend {
+ struct wlr_backend backend;
+
+ /* local state */
+ bool started;
+ struct wl_display *local_display;
+ struct wl_list devices;
+ struct wl_list outputs;
+ struct wlr_egl egl;
+ struct wlr_renderer *renderer;
+ size_t requested_outputs;
+ struct wl_listener local_display_destroy;
+ /* remote state */
+ struct wl_display *remote_display;
+ struct wl_event_source *remote_display_src;
+ struct wl_registry *registry;
+ struct wl_compositor *compositor;
+ struct zxdg_shell_v6 *shell;
+ struct wl_shm *shm;
+ struct wl_seat *seat;
+ struct wl_pointer *pointer;
+ struct wl_keyboard *keyboard;
+ struct wlr_wl_pointer *current_pointer;
+ char *seat_name;
+};
+
+struct wlr_wl_output {
+ struct wlr_output wlr_output;
+
+ struct wlr_wl_backend *backend;
+ struct wl_list link;
+
+ struct wl_surface *surface;
+ struct wl_callback *frame_callback;
+ struct zxdg_surface_v6 *xdg_surface;
+ struct zxdg_toplevel_v6 *xdg_toplevel;
+ struct wl_egl_window *egl_window;
+ EGLSurface egl_surface;
+
+ uint32_t enter_serial;
+
+ struct {
+ struct wl_surface *surface;
+ struct wl_egl_window *egl_window;
+ int32_t hotspot_x, hotspot_y;
+ int32_t width, height;
+ } cursor;
+};
+
+struct wlr_wl_input_device {
+ struct wlr_input_device wlr_input_device;
+
+ struct wlr_wl_backend *backend;
+ void *resource;
+};
+
+struct wlr_wl_pointer {
+ struct wlr_pointer wlr_pointer;
+
+ struct wlr_wl_input_device *input_device;
+ struct wl_pointer *wl_pointer;
+ enum wlr_axis_source axis_source;
+ int32_t axis_discrete;
+ struct wlr_wl_output *output;
+
+ struct wl_listener output_destroy;
+};
+
+struct wlr_wl_backend *get_wl_backend_from_backend(struct wlr_backend *backend);
+void update_wl_output_cursor(struct wlr_wl_output *output);
+struct wlr_wl_pointer *pointer_get_wl(struct wlr_pointer *wlr_pointer);
+void create_wl_pointer(struct wl_pointer *wl_pointer, struct wlr_wl_output *output);
+void create_wl_keyboard(struct wl_keyboard *wl_keyboard, struct wlr_wl_backend *wl);
+
+extern const struct wl_seat_listener seat_listener;
+
+#endif
diff --git a/include/backend/x11.h b/include/backend/x11.h
new file mode 100644
index 00000000..1a8341f6
--- /dev/null
+++ b/include/backend/x11.h
@@ -0,0 +1,91 @@
+#ifndef BACKEND_X11_H
+#define BACKEND_X11_H
+
+#include <stdbool.h>
+
+#include <X11/Xlib-xcb.h>
+#include <wayland-server.h>
+#include <xcb/xcb.h>
+
+#include <wlr/backend/x11.h>
+#include <wlr/config.h>
+#include <wlr/interfaces/wlr_input_device.h>
+#include <wlr/interfaces/wlr_output.h>
+#include <wlr/render/egl.h>
+#include <wlr/render/wlr_renderer.h>
+
+#define XCB_EVENT_RESPONSE_TYPE_MASK 0x7f
+
+#define X11_DEFAULT_REFRESH (60 * 1000) // 60 Hz
+
+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;
+ EGLSurface surf;
+
+ struct wlr_pointer pointer;
+ struct wlr_input_device pointer_dev;
+
+ struct wl_event_source *frame_timer;
+ int frame_delay;
+
+ bool cursor_hidden;
+};
+
+struct wlr_x11_backend {
+ struct wlr_backend backend;
+ struct wl_display *wl_display;
+ bool started;
+
+ Display *xlib_conn;
+ xcb_connection_t *xcb;
+ xcb_screen_t *screen;
+
+ size_t requested_outputs;
+ struct wl_list outputs; // wlr_x11_output::link
+
+ struct wlr_keyboard keyboard;
+ struct wlr_input_device keyboard_dev;
+
+ struct wlr_egl egl;
+ struct wlr_renderer *renderer;
+ 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;
+ } atoms;
+
+ // The time we last received an event
+ xcb_timestamp_t time;
+
+ uint8_t xinput_opcode;
+
+ struct wl_listener display_destroy;
+};
+
+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 keyboard_impl;
+extern const struct wlr_pointer_impl pointer_impl;
+extern const struct wlr_input_device_impl input_device_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);
+
+#endif
diff --git a/include/meson.build b/include/meson.build
new file mode 100644
index 00000000..d16d6ef4
--- /dev/null
+++ b/include/meson.build
@@ -0,0 +1 @@
+subdir('wlr')
diff --git a/include/render/gles2.h b/include/render/gles2.h
new file mode 100644
index 00000000..1857383f
--- /dev/null
+++ b/include/render/gles2.h
@@ -0,0 +1,108 @@
+#ifndef RENDER_GLES2_H
+#define RENDER_GLES2_H
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <wlr/backend.h>
+#include <wlr/render/egl.h>
+#include <wlr/render/gles2.h>
+#include <wlr/render/interface.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/render/wlr_texture.h>
+#include <wlr/util/log.h>
+
+extern PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
+
+struct wlr_gles2_pixel_format {
+ enum wl_shm_format wl_format;
+ GLint gl_format, gl_type;
+ int depth, bpp;
+ bool has_alpha;
+};
+
+struct wlr_gles2_tex_shader {
+ GLuint program;
+ GLint proj;
+ GLint invert_y;
+ GLint tex;
+ GLint alpha;
+};
+
+struct wlr_gles2_renderer {
+ struct wlr_renderer wlr_renderer;
+
+ struct wlr_egl *egl;
+ const char *exts_str;
+
+ struct {
+ bool read_format_bgra_ext;
+ bool debug_khr;
+ bool egl_image_external_oes;
+ } exts;
+
+ struct {
+ struct {
+ GLuint program;
+ GLint proj;
+ GLint color;
+ } quad;
+ struct {
+ GLuint program;
+ GLint proj;
+ GLint color;
+ } ellipse;
+ struct wlr_gles2_tex_shader tex_rgba;
+ struct wlr_gles2_tex_shader tex_rgbx;
+ struct wlr_gles2_tex_shader tex_ext;
+ } shaders;
+
+ uint32_t viewport_width, viewport_height;
+};
+
+enum wlr_gles2_texture_type {
+ WLR_GLES2_TEXTURE_GLTEX,
+ WLR_GLES2_TEXTURE_WL_DRM_GL,
+ WLR_GLES2_TEXTURE_WL_DRM_EXT,
+ WLR_GLES2_TEXTURE_DMABUF,
+};
+
+struct wlr_gles2_texture {
+ struct wlr_texture wlr_texture;
+
+ struct wlr_egl *egl;
+ enum wlr_gles2_texture_type type;
+ int width, height;
+ bool has_alpha;
+ enum wl_shm_format wl_format; // used to interpret upload data
+ bool inverted_y;
+
+ // Not set if WLR_GLES2_TEXTURE_GLTEX
+ EGLImageKHR image;
+ GLuint image_tex;
+
+ union {
+ GLuint gl_tex;
+ struct wl_resource *wl_drm;
+ };
+};
+
+const struct wlr_gles2_pixel_format *get_gles2_format_from_wl(
+ enum wl_shm_format fmt);
+const struct wlr_gles2_pixel_format *get_gles2_format_from_gl(
+ GLint gl_format, GLint gl_type, bool alpha);
+const enum wl_shm_format *get_gles2_wl_formats(size_t *len);
+
+struct wlr_gles2_texture *gles2_get_texture(
+ struct wlr_texture *wlr_texture);
+
+void push_gles2_marker(const char *file, const char *func);
+void pop_gles2_marker(void);
+#define PUSH_GLES2_DEBUG push_gles2_marker(_wlr_strip_path(__FILE__), __func__)
+#define POP_GLES2_DEBUG pop_gles2_marker()
+
+#endif
diff --git a/include/rootston/bindings.h b/include/rootston/bindings.h
new file mode 100644
index 00000000..db38130b
--- /dev/null
+++ b/include/rootston/bindings.h
@@ -0,0 +1,9 @@
+#ifndef ROOTSTON_BINDINGS_H
+#define ROOTSTON_BINDINGS_H
+
+#include "rootston/seat.h"
+#include "rootston/input.h"
+
+void execute_binding_command(struct roots_seat *seat, struct roots_input *input, const char *command);
+
+#endif //ROOTSTON_BINDINGS_H
diff --git a/include/rootston/config.h b/include/rootston/config.h
new file mode 100644
index 00000000..f8132269
--- /dev/null
+++ b/include/rootston/config.h
@@ -0,0 +1,133 @@
+#ifndef ROOTSTON_CONFIG_H
+#define ROOTSTON_CONFIG_H
+
+#include <xf86drmMode.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_switch.h>
+#include <wlr/types/wlr_output_layout.h>
+
+#define ROOTS_CONFIG_DEFAULT_SEAT_NAME "seat0"
+
+struct roots_output_mode_config {
+ drmModeModeInfo info;
+ struct wl_list link;
+};
+
+struct roots_output_config {
+ char *name;
+ bool enable;
+ enum wl_output_transform transform;
+ int x, y;
+ float scale;
+ struct wl_list link;
+ struct {
+ int width, height;
+ float refresh_rate;
+ } mode;
+ struct wl_list modes;
+};
+
+struct roots_device_config {
+ char *name;
+ char *seat;
+ char *mapped_output;
+ bool tap_enabled;
+ struct wlr_box *mapped_box;
+ struct wl_list link;
+};
+
+struct roots_binding_config {
+ uint32_t modifiers;
+ xkb_keysym_t *keysyms;
+ size_t keysyms_len;
+ char *command;
+ struct wl_list link;
+};
+
+struct roots_keyboard_config {
+ char *name;
+ char *seat;
+ uint32_t meta_key;
+ char *rules;
+ char *model;
+ char *layout;
+ char *variant;
+ char *options;
+ int repeat_rate, repeat_delay;
+ struct wl_list link;
+};
+
+struct roots_cursor_config {
+ char *seat;
+ char *mapped_output;
+ struct wlr_box *mapped_box;
+ char *theme;
+ char *default_image;
+ struct wl_list link;
+};
+
+struct roots_switch_config {
+ char *name;
+ enum wlr_switch_type switch_type;
+ enum wlr_switch_state switch_state;
+ char *command;
+ struct wl_list link;
+};
+
+struct roots_config {
+ bool xwayland;
+ bool xwayland_lazy;
+
+ struct wl_list outputs;
+ struct wl_list devices;
+ struct wl_list bindings;
+ struct wl_list keyboards;
+ struct wl_list cursors;
+ struct wl_list switches;
+
+ char *config_path;
+ char *startup_cmd;
+ bool debug_damage_tracking;
+};
+
+/**
+ * Create a roots config from the given command line arguments. Command line
+ * arguments can specify the location of the config file. If it is not
+ * specified, the default location will be used.
+ */
+struct roots_config *roots_config_create_from_args(int argc, char *argv[]);
+
+/**
+ * Destroy the config and free its resources.
+ */
+void roots_config_destroy(struct roots_config *config);
+
+/**
+ * Get configuration for the output. If the output is not configured, returns
+ * NULL.
+ */
+struct roots_output_config *roots_config_get_output(struct roots_config *config,
+ struct wlr_output *output);
+
+/**
+ * Get configuration for the device. If the device is not configured, returns
+ * NULL.
+ */
+struct roots_device_config *roots_config_get_device(struct roots_config *config,
+ struct wlr_input_device *device);
+
+/**
+ * Get configuration for the keyboard. If the keyboard is not configured,
+ * returns NULL. A NULL device returns the default config for keyboards.
+ */
+struct roots_keyboard_config *roots_config_get_keyboard(
+ struct roots_config *config, struct wlr_input_device *device);
+
+/**
+ * Get configuration for the cursor. If the cursor is not configured, returns
+ * NULL. A NULL seat_name returns the default config for cursors.
+ */
+struct roots_cursor_config *roots_config_get_cursor(struct roots_config *config,
+ const char *seat_name);
+
+#endif
diff --git a/include/rootston/cursor.h b/include/rootston/cursor.h
new file mode 100644
index 00000000..b5bb682f
--- /dev/null
+++ b/include/rootston/cursor.h
@@ -0,0 +1,105 @@
+#ifndef ROOTSTON_CURSOR_H
+#define ROOTSTON_CURSOR_H
+
+#include <wlr/types/wlr_pointer_constraints_v1.h>
+#include "rootston/seat.h"
+
+enum roots_cursor_mode {
+ ROOTS_CURSOR_PASSTHROUGH = 0,
+ ROOTS_CURSOR_MOVE = 1,
+ ROOTS_CURSOR_RESIZE = 2,
+ ROOTS_CURSOR_ROTATE = 3,
+};
+
+struct roots_cursor {
+ struct roots_seat *seat;
+ struct wlr_cursor *cursor;
+
+ struct wlr_pointer_constraint_v1 *active_constraint;
+ pixman_region32_t confine; // invalid if active_constraint == NULL
+
+ const char *default_xcursor;
+
+ enum roots_cursor_mode mode;
+
+ // state from input (review if this is necessary)
+ struct wlr_xcursor_manager *xcursor_manager;
+ struct wlr_seat *wl_seat;
+ struct wl_client *cursor_client;
+ int offs_x, offs_y;
+ int view_x, view_y, view_width, view_height;
+ float view_rotation;
+ uint32_t resize_edges;
+
+ struct roots_seat_view *pointer_view;
+ struct wlr_surface *wlr_surface;
+
+ struct wl_listener motion;
+ struct wl_listener motion_absolute;
+ struct wl_listener button;
+ struct wl_listener axis;
+
+ struct wl_listener touch_down;
+ struct wl_listener touch_up;
+ struct wl_listener touch_motion;
+
+ struct wl_listener tool_axis;
+ struct wl_listener tool_tip;
+ struct wl_listener tool_proximity;
+ struct wl_listener tool_button;
+
+ struct wl_listener request_set_cursor;
+
+ struct wl_listener focus_change;
+
+ struct wl_listener constraint_commit;
+};
+
+struct roots_cursor *roots_cursor_create(struct roots_seat *seat);
+
+void roots_cursor_destroy(struct roots_cursor *cursor);
+
+void roots_cursor_handle_motion(struct roots_cursor *cursor,
+ struct wlr_event_pointer_motion *event);
+
+void roots_cursor_handle_motion_absolute(struct roots_cursor *cursor,
+ struct wlr_event_pointer_motion_absolute *event);
+
+void roots_cursor_handle_button(struct roots_cursor *cursor,
+ struct wlr_event_pointer_button *event);
+
+void roots_cursor_handle_axis(struct roots_cursor *cursor,
+ struct wlr_event_pointer_axis *event);
+
+void roots_cursor_handle_touch_down(struct roots_cursor *cursor,
+ struct wlr_event_touch_down *event);
+
+void roots_cursor_handle_touch_up(struct roots_cursor *cursor,
+ struct wlr_event_touch_up *event);
+
+void roots_cursor_handle_touch_motion(struct roots_cursor *cursor,
+ struct wlr_event_touch_motion *event);
+
+void roots_cursor_handle_tool_axis(struct roots_cursor *cursor,
+ struct wlr_event_tablet_tool_axis *event);
+
+void roots_cursor_handle_tool_tip(struct roots_cursor *cursor,
+ struct wlr_event_tablet_tool_tip *event);
+
+void roots_cursor_handle_request_set_cursor(struct roots_cursor *cursor,
+ struct wlr_seat_pointer_request_set_cursor_event *event);
+
+void roots_cursor_handle_focus_change(struct roots_cursor *cursor,
+ struct wlr_seat_pointer_focus_change_event *event);
+
+void roots_cursor_handle_constraint_commit(struct roots_cursor *cursor);
+
+void roots_cursor_update_position(struct roots_cursor *cursor,
+ uint32_t time);
+
+void roots_cursor_update_focus(struct roots_cursor *cursor);
+
+void roots_cursor_constrain(struct roots_cursor *cursor,
+ struct wlr_pointer_constraint_v1 *constraint, double sx, double sy);
+
+#endif
diff --git a/include/rootston/desktop.h b/include/rootston/desktop.h
new file mode 100644
index 00000000..b1fcaca0
--- /dev/null
+++ b/include/rootston/desktop.h
@@ -0,0 +1,119 @@
+#ifndef ROOTSTON_DESKTOP_H
+#define ROOTSTON_DESKTOP_H
+#include <time.h>
+#include <wayland-server.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_foreign_toplevel_management_v1.h>
+#include <wlr/types/wlr_gamma_control_v1.h>
+#include <wlr/types/wlr_gamma_control.h>
+#include <wlr/types/wlr_idle_inhibit_v1.h>
+#include <wlr/types/wlr_idle.h>
+#include <wlr/types/wlr_input_inhibitor.h>
+#include <wlr/types/wlr_input_method_v2.h>
+#include <wlr/types/wlr_layer_shell_v1.h>
+#include <wlr/types/wlr_list.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_presentation_time.h>
+#include <wlr/types/wlr_gtk_primary_selection.h>
+#include <wlr/types/wlr_screencopy_v1.h>
+#include <wlr/types/wlr_screenshooter.h>
+#include <wlr/types/wlr_text_input_v3.h>
+#include <wlr/types/wlr_virtual_keyboard_v1.h>
+#include <wlr/types/wlr_wl_shell.h>
+#include <wlr/types/wlr_xcursor_manager.h>
+#include <wlr/types/wlr_xdg_decoration_v1.h>
+#include <wlr/types/wlr_xdg_shell_v6.h>
+#include <wlr/types/wlr_xdg_shell.h>
+#include "rootston/config.h"
+#include "rootston/output.h"
+#include "rootston/view.h"
+
+struct roots_desktop {
+ struct wl_list views; // roots_view::link
+
+ struct wl_list outputs; // roots_output::link
+ struct timespec last_frame;
+
+ struct roots_server *server;
+ struct roots_config *config;
+
+ struct wlr_output_layout *layout;
+ struct wlr_xcursor_manager *xcursor_manager;
+
+ struct wlr_compositor *compositor;
+ struct wlr_wl_shell *wl_shell;
+ struct wlr_xdg_shell_v6 *xdg_shell_v6;
+ struct wlr_xdg_shell *xdg_shell;
+ struct wlr_gamma_control_manager *gamma_control_manager;
+ struct wlr_gamma_control_manager_v1 *gamma_control_manager_v1;
+ struct wlr_screenshooter *screenshooter;
+ struct wlr_export_dmabuf_manager_v1 *export_dmabuf_manager_v1;
+ struct wlr_server_decoration_manager *server_decoration_manager;
+ struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager;
+ struct wlr_gtk_primary_selection_device_manager *primary_selection_device_manager;
+ struct wlr_idle *idle;
+ struct wlr_idle_inhibit_manager_v1 *idle_inhibit;
+ struct wlr_input_inhibit_manager *input_inhibit;
+ struct wlr_layer_shell_v1 *layer_shell;
+ struct wlr_input_method_manager_v2 *input_method;
+ struct wlr_text_input_manager_v3 *text_input;
+ struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard;
+ struct wlr_screencopy_manager_v1 *screencopy;
+ struct wlr_tablet_manager_v2 *tablet_v2;
+ struct wlr_pointer_constraints_v1 *pointer_constraints;
+ struct wlr_presentation *presentation;
+ struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager_v1;
+
+ struct wl_listener new_output;
+ struct wl_listener layout_change;
+ struct wl_listener xdg_shell_v6_surface;
+ struct wl_listener xdg_shell_surface;
+ struct wl_listener wl_shell_surface;
+ struct wl_listener layer_shell_surface;
+ struct wl_listener xdg_toplevel_decoration;
+ struct wl_listener input_inhibit_activate;
+ struct wl_listener input_inhibit_deactivate;
+ struct wl_listener virtual_keyboard_new;
+ struct wl_listener pointer_constraint;
+
+#if WLR_HAS_XWAYLAND
+ struct wlr_xwayland *xwayland;
+ struct wl_listener xwayland_surface;
+#endif
+};
+
+struct roots_server;
+
+struct roots_desktop *desktop_create(struct roots_server *server,
+ struct roots_config *config);
+void desktop_destroy(struct roots_desktop *desktop);
+struct roots_output *desktop_output_from_wlr_output(
+ struct roots_desktop *desktop, struct wlr_output *output);
+
+struct wlr_surface *desktop_surface_at(struct roots_desktop *desktop,
+ double lx, double ly, double *sx, double *sy,
+ struct roots_view **view);
+
+struct roots_view *view_create(struct roots_desktop *desktop);
+void view_destroy(struct roots_view *view);
+void view_activate(struct roots_view *view, bool activate);
+void view_apply_damage(struct roots_view *view);
+void view_damage_whole(struct roots_view *view);
+void view_update_position(struct roots_view *view, int x, int y);
+void view_update_size(struct roots_view *view, int width, int height);
+void view_update_decorated(struct roots_view *view, bool decorated);
+void view_initial_focus(struct roots_view *view);
+void view_map(struct roots_view *view, struct wlr_surface *surface);
+void view_unmap(struct roots_view *view);
+void view_arrange_maximized(struct roots_view *view);
+
+void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data);
+void handle_xdg_shell_surface(struct wl_listener *listener, void *data);
+void handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data);
+void handle_wl_shell_surface(struct wl_listener *listener, void *data);
+void handle_layer_shell_surface(struct wl_listener *listener, void *data);
+void handle_xwayland_surface(struct wl_listener *listener, void *data);
+
+#endif
diff --git a/include/rootston/ini.h b/include/rootston/ini.h
new file mode 100644
index 00000000..2804255b
--- /dev/null
+++ b/include/rootston/ini.h
@@ -0,0 +1,93 @@
+/* inih -- simple .INI file parser
+
+inih is released under the New BSD license (see LICENSE.txt). Go to the project
+home page for more info:
+
+https://github.com/benhoyt/inih
+
+*/
+
+#ifndef __INI_H__
+#define __INI_H__
+
+/* Make this header file easier to include in C++ code */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+/* Typedef for prototype of handler function. */
+typedef int (*ini_handler)(void* user, const char* section,
+ const char* name, const char* value);
+
+/* Typedef for prototype of fgets-style reader function. */
+typedef char* (*ini_reader)(char* str, int num, void* stream);
+
+/* Parse given INI-style file. May have [section]s, name=value pairs
+ (whitespace stripped), and comments starting with ';' (semicolon). Section
+ is "" if name=value pair parsed before any section heading. name:value
+ pairs are also supported as a concession to Python's configparser.
+
+ For each name=value pair parsed, call handler function with given user
+ pointer as well as section, name, and value (data only valid for duration
+ of handler call). Handler should return nonzero on success, zero on error.
+
+ Returns 0 on success, line number of first error on parse error (doesn't
+ stop on first error), -1 on file open error, or -2 on memory allocation
+ error (only when INI_USE_STACK is zero).
+*/
+int ini_parse(const char* filename, ini_handler handler, void* user);
+
+/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
+ close the file when it's finished -- the caller must do that. */
+int ini_parse_file(FILE* file, ini_handler handler, void* user);
+
+/* Same as ini_parse(), but takes an ini_reader function pointer instead of
+ filename. Used for implementing custom or string-based I/O. */
+int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
+ void* user);
+
+/* Nonzero to allow multi-line value parsing, in the style of Python's
+ configparser. If allowed, ini_parse() will call the handler with the same
+ name for each subsequent line parsed. */
+#ifndef INI_ALLOW_MULTILINE
+#define INI_ALLOW_MULTILINE 1
+#endif
+
+/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
+ the file. See http://code.google.com/p/inih/issues/detail?id=21 */
+#ifndef INI_ALLOW_BOM
+#define INI_ALLOW_BOM 1
+#endif
+
+/* Nonzero to allow inline comments (with valid inline comment characters
+ specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
+ Python 3.2+ configparser behaviour. */
+#ifndef INI_ALLOW_INLINE_COMMENTS
+#define INI_ALLOW_INLINE_COMMENTS 1
+#endif
+#ifndef INI_INLINE_COMMENT_PREFIXES
+#define INI_INLINE_COMMENT_PREFIXES ";"
+#endif
+
+/* Nonzero to use stack, zero to use heap (malloc/free). */
+#ifndef INI_USE_STACK
+#define INI_USE_STACK 1
+#endif
+
+/* Stop parsing on first error (default is to keep parsing). */
+#ifndef INI_STOP_ON_FIRST_ERROR
+#define INI_STOP_ON_FIRST_ERROR 0
+#endif
+
+/* Maximum line length for any line in INI file. */
+#ifndef INI_MAX_LINE
+#define INI_MAX_LINE 2000
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __INI_H__ */
diff --git a/include/rootston/input.h b/include/rootston/input.h
new file mode 100644
index 00000000..31810b4d
--- /dev/null
+++ b/include/rootston/input.h
@@ -0,0 +1,37 @@
+#ifndef ROOTSTON_INPUT_H
+#define ROOTSTON_INPUT_H
+
+#include <wayland-server.h>
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_seat.h>
+#include "rootston/config.h"
+#include "rootston/cursor.h"
+#include "rootston/server.h"
+#include "rootston/view.h"
+
+struct roots_input {
+ struct roots_config *config;
+ struct roots_server *server;
+
+ struct wl_listener new_input;
+
+ struct wl_list seats; // roots_seat::link
+};
+
+struct roots_input *input_create(struct roots_server *server,
+ struct roots_config *config);
+void input_destroy(struct roots_input *input);
+
+struct roots_seat *input_seat_from_wlr_seat(struct roots_input *input,
+ struct wlr_seat *seat);
+
+bool input_view_has_focus(struct roots_input *input, struct roots_view *view);
+
+struct roots_seat *input_get_seat(struct roots_input *input, char *name);
+
+struct roots_seat *input_last_active_seat(struct roots_input *input);
+
+void input_update_cursor_focus(struct roots_input *input);
+
+#endif
diff --git a/include/rootston/keyboard.h b/include/rootston/keyboard.h
new file mode 100644
index 00000000..0140389a
--- /dev/null
+++ b/include/rootston/keyboard.h
@@ -0,0 +1,34 @@
+#ifndef ROOTSTON_KEYBOARD_H
+#define ROOTSTON_KEYBOARD_H
+
+#include <xkbcommon/xkbcommon.h>
+#include "rootston/input.h"
+
+#define ROOTS_KEYBOARD_PRESSED_KEYSYMS_CAP 32
+
+struct roots_keyboard {
+ struct roots_input *input;
+ struct roots_seat *seat;
+ struct wlr_input_device *device;
+ struct roots_keyboard_config *config;
+ struct wl_list link;
+
+ struct wl_listener device_destroy;
+ struct wl_listener keyboard_key;
+ struct wl_listener keyboard_modifiers;
+
+ xkb_keysym_t pressed_keysyms_translated[ROOTS_KEYBOARD_PRESSED_KEYSYMS_CAP];
+ xkb_keysym_t pressed_keysyms_raw[ROOTS_KEYBOARD_PRESSED_KEYSYMS_CAP];
+};
+
+struct roots_keyboard *roots_keyboard_create(struct wlr_input_device *device,
+ struct roots_input *input);
+
+void roots_keyboard_destroy(struct roots_keyboard *keyboard);
+
+void roots_keyboard_handle_key(struct roots_keyboard *keyboard,
+ struct wlr_event_keyboard_key *event);
+
+void roots_keyboard_handle_modifiers(struct roots_keyboard *r_keyboard);
+
+#endif
diff --git a/include/rootston/layers.h b/include/rootston/layers.h
new file mode 100644
index 00000000..0dacf20f
--- /dev/null
+++ b/include/rootston/layers.h
@@ -0,0 +1,35 @@
+#ifndef ROOTSTON_LAYERS_H
+#define ROOTSTON_LAYERS_H
+#include <stdbool.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/types/wlr_layer_shell_v1.h>
+
+struct roots_layer_surface {
+ struct wlr_layer_surface_v1 *layer_surface;
+ struct wl_list link;
+
+ struct wl_listener destroy;
+ struct wl_listener map;
+ struct wl_listener unmap;
+ struct wl_listener surface_commit;
+ struct wl_listener output_destroy;
+ struct wl_listener new_popup;
+
+ bool configured;
+ struct wlr_box geo;
+};
+
+struct roots_layer_popup {
+ struct roots_layer_surface *parent;
+ struct wlr_xdg_popup *wlr_popup;
+ struct wl_listener map;
+ struct wl_listener unmap;
+ struct wl_listener destroy;
+ struct wl_listener commit;
+};
+
+struct roots_output;
+void arrange_layers(struct roots_output *output);
+
+#endif
diff --git a/include/rootston/output.h b/include/rootston/output.h
new file mode 100644
index 00000000..3f07ab6f
--- /dev/null
+++ b/include/rootston/output.h
@@ -0,0 +1,52 @@
+#ifndef ROOTSTON_OUTPUT_H
+#define ROOTSTON_OUTPUT_H
+#include <pixman.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_output_damage.h>
+
+struct roots_desktop;
+
+struct roots_output {
+ struct roots_desktop *desktop;
+ struct wlr_output *wlr_output;
+ struct wl_list link; // roots_desktop:outputs
+
+ struct roots_view *fullscreen_view;
+ struct wl_list layers[4]; // layer_surface::link
+
+ struct timespec last_frame;
+ struct wlr_output_damage *damage;
+
+ struct wlr_box usable_area;
+
+ struct wl_listener destroy;
+ struct wl_listener mode;
+ struct wl_listener transform;
+ struct wl_listener present;
+ struct wl_listener damage_frame;
+ struct wl_listener damage_destroy;
+};
+
+void rotate_child_position(double *sx, double *sy, double sw, double sh,
+ double pw, double ph, float rotation);
+
+void handle_new_output(struct wl_listener *listener, void *data);
+
+struct roots_view;
+struct roots_drag_icon;
+
+void output_damage_whole(struct roots_output *output);
+void output_damage_whole_view(struct roots_output *output,
+ struct roots_view *view);
+void output_damage_from_view(struct roots_output *output,
+ struct roots_view *view);
+void output_damage_whole_drag_icon(struct roots_output *output,
+ struct roots_drag_icon *icon);
+void output_damage_from_local_surface(struct roots_output *output,
+ struct wlr_surface *surface, double ox, double oy, float rotation);
+void output_damage_whole_local_surface(struct roots_output *output,
+ struct wlr_surface *surface, double ox, double oy, float rotation);
+
+#endif
diff --git a/include/rootston/seat.h b/include/rootston/seat.h
new file mode 100644
index 00000000..105ba3aa
--- /dev/null
+++ b/include/rootston/seat.h
@@ -0,0 +1,181 @@
+#ifndef ROOTSTON_SEAT_H
+#define ROOTSTON_SEAT_H
+
+#include <wayland-server.h>
+#include "rootston/input.h"
+#include "rootston/keyboard.h"
+#include "rootston/layers.h"
+#include "rootston/switch.h"
+#include "rootston/text_input.h"
+
+struct roots_seat {
+ struct roots_input *input;
+ struct wlr_seat *seat;
+ struct roots_cursor *cursor;
+ struct wl_list link; // roots_input::seats
+
+ // coordinates of the first touch point if it exists
+ int32_t touch_id;
+ double touch_x, touch_y;
+
+ // If the focused layer is set, views cannot receive keyboard focus
+ struct wlr_layer_surface_v1 *focused_layer;
+
+ struct roots_input_method_relay im_relay;
+
+ // If non-null, only this client can receive input events
+ struct wl_client *exclusive_client;
+
+ struct wl_list views; // roots_seat_view::link
+ bool has_focus;
+
+ struct wl_list drag_icons; // roots_drag_icon::link
+
+ struct wl_list keyboards;
+ struct wl_list pointers;
+ struct wl_list switches;
+ struct wl_list touch;
+ struct wl_list tablets;
+ struct wl_list tablet_pads;
+
+ struct wl_listener new_drag_icon;
+ struct wl_listener destroy;
+};
+
+struct roots_seat_view {
+ struct roots_seat *seat;
+ struct roots_view *view;
+
+ bool has_button_grab;
+ double grab_sx;
+ double grab_sy;
+
+ struct wl_list link; // roots_seat::views
+
+ struct wl_listener view_unmap;
+ struct wl_listener view_destroy;
+};
+
+struct roots_drag_icon {
+ struct roots_seat *seat;
+ struct wlr_drag_icon *wlr_drag_icon;
+ struct wl_list link;
+
+ double x, y;
+
+ struct wl_listener surface_commit;
+ struct wl_listener map;
+ struct wl_listener unmap;
+ struct wl_listener destroy;
+};
+
+struct roots_pointer {
+ struct roots_seat *seat;
+ struct wlr_input_device *device;
+ struct wl_listener device_destroy;
+ struct wl_list link;
+};
+
+struct roots_touch {
+ struct roots_seat *seat;
+ struct wlr_input_device *device;
+ struct wl_listener device_destroy;
+ struct wl_list link;
+};
+
+struct roots_tablet {
+ struct roots_seat *seat;
+ struct wlr_input_device *device;
+ struct wlr_tablet_v2_tablet *tablet_v2;
+
+ struct wl_listener device_destroy;
+ struct wl_listener axis;
+ struct wl_listener proximity;
+ struct wl_listener tip;
+ struct wl_listener button;
+ struct wl_list link;
+};
+
+struct roots_tablet_pad {
+ struct wl_list link;
+ struct wlr_tablet_v2_tablet_pad *tablet_v2_pad;
+
+ struct roots_seat *seat;
+ struct wlr_input_device *device;
+
+ struct wl_listener device_destroy;
+ struct wl_listener attach;
+ struct wl_listener button;
+ struct wl_listener ring;
+ struct wl_listener strip;
+
+ struct roots_tablet *tablet;
+ struct wl_listener tablet_destroy;
+};
+
+struct roots_tablet_tool {
+ struct wl_list link;
+ struct wl_list tool_link;
+ struct wlr_tablet_v2_tablet_tool *tablet_v2_tool;
+
+ struct roots_seat *seat;
+ double tilt_x, tilt_y;
+
+ struct wl_listener set_cursor;
+ struct wl_listener tool_destroy;
+
+ struct roots_tablet *current_tablet;
+ struct wl_listener tablet_destroy;
+};
+
+struct roots_pointer_constraint {
+ struct wlr_pointer_constraint_v1 *constraint;
+
+ struct wl_listener destroy;
+};
+
+struct roots_seat *roots_seat_create(struct roots_input *input, char *name);
+
+void roots_seat_destroy(struct roots_seat *seat);
+
+void roots_seat_add_device(struct roots_seat *seat,
+ struct wlr_input_device *device);
+
+void roots_seat_configure_cursor(struct roots_seat *seat);
+
+void roots_seat_configure_xcursor(struct roots_seat *seat);
+
+bool roots_seat_has_meta_pressed(struct roots_seat *seat);
+
+struct roots_view *roots_seat_get_focus(struct roots_seat *seat);
+
+void roots_seat_set_focus(struct roots_seat *seat, struct roots_view *view);
+
+void roots_seat_set_focus_layer(struct roots_seat *seat,
+ struct wlr_layer_surface_v1 *layer);
+
+void roots_seat_cycle_focus(struct roots_seat *seat);
+
+void roots_seat_begin_move(struct roots_seat *seat, struct roots_view *view);
+
+void roots_seat_begin_resize(struct roots_seat *seat, struct roots_view *view,
+ uint32_t edges);
+
+void roots_seat_begin_rotate(struct roots_seat *seat, struct roots_view *view);
+
+void roots_seat_end_compositor_grab(struct roots_seat *seat);
+
+struct roots_seat_view *roots_seat_view_from_view( struct roots_seat *seat,
+ struct roots_view *view);
+
+void roots_drag_icon_update_position(struct roots_drag_icon *icon);
+
+void roots_drag_icon_damage_whole(struct roots_drag_icon *icon);
+
+void roots_seat_set_exclusive_client(struct roots_seat *seat,
+ struct wl_client *client);
+
+bool roots_seat_allow_input(struct roots_seat *seat,
+ struct wl_resource *resource);
+
+#endif
diff --git a/include/rootston/server.h b/include/rootston/server.h
new file mode 100644
index 00000000..1836a374
--- /dev/null
+++ b/include/rootston/server.h
@@ -0,0 +1,37 @@
+#ifndef _ROOTSTON_SERVER_H
+#define _ROOTSTON_SERVER_H
+
+#include <wayland-server.h>
+#include <wlr/backend.h>
+#include <wlr/backend/session.h>
+#include <wlr/config.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_data_device.h>
+#if WLR_HAS_XWAYLAND
+#include <wlr/xwayland.h>
+#endif
+#include "rootston/config.h"
+#include "rootston/desktop.h"
+#include "rootston/input.h"
+
+struct roots_server {
+ /* Rootston resources */
+ struct roots_config *config;
+ struct roots_desktop *desktop;
+ struct roots_input *input;
+
+ /* Wayland resources */
+ struct wl_display *wl_display;
+ struct wl_event_loop *wl_event_loop;
+
+ /* WLR tools */
+ struct wlr_backend *backend;
+ struct wlr_renderer *renderer;
+
+ /* Global resources */
+ struct wlr_data_device_manager *data_device_manager;
+};
+
+extern struct roots_server server;
+
+#endif
diff --git a/include/rootston/switch.h b/include/rootston/switch.h
new file mode 100644
index 00000000..28197774
--- /dev/null
+++ b/include/rootston/switch.h
@@ -0,0 +1,18 @@
+#ifndef ROOTSTON_SWITCH_H
+#define ROOTSTON_SWITCH_H
+
+#include "rootston/input.h"
+
+struct roots_switch {
+ struct roots_seat *seat;
+ struct wlr_input_device *device;
+ struct wl_listener device_destroy;
+
+ struct wl_listener toggle;
+ struct wl_list link;
+};
+
+void roots_switch_handle_toggle(struct roots_switch *lid_switch,
+ struct wlr_event_switch_toggle *event);
+
+#endif // ROOTSTON_SWITCH_H
diff --git a/include/rootston/text_input.h b/include/rootston/text_input.h
new file mode 100644
index 00000000..82e45e3e
--- /dev/null
+++ b/include/rootston/text_input.h
@@ -0,0 +1,63 @@
+#ifndef ROOTSTON_TEXT_INPUT_H
+#define ROOTSTON_TEXT_INPUT_H
+
+#include <wlr/types/wlr_text_input_v3.h>
+#include <wlr/types/wlr_input_method_v2.h>
+#include <wlr/types/wlr_surface.h>
+#include "rootston/seat.h"
+
+/**
+ * The relay structure manages the relationship between text-input and
+ * input_method interfaces on a given seat. Multiple text-input interfaces may
+ * be bound to a relay, but at most one will be focused (reveiving events) at
+ * a time. At most one input-method interface may be bound to the seat. The
+ * relay manages life cycle of both sides. When both sides are present and
+ * focused, the relay passes messages between them.
+ *
+ * Text input focus is a subset of keyboard focus - if the text-input is
+ * in the focused state, wl_keyboard sent an enter as well. However, having
+ * wl_keyboard focused doesn't mean that text-input will be focused.
+ */
+struct roots_input_method_relay {
+ struct roots_seat *seat;
+
+ struct wl_list text_inputs; // roots_text_input::link
+ struct wlr_input_method_v2 *input_method; // doesn't have to be present
+
+ struct wl_listener text_input_new;
+ struct wl_listener text_input_enable;
+ struct wl_listener text_input_commit;
+ struct wl_listener text_input_disable;
+ struct wl_listener text_input_destroy;
+
+ struct wl_listener input_method_new;
+ struct wl_listener input_method_commit;
+ struct wl_listener input_method_destroy;
+};
+
+struct roots_text_input {
+ struct roots_input_method_relay *relay;
+
+ struct wlr_text_input_v3 *input;
+ // The surface getting seat's focus. Stored for when text-input cannot
+ // be sent an enter event immediately after getting focus, e.g. when
+ // there's no input method available. Cleared once text-input is entered.
+ struct wlr_surface *pending_focused_surface;
+
+ struct wl_list link;
+
+ struct wl_listener pending_focused_surface_destroy;
+};
+
+void roots_input_method_relay_init(struct roots_seat *seat,
+ struct roots_input_method_relay *relay);
+
+// Updates currently focused surface. Surface must belong to the same seat.
+void roots_input_method_relay_set_focus(struct roots_input_method_relay *relay,
+ struct wlr_surface *surface);
+
+struct roots_text_input *roots_text_input_create(
+ struct roots_input_method_relay *relay,
+ struct wlr_text_input_v3 *text_input);
+
+#endif
diff --git a/include/rootston/view.h b/include/rootston/view.h
new file mode 100644
index 00000000..b1feb5ce
--- /dev/null
+++ b/include/rootston/view.h
@@ -0,0 +1,260 @@
+#ifndef ROOTSTON_VIEW_H
+#define ROOTSTON_VIEW_H
+#include <stdbool.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_foreign_toplevel_management_v1.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/types/wlr_xdg_decoration_v1.h>
+#include <wlr/types/wlr_xdg_shell_v6.h>
+#include <wlr/types/wlr_xdg_shell.h>
+
+struct roots_wl_shell_surface {
+ struct roots_view *view;
+
+ struct wl_listener destroy;
+ struct wl_listener new_popup;
+ struct wl_listener request_move;
+ struct wl_listener request_resize;
+ struct wl_listener request_maximize;
+ struct wl_listener request_fullscreen;
+ struct wl_listener set_state;
+ struct wl_listener set_title;
+ struct wl_listener set_class;
+
+ struct wl_listener surface_commit;
+};
+
+struct roots_xdg_surface_v6 {
+ struct roots_view *view;
+
+ struct wl_listener destroy;
+ struct wl_listener new_popup;
+ struct wl_listener map;
+ struct wl_listener unmap;
+ struct wl_listener request_move;
+ struct wl_listener request_resize;
+ struct wl_listener request_maximize;
+ struct wl_listener request_fullscreen;
+ struct wl_listener set_title;
+ struct wl_listener set_app_id;
+
+ struct wl_listener surface_commit;
+
+ uint32_t pending_move_resize_configure_serial;
+};
+
+struct roots_xdg_toplevel_decoration;
+
+struct roots_xdg_surface {
+ struct roots_view *view;
+
+ struct wl_listener destroy;
+ struct wl_listener new_popup;
+ struct wl_listener map;
+ struct wl_listener unmap;
+ struct wl_listener request_move;
+ struct wl_listener request_resize;
+ struct wl_listener request_maximize;
+ struct wl_listener request_fullscreen;
+ struct wl_listener set_title;
+ struct wl_listener set_app_id;
+
+
+ struct wl_listener surface_commit;
+
+ uint32_t pending_move_resize_configure_serial;
+
+ struct roots_xdg_toplevel_decoration *xdg_toplevel_decoration;
+};
+
+struct roots_xwayland_surface {
+ struct roots_view *view;
+
+ struct wl_listener destroy;
+ struct wl_listener request_configure;
+ struct wl_listener request_move;
+ struct wl_listener request_resize;
+ struct wl_listener request_maximize;
+ struct wl_listener request_fullscreen;
+ struct wl_listener map;
+ struct wl_listener unmap;
+ struct wl_listener set_title;
+ struct wl_listener set_class;
+
+ struct wl_listener surface_commit;
+};
+
+enum roots_view_type {
+ ROOTS_WL_SHELL_VIEW,
+ ROOTS_XDG_SHELL_V6_VIEW,
+ ROOTS_XDG_SHELL_VIEW,
+#if WLR_HAS_XWAYLAND
+ ROOTS_XWAYLAND_VIEW,
+#endif
+};
+
+struct roots_view {
+ struct roots_desktop *desktop;
+ struct wl_list link; // roots_desktop::views
+
+ struct wlr_box box;
+ float rotation;
+ float alpha;
+
+ bool decorated;
+ int border_width;
+ int titlebar_height;
+
+ bool maximized;
+ struct roots_output *fullscreen_output;
+ struct {
+ double x, y;
+ uint32_t width, height;
+ float rotation;
+ } saved;
+
+ struct {
+ bool update_x, update_y;
+ double x, y;
+ uint32_t width, height;
+ } pending_move_resize;
+
+ // TODO: Something for roots-enforced width/height
+ enum roots_view_type type;
+ union {
+ struct wlr_wl_shell_surface *wl_shell_surface;
+ struct wlr_xdg_surface_v6 *xdg_surface_v6;
+ struct wlr_xdg_surface *xdg_surface;
+#if WLR_HAS_XWAYLAND
+ struct wlr_xwayland_surface *xwayland_surface;
+#endif
+ };
+ union {
+ struct roots_wl_shell_surface *roots_wl_shell_surface;
+ struct roots_xdg_surface_v6 *roots_xdg_surface_v6;
+ struct roots_xdg_surface *roots_xdg_surface;
+#if WLR_HAS_XWAYLAND
+ struct roots_xwayland_surface *roots_xwayland_surface;
+#endif
+ };
+
+ struct wlr_surface *wlr_surface;
+ struct wl_list children; // roots_view_child::link
+
+ struct wlr_foreign_toplevel_handle_v1 *toplevel_handle;
+ struct wl_listener toplevel_handle_request_maximize;
+ struct wl_listener toplevel_handle_request_activate;
+ struct wl_listener toplevel_handle_request_close;
+
+ struct wl_listener new_subsurface;
+
+ struct {
+ struct wl_signal unmap;
+ struct wl_signal destroy;
+ } events;
+
+ // TODO: this should follow the typical type/impl pattern we use elsewhere
+ void (*activate)(struct roots_view *view, bool active);
+ void (*move)(struct roots_view *view, double x, double y);
+ void (*resize)(struct roots_view *view, uint32_t width, uint32_t height);
+ void (*move_resize)(struct roots_view *view, double x, double y,
+ uint32_t width, uint32_t height);
+ void (*maximize)(struct roots_view *view, bool maximized);
+ void (*set_fullscreen)(struct roots_view *view, bool fullscreen);
+ void (*close)(struct roots_view *view);
+ void (*destroy)(struct roots_view *view);
+};
+
+struct roots_view_child {
+ struct roots_view *view;
+ struct wlr_surface *wlr_surface;
+ struct wl_list link;
+
+ struct wl_listener commit;
+ struct wl_listener new_subsurface;
+
+ void (*destroy)(struct roots_view_child *child);
+};
+
+struct roots_subsurface {
+ struct roots_view_child view_child;
+ struct wlr_subsurface *wlr_subsurface;
+ struct wl_listener destroy;
+};
+
+struct roots_wl_shell_popup {
+ struct roots_view_child view_child;
+ struct wlr_wl_shell_surface *wlr_wl_shell_surface;
+ struct wl_listener destroy;
+ struct wl_listener set_state;
+ struct wl_listener new_popup;
+};
+
+struct roots_xdg_popup_v6 {
+ struct roots_view_child view_child;
+ struct wlr_xdg_popup_v6 *wlr_popup;
+ struct wl_listener destroy;
+ struct wl_listener map;
+ struct wl_listener unmap;
+ struct wl_listener new_popup;
+};
+
+struct roots_xdg_popup {
+ struct roots_view_child view_child;
+ struct wlr_xdg_popup *wlr_popup;
+ struct wl_listener destroy;
+ struct wl_listener map;
+ struct wl_listener unmap;
+ struct wl_listener new_popup;
+};
+
+struct roots_xdg_toplevel_decoration {
+ struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration;
+ struct roots_xdg_surface *surface;
+ struct wl_listener destroy;
+ struct wl_listener request_mode;
+ struct wl_listener surface_commit;
+};
+
+void view_get_box(const struct roots_view *view, struct wlr_box *box);
+void view_activate(struct roots_view *view, bool active);
+void view_move(struct roots_view *view, double x, double y);
+void view_resize(struct roots_view *view, uint32_t width, uint32_t height);
+void view_move_resize(struct roots_view *view, double x, double y,
+ uint32_t width, uint32_t height);
+void view_maximize(struct roots_view *view, bool maximized);
+void view_set_fullscreen(struct roots_view *view, bool fullscreen,
+ struct wlr_output *output);
+void view_rotate(struct roots_view *view, float rotation);
+void view_cycle_alpha(struct roots_view *view);
+void view_close(struct roots_view *view);
+bool view_center(struct roots_view *view);
+void view_setup(struct roots_view *view);
+void view_teardown(struct roots_view *view);
+
+void view_set_title(struct roots_view *view, const char *title);
+void view_set_app_id(struct roots_view *view, const char *app_id);
+void view_create_foreign_toplevel_handle(struct roots_view *view);
+
+void view_get_deco_box(const struct roots_view *view, struct wlr_box *box);
+
+enum roots_deco_part {
+ ROOTS_DECO_PART_NONE = 0,
+ ROOTS_DECO_PART_TOP_BORDER = (1 << 0),
+ ROOTS_DECO_PART_BOTTOM_BORDER = (1 << 1),
+ ROOTS_DECO_PART_LEFT_BORDER = (1 << 2),
+ ROOTS_DECO_PART_RIGHT_BORDER = (1 << 3),
+ ROOTS_DECO_PART_TITLEBAR = (1 << 4),
+};
+
+enum roots_deco_part view_get_deco_part(struct roots_view *view, double sx, double sy);
+
+void view_child_init(struct roots_view_child *child, struct roots_view *view,
+ struct wlr_surface *wlr_surface);
+void view_child_finish(struct roots_view_child *child);
+
+struct roots_subsurface *subsurface_create(struct roots_view *view,
+ struct wlr_subsurface *wlr_subsurface);
+
+#endif
diff --git a/include/rootston/virtual_keyboard.h b/include/rootston/virtual_keyboard.h
new file mode 100644
index 00000000..613e5595
--- /dev/null
+++ b/include/rootston/virtual_keyboard.h
@@ -0,0 +1,7 @@
+#ifndef ROOTSTON_VIRTUAL_KEYBOARD_H
+#define ROOTSTON_VIRTUAL_KEYBOARD_H
+
+#include <wayland-server-core.h>
+
+void handle_virtual_keyboard(struct wl_listener *listener, void *data);
+#endif
diff --git a/include/rootston/xcursor.h b/include/rootston/xcursor.h
new file mode 100644
index 00000000..f78489a4
--- /dev/null
+++ b/include/rootston/xcursor.h
@@ -0,0 +1,12 @@
+#ifndef ROOTSTON_XCURSOR_H
+#define ROOTSTON_XCURSOR_H
+
+#include <stdint.h>
+
+#define ROOTS_XCURSOR_SIZE 24
+
+#define ROOTS_XCURSOR_DEFAULT "left_ptr"
+#define ROOTS_XCURSOR_MOVE "grabbing"
+#define ROOTS_XCURSOR_ROTATE "grabbing"
+
+#endif
diff --git a/include/types/wlr_data_device.h b/include/types/wlr_data_device.h
new file mode 100644
index 00000000..376c5f09
--- /dev/null
+++ b/include/types/wlr_data_device.h
@@ -0,0 +1,37 @@
+#ifndef TYPES_WLR_DATA_DEVICE_H
+#define TYPES_WLR_DATA_DEVICE_H
+
+#include <wayland-server.h>
+
+#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_client *client,
+ struct wlr_data_source *source, uint32_t version);
+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);
+struct wlr_data_offer *data_source_send_offer(struct wlr_data_source *source,
+ struct wl_resource *device_resource);
+void data_source_notify_finish(struct wlr_data_source *source);
+
+bool seat_client_start_drag(struct wlr_seat_client *client,
+ struct wlr_data_source *source, struct wlr_surface *icon_surface,
+ struct wlr_surface *origin, uint32_t serial);
+
+#endif
diff --git a/include/types/wlr_seat.h b/include/types/wlr_seat.h
new file mode 100644
index 00000000..15f1dc38
--- /dev/null
+++ b/include/types/wlr_seat.h
@@ -0,0 +1,23 @@
+#ifndef TYPES_WLR_SEAT_H
+#define TYPES_WLR_SEAT_H
+
+#include <wayland-server.h>
+#include <wlr/types/wlr_seat.h>
+
+const struct wlr_pointer_grab_interface default_pointer_grab_impl;
+const struct wlr_keyboard_grab_interface default_keyboard_grab_impl;
+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_destroy_pointer(struct wl_resource *resource);
+
+void seat_client_create_keyboard(struct wlr_seat_client *seat_client,
+ uint32_t version, uint32_t id);
+void seat_client_destroy_keyboard(struct wl_resource *resource);
+
+void seat_client_create_touch(struct wlr_seat_client *seat_client,
+ uint32_t version, uint32_t id);
+void seat_client_destroy_touch(struct wl_resource *resource);
+
+#endif
diff --git a/include/types/wlr_tablet_v2.h b/include/types/wlr_tablet_v2.h
new file mode 100644
index 00000000..becde596
--- /dev/null
+++ b/include/types/wlr_tablet_v2.h
@@ -0,0 +1,93 @@
+#ifndef TYPES_WLR_TABLET_V2_H
+#define TYPES_WLR_TABLET_V2_H
+
+#include "tablet-unstable-v2-protocol.h"
+#include <wayland-server.h>
+#include <wlr/types/wlr_tablet_v2.h>
+
+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;
+
+ 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 00000000..08a691bd
--- /dev/null
+++ b/include/types/wlr_xdg_shell.h
@@ -0,0 +1,48 @@
+#ifndef TYPES_WLR_XDG_SHELL_H
+#define TYPES_WLR_XDG_SHELL_H
+
+#include <wayland-server.h>
+#include <wlr/types/wlr_xdg_shell.h>
+#include "xdg-shell-protocol.h"
+
+struct wlr_xdg_positioner_resource {
+ struct wl_resource *resource;
+ struct wlr_xdg_positioner attrs;
+};
+
+extern const struct wlr_surface_role xdg_toplevel_surface_role;
+extern const struct wlr_surface_role xdg_popup_surface_role;
+
+uint32_t schedule_xdg_surface_configure(struct wlr_xdg_surface *surface);
+struct wlr_xdg_surface *create_xdg_surface(
+ struct wlr_xdg_client *client, struct wlr_surface *surface,
+ uint32_t id);
+void unmap_xdg_surface(struct wlr_xdg_surface *surface);
+void reset_xdg_surface(struct wlr_xdg_surface *xdg_surface);
+void destroy_xdg_surface(struct wlr_xdg_surface *surface);
+void handle_xdg_surface_commit(struct wlr_surface *wlr_surface);
+void handle_xdg_surface_precommit(struct wlr_surface *wlr_surface);
+
+void create_xdg_positioner(struct wlr_xdg_client *client, uint32_t id);
+struct wlr_xdg_positioner_resource *get_xdg_positioner_from_resource(
+ struct wl_resource *resource);
+
+void create_xdg_popup(struct wlr_xdg_surface *xdg_surface,
+ struct wlr_xdg_surface *parent,
+ struct wlr_xdg_positioner_resource *positioner, int32_t id);
+void handle_xdg_surface_popup_committed(struct wlr_xdg_surface *surface);
+struct wlr_xdg_popup_grab *get_xdg_shell_popup_grab_from_seat(
+ struct wlr_xdg_shell *shell, struct wlr_seat *seat);
+void destroy_xdg_popup(struct wlr_xdg_surface *surface);
+
+void create_xdg_toplevel(struct wlr_xdg_surface *xdg_surface,
+ uint32_t id);
+void handle_xdg_surface_toplevel_committed(struct wlr_xdg_surface *surface);
+void send_xdg_toplevel_configure(struct wlr_xdg_surface *surface,
+ struct wlr_xdg_surface_configure *configure);
+void handle_xdg_toplevel_ack_configure(struct wlr_xdg_surface *surface,
+ struct wlr_xdg_surface_configure *configure);
+bool compare_xdg_surface_toplevel_state(struct wlr_xdg_toplevel *state);
+void destroy_xdg_toplevel(struct wlr_xdg_surface *surface);
+
+#endif
diff --git a/include/types/wlr_xdg_shell_v6.h b/include/types/wlr_xdg_shell_v6.h
new file mode 100644
index 00000000..59fca667
--- /dev/null
+++ b/include/types/wlr_xdg_shell_v6.h
@@ -0,0 +1,47 @@
+#ifndef TYPES_WLR_XDG_SHELL_V6_H
+#define TYPES_WLR_XDG_SHELL_V6_H
+
+#include <wayland-server.h>
+#include <wlr/types/wlr_xdg_shell_v6.h>
+#include "xdg-shell-unstable-v6-protocol.h"
+
+struct wlr_xdg_positioner_v6_resource {
+ struct wl_resource *resource;
+ struct wlr_xdg_positioner_v6 attrs;
+};
+
+extern const struct wlr_surface_role xdg_toplevel_v6_surface_role;
+extern const struct wlr_surface_role xdg_popup_v6_surface_role;
+
+uint32_t schedule_xdg_surface_v6_configure(struct wlr_xdg_surface_v6 *surface);
+struct wlr_xdg_surface_v6 *create_xdg_surface_v6(
+ struct wlr_xdg_client_v6 *client, struct wlr_surface *surface,
+ uint32_t id);
+void unmap_xdg_surface_v6(struct wlr_xdg_surface_v6 *surface);
+void destroy_xdg_surface_v6(struct wlr_xdg_surface_v6 *surface);
+void handle_xdg_surface_v6_commit(struct wlr_surface *wlr_surface);
+void handle_xdg_surface_v6_precommit(struct wlr_surface *wlr_surface);
+
+void create_xdg_positioner_v6(struct wlr_xdg_client_v6 *client, uint32_t id);
+struct wlr_xdg_positioner_v6_resource *get_xdg_positioner_v6_from_resource(
+ struct wl_resource *resource);
+
+void create_xdg_popup_v6(struct wlr_xdg_surface_v6 *xdg_surface,
+ struct wlr_xdg_surface_v6 *parent,
+ struct wlr_xdg_positioner_v6_resource *positioner, int32_t id);
+void handle_xdg_surface_v6_popup_committed(struct wlr_xdg_surface_v6 *surface);
+struct wlr_xdg_popup_grab_v6 *get_xdg_shell_v6_popup_grab_from_seat(
+ struct wlr_xdg_shell_v6 *shell, struct wlr_seat *seat);
+void destroy_xdg_popup_v6(struct wlr_xdg_surface_v6 *surface);
+
+void create_xdg_toplevel_v6(struct wlr_xdg_surface_v6 *xdg_surface,
+ uint32_t id);
+void handle_xdg_surface_v6_toplevel_committed(struct wlr_xdg_surface_v6 *surface);
+void send_xdg_toplevel_v6_configure(struct wlr_xdg_surface_v6 *surface,
+ struct wlr_xdg_surface_v6_configure *configure);
+void handle_xdg_toplevel_v6_ack_configure(struct wlr_xdg_surface_v6 *surface,
+ struct wlr_xdg_surface_v6_configure *configure);
+bool compare_xdg_surface_v6_toplevel_state(struct wlr_xdg_toplevel_v6 *state);
+void destroy_xdg_toplevel_v6(struct wlr_xdg_surface_v6 *surface);
+
+#endif
diff --git a/include/util/array.h b/include/util/array.h
new file mode 100644
index 00000000..1c046e1d
--- /dev/null
+++ b/include/util/array.h
@@ -0,0 +1,9 @@
+#ifndef UTIL_ARRAY_H
+#define UTIL_ARRAY_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+size_t push_zeroes_to_end(uint32_t arr[], size_t n);
+
+#endif
diff --git a/include/util/shm.h b/include/util/shm.h
new file mode 100644
index 00000000..fb67b711
--- /dev/null
+++ b/include/util/shm.h
@@ -0,0 +1,7 @@
+#ifndef UTIL_SHM_H
+#define UTIL_SHM_H
+
+int create_shm_file(void);
+int allocate_shm_file(size_t size);
+
+#endif
diff --git a/include/util/signal.h b/include/util/signal.h
new file mode 100644
index 00000000..cc6cb525
--- /dev/null
+++ b/include/util/signal.h
@@ -0,0 +1,8 @@
+#ifndef UTIL_SIGNAL_H
+#define UTIL_SIGNAL_H
+
+#include <wayland-server.h>
+
+void wlr_signal_emit_safe(struct wl_signal *signal, void *data);
+
+#endif
diff --git a/include/wlr/backend.h b/include/wlr/backend.h
new file mode 100644
index 00000000..54f2b5e8
--- /dev/null
+++ b/include/wlr/backend.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_BACKEND_H
+#define WLR_BACKEND_H
+
+#include <wayland-server.h>
+#include <wlr/backend/session.h>
+#include <wlr/render/egl.h>
+
+struct wlr_backend_impl;
+
+struct wlr_backend {
+ const struct wlr_backend_impl *impl;
+
+ struct {
+ /** Raised when destroyed, passed the wlr_backend reference */
+ struct wl_signal destroy;
+ /** Raised when new inputs are added, passed the wlr_input_device */
+ struct wl_signal new_input;
+ /** Raised when new outputs are added, passed the wlr_output */
+ struct wl_signal new_output;
+ } events;
+};
+
+typedef struct wlr_renderer *(*wlr_renderer_create_func_t)(struct wlr_egl *egl, EGLenum platform,
+ void *remote_display, EGLint *config_attribs, EGLint visual_id);
+/**
+ * Automatically initializes the most suitable backend given the environment.
+ * Will always return a multibackend. The backend is created but not started.
+ * Returns NULL on failure.
+ *
+ * The compositor can request to initialize the backend's renderer by setting
+ * the create_render_func. The callback must initialize the given wlr_egl and
+ * return a valid wlr_renderer, or NULL if it has failed to initiaze it.
+ * Pass NULL as create_renderer_func to use the backend's default renderer.
+ */
+struct wlr_backend *wlr_backend_autocreate(struct wl_display *display,
+ wlr_renderer_create_func_t create_renderer_func);
+/**
+ * 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 wl_display is destroyed.
+ */
+void wlr_backend_destroy(struct wlr_backend *backend);
+/**
+ * Obtains the wlr_renderer reference this backend is using.
+ */
+struct wlr_renderer *wlr_backend_get_renderer(struct wlr_backend *backend);
+/**
+ * Obtains the wlr_session reference from this backend if there is any.
+ * Might return NULL for backends that don't use a session.
+ */
+struct wlr_session *wlr_backend_get_session(struct wlr_backend *backend);
+/**
+ * Returns the clock used by the backend for presentation feedback.
+ */
+clockid_t wlr_backend_get_presentation_clock(struct wlr_backend *backend);
+
+#endif
diff --git a/include/wlr/backend/drm.h b/include/wlr/backend/drm.h
new file mode 100644
index 00000000..3724adfb
--- /dev/null
+++ b/include/wlr/backend/drm.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_BACKEND_DRM_H
+#define WLR_BACKEND_DRM_H
+
+#include <wayland-server.h>
+#include <wlr/backend.h>
+#include <wlr/backend/session.h>
+#include <wlr/types/wlr_output.h>
+
+/**
+ * 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 wl_display *display,
+ struct wlr_session *session, int gpu_fd, struct wlr_backend *parent,
+ wlr_renderer_create_func_t create_renderer_func);
+
+bool wlr_backend_is_drm(struct wlr_backend *backend);
+bool wlr_output_is_drm(struct wlr_output *output);
+
+/**
+ * Add mode to the list of available modes
+ */
+typedef struct _drmModeModeInfo drmModeModeInfo;
+bool wlr_drm_connector_add_mode(struct wlr_output *output, const drmModeModeInfo *mode);
+
+#endif
diff --git a/include/wlr/backend/headless.h b/include/wlr/backend/headless.h
new file mode 100644
index 00000000..eab102e2
--- /dev/null
+++ b/include/wlr/backend/headless.h
@@ -0,0 +1,40 @@
+/*
+ * 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 <wlr/backend.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_output.h>
+
+/**
+ * Creates a headless backend. A headless backend has no outputs or inputs by
+ * default.
+ */
+struct wlr_backend *wlr_headless_backend_create(struct wl_display *display,
+ wlr_renderer_create_func_t create_renderer_func);
+/**
+ * Create a new headless output backed by an in-memory EGL framebuffer. You can
+ * read pixels from this framebuffer via wlr_renderer_read_pixels but it is
+ * otherwise not displayed.
+ */
+struct wlr_output *wlr_headless_add_output(struct wlr_backend *backend,
+ unsigned int width, unsigned int height);
+/**
+ * Creates a new input device. The caller is responsible for manually raising
+ * any event signals on the new input device if it wants to simulate input
+ * events.
+ */
+struct wlr_input_device *wlr_headless_add_input_device(
+ struct wlr_backend *backend, enum wlr_input_device_type type);
+bool wlr_backend_is_headless(struct wlr_backend *backend);
+bool wlr_input_device_is_headless(struct wlr_input_device *device);
+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 00000000..4a6a5cbb
--- /dev/null
+++ b/include/wlr/backend/interface.h
@@ -0,0 +1,32 @@
+/*
+ * 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 <stdbool.h>
+#include <time.h>
+#include <wlr/backend.h>
+#include <wlr/render/egl.h>
+
+struct wlr_backend_impl {
+ bool (*start)(struct wlr_backend *backend);
+ void (*destroy)(struct wlr_backend *backend);
+ struct wlr_renderer *(*get_renderer)(struct wlr_backend *backend);
+ struct wlr_session *(*get_session)(struct wlr_backend *backend);
+ clockid_t (*get_presentation_clock)(struct wlr_backend *backend);
+};
+
+/**
+ * Initializes common state on a wlr_backend and sets the implementation to the
+ * provided wlr_backend_impl reference.
+ */
+void wlr_backend_init(struct wlr_backend *backend,
+ const struct wlr_backend_impl *impl);
+
+#endif
diff --git a/include/wlr/backend/libinput.h b/include/wlr/backend/libinput.h
new file mode 100644
index 00000000..1a2ab294
--- /dev/null
+++ b/include/wlr/backend/libinput.h
@@ -0,0 +1,27 @@
+/*
+ * 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 <libinput.h>
+#include <wayland-server.h>
+#include <wlr/backend.h>
+#include <wlr/backend/session.h>
+#include <wlr/types/wlr_input_device.h>
+
+struct wlr_backend *wlr_libinput_backend_create(struct wl_display *display,
+ struct wlr_session *session);
+/** Gets the underlying libinput_device handle for the given wlr_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/meson.build b/include/wlr/backend/meson.build
new file mode 100644
index 00000000..3d6f0e40
--- /dev/null
+++ b/include/wlr/backend/meson.build
@@ -0,0 +1,16 @@
+install_headers(
+ 'drm.h',
+ 'headless.h',
+ 'interface.h',
+ 'libinput.h',
+ 'multi.h',
+ 'session.h',
+ 'wayland.h',
+ subdir: 'wlr/backend',
+)
+
+if conf_data.get('WLR_HAS_X11_BACKEND', 0) == 1
+ install_headers('x11.h', subdir: 'wlr/backend')
+endif
+
+subdir('session')
diff --git a/include/wlr/backend/multi.h b/include/wlr/backend/multi.h
new file mode 100644
index 00000000..0687f4b6
--- /dev/null
+++ b/include/wlr/backend/multi.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_BACKEND_MULTI_H
+#define WLR_BACKEND_MULTI_H
+
+#include <wlr/backend.h>
+#include <wlr/backend/session.h>
+
+/**
+ * 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_display *display);
+/**
+ * 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 00000000..7b26f34c
--- /dev/null
+++ b/include/wlr/backend/session.h
@@ -0,0 +1,94 @@
+#ifndef WLR_BACKEND_SESSION_H
+#define WLR_BACKEND_SESSION_H
+
+#include <libudev.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <wayland-server.h>
+
+struct session_impl;
+
+struct wlr_device {
+ int fd;
+ dev_t dev;
+ struct wl_signal signal;
+
+ struct wl_list link;
+};
+
+struct wlr_session {
+ const struct session_impl *impl;
+ /*
+ * Signal for when the session becomes active/inactive.
+ * It's called when we swap virtual terminal.
+ */
+ struct wl_signal session_signal;
+ 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 wl_list devices;
+
+ struct wl_listener display_destroy;
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+};
+
+/*
+ * 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.).
+ *
+ * If logind support is not enabled, you must have CAP_SYS_ADMIN or be root.
+ * It is safe to drop privileges after this is called.
+ *
+ * Returns NULL on error.
+ */
+struct wlr_session *wlr_session_create(struct wl_display *disp);
+
+/*
+ * 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.
+ *
+ * When the session becomes inactive:
+ * - DRM files lose their DRM master status
+ * - evdev files become invalid and should be closed
+ *
+ * Returns -errno on error.
+ */
+int 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, int fd);
+
+void wlr_session_signal_add(struct wlr_session *session, int fd,
+ struct wl_listener *listener);
+/*
+ * Changes the virtual terminal.
+ */
+bool wlr_session_change_vt(struct wlr_session *session, unsigned vt);
+
+size_t wlr_session_find_gpus(struct wlr_session *session,
+ size_t ret_len, int *ret);
+
+#endif
diff --git a/include/wlr/backend/session/interface.h b/include/wlr/backend/session/interface.h
new file mode 100644
index 00000000..5ccf9c8a
--- /dev/null
+++ b/include/wlr/backend/session/interface.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_BACKEND_SESSION_INTERFACE_H
+#define WLR_BACKEND_SESSION_INTERFACE_H
+
+#include <wlr/backend/session.h>
+
+struct session_impl {
+ struct wlr_session *(*create)(struct wl_display *disp);
+ void (*destroy)(struct wlr_session *session);
+ int (*open)(struct wlr_session *session, const char *path);
+ void (*close)(struct wlr_session *session, int fd);
+ bool (*change_vt)(struct wlr_session *session, unsigned vt);
+};
+
+#endif
diff --git a/include/wlr/backend/session/meson.build b/include/wlr/backend/session/meson.build
new file mode 100644
index 00000000..21b5a96b
--- /dev/null
+++ b/include/wlr/backend/session/meson.build
@@ -0,0 +1 @@
+install_headers('interface.h', subdir: 'wlr/backend/session')
diff --git a/include/wlr/backend/wayland.h b/include/wlr/backend/wayland.h
new file mode 100644
index 00000000..119ea247
--- /dev/null
+++ b/include/wlr/backend/wayland.h
@@ -0,0 +1,45 @@
+#ifndef WLR_BACKEND_WAYLAND_H
+#define WLR_BACKEND_WAYLAND_H
+
+#include <stdbool.h>
+#include <wayland-client.h>
+#include <wayland-server.h>
+#include <wlr/backend.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_output.h>
+
+/**
+ * Creates a new wlr_wl_backend. This backend will be created with no outputs;
+ * you must use wlr_wl_output_create to add them.
+ *
+ * The `remote` argument is the name of the host compositor wayland socket. Set
+ * to NULL for the default behaviour (WAYLAND_DISPLAY env variable or wayland-0
+ * default)
+ */
+struct wlr_backend *wlr_wl_backend_create(struct wl_display *display, const char *remote,
+ wlr_renderer_create_func_t create_renderer_func);
+
+/**
+ * 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 output_add signal).
+ */
+struct wlr_output *wlr_wl_output_create(struct wlr_backend *backend);
+
+/**
+ * True if the given backend is a wlr_wl_backend.
+ */
+bool wlr_backend_is_wl(struct wlr_backend *backend);
+
+/**
+ * True if the given input device is a wlr_wl_input_device.
+ */
+bool wlr_input_device_is_wl(struct wlr_input_device *device);
+
+/**
+ * True if the given output is a wlr_wl_output.
+ */
+bool wlr_output_is_wl(struct wlr_output *output);
+
+#endif
diff --git a/include/wlr/backend/x11.h b/include/wlr/backend/x11.h
new file mode 100644
index 00000000..5793a3b9
--- /dev/null
+++ b/include/wlr/backend/x11.h
@@ -0,0 +1,20 @@
+#ifndef WLR_BACKEND_X11_H
+#define WLR_BACKEND_X11_H
+
+#include <stdbool.h>
+
+#include <wayland-server.h>
+
+#include <wlr/backend.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_output.h>
+
+struct wlr_backend *wlr_x11_backend_create(struct wl_display *display,
+ const char *x11_display, wlr_renderer_create_func_t create_renderer_func);
+struct wlr_output *wlr_x11_output_create(struct wlr_backend *backend);
+
+bool wlr_backend_is_x11(struct wlr_backend *backend);
+bool wlr_input_device_is_x11(struct wlr_input_device *device);
+bool wlr_output_is_x11(struct wlr_output *output);
+
+#endif
diff --git a/include/wlr/config.h.in b/include/wlr/config.h.in
new file mode 100644
index 00000000..94273fac
--- /dev/null
+++ b/include/wlr/config.h.in
@@ -0,0 +1,16 @@
+#ifndef WLR_CONFIG_H
+#define WLR_CONFIG_H
+
+#mesondefine WLR_HAS_LIBCAP
+
+#mesondefine WLR_HAS_SYSTEMD
+#mesondefine WLR_HAS_ELOGIND
+
+#mesondefine WLR_HAS_X11_BACKEND
+
+#mesondefine WLR_HAS_XWAYLAND
+
+#mesondefine WLR_HAS_XCB_ERRORS
+#mesondefine WLR_HAS_XCB_ICCCM
+
+#endif
diff --git a/include/wlr/interfaces/meson.build b/include/wlr/interfaces/meson.build
new file mode 100644
index 00000000..7d4d811d
--- /dev/null
+++ b/include/wlr/interfaces/meson.build
@@ -0,0 +1,11 @@
+install_headers(
+ 'wlr_input_device.h',
+ 'wlr_keyboard.h',
+ 'wlr_output.h',
+ 'wlr_pointer.h',
+ 'wlr_switch.h',
+ 'wlr_tablet_pad.h',
+ 'wlr_tablet_tool.h',
+ 'wlr_touch.h',
+ subdir: 'wlr/interfaces',
+)
diff --git a/include/wlr/interfaces/wlr_input_device.h b/include/wlr/interfaces/wlr_input_device.h
new file mode 100644
index 00000000..05248bf6
--- /dev/null
+++ b/include/wlr/interfaces/wlr_input_device.h
@@ -0,0 +1,25 @@
+/*
+ * 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_INPUT_DEVICE_H
+#define WLR_INTERFACES_WLR_INPUT_DEVICE_H
+
+#include <wlr/types/wlr_input_device.h>
+
+struct wlr_input_device_impl {
+ void (*destroy)(struct wlr_input_device *wlr_device);
+};
+
+void wlr_input_device_init(
+ struct wlr_input_device *wlr_device,
+ enum wlr_input_device_type type,
+ const struct wlr_input_device_impl *impl,
+ const char *name, int vendor, int product);
+void wlr_input_device_destroy(struct wlr_input_device *dev);
+
+#endif
diff --git a/include/wlr/interfaces/wlr_keyboard.h b/include/wlr/interfaces/wlr_keyboard.h
new file mode 100644
index 00000000..5d537827
--- /dev/null
+++ b/include/wlr/interfaces/wlr_keyboard.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_INTERFACES_WLR_KEYBOARD_H
+#define WLR_INTERFACES_WLR_KEYBOARD_H
+
+#include <stdint.h>
+#include <wlr/types/wlr_keyboard.h>
+
+struct wlr_keyboard_impl {
+ void (*destroy)(struct wlr_keyboard *keyboard);
+ void (*led_update)(struct wlr_keyboard *keyboard, uint32_t leds);
+};
+
+void wlr_keyboard_init(struct wlr_keyboard *keyboard,
+ const struct wlr_keyboard_impl *impl);
+void wlr_keyboard_destroy(struct wlr_keyboard *keyboard);
+void wlr_keyboard_notify_key(struct wlr_keyboard *keyboard,
+ struct wlr_event_keyboard_key *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 00000000..f7ffe3b4
--- /dev/null
+++ b/include/wlr/interfaces/wlr_output.h
@@ -0,0 +1,52 @@
+/*
+ * 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 <stdbool.h>
+#include <wlr/backend.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_output.h>
+
+struct wlr_output_impl {
+ bool (*enable)(struct wlr_output *output, bool enable);
+ bool (*set_mode)(struct wlr_output *output, struct wlr_output_mode *mode);
+ bool (*set_custom_mode)(struct wlr_output *output, int32_t width,
+ int32_t height, int32_t refresh);
+ void (*transform)(struct wlr_output *output,
+ enum wl_output_transform transform);
+ bool (*set_cursor)(struct wlr_output *output, struct wlr_texture *texture,
+ int32_t scale, enum wl_output_transform transform,
+ int32_t hotspot_x, int32_t hotspot_y, bool update_texture);
+ bool (*move_cursor)(struct wlr_output *output, int x, int y);
+ void (*destroy)(struct wlr_output *output);
+ bool (*make_current)(struct wlr_output *output, int *buffer_age);
+ bool (*swap_buffers)(struct wlr_output *output, pixman_region32_t *damage);
+ bool (*set_gamma)(struct wlr_output *output, size_t size,
+ const uint16_t *r, const uint16_t *g, const uint16_t *b);
+ size_t (*get_gamma_size)(struct wlr_output *output);
+ bool (*export_dmabuf)(struct wlr_output *output,
+ struct wlr_dmabuf_attributes *attribs);
+ bool (*schedule_frame)(struct wlr_output *output);
+};
+
+void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend,
+ const struct wlr_output_impl *impl, struct wl_display *display);
+void wlr_output_update_mode(struct wlr_output *output,
+ struct wlr_output_mode *mode);
+void wlr_output_update_custom_mode(struct wlr_output *output, int32_t width,
+ int32_t height, int32_t refresh);
+void wlr_output_update_enabled(struct wlr_output *output, bool enabled);
+void wlr_output_update_needs_swap(struct wlr_output *output);
+void wlr_output_damage_whole(struct wlr_output *output);
+void wlr_output_send_frame(struct wlr_output *output);
+void wlr_output_send_present(struct wlr_output *output,
+ struct wlr_output_event_present *event);
+
+#endif
diff --git a/include/wlr/interfaces/wlr_pointer.h b/include/wlr/interfaces/wlr_pointer.h
new file mode 100644
index 00000000..fd3ab102
--- /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 <wlr/types/wlr_pointer.h>
+
+struct wlr_pointer_impl {
+ void (*destroy)(struct wlr_pointer *pointer);
+};
+
+void wlr_pointer_init(struct wlr_pointer *pointer,
+ const struct wlr_pointer_impl *impl);
+void wlr_pointer_destroy(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 00000000..0b0454f5
--- /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 <wlr/types/wlr_switch.h>
+
+struct wlr_switch_impl {
+ void (*destroy)(struct wlr_switch *lid_switch);
+};
+
+void wlr_switch_init(struct wlr_switch *lid_switch,
+ struct wlr_switch_impl *impl);
+void wlr_switch_destroy(struct wlr_switch *lid_switch);
+
+#endif
diff --git a/include/wlr/interfaces/wlr_tablet_pad.h b/include/wlr/interfaces/wlr_tablet_pad.h
new file mode 100644
index 00000000..86bbe9c3
--- /dev/null
+++ b/include/wlr/interfaces/wlr_tablet_pad.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_PAD_H
+#define WLR_INTERFACES_WLR_TABLET_PAD_H
+
+#include <wlr/types/wlr_tablet_pad.h>
+
+struct wlr_tablet_pad_impl {
+ void (*destroy)(struct wlr_tablet_pad *pad);
+};
+
+void wlr_tablet_pad_init(struct wlr_tablet_pad *pad,
+ struct wlr_tablet_pad_impl *impl);
+void wlr_tablet_pad_destroy(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 00000000..9cfc3ca0
--- /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 <wlr/types/wlr_tablet_tool.h>
+
+struct wlr_tablet_impl {
+ void (*destroy)(struct wlr_tablet *tablet);
+};
+
+void wlr_tablet_init(struct wlr_tablet *tablet,
+ struct wlr_tablet_impl *impl);
+void wlr_tablet_destroy(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 00000000..cc426332
--- /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 <wlr/types/wlr_touch.h>
+
+struct wlr_touch_impl {
+ void (*destroy)(struct wlr_touch *touch);
+};
+
+void wlr_touch_init(struct wlr_touch *touch,
+ struct wlr_touch_impl *impl);
+void wlr_touch_destroy(struct wlr_touch *touch);
+
+#endif
diff --git a/include/wlr/meson.build b/include/wlr/meson.build
new file mode 100644
index 00000000..8874dbc7
--- /dev/null
+++ b/include/wlr/meson.build
@@ -0,0 +1,26 @@
+version_array = meson.project_version().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])
+version_data.set('WLR_VERSION_API_CURRENT', so_version[0])
+version_data.set('WLR_VERSION_API_REVISION', so_version[1])
+version_data.set('WLR_VERSION_API_AGE', so_version[2])
+
+install_headers(
+ configure_file(input: 'config.h.in', output: 'config.h',configuration: conf_data),
+ configure_file(input: 'version.h.in', output: 'version.h', configuration: version_data),
+ 'backend.h',
+ 'xcursor.h',
+ subdir: 'wlr'
+)
+if conf_data.get('WLR_HAS_XWAYLAND', 0) == 1
+ install_headers('xwayland.h', subdir: 'wlr')
+endif
+
+subdir('backend')
+subdir('interfaces')
+subdir('render')
+subdir('types')
+subdir('util')
diff --git a/include/wlr/render/dmabuf.h b/include/wlr/render/dmabuf.h
new file mode 100644
index 00000000..32cfe874
--- /dev/null
+++ b/include/wlr/render/dmabuf.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_RENDER_DMABUF_H
+#define WLR_RENDER_DMABUF_H
+
+#include <stdint.h>
+
+// So we don't have to pull in linux specific drm headers
+#ifndef DRM_FORMAT_MOD_INVALID
+#define DRM_FORMAT_MOD_INVALID ((1ULL<<56) - 1)
+#endif
+
+#ifndef DRM_FORMAT_MOD_LINEAR
+#define DRM_FORMAT_MOD_LINEAR 0
+#endif
+
+#define WLR_DMABUF_MAX_PLANES 4
+
+enum wlr_dmabuf_attributes_flags {
+ WLR_DMABUF_ATTRIBUTES_FLAGS_Y_INVERT = 1,
+ WLR_DMABUF_ATTRIBUTES_FLAGS_INTERLACED = 2,
+ WLR_DMABUF_ATTRIBUTES_FLAGS_BOTTOM_FIRST = 4,
+};
+
+struct wlr_dmabuf_attributes {
+ int32_t width, height;
+ uint32_t format;
+ uint32_t flags; // enum wlr_dmabuf_attributes_flags
+ uint64_t modifier;
+
+ 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);
+
+#endif
diff --git a/include/wlr/render/egl.h b/include/wlr/render/egl.h
new file mode 100644
index 00000000..269af7e2
--- /dev/null
+++ b/include/wlr/render/egl.h
@@ -0,0 +1,118 @@
+/*
+ * 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
+
+#include <wlr/config.h>
+
+#if !WLR_HAS_X11_BACKEND && !WLR_HAS_XWAYLAND && !defined MESA_EGL_NO_X11_HEADERS
+#define MESA_EGL_NO_X11_HEADERS
+#endif
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <pixman.h>
+#include <stdbool.h>
+#include <wayland-server.h>
+#include <wlr/render/dmabuf.h>
+
+struct wlr_egl {
+ EGLenum platform;
+ EGLDisplay display;
+ EGLConfig config;
+ EGLContext context;
+
+ const char *exts_str;
+
+ struct {
+ bool bind_wayland_display_wl;
+ bool buffer_age_ext;
+ bool image_base_khr;
+ bool image_dma_buf_export_mesa;
+ bool image_dmabuf_import_ext;
+ bool image_dmabuf_import_modifiers_ext;
+ bool swap_buffers_with_damage_ext;
+ bool swap_buffers_with_damage_khr;
+ } exts;
+
+ struct wl_display *wl_display;
+};
+
+// TODO: Allocate and return a wlr_egl
+/**
+ * Initializes an EGL context for the given platform and remote display.
+ * Will attempt to load all possibly required api functions.
+ */
+bool wlr_egl_init(struct wlr_egl *egl, EGLenum platform, void *remote_display,
+ EGLint *config_attribs, EGLint visual_id);
+
+/**
+ * Frees all related EGL resources, makes the context not-current and
+ * unbinds a bound wayland display.
+ */
+void wlr_egl_finish(struct wlr_egl *egl);
+
+/**
+ * Binds the given display to the EGL instance.
+ * This will allow clients to create EGL surfaces from wayland ones and render
+ * to it.
+ */
+bool wlr_egl_bind_display(struct wlr_egl *egl, struct wl_display *local_display);
+
+/**
+ * Returns a surface for the given native window
+ * The window must match the remote display the wlr_egl was created with.
+ */
+EGLSurface wlr_egl_create_surface(struct wlr_egl *egl, void *window);
+
+/**
+ * Creates an EGL image from the given wl_drm buffer resource.
+ */
+EGLImageKHR wlr_egl_create_image_from_wl_drm(struct wlr_egl *egl,
+ struct wl_resource *data, EGLint *fmt, int *width, int *height,
+ bool *inverted_y);
+
+/**
+ * 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);
+
+/**
+ * Get the available dmabuf formats
+ */
+int wlr_egl_get_dmabuf_formats(struct wlr_egl *egl, int **formats);
+
+/**
+ * Get the available dmabuf modifiers for a given format
+ */
+int wlr_egl_get_dmabuf_modifiers(struct wlr_egl *egl, int format,
+ uint64_t **modifiers);
+
+bool wlr_egl_export_image_to_dmabuf(struct wlr_egl *egl, EGLImageKHR image,
+ int32_t width, int32_t height, uint32_t flags,
+ struct wlr_dmabuf_attributes *attribs);
+
+/**
+ * Destroys an EGL image created with the given wlr_egl.
+ */
+bool wlr_egl_destroy_image(struct wlr_egl *egl, EGLImageKHR image);
+
+bool wlr_egl_make_current(struct wlr_egl *egl, EGLSurface surface,
+ int *buffer_age);
+
+bool wlr_egl_is_current(struct wlr_egl *egl);
+
+bool wlr_egl_swap_buffers(struct wlr_egl *egl, EGLSurface surface,
+ pixman_region32_t *damage);
+
+bool wlr_egl_destroy_surface(struct wlr_egl *egl, EGLSurface surface);
+
+#endif
diff --git a/include/wlr/render/gles2.h b/include/wlr/render/gles2.h
new file mode 100644
index 00000000..fca11ab8
--- /dev/null
+++ b/include/wlr/render/gles2.h
@@ -0,0 +1,27 @@
+/*
+ * 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 <wlr/backend.h>
+#include <wlr/render/wlr_renderer.h>
+
+struct wlr_egl;
+
+struct wlr_renderer *wlr_gles2_renderer_create(struct wlr_egl *egl);
+
+struct wlr_texture *wlr_gles2_texture_from_pixels(struct wlr_egl *egl,
+ enum wl_shm_format wl_fmt, uint32_t stride, uint32_t width, uint32_t height,
+ const void *data);
+struct wlr_texture *wlr_gles2_texture_from_wl_drm(struct wlr_egl *egl,
+ struct wl_resource *data);
+struct wlr_texture *wlr_gles2_texture_from_dmabuf(struct wlr_egl *egl,
+ struct wlr_dmabuf_attributes *attribs);
+
+#endif
diff --git a/include/wlr/render/interface.h b/include/wlr/render/interface.h
new file mode 100644
index 00000000..c98a7cda
--- /dev/null
+++ b/include/wlr/render/interface.h
@@ -0,0 +1,87 @@
+/*
+ * 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 <wlr/config.h>
+
+#if !WLR_HAS_X11_BACKEND && !WLR_HAS_XWAYLAND && !defined MESA_EGL_NO_X11_HEADERS
+#define MESA_EGL_NO_X11_HEADERS
+#endif
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <stdbool.h>
+#include <wayland-server-protocol.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/render/wlr_texture.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/render/dmabuf.h>
+
+struct wlr_renderer_impl {
+ void (*begin)(struct wlr_renderer *renderer, uint32_t width,
+ uint32_t height);
+ void (*end)(struct wlr_renderer *renderer);
+ void (*clear)(struct wlr_renderer *renderer, const float color[static 4]);
+ void (*scissor)(struct wlr_renderer *renderer, struct wlr_box *box);
+ bool (*render_texture_with_matrix)(struct wlr_renderer *renderer,
+ struct wlr_texture *texture, const float matrix[static 9],
+ float alpha);
+ void (*render_quad_with_matrix)(struct wlr_renderer *renderer,
+ const float color[static 4], const float matrix[static 9]);
+ void (*render_ellipse_with_matrix)(struct wlr_renderer *renderer,
+ const float color[static 4], const float matrix[static 9]);
+ const enum wl_shm_format *(*formats)(
+ struct wlr_renderer *renderer, size_t *len);
+ bool (*format_supported)(struct wlr_renderer *renderer,
+ enum wl_shm_format fmt);
+ bool (*resource_is_wl_drm_buffer)(struct wlr_renderer *renderer,
+ struct wl_resource *resource);
+ void (*wl_drm_buffer_get_size)(struct wlr_renderer *renderer,
+ struct wl_resource *buffer, int *width, int *height);
+ int (*get_dmabuf_formats)(struct wlr_renderer *renderer, int **formats);
+ int (*get_dmabuf_modifiers)(struct wlr_renderer *renderer, int format,
+ uint64_t **modifiers);
+ enum wl_shm_format (*preferred_read_format)(struct wlr_renderer *renderer);
+ bool (*read_pixels)(struct wlr_renderer *renderer, enum wl_shm_format fmt,
+ uint32_t *flags, 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);
+ struct wlr_texture *(*texture_from_pixels)(struct wlr_renderer *renderer,
+ enum wl_shm_format fmt, uint32_t stride, uint32_t width,
+ uint32_t height, const void *data);
+ struct wlr_texture *(*texture_from_wl_drm)(struct wlr_renderer *renderer,
+ struct wl_resource *data);
+ struct wlr_texture *(*texture_from_dmabuf)(struct wlr_renderer *renderer,
+ struct wlr_dmabuf_attributes *attribs);
+ void (*destroy)(struct wlr_renderer *renderer);
+ void (*init_wl_display)(struct wlr_renderer *renderer,
+ struct wl_display *wl_display);
+};
+
+void wlr_renderer_init(struct wlr_renderer *renderer,
+ const struct wlr_renderer_impl *impl);
+
+struct wlr_texture_impl {
+ void (*get_size)(struct wlr_texture *texture, int *width, int *height);
+ bool (*is_opaque)(struct wlr_texture *texture);
+ bool (*write_pixels)(struct wlr_texture *texture,
+ 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,
+ const void *data);
+ bool (*to_dmabuf)(struct wlr_texture *texture,
+ struct wlr_dmabuf_attributes *attribs);
+ void (*destroy)(struct wlr_texture *texture);
+};
+
+void wlr_texture_init(struct wlr_texture *texture,
+ const struct wlr_texture_impl *impl);
+
+#endif
diff --git a/include/wlr/render/meson.build b/include/wlr/render/meson.build
new file mode 100644
index 00000000..05127bb7
--- /dev/null
+++ b/include/wlr/render/meson.build
@@ -0,0 +1,9 @@
+install_headers(
+ 'dmabuf.h',
+ 'egl.h',
+ 'gles2.h',
+ 'interface.h',
+ 'wlr_renderer.h',
+ 'wlr_texture.h',
+ subdir: 'wlr/render'
+)
diff --git a/include/wlr/render/wlr_renderer.h b/include/wlr/render/wlr_renderer.h
new file mode 100644
index 00000000..9c031b7f
--- /dev/null
+++ b/include/wlr/render/wlr_renderer.h
@@ -0,0 +1,121 @@
+/*
+ * 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 <stdint.h>
+#include <wayland-server-protocol.h>
+#include <wlr/render/egl.h>
+#include <wlr/render/wlr_texture.h>
+#include <wlr/types/wlr_box.h>
+
+enum wlr_renderer_read_pixels_flags {
+ WLR_RENDERER_READ_PIXELS_Y_INVERT = 1,
+};
+
+struct wlr_renderer_impl;
+
+struct wlr_renderer {
+ const struct wlr_renderer_impl *impl;
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+};
+
+struct wlr_renderer *wlr_renderer_autocreate(struct wlr_egl *egl, EGLenum platform,
+ void *remote_display, EGLint *config_attribs, EGLint visual_id);
+
+void wlr_renderer_begin(struct wlr_renderer *r, int width, int height);
+void wlr_renderer_end(struct wlr_renderer *r);
+void wlr_renderer_clear(struct wlr_renderer *r, const float color[static 4]);
+/**
+ * Defines a scissor box. Only pixels that lie within the scissor box can be
+ * modified by drawing functions. Providing a NULL `box` disables the scissor
+ * box.
+ */
+void wlr_renderer_scissor(struct wlr_renderer *r, struct wlr_box *box);
+/**
+ * Renders the requested texture.
+ */
+bool wlr_render_texture(struct wlr_renderer *r, struct wlr_texture *texture,
+ const float projection[static 9], int x, int y, float alpha);
+/**
+ * Renders the requested texture using the provided matrix.
+ */
+bool wlr_render_texture_with_matrix(struct wlr_renderer *r,
+ struct wlr_texture *texture, const float matrix[static 9], float alpha);
+/**
+ * Renders a solid rectangle in the specified color.
+ */
+void wlr_render_rect(struct wlr_renderer *r, const struct wlr_box *box,
+ const float color[static 4], const float projection[static 9]);
+/**
+ * Renders a solid quadrangle in the specified color with the specified matrix.
+ */
+void wlr_render_quad_with_matrix(struct wlr_renderer *r,
+ const float color[static 4], const float matrix[static 9]);
+/**
+ * Renders a solid ellipse in the specified color.
+ */
+void wlr_render_ellipse(struct wlr_renderer *r, const struct wlr_box *box,
+ const float color[static 4], const float projection[static 9]);
+/**
+ * Renders a solid ellipse in the specified color with the specified matrix.
+ */
+void wlr_render_ellipse_with_matrix(struct wlr_renderer *r,
+ const float color[static 4], const float matrix[static 9]);
+/**
+ * Returns a list of pixel formats supported by this renderer.
+ */
+const enum wl_shm_format *wlr_renderer_get_formats(struct wlr_renderer *r,
+ size_t *len);
+/**
+ * Returns true if this wl_buffer is a wl_drm buffer.
+ */
+bool wlr_renderer_resource_is_wl_drm_buffer(struct wlr_renderer *renderer,
+ struct wl_resource *buffer);
+/**
+ * Gets the width and height of a wl_drm buffer.
+ */
+void wlr_renderer_wl_drm_buffer_get_size(struct wlr_renderer *renderer,
+ struct wl_resource *buffer, int *width, int *height);
+/**
+ * Get the available dmabuf formats
+ */
+int wlr_renderer_get_dmabuf_formats(struct wlr_renderer *renderer,
+ int **formats);
+/**
+ * Get the available dmabuf modifiers for a given format
+ */
+int wlr_renderer_get_dmabuf_modifiers(struct wlr_renderer *renderer, int format,
+ uint64_t **modifiers);
+/**
+ * Reads out of pixels of the currently bound surface into data. `stride` is in
+ * bytes.
+ *
+ * If `flags` is not NULl, the caller indicates that it accepts frame flags
+ * defined in `enum wlr_renderer_read_pixels_flags`.
+ */
+bool wlr_renderer_read_pixels(struct wlr_renderer *r, enum wl_shm_format fmt,
+ uint32_t *flags, 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);
+/**
+ * Checks if a format is supported.
+ */
+bool wlr_renderer_format_supported(struct wlr_renderer *r,
+ enum wl_shm_format fmt);
+void wlr_renderer_init_wl_display(struct wlr_renderer *r,
+ struct wl_display *wl_display);
+/**
+ * Destroys this wlr_renderer. Textures must be destroyed separately.
+ */
+void wlr_renderer_destroy(struct wlr_renderer *renderer);
+
+#endif
diff --git a/include/wlr/render/wlr_texture.h b/include/wlr/render/wlr_texture.h
new file mode 100644
index 00000000..f210717a
--- /dev/null
+++ b/include/wlr/render/wlr_texture.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_RENDER_WLR_TEXTURE_H
+#define WLR_RENDER_WLR_TEXTURE_H
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <stdint.h>
+#include <wayland-server-protocol.h>
+#include <wlr/render/dmabuf.h>
+
+struct wlr_renderer;
+struct wlr_texture_impl;
+
+struct wlr_texture {
+ const struct wlr_texture_impl *impl;
+};
+
+/**
+ * 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,
+ enum wl_shm_format wl_fmt, uint32_t stride, uint32_t width, uint32_t height,
+ const void *data);
+
+/**
+ * Create a new texture from a wl_drm resource. The returned texture is
+ * immutable.
+ */
+struct wlr_texture *wlr_texture_from_wl_drm(struct wlr_renderer *renderer,
+ struct wl_resource *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);
+
+/**
+ * Get the texture width and height.
+ */
+void wlr_texture_get_size(struct wlr_texture *texture, int *width, int *height);
+
+/**
+ * Returns true if this texture is using a fully opaque format.
+ */
+bool wlr_texture_is_opaque(struct wlr_texture *texture);
+
+/**
+ * Update a texture with raw pixels. The texture must be mutable, and the input
+ * data must have the same pixel format that the texture was created with.
+ */
+bool wlr_texture_write_pixels(struct wlr_texture *texture,
+ 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,
+ const void *data);
+
+bool wlr_texture_to_dmabuf(struct wlr_texture *texture,
+ struct wlr_dmabuf_attributes *attribs);
+
+/**
+ * Destroys this wlr_texture.
+ */
+void wlr_texture_destroy(struct wlr_texture *texture);
+
+#endif
diff --git a/include/wlr/types/meson.build b/include/wlr/types/meson.build
new file mode 100644
index 00000000..752c0dea
--- /dev/null
+++ b/include/wlr/types/meson.build
@@ -0,0 +1,49 @@
+install_headers(
+ 'wlr_box.h',
+ 'wlr_buffer.h',
+ 'wlr_compositor.h',
+ 'wlr_cursor.h',
+ 'wlr_data_device.h',
+ 'wlr_export_dmabuf_v1.h',
+ 'wlr_foreign_toplevel_management_v1.h',
+ 'wlr_gamma_control_v1.h',
+ 'wlr_gamma_control.h',
+ 'wlr_gtk_primary_selection.h',
+ 'wlr_idle_inhibit_v1.h',
+ 'wlr_idle.h',
+ 'wlr_input_device.h',
+ 'wlr_input_inhibitor.h',
+ 'wlr_input_method_v2.h',
+ 'wlr_keyboard.h',
+ 'wlr_layer_shell_v1.h',
+ 'wlr_linux_dmabuf_v1.h',
+ 'wlr_list.h',
+ 'wlr_matrix.h',
+ 'wlr_output_damage.h',
+ 'wlr_output_layout.h',
+ 'wlr_output.h',
+ 'wlr_pointer.h',
+ 'wlr_pointer_constraints_v1.h',
+ 'wlr_presentation_time.h',
+ 'wlr_primary_selection.h',
+ 'wlr_region.h',
+ 'wlr_screencopy_v1.h',
+ 'wlr_screenshooter.h',
+ 'wlr_seat.h',
+ 'wlr_server_decoration.h',
+ 'wlr_surface.h',
+ 'wlr_switch.h',
+ 'wlr_tablet_pad.h',
+ 'wlr_tablet_tool.h',
+ 'wlr_tablet_v2.h',
+ 'wlr_text_input_v3.h',
+ 'wlr_touch.h',
+ 'wlr_virtual_keyboard_v1.h',
+ 'wlr_wl_shell.h',
+ 'wlr_xcursor_manager.h',
+ 'wlr_xdg_decoration_v1.h',
+ 'wlr_xdg_output_v1.h',
+ 'wlr_xdg_shell_v6.h',
+ 'wlr_xdg_shell.h',
+ subdir: 'wlr/types',
+)
diff --git a/include/wlr/types/wlr_box.h b/include/wlr/types/wlr_box.h
new file mode 100644
index 00000000..40fd69bd
--- /dev/null
+++ b/include/wlr/types/wlr_box.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_BOX_H
+#define WLR_TYPES_WLR_BOX_H
+
+#include <pixman.h>
+#include <stdbool.h>
+#include <wayland-server.h>
+
+struct wlr_box {
+ int x, y;
+ int width, height;
+};
+
+void wlr_box_closest_point(const struct wlr_box *box, double x, double y,
+ double *dest_x, double *dest_y);
+
+bool wlr_box_intersection(struct wlr_box *dest, const struct wlr_box *box_a,
+ const struct wlr_box *box_b);
+
+bool wlr_box_contains_point(const struct wlr_box *box, double x, double y);
+
+bool wlr_box_empty(const struct wlr_box *box);
+
+/**
+ * Transforms a box inside a `width` x `height` box.
+ */
+void wlr_box_transform(struct wlr_box *dest, const struct wlr_box *box,
+ enum wl_output_transform transform, int width, int height);
+
+/**
+ * Creates the smallest box that contains the box rotated about its center.
+ */
+void wlr_box_rotated_bounds(struct wlr_box *dest, const struct wlr_box *box, float rotation);
+
+void wlr_box_from_pixman_box32(struct wlr_box *dest, const pixman_box32_t box);
+
+#endif
diff --git a/include/wlr/types/wlr_buffer.h b/include/wlr/types/wlr_buffer.h
new file mode 100644
index 00000000..0c987b17
--- /dev/null
+++ b/include/wlr/types/wlr_buffer.h
@@ -0,0 +1,71 @@
+/*
+ * 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 <pixman.h>
+#include <wayland-server.h>
+
+/**
+ * A client buffer.
+ */
+struct wlr_buffer {
+ /**
+ * The buffer resource, if any. Will be NULL if the client destroys it.
+ */
+ struct wl_resource *resource;
+ /**
+ * 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;
+ bool released;
+ size_t n_refs;
+
+ struct wl_listener resource_destroy;
+};
+
+struct wlr_renderer;
+
+/**
+ * Check if a resource is a wl_buffer resource.
+ */
+bool wlr_resource_is_buffer(struct wl_resource *resource);
+/**
+ * Get the size of a wl_buffer resource.
+ */
+bool wlr_buffer_get_resource_size(struct wl_resource *resource,
+ struct wlr_renderer *renderer, int *width, int *height);
+
+/**
+ * Upload a buffer to the GPU and reference it.
+ */
+struct wlr_buffer *wlr_buffer_create(struct wlr_renderer *renderer,
+ struct wl_resource *resource);
+/**
+ * Reference the buffer.
+ */
+struct wlr_buffer *wlr_buffer_ref(struct wlr_buffer *buffer);
+/**
+ * Unreference the buffer. After this call, `buffer` may not be accessed
+ * anymore.
+ */
+void wlr_buffer_unref(struct wlr_buffer *buffer);
+/**
+ * Try to update the buffer's content. On success, returns the updated buffer
+ * and destroys the provided `buffer`. On error, `buffer` is intact and NULL is
+ * returned.
+ *
+ * Fails if there's more than one reference to the buffer or if the texture
+ * isn't mutable.
+ */
+struct wlr_buffer *wlr_buffer_apply_damage(struct wlr_buffer *buffer,
+ struct wl_resource *resource, pixman_region32_t *damage);
+
+#endif
diff --git a/include/wlr/types/wlr_compositor.h b/include/wlr/types/wlr_compositor.h
new file mode 100644
index 00000000..36b9e83f
--- /dev/null
+++ b/include/wlr/types/wlr_compositor.h
@@ -0,0 +1,53 @@
+/*
+ * 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 <wayland-server.h>
+#include <wlr/render/wlr_renderer.h>
+
+struct wlr_surface;
+
+struct wlr_subcompositor {
+ struct wl_global *global;
+ struct wl_list resources;
+ struct wl_list subsurface_resources;
+};
+
+struct wlr_compositor {
+ struct wl_global *global;
+ struct wl_list resources;
+ struct wlr_renderer *renderer;
+ struct wl_list surface_resources;
+ struct wl_list region_resources;
+
+ struct wlr_subcompositor subcompositor;
+
+ struct wl_listener display_destroy;
+
+ struct {
+ struct wl_signal new_surface;
+ struct wl_signal destroy;
+ } events;
+};
+
+void wlr_compositor_destroy(struct wlr_compositor *wlr_compositor);
+struct wlr_compositor *wlr_compositor_create(struct wl_display *display,
+ struct wlr_renderer *renderer);
+
+bool wlr_surface_is_subsurface(struct wlr_surface *surface);
+
+/**
+ * Get a subsurface from a surface. Can return NULL if the subsurface has been
+ * destroyed.
+ */
+struct wlr_subsurface *wlr_subsurface_from_wlr_surface(
+ 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 00000000..44ced1f0
--- /dev/null
+++ b/include/wlr/types/wlr_cursor.h
@@ -0,0 +1,194 @@
+/*
+ * 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 <wayland-server.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_output.h>
+
+/**
+ * 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 wlr_output,
+ * wlr_output_layout, and 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 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_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 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 touch_up;
+ struct wl_signal touch_down;
+ struct wl_signal touch_motion;
+ struct wl_signal touch_cancel;
+
+ 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 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 image. stride is given in bytes. If pixels is NULL, hides the
+ * cursor.
+ *
+ * If scale isn't zero, the image is only set on outputs having the provided
+ * scale.
+ */
+void wlr_cursor_set_image(struct wlr_cursor *cur, const uint8_t *pixels,
+ int32_t stride, uint32_t width, uint32_t height, int32_t hotspot_x,
+ int32_t hotspot_y, float scale);
+
+/**
+ * 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_TOOL
+ */
+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 wlr_output_layout.
+ */
+void wlr_cursor_map_to_region(struct wlr_cursor *cur, struct wlr_box *box);
+
+/**
+ * Maps inputs from this input device to an arbitrary region on the associated
+ * wlr_output_layout.
+ */
+void wlr_cursor_map_input_to_region(struct wlr_cursor *cur,
+ struct wlr_input_device *dev, struct wlr_box *box);
+
+#endif
diff --git a/include/wlr/types/wlr_data_device.h b/include/wlr/types/wlr_data_device.h
new file mode 100644
index 00000000..9c4ce995
--- /dev/null
+++ b/include/wlr/types/wlr_data_device.h
@@ -0,0 +1,231 @@
+/*
+ * 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 <wayland-server.h>
+#include <wlr/types/wlr_seat.h>
+
+extern const struct wlr_pointer_grab_interface
+ wlr_data_device_pointer_drag_interface;
+
+extern const struct wlr_keyboard_grab_interface
+ wlr_data_device_keyboard_drag_interface;
+
+extern const struct wlr_touch_grab_interface
+ wlr_data_device_touch_drag_interface;
+
+struct wlr_data_device_manager {
+ struct wl_global *global;
+ struct wl_list resources;
+ struct wl_list data_sources;
+
+ struct wl_listener display_destroy;
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+
+ void *data;
+};
+
+struct wlr_data_offer {
+ struct wl_resource *resource;
+ struct wlr_data_source *source;
+
+ 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 wl_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 (*cancel)(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_icon {
+ struct wlr_surface *surface;
+ struct wlr_seat_client *client;
+ struct wl_list link; // wlr_seat::drag_icons
+ bool mapped;
+
+ bool is_pointer;
+ int32_t touch_id;
+
+ struct {
+ struct wl_signal map;
+ struct wl_signal unmap;
+ struct wl_signal destroy;
+ } events;
+
+ struct wl_listener surface_destroy;
+ struct wl_listener seat_client_destroy;
+
+ void *data;
+};
+
+struct wlr_drag {
+ 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 wlr_seat_client *seat_client;
+ struct wlr_seat_client *focus_client;
+
+ bool is_pointer_grab;
+
+ struct wlr_drag_icon *icon;
+ struct wlr_surface *focus;
+ struct wlr_data_source *source;
+
+ bool cancelling;
+ int32_t grab_touch_id;
+
+ struct wl_listener point_destroy;
+ struct wl_listener source_destroy;
+ struct wl_listener seat_client_destroy;
+ struct wl_listener icon_destroy;
+
+ struct {
+ struct wl_signal focus;
+ struct wl_signal motion;
+ struct wl_signal drop;
+ struct wl_signal destroy;
+ } events;
+};
+
+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);
+
+/**
+ * Destroys a wlr_data_device_manager and removes its wl_data_device_manager global.
+ */
+void wlr_data_device_manager_destroy(struct wlr_data_device_manager *manager);
+
+/**
+ * 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 wlr_seat_client_send_selection(struct wlr_seat_client *seat_client);
+
+/**
+ * Sets the current selection for the seat. This removes the previous one if
+ * there was any.
+ */
+void wlr_seat_set_selection(struct wlr_seat *seat,
+ struct wlr_data_source *source, uint32_t serial);
+
+/**
+ * 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);
+
+/**
+ * Finishes the data source.
+ */
+void wlr_data_source_finish(struct wlr_data_source *source);
+
+/**
+ * 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
+ * potentially destroys immediately the data source.
+ */
+void wlr_data_source_cancel(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_export_dmabuf_v1.h b/include/wlr/types/wlr_export_dmabuf_v1.h
new file mode 100644
index 00000000..204da985
--- /dev/null
+++ b/include/wlr/types/wlr_export_dmabuf_v1.h
@@ -0,0 +1,46 @@
+/*
+ * 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 <stdbool.h>
+#include <wayland-server.h>
+#include <wlr/render/dmabuf.h>
+
+struct wlr_export_dmabuf_manager_v1 {
+ struct wl_global *global;
+ struct wl_list resources; // wl_resource_get_link
+ 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_dmabuf_attributes attribs;
+ struct wlr_output *output;
+
+ bool cursor_locked;
+
+ struct wl_listener output_swap_buffers;
+};
+
+struct wlr_export_dmabuf_manager_v1 *wlr_export_dmabuf_manager_v1_create(
+ struct wl_display *display);
+void wlr_export_dmabuf_manager_v1_destroy(
+ struct wlr_export_dmabuf_manager_v1 *manager);
+
+#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 00000000..75ae0e64
--- /dev/null
+++ b/include/wlr/types/wlr_foreign_toplevel_management_v1.h
@@ -0,0 +1,120 @@
+/*
+ * 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 <wayland-server.h>
+#include <wlr/types/wlr_output.h>
+
+struct wlr_foreign_toplevel_manager_v1 {
+ struct wl_event_loop *event_loop;
+ struct wl_global *global;
+ struct wl_list resources;
+ 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,
+ WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED = 2,
+ WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED = 4,
+};
+
+struct wlr_foreign_toplevel_handle_v1_output {
+ struct wl_list link; // wlr_foreign_toplevel_handle_v1::outputs
+ struct wl_listener output_destroy;
+ struct wlr_output *output;
+
+ struct wlr_foreign_toplevel_handle_v1 *toplevel;
+};
+
+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 wl_list outputs; // wlr_foreign_toplevel_v1_output
+ uint32_t state; // wlr_foreign_toplevel_v1_state
+
+ struct {
+ // wlr_foreign_toplevel_handle_v1_maximized_event
+ struct wl_signal request_maximize;
+ //wlr_foreign_toplevel_handle_v1_minimized_event
+ struct wl_signal request_minimize;
+ //wlr_foreign_toplevel_handle_v1_activated_event
+ struct wl_signal request_activate;
+ struct wl_signal request_close;
+
+ //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_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);
+void wlr_foreign_toplevel_manager_v1_destroy(
+ struct wlr_foreign_toplevel_manager_v1 *manager);
+
+struct wlr_foreign_toplevel_handle_v1 *wlr_foreign_toplevel_handle_v1_create(
+ struct wlr_foreign_toplevel_manager_v1 *manager);
+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);
+
+#endif
diff --git a/include/wlr/types/wlr_gamma_control.h b/include/wlr/types/wlr_gamma_control.h
new file mode 100644
index 00000000..912a413c
--- /dev/null
+++ b/include/wlr/types/wlr_gamma_control.h
@@ -0,0 +1,46 @@
+/*
+ * 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_GAMMA_CONTROL_H
+#define WLR_TYPES_WLR_GAMMA_CONTROL_H
+
+#include <wayland-server.h>
+
+struct wlr_gamma_control_manager {
+ struct wl_global *global;
+ struct wl_list controls; // wlr_gamma_control::link
+
+ struct wl_listener display_destroy;
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+
+ void *data;
+};
+
+struct wlr_gamma_control {
+ struct wl_resource *resource;
+ struct wlr_output *output;
+ struct wl_list link;
+
+ struct wl_listener output_destroy_listener;
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+
+ void* data;
+};
+
+struct wlr_gamma_control_manager *wlr_gamma_control_manager_create(
+ struct wl_display *display);
+void wlr_gamma_control_manager_destroy(
+ struct wlr_gamma_control_manager *gamma_control_manager);
+
+#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 00000000..f186aa81
--- /dev/null
+++ b/include/wlr/types/wlr_gamma_control_v1.h
@@ -0,0 +1,35 @@
+#ifndef WLR_TYPES_WLR_GAMMA_CONTROL_V1_H
+#define WLR_TYPES_WLR_GAMMA_CONTROL_V1_H
+
+#include <wayland-server.h>
+
+struct wlr_gamma_control_manager_v1 {
+ struct wl_global *global;
+ struct wl_list resources;
+ struct wl_list controls; // wlr_gamma_control_v1::link
+
+ struct wl_listener display_destroy;
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+
+ void *data;
+};
+
+struct wlr_gamma_control_v1 {
+ struct wl_resource *resource;
+ struct wlr_output *output;
+ struct wl_list link;
+
+ struct wl_listener output_destroy_listener;
+
+ void *data;
+};
+
+struct wlr_gamma_control_manager_v1 *wlr_gamma_control_manager_v1_create(
+ struct wl_display *display);
+void wlr_gamma_control_manager_v1_destroy(
+ struct wlr_gamma_control_manager_v1 *manager);
+
+#endif
diff --git a/include/wlr/types/wlr_gtk_primary_selection.h b/include/wlr/types/wlr_gtk_primary_selection.h
new file mode 100644
index 00000000..436a50d2
--- /dev/null
+++ b/include/wlr/types/wlr_gtk_primary_selection.h
@@ -0,0 +1,53 @@
+/*
+ * 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_GTK_PRIMARY_SELECTION_H
+#define WLR_TYPES_WLR_GTK_PRIMARY_SELECTION_H
+
+#include <wayland-server.h>
+#include <wlr/types/wlr_seat.h>
+
+struct wlr_gtk_primary_selection_device_manager {
+ struct wl_global *global;
+ struct wl_list resources; // wl_resource_get_link
+ struct wl_list devices; // wlr_gtk_primary_selection_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_gtk_primary_selection_device {
+ struct wlr_gtk_primary_selection_device_manager *manager;
+ struct wlr_seat *seat;
+ struct wl_list link; // wlr_gtk_primary_selection_device_manager::devices
+ struct wl_list resources; // wl_resource_get_link
+
+ struct wl_list offers; // wl_resource_get_link
+ uint32_t selection_serial;
+
+ struct wl_listener seat_destroy;
+ struct wl_listener seat_focus_change;
+ struct wl_listener seat_primary_selection;
+
+ void *data;
+};
+
+struct wlr_gtk_primary_selection_device_manager *
+ wlr_gtk_primary_selection_device_manager_create(struct wl_display *display);
+void wlr_gtk_primary_selection_device_manager_destroy(
+ struct wlr_gtk_primary_selection_device_manager *manager);
+
+#endif
diff --git a/include/wlr/types/wlr_idle.h b/include/wlr/types/wlr_idle.h
new file mode 100644
index 00000000..d8c81a60
--- /dev/null
+++ b/include/wlr/types/wlr_idle.h
@@ -0,0 +1,71 @@
+/*
+ * 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_H
+#define WLR_TYPES_WLR_IDLE_H
+
+#include <wayland-server.h>
+#include <wlr/types/wlr_seat.h>
+
+/**
+ * Idle protocol is used to create timers which will notify the client when the
+ * compositor does not receive any input for a given time(in milliseconds). Also
+ * the client will be notified when the timer receives an activity notify and already
+ * was in idle state. Besides this, the client is able to simulate user activity
+ * which will reset the timers and at any time can destroy the timer.
+ */
+
+
+struct wlr_idle {
+ struct wl_global *global;
+ struct wl_list idle_timers; // wlr_idle_timeout::link
+ struct wl_event_loop *event_loop;
+ bool enabled;
+
+ struct wl_listener display_destroy;
+ struct {
+ struct wl_signal activity_notify;
+ struct wl_signal destroy;
+ } events;
+
+ void *data;
+};
+
+struct wlr_idle_timeout {
+ struct wl_resource *resource;
+ struct wl_list link;
+ struct wlr_seat *seat;
+
+ struct wl_event_source *idle_source;
+ bool idle_state;
+ bool enabled;
+ uint32_t timeout; // milliseconds
+
+ struct wl_listener input_listener;
+ struct wl_listener seat_destroy;
+
+ void *data;
+};
+
+struct wlr_idle *wlr_idle_create(struct wl_display *display);
+
+void wlr_idle_destroy(struct wlr_idle *idle);
+
+/**
+ * Send notification to restart all timers for the given seat. Called by
+ * compositor when there is an user activity event on that seat.
+ */
+void wlr_idle_notify_activity(struct wlr_idle *idle, struct wlr_seat *seat);
+
+/**
+ * Enable or disable timers for a given idle resource by seat.
+ * Passing a NULL seat means update timers for all seats.
+ */
+void wlr_idle_set_enabled(struct wlr_idle *idle, struct wlr_seat *seat,
+ bool enabled);
+#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 00000000..2093eafe
--- /dev/null
+++ b/include/wlr/types/wlr_idle_inhibit_v1.h
@@ -0,0 +1,58 @@
+/*
+ * 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 <wayland-server.h>
+
+/* 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 resources; // wl_resource_get_link
+ 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 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);
+void wlr_idle_inhibit_v1_destroy(struct wlr_idle_inhibit_manager_v1 *idle_inhibit);
+
+#endif
diff --git a/include/wlr/types/wlr_input_device.h b/include/wlr/types/wlr_input_device.h
new file mode 100644
index 00000000..f948d55b
--- /dev/null
+++ b/include/wlr/types/wlr_input_device.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_TYPES_WLR_INPUT_DEVICE_H
+#define WLR_TYPES_WLR_INPUT_DEVICE_H
+
+enum wlr_button_state {
+ WLR_BUTTON_RELEASED,
+ WLR_BUTTON_PRESSED,
+};
+
+enum wlr_input_device_type {
+ WLR_INPUT_DEVICE_KEYBOARD,
+ WLR_INPUT_DEVICE_POINTER,
+ WLR_INPUT_DEVICE_TOUCH,
+ WLR_INPUT_DEVICE_TABLET_TOOL,
+ WLR_INPUT_DEVICE_TABLET_PAD,
+ WLR_INPUT_DEVICE_SWITCH,
+};
+
+/* Note: these are circular dependencies */
+#include <wlr/types/wlr_keyboard.h>
+#include <wlr/types/wlr_pointer.h>
+#include <wlr/types/wlr_touch.h>
+#include <wlr/types/wlr_tablet_tool.h>
+#include <wlr/types/wlr_tablet_pad.h>
+#include <wlr/types/wlr_switch.h>
+
+struct wlr_input_device_impl;
+
+struct wlr_input_device {
+ const struct wlr_input_device_impl *impl;
+
+ enum wlr_input_device_type type;
+ unsigned int vendor, product;
+ char *name;
+ // Or 0 if not applicable to this device
+ double width_mm, height_mm;
+ char *output_name;
+
+ /* wlr_input_device.type determines which of these is valid */
+ union {
+ void *_device;
+ struct wlr_keyboard *keyboard;
+ struct wlr_pointer *pointer;
+ struct wlr_switch *lid_switch;
+ struct wlr_touch *touch;
+ struct wlr_tablet *tablet;
+ struct wlr_tablet_pad *tablet_pad;
+ };
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+
+ void *data;
+
+ struct wl_list link;
+};
+
+#endif
diff --git a/include/wlr/types/wlr_input_inhibitor.h b/include/wlr/types/wlr_input_inhibitor.h
new file mode 100644
index 00000000..f3187540
--- /dev/null
+++ b/include/wlr/types/wlr_input_inhibitor.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_INPUT_INHIBITOR_H
+#define WLR_TYPES_INPUT_INHIBITOR_H
+#include <wayland-server.h>
+
+struct wlr_input_inhibit_manager {
+ struct wl_global *global;
+ struct wl_client *active_client;
+ struct wl_resource *active_inhibitor;
+
+ struct wl_listener display_destroy;
+
+ struct {
+ struct wl_signal activate; // struct wlr_input_inhibit_manager *
+ struct wl_signal deactivate; // struct wlr_input_inhibit_manager *
+ struct wl_signal destroy;
+ } events;
+
+ void *data;
+};
+
+struct wlr_input_inhibit_manager *wlr_input_inhibit_manager_create(
+ struct wl_display *display);
+void wlr_input_inhibit_manager_destroy(
+ struct wlr_input_inhibit_manager *manager);
+
+#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 00000000..d22d54d1
--- /dev/null
+++ b/include/wlr/types/wlr_input_method_v2.h
@@ -0,0 +1,87 @@
+/*
+ * 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 <stdint.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_seat.h>
+
+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_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 link;
+
+ struct wl_listener seat_destroy;
+
+ struct {
+ struct wl_signal commit; // (struct wlr_input_method_v2*)
+ struct wl_signal destroy; // (struct wlr_input_method_v2*)
+ } events;
+};
+
+struct wlr_input_method_manager_v2 {
+ struct wl_global *global;
+ struct wl_list bound_resources; // struct wl_resource*::link
+ 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_manager_v2_destroy(
+ struct wlr_input_method_manager_v2 *manager);
+
+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);
+#endif
diff --git a/include/wlr/types/wlr_keyboard.h b/include/wlr/types/wlr_keyboard.h
new file mode 100644
index 00000000..ae279541
--- /dev/null
+++ b/include/wlr/types/wlr_keyboard.h
@@ -0,0 +1,115 @@
+/*
+ * 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 <stdbool.h>
+#include <stdint.h>
+#include <wayland-server.h>
+#include <wayland-server.h>
+#include <xkbcommon/xkbcommon.h>
+
+#define WLR_LED_COUNT 3
+
+enum wlr_keyboard_led {
+ WLR_LED_NUM_LOCK = 1,
+ WLR_LED_CAPS_LOCK = 2,
+ WLR_LED_SCROLL_LOCK = 4,
+};
+
+#define WLR_MODIFIER_COUNT 8
+
+enum wlr_keyboard_modifier {
+ WLR_MODIFIER_SHIFT = 1,
+ WLR_MODIFIER_CAPS = 2,
+ WLR_MODIFIER_CTRL = 4,
+ WLR_MODIFIER_ALT = 8,
+ WLR_MODIFIER_MOD2 = 16,
+ WLR_MODIFIER_MOD3 = 32,
+ WLR_MODIFIER_LOGO = 64,
+ WLR_MODIFIER_MOD5 = 128,
+};
+
+#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_mod_mask_t group;
+};
+
+struct wlr_keyboard {
+ const struct wlr_keyboard_impl *impl;
+
+ char *keymap_string;
+ size_t keymap_size;
+ 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 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 `wlr_event_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
+ * `wlr_keyboard` has been updated. At this time, you can read the
+ * modifier state of the `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;
+};
+
+enum wlr_key_state {
+ WLR_KEY_RELEASED,
+ WLR_KEY_PRESSED,
+};
+
+struct wlr_event_keyboard_key {
+ uint32_t time_msec;
+ uint32_t keycode;
+ bool update_state; // if backend doesn't update modifiers on its own
+ enum wlr_key_state state;
+};
+
+void wlr_keyboard_set_keymap(struct wlr_keyboard *kb,
+ struct xkb_keymap *keymap);
+/**
+ * Sets 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,
+ int32_t delay);
+void wlr_keyboard_led_update(struct wlr_keyboard *keyboard, uint32_t leds);
+uint32_t wlr_keyboard_get_modifiers(struct wlr_keyboard *keyboard);
+
+#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 00000000..838b2e83
--- /dev/null
+++ b/include/wlr/types/wlr_layer_shell_v1.h
@@ -0,0 +1,134 @@
+/*
+ * 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 <stdbool.h>
+#include <stdint.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_surface.h>
+#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
+ * 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_list resources; // wl_resource
+ struct wl_list surfaces; // wl_layer_surface
+
+ struct wl_listener display_destroy;
+
+ struct {
+ // struct wlr_layer_surface_v1 *
+ // 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 wl_signal destroy;
+ } events;
+
+ void *data;
+};
+
+struct wlr_layer_surface_v1_state {
+ uint32_t anchor;
+ int32_t exclusive_zone;
+ struct {
+ uint32_t top, right, bottom, left;
+ } margin;
+ bool keyboard_interactive;
+ uint32_t desired_width, desired_height;
+ 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;
+ struct wlr_layer_surface_v1_state state;
+};
+
+struct wlr_layer_surface_v1 {
+ struct wl_list link; // wlr_layer_shell_v1::surfaces
+ 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;
+ enum zwlr_layer_shell_v1_layer layer;
+
+ bool added, configured, mapped, closed;
+ uint32_t configure_serial;
+ struct wl_event_source *configure_idle;
+ uint32_t configure_next_serial;
+ struct wl_list configure_list;
+
+ struct wlr_layer_surface_v1_configure *acked_configure;
+
+ struct wlr_layer_surface_v1_state client_pending;
+ struct wlr_layer_surface_v1_state server_pending;
+ struct wlr_layer_surface_v1_state current;
+
+ struct wl_listener surface_destroy;
+
+ struct {
+ struct wl_signal destroy;
+ struct wl_signal map;
+ struct wl_signal unmap;
+ struct wl_signal new_popup;
+ } events;
+
+ void *data;
+};
+
+struct wlr_layer_shell_v1 *wlr_layer_shell_v1_create(struct wl_display *display);
+void wlr_layer_shell_v1_destroy(struct wlr_layer_shell_v1 *layer_shell);
+
+/**
+ * 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.
+ */
+void wlr_layer_surface_v1_configure(struct wlr_layer_surface_v1 *surface,
+ uint32_t width, uint32_t height);
+
+/**
+ * Unmaps this layer surface and notifies the client that it has been closed.
+ */
+void wlr_layer_surface_v1_close(struct wlr_layer_surface_v1 *surface);
+
+bool wlr_surface_is_layer_surface(struct wlr_surface *surface);
+
+struct wlr_layer_surface_v1 *wlr_layer_surface_v1_from_wlr_surface(
+ struct wlr_surface *surface);
+
+/* Calls the iterator function for each sub-surface and popup of this surface */
+void wlr_layer_surface_v1_for_each_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);
+
+#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 00000000..f21b0b3a
--- /dev/null
+++ b/include/wlr/types/wlr_linux_dmabuf_v1.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_LINUX_DMABUF_H
+#define WLR_TYPES_WLR_LINUX_DMABUF_H
+
+#include <stdint.h>
+#include <wayland-server-protocol.h>
+#include <wlr/render/dmabuf.h>
+
+struct wlr_dmabuf_v1_buffer {
+ struct wlr_renderer *renderer;
+ struct wl_resource *buffer_resource;
+ struct wl_resource *params_resource;
+ struct wlr_dmabuf_attributes attributes;
+ bool has_modifier;
+};
+
+/**
+ * Returns true if the given resource was created via the linux-dmabuf
+ * buffer protocol, false otherwise
+ */
+bool wlr_dmabuf_v1_resource_is_buffer(struct wl_resource *buffer_resource);
+
+/**
+ * Returns the wlr_dmabuf_buffer if the given resource was created
+ * via the linux-dmabuf buffer protocol
+ */
+struct wlr_dmabuf_v1_buffer *wlr_dmabuf_v1_buffer_from_buffer_resource(
+ struct wl_resource *buffer_resource);
+
+/**
+ * Returns the wlr_dmabuf_buffer if the given resource was created
+ * via the linux-dmabuf params protocol
+ */
+struct wlr_dmabuf_v1_buffer *wlr_dmabuf_v1_buffer_from_params_resource(
+ struct wl_resource *params_resource);
+
+/* the protocol interface */
+struct wlr_linux_dmabuf_v1 {
+ struct wl_global *global;
+ struct wlr_renderer *renderer;
+ struct wl_list resources;
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+
+ struct wl_listener display_destroy;
+ struct wl_listener renderer_destroy;
+};
+
+/**
+ * Create linux-dmabuf interface
+ */
+struct wlr_linux_dmabuf_v1 *wlr_linux_dmabuf_v1_create(struct wl_display *display,
+ struct wlr_renderer *renderer);
+/**
+ * Destroy the linux-dmabuf interface
+ */
+void wlr_linux_dmabuf_v1_destroy(struct wlr_linux_dmabuf_v1 *linux_dmabuf);
+
+/**
+ * Returns the wlr_linux_dmabuf if the given resource was created
+ * via the linux_dmabuf protocol
+ */
+struct wlr_linux_dmabuf_v1 *wlr_linux_dmabuf_v1_from_resource(
+ struct wl_resource *resource);
+
+#endif
diff --git a/include/wlr/types/wlr_list.h b/include/wlr/types/wlr_list.h
new file mode 100644
index 00000000..60f388f9
--- /dev/null
+++ b/include/wlr/types/wlr_list.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_LIST_H
+#define WLR_TYPES_WLR_LIST_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct wlr_list {
+ size_t capacity;
+ size_t length;
+ void **items;
+};
+
+/**
+ * Initialize a list. Returns true on success, false on failure.
+ */
+bool wlr_list_init(struct wlr_list *list);
+
+/**
+ * Deinitialize a list.
+ */
+void wlr_list_finish(struct wlr_list *list);
+
+/**
+ * Executes `callback` on each element in the list.
+ */
+void wlr_list_for_each(struct wlr_list *list, void (*callback)(void *item));
+
+/**
+ * Add `item` to the end of a list.
+ * Returns: new list length or `-1` on failure.
+ */
+ssize_t wlr_list_push(struct wlr_list *list, void *item);
+
+/**
+ * Place `item` into index `index` in the list.
+ * Returns: new list length or `-1` on failure.
+ */
+ssize_t wlr_list_insert(struct wlr_list *list, size_t index, void *item);
+
+/**
+ * Remove an item from the list.
+ */
+void wlr_list_del(struct wlr_list *list, size_t index);
+
+/**
+ * Remove and return an item from the end of the list.
+ */
+void *wlr_list_pop(struct wlr_list *list);
+
+/**
+ * Get a reference to the last item of a list without removal.
+ */
+void *wlr_list_peek(struct wlr_list *list);
+
+/**
+ * Append each item in `source` to `list`.
+ * Does not modify `source`.
+ * Returns: new list length or `-1` on failure.
+ */
+ssize_t wlr_list_cat(struct wlr_list *list, const struct wlr_list *source);
+
+/**
+ * Sort a list using `qsort`.
+ */
+void wlr_list_qsort(struct wlr_list *list,
+ int compare(const void *left, const void *right));
+
+/**
+ * Return the index of the first item in the list that returns 0 for the given
+ * `compare` function, or -1 if none matches.
+ */
+ssize_t wlr_list_find(struct wlr_list *list,
+ int compare(const void *item, const void *cmp_to), const void *cmp_to);
+
+#endif
diff --git a/include/wlr/types/wlr_matrix.h b/include/wlr/types/wlr_matrix.h
new file mode 100644
index 00000000..a3961c35
--- /dev/null
+++ b/include/wlr/types/wlr_matrix.h
@@ -0,0 +1,59 @@
+/*
+ * 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 by email and follow a 1-year deprecation
+ * schedule. Send an email to ~sircmpwn/wlroots-announce+subscribe@lists.sr.ht
+ * to receive these announcements.
+ */
+
+#ifndef WLR_TYPES_WLR_MATRIX_H
+#define WLR_TYPES_WLR_MATRIX_H
+
+#include <wayland-server.h>
+#include <wlr/types/wlr_box.h>
+
+/** 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);
+
+/** Writes a 2D orthographic projection matrix to mat of (width, height) with a
+ * specified wl_output_transform*/
+void wlr_matrix_projection(float mat[static 9], int width, int height,
+ 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 00000000..22822b11
--- /dev/null
+++ b/include/wlr/types/wlr_output.h
@@ -0,0 +1,278 @@
+/*
+ * 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 <pixman.h>
+#include <stdbool.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wayland-util.h>
+#include <wlr/render/dmabuf.h>
+
+struct wlr_output_mode {
+ uint32_t flags; // enum wl_output_mode
+ int32_t width, height;
+ int32_t refresh; // mHz
+ struct wl_list link;
+};
+
+struct wlr_output_cursor {
+ struct wlr_output *output;
+ double x, y;
+ bool enabled;
+ bool visible;
+ uint32_t width, height;
+ int32_t hotspot_x, hotspot_y;
+ struct wl_list link;
+
+ // only when using a software cursor without a surface
+ struct wlr_texture *texture;
+
+ // only when using a cursor surface
+ struct wlr_surface *surface;
+ struct wl_listener surface_commit;
+ struct wl_listener surface_destroy;
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+};
+
+struct wlr_output_impl;
+
+/**
+ * A compositor output region. This typically corresponds to a monitor that
+ * displays part of the compositor space.
+ *
+ * Compositors should listen to the `frame` event to render an output. They
+ * should call `wlr_output_make_current`, render and then call
+ * `wlr_output_swap_buffers`. No rendering should happen outside a `frame` event
+ * handler.
+ */
+struct wlr_output {
+ const struct wlr_output_impl *impl;
+ struct wlr_backend *backend;
+ struct wl_display *display;
+
+ struct wl_global *global;
+ struct wl_list resources;
+
+ char name[24];
+ char make[56];
+ char model[16];
+ char serial[16];
+ int32_t phys_width, phys_height; // mm
+
+ // Note: some backends may have zero modes
+ struct wl_list modes;
+ 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;
+
+ bool needs_swap;
+ // damage for cursors and fullscreen surface, in output-local coordinates
+ pixman_region32_t damage;
+ bool frame_pending;
+ float transform_matrix[9];
+
+ struct {
+ // Request to render a frame
+ struct wl_signal frame;
+ // Emitted when buffers need to be swapped (because software cursors or
+ // fullscreen damage or because of backend-specific logic)
+ struct wl_signal needs_swap;
+ // Emitted right before buffer swap
+ struct wl_signal swap_buffers; // wlr_output_event_swap_buffers
+ // Emitted right after the buffer has been presented to the user
+ struct wl_signal present; // wlr_output_event_present
+ struct wl_signal enable;
+ struct wl_signal mode;
+ struct wl_signal scale;
+ struct wl_signal transform;
+ struct wl_signal destroy;
+ } events;
+
+ struct wl_event_source *idle_frame;
+
+ struct wl_list cursors; // wlr_output_cursor::link
+ struct wlr_output_cursor *hardware_cursor;
+ int software_cursor_locks; // number of locks forcing software cursors
+
+ // the output position in layout space reported to clients
+ int32_t lx, ly;
+
+ struct wl_listener display_destroy;
+
+ void *data;
+};
+
+struct wlr_output_event_swap_buffers {
+ struct wlr_output *output;
+ struct timespec *when;
+ pixman_region32_t *damage; // output-buffer-local coordinates
+};
+
+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;
+ // 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_surface;
+
+/**
+ * Enables or disables the output. A disabled output is turned off and doesn't
+ * emit `frame` events.
+ */
+bool wlr_output_enable(struct wlr_output *output, bool enable);
+void wlr_output_create_global(struct wlr_output *output);
+void wlr_output_destroy_global(struct wlr_output *output);
+/**
+ * Sets the output mode. Enables the output if it's currently disabled.
+ */
+bool wlr_output_set_mode(struct wlr_output *output,
+ struct wlr_output_mode *mode);
+/**
+ * Sets a custom mode on the output. If modes are available, they are preferred.
+ * Setting `refresh` to zero lets the backend pick a preferred value.
+ */
+bool wlr_output_set_custom_mode(struct wlr_output *output, int32_t width,
+ int32_t height, int32_t refresh);
+void wlr_output_set_transform(struct wlr_output *output,
+ enum wl_output_transform transform);
+void wlr_output_set_position(struct wlr_output *output, int32_t lx, int32_t ly);
+void wlr_output_set_scale(struct wlr_output *output, float scale);
+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);
+/**
+ * Makes the output rendering context current.
+ *
+ * `buffer_age` is set to the drawing buffer age in number of frames or -1 if
+ * unknown. This is useful for damage tracking.
+ */
+bool wlr_output_make_current(struct wlr_output *output, int *buffer_age);
+/**
+ * Get the preferred format for reading pixels.
+ * This function might change the current rendering context.
+ */
+bool wlr_output_preferred_read_format(struct wlr_output *output,
+ enum wl_shm_format *fmt);
+/**
+ * Swaps the output buffers. If the time of the frame isn't known, set `when` to
+ * NULL. If the compositor doesn't support damage tracking, set `damage` to
+ * NULL.
+ *
+ * Damage is given in output-buffer-local coordinates (ie. scaled and
+ * transformed).
+ *
+ * Swapping buffers schedules a `frame` event.
+ */
+bool wlr_output_swap_buffers(struct wlr_output *output, struct timespec *when,
+ pixman_region32_t *damage);
+/**
+ * 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);
+/**
+ * Sets the gamma table for this 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.
+ */
+bool wlr_output_set_gamma(struct wlr_output *output, size_t size,
+ const uint16_t *r, const uint16_t *g, const uint16_t *b);
+bool wlr_output_export_dmabuf(struct wlr_output *output,
+ struct wlr_dmabuf_attributes *attribs);
+struct wlr_output *wlr_output_from_resource(struct wl_resource *resource);
+/**
+ * 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,
+ pixman_region32_t *damage);
+
+
+struct wlr_output_cursor *wlr_output_cursor_create(struct wlr_output *output);
+/**
+ * Sets the cursor image. The image must be already scaled for the output.
+ */
+bool wlr_output_cursor_set_image(struct wlr_output_cursor *cursor,
+ const uint8_t *pixels, int32_t stride, uint32_t width, uint32_t height,
+ int32_t hotspot_x, int32_t hotspot_y);
+void wlr_output_cursor_set_surface(struct wlr_output_cursor *cursor,
+ struct wlr_surface *surface, 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);
+
+
+/**
+ * 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);
+
+#endif
diff --git a/include/wlr/types/wlr_output_damage.h b/include/wlr/types/wlr_output_damage.h
new file mode 100644
index 00000000..d614e6d6
--- /dev/null
+++ b/include/wlr/types/wlr_output_damage.h
@@ -0,0 +1,87 @@
+/*
+ * 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_DAMAGE_H
+#define WLR_TYPES_WLR_OUTPUT_DAMAGE_H
+
+#include <pixman.h>
+#include <time.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_output.h>
+
+/**
+ * Damage tracking requires to keep track of previous frames' damage. To allow
+ * damage tracking to work with triple buffering, a history of two frames is
+ * required.
+ */
+#define WLR_OUTPUT_DAMAGE_PREVIOUS_LEN 2
+
+/**
+ * Tracks damage for an output.
+ *
+ * When a `frame` event is emitted, `wlr_output_damage_make_current` should be
+ * called. If necessary, the output should be repainted and
+ * `wlr_output_damage_swap_buffers` should be called. No rendering should happen
+ * outside a `frame` event handler.
+ */
+struct wlr_output_damage {
+ struct wlr_output *output;
+ int max_rects; // max number of damaged rectangles
+
+ pixman_region32_t current; // in output-local coordinates
+
+ // circular queue for previous damage
+ pixman_region32_t previous[WLR_OUTPUT_DAMAGE_PREVIOUS_LEN];
+ size_t previous_idx;
+
+ struct {
+ struct wl_signal frame;
+ struct wl_signal destroy;
+ } events;
+
+ struct wl_listener output_destroy;
+ struct wl_listener output_mode;
+ struct wl_listener output_transform;
+ struct wl_listener output_scale;
+ struct wl_listener output_needs_swap;
+ struct wl_listener output_frame;
+};
+
+struct wlr_output_damage *wlr_output_damage_create(struct wlr_output *output);
+void wlr_output_damage_destroy(struct wlr_output_damage *output_damage);
+/**
+ * Makes the output rendering context current. `needs_swap` is set to true if
+ * `wlr_output_damage_swap_buffers` needs to be called. The region of the output
+ * that needs to be repainted is added to `damage`.
+ */
+bool wlr_output_damage_make_current(struct wlr_output_damage *output_damage,
+ bool *needs_swap, pixman_region32_t *damage);
+/**
+ * Swaps the output buffers. If the time of the frame isn't known, set `when` to
+ * NULL.
+ *
+ * Swapping buffers schedules a `frame` event.
+ */
+bool wlr_output_damage_swap_buffers(struct wlr_output_damage *output_damage,
+ struct timespec *when, pixman_region32_t *damage);
+/**
+ * Accumulates damage and schedules a `frame` event.
+ */
+void wlr_output_damage_add(struct wlr_output_damage *output_damage,
+ pixman_region32_t *damage);
+/**
+ * Damages the whole output and schedules a `frame` event.
+ */
+void wlr_output_damage_add_whole(struct wlr_output_damage *output_damage);
+/**
+ * Accumulates damage from a box and schedules a `frame` event.
+ */
+void wlr_output_damage_add_box(struct wlr_output_damage *output_damage,
+ struct wlr_box *box);
+
+#endif
diff --git a/include/wlr/types/wlr_output_layout.h b/include/wlr/types/wlr_output_layout.h
new file mode 100644
index 00000000..cc9d2328
--- /dev/null
+++ b/include/wlr/types/wlr_output_layout.h
@@ -0,0 +1,133 @@
+/*
+ * 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 <stdbool.h>
+#include <wayland-util.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_output.h>
+
+struct wlr_output_layout_state;
+
+struct wlr_output_layout {
+ struct wl_list outputs;
+ struct wlr_output_layout_state *state;
+
+ struct {
+ struct wl_signal add;
+ struct wl_signal change;
+ struct wl_signal destroy;
+ } events;
+
+ void *data;
+};
+
+struct wlr_output_layout_output_state;
+
+struct wlr_output_layout_output {
+ struct wlr_output *output;
+ int x, y;
+ struct wl_list link;
+ struct wlr_output_layout_output_state *state;
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+};
+
+/**
+ * Creates a wlr_output_layout, which can be used to describing outputs in
+ * physical space relative to one another, and perform various useful operations
+ * on that state.
+ */
+struct wlr_output_layout *wlr_output_layout_create();
+
+void wlr_output_layout_destroy(struct wlr_output_layout *layout);
+
+struct wlr_output_layout_output *wlr_output_layout_get(
+ struct wlr_output_layout *layout, struct wlr_output *reference);
+
+struct wlr_output *wlr_output_layout_output_at(struct wlr_output_layout *layout,
+ double lx, double ly);
+
+void wlr_output_layout_add(struct wlr_output_layout *layout,
+ struct wlr_output *output, int lx, int ly);
+
+void wlr_output_layout_move(struct wlr_output_layout *layout,
+ struct wlr_output *output, int lx, int ly);
+
+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.
+ */
+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.
+ */
+struct wlr_box *wlr_output_layout_get_box(
+ struct wlr_output_layout *layout, struct wlr_output *reference);
+
+/**
+* Add an auto configured output to the layout. This will place the output in a
+* sensible location in the layout. The coordinates of the output in the layout
+* may adjust dynamically when the layout changes. If the output is already in
+* the layout, it will become auto configured. If the position of the output is
+* set such as with `wlr_output_layout_move()`, the output will become manually
+* configured.
+*/
+void wlr_output_layout_add_auto(struct wlr_output_layout *layout,
+ struct wlr_output *output);
+
+/**
+ * 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,
+ WLR_DIRECTION_DOWN = 2,
+ WLR_DIRECTION_LEFT = 4,
+ WLR_DIRECTION_RIGHT = 8,
+};
+
+/**
+ * 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_pointer.h b/include/wlr/types/wlr_pointer.h
new file mode 100644
index 00000000..7dc643ae
--- /dev/null
+++ b/include/wlr/types/wlr_pointer.h
@@ -0,0 +1,72 @@
+/*
+ * 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 <stdint.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_input_device.h>
+
+struct wlr_pointer_impl;
+
+struct wlr_pointer {
+ const struct wlr_pointer_impl *impl;
+
+ struct {
+ struct wl_signal motion;
+ struct wl_signal motion_absolute;
+ struct wl_signal button;
+ struct wl_signal axis;
+ } events;
+
+ void *data;
+};
+
+struct wlr_event_pointer_motion {
+ struct wlr_input_device *device;
+ uint32_t time_msec;
+ double delta_x, delta_y;
+};
+
+struct wlr_event_pointer_motion_absolute {
+ struct wlr_input_device *device;
+ uint32_t time_msec;
+ // From 0..1
+ double x, y;
+};
+
+struct wlr_event_pointer_button {
+ struct wlr_input_device *device;
+ uint32_t time_msec;
+ uint32_t button;
+ enum wlr_button_state state;
+};
+
+enum wlr_axis_source {
+ WLR_AXIS_SOURCE_WHEEL,
+ WLR_AXIS_SOURCE_FINGER,
+ WLR_AXIS_SOURCE_CONTINUOUS,
+ WLR_AXIS_SOURCE_WHEEL_TILT,
+};
+
+enum wlr_axis_orientation {
+ WLR_AXIS_ORIENTATION_VERTICAL,
+ WLR_AXIS_ORIENTATION_HORIZONTAL,
+};
+
+struct wlr_event_pointer_axis {
+ struct wlr_input_device *device;
+ uint32_t time_msec;
+ enum wlr_axis_source source;
+ enum wlr_axis_orientation orientation;
+ double delta;
+ int32_t delta_discrete;
+};
+
+#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 00000000..fef8c2e9
--- /dev/null
+++ b/include/wlr/types/wlr_pointer_constraints_v1.h
@@ -0,0 +1,102 @@
+/*
+ * 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 <stdint.h>
+#include <wayland-server.h>
+#include <pixman.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_seat.h>
+#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 {
+ 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_listener surface_commit;
+ struct wl_listener surface_destroy;
+ struct wl_listener seat_destroy;
+
+ struct wl_list link; // wlr_pointer_constraints_v1::constraints
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+
+ void *data;
+};
+
+struct wlr_pointer_constraints_v1 {
+ struct wl_list resources; // wl_resource_get_link
+ struct wl_global *global;
+
+ struct {
+ /**
+ * Called when a new pointer constraint is created.
+ *
+ * data: struct wlr_pointer_constraint_v1 *
+ */
+ struct wl_signal new_constraint;
+ } events;
+
+ struct wl_list constraints; // wlr_pointer_constraint_v1::link
+
+ void *data;
+};
+
+struct wlr_pointer_constraints_v1 *wlr_pointer_constraints_v1_create(
+ struct wl_display *display);
+void wlr_pointer_constraints_v1_destroy(
+ struct wlr_pointer_constraints_v1 *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);
+
+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_presentation_time.h b/include/wlr/types/wlr_presentation_time.h
new file mode 100644
index 00000000..9f9f0e87
--- /dev/null
+++ b/include/wlr/types/wlr_presentation_time.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_PRESENTATION_TIME_H
+#define WLR_TYPES_WLR_PRESENTATION_TIME_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <time.h>
+#include <wayland-server.h>
+
+struct wlr_presentation {
+ struct wl_global *global;
+ struct wl_list resources; // wl_resource_get_link
+ struct wl_list feedbacks; // wlr_presentation_feedback::link
+ clockid_t clock;
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+
+ struct wl_listener display_destroy;
+};
+
+struct wlr_presentation_feedback {
+ struct wl_resource *resource;
+ struct wlr_presentation *presentation;
+ struct wlr_surface *surface;
+ bool committed;
+ struct wl_list link; // wlr_presentation::feedbacks
+
+ struct wl_listener surface_commit;
+ struct wl_listener surface_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; // wp_presentation_feedback_kind
+};
+
+struct wlr_backend;
+
+struct wlr_presentation *wlr_presentation_create(struct wl_display *display,
+ struct wlr_backend *backend);
+void wlr_presentation_destroy(struct wlr_presentation *presentation);
+void wlr_presentation_send_surface_presented(
+ struct wlr_presentation *presentation, struct wlr_surface *surface,
+ struct wlr_presentation_event *event);
+
+#endif
diff --git a/include/wlr/types/wlr_primary_selection.h b/include/wlr/types/wlr_primary_selection.h
new file mode 100644
index 00000000..9be61acc
--- /dev/null
+++ b/include/wlr/types/wlr_primary_selection.h
@@ -0,0 +1,54 @@
+/*
+ * 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 <wayland-server.h>
+#include <wlr/types/wlr_seat.h>
+
+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);
+
+void wlr_seat_set_primary_selection(struct wlr_seat *seat,
+ struct wlr_primary_selection_source *source);
+
+#endif
diff --git a/include/wlr/types/wlr_region.h b/include/wlr/types/wlr_region.h
new file mode 100644
index 00000000..ec7f73aa
--- /dev/null
+++ b/include/wlr/types/wlr_region.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_TYPES_WLR_REGION_H
+#define WLR_TYPES_WLR_REGION_H
+
+#include <pixman.h>
+#include <wayland-server-protocol.h>
+
+/*
+ * Creates a new region resource with the provided new ID. If `resource_list` is
+ * non-NULL, adds the region's resource to the list.
+ */
+struct wl_resource *wlr_region_create(struct wl_client *client,
+ uint32_t version, uint32_t id, struct wl_list *resource_list);
+
+pixman_region32_t *wlr_region_from_resource(struct wl_resource *resource);
+
+#endif
diff --git a/include/wlr/types/wlr_screencopy_v1.h b/include/wlr/types/wlr_screencopy_v1.h
new file mode 100644
index 00000000..c7197bab
--- /dev/null
+++ b/include/wlr/types/wlr_screencopy_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_SCREENCOPY_V1_H
+#define WLR_TYPES_WLR_SCREENCOPY_V1_H
+
+#include <stdbool.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_box.h>
+
+struct wlr_screencopy_manager_v1 {
+ struct wl_global *global;
+ struct wl_list resources; // wl_resource
+ 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_frame_v1 {
+ struct wl_resource *resource;
+ struct wlr_screencopy_manager_v1 *manager;
+ struct wl_list link;
+
+ enum wl_shm_format format;
+ struct wlr_box box;
+ int stride;
+
+ bool overlay_cursor, cursor_locked;
+
+ struct wl_shm_buffer *buffer;
+ struct wl_listener buffer_destroy;
+
+ struct wlr_output *output;
+ struct wl_listener output_swap_buffers;
+
+ void *data;
+};
+
+struct wlr_screencopy_manager_v1 *wlr_screencopy_manager_v1_create(
+ struct wl_display *display);
+void wlr_screencopy_manager_v1_destroy(
+ struct wlr_screencopy_manager_v1 *screencopy);
+
+#endif
diff --git a/include/wlr/types/wlr_screenshooter.h b/include/wlr/types/wlr_screenshooter.h
new file mode 100644
index 00000000..b7b87b39
--- /dev/null
+++ b/include/wlr/types/wlr_screenshooter.h
@@ -0,0 +1,41 @@
+/*
+ * 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_SCREENSHOOTER_H
+#define WLR_TYPES_WLR_SCREENSHOOTER_H
+
+#include <wayland-server.h>
+
+struct wlr_screenshooter {
+ struct wl_global *global;
+ struct wl_list screenshots; // wlr_screenshot::link
+
+ struct wl_listener display_destroy;
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+
+ void *data;
+};
+
+struct wlr_screenshot {
+ struct wl_resource *resource;
+ struct wl_resource *output_resource;
+ struct wl_list link;
+
+ struct wlr_output *output;
+ struct wlr_screenshooter *screenshooter;
+
+ void* data;
+};
+
+struct wlr_screenshooter *wlr_screenshooter_create(struct wl_display *display);
+void wlr_screenshooter_destroy(struct wlr_screenshooter *screenshooter);
+
+#endif
diff --git a/include/wlr/types/wlr_seat.h b/include/wlr/types/wlr_seat.h
new file mode 100644
index 00000000..942a3420
--- /dev/null
+++ b/include/wlr/types/wlr_seat.h
@@ -0,0 +1,573 @@
+/*
+ * 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 <time.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_keyboard.h>
+#include <wlr/types/wlr_surface.h>
+
+/**
+ * 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 wlr_seat; some may be NULL.
+ */
+struct wlr_seat_client {
+ struct wl_client *client;
+ struct wlr_seat *seat;
+
+ // 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;
+
+ struct wl_list link;
+};
+
+struct wlr_touch_point {
+ int32_t touch_id;
+ struct wlr_surface *surface;
+ 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 {
+ 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 (*motion)(struct wlr_seat_pointer_grab *grab, uint32_t time,
+ double sx, double sy);
+ uint32_t (*button)(struct wlr_seat_pointer_grab *grab, uint32_t time,
+ uint32_t button, uint32_t state);
+ void (*axis)(struct wlr_seat_pointer_grab *grab, uint32_t time,
+ enum wlr_axis_orientation orientation, double value,
+ int32_t value_discrete, enum wlr_axis_source source);
+ 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, uint32_t keycodes[],
+ size_t num_keycodes, struct wlr_keyboard_modifiers *modifiers);
+ void (*key)(struct wlr_seat_keyboard_grab *grab, uint32_t time,
+ uint32_t key, uint32_t state);
+ void (*modifiers)(struct wlr_seat_keyboard_grab *grab,
+ 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,
+ struct wlr_touch_point *point);
+ void (*up)(struct wlr_seat_touch_grab *grab, uint32_t time,
+ struct wlr_touch_point *point);
+ void (*motion)(struct wlr_seat_touch_grab *grab, uint32_t time,
+ struct wlr_touch_point *point);
+ void (*enter)(struct wlr_seat_touch_grab *grab, uint32_t time,
+ struct wlr_touch_point *point);
+ // XXX this will conflict with the actual touch cancel which is different so
+ // we need to rename this
+ void (*cancel)(struct wlr_seat_touch_grab *grab);
+};
+
+/**
+ * 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;
+};
+
+struct wlr_seat_pointer_state {
+ struct wlr_seat *seat;
+ struct wlr_seat_client *focused_client;
+ struct wlr_surface *focused_surface;
+
+ struct wlr_seat_pointer_grab *grab;
+ struct wlr_seat_pointer_grab *default_grab;
+
+ uint32_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; // wlr_seat_pointer_focus_change_event
+ } events;
+};
+
+// TODO: May be useful to be able to simulate keyboard input 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; // 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;
+ struct wl_list drag_icons; // wlr_drag_icon::link
+
+ char *name;
+ uint32_t capabilities;
+ struct timespec last_event;
+
+ struct wlr_data_source *selection_source;
+ uint32_t selection_serial;
+
+ struct wlr_primary_selection_source *primary_selection_source;
+
+ // `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 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 wl_signal request_set_cursor;
+
+ struct wl_signal selection;
+ struct wl_signal primary_selection;
+
+ struct wl_signal start_drag;
+ struct wl_signal new_drag_icon;
+
+ 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_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 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 wlr_seat and removes its wl_seat global.
+ */
+void wlr_seat_destroy(struct wlr_seat *wlr_seat);
+/**
+ * Gets a 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.
+ * Compositor should use `wlr_seat_pointer_notify_enter()` to change pointer
+ * focus to respect pointer grabs.
+ */
+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.
+ */
+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. Compositors should use
+ * `wlr_seat_pointer_notify_motion()` to send motion events to respect pointer
+ * grabs.
+ */
+void wlr_seat_pointer_send_motion(struct wlr_seat *wlr_seat, uint32_t time,
+ 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. Compositors should use
+ * `wlr_seat_pointer_notify_button()` to send button events to respect pointer
+ * grabs.
+ */
+uint32_t wlr_seat_pointer_send_button(struct wlr_seat *wlr_seat, uint32_t time,
+ uint32_t button, uint32_t state);
+
+/**
+ * Send an axis event to the surface with pointer focus. Compositors should use
+ * `wlr_seat_pointer_notify_axis()` to send axis events to respect pointer
+ * grabs.
+ **/
+void wlr_seat_pointer_send_axis(struct wlr_seat *wlr_seat, uint32_t time,
+ enum wlr_axis_orientation orientation, double value,
+ int32_t value_discrete, enum wlr_axis_source source);
+
+/**
+ * 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);
+
+/**
+ * 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.
+ */
+void wlr_seat_pointer_notify_enter(struct wlr_seat *wlr_seat,
+ struct wlr_surface *surface, double sx, double sy);
+
+/**
+ * Notify the seat of motion over the given surface. Pass surface-local
+ * coordinates where the pointer motion occurred.
+ */
+void wlr_seat_pointer_notify_motion(struct wlr_seat *wlr_seat, uint32_t time,
+ 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.
+ */
+uint32_t wlr_seat_pointer_notify_button(struct wlr_seat *wlr_seat,
+ uint32_t time, uint32_t button, uint32_t state);
+
+/**
+ * Notify the seat of an axis event.
+ */
+void wlr_seat_pointer_notify_axis(struct wlr_seat *wlr_seat, uint32_t time,
+ enum wlr_axis_orientation orientation, double value,
+ int32_t value_discrete, enum wlr_axis_source source);
+
+/**
+ * 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_input_device *dev);
+
+/**
+ * Get the active keyboard for the seat.
+ */
+struct wlr_keyboard *wlr_seat_get_keyboard(struct wlr_seat *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);
+
+/**
+ * Send the keyboard key to focused keyboard resources. Compositors should use
+ * `wlr_seat_notify_key()` to respect keyboard grabs.
+ */
+void wlr_seat_keyboard_send_key(struct wlr_seat *seat, uint32_t time,
+ uint32_t key, uint32_t state);
+
+/**
+ * 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,
+ uint32_t key, uint32_t state);
+
+/**
+ * Send the modifier state to focused keyboard resources. Compositors should use
+ * `wlr_seat_keyboard_notify_modifiers()` to respect any keyboard grabs.
+ */
+void wlr_seat_keyboard_send_modifiers(struct wlr_seat *seat,
+ struct wlr_keyboard_modifiers *modifiers);
+
+/**
+ * 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,
+ 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, uint32_t keycodes[], size_t num_keycodes,
+ 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. Compositors should use
+ * `wlr_seat_keyboard_notify_enter()` to change keyboard focus to respect
+ * keyboard grabs.
+ */
+void wlr_seat_keyboard_enter(struct wlr_seat *seat,
+ struct wlr_surface *surface, uint32_t keycodes[], size_t num_keycodes,
+ struct wlr_keyboard_modifiers *modifiers);
+
+/**
+ * Clear the focused surface for the keyboard and leave all entered surfaces.
+ */
+void wlr_seat_keyboard_clear_focus(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);
+
+/**
+ * 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);
+
+/**
+ * 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 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, 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,
+ 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,
+ int32_t touch_id, double sx, double sy);
+
+/**
+ * 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, 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,
+ 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. Compositors should use
+ * `wlr_seat_touch_notify_down()` to respect any grabs of the touch device.
+ */
+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);
+
+/**
+ * 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 cooresponding touch down
+ * event. This will remove the touch point. Compositors should use
+ * `wlr_seat_touch_notify_up()` to respect any grabs of the touch device.
+ */
+void wlr_seat_touch_send_up(struct wlr_seat *seat, uint32_t time,
+ 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. Compositors should use `wlr_seat_touch_notify_motion()` to
+ * respect any grabs of the touch device.
+ */
+void wlr_seat_touch_send_motion(struct wlr_seat *seat, uint32_t time,
+ int32_t touch_id, double sx, double sy);
+
+/**
+ * How many touch points are currently down for the seat.
+ */
+int wlr_seat_touch_num_points(struct wlr_seat *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 grab action such as an
+ * interactive move or resize.
+ */
+bool wlr_seat_validate_grab_serial(struct wlr_seat *seat, uint32_t serial);
+
+struct wlr_seat_client *wlr_seat_client_from_resource(
+ struct wl_resource *resource);
+
+struct wlr_seat_client *wlr_seat_client_from_pointer_resource(
+ struct wl_resource *resource);
+
+#endif
diff --git a/include/wlr/types/wlr_server_decoration.h b/include/wlr/types/wlr_server_decoration.h
new file mode 100644
index 00000000..ff8d1369
--- /dev/null
+++ b/include/wlr/types/wlr_server_decoration.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_SERVER_DECORATION_H
+#define WLR_TYPES_WLR_SERVER_DECORATION_H
+
+#include <wayland-server.h>
+
+/**
+ * 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,
+};
+
+struct wlr_server_decoration_manager {
+ struct wl_global *global;
+ struct wl_list resources;
+ 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);
+void wlr_server_decoration_manager_destroy(
+ struct wlr_server_decoration_manager *manager);
+
+#endif
diff --git a/include/wlr/types/wlr_surface.h b/include/wlr/types/wlr_surface.h
new file mode 100644
index 00000000..b8f8c02a
--- /dev/null
+++ b/include/wlr/types/wlr_surface.h
@@ -0,0 +1,246 @@
+/*
+ * 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_SURFACE_H
+#define WLR_TYPES_WLR_SURFACE_H
+
+#include <pixman.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_output.h>
+
+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,
+};
+
+struct wlr_surface_state {
+ uint32_t committed; // enum wlr_surface_state_field
+
+ struct wl_resource *buffer_resource;
+ 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_listener buffer_destroy;
+};
+
+struct wlr_surface_role {
+ const char *name;
+ void (*commit)(struct wlr_surface *surface);
+ void (*precommit)(struct wlr_surface *surface);
+};
+
+struct wlr_surface {
+ struct wl_resource *resource;
+ struct wlr_renderer *renderer;
+ /**
+ * 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_buffer *buffer;
+ /**
+ * The buffer position, in surface-local units.
+ */
+ int sx, sy;
+ /**
+ * 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.
+ */
+ 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. `previous` contains the state of
+ * the previous commit.
+ */
+ struct wlr_surface_state current, pending, previous;
+
+ const struct wlr_surface_role *role; // the lifetime-bound role or NULL
+ void *role_data; // role-specific data
+
+ struct {
+ struct wl_signal commit;
+ struct wl_signal new_subsurface;
+ struct wl_signal destroy;
+ } events;
+
+ struct wl_list subsurfaces; // wlr_subsurface::parent_link
+
+ // wlr_subsurface::parent_pending_link
+ struct wl_list subsurface_pending_list;
+
+ struct wl_listener renderer_destroy;
+
+ void *data;
+};
+
+struct wlr_subsurface_state {
+ int32_t x, y;
+};
+
+struct wlr_subsurface {
+ struct wl_resource *resource;
+ struct wlr_surface *surface;
+ struct wlr_surface *parent;
+
+ struct wlr_subsurface_state current, pending;
+
+ struct wlr_surface_state cached;
+ bool has_cache;
+
+ bool synchronized;
+ bool reordered;
+
+ struct wl_list parent_link;
+ struct wl_list parent_pending_link;
+
+ struct wl_listener surface_destroy;
+ struct wl_listener parent_destroy;
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+
+ void *data;
+};
+
+typedef void (*wlr_surface_iterator_func_t)(struct wlr_surface *surface,
+ int sx, int sy, void *data);
+
+struct wlr_renderer;
+
+/**
+ * Create a new surface resource with the provided new ID. If `resource_list`
+ * is non-NULL, adds the surface's resource to the list.
+ */
+struct wlr_surface *wlr_surface_create(struct wl_client *client,
+ uint32_t version, uint32_t id, struct wlr_renderer *renderer,
+ struct wl_list *resource_list);
+
+/**
+ * Set the lifetime role for this surface. Returns 0 on success or -1 if the
+ * role cannot be set.
+ */
+bool wlr_surface_set_role(struct wlr_surface *surface,
+ const struct wlr_surface_role *role, void *role_data,
+ struct wl_resource *error_resource, uint32_t error_code);
+
+/**
+ * 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, has
+ * committed a null buffer, or something went wrong with uploading the buffer.
+ */
+bool wlr_surface_has_buffer(struct wlr_surface *surface);
+
+/**
+ * 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);
+
+/**
+ * Create a new subsurface resource with the provided new ID. If `resource_list`
+ * is non-NULL, adds the subsurface's resource to the list.
+ */
+struct wlr_subsurface *wlr_subsurface_create(struct wlr_surface *surface,
+ struct wlr_surface *parent, uint32_t version, uint32_t id,
+ struct wl_list *resource_list);
+
+/**
+ * Get the root of the subsurface tree for this surface.
+ */
+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 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);
+
+void wlr_surface_send_enter(struct wlr_surface *surface,
+ struct wlr_output *output);
+
+void wlr_surface_send_leave(struct wlr_surface *surface,
+ struct wlr_output *output);
+
+void wlr_surface_send_frame_done(struct wlr_surface *surface,
+ const struct timespec *when);
+
+struct wlr_box;
+/**
+ * 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);
+
+struct wlr_surface *wlr_surface_from_resource(struct wl_resource *resource);
+
+/**
+ * Call `iterator` on each surface in the surface tree, 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 damage to the surface in terms of surface local
+ * coordinates. This includes damage induced by resizing and moving the
+ * surface. The damage is not expected to be bounded by the surface itself.
+ */
+void wlr_surface_get_effective_damage(struct wlr_surface *surface,
+ pixman_region32_t *damage);
+
+#endif
diff --git a/include/wlr/types/wlr_switch.h b/include/wlr/types/wlr_switch.h
new file mode 100644
index 00000000..df1c8579
--- /dev/null
+++ b/include/wlr/types/wlr_switch.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_SWITCH_H
+#define WLR_TYPES_WLR_SWITCH_H
+
+#include <stdint.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_list.h>
+
+struct wlr_switch_impl;
+
+struct wlr_switch {
+ struct wlr_switch_impl *impl;
+
+ struct {
+ struct wl_signal toggle;
+ } events;
+
+ void *data;
+};
+
+enum wlr_switch_type {
+ WLR_SWITCH_TYPE_LID = 1,
+ WLR_SWITCH_TYPE_TABLET_MODE,
+};
+
+enum wlr_switch_state {
+ WLR_SWITCH_STATE_OFF = 0,
+ WLR_SWITCH_STATE_ON,
+ WLR_SWITCH_STATE_TOGGLE
+};
+
+struct wlr_event_switch_toggle {
+ struct wlr_input_device *device;
+ uint32_t time_msec;
+ enum wlr_switch_type switch_type;
+ enum wlr_switch_state switch_state;
+};
+
+#endif
diff --git a/include/wlr/types/wlr_tablet_pad.h b/include/wlr/types/wlr_tablet_pad.h
new file mode 100644
index 00000000..d9bb284f
--- /dev/null
+++ b/include/wlr/types/wlr_tablet_pad.h
@@ -0,0 +1,94 @@
+/*
+ * 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 <stdint.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_list.h>
+
+/*
+ * 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_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 wlr_list 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_event_tablet_pad_button {
+ 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_event_tablet_pad_ring {
+ 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_event_tablet_pad_strip {
+ uint32_t time_msec;
+ enum wlr_tablet_pad_strip_source source;
+ uint32_t strip;
+ double position;
+ unsigned int mode;
+};
+
+#endif
diff --git a/include/wlr/types/wlr_tablet_tool.h b/include/wlr/types/wlr_tablet_tool.h
new file mode 100644
index 00000000..cb516ed9
--- /dev/null
+++ b/include/wlr/types/wlr_tablet_tool.h
@@ -0,0 +1,136 @@
+/*
+ * 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 <stdint.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_list.h>
+
+/*
+ * 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 {
+ WLR_TABLET_TOOL_TYPE_PEN = 1, /**< A generic pen */
+ WLR_TABLET_TOOL_TYPE_ERASER, /**< Eraser */
+ WLR_TABLET_TOOL_TYPE_BRUSH, /**< A paintbrush-like tool */
+ WLR_TABLET_TOOL_TYPE_PENCIL, /**< Physical drawing tool, e.g.
+ Wacom Inking Pen */
+ WLR_TABLET_TOOL_TYPE_AIRBRUSH, /**< An airbrush-like tool */
+ WLR_TABLET_TOOL_TYPE_MOUSE, /**< A mouse bound to the tablet */
+ WLR_TABLET_TOOL_TYPE_LENS, /**< A mouse tool with a lens */
+};
+
+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_tablet_impl *impl;
+
+ struct {
+ struct wl_signal axis;
+ struct wl_signal proximity;
+ struct wl_signal tip;
+ struct wl_signal button;
+ } events;
+
+ const char *name;
+ struct wlr_list paths; // char *
+
+ void *data;
+};
+
+enum wlr_tablet_tool_axes {
+ WLR_TABLET_TOOL_AXIS_X = 1,
+ WLR_TABLET_TOOL_AXIS_Y = 2,
+ WLR_TABLET_TOOL_AXIS_DISTANCE = 4,
+ WLR_TABLET_TOOL_AXIS_PRESSURE = 8,
+ WLR_TABLET_TOOL_AXIS_TILT_X = 16,
+ WLR_TABLET_TOOL_AXIS_TILT_Y = 32,
+ WLR_TABLET_TOOL_AXIS_ROTATION = 64,
+ WLR_TABLET_TOOL_AXIS_SLIDER = 128,
+ WLR_TABLET_TOOL_AXIS_WHEEL = 256,
+};
+
+struct wlr_event_tablet_tool_axis {
+ struct wlr_input_device *device;
+ 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_event_tablet_tool_proximity {
+ struct wlr_input_device *device;
+ 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_event_tablet_tool_tip {
+ struct wlr_input_device *device;
+ struct wlr_tablet_tool *tool;
+ uint32_t time_msec;
+ // From 0..1
+ double x, y;
+ enum wlr_tablet_tool_tip_state state;
+};
+
+struct wlr_event_tablet_tool_button {
+ struct wlr_input_device *device;
+ struct wlr_tablet_tool *tool;
+ uint32_t time_msec;
+ uint32_t button;
+ enum wlr_button_state state;
+};
+
+#endif
diff --git a/include/wlr/types/wlr_tablet_v2.h b/include/wlr/types/wlr_tablet_v2.h
new file mode 100644
index 00000000..d6fd646d
--- /dev/null
+++ b/include/wlr/types/wlr_tablet_v2.h
@@ -0,0 +1,330 @@
+/*
+ * 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 <wayland-server.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/types/wlr_input_device.h>
+
+#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_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 tool_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_tablet_v2_destroy(struct wlr_tablet_manager_v2 *manager);
+
+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);
+
+
+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_text_input_v3.h b/include/wlr/types/wlr_text_input_v3.h
new file mode 100644
index 00000000..0db0cf47
--- /dev/null
+++ b/include/wlr/types/wlr_text_input_v3.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_TYPES_WLR_TEXT_INPUT_V3_H
+#define WLR_TYPES_WLR_TEXT_INPUT_V3_H
+
+#include <wayland-server.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/types/wlr_surface.h>
+
+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 {
+ int32_t x;
+ int32_t y;
+ int32_t width;
+ int32_t height;
+ } cursor_rectangle;
+};
+
+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;
+
+ 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 bound_resources; // struct wl_resource*::link
+ 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);
+void wlr_text_input_manager_v3_destroy(
+ struct wlr_text_input_manager_v3 *manager);
+
+// 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, uint32_t cursor_begin, uint32_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 00000000..99316ae0
--- /dev/null
+++ b/include/wlr/types/wlr_touch.h
@@ -0,0 +1,58 @@
+/*
+ * 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 <stdint.h>
+#include <wayland-server.h>
+
+struct wlr_touch_impl;
+
+struct wlr_touch {
+ struct wlr_touch_impl *impl;
+
+ struct {
+ struct wl_signal down;
+ struct wl_signal up;
+ struct wl_signal motion;
+ struct wl_signal cancel;
+ } events;
+
+ void *data;
+};
+
+struct wlr_event_touch_down {
+ struct wlr_input_device *device;
+ uint32_t time_msec;
+ int32_t touch_id;
+ // From 0..1
+ double x, y;
+};
+
+struct wlr_event_touch_up {
+ struct wlr_input_device *device;
+ uint32_t time_msec;
+ int32_t touch_id;
+};
+
+struct wlr_event_touch_motion {
+ struct wlr_input_device *device;
+ uint32_t time_msec;
+ int32_t touch_id;
+ // From 0..1
+ double x, y;
+};
+
+struct wlr_event_touch_cancel {
+ struct wlr_input_device *device;
+ uint32_t time_msec;
+ int32_t touch_id;
+};
+
+#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 00000000..e75ed8ec
--- /dev/null
+++ b/include/wlr/types/wlr_virtual_keyboard_v1.h
@@ -0,0 +1,46 @@
+/*
+ * 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 <wayland-server.h>
+#include <wlr/interfaces/wlr_input_device.h>
+#include <wlr/interfaces/wlr_keyboard.h>
+
+struct wlr_virtual_keyboard_manager_v1 {
+ struct wl_global *global;
+ struct wl_list resources; // struct wl_resource*
+ struct wl_list virtual_keyboards; // struct wlr_virtual_keyboard_v1*
+
+ 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 wl_resource *resource;
+ struct wlr_input_device input_device;
+ struct wlr_seat *seat;
+
+ struct wl_list link;
+
+ struct {
+ struct wl_signal destroy; // struct wlr_virtual_keyboard_v1*
+ } events;
+};
+
+struct wlr_virtual_keyboard_manager_v1* wlr_virtual_keyboard_manager_v1_create(
+ struct wl_display *display);
+void wlr_virtual_keyboard_manager_v1_destroy(
+ struct wlr_virtual_keyboard_manager_v1 *manager);
+
+#endif
diff --git a/include/wlr/types/wlr_wl_shell.h b/include/wlr/types/wlr_wl_shell.h
new file mode 100644
index 00000000..dffbb4d7
--- /dev/null
+++ b/include/wlr/types/wlr_wl_shell.h
@@ -0,0 +1,175 @@
+/*
+ * 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_WL_SHELL_H
+#define WLR_TYPES_WLR_WL_SHELL_H
+
+#include <stdbool.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_seat.h>
+
+struct wlr_wl_shell {
+ struct wl_global *global;
+ struct wl_list resources;
+ struct wl_list surfaces;
+ struct wl_list popup_grabs;
+ uint32_t ping_timeout;
+
+ struct wl_listener display_destroy;
+
+ struct {
+ struct wl_signal new_surface;
+ struct wl_signal destroy;
+ } events;
+
+ void *data;
+};
+
+struct wlr_wl_shell_surface_transient_state {
+ int32_t x;
+ int32_t y;
+ enum wl_shell_surface_transient flags;
+};
+
+struct wlr_wl_shell_surface_popup_state {
+ struct wlr_seat *seat;
+ uint32_t serial;
+};
+
+// each seat gets a popup grab
+struct wlr_wl_shell_popup_grab {
+ struct wl_client *client;
+ struct wlr_seat_pointer_grab pointer_grab;
+ struct wlr_seat *seat;
+ struct wl_list popups;
+ struct wl_list link; // wlr_wl_shell::popup_grabs
+};
+
+enum wlr_wl_shell_surface_state {
+ WLR_WL_SHELL_SURFACE_STATE_NONE,
+ WLR_WL_SHELL_SURFACE_STATE_TOPLEVEL,
+ WLR_WL_SHELL_SURFACE_STATE_MAXIMIZED,
+ WLR_WL_SHELL_SURFACE_STATE_FULLSCREEN,
+ WLR_WL_SHELL_SURFACE_STATE_TRANSIENT,
+ WLR_WL_SHELL_SURFACE_STATE_POPUP,
+};
+
+struct wlr_wl_shell_surface {
+ struct wlr_wl_shell *shell;
+ struct wl_client *client;
+ struct wl_resource *resource;
+ struct wlr_surface *surface;
+ bool configured;
+ struct wl_list link; // wlr_wl_shell::surfaces
+
+ uint32_t ping_serial;
+ struct wl_event_source *ping_timer;
+
+ enum wlr_wl_shell_surface_state state;
+ struct wlr_wl_shell_surface_transient_state *transient_state;
+ struct wlr_wl_shell_surface_popup_state *popup_state;
+ struct wl_list grab_link; // wlr_wl_shell_popup_grab::popups
+
+ char *title;
+ char *class;
+
+ struct wl_listener surface_destroy;
+
+ struct wlr_wl_shell_surface *parent;
+ struct wl_list popup_link;
+ struct wl_list popups;
+ bool popup_mapped;
+
+ struct {
+ struct wl_signal destroy;
+ struct wl_signal ping_timeout;
+ struct wl_signal new_popup;
+
+ struct wl_signal request_move;
+ struct wl_signal request_resize;
+ struct wl_signal request_fullscreen;
+ struct wl_signal request_maximize;
+
+ struct wl_signal set_state;
+ struct wl_signal set_title;
+ struct wl_signal set_class;
+ } events;
+
+ void *data;
+};
+
+struct wlr_wl_shell_surface_move_event {
+ struct wlr_wl_shell_surface *surface;
+ struct wlr_seat_client *seat;
+ uint32_t serial;
+};
+
+struct wlr_wl_shell_surface_resize_event {
+ struct wlr_wl_shell_surface *surface;
+ struct wlr_seat_client *seat;
+ uint32_t serial;
+ enum wl_shell_surface_resize edges;
+};
+
+struct wlr_wl_shell_surface_set_fullscreen_event {
+ struct wlr_wl_shell_surface *surface;
+ enum wl_shell_surface_fullscreen_method method;
+ uint32_t framerate;
+ struct wlr_output *output;
+};
+
+struct wlr_wl_shell_surface_maximize_event {
+ struct wlr_wl_shell_surface *surface;
+ struct wlr_output *output;
+};
+
+/**
+ * Create a wl_shell for this display.
+ */
+struct wlr_wl_shell *wlr_wl_shell_create(struct wl_display *display);
+
+/**
+ * Destroy this surface.
+ */
+void wlr_wl_shell_destroy(struct wlr_wl_shell *wlr_wl_shell);
+
+/**
+ * Send a ping to the surface. If the surface does not respond with a pong
+ * within a reasonable amount of time, the ping timeout event will be emitted.
+ */
+void wlr_wl_shell_surface_ping(struct wlr_wl_shell_surface *surface);
+
+/**
+ * Request that the surface configure itself to be the given size.
+ */
+void wlr_wl_shell_surface_configure(struct wlr_wl_shell_surface *surface,
+ enum wl_shell_surface_resize edges, int32_t width, int32_t height);
+
+/**
+ * Find a surface within this wl-shell 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_wl_shell_surface_surface_at(
+ struct wlr_wl_shell_surface *surface, double sx, double sy,
+ double *sub_sx, double *sub_sy);
+
+bool wlr_surface_is_wl_shell_surface(struct wlr_surface *surface);
+
+struct wlr_wl_shell_surface *wlr_wl_shell_surface_from_wlr_surface(
+ struct wlr_surface *surface);
+
+/**
+ * Call `iterator` on each surface in the shell surface tree, with the surface's
+ * position relative to the root xdg-surface. The function is called from root to
+ * leaves (in rendering order).
+ */
+void wlr_wl_shell_surface_for_each_surface(struct wlr_wl_shell_surface *surface,
+ wlr_surface_iterator_func_t iterator, void *user_data);
+
+#endif
diff --git a/include/wlr/types/wlr_xcursor_manager.h b/include/wlr/types/wlr_xcursor_manager.h
new file mode 100644
index 00000000..285006bf
--- /dev/null
+++ b/include/wlr/types/wlr_xcursor_manager.h
@@ -0,0 +1,69 @@
+/*
+ * 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 <wayland-server.h>
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/xcursor.h>
+
+/**
+ * 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;
+};
+
+/**
+ * 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.
+ */
+int 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 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);
+
+/**
+ * Set a wlr_cursor's cursor image to the specified cursor name for all scale
+ * factors. wlr_cursor will take over from this point and ensure the correct
+ * cursor is used on each output, assuming a wlr_output_layout is attached to
+ * it.
+ */
+void wlr_xcursor_manager_set_cursor_image(struct wlr_xcursor_manager *manager,
+ const char *name, struct wlr_cursor *cursor);
+
+#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 00000000..ba1ad84b
--- /dev/null
+++ b/include/wlr/types/wlr_xdg_decoration_v1.h
@@ -0,0 +1,69 @@
+#ifndef WLR_TYPES_WLR_XDG_DECORATION_V1
+#define WLR_TYPES_WLR_XDG_DECORATION_V1
+
+#include <wayland-server.h>
+#include <wlr/types/wlr_xdg_shell.h>
+
+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 resources;
+ 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 {
+ struct wl_resource *resource;
+ struct wlr_xdg_surface *surface;
+ struct wlr_xdg_decoration_manager_v1 *manager;
+ struct wl_list link; // wlr_xdg_decoration_manager_v1::link
+
+ bool added;
+ enum wlr_xdg_toplevel_decoration_v1_mode current_mode;
+ enum wlr_xdg_toplevel_decoration_v1_mode client_pending_mode;
+ enum wlr_xdg_toplevel_decoration_v1_mode server_pending_mode;
+
+ struct wl_list configure_list; // wlr_xdg_toplevel_decoration_v1_configure::link
+
+ struct {
+ struct wl_signal destroy;
+ struct wl_signal request_mode;
+ } events;
+
+ struct wl_listener surface_destroy;
+ struct wl_listener surface_configure;
+ struct wl_listener surface_ack_configure;
+ struct wl_listener surface_commit;
+
+ void *data;
+};
+
+struct wlr_xdg_decoration_manager_v1 *
+ wlr_xdg_decoration_manager_v1_create(struct wl_display *display);
+void wlr_xdg_decoration_manager_v1_destroy(
+ struct wlr_xdg_decoration_manager_v1 *manager);
+
+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_output_v1.h b/include/wlr/types/wlr_xdg_output_v1.h
new file mode 100644
index 00000000..d4279fb9
--- /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 <wayland-server.h>
+#include <wlr/types/wlr_output_layout.h>
+
+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 wlr_xdg_output_manager_v1 {
+ struct wl_global *global;
+ struct wl_list resources;
+ struct wlr_output_layout *layout;
+
+ struct wl_list outputs;
+
+ struct wl_listener layout_add;
+ struct wl_listener layout_change;
+ struct wl_listener layout_destroy;
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+};
+
+struct wlr_xdg_output_manager_v1 *wlr_xdg_output_manager_v1_create(
+ struct wl_display *display, struct wlr_output_layout *layout);
+void wlr_xdg_output_manager_v1_destroy(struct wlr_xdg_output_manager_v1 *manager);
+
+#endif
diff --git a/include/wlr/types/wlr_xdg_shell.h b/include/wlr/types/wlr_xdg_shell.h
new file mode 100644
index 00000000..1bca9ef3
--- /dev/null
+++ b/include/wlr/types/wlr_xdg_shell.h
@@ -0,0 +1,389 @@
+/*
+ * 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 <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_seat.h>
+#include <wayland-server.h>
+#include "xdg-shell-protocol.h"
+
+struct wlr_xdg_shell {
+ struct wl_global *global;
+ struct wl_list clients;
+ struct wl_list popup_grabs;
+ uint32_t ping_timeout;
+
+ struct wl_listener display_destroy;
+
+ struct {
+ /**
+ * The `new_surface` event signals that a client has requested to
+ * create a new shell surface. At this point, the surface is ready to
+ * be configured but is not mapped or ready receive input events. The
+ * surface will be ready to be managed on the `map` event.
+ */
+ struct wl_signal new_surface;
+ 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 {
+ struct wl_resource *resource;
+
+ struct wlr_box anchor_rect;
+ enum xdg_positioner_anchor anchor;
+ enum xdg_positioner_gravity gravity;
+ enum xdg_positioner_constraint_adjustment constraint_adjustment;
+
+ struct {
+ int32_t width, height;
+ } size;
+
+ struct {
+ int32_t x, y;
+ } offset;
+};
+
+struct wlr_xdg_popup {
+ struct wlr_xdg_surface *base;
+ struct wl_list link;
+
+ struct wl_resource *resource;
+ bool committed;
+ struct wlr_surface *parent;
+ struct wlr_seat *seat;
+
+ // Position of the popup relative to the upper left corner of the window
+ // geometry of the parent surface
+ struct wlr_box geometry;
+
+ struct wlr_xdg_positioner positioner;
+
+ struct wl_list grab_link; // wlr_xdg_popup_grab::popups
+};
+
+// 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 *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;
+ uint32_t tiled; // enum wlr_edges
+ uint32_t width, height;
+ uint32_t max_width, max_height;
+ uint32_t min_width, min_height;
+};
+
+struct wlr_xdg_toplevel {
+ struct wl_resource *resource;
+ struct wlr_xdg_surface *base;
+ struct wlr_xdg_surface *parent;
+ bool added;
+
+ struct wlr_xdg_toplevel_state client_pending;
+ struct wlr_xdg_toplevel_state server_pending;
+ struct wlr_xdg_toplevel_state current;
+
+ char *title;
+ char *app_id;
+
+ struct {
+ 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;
+};
+
+struct wlr_xdg_surface_configure {
+ struct wlr_xdg_surface *surface;
+ struct wl_list link; // wlr_xdg_surface::configure_list
+ uint32_t serial;
+
+ struct wlr_xdg_toplevel_state *toplevel_state;
+};
+
+/**
+ * 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.
+ *
+ * When a surface has a role and is ready to be displayed, the `map` event is
+ * emitted. When a surface should no longer be displayed, the `unmap` event is
+ * emitted. The `unmap` event is guaranteed to be emitted before the `destroy`
+ * event if the view is destroyed when mapped.
+ */
+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
+ enum wlr_xdg_surface_role role;
+
+ union {
+ struct wlr_xdg_toplevel *toplevel;
+ struct wlr_xdg_popup *popup;
+ };
+
+ struct wl_list popups; // wlr_xdg_popup::link
+
+ bool added, configured, mapped;
+ uint32_t configure_serial;
+ struct wl_event_source *configure_idle;
+ uint32_t configure_next_serial;
+ struct wl_list configure_list;
+
+ bool has_next_geometry;
+ struct wlr_box next_geometry;
+ struct wlr_box geometry;
+
+ struct wl_listener surface_destroy;
+ struct wl_listener surface_commit;
+
+ struct {
+ struct wl_signal destroy;
+ struct wl_signal ping_timeout;
+ struct wl_signal new_popup;
+ /**
+ * The `map` event signals that the shell surface is ready to be
+ * managed by the compositor and rendered on the screen. At this point,
+ * the surface has configured its properties, has had the opportunity
+ * to bind to the seat to receive input events, and has a buffer that
+ * is ready to be rendered. You can now safely add this surface to a
+ * list of views.
+ */
+ struct wl_signal map;
+ /**
+ * The `unmap` event signals that the surface is no longer in a state
+ * where it should be shown on the screen. This might happen if the
+ * surface no longer has a displayable buffer because either the
+ * surface has been hidden or is about to be destroyed.
+ */
+ struct wl_signal unmap;
+
+ // for protocol extensions
+ struct wl_signal configure; // wlr_xdg_surface_configure
+ struct wl_signal ack_configure; // wlr_xdg_surface_configure
+ } events;
+
+ void *data;
+};
+
+struct wlr_xdg_toplevel_move_event {
+ struct wlr_xdg_surface *surface;
+ struct wlr_seat_client *seat;
+ uint32_t serial;
+};
+
+struct wlr_xdg_toplevel_resize_event {
+ struct wlr_xdg_surface *surface;
+ struct wlr_seat_client *seat;
+ uint32_t serial;
+ uint32_t edges;
+};
+
+struct wlr_xdg_toplevel_set_fullscreen_event {
+ struct wlr_xdg_surface *surface;
+ bool fullscreen;
+ struct wlr_output *output;
+};
+
+struct wlr_xdg_toplevel_show_window_menu_event {
+ struct wlr_xdg_surface *surface;
+ struct wlr_seat_client *seat;
+ uint32_t serial;
+ uint32_t x, y;
+};
+
+struct wlr_xdg_shell *wlr_xdg_shell_create(struct wl_display *display);
+void wlr_xdg_shell_destroy(struct wlr_xdg_shell *xdg_shell);
+
+struct wlr_xdg_surface *wlr_xdg_surface_from_resource(
+ struct wl_resource *resource);
+struct wlr_xdg_surface *wlr_xdg_surface_from_popup_resource(
+ struct wl_resource *resource);
+struct wlr_xdg_surface *wlr_xdg_surface_from_toplevel_resource(
+ struct wl_resource *resource);
+
+struct wlr_box wlr_xdg_positioner_get_geometry(
+ struct wlr_xdg_positioner *positioner);
+
+/**
+ * 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_surface *surface,
+ uint32_t width, uint32_t height);
+
+/**
+ * Request that this toplevel surface show itself in an activated or deactivated
+ * state. Returns the associated configure serial.
+ */
+uint32_t wlr_xdg_toplevel_set_activated(struct wlr_xdg_surface *surface,
+ bool activated);
+
+/**
+ * Request that this toplevel surface consider itself maximized or not
+ * maximized. Returns the associated configure serial.
+ */
+uint32_t wlr_xdg_toplevel_set_maximized(struct wlr_xdg_surface *surface,
+ bool maximized);
+
+/**
+ * Request that this toplevel surface consider itself fullscreen or not
+ * fullscreen. Returns the associated configure serial.
+ */
+uint32_t wlr_xdg_toplevel_set_fullscreen(struct wlr_xdg_surface *surface,
+ bool fullscreen);
+
+/**
+ * Request that this toplevel surface consider itself to be resizing or not
+ * resizing. Returns the associated configure serial.
+ */
+uint32_t wlr_xdg_toplevel_set_resizing(struct wlr_xdg_surface *surface,
+ bool resizing);
+
+/**
+ * Request that this toplevel surface 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_surface *surface,
+ uint32_t tiled_edges);
+
+/**
+ * Request that this xdg surface closes.
+ */
+void wlr_xdg_surface_send_close(struct wlr_xdg_surface *surface);
+
+/**
+ * Get the geometry for this positioner based on the anchor rect, gravity, and
+ * size of this positioner.
+ */
+struct wlr_box wlr_xdg_positioner_get_geometry(
+ struct wlr_xdg_positioner *positioner);
+
+/**
+ * Get the anchor point for this popup in the toplevel parent's coordinate system.
+ */
+void wlr_xdg_popup_get_anchor_point(struct wlr_xdg_popup *popup,
+ int *toplevel_sx, int *toplevel_sy);
+
+/**
+ * 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,
+ struct wlr_box *toplevel_sx_box);
+
+/**
+ Invert the right/left anchor and gravity for this positioner. This can be
+ used to "flip" the positioner around the anchor rect in the x direction.
+ */
+void wlr_positioner_invert_x(struct wlr_xdg_positioner *positioner);
+
+/**
+ Invert the top/bottom anchor and gravity for this positioner. This can be
+ used to "flip" the positioner around the anchor rect in the y direction.
+ */
+void wlr_positioner_invert_y(struct wlr_xdg_positioner *positioner);
+
+/**
+ * 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);
+
+bool wlr_surface_is_xdg_surface(struct wlr_surface *surface);
+
+struct wlr_xdg_surface *wlr_xdg_surface_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 surface and popup in the xdg-surface tree, 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);
+
+/**
+ * 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);
+
+/**
+ * Call `iterator` on each popup in the xdg-surface tree, with the popup'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(struct wlr_xdg_surface *surface,
+ wlr_surface_iterator_func_t iterator, void *user_data);
+
+#endif
diff --git a/include/wlr/types/wlr_xdg_shell_v6.h b/include/wlr/types/wlr_xdg_shell_v6.h
new file mode 100644
index 00000000..a69e488f
--- /dev/null
+++ b/include/wlr/types/wlr_xdg_shell_v6.h
@@ -0,0 +1,359 @@
+/*
+ * 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_V6_H
+#define WLR_TYPES_WLR_XDG_SHELL_V6_H
+
+#include <wayland-server.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_seat.h>
+#include "xdg-shell-unstable-v6-protocol.h"
+
+struct wlr_xdg_shell_v6 {
+ struct wl_global *global;
+ struct wl_list clients;
+ struct wl_list popup_grabs;
+ uint32_t ping_timeout;
+
+ struct wl_listener display_destroy;
+
+ struct {
+ /**
+ * The `new_surface` event signals that a client has requested to
+ * create a new shell surface. At this point, the surface is ready to
+ * be configured but is not mapped or ready receive input events. The
+ * surface will be ready to be managed on the `map` event.
+ */
+ struct wl_signal new_surface;
+ struct wl_signal destroy;
+ } events;
+
+ void *data;
+};
+
+struct wlr_xdg_client_v6 {
+ struct wlr_xdg_shell_v6 *shell;
+ struct wl_resource *resource;
+ struct wl_client *client;
+ struct wl_list surfaces;
+
+ struct wl_list link; // wlr_xdg_shell_v6::clients
+
+ uint32_t ping_serial;
+ struct wl_event_source *ping_timer;
+};
+
+struct wlr_xdg_positioner_v6 {
+ struct wlr_box anchor_rect;
+ enum zxdg_positioner_v6_anchor anchor;
+ enum zxdg_positioner_v6_gravity gravity;
+ enum zxdg_positioner_v6_constraint_adjustment constraint_adjustment;
+
+ struct {
+ int32_t width, height;
+ } size;
+
+ struct {
+ int32_t x, y;
+ } offset;
+};
+
+struct wlr_xdg_popup_v6 {
+ struct wlr_xdg_surface_v6 *base;
+ struct wl_list link;
+
+ struct wl_resource *resource;
+ bool committed;
+ struct wlr_xdg_surface_v6 *parent;
+ struct wlr_seat *seat;
+
+ // Position of the popup relative to the upper left corner of the window
+ // geometry of the parent surface
+ struct wlr_box geometry;
+
+ struct wlr_xdg_positioner_v6 positioner;
+
+ struct wl_list grab_link; // wlr_xdg_popup_grab_v6::popups
+};
+
+// each seat gets a popup grab
+struct wlr_xdg_popup_grab_v6 {
+ struct wl_client *client;
+ struct wlr_seat_pointer_grab pointer_grab;
+ struct wlr_seat_keyboard_grab keyboard_grab;
+ struct wlr_seat *seat;
+ struct wl_list popups;
+ struct wl_list link; // wlr_xdg_shell_v6::popup_grabs
+ struct wl_listener seat_destroy;
+};
+
+enum wlr_xdg_surface_v6_role {
+ WLR_XDG_SURFACE_V6_ROLE_NONE,
+ WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL,
+ WLR_XDG_SURFACE_V6_ROLE_POPUP,
+};
+
+struct wlr_xdg_toplevel_v6_state {
+ bool maximized, fullscreen, resizing, activated;
+ uint32_t width, height;
+ uint32_t max_width, max_height;
+ uint32_t min_width, min_height;
+};
+
+/**
+ * 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.
+ *
+ * When a surface has a role and is ready to be displayed, the `map` event is
+ * emitted. When a surface should no longer be displayed, the `unmap` event is
+ * emitted. The `unmap` event is guaranteed to be emitted before the `destroy`
+ * event if the view is destroyed when mapped.
+ */
+struct wlr_xdg_toplevel_v6 {
+ struct wl_resource *resource;
+ struct wlr_xdg_surface_v6 *base;
+ struct wlr_xdg_surface_v6 *parent;
+ bool added;
+
+ struct wlr_xdg_toplevel_v6_state client_pending;
+ struct wlr_xdg_toplevel_v6_state server_pending;
+ struct wlr_xdg_toplevel_v6_state current;
+
+ char *title;
+ char *app_id;
+
+ struct {
+ 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;
+};
+
+struct wlr_xdg_surface_v6_configure {
+ struct wl_list link; // wlr_xdg_surface_v6::configure_list
+ uint32_t serial;
+
+ struct wlr_xdg_toplevel_v6_state *toplevel_state;
+};
+
+struct wlr_xdg_surface_v6 {
+ struct wlr_xdg_client_v6 *client;
+ struct wl_resource *resource;
+ struct wlr_surface *surface;
+ struct wl_list link; // wlr_xdg_client_v6::surfaces
+ enum wlr_xdg_surface_v6_role role;
+
+ union {
+ struct wlr_xdg_toplevel_v6 *toplevel;
+ struct wlr_xdg_popup_v6 *popup;
+ };
+
+ struct wl_list popups; // wlr_xdg_popup_v6::link
+
+ bool added, configured, mapped;
+ uint32_t configure_serial;
+ struct wl_event_source *configure_idle;
+ uint32_t configure_next_serial;
+ struct wl_list configure_list;
+
+ bool has_next_geometry;
+ struct wlr_box next_geometry;
+ struct wlr_box geometry;
+
+ struct wl_listener surface_destroy;
+ struct wl_listener surface_commit;
+
+ struct {
+ struct wl_signal destroy;
+ struct wl_signal ping_timeout;
+ struct wl_signal new_popup;
+ /**
+ * The `map` event signals that the shell surface is ready to be
+ * managed by the compositor and rendered on the screen. At this point,
+ * the surface has configured its properties, has had the opportunity
+ * to bind to the seat to receive input events, and has a buffer that
+ * is ready to be rendered. You can now safely add this surface to a
+ * list of views.
+ */
+ struct wl_signal map;
+ /**
+ * The `unmap` event signals that the surface is no longer in a state
+ * where it should be shown on the screen. This might happen if the
+ * surface no longer has a displayable buffer because either the
+ * surface has been hidden or is about to be destroyed.
+ */
+ struct wl_signal unmap;
+ } events;
+
+ void *data;
+};
+
+struct wlr_xdg_toplevel_v6_move_event {
+ struct wlr_xdg_surface_v6 *surface;
+ struct wlr_seat_client *seat;
+ uint32_t serial;
+};
+
+struct wlr_xdg_toplevel_v6_resize_event {
+ struct wlr_xdg_surface_v6 *surface;
+ struct wlr_seat_client *seat;
+ uint32_t serial;
+ uint32_t edges;
+};
+
+struct wlr_xdg_toplevel_v6_set_fullscreen_event {
+ struct wlr_xdg_surface_v6 *surface;
+ bool fullscreen;
+ struct wlr_output *output;
+};
+
+struct wlr_xdg_toplevel_v6_show_window_menu_event {
+ struct wlr_xdg_surface_v6 *surface;
+ struct wlr_seat_client *seat;
+ uint32_t serial;
+ uint32_t x, y;
+};
+
+struct wlr_xdg_shell_v6 *wlr_xdg_shell_v6_create(struct wl_display *display);
+void wlr_xdg_shell_v6_destroy(struct wlr_xdg_shell_v6 *xdg_shell);
+
+/**
+ * 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_v6_ping(struct wlr_xdg_surface_v6 *surface);
+
+/**
+ * Request that this toplevel surface be the given size. Returns the associated
+ * configure serial.
+ */
+uint32_t wlr_xdg_toplevel_v6_set_size(struct wlr_xdg_surface_v6 *surface,
+ uint32_t width, uint32_t height);
+
+/**
+ * Request that this toplevel surface show itself in an activated or deactivated
+ * state. Returns the associated configure serial.
+ */
+uint32_t wlr_xdg_toplevel_v6_set_activated(struct wlr_xdg_surface_v6 *surface,
+ bool activated);
+
+/**
+ * Request that this toplevel surface consider itself maximized or not
+ * maximized. Returns the associated configure serial.
+ */
+uint32_t wlr_xdg_toplevel_v6_set_maximized(struct wlr_xdg_surface_v6 *surface,
+ bool maximized);
+
+/**
+ * Request that this toplevel surface consider itself fullscreen or not
+ * fullscreen. Returns the associated configure serial.
+ */
+uint32_t wlr_xdg_toplevel_v6_set_fullscreen(struct wlr_xdg_surface_v6 *surface,
+ bool fullscreen);
+
+/**
+ * Request that this toplevel surface consider itself to be resizing or not
+ * resizing. Returns the associated configure serial.
+ */
+uint32_t wlr_xdg_toplevel_v6_set_resizing(struct wlr_xdg_surface_v6 *surface,
+ bool resizing);
+
+/**
+ * Request that this xdg surface closes.
+ */
+void wlr_xdg_surface_v6_send_close(struct wlr_xdg_surface_v6 *surface);
+
+/**
+ * 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_v6_surface_at(
+ struct wlr_xdg_surface_v6 *surface, double sx, double sy,
+ double *sub_x, double *sub_y);
+
+/**
+ * Get the geometry for this positioner based on the anchor rect, gravity, and
+ * size of this positioner.
+ */
+struct wlr_box wlr_xdg_positioner_v6_get_geometry(
+ struct wlr_xdg_positioner_v6 *positioner);
+
+/**
+ * Get the anchor point for this popup in the toplevel parent's coordinate system.
+ */
+void wlr_xdg_popup_v6_get_anchor_point(struct wlr_xdg_popup_v6 *popup,
+ int *toplevel_sx, int *toplevel_sy);
+
+/**
+ * Convert the given coordinates in the popup coordinate system to the toplevel
+ * surface coordinate system.
+ */
+void wlr_xdg_popup_v6_get_toplevel_coords(struct wlr_xdg_popup_v6 *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_v6_unconstrain_from_box(struct wlr_xdg_popup_v6 *popup,
+ struct wlr_box *toplevel_sx_box);
+
+/**
+ Invert the right/left anchor and gravity for this positioner. This can be
+ used to "flip" the positioner around the anchor rect in the x direction.
+ */
+void wlr_positioner_v6_invert_x(
+ struct wlr_xdg_positioner_v6 *positioner);
+
+/**
+ Invert the top/bottom anchor and gravity for this positioner. This can be
+ used to "flip" the positioner around the anchor rect in the y direction.
+ */
+void wlr_positioner_v6_invert_y(
+ struct wlr_xdg_positioner_v6 *positioner);
+
+bool wlr_surface_is_xdg_surface_v6(struct wlr_surface *surface);
+
+struct wlr_xdg_surface_v6 *wlr_xdg_surface_v6_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_v6_get_geometry(struct wlr_xdg_surface_v6 *surface, struct wlr_box *box);
+
+/**
+ * Call `iterator` on each surface and popup in the xdg-surface tree, 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_v6_for_each_surface(struct wlr_xdg_surface_v6 *surface,
+ wlr_surface_iterator_func_t iterator, void *user_data);
+
+/**
+ * Call `iterator` on each popup in the xdg-surface tree, with the popup's
+ * position relative to the root xdg-surface. The function is called from root
+ * to leaves (in rendering order).
+ */
+void wlr_xdg_surface_v6_for_each_popup(struct wlr_xdg_surface_v6 *surface,
+ wlr_surface_iterator_func_t iterator, void *user_data);
+
+#endif
diff --git a/include/wlr/util/edges.h b/include/wlr/util/edges.h
new file mode 100644
index 00000000..bf1eb1e7
--- /dev/null
+++ b/include/wlr/util/edges.h
@@ -0,0 +1,28 @@
+/*
+ * 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 by email and follow a 1-year deprecation
+ * schedule. Send an email to ~sircmpwn/wlroots-announce+subscribe@lists.sr.ht
+ * to receive these announcements.
+ */
+
+#ifndef WLR_UTIL_EDGES_H
+#define WLR_UTIL_EDGES_H
+
+enum wlr_edges {
+ WLR_EDGE_NONE = 0,
+ WLR_EDGE_TOP = 1,
+ WLR_EDGE_BOTTOM = 2,
+ WLR_EDGE_LEFT = 4,
+ WLR_EDGE_RIGHT = 8,
+};
+
+#endif
diff --git a/include/wlr/util/log.h b/include/wlr/util/log.h
new file mode 100644
index 00000000..2c441180
--- /dev/null
+++ b/include/wlr/util/log.h
@@ -0,0 +1,64 @@
+/*
+ * 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 by email and follow a 1-year deprecation
+ * schedule. Send an email to ~sircmpwn/wlroots-announce+subscribe@lists.sr.ht
+ * to receive these announcements.
+ */
+
+#ifndef WLR_UTIL_LOG_H
+#define WLR_UTIL_LOG_H
+
+#include <stdbool.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+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);
+
+// Will log all messages less than or equal to `verbosity`
+// If `callback` is NULL, wlr will use its default logger.
+// The 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);
+
+// Returns the log verbosity provided to 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);
+const char *_wlr_strip_path(const char *filepath);
+
+#define wlr_log(verb, fmt, ...) \
+ _wlr_log(verb, "[%s:%d] " fmt, _wlr_strip_path(__FILE__), __LINE__, ##__VA_ARGS__)
+
+#define wlr_vlog(verb, fmt, args) \
+ _wlr_vlog(verb, "[%s:%d] " fmt, _wlr_strip_path(__FILE__), __LINE__, args)
+
+#define wlr_log_errno(verb, fmt, ...) \
+ wlr_log(verb, fmt ": %s", ##__VA_ARGS__, strerror(errno))
+
+#endif
diff --git a/include/wlr/util/meson.build b/include/wlr/util/meson.build
new file mode 100644
index 00000000..ee72cbd6
--- /dev/null
+++ b/include/wlr/util/meson.build
@@ -0,0 +1,6 @@
+install_headers(
+ 'edges.h',
+ 'log.h',
+ 'region.h',
+ subdir: 'wlr/util',
+)
diff --git a/include/wlr/util/region.h b/include/wlr/util/region.h
new file mode 100644
index 00000000..4aca07e1
--- /dev/null
+++ b/include/wlr/util/region.h
@@ -0,0 +1,56 @@
+/*
+ * 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 by email and follow a 1-year deprecation
+ * schedule. Send an email to ~sircmpwn/wlroots-announce+subscribe@lists.sr.ht
+ * to receive these announcements.
+ */
+
+#ifndef WLR_UTIL_REGION_H
+#define WLR_UTIL_REGION_H
+
+#include <stdbool.h>
+#include <pixman.h>
+#include <wayland-server.h>
+
+/**
+ * Scales a region, ie. multiplies all its coordinates by `scale`.
+ *
+ * The resulting coordinates are rounded up or down so that the new region is
+ * at least as big as the original one.
+ */
+void wlr_region_scale(pixman_region32_t *dst, pixman_region32_t *src,
+ float scale);
+
+/**
+ * Applies a transform to a region inside a box of size `width` x `height`.
+ */
+void wlr_region_transform(pixman_region32_t *dst, pixman_region32_t *src,
+ enum wl_output_transform transform, int width, int height);
+
+/**
+ * Expands the region of `distance`. If `distance` is negative, it shrinks the
+ * region.
+ */
+void wlr_region_expand(pixman_region32_t *dst, 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, pixman_region32_t *src,
+ float rotation, int ox, int oy);
+
+bool wlr_region_confine(pixman_region32_t *region, double x1, double y1, double x2,
+ double y2, double *x2_out, double *y2_out);
+
+#endif
diff --git a/include/wlr/version.h.in b/include/wlr/version.h.in
new file mode 100644
index 00000000..cdc0fd75
--- /dev/null
+++ b/include/wlr/version.h.in
@@ -0,0 +1,16 @@
+#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)
+
+#mesondefine WLR_VERSION_API_CURRENT
+#mesondefine WLR_VERSION_API_REVISION
+#mesondefine WLR_VERSION_API_AGE
+
+#endif
diff --git a/include/wlr/xcursor.h b/include/wlr/xcursor.h
new file mode 100644
index 00000000..39874f39
--- /dev/null
+++ b/include/wlr/xcursor.h
@@ -0,0 +1,102 @@
+/*
+ * 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 by email and follow a 1-year deprecation
+ * schedule. Send an email to ~sircmpwn/wlroots-announce+subscribe@lists.sr.ht
+ * to receive these announcements.
+ */
+
+#ifndef WLR_XCURSOR_H
+#define WLR_XCURSOR_H
+
+#include <stdint.h>
+#include <wlr/util/edges.h>
+
+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;
+};
+
+struct wlr_xcursor {
+ unsigned int image_count;
+ struct wlr_xcursor_image **images;
+ char *name;
+ uint32_t total_delay; /* length 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 at the given cursor size (in pixels). This is
+ * useful if you need cursor images for your compositor to use when a
+ * client-side cursors 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).
+ */
+struct wlr_xcursor_theme *wlr_xcursor_theme_load(const char *name, int size);
+
+void wlr_xcursor_theme_destroy(struct wlr_xcursor_theme *theme);
+
+/**
+ * Obtains a wlr_xcursor image for the specified cursor name (e.g. "left_ptr").
+ */
+struct wlr_xcursor *wlr_xcursor_theme_get_cursor(
+ struct wlr_xcursor_theme *theme, const char *name);
+
+/**
+ * Returns the current frame number for an animated cursor give a monotonic time
+ * reference.
+ */
+int wlr_xcursor_frame(struct wlr_xcursor *cursor, uint32_t time);
+
+/**
+ * Get the name of the resize cursor image 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 00000000..40cc8848
--- /dev/null
+++ b/include/wlr/xwayland.h
@@ -0,0 +1,260 @@
+/*
+ * 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_H
+#define WLR_XWAYLAND_H
+
+#include <stdbool.h>
+#include <time.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_seat.h>
+#include <xcb/xcb.h>
+
+struct wlr_xwm;
+struct wlr_xwayland_cursor;
+struct wlr_gtk_primary_selection_device_manager;
+
+struct wlr_xwayland {
+ pid_t pid;
+ struct wl_client *client;
+ struct wl_event_source *sigusr1_source;
+ struct wl_listener client_destroy;
+ struct wlr_xwm *xwm;
+ struct wlr_xwayland_cursor *cursor;
+ int wm_fd[2], wl_fd[2];
+
+ time_t server_start;
+
+ /* Anything above display is reset on Xwayland restart, rest is conserved */
+
+ int display;
+ int x_fd[2];
+ struct wl_event_source *x_fd_read_event[2];
+ struct wl_listener display_destroy;
+
+ bool lazy;
+
+ struct wl_display *wl_display;
+ struct wlr_compositor *compositor;
+ struct wlr_seat *seat;
+
+ struct {
+ struct wl_signal ready;
+ struct wl_signal new_surface;
+ } events;
+
+ struct wl_listener seat_destroy;
+
+ /**
+ * 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);
+
+ 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,
+};
+
+struct wlr_xwayland_surface_hints {
+ uint32_t flags;
+ uint32_t input;
+ int32_t initial_state;
+ xcb_pixmap_t icon_pixmap;
+ xcb_window_t icon_window;
+ int32_t icon_x, icon_y;
+ xcb_pixmap_t icon_mask;
+ xcb_window_t window_group;
+};
+
+struct wlr_xwayland_surface_size_hints {
+ uint32_t flags;
+ int32_t x, y;
+ int32_t width, height;
+ int32_t min_width, min_height;
+ int32_t max_width, max_height;
+ int32_t width_inc, height_inc;
+ int32_t base_width, base_height;
+ int32_t min_aspect_num, min_aspect_den;
+ int32_t max_aspect_num, max_aspect_den;
+ uint32_t win_gravity;
+};
+
+/**
+ * An Xwayland user interface component. It has an absolute position in
+ * layout-local coordinates.
+ *
+ * When a surface is ready to be displayed, the `map` event is emitted. When a
+ * surface should no longer be displayed, the `unmap` event is emitted. The
+ * `unmap` event is guaranteed to be emitted before the `destroy` event if the
+ * view is destroyed when mapped.
+ */
+struct wlr_xwayland_surface {
+ xcb_window_t window_id;
+ struct wlr_xwm *xwm;
+ uint32_t surface_id;
+
+ struct wl_list link;
+ struct wl_list unpaired_link;
+
+ struct wlr_surface *surface;
+ int16_t x, y;
+ uint16_t width, height;
+ uint16_t saved_width, saved_height;
+ bool override_redirect;
+ bool mapped;
+
+ char *title;
+ char *class;
+ char *instance;
+ char *role;
+ 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;
+ struct wlr_xwayland_surface_hints *hints;
+ uint32_t hints_urgency;
+ struct wlr_xwayland_surface_size_hints *size_hints;
+
+ bool pinging;
+ struct wl_event_source *ping_timer;
+
+ // _NET_WM_STATE
+ bool modal;
+ bool fullscreen;
+ bool maximized_vert, maximized_horz;
+
+ bool has_alpha;
+
+ struct {
+ struct wl_signal destroy;
+ struct wl_signal request_configure;
+ struct wl_signal request_move;
+ struct wl_signal request_resize;
+ struct wl_signal request_maximize;
+ struct wl_signal request_fullscreen;
+ struct wl_signal request_activate;
+
+ struct wl_signal map;
+ struct wl_signal unmap;
+ struct wl_signal set_title;
+ struct wl_signal set_class;
+ struct wl_signal set_role;
+ struct wl_signal set_parent;
+ struct wl_signal set_pid;
+ struct wl_signal set_window_type;
+ struct wl_signal set_hints;
+ struct wl_signal set_decorations;
+ struct wl_signal set_override_redirect;
+ struct wl_signal ping_timeout;
+ } events;
+
+ struct wl_listener surface_destroy;
+
+ void *data;
+};
+
+struct wlr_xwayland_surface_configure_event {
+ struct wlr_xwayland_surface *surface;
+ int16_t x, y;
+ uint16_t width, height;
+};
+
+// TODO: maybe add a seat to these
+struct wlr_xwayland_move_event {
+ struct wlr_xwayland_surface *surface;
+};
+
+struct wlr_xwayland_resize_event {
+ struct wlr_xwayland_surface *surface;
+ uint32_t edges;
+};
+
+/** Create an Xwayland server.
+ *
+ * The server supports a lazy mode in which Xwayland is only started when a
+ * client tries to connect.
+ *
+ * Note: wlr_xwayland will setup a global SIGUSR1 handler on the compositor
+ * process.
+ */
+struct wlr_xwayland *wlr_xwayland_create(struct wl_display *wl_display,
+ struct wlr_compositor *compositor, bool lazy);
+
+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);
+
+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_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);
+
+bool wlr_surface_is_xwayland_surface(struct wlr_surface *surface);
+
+struct wlr_xwayland_surface *wlr_xwayland_surface_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 *surface);
+
+
+#endif
diff --git a/include/xcursor/cursor_data.h b/include/xcursor/cursor_data.h
new file mode 100644
index 00000000..dd7a80af
--- /dev/null
+++ b/include/xcursor/cursor_data.h
@@ -0,0 +1,554 @@
+/*
+* 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 <stdint.h>
+
+static 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 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 },
+};
diff --git a/include/xcursor/xcursor.h b/include/xcursor/xcursor.h
new file mode 100644
index 00000000..62e23220
--- /dev/null
+++ b/include/xcursor/xcursor.h
@@ -0,0 +1,65 @@
+/*
+ * 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
+
+typedef int XcursorBool;
+typedef unsigned int XcursorUInt;
+
+typedef XcursorUInt XcursorDim;
+typedef XcursorUInt XcursorPixel;
+
+typedef struct _XcursorImage {
+ XcursorUInt version; /* version of the image data */
+ XcursorDim size; /* nominal size for matching */
+ XcursorDim width; /* actual width */
+ XcursorDim height; /* actual height */
+ XcursorDim xhot; /* hot spot x (must be inside image) */
+ XcursorDim yhot; /* hot spot y (must be inside image) */
+ XcursorUInt delay; /* animation delay to next frame (ms) */
+ XcursorPixel *pixels; /* pointer to pixels */
+} XcursorImage;
+
+/*
+ * Other data structures exposed by the library API
+ */
+typedef struct _XcursorImages {
+ int nimage; /* number of images */
+ XcursorImage **images; /* array of XcursorImage pointers */
+ char *name; /* name used to load images */
+} XcursorImages;
+
+XcursorImages *
+XcursorLibraryLoadImages (const char *file, const char *theme, int size);
+
+void
+XcursorImagesDestroy (XcursorImages *images);
+
+void
+xcursor_load_theme(const char *theme, int size,
+ void (*load_callback)(XcursorImages *, void *),
+ void *user_data);
+#endif
diff --git a/include/xwayland/selection.h b/include/xwayland/selection.h
new file mode 100644
index 00000000..85201461
--- /dev/null
+++ b/include/xwayland/selection.h
@@ -0,0 +1,74 @@
+#ifndef XWAYLAND_SELECTION_H
+#define XWAYLAND_SELECTION_H
+
+#include <xcb/xfixes.h>
+
+#define INCR_CHUNK_SIZE (64 * 1024)
+
+#define XDND_VERSION 5
+
+struct wlr_primary_selection_source;
+
+struct wlr_xwm_selection;
+
+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 source_fd;
+ struct wl_event_source *source;
+
+ // when sending to x11
+ xcb_selection_request_event_t request;
+ struct wl_list outgoing_link;
+
+ // when receiving from x11
+ int property_start;
+ xcb_get_property_reply_t *property_reply;
+};
+
+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 wlr_xwm_selection_transfer incoming;
+ struct wl_list outgoing;
+};
+
+void xwm_selection_transfer_remove_source(
+ struct wlr_xwm_selection_transfer *transfer);
+void xwm_selection_transfer_close_source_fd(
+ struct wlr_xwm_selection_transfer *transfer);
+void xwm_selection_transfer_destroy_property_reply(
+ 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_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 *xwm);
+void xwm_selection_finish(struct wlr_xwm *xwm);
+
+#endif
diff --git a/include/xwayland/xwm.h b/include/xwayland/xwm.h
new file mode 100644
index 00000000..c1be572b
--- /dev/null
+++ b/include/xwayland/xwm.h
@@ -0,0 +1,158 @@
+#ifndef XWAYLAND_XWM_H
+#define XWAYLAND_XWM_H
+
+#include <wayland-server-core.h>
+#include <wlr/config.h>
+#include <wlr/xwayland.h>
+#include <xcb/render.h>
+#if WLR_HAS_XCB_ICCCM
+#include <xcb/xcb_icccm.h>
+#endif
+#if WLR_HAS_XCB_ERRORS
+#include <xcb/xcb_errors.h>
+#endif
+#include "xwayland/selection.h"
+
+/* 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,
+ 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_WINDOW_TYPE,
+ WM_TAKE_FOCUS,
+ WINDOW,
+ _NET_ACTIVE_WINDOW,
+ _NET_WM_MOVERESIZE,
+ _NET_WM_NAME,
+ _NET_SUPPORTING_WM_CHECK,
+ _NET_WM_STATE_MODAL,
+ _NET_WM_STATE_FULLSCREEN,
+ _NET_WM_STATE_MAXIMIZED_VERT,
+ _NET_WM_STATE_MAXIMIZED_HORZ,
+ _NET_WM_PING,
+ WM_STATE,
+ CLIPBOARD,
+ PRIMARY,
+ WL_SELECTION,
+ TARGETS,
+ CLIPBOARD_MANAGER,
+ INCR,
+ TEXT,
+ TIMESTAMP,
+ DELETE,
+ 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,
+ 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,
+ ATOM_LAST,
+};
+
+extern const char *atom_map[ATOM_LAST];
+
+enum net_wm_state_action {
+ NET_WM_STATE_REMOVE = 0,
+ NET_WM_STATE_ADD = 1,
+ NET_WM_STATE_TOGGLE = 2,
+};
+
+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;
+
+ xcb_window_t selection_window;
+ struct wlr_xwm_selection clipboard_selection;
+ struct wlr_xwm_selection primary_selection;
+
+ xcb_window_t dnd_window;
+ struct wlr_xwm_selection dnd_selection;
+
+ struct wlr_xwayland_surface *focus_surface;
+
+ struct wl_list surfaces; // wlr_xwayland_surface::link
+ struct wl_list unpaired_surfaces; // wlr_xwayland_surface::unpaired_link
+
+ struct wlr_drag *drag;
+ struct wlr_xwayland_surface *drag_focus;
+
+ const xcb_query_extension_reply_t *xfixes;
+#if WLR_HAS_XCB_ERRORS
+ xcb_errors_context_t *errors_context;
+#endif
+
+ struct wl_listener compositor_new_surface;
+ struct wl_listener compositor_destroy;
+ struct wl_listener seat_selection;
+ struct wl_listener seat_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);
+
+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 00000000..df1dbb84
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,205 @@
+project(
+ 'wlroots',
+ 'c',
+ version: '0.2.0',
+ license: 'MIT',
+ meson_version: '>=0.48.0',
+ default_options: [
+ 'c_std=c11',
+ 'warning_level=2',
+ 'werror=true',
+ ],
+)
+
+# Format of so_version is CURRENT, REVISION, AGE.
+# See: https://autotools.io/libtool/version.html
+# for a reference about clean library versioning.
+so_version = ['0', '0', '0']
+
+add_project_arguments(
+ [
+ '-DWLR_SRC_DIR="@0@"'.format(meson.current_source_dir()),
+ '-DWLR_USE_UNSTABLE',
+
+ '-Wno-unused-parameter',
+ '-Wundef',
+ ],
+ language: 'c',
+)
+
+conf_data = configuration_data()
+conf_data.set10('WLR_HAS_LIBCAP', false)
+conf_data.set10('WLR_HAS_SYSTEMD', false)
+conf_data.set10('WLR_HAS_ELOGIND', false)
+conf_data.set10('WLR_HAS_X11_BACKEND', false)
+conf_data.set10('WLR_HAS_XWAYLAND', false)
+conf_data.set10('WLR_HAS_XCB_ERRORS', false)
+conf_data.set10('WLR_HAS_XCB_ICCCM', false)
+
+wlr_inc = include_directories('.', 'include')
+
+cc = meson.get_compiler('c')
+
+# Clang complains about some zeroed initializer lists (= {0}), even though they
+# are valid
+if cc.get_id() == 'clang'
+ add_project_arguments('-Wno-missing-field-initializers', language: 'c')
+ add_project_arguments('-Wno-missing-braces', language: 'c')
+endif
+
+# Avoid wl_buffer deprecation warnings
+add_project_arguments('-DWL_HIDE_DEPRECATED', language: 'c')
+
+wayland_server = dependency('wayland-server', version: '>=1.16')
+wayland_client = dependency('wayland-client')
+wayland_egl = dependency('wayland-egl')
+wayland_protos = dependency('wayland-protocols', version: '>=1.15')
+egl = dependency('egl')
+glesv2 = dependency('glesv2')
+drm = dependency('libdrm')
+gbm = dependency('gbm', version: '>=17.1.0')
+libinput = dependency('libinput', version: '>=1.7.0')
+xkbcommon = dependency('xkbcommon')
+udev = dependency('libudev')
+pixman = dependency('pixman-1')
+libcap = dependency('libcap', required: get_option('libcap'))
+logind = dependency('lib' + get_option('logind-provider'), required: get_option('logind'), version: '>=237')
+math = cc.find_library('m')
+rt = cc.find_library('rt')
+
+wlr_parts = []
+wlr_deps = []
+
+if libcap.found()
+ conf_data.set10('WLR_HAS_LIBCAP', true)
+ wlr_deps += libcap
+endif
+
+if logind.found()
+ conf_data.set10('WLR_HAS_' + get_option('logind-provider').to_upper(), true)
+ wlr_deps += logind
+endif
+
+subdir('protocol')
+subdir('render')
+
+subdir('backend')
+subdir('types')
+subdir('util')
+subdir('xcursor')
+subdir('xwayland')
+
+subdir('include')
+
+wlr_parts += [
+ lib_wl_protos,
+ lib_wlr_backend,
+ lib_wlr_render,
+ lib_wlr_types,
+ lib_wlr_util,
+ lib_wlr_xcursor,
+]
+
+wlr_deps += [
+ wayland_server,
+ wayland_client,
+ wayland_egl,
+ wayland_protos,
+ egl,
+ glesv2,
+ drm,
+ gbm,
+ libinput,
+ xkbcommon,
+ udev,
+ pixman,
+ math,
+]
+
+if host_machine.system() == 'freebsd'
+ override_options = ['b_lundef=false']
+else
+ override_options = []
+endif
+
+symbols_file = 'wlroots.syms'
+symbols_flag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), symbols_file)
+lib_wlr = library(
+ meson.project_name(),
+ version: '.'.join(so_version),
+ link_whole: wlr_parts,
+ dependencies: wlr_deps,
+ include_directories: wlr_inc,
+ install: true,
+ link_args : symbols_flag,
+ link_depends: symbols_file,
+ override_options: override_options,
+)
+
+wlroots = declare_dependency(
+ link_with: lib_wlr,
+ dependencies: wlr_deps,
+ include_directories: wlr_inc,
+)
+
+summary = [
+ '',
+ '----------------',
+ 'wlroots @0@'.format(meson.project_version()),
+ '',
+ ' libcap: @0@'.format(conf_data.get('WLR_HAS_LIBCAP', false)),
+ ' systemd: @0@'.format(conf_data.get('WLR_HAS_SYSTEMD', false)),
+ ' elogind: @0@'.format(conf_data.get('WLR_HAS_ELOGIND', false)),
+ ' xwayland: @0@'.format(conf_data.get('WLR_HAS_XWAYLAND', false)),
+ ' x11_backend: @0@'.format(conf_data.get('WLR_HAS_X11_BACKEND', false)),
+ ' xcb-icccm: @0@'.format(conf_data.get('WLR_HAS_XCB_ICCCM', false)),
+ ' xcb-errors: @0@'.format(conf_data.get('WLR_HAS_XCB_ERRORS', false)),
+ '----------------',
+ ''
+]
+message('\n'.join(summary))
+
+subdir('examples')
+subdir('rootston')
+
+pkgconfig = import('pkgconfig')
+pkgconfig.generate(
+ libraries: lib_wlr,
+ version: meson.project_version(),
+ filebase: meson.project_name(),
+ name: meson.project_name(),
+ description: 'Wayland compositor library',
+)
+
+git = find_program('git', required: false)
+if git.found()
+ all_files = run_command(
+ git,
+ '--git-dir=@0@/.git'.format(meson.current_source_dir()),
+ 'ls-files',
+ ':/*.[ch]',
+ )
+ all_files = files(all_files.stdout().split())
+
+ etags = find_program('etags', required: false)
+ if etags.found() and all_files.length() > 0
+ custom_target(
+ 'etags',
+ build_by_default: true,
+ input: all_files,
+ output: 'TAGS',
+ command: [etags, '-o', '@OUTPUT@', '@INPUT@'],
+ )
+ endif
+
+ ctags = find_program('ctags', required: false)
+ if ctags.found() and all_files.length() > 0
+ custom_target(
+ 'ctags',
+ build_by_default: true,
+ input: all_files,
+ output: 'tags',
+ command: [ctags, '-f', '@OUTPUT@', '@INPUT@'],
+ )
+ endif
+endif
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644
index 00000000..19a6cad7
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1,9 @@
+option('libcap', type: 'feature', value: 'auto', description: 'Enable support for rootless session via capabilities (cap_sys_admin)')
+option('logind', type: 'feature', value: 'auto', description: 'Enable support for rootless session via logind')
+option('logind-provider', type: 'combo', choices: ['systemd', 'elogind'], value: 'systemd', description: 'Provider of logind support library')
+option('xcb-errors', type: 'feature', value: 'auto', description: 'Use xcb-errors util library')
+option('xcb-icccm', type: 'feature', value: 'auto', description: 'Use xcb-icccm util library')
+option('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications')
+option('x11-backend', type: 'feature', value: 'auto', description: 'Enable X11 backend')
+option('rootston', type: 'boolean', value: true, description: 'Build the rootston example compositor')
+option('examples', type: 'boolean', value: true, description: 'Build example applications')
diff --git a/protocol/gamma-control.xml b/protocol/gamma-control.xml
new file mode 100644
index 00000000..e6e33265
--- /dev/null
+++ b/protocol/gamma-control.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="gamma_control">
+
+ <copyright>
+ Copyright © 2015 Giulio camuffo
+
+ 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.
+ </copyright>
+
+ <interface name="gamma_control_manager" version="1">
+ <request name="destroy" type="destructor"/>
+
+ <request name="get_gamma_control">
+ <arg name="id" type="new_id" interface="gamma_control"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ </request>
+ </interface>
+
+ <interface name="gamma_control" version="1">
+ <enum name="error">
+ <entry name="invalid_gamma" value="0"/>
+ </enum>
+
+ <request name="destroy" type="destructor"/>
+
+ <request name="set_gamma">
+ <arg name="red" type="array"/>
+ <arg name="green" type="array"/>
+ <arg name="blue" type="array"/>
+ </request>
+
+ <request name="reset_gamma"/>
+
+ <event name="gamma_size">
+ <arg name="size" type="uint"/>
+ </event>
+ </interface>
+</protocol>
diff --git a/protocol/gtk-primary-selection.xml b/protocol/gtk-primary-selection.xml
new file mode 100644
index 00000000..02cab94f
--- /dev/null
+++ b/protocol/gtk-primary-selection.xml
@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="gtk_primary_selection">
+ <copyright>
+ Copyright © 2015, 2016 Red Hat
+
+ 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.
+ </copyright>
+
+ <description summary="Primary selection protocol">
+ This protocol provides the ability to have a primary selection device to
+ match that of the X server. This primary selection is a shortcut to the
+ common clipboard selection, where text just needs to be selected in order
+ to allow copying it elsewhere. The de facto way to perform this action
+ is the middle mouse button, although it is not limited to this one.
+
+ Clients wishing to honor primary selection should create a primary
+ selection source and set it as the selection through
+ wp_primary_selection_device.set_selection whenever the text selection
+ changes. In order to minimize calls in pointer-driven text selection,
+ it should happen only once after the operation finished. Similarly,
+ a NULL source should be set when text is unselected.
+
+ wp_primary_selection_offer objects are first announced through the
+ wp_primary_selection_device.data_offer event. Immediately after this event,
+ the primary data offer will emit wp_primary_selection_offer.offer events
+ to let know of the mime types being offered.
+
+ When the primary selection changes, the client with the keyboard focus
+ will receive wp_primary_selection_device.selection events. Only the client
+ with the keyboard focus will receive such events with a non-NULL
+ wp_primary_selection_offer. Across keyboard focus changes, previously
+ focused clients will receive wp_primary_selection_device.events with a
+ NULL wp_primary_selection_offer.
+
+ In order to request the primary selection data, the client must pass
+ a recent serial pertaining to the press event that is triggering the
+ operation, if the compositor deems the serial valid and recent, the
+ wp_primary_selection_source.send event will happen in the other end
+ to let the transfer begin. The client owning the primary selection
+ should write the requested data, and close the file descriptor
+ immediately.
+
+ If the primary selection owner client disappeared during the transfer,
+ the client reading the data will receive a
+ wp_primary_selection_device.selection event with a NULL
+ wp_primary_selection_offer, the client should take this as a hint
+ to finish the reads related to the no longer existing offer.
+
+ The primary selection owner should be checking for errors during
+ writes, merely cancelling the ongoing transfer if any happened.
+ </description>
+
+ <interface name="gtk_primary_selection_device_manager" version="1">
+ <description summary="X primary selection emulation">
+ The primary selection device manager is a singleton global object that
+ provides access to the primary selection. It allows to create
+ wp_primary_selection_source objects, as well as retrieving the per-seat
+ wp_primary_selection_device objects.
+ </description>
+
+ <request name="create_source">
+ <description summary="create a new primary selection source">
+ Create a new primary selection source.
+ </description>
+ <arg name="id" type="new_id" interface="gtk_primary_selection_source"/>
+ </request>
+
+ <request name="get_device">
+ <description summary="create a new primary selection device">
+ Create a new data device for a given seat.
+ </description>
+ <arg name="id" type="new_id" interface="gtk_primary_selection_device"/>
+ <arg name="seat" type="object" interface="wl_seat"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the primary selection device manager">
+ Destroy the primary selection device manager.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="gtk_primary_selection_device" version="1">
+ <request name="set_selection">
+ <description summary="set the primary selection">
+ Replaces the current selection. The previous owner of the primary selection
+ will receive a wp_primary_selection_source.cancelled event.
+
+ To unset the selection, set the source to NULL.
+ </description>
+ <arg name="source" type="object" interface="gtk_primary_selection_source" allow-null="true"/>
+ <arg name="serial" type="uint" summary="serial of the event that triggered this request"/>
+ </request>
+
+ <event name="data_offer">
+ <description summary="introduce a new wp_primary_selection_offer">
+ Introduces a new wp_primary_selection_offer object that may be used
+ to receive the current primary selection. Immediately following this
+ event, the new wp_primary_selection_offer object will send
+ wp_primary_selection_offer.offer events to describe the offered mime
+ types.
+ </description>
+ <arg name="offer" type="new_id" interface="gtk_primary_selection_offer"/>
+ </event>
+
+ <event name="selection">
+ <description summary="advertise a new primary selection">
+ The wp_primary_selection_device.selection event is sent to notify the
+ client of a new primary selection. This event is sent after the
+ wp_primary_selection.data_offer event introducing this object, and after
+ the offer has announced its mimetypes through
+ wp_primary_selection_offer.offer.
+
+ The data_offer is valid until a new offer or NULL is received
+ or until the client loses keyboard focus. The client must destroy the
+ previous selection data_offer, if any, upon receiving this event.
+ </description>
+ <arg name="id" type="object" interface="gtk_primary_selection_offer" allow-null="true"/>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the primary selection device">
+ Destroy the primary selection device.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="gtk_primary_selection_offer" version="1">
+ <description summary="offer to transfer primary selection contents">
+ A wp_primary_selection_offer represents an offer to transfer the contents
+ of the primary selection clipboard to the client. Similar to
+ wl_data_offer, the offer also describes the mime types that the source
+ will transferthat the
+ data can be converted to and provides the mechanisms for transferring the
+ data directly to the client.
+ </description>
+
+ <request name="receive">
+ <description summary="request that the data is transferred">
+ To transfer the contents of the primary selection clipboard, the client
+ issues this request and indicates the mime type that 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
+ closes its end, at which point the transfer is complete.
+ </description>
+ <arg name="mime_type" type="string"/>
+ <arg name="fd" type="fd"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the primary selection offer">
+ Destroy the primary selection offer.
+ </description>
+ </request>
+
+ <event name="offer">
+ <description summary="advertise offered mime type">
+ Sent immediately after creating announcing the wp_primary_selection_offer
+ through wp_primary_selection_device.data_offer. One event is sent per
+ offered mime type.
+ </description>
+ <arg name="mime_type" type="string"/>
+ </event>
+ </interface>
+
+ <interface name="gtk_primary_selection_source" version="1">
+ <description summary="offer to replace the contents of the primary selection">
+ The source side of a wp_primary_selection_offer, it provides a way to
+ describe the offered data and respond to requests to transfer the
+ requested contents of the primary selection clipboard.
+ </description>
+
+ <request name="offer">
+ <description summary="add an offered mime type">
+ This request adds a mime type to the set of mime types advertised to
+ targets. Can be called several times to offer multiple types.
+ </description>
+ <arg name="mime_type" type="string"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the primary selection source">
+ Destroy the primary selection source.
+ </description>
+ </request>
+
+ <event name="send">
+ <description summary="send the primary selection contents">
+ Request for the current primary selection contents from the client.
+ Send the specified mime type over the passed file descriptor, then
+ close it.
+ </description>
+ <arg name="mime_type" type="string"/>
+ <arg name="fd" type="fd"/>
+ </event>
+
+ <event name="cancelled">
+ <description summary="request for primary selection contents was canceled">
+ This primary selection source is no longer valid. The client should
+ clean up and destroy this primary selection source.
+ </description>
+ </event>
+ </interface>
+</protocol>
diff --git a/protocol/idle.xml b/protocol/idle.xml
new file mode 100644
index 00000000..92d9989c
--- /dev/null
+++ b/protocol/idle.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="idle">
+ <copyright><![CDATA[
+ Copyright (C) 2015 Martin Gräßlin
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ ]]></copyright>
+ <interface name="org_kde_kwin_idle" version="1">
+ <description summary="User idle time manager">
+ This interface allows to monitor user idle time on a given seat. The interface
+ allows to register timers which trigger after no user activity was registered
+ on the seat for a given interval. It notifies when user activity resumes.
+
+ This is useful for applications wanting to perform actions when the user is not
+ interacting with the system, e.g. chat applications setting the user as away, power
+ management features to dim screen, etc..
+ </description>
+ <request name="get_idle_timeout">
+ <arg name="id" type="new_id" interface="org_kde_kwin_idle_timeout"/>
+ <arg name="seat" type="object" interface="wl_seat"/>
+ <arg name="timeout" type="uint" summary="The idle timeout in msec"/>
+ </request>
+ </interface>
+ <interface name="org_kde_kwin_idle_timeout" version="1">
+ <request name="release" type="destructor">
+ <description summary="release the timeout object"/>
+ </request>
+ <request name="simulate_user_activity">
+ <description summary="Simulates user activity for this timeout, behaves just like real user activity on the seat"/>
+ </request>
+ <event name="idle">
+ <description summary="Triggered when there has not been any user activity in the requested idle time interval"/>
+ </event>
+ <event name="resumed">
+ <description summary="Triggered on the first user activity after an idle event"/>
+ </event>
+ </interface>
+</protocol>
diff --git a/protocol/input-method-unstable-v2.xml b/protocol/input-method-unstable-v2.xml
new file mode 100644
index 00000000..62be9d94
--- /dev/null
+++ b/protocol/input-method-unstable-v2.xml
@@ -0,0 +1,490 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="input_method_unstable_v2">
+
+ <copyright>
+ 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.
+ </copyright>
+
+ <description summary="Protocol for creating input methods">
+ 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.
+ </description>
+
+ <interface name="zwp_input_method_v2" version="1">
+ <description summary="input method">
+ 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.
+ </description>
+
+ <event name="activate">
+ <description summary="input method has been requested">
+ 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.
+ </description>
+ </event>
+
+ <event name="deactivate">
+ <description summary="deactivate event">
+ 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.
+ </description>
+ </event>
+
+ <event name="surrounding_text">
+ <description summary="surrounding text event">
+ 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.
+ </description>
+ <arg name="text" type="string"/>
+ <arg name="cursor" type="uint"/>
+ <arg name="anchor" type="uint"/>
+ </event>
+
+ <event name="text_change_cause">
+ <description summary="indicates the cause of surrounding text change">
+ 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.
+ </description>
+ <arg name="cause" type="uint" enum="zwp_text_input_v3.change_cause"/>
+ </event>
+
+ <event name="content_type">
+ <description summary="content purpose and hint">
+ 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.
+ </description>
+ <arg name="hint" type="uint" enum="zwp_text_input_v3.content_hint"/>
+ <arg name="purpose" type="uint" enum="zwp_text_input_v3.content_purpose"/>
+ </event>
+
+ <event name="done">
+ <description summary="apply state">
+ 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.
+ </description>
+ </event>
+
+ <request name="commit_string">
+ <description summary="commit string">
+ 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.
+ </description>
+ <arg name="text" type="string"/>
+ </request>
+
+ <request name="set_preedit_string">
+ <description summary="pre-edit 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.
+ </description>
+ <arg name="text" type="string"/>
+ <arg name="cursor_begin" type="int"/>
+ <arg name="cursor_end" type="int"/>
+ </request>
+
+ <request name="delete_surrounding_text">
+ <description summary="delete text">
+ 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.
+ </description>
+ <arg name="before_length" type="uint"/>
+ <arg name="after_length" type="uint"/>
+ </request>
+
+ <request name="commit">
+ <description summary="apply state">
+ 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.
+ </description>
+ <arg name="serial" type="uint"/>
+ </request>
+
+ <request name="get_input_popup_surface">
+ <description summary="create popup surface">
+ 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.
+ </description>
+ <arg name="id" type="new_id" interface="zwp_input_popup_surface_v2"/>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ </request>
+
+ <request name="grab_keyboard">
+ <description summary="grab hardware keyboard">
+ 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.
+ </description>
+ <arg name="keyboard" type="new_id"
+ interface="zwp_input_method_keyboard_grab_v2"/>
+ </request>
+
+ <event name="unavailable">
+ <description summary="input method unavailable">
+ 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
+ useable, 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.
+ </description>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the text input">
+ 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.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwp_input_popup_surface_v2" version="1">
+ <description summary="popup surface">
+ 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.
+ </description>
+
+ <event name="text_input_rectangle">
+ <description summary="set text input area position">
+ 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.
+ </description>
+ <arg name="x" type="int"/>
+ <arg name="y" type="int"/>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </event>
+
+ <request name="destroy" type="destructor"/>
+ </interface>
+
+ <interface name="zwp_input_method_keyboard_grab_v2" version="1">
+ <!-- Closely follows wl_keyboard version 6 -->
+ <description summary="keyboard grab">
+ The zwp_input_method_keyboard_grab_v2 interface represents an exclusive
+ grab of the wl_keyboard interface associated with the seat.
+ </description>
+
+ <event name="keymap">
+ <description summary="keyboard mapping">
+ This event provides a file descriptor to the client which can be
+ memory-mapped to provide a keyboard mapping description.
+ </description>
+ <arg name="format" type="uint" enum="wl_keyboard.keymap_format"
+ summary="keymap format"/>
+ <arg name="fd" type="fd" summary="keymap file descriptor"/>
+ <arg name="size" type="uint" summary="keymap size, in bytes"/>
+ </event>
+
+ <event name="key">
+ <description summary="key event">
+ A key was pressed or released.
+ The time argument is a timestamp with millisecond granularity, with an
+ undefined base.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the key event"/>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="key" type="uint" summary="key that produced the event"/>
+ <arg name="state" type="uint" enum="wl_keyboard.key_state"
+ summary="physical state of the key"/>
+ </event>
+
+ <event name="modifiers">
+ <description summary="modifier and group state">
+ Notifies clients that the modifier and/or group state has changed, and
+ it should update its local state.
+ </description>
+ <arg name="serial" type="uint" summary="serial number of the modifiers event"/>
+ <arg name="mods_depressed" type="uint" summary="depressed modifiers"/>
+ <arg name="mods_latched" type="uint" summary="latched modifiers"/>
+ <arg name="mods_locked" type="uint" summary="locked modifiers"/>
+ <arg name="group" type="uint" summary="keyboard layout"/>
+ </event>
+
+ <request name="release" type="destructor">
+ <description summary="release the grab object"/>
+ </request>
+
+ <event name="repeat_info">
+ <description summary="repeat rate and delay">
+ 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.
+ </description>
+ <arg name="rate" type="int"
+ summary="the rate of repeating keys in characters per second"/>
+ <arg name="delay" type="int"
+ summary="delay in milliseconds since key down until repeating starts"/>
+ </event>
+ </interface>
+
+ <interface name="zwp_input_method_manager_v2" version="1">
+ <description summary="input method manager">
+ 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.
+ </description>
+
+ <request name="get_input_method">
+ <description summary="request an input method object">
+ Request a new input zwp_input_method_v2 object associated with a given
+ seat.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat"/>
+ <arg name="input_method" type="new_id" interface="zwp_input_method_v2"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the input method manager">
+ Destroys the zwp_input_method_manager_v2 object.
+
+ The zwp_input_method_v2 objects originating from it remain valid.
+ </description>
+ </request>
+ </interface>
+</protocol>
diff --git a/protocol/meson.build b/protocol/meson.build
new file mode 100644
index 00000000..58f57046
--- /dev/null
+++ b/protocol/meson.build
@@ -0,0 +1,95 @@
+wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir')
+
+wayland_scanner_dep = dependency('wayland-scanner', required: false, native: true)
+if wayland_scanner_dep.found()
+ wayland_scanner = find_program(
+ wayland_scanner_dep.get_pkgconfig_variable('wayland_scanner'),
+ native: true,
+ )
+else
+ wayland_scanner = find_program('wayland-scanner', native: true)
+endif
+
+protocols = [
+ [wl_protocol_dir, 'stable/presentation-time/presentation-time.xml'],
+ [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
+ [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
+ [wl_protocol_dir, 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml'],
+ [wl_protocol_dir, 'unstable/tablet/tablet-unstable-v2.xml'],
+ [wl_protocol_dir, 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'],
+ [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
+ [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'],
+ [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'],
+ 'gamma-control.xml',
+ 'gtk-primary-selection.xml',
+ 'idle.xml',
+ 'input-method-unstable-v2.xml',
+ 'screenshooter.xml',
+ 'server-decoration.xml',
+ 'text-input-unstable-v3.xml',
+ 'virtual-keyboard-unstable-v1.xml',
+ 'wlr-export-dmabuf-unstable-v1.xml',
+ 'wlr-gamma-control-unstable-v1.xml',
+ 'wlr-foreign-toplevel-management-unstable-v1.xml',
+ 'wlr-input-inhibitor-unstable-v1.xml',
+ 'wlr-layer-shell-unstable-v1.xml',
+ 'wlr-screencopy-unstable-v1.xml',
+]
+
+client_protocols = [
+ [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
+ [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
+ [wl_protocol_dir, 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'],
+ [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'],
+ [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'],
+ 'idle.xml',
+ 'input-method-unstable-v2.xml',
+ 'screenshooter.xml',
+ 'text-input-unstable-v3.xml',
+ 'wlr-export-dmabuf-unstable-v1.xml',
+ 'wlr-gamma-control-unstable-v1.xml',
+ 'wlr-foreign-toplevel-management-unstable-v1.xml',
+ 'wlr-input-inhibitor-unstable-v1.xml',
+ 'wlr-layer-shell-unstable-v1.xml',
+ 'wlr-screencopy-unstable-v1.xml',
+]
+
+wl_protos_src = []
+wl_protos_headers = []
+
+foreach p : protocols
+ xml = join_paths(p)
+ wl_protos_src += custom_target(
+ xml.underscorify() + '_server_c',
+ input: xml,
+ output: '@BASENAME@-protocol.c',
+ command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'],
+ )
+ wl_protos_headers += custom_target(
+ xml.underscorify() + '_server_h',
+ input: xml,
+ output: '@BASENAME@-protocol.h',
+ command: [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'],
+ )
+endforeach
+
+foreach p : client_protocols
+ xml = join_paths(p)
+ wl_protos_headers += custom_target(
+ xml.underscorify() + '_client_h',
+ input: xml,
+ output: '@BASENAME@-client-protocol.h',
+ command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'],
+ )
+endforeach
+
+lib_wl_protos = static_library(
+ 'wl_protos',
+ wl_protos_src + wl_protos_headers,
+ dependencies: wayland_client.partial_dependency(compile_args: true),
+)
+
+wlr_protos = declare_dependency(
+ link_with: lib_wl_protos,
+ sources: wl_protos_headers,
+)
diff --git a/protocol/screenshooter.xml b/protocol/screenshooter.xml
new file mode 100644
index 00000000..fc48eac9
--- /dev/null
+++ b/protocol/screenshooter.xml
@@ -0,0 +1,16 @@
+<protocol name="orbital_screenshooter">
+
+ <interface name="orbital_screenshooter" version="1">
+ <request name="shoot">
+ <arg name="id" type="new_id" interface="orbital_screenshot"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ <arg name="buffer" type="object" interface="wl_buffer"/>
+ </request>
+ </interface>
+
+ <interface name="orbital_screenshot" version="1">
+ <event name="done">
+ </event>
+ </interface>
+
+</protocol>
diff --git a/protocol/server-decoration.xml b/protocol/server-decoration.xml
new file mode 100644
index 00000000..45f11284
--- /dev/null
+++ b/protocol/server-decoration.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="server_decoration">
+ <copyright><![CDATA[
+ Copyright (C) 2015 Martin Gräßlin
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ ]]></copyright>
+ <interface name="org_kde_kwin_server_decoration_manager" version="1">
+ <description summary="Server side window decoration manager">
+ 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.
+ </description>
+ <request name="create">
+ <description summary="Create a server-side decoration object for a given surface">
+ 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.
+ </description>
+ <arg name="id" type="new_id" interface="org_kde_kwin_server_decoration"/>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ </request>
+ <enum name="mode">
+ <description summary="Possible values to use in request_mode and the event mode."/>
+ <entry name="None" value="0" summary="Undecorated: The surface is not decorated at all, neither server nor client-side. An example is a popup surface which should not be decorated."/>
+ <entry name="Client" value="1" summary="Client-side decoration: The decoration is part of the surface and the client."/>
+ <entry name="Server" value="2" summary="Server-side decoration: The server embeds the surface into a decoration frame."/>
+ </enum>
+ <event name="default_mode">
+ <description summary="The default mode used on the server">
+ 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.
+ </description>
+ <arg name="mode" type="uint" summary="The default decoration mode applied to newly created server decorations."/>
+ </event>
+ </interface>
+ <interface name="org_kde_kwin_server_decoration" version="1">
+ <request name="release" type="destructor">
+ <description summary="release the server decoration object"/>
+ </request>
+ <enum name="mode">
+ <description summary="Possible values to use in request_mode and the event mode."/>
+ <entry name="None" value="0" summary="Undecorated: The surface is not decorated at all, neither server nor client-side. An example is a popup surface which should not be decorated."/>
+ <entry name="Client" value="1" summary="Client-side decoration: The decoration is part of the surface and the client."/>
+ <entry name="Server" value="2" summary="Server-side decoration: The server embeds the surface into a decoration frame."/>
+ </enum>
+ <request name="request_mode">
+ <description summary="The decoration mode the surface wants to use."/>
+ <arg name="mode" type="uint" summary="The mode this surface wants to use."/>
+ </request>
+ <event name="mode">
+ <description summary="The new decoration mode applied by the server">
+ 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.
+ </description>
+ <arg name="mode" type="uint" summary="The decoration mode applied to the surface by the server."/>
+ </event>
+ </interface>
+</protocol>
diff --git a/protocol/text-input-unstable-v3.xml b/protocol/text-input-unstable-v3.xml
new file mode 100644
index 00000000..8b710fd6
--- /dev/null
+++ b/protocol/text-input-unstable-v3.xml
@@ -0,0 +1,441 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<protocol name="text_input_unstable_v3">
+ <copyright>
+ Copyright © 2012, 2013 Intel Corporation
+ Copyright © 2015, 2016 Jan Arne Petersen
+ Copyright © 2017, 2018 Red Hat, Inc.
+ Copyright © 2018 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.
+ </copyright>
+
+ <description summary="Protocol for composing text">
+ This protocol allows compositors to act as input methods and to send text
+ to applications. A text input object is used to manage state of what are
+ typically text entry fields in the application.
+
+ 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.
+ </description>
+
+ <interface name="zwp_text_input_v3" version="1">
+ <description summary="text input">
+ The zwp_text_input_v3 interface represents text input and input methods
+ associated with a seat. It provides enter/leave events to follow the
+ text input focus for a seat.
+
+ Requests are used to enable/disable the text-input object and set
+ state information like surrounding and selected text or the content type.
+ The information about the entered text is sent to the text-input object
+ via the preedit_string and commit_string events.
+
+ Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices
+ must not point to middle bytes inside a code point: they must either
+ point to the first byte of a code point or to the end of the buffer.
+ Lengths must be measured between two valid indices.
+
+ Focus moving throughout surfaces will result in the emission of
+ zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused
+ surface must commit zwp_text_input_v3.enable and
+ zwp_text_input_v3.disable requests as the keyboard focus moves across
+ editable and non-editable elements of the UI. Those two requests are not
+ expected to be paired with each other, the compositor must be able to
+ handle consecutive series of the same request.
+
+ State is sent by the state requests (set_surrounding_text,
+ set_content_type and set_cursor_rectangle) and a commit request. After an
+ enter event or disable request all state information is invalidated and
+ needs to be resent by the client.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="Destroy the wp_text_input">
+ Destroy the wp_text_input object. Also disables all surfaces enabled
+ through this wp_text_input object.
+ </description>
+ </request>
+
+ <request name="enable">
+ <description summary="Request text input to be enabled">
+ Requests text input on the surface previously obtained from the enter
+ event.
+
+ This request must be issued every time the active text input changes
+ to a new one, including within the current surface. Use
+ zwp_text_input_v3.disable when there is no longer any input focus on
+ the current surface.
+
+ This request resets all state associated with previous enable, disable,
+ set_surrounding_text, set_text_change_cause, set_content_type, and
+ set_cursor_rectangle requests, as well as the state associated with
+ preedit_string, commit_string, and delete_surrounding_text events.
+
+ The set_surrounding_text, set_content_type and set_cursor_rectangle
+ requests must follow if the text input supports the necessary
+ functionality.
+
+ State set with this request is double-buffered. It will get applied on
+ the next zwp_text_input_v3.commit request, and stay valid until the
+ next committed enable or disable request.
+
+ The changes must be applied by the compositor after issuing a
+ zwp_text_input_v3.commit request.
+ </description>
+ </request>
+
+ <request name="disable">
+ <description summary="Disable text input on a surface">
+ Explicitly disable text input on the current surface (typically when
+ there is no focus on any text entry inside the surface).
+
+ State set with this request is double-buffered. It will get applied on
+ the next zwp_text_input_v3.commit request.
+ </description>
+ </request>
+
+ <request name="set_surrounding_text">
+ <description summary="sets the surrounding text">
+ Sets the surrounding plain text around the input, excluding the preedit
+ text.
+
+ The client should notify the compositor of any changes in any of the
+ values carried with this request, including changes caused by handling
+ incoming text-input events as well as changes caused by other
+ mechanisms like keyboard typing.
+
+ If the client is unaware of the text around the cursor, it should not
+ issue this request, to signify lack of support to the compositor.
+
+ Text is UTF-8 encoded, and should include the cursor position, the
+ complete selection and additional characters before and after them.
+ 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 text buffer.
+
+ Anchor is the byte offset of the selection anchor within text buffer.
+ If there is no selected text, anchor is the same as cursor.
+
+ If any preedit text is present, it is replaced with a cursor for the
+ purpose of this event.
+
+ Values set with this request are double-buffered. They will get applied
+ on the next zwp_text_input_v3.commit request, and stay valid until the
+ next committed enable or disable request.
+
+ 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.
+ </description>
+ <arg name="text" type="string"/>
+ <arg name="cursor" type="int"/>
+ <arg name="anchor" type="int"/>
+ </request>
+
+ <enum name="change_cause">
+ <description summary="text change reason">
+ Reason for the change of surrounding text or cursor posision.
+ </description>
+ <entry name="input_method" value="0" summary="input method caused the change"/>
+ <entry name="other" value="1" summary="something else than the input method caused the change"/>
+ </enum>
+
+ <request name="set_text_change_cause">
+ <description summary="indicates the cause of surrounding text change">
+ Tells the compositor why the text surrounding the cursor changed.
+
+ Whenever the client detects an external change in text, cursor, or
+ anchor posision, 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 request is double-buffered. It must be applied
+ and reset to initial at the next zwp_text_input_v3.commit request.
+
+ The initial value of cause is input_method.
+ </description>
+ <arg name="cause" type="uint" enum="change_cause"/>
+ </request>
+
+ <enum name="content_hint" bitfield="true">
+ <description summary="content hint">
+ Content hint is a bitmask to allow to modify the behavior of the text
+ input.
+ </description>
+ <entry name="none" value="0x0" summary="no special behavior"/>
+ <entry name="completion" value="0x1" summary="suggest word completions"/>
+ <entry name="spellcheck" value="0x2" summary="suggest word corrections"/>
+ <entry name="auto_capitalization" value="0x4" summary="switch to uppercase letters at the start of a sentence"/>
+ <entry name="lowercase" value="0x8" summary="prefer lowercase letters"/>
+ <entry name="uppercase" value="0x10" summary="prefer uppercase letters"/>
+ <entry name="titlecase" value="0x20" summary="prefer casing for titles and headings (can be language dependent)"/>
+ <entry name="hidden_text" value="0x40" summary="characters should be hidden"/>
+ <entry name="sensitive_data" value="0x80" summary="typed text should not be stored"/>
+ <entry name="latin" value="0x100" summary="just Latin characters should be entered"/>
+ <entry name="multiline" value="0x200" summary="the text input is multiline"/>
+ </enum>
+
+ <enum name="content_purpose">
+ <description summary="content purpose">
+ The content purpose allows to specify the primary purpose of a text
+ input.
+
+ This allows an input method to show special purpose input panels with
+ extra characters or to disallow some characters.
+ </description>
+ <entry name="normal" value="0" summary="default input, allowing all characters"/>
+ <entry name="alpha" value="1" summary="allow only alphabetic characters"/>
+ <entry name="digits" value="2" summary="allow only digits"/>
+ <entry name="number" value="3" summary="input a number (including decimal separator and sign)"/>
+ <entry name="phone" value="4" summary="input a phone number"/>
+ <entry name="url" value="5" summary="input an URL"/>
+ <entry name="email" value="6" summary="input an email address"/>
+ <entry name="name" value="7" summary="input a name of a person"/>
+ <entry name="password" value="8" summary="input a password (combine with sensitive_data hint)"/>
+ <entry name="pin" value="9" summary="input is a numeric password (combine with sensitive_data hint)"/>
+ <entry name="date" value="10" summary="input a date"/>
+ <entry name="time" value="11" summary="input a time"/>
+ <entry name="datetime" value="12" summary="input a date and time"/>
+ <entry name="terminal" value="13" summary="input for a terminal"/>
+ </enum>
+
+ <request name="set_content_type">
+ <description summary="set content purpose and hint">
+ Sets the content purpose and content hint. While the purpose is the
+ basic purpose of an input field, the hint flags allow to modify some of
+ the behavior.
+
+ Values set with this request are double-buffered. They will get applied
+ on the next zwp_text_input_v3.commit request.
+ Subsequent attempts to update them may have no effect. The values
+ remain valid until the next committed enable or disable request.
+
+ The initial value for hint is none, and the initial value for purpose
+ is normal.
+ </description>
+ <arg name="hint" type="uint" enum="content_hint"/>
+ <arg name="purpose" type="uint" enum="content_purpose"/>
+ </request>
+
+ <request name="set_cursor_rectangle">
+ <description summary="set cursor position">
+ Marks an area around the cursor as a x, y, width, height rectangle in
+ surface local coordinates.
+
+ Allows the compositor to put a window with word suggestions near the
+ cursor, without obstructing the text being input.
+
+ If the client is unaware of the position of edited text, it should not
+ issue this request, to signify lack of support to the compositor.
+
+ Values set with this request are double-buffered. They will get applied
+ on the next zwp_text_input_v3.commit request, and stay valid until the
+ next committed enable or disable request.
+
+ The initial values describing a cursor rectangle are empty. That means
+ the text input does not support describing the cursor area. If the
+ empty values get applied, subsequent attempts to change them may have
+ no effect.
+ </description>
+ <arg name="x" type="int"/>
+ <arg name="y" type="int"/>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="commit">
+ <description summary="commit state">
+ Atomically applies state changes recently sent to the compositor.
+
+ The commit request establishes and updates the state of the client, and
+ must be issued after any changes to apply them.
+
+ Text input state (enabled status, content purpose, content hint,
+ surrounding text and change cause, cursor rectangle) is conceptually
+ double-buffered within the context of a text input, i.e. between a
+ committed enable request and the following committed enable or disable
+ request.
+
+ Protocol requests modify the pending state, as opposed to the current
+ state in use by the input method. A commit request atomically applies
+ all pending state, replacing the current state. After commit, the new
+ pending state is as documented for each related request.
+
+ Requests are applied in the order of arrival.
+
+ Neither current nor pending state are modified unless noted otherwise.
+
+ The compositor must count the number of commit requests coming from
+ each zwp_text_input_v3 object and use the count as the serial in done
+ events.
+ </description>
+ </request>
+
+ <event name="enter">
+ <description summary="enter event">
+ Notification that this seat's text-input focus is on a certain surface.
+
+ When the seat has the keyboard capability the text-input focus follows
+ the keyboard focus. This event sets the current surface for the
+ text-input object.
+ </description>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ </event>
+
+ <event name="leave">
+ <description summary="leave event">
+ Notification that this seat's text-input focus is no longer on a
+ certain surface. The client should reset any preedit string previously
+ set.
+
+ The leave notification clears the current surface. It is sent before
+ the enter notification for the new focus.
+
+ When the seat has the keyboard capability the text-input focus follows
+ the keyboard focus.
+ </description>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ </event>
+
+ <event name="preedit_string">
+ <description summary="pre-edit">
+ Notify when a new composing text (pre-edit) should be set at the
+ current cursor position. Any previously set composing text must be
+ removed. Any previously existing selected text must be removed.
+
+ The argument text contains the pre-edit string buffer.
+
+ The parameters cursor_begin and cursor_end are counted in bytes
+ relative to the beginning of the submitted text buffer. Cursor should
+ be hidden when both are equal to -1.
+
+ They could be represented by the client as a line if both values are
+ the same, or as a text highlight otherwise.
+
+ Values set with this event are double-buffered. They must be applied
+ and reset to initial on the next zwp_text_input_v3.done event.
+
+ The initial value of text is an empty string, and cursor_begin,
+ cursor_end and cursor_hidden are all 0.
+ </description>
+ <arg name="text" type="string" allow-null="true"/>
+ <arg name="cursor_begin" type="int"/>
+ <arg name="cursor_end" type="int"/>
+ </event>
+
+ <event name="commit_string">
+ <description summary="text commit">
+ Notify when text should be inserted into the editor widget. The text to
+ commit could be either just a single character after a key press or the
+ result of some composing (pre-edit).
+
+ Values set with this event are double-buffered. They must be applied
+ and reset to initial on the next zwp_text_input_v3.done event.
+
+ The initial value of text is an empty string.
+ </description>
+ <arg name="text" type="string" allow-null="true"/>
+ </event>
+
+ <event name="delete_surrounding_text">
+ <description summary="delete surrounding text">
+ Notify when the text around the current cursor position should be
+ deleted.
+
+ Before_length and after_length are the number of bytes before and after
+ the current cursor index (excluding the selection) to delete.
+
+ If a preedit text is present, in effect before_length is counted from
+ the beginning of it, and after_length from its end (see done event
+ sequence).
+
+ Values set with this event are double-buffered. They must be applied
+ and reset to initial on the next zwp_text_input_v3.done event.
+
+ The initial values of both before_length and after_length are 0.
+ </description>
+ <arg name="before_length" type="uint" summary="length of text before current cursor position"/>
+ <arg name="after_length" type="uint" summary="length of text after current cursor position"/>
+ </event>
+
+ <event name="done">
+ <description summary="apply changes">
+ Instruct the application to apply changes to state requested by the
+ preedit_string, commit_string and delete_surrounding_text events. The
+ state relating to these events is double-buffered, and each one
+ modifies the pending state. This event replaces the current state with
+ the pending state.
+
+ The application must 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_text_input_v3
+ object known to the compositor. The value of the serial argument must
+ be equal to the number of commit requests already issued on that object.
+ When the client receives a done event with a serial different than the
+ number of past commit requests, it must proceed as normal, except it
+ should not change the current state of the zwp_text_input_v3 object.
+ </description>
+ <arg name="serial" type="uint"/>
+ </event>
+ </interface>
+
+ <interface name="zwp_text_input_manager_v3" version="1">
+ <description summary="text input manager">
+ A factory for text-input objects. This object is a global singleton.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="Destroy the wp_text_input_manager">
+ Destroy the wp_text_input_manager object.
+ </description>
+ </request>
+
+ <request name="get_text_input">
+ <description summary="create a new text input object">
+ Creates a new text-input object for a given seat.
+ </description>
+ <arg name="id" type="new_id" interface="zwp_text_input_v3"/>
+ <arg name="seat" type="object" interface="wl_seat"/>
+ </request>
+ </interface>
+</protocol>
diff --git a/protocol/virtual-keyboard-unstable-v1.xml b/protocol/virtual-keyboard-unstable-v1.xml
new file mode 100644
index 00000000..5095c91b
--- /dev/null
+++ b/protocol/virtual-keyboard-unstable-v1.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="virtual_keyboard_unstable_v1">
+ <copyright>
+ 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.
+ </copyright>
+
+ <interface name="zwp_virtual_keyboard_v1" version="1">
+ <description summary="virtual keyboard">
+ 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.
+ </description>
+
+ <request name="keymap">
+ <description summary="keyboard mapping">
+ 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.
+ </description>
+ <arg name="format" type="uint" summary="keymap format"/>
+ <arg name="fd" type="fd" summary="keymap file descriptor"/>
+ <arg name="size" type="uint" summary="keymap size, in bytes"/>
+ </request>
+
+ <enum name="error">
+ <entry name="no_keymap" value="0" summary="No keymap was set"/>
+ </enum>
+
+ <request name="key">
+ <description summary="key event">
+ 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.
+ </description>
+ <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
+ <arg name="key" type="uint" summary="key that produced the event"/>
+ <arg name="state" type="uint" summary="physical state of the key"/>
+ </request>
+
+ <request name="modifiers">
+ <description summary="modifier and group state">
+ 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.
+ </description>
+ <arg name="mods_depressed" type="uint" summary="depressed modifiers"/>
+ <arg name="mods_latched" type="uint" summary="latched modifiers"/>
+ <arg name="mods_locked" type="uint" summary="locked modifiers"/>
+ <arg name="group" type="uint" summary="keyboard layout"/>
+ </request>
+
+ <request name="destroy" type="destructor" since="1">
+ <description summary="destroy the virtual keyboard keyboard object"/>
+ </request>
+ </interface>
+
+ <interface name="zwp_virtual_keyboard_manager_v1" version="1">
+ <description summary="virtual keyboard manager">
+ A virtual keyboard manager allows an application to provide keyboard
+ input events as if they came from a physical keyboard.
+ </description>
+
+ <enum name="error">
+ <entry name="unauthorized" value="0" summary="client not authorized to use the interface"/>
+ </enum>
+
+ <request name="create_virtual_keyboard">
+ <description summary="Create a new virtual 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.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat"/>
+ <arg name="id" type="new_id" interface="zwp_virtual_keyboard_v1"/>
+ </request>
+ </interface>
+</protocol>
diff --git a/protocol/wlr-export-dmabuf-unstable-v1.xml b/protocol/wlr-export-dmabuf-unstable-v1.xml
new file mode 100644
index 00000000..751f7efb
--- /dev/null
+++ b/protocol/wlr-export-dmabuf-unstable-v1.xml
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_export_dmabuf_unstable_v1">
+ <copyright>
+ 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.
+ </copyright>
+
+ <description summary="a protocol for low overhead screen content capturing">
+ 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.
+ </description>
+
+ <interface name="zwlr_export_dmabuf_manager_v1" version="1">
+ <description summary="manager to inform clients and begin capturing">
+ This object is a manager with which to start capturing from sources.
+ </description>
+
+ <request name="capture_output">
+ <description summary="capture a frame from an output">
+ Capture the next frame of a an entire output.
+ </description>
+ <arg name="frame" type="new_id" interface="zwlr_export_dmabuf_frame_v1"/>
+ <arg name="overlay_cursor" type="int"
+ summary="include custom client hardware cursor on top of the frame"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the manager">
+ All objects created by the manager will still remain valid, until their
+ appropriate destroy request has been called.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwlr_export_dmabuf_frame_v1" version="1">
+ <description summary="a DMA-BUF frame">
+ 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.
+ </description>
+
+ <enum name="flags">
+ <description summary="frame flags">
+ Special flags that should be respected by the client.
+ </description>
+ <entry name="transient" value="0x1"
+ summary="clients should copy frame before processing"/>
+ </enum>
+
+ <event name="frame">
+ <description summary="a frame description">
+ 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.
+ </description>
+ <arg name="width" type="uint"
+ summary="frame width in pixels"/>
+ <arg name="height" type="uint"
+ summary="frame height in pixels"/>
+ <arg name="offset_x" type="uint"
+ summary="crop offset for the x axis"/>
+ <arg name="offset_y" type="uint"
+ summary="crop offset for the y axis"/>
+ <arg name="buffer_flags" type="uint"
+ summary="flags which indicate properties (invert, interlacing),
+ has the same values as zwp_linux_buffer_params_v1:flags"/>
+ <arg name="flags" type="uint" enum="flags"
+ summary="indicates special frame features"/>
+ <arg name="format" type="uint"
+ summary="format of the frame (DRM_FORMAT_*)"/>
+ <arg name="mod_high" type="uint"
+ summary="drm format modifier, high"/>
+ <arg name="mod_low" type="uint"
+ summary="drm format modifier, low"/>
+ <arg name="num_objects" type="uint"
+ summary="indicates how many objects (FDs) the frame has (max 4)"/>
+ </event>
+
+ <event name="object">
+ <description summary="an object description">
+ 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.
+ </description>
+ <arg name="index" type="uint"
+ summary="index of the current object"/>
+ <arg name="fd" type="fd"
+ summary="fd of the current object"/>
+ <arg name="size" type="uint"
+ summary="size in bytes for the current object"/>
+ <arg name="offset" type="uint"
+ summary="starting point for the data in the object's fd"/>
+ <arg name="stride" type="uint"
+ summary="line size in bytes"/>
+ <arg name="plane_index" type="uint"
+ summary="index of the the plane the data in the object applies to"/>
+ </event>
+
+ <event name="ready">
+ <description summary="indicates frame is available for reading">
+ 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.
+ </description>
+ <arg name="tv_sec_hi" type="uint"
+ summary="high 32 bits of the seconds part of the timestamp"/>
+ <arg name="tv_sec_lo" type="uint"
+ summary="low 32 bits of the seconds part of the timestamp"/>
+ <arg name="tv_nsec" type="uint"
+ summary="nanoseconds part of the timestamp"/>
+ </event>
+
+ <enum name="cancel_reason">
+ <description summary="cancel reason">
+ Indicates reason for cancelling the frame.
+ </description>
+ <entry name="temporary" value="0"
+ summary="temporary error, source will produce more frames"/>
+ <entry name="permanent" value="1"
+ summary="fatal error, source will not produce frames"/>
+ <entry name="resizing" value="2"
+ summary="temporary error, source will produce more frames"/>
+ </enum>
+
+ <event name="cancel">
+ <description summary="indicates the frame is no longer valid">
+ 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.
+ </description>
+ <arg name="reason" type="uint" enum="cancel_reason"
+ summary="indicates a reason for cancelling this frame capture"/>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="delete this object, used or not">
+ 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.
+ </description>
+ </request>
+ </interface>
+</protocol>
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 00000000..234d9535
--- /dev/null
+++ b/protocol/wlr-foreign-toplevel-management-unstable-v1.xml
@@ -0,0 +1,235 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_foreign_toplevel_management_unstable_v1">
+ <copyright>
+ 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.
+ </copyright>
+
+ <interface name="zwlr_foreign_toplevel_manager_v1" version="1">
+ <description summary="list and control opened apps">
+ 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
+ </description>
+
+ <event name="toplevel">
+ <description summary="a toplevel has been created">
+ 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.
+ </description>
+ <arg name="toplevel" type="new_id" interface="zwlr_foreign_toplevel_handle_v1"/>
+ </event>
+
+ <request name="stop">
+ <description summary="stop sending events">
+ 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.
+ </description>
+ </request>
+
+ <event name="finished">
+ <description summary="the compositor has finished with the toplevel manager">
+ 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.
+ </description>
+ </event>
+ </interface>
+
+ <interface name="zwlr_foreign_toplevel_handle_v1" version="1">
+ <description summary="an opened toplevel">
+ 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.
+ </description>
+
+ <event name="title">
+ <description summary="title change">
+ This event is emitted whenever the title of the toplevel changes.
+ </description>
+ <arg name="title" type="string"/>
+ </event>
+
+ <event name="app_id">
+ <description summary="app-id change">
+ This event is emitted whenever the app-id of the toplevel changes.
+ </description>
+ <arg name="app_id" type="string"/>
+ </event>
+
+ <event name="output_enter">
+ <description summary="toplevel entered an output">
+ This event is emitted whenever the toplevel becomes visible on
+ the given output. A toplevel may be visible on multiple outputs.
+ </description>
+ <arg name="output" type="object" interface="wl_output"/>
+ </event>
+
+ <event name="output_leave">
+ <description summary="toplevel left an output">
+ 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.
+ </description>
+ <arg name="output" type="object" interface="wl_output"/>
+ </event>
+
+ <request name="set_maximized">
+ <description summary="requests that the toplevel be maximized">
+ Requests that the toplevel be maximized. If the maximized state actually
+ changes, this will be indicated by the state event.
+ </description>
+ </request>
+
+ <request name="unset_maximized">
+ <description summary="requests that the toplevel be unmaximized">
+ Requests that the toplevel be unmaximized. If the maximized state actually
+ changes, this will be indicated by the state event.
+ </description>
+ </request>
+
+ <request name="set_minimized">
+ <description summary="requests that the toplevel be minimized">
+ Requests that the toplevel be minimized. If the minimized state actually
+ changes, this will be indicated by the state event.
+ </description>
+ </request>
+
+ <request name="unset_minimized">
+ <description summary="requests that the toplevel be unminimized">
+ Requests that the toplevel be unminimized. If the minimized state actually
+ changes, this will be indicated by the state event.
+ </description>
+ </request>
+
+ <request name="activate">
+ <description summary="activate the toplevel">
+ Request that this toplevel be activated on the given seat.
+ There is no guarantee the toplevel will be actually activated.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat"/>
+ </request>
+
+ <enum name="state">
+ <description summary="types of states on the toplevel">
+ The different states that a toplevel can have. These have the same meaning
+ as the states with the same names defined in xdg-toplevel
+ </description>
+
+ <entry name="maximized" value="0" summary="the toplevel is maximized"/>
+ <entry name="minimized" value="1" summary="the toplevel is minimized"/>
+ <entry name="activated" value="2" summary="the toplevel is active"/>
+ </enum>
+
+ <event name="state">
+ <description summary="the toplevel state changed">
+ 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.
+ </description>
+
+ <arg name="state" type="array"/>
+ </event>
+
+ <event name="done">
+ <description summary="all information about the toplevel has been sent">
+ 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.
+ </description>
+ </event>
+
+ <request name="close">
+ <description summary="request that the toplevel be closed">
+ 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.
+ </description>
+ </request>
+
+ <request name="set_rectangle">
+ <description summary="the rectangle which represents the toplevel">
+ 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.
+ </description>
+
+ <arg name="surface" type="object" interface="wl_surface"/>
+ <arg name="x" type="int"/>
+ <arg name="y" type="int"/>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <enum name="error">
+ <entry name="invalid_rectangle" value="0"
+ summary="the provided rectangle is invalid"/>
+ </enum>
+
+ <event name="closed">
+ <description summary="this toplevel has been destroyed">
+ 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.
+ </description>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the zwlr_foreign_toplevel_handle_v1 object">
+ 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.
+ </description>
+ </request>
+ </interface>
+</protocol>
diff --git a/protocol/wlr-gamma-control-unstable-v1.xml b/protocol/wlr-gamma-control-unstable-v1.xml
new file mode 100644
index 00000000..f55f3fe5
--- /dev/null
+++ b/protocol/wlr-gamma-control-unstable-v1.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_gamma_control_unstable_v1">
+ <copyright>
+ 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.
+ </copyright>
+
+ <description summary="manage gamma tables of outputs">
+ 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.
+ </description>
+
+ <interface name="zwlr_gamma_control_manager_v1" version="1">
+ <description summary="manager to create per-output gamma controls">
+ This interface is a manager that allows creating per-output gamma
+ controls.
+ </description>
+
+ <request name="get_gamma_control">
+ <description summary="get a gamma control for an output">
+ Create a gamma control that can be used to adjust gamma tables for the
+ provided output.
+ </description>
+ <arg name="id" type="new_id" interface="zwlr_gamma_control_v1"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the manager">
+ All objects created by the manager will still remain valid, until their
+ appropriate destroy request has been called.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwlr_gamma_control_v1" version="1">
+ <description summary="adjust gamma tables for an output">
+ 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.
+ </description>
+
+ <event name="gamma_size">
+ <description summary="size of gamma ramps">
+ Advertise the size of each gamma ramp.
+
+ This event is sent immediately when the gamma control object is created.
+ </description>
+ <arg name="size" type="uint" summary="number of elements in a ramp"/>
+ </event>
+
+ <enum name="error">
+ <entry name="invalid_gamma" value="1" summary="invalid gamma tables"/>
+ </enum>
+
+ <request name="set_gamma">
+ <description summary="set the gamma table">
+ 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.
+ </description>
+ <arg name="fd" type="fd" summary="gamma table file descriptor"/>
+ </request>
+
+ <event name="failed">
+ <description summary="object no longer valid">
+ 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 transfered gamma control to another client
+
+ Upon receiving this event, the client should destroy this object.
+ </description>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy this control">
+ Destroys the gamma control object. If the object is still valid, this
+ restores the original gamma tables.
+ </description>
+ </request>
+ </interface>
+</protocol>
diff --git a/protocol/wlr-input-inhibitor-unstable-v1.xml b/protocol/wlr-input-inhibitor-unstable-v1.xml
new file mode 100644
index 00000000..b62d1bb4
--- /dev/null
+++ b/protocol/wlr-input-inhibitor-unstable-v1.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_input_inhibit_unstable_v1">
+ <copyright>
+ Copyright © 2018 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.
+ </copyright>
+
+ <interface name="zwlr_input_inhibit_manager_v1" version="1">
+ <description summary="inhibits input events to other clients">
+ Clients can use this interface to prevent input events from being sent to
+ any surfaces but its own, which is useful for example in lock screen
+ software. It is assumed that access to this interface will be locked down
+ to whitelisted clients by the compositor.
+ </description>
+
+ <request name="get_inhibitor">
+ <description summary="inhibit input to other clients">
+ Activates the input inhibitor. As long as the inhibitor is active, the
+ compositor will not send input events to other clients.
+ </description>
+ <arg name="id" type="new_id" interface="zwlr_input_inhibitor_v1"/>
+ </request>
+
+ <enum name="error">
+ <entry name="already_inhibited" value="0" summary="an input inhibitor is already in use on the compositor"/>
+ </enum>
+ </interface>
+
+ <interface name="zwlr_input_inhibitor_v1" version="1">
+ <description summary="inhibits input to other clients">
+ While this resource exists, input to clients other than the owner of the
+ inhibitor resource will not receive input events. The client that owns
+ this resource will receive all input events normally. The compositor will
+ also disable all of its own input processing (such as keyboard shortcuts)
+ while the inhibitor is active.
+
+ The compositor may continue to send input events to selected clients,
+ such as an on-screen keyboard (via the input-method protocol).
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the input inhibitor object">
+ Destroy the inhibitor and allow other clients to receive input.
+ </description>
+ </request>
+ </interface>
+</protocol>
diff --git a/protocol/wlr-layer-shell-unstable-v1.xml b/protocol/wlr-layer-shell-unstable-v1.xml
new file mode 100644
index 00000000..216e0d9f
--- /dev/null
+++ b/protocol/wlr-layer-shell-unstable-v1.xml
@@ -0,0 +1,285 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_layer_shell_unstable_v1">
+ <copyright>
+ 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.
+ </copyright>
+
+ <interface name="zwlr_layer_shell_v1" version="1">
+ <description summary="create surfaces that are layers of the desktop">
+ 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.
+ </description>
+
+ <request name="get_layer_surface">
+ <description summary="create a layer_surface from a surface">
+ 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.
+
+ 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.
+ </description>
+ <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+ <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
+ <arg name="namespace" type="string" summary="namespace for the layer surface"/>
+ </request>
+
+ <enum name="error">
+ <entry name="role" value="0" summary="wl_surface has another role"/>
+ <entry name="invalid_layer" value="1" summary="layer value is invalid"/>
+ <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
+ </enum>
+
+ <enum name="layer">
+ <description summary="available layers for surfaces">
+ 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.
+ </description>
+
+ <entry name="background" value="0"/>
+ <entry name="bottom" value="1"/>
+ <entry name="top" value="2"/>
+ <entry name="overlay" value="3"/>
+ </enum>
+ </interface>
+
+ <interface name="zwlr_layer_surface_v1" version="1">
+ <description summary="layer metadata interface">
+ 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 (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.
+ </description>
+
+ <request name="set_size">
+ <description summary="sets the size of the surface">
+ 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.
+ </description>
+ <arg name="width" type="uint"/>
+ <arg name="height" type="uint"/>
+ </request>
+
+ <request name="set_anchor">
+ <description summary="configures the anchor point of the surface">
+ Requests that the compositor anchor the surface to the specified edges
+ and corners. If two orthoginal 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.
+ </description>
+ <arg name="anchor" type="uint" enum="anchor"/>
+ </request>
+
+ <request name="set_exclusive_zone">
+ <description summary="configures the exclusive geometry of this surface">
+ Requests that the compositor avoids occluding an area of the surface
+ 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 an
+ edge, rather than a corner. The zone is the number of surface-local
+ coordinates from the edge that are considered 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 excluzive 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.
+ </description>
+ <arg name="zone" type="int"/>
+ </request>
+
+ <request name="set_margin">
+ <description summary="sets a margin from the anchor point">
+ 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.
+ </description>
+ <arg name="top" type="int"/>
+ <arg name="right" type="int"/>
+ <arg name="bottom" type="int"/>
+ <arg name="left" type="int"/>
+ </request>
+
+ <request name="set_keyboard_interactivity">
+ <description summary="requests keyboard events">
+ Set to 1 to request that the seat send keyboard events to this layer
+ surface. For layers below the shell surface layer, the seat will use
+ normal focus semantics. For layers above the shell surface layers, the
+ seat will always give exclusive keyboard focus to the top-most layer
+ which has keyboard interactivity set to true.
+
+ 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.
+
+ Events is double-buffered, see wl_surface.commit.
+ </description>
+ <arg name="keyboard_interactivity" type="uint"/>
+ </request>
+
+ <request name="get_popup">
+ <description summary="assign this layer_surface as an xdg_popup parent">
+ 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.
+ </description>
+ <arg name="popup" type="object" interface="xdg_popup"/>
+ </request>
+
+ <request name="ack_configure">
+ <description summary="ack a configure event">
+ 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.
+ </description>
+ <arg name="serial" type="uint" summary="the serial from the configure event"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the layer_surface">
+ This request destroys the layer surface.
+ </description>
+ </request>
+
+ <event name="configure">
+ <description summary="suggest a surface change">
+ 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.
+ </description>
+ <arg name="serial" type="uint"/>
+ <arg name="width" type="uint"/>
+ <arg name="height" type="uint"/>
+ </event>
+
+ <event name="closed">
+ <description summary="surface should be closed">
+ 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.
+ </description>
+ </event>
+
+ <enum name="error">
+ <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
+ <entry name="invalid_size" value="1" summary="size is invalid"/>
+ <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
+ </enum>
+
+ <enum name="anchor" bitfield="true">
+ <entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
+ <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
+ <entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
+ <entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
+ </enum>
+ </interface>
+</protocol>
diff --git a/protocol/wlr-screencopy-unstable-v1.xml b/protocol/wlr-screencopy-unstable-v1.xml
new file mode 100644
index 00000000..a7a2d172
--- /dev/null
+++ b/protocol/wlr-screencopy-unstable-v1.xml
@@ -0,0 +1,179 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wlr_screencopy_unstable_v1">
+ <copyright>
+ Copyright © 2018 Simon Ser
+
+ 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.
+ </copyright>
+
+ <description summary="screen content capturing on client buffers">
+ 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.
+ </description>
+
+ <interface name="zwlr_screencopy_manager_v1" version="1">
+ <description summary="manager to inform clients and begin capturing">
+ This object is a manager which offers requests to start capturing from a
+ source.
+ </description>
+
+ <request name="capture_output">
+ <description summary="capture an output">
+ Capture the next frame of an entire output.
+ </description>
+ <arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
+ <arg name="overlay_cursor" type="int"
+ summary="composite cursor onto the frame"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ </request>
+
+ <request name="capture_output_region">
+ <description summary="capture an output's region">
+ 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.
+ </description>
+ <arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
+ <arg name="overlay_cursor" type="int"
+ summary="composite cursor onto the frame"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ <arg name="x" type="int"/>
+ <arg name="y" type="int"/>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the manager">
+ All objects created by the manager will still remain valid, until their
+ appropriate destroy request has been called.
+ </description>
+ </request>
+ </interface>
+
+ <interface name="zwlr_screencopy_frame_v1" version="1">
+ <description summary="a frame ready for copy">
+ This object represents a single frame.
+
+ When created, a "buffer" event will be sent. 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.
+
+ 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.
+ </description>
+
+ <event name="buffer">
+ <description summary="buffer information">
+ Provides information about the frame's buffer. This event is sent once
+ as soon as the frame is created.
+
+ The client should then create a buffer with the provided attributes, and
+ send a "copy" request.
+ </description>
+ <arg name="format" type="uint" summary="buffer format"/>
+ <arg name="width" type="uint" summary="buffer width"/>
+ <arg name="height" type="uint" summary="buffer height"/>
+ <arg name="stride" type="uint" summary="buffer stride"/>
+ </event>
+
+ <request name="copy">
+ <description summary="copy the frame">
+ Copy the frame to the supplied buffer. The buffer must have a the
+ correct size, see zwlr_screencopy_frame_v1.buffer. 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.
+ </description>
+ <arg name="buffer" type="object" interface="wl_buffer"/>
+ </request>
+
+ <enum name="error">
+ <entry name="already_used" value="0"
+ summary="the object has already been used to copy a wl_buffer"/>
+ <entry name="invalid_buffer" value="1"
+ summary="buffer attributes are invalid"/>
+ </enum>
+
+ <enum name="flags" bitfield="true">
+ <entry name="y_invert" value="1" summary="contents are y-inverted"/>
+ </enum>
+
+ <event name="flags">
+ <description summary="frame flags">
+ Provides flags about the frame. This event is sent once before the
+ "ready" event.
+ </description>
+ <arg name="flags" type="uint" enum="flags" summary="frame flags"/>
+ </event>
+
+ <event name="ready">
+ <description summary="indicates frame is available for reading">
+ 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.
+ </description>
+ <arg name="tv_sec_hi" type="uint"
+ summary="high 32 bits of the seconds part of the timestamp"/>
+ <arg name="tv_sec_lo" type="uint"
+ summary="low 32 bits of the seconds part of the timestamp"/>
+ <arg name="tv_nsec" type="uint"
+ summary="nanoseconds part of the timestamp"/>
+ </event>
+
+ <event name="failed">
+ <description summary="frame copy failed">
+ This event indicates that the attempted frame copy has failed.
+
+ After receiving this event, the client should destroy the object.
+ </description>
+ </event>
+
+ <request name="destroy" type="destructor">
+ <description summary="delete this object, used or not">
+ Destroys the frame. This request can be sent at any time by the client.
+ </description>
+ </request>
+ </interface>
+</protocol>
diff --git a/render/dmabuf.c b/render/dmabuf.c
new file mode 100644
index 00000000..6b500748
--- /dev/null
+++ b/render/dmabuf.c
@@ -0,0 +1,10 @@
+#include <unistd.h>
+#include <wlr/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;
+}
diff --git a/render/egl.c b/render/egl.c
new file mode 100644
index 00000000..cc00dece
--- /dev/null
+++ b/render/egl.c
@@ -0,0 +1,609 @@
+#include <assert.h>
+#include <drm_fourcc.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wlr/render/egl.h>
+#include <wlr/util/log.h>
+#include <wlr/util/region.h>
+#include "glapi.h"
+
+static bool egl_get_config(EGLDisplay disp, EGLint *attribs, EGLConfig *out,
+ EGLint visual_id) {
+ EGLint count = 0, matched = 0, ret;
+
+ ret = eglGetConfigs(disp, NULL, 0, &count);
+ if (ret == EGL_FALSE || count == 0) {
+ wlr_log(WLR_ERROR, "eglGetConfigs returned no configs");
+ return false;
+ }
+
+ EGLConfig configs[count];
+
+ ret = eglChooseConfig(disp, attribs, configs, count, &matched);
+ if (ret == EGL_FALSE) {
+ wlr_log(WLR_ERROR, "eglChooseConfig failed");
+ return false;
+ }
+
+ for (int i = 0; i < matched; ++i) {
+ EGLint visual;
+ if (!eglGetConfigAttrib(disp, configs[i],
+ EGL_NATIVE_VISUAL_ID, &visual)) {
+ continue;
+ }
+
+ if (!visual_id || visual == visual_id) {
+ *out = configs[i];
+ return true;
+ }
+ }
+
+ wlr_log(WLR_ERROR, "no valid egl config found");
+ return false;
+}
+
+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 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] %s: %s", command, 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 print_dmabuf_formats(struct wlr_egl *egl) {
+ /* Avoid log msg if extension is not present */
+ if (!egl->exts.image_dmabuf_import_modifiers_ext) {
+ return;
+ }
+
+ int *formats;
+ int num = wlr_egl_get_dmabuf_formats(egl, &formats);
+ if (num < 0) {
+ return;
+ }
+
+ char str_formats[num * 5 + 1];
+ for (int i = 0; i < num; i++) {
+ snprintf(&str_formats[i*5], (num - i) * 5 + 1, "%.4s ",
+ (char*)&formats[i]);
+ }
+ wlr_log(WLR_DEBUG, "Supported dmabuf buffer formats: %s", str_formats);
+ free(formats);
+}
+
+bool wlr_egl_init(struct wlr_egl *egl, EGLenum platform, void *remote_display,
+ EGLint *config_attribs, EGLint visual_id) {
+ if (!load_glapi()) {
+ return false;
+ }
+
+ if (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,
+ };
+ 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");
+ goto error;
+ }
+
+ if (platform == EGL_PLATFORM_SURFACELESS_MESA) {
+ assert(remote_display == NULL);
+ egl->display = eglGetPlatformDisplayEXT(platform, EGL_DEFAULT_DISPLAY, NULL);
+ } else {
+ egl->display = eglGetPlatformDisplayEXT(platform, remote_display, NULL);
+ }
+ if (egl->display == EGL_NO_DISPLAY) {
+ wlr_log(WLR_ERROR, "Failed to create EGL display");
+ goto error;
+ }
+
+ egl->platform = platform;
+
+ EGLint major, minor;
+ if (eglInitialize(egl->display, &major, &minor) == EGL_FALSE) {
+ wlr_log(WLR_ERROR, "Failed to initialize EGL");
+ goto error;
+ }
+
+ if (!egl_get_config(egl->display, config_attribs, &egl->config, visual_id)) {
+ wlr_log(WLR_ERROR, "Failed to get EGL config");
+ goto error;
+ }
+
+ egl->exts_str = eglQueryString(egl->display, EGL_EXTENSIONS);
+
+ wlr_log(WLR_INFO, "Using EGL %d.%d", (int)major, (int)minor);
+ wlr_log(WLR_INFO, "Supported EGL extensions: %s", egl->exts_str);
+ wlr_log(WLR_INFO, "EGL vendor: %s", eglQueryString(egl->display, EGL_VENDOR));
+
+ egl->exts.image_base_khr =
+ check_egl_ext(egl->exts_str, "EGL_KHR_image_base")
+ && eglCreateImageKHR && eglDestroyImageKHR;
+
+ egl->exts.buffer_age_ext =
+ check_egl_ext(egl->exts_str, "EGL_EXT_buffer_age");
+ egl->exts.swap_buffers_with_damage_ext =
+ (check_egl_ext(egl->exts_str, "EGL_EXT_swap_buffers_with_damage") &&
+ eglSwapBuffersWithDamageEXT);
+ egl->exts.swap_buffers_with_damage_khr =
+ (check_egl_ext(egl->exts_str, "EGL_KHR_swap_buffers_with_damage") &&
+ eglSwapBuffersWithDamageKHR);
+
+ egl->exts.image_dmabuf_import_ext =
+ check_egl_ext(egl->exts_str, "EGL_EXT_image_dma_buf_import");
+ egl->exts.image_dmabuf_import_modifiers_ext =
+ check_egl_ext(egl->exts_str, "EGL_EXT_image_dma_buf_import_modifiers")
+ && eglQueryDmaBufFormatsEXT && eglQueryDmaBufModifiersEXT;
+
+ egl->exts.image_dma_buf_export_mesa =
+ check_egl_ext(egl->exts_str, "EGL_MESA_image_dma_buf_export") &&
+ eglExportDMABUFImageQueryMESA && eglExportDMABUFImageMESA;
+
+ print_dmabuf_formats(egl);
+
+ egl->exts.bind_wayland_display_wl =
+ check_egl_ext(egl->exts_str, "EGL_WL_bind_wayland_display")
+ && eglBindWaylandDisplayWL && eglUnbindWaylandDisplayWL
+ && eglQueryWaylandBufferWL;
+
+ bool ext_context_priority =
+ check_egl_ext(egl->exts_str, "EGL_IMG_context_priority");
+
+ size_t atti = 0;
+ EGLint attribs[5];
+ attribs[atti++] = EGL_CONTEXT_CLIENT_VERSION;
+ attribs[atti++] = 2;
+
+ // On DRM, request a high priority context if possible
+ bool request_high_priority = ext_context_priority &&
+ platform == EGL_PLATFORM_GBM_MESA;
+
+ // 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;
+ }
+
+ attribs[atti++] = EGL_NONE;
+ assert(atti <= sizeof(attribs)/sizeof(attribs[0]));
+
+ egl->context = eglCreateContext(egl->display, egl->config,
+ EGL_NO_CONTEXT, attribs);
+ if (egl->context == EGL_NO_CONTEXT) {
+ wlr_log(WLR_ERROR, "Failed to create EGL context");
+ goto error;
+ }
+
+ 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");
+ }
+ }
+
+ if (!eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ egl->context)) {
+ wlr_log(WLR_ERROR, "Failed to make EGL context current");
+ goto error;
+ }
+
+ return true;
+
+error:
+ eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ if (egl->display) {
+ eglTerminate(egl->display);
+ }
+ eglReleaseThread();
+ return false;
+}
+
+void wlr_egl_finish(struct wlr_egl *egl) {
+ if (egl == NULL) {
+ return;
+ }
+
+ eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ if (egl->wl_display) {
+ assert(egl->exts.bind_wayland_display_wl);
+ eglUnbindWaylandDisplayWL(egl->display, egl->wl_display);
+ }
+
+ eglDestroyContext(egl->display, egl->context);
+ eglTerminate(egl->display);
+ eglReleaseThread();
+}
+
+bool wlr_egl_bind_display(struct wlr_egl *egl, struct wl_display *local_display) {
+ if (!egl->exts.bind_wayland_display_wl) {
+ return false;
+ }
+
+ if (eglBindWaylandDisplayWL(egl->display, local_display)) {
+ egl->wl_display = local_display;
+ return true;
+ }
+
+ return false;
+}
+
+bool wlr_egl_destroy_image(struct wlr_egl *egl, EGLImage image) {
+ if (!egl->exts.image_base_khr) {
+ return false;
+ }
+ if (!image) {
+ return true;
+ }
+ return eglDestroyImageKHR(egl->display, image);
+}
+
+EGLSurface wlr_egl_create_surface(struct wlr_egl *egl, void *window) {
+ assert(eglCreatePlatformWindowSurfaceEXT);
+ EGLSurface surf = eglCreatePlatformWindowSurfaceEXT(egl->display,
+ egl->config, window, NULL);
+ if (surf == EGL_NO_SURFACE) {
+ wlr_log(WLR_ERROR, "Failed to create EGL surface");
+ return EGL_NO_SURFACE;
+ }
+ return surf;
+}
+
+static int egl_get_buffer_age(struct wlr_egl *egl, EGLSurface surface) {
+ if (!egl->exts.buffer_age_ext) {
+ return -1;
+ }
+
+ EGLint buffer_age;
+ EGLBoolean ok = eglQuerySurface(egl->display, surface,
+ EGL_BUFFER_AGE_EXT, &buffer_age);
+ if (!ok) {
+ wlr_log(WLR_ERROR, "Failed to get EGL surface buffer age");
+ return -1;
+ }
+
+ return buffer_age;
+}
+
+bool wlr_egl_make_current(struct wlr_egl *egl, EGLSurface surface,
+ int *buffer_age) {
+ if (!eglMakeCurrent(egl->display, surface, surface, egl->context)) {
+ wlr_log(WLR_ERROR, "eglMakeCurrent failed");
+ return false;
+ }
+
+ if (buffer_age != NULL) {
+ *buffer_age = egl_get_buffer_age(egl, surface);
+ }
+ return true;
+}
+
+bool wlr_egl_is_current(struct wlr_egl *egl) {
+ return eglGetCurrentContext() == egl->context;
+}
+
+bool wlr_egl_swap_buffers(struct wlr_egl *egl, EGLSurface surface,
+ pixman_region32_t *damage) {
+ // Never block when swapping buffers on Wayland
+ if (egl->platform == EGL_PLATFORM_WAYLAND_EXT) {
+ eglSwapInterval(egl->display, 0);
+ }
+
+ EGLBoolean ret;
+ if (damage != NULL && (egl->exts.swap_buffers_with_damage_ext ||
+ egl->exts.swap_buffers_with_damage_khr)) {
+ EGLint width = 0, height = 0;
+ eglQuerySurface(egl->display, surface, EGL_WIDTH, &width);
+ eglQuerySurface(egl->display, surface, EGL_HEIGHT, &height);
+
+ pixman_region32_t flipped_damage;
+ pixman_region32_init(&flipped_damage);
+ wlr_region_transform(&flipped_damage, damage,
+ WL_OUTPUT_TRANSFORM_FLIPPED_180, width, height);
+
+ int nrects;
+ pixman_box32_t *rects =
+ pixman_region32_rectangles(&flipped_damage, &nrects);
+ EGLint egl_damage[4 * nrects];
+ for (int i = 0; i < nrects; ++i) {
+ egl_damage[4*i] = rects[i].x1;
+ egl_damage[4*i + 1] = rects[i].y1;
+ egl_damage[4*i + 2] = rects[i].x2 - rects[i].x1;
+ egl_damage[4*i + 3] = rects[i].y2 - rects[i].y1;
+ }
+
+ pixman_region32_fini(&flipped_damage);
+
+ if (egl->exts.swap_buffers_with_damage_ext) {
+ ret = eglSwapBuffersWithDamageEXT(egl->display, surface, egl_damage,
+ nrects);
+ } else {
+ ret = eglSwapBuffersWithDamageKHR(egl->display, surface, egl_damage,
+ nrects);
+ }
+ } else {
+ ret = eglSwapBuffers(egl->display, surface);
+ }
+
+ if (!ret) {
+ wlr_log(WLR_ERROR, "eglSwapBuffers failed");
+ return false;
+ }
+ return true;
+}
+
+EGLImageKHR wlr_egl_create_image_from_wl_drm(struct wlr_egl *egl,
+ struct wl_resource *data, EGLint *fmt, int *width, int *height,
+ bool *inverted_y) {
+ if (!egl->exts.bind_wayland_display_wl || !egl->exts.image_base_khr) {
+ return NULL;
+ }
+
+ if (!eglQueryWaylandBufferWL(egl->display, data, EGL_TEXTURE_FORMAT, fmt)) {
+ return NULL;
+ }
+
+ eglQueryWaylandBufferWL(egl->display, data, EGL_WIDTH, width);
+ eglQueryWaylandBufferWL(egl->display, data, EGL_HEIGHT, height);
+
+ EGLint _inverted_y;
+ if (eglQueryWaylandBufferWL(egl->display, data, EGL_WAYLAND_Y_INVERTED_WL,
+ &_inverted_y)) {
+ *inverted_y = !!_inverted_y;
+ } else {
+ *inverted_y = false;
+ }
+
+ const EGLint attribs[] = {
+ EGL_WAYLAND_PLANE_WL, 0,
+ EGL_NONE,
+ };
+ return eglCreateImageKHR(egl->display, egl->context, EGL_WAYLAND_BUFFER_WL,
+ data, attribs);
+}
+
+EGLImageKHR wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl,
+ struct wlr_dmabuf_attributes *attributes) {
+ if (!egl->exts.image_base_khr || !egl->exts.image_dmabuf_import_ext) {
+ wlr_log(WLR_ERROR, "dmabuf import extension not present");
+ return NULL;
+ }
+
+ bool has_modifier = false;
+
+ // we assume the same way we assumed formats without the import_modifiers
+ // extension that mod_linear is supported. The special mod mod_invalid
+ // is sometimes used to signal modifier unawareness which is what we
+ // have here
+ if (attributes->modifier != DRM_FORMAT_MOD_INVALID &&
+ attributes->modifier != DRM_FORMAT_MOD_LINEAR) {
+ if (!egl->exts.image_dmabuf_import_modifiers_ext) {
+ wlr_log(WLR_ERROR, "dmabuf modifiers extension not present");
+ return NULL;
+ }
+ has_modifier = true;
+ }
+
+ 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 (has_modifier) {
+ attribs[atti++] = attr_names[i].mod_lo;
+ attribs[atti++] = attributes->modifier & 0xFFFFFFFF;
+ attribs[atti++] = attr_names[i].mod_hi;
+ attribs[atti++] = attributes->modifier >> 32;
+ }
+ }
+ attribs[atti++] = EGL_NONE;
+ assert(atti < sizeof(attribs)/sizeof(attribs[0]));
+
+ return eglCreateImageKHR(egl->display, EGL_NO_CONTEXT,
+ EGL_LINUX_DMA_BUF_EXT, NULL, attribs);
+}
+
+int wlr_egl_get_dmabuf_formats(struct wlr_egl *egl,
+ int **formats) {
+ if (!egl->exts.image_dmabuf_import_ext) {
+ wlr_log(WLR_DEBUG, "dmabuf 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.image_dmabuf_import_modifiers_ext) {
+ static const int fallback_formats[] = {
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XRGB8888,
+ };
+ static unsigned num = sizeof(fallback_formats) /
+ sizeof(fallback_formats[0]);
+
+ *formats = calloc(num, sizeof(int));
+ if (!*formats) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ return -1;
+ }
+
+ memcpy(*formats, fallback_formats, num * sizeof(**formats));
+ return num;
+ }
+
+ EGLint num;
+ if (!eglQueryDmaBufFormatsEXT(egl->display, 0, NULL, &num)) {
+ wlr_log(WLR_ERROR, "failed to query number of dmabuf formats");
+ return -1;
+ }
+
+ *formats = calloc(num, sizeof(int));
+ if (*formats == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno));
+ return -1;
+ }
+
+ if (!eglQueryDmaBufFormatsEXT(egl->display, num, *formats, &num)) {
+ wlr_log(WLR_ERROR, "failed to query dmabuf format");
+ free(*formats);
+ return -1;
+ }
+ return num;
+}
+
+int wlr_egl_get_dmabuf_modifiers(struct wlr_egl *egl,
+ int format, uint64_t **modifiers) {
+ if (!egl->exts.image_dmabuf_import_ext) {
+ wlr_log(WLR_DEBUG, "dmabuf extension not present");
+ return -1;
+ }
+
+ if(!egl->exts.image_dmabuf_import_modifiers_ext) {
+ *modifiers = NULL;
+ return 0;
+ }
+
+ EGLint num;
+ if (!eglQueryDmaBufModifiersEXT(egl->display, format, 0,
+ NULL, NULL, &num)) {
+ wlr_log(WLR_ERROR, "failed to query dmabuf number of modifiers");
+ return -1;
+ }
+
+ *modifiers = calloc(num, sizeof(uint64_t));
+ if (*modifiers == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno));
+ return -1;
+ }
+
+ if (!eglQueryDmaBufModifiersEXT(egl->display, format, num,
+ *modifiers, NULL, &num)) {
+ wlr_log(WLR_ERROR, "failed to query dmabuf modifiers");
+ free(*modifiers);
+ return -1;
+ }
+ return num;
+}
+
+bool wlr_egl_export_image_to_dmabuf(struct wlr_egl *egl, EGLImageKHR image,
+ int32_t width, int32_t height, uint32_t flags,
+ struct wlr_dmabuf_attributes *attribs) {
+ memset(attribs, 0, sizeof(struct wlr_dmabuf_attributes));
+
+ if (!egl->exts.image_dma_buf_export_mesa) {
+ return false;
+ }
+
+ // Only one set of modifiers is returned for all planes
+ if (!eglExportDMABUFImageQueryMESA(egl->display, image,
+ (int *)&attribs->format, &attribs->n_planes, &attribs->modifier)) {
+ return false;
+ }
+ if (attribs->n_planes > WLR_DMABUF_MAX_PLANES) {
+ wlr_log(WLR_ERROR, "EGL returned %d planes, but only %d are supported",
+ attribs->n_planes, WLR_DMABUF_MAX_PLANES);
+ return false;
+ }
+
+ if (!eglExportDMABUFImageMESA(egl->display, image, attribs->fd,
+ (EGLint *)attribs->stride, (EGLint *)attribs->offset)) {
+ return false;
+ }
+
+ attribs->width = width;
+ attribs->height = height;
+ attribs->flags = flags;
+ return true;
+}
+
+bool wlr_egl_destroy_surface(struct wlr_egl *egl, EGLSurface surface) {
+ if (!surface) {
+ return true;
+ }
+ return eglDestroySurface(egl->display, surface);
+}
diff --git a/render/glapi.txt b/render/glapi.txt
new file mode 100644
index 00000000..b1166f27
--- /dev/null
+++ b/render/glapi.txt
@@ -0,0 +1,19 @@
+eglGetPlatformDisplayEXT
+eglCreatePlatformWindowSurfaceEXT
+-eglCreateImageKHR
+-eglDestroyImageKHR
+-eglQueryWaylandBufferWL
+-eglBindWaylandDisplayWL
+-eglUnbindWaylandDisplayWL
+-glEGLImageTargetTexture2DOES
+-eglSwapBuffersWithDamageEXT
+-eglSwapBuffersWithDamageKHR
+-eglQueryDmaBufFormatsEXT
+-eglQueryDmaBufModifiersEXT
+-eglExportDMABUFImageQueryMESA
+-eglExportDMABUFImageMESA
+-eglDebugMessageControlKHR
+-glDebugMessageCallbackKHR
+-glDebugMessageControlKHR
+-glPopDebugGroupKHR
+-glPushDebugGroupKHR
diff --git a/render/gles2/pixel_format.c b/render/gles2/pixel_format.c
new file mode 100644
index 00000000..a4b4c101
--- /dev/null
+++ b/render/gles2/pixel_format.c
@@ -0,0 +1,78 @@
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include "render/gles2.h"
+
+/*
+ * The wayland formats are little endian while the GL formats are big endian,
+ * so WL_SHM_FORMAT_ARGB8888 is actually compatible with GL_BGRA_EXT.
+ */
+static const struct wlr_gles2_pixel_format formats[] = {
+ {
+ .wl_format = WL_SHM_FORMAT_ARGB8888,
+ .depth = 32,
+ .bpp = 32,
+ .gl_format = GL_BGRA_EXT,
+ .gl_type = GL_UNSIGNED_BYTE,
+ .has_alpha = true,
+ },
+ {
+ .wl_format = WL_SHM_FORMAT_XRGB8888,
+ .depth = 24,
+ .bpp = 32,
+ .gl_format = GL_BGRA_EXT,
+ .gl_type = GL_UNSIGNED_BYTE,
+ .has_alpha = false,
+ },
+ {
+ .wl_format = WL_SHM_FORMAT_XBGR8888,
+ .depth = 24,
+ .bpp = 32,
+ .gl_format = GL_RGBA,
+ .gl_type = GL_UNSIGNED_BYTE,
+ .has_alpha = false,
+ },
+ {
+ .wl_format = WL_SHM_FORMAT_ABGR8888,
+ .depth = 32,
+ .bpp = 32,
+ .gl_format = GL_RGBA,
+ .gl_type = GL_UNSIGNED_BYTE,
+ .has_alpha = true,
+ },
+};
+
+static const enum wl_shm_format wl_formats[] = {
+ WL_SHM_FORMAT_ARGB8888,
+ WL_SHM_FORMAT_XRGB8888,
+ WL_SHM_FORMAT_ABGR8888,
+ WL_SHM_FORMAT_XBGR8888,
+};
+
+// TODO: more pixel formats
+
+const struct wlr_gles2_pixel_format *get_gles2_format_from_wl(
+ enum wl_shm_format fmt) {
+ for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); ++i) {
+ if (formats[i].wl_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 &&
+ formats[i].has_alpha == alpha) {
+ return &formats[i];
+ }
+ }
+ return NULL;
+}
+
+const enum wl_shm_format *get_gles2_wl_formats(size_t *len) {
+ *len = sizeof(wl_formats) / sizeof(wl_formats[0]);
+ return wl_formats;
+}
diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c
new file mode 100644
index 00000000..50689ad4
--- /dev/null
+++ b/render/gles2/renderer.c
@@ -0,0 +1,655 @@
+#include <assert.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wayland-server-protocol.h>
+#include <wayland-util.h>
+#include <wlr/render/egl.h>
+#include <wlr/render/interface.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/util/log.h>
+#include "glapi.h"
+#include "render/gles2.h"
+
+static const struct wlr_renderer_impl renderer_impl;
+
+static struct wlr_gles2_renderer *gles2_get_renderer(
+ struct wlr_renderer *wlr_renderer) {
+ assert(wlr_renderer->impl == &renderer_impl);
+ return (struct wlr_gles2_renderer *)wlr_renderer;
+}
+
+static struct wlr_gles2_renderer *gles2_get_renderer_in_context(
+ struct wlr_renderer *wlr_renderer) {
+ struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer);
+ assert(wlr_egl_is_current(renderer->egl));
+ return renderer;
+}
+
+static void gles2_begin(struct wlr_renderer *wlr_renderer, uint32_t width,
+ uint32_t height) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer_in_context(wlr_renderer);
+
+ PUSH_GLES2_DEBUG;
+
+ glViewport(0, 0, width, height);
+ renderer->viewport_width = width;
+ renderer->viewport_height = height;
+
+ // enable transparency
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+ // XXX: maybe we should save output projection and remove some of the need
+ // for users to sling matricies themselves
+
+ POP_GLES2_DEBUG;
+}
+
+static void gles2_end(struct wlr_renderer *wlr_renderer) {
+ gles2_get_renderer_in_context(wlr_renderer);
+ // no-op
+}
+
+static void gles2_clear(struct wlr_renderer *wlr_renderer,
+ const float color[static 4]) {
+ gles2_get_renderer_in_context(wlr_renderer);
+
+ PUSH_GLES2_DEBUG;
+ glClearColor(color[0], color[1], color[2], color[3]);
+ glClear(GL_COLOR_BUFFER_BIT);
+ POP_GLES2_DEBUG;
+}
+
+static void gles2_scissor(struct wlr_renderer *wlr_renderer,
+ struct wlr_box *box) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer_in_context(wlr_renderer);
+
+ PUSH_GLES2_DEBUG;
+ if (box != NULL) {
+ struct wlr_box gl_box;
+ wlr_box_transform(&gl_box, box, WL_OUTPUT_TRANSFORM_FLIPPED_180,
+ renderer->viewport_width, renderer->viewport_height);
+
+ glScissor(gl_box.x, gl_box.y, gl_box.width, gl_box.height);
+ glEnable(GL_SCISSOR_TEST);
+ } else {
+ glDisable(GL_SCISSOR_TEST);
+ }
+ POP_GLES2_DEBUG;
+}
+
+static void draw_quad(void) {
+ GLfloat verts[] = {
+ 1, 0, // top right
+ 0, 0, // top left
+ 1, 1, // bottom right
+ 0, 1, // bottom left
+ };
+ GLfloat texcoord[] = {
+ 1, 0, // top right
+ 0, 0, // top left
+ 1, 1, // bottom right
+ 0, 1, // bottom left
+ };
+
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, verts);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, texcoord);
+
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+ glDisableVertexAttribArray(0);
+ glDisableVertexAttribArray(1);
+}
+
+static bool gles2_render_texture_with_matrix(struct wlr_renderer *wlr_renderer,
+ struct wlr_texture *wlr_texture, const float matrix[static 9],
+ float alpha) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer_in_context(wlr_renderer);
+ struct wlr_gles2_texture *texture =
+ gles2_get_texture(wlr_texture);
+
+ struct wlr_gles2_tex_shader *shader = NULL;
+ GLenum target = 0;
+
+ switch (texture->type) {
+ case WLR_GLES2_TEXTURE_GLTEX:
+ case WLR_GLES2_TEXTURE_WL_DRM_GL:
+ if (texture->has_alpha) {
+ shader = &renderer->shaders.tex_rgba;
+ } else {
+ shader = &renderer->shaders.tex_rgbx;
+ }
+ target = GL_TEXTURE_2D;
+ break;
+ case WLR_GLES2_TEXTURE_WL_DRM_EXT:
+ case WLR_GLES2_TEXTURE_DMABUF:
+ shader = &renderer->shaders.tex_ext;
+ target = GL_TEXTURE_EXTERNAL_OES;
+
+ if (!renderer->exts.egl_image_external_oes) {
+ wlr_log(WLR_ERROR, "Failed to render texture: "
+ "GL_TEXTURE_EXTERNAL_OES not supported");
+ return false;
+ }
+ break;
+ }
+
+ // OpenGL ES 2 requires the glUniformMatrix3fv transpose parameter to be set
+ // to GL_FALSE
+ float transposition[9];
+ wlr_matrix_transpose(transposition, matrix);
+
+ PUSH_GLES2_DEBUG;
+
+ GLuint tex_id = texture->type == WLR_GLES2_TEXTURE_GLTEX ?
+ texture->gl_tex : texture->image_tex;
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(target, tex_id);
+
+ glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ glUseProgram(shader->program);
+
+ glUniformMatrix3fv(shader->proj, 1, GL_FALSE, transposition);
+ glUniform1i(shader->invert_y, texture->inverted_y);
+ glUniform1i(shader->tex, 0);
+ glUniform1f(shader->alpha, alpha);
+
+ draw_quad();
+
+ POP_GLES2_DEBUG;
+ return true;
+}
+
+
+static void gles2_render_quad_with_matrix(struct wlr_renderer *wlr_renderer,
+ const float color[static 4], const float matrix[static 9]) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer_in_context(wlr_renderer);
+
+ // OpenGL ES 2 requires the glUniformMatrix3fv transpose parameter to be set
+ // to GL_FALSE
+ float transposition[9];
+ wlr_matrix_transpose(transposition, matrix);
+
+ PUSH_GLES2_DEBUG;
+ glUseProgram(renderer->shaders.quad.program);
+
+ glUniformMatrix3fv(renderer->shaders.quad.proj, 1, GL_FALSE, transposition);
+ glUniform4f(renderer->shaders.quad.color, color[0], color[1], color[2], color[3]);
+ draw_quad();
+ POP_GLES2_DEBUG;
+}
+
+static void gles2_render_ellipse_with_matrix(struct wlr_renderer *wlr_renderer,
+ const float color[static 4], const float matrix[static 9]) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer_in_context(wlr_renderer);
+
+ // OpenGL ES 2 requires the glUniformMatrix3fv transpose parameter to be set
+ // to GL_FALSE
+ float transposition[9];
+ wlr_matrix_transpose(transposition, matrix);
+
+ PUSH_GLES2_DEBUG;
+ glUseProgram(renderer->shaders.ellipse.program);
+
+ glUniformMatrix3fv(renderer->shaders.ellipse.proj, 1, GL_FALSE, transposition);
+ glUniform4f(renderer->shaders.ellipse.color, color[0], color[1], color[2], color[3]);
+ draw_quad();
+ POP_GLES2_DEBUG;
+}
+
+static const enum wl_shm_format *gles2_renderer_formats(
+ struct wlr_renderer *wlr_renderer, size_t *len) {
+ return get_gles2_wl_formats(len);
+}
+
+static bool gles2_format_supported(struct wlr_renderer *wlr_renderer,
+ enum wl_shm_format wl_fmt) {
+ return get_gles2_format_from_wl(wl_fmt) != NULL;
+}
+
+static bool gles2_resource_is_wl_drm_buffer(struct wlr_renderer *wlr_renderer,
+ struct wl_resource *resource) {
+ struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer);
+
+ if (!eglQueryWaylandBufferWL) {
+ return false;
+ }
+
+ EGLint fmt;
+ return eglQueryWaylandBufferWL(renderer->egl->display, resource,
+ EGL_TEXTURE_FORMAT, &fmt);
+}
+
+static void gles2_wl_drm_buffer_get_size(struct wlr_renderer *wlr_renderer,
+ struct wl_resource *buffer, int *width, int *height) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer(wlr_renderer);
+
+ if (!eglQueryWaylandBufferWL) {
+ return;
+ }
+
+ eglQueryWaylandBufferWL(renderer->egl->display, buffer, EGL_WIDTH, width);
+ eglQueryWaylandBufferWL(renderer->egl->display, buffer, EGL_HEIGHT, height);
+}
+
+static int gles2_get_dmabuf_formats(struct wlr_renderer *wlr_renderer,
+ int **formats) {
+ struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer);
+ return wlr_egl_get_dmabuf_formats(renderer->egl, formats);
+}
+
+static int gles2_get_dmabuf_modifiers(struct wlr_renderer *wlr_renderer,
+ int format, uint64_t **modifiers) {
+ struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer);
+ return wlr_egl_get_dmabuf_modifiers(renderer->egl, format, modifiers);
+}
+
+static enum wl_shm_format gles2_preferred_read_format(
+ struct wlr_renderer *wlr_renderer) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer_in_context(wlr_renderer);
+
+ GLint gl_format = -1, gl_type = -1;
+ PUSH_GLES2_DEBUG;
+ glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &gl_format);
+ glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &gl_type);
+ POP_GLES2_DEBUG;
+
+ EGLint alpha_size = -1;
+ eglGetConfigAttrib(renderer->egl->display, renderer->egl->config,
+ EGL_ALPHA_SIZE, &alpha_size);
+
+ const struct wlr_gles2_pixel_format *fmt =
+ get_gles2_format_from_gl(gl_format, gl_type, alpha_size > 0);
+ if (fmt != NULL) {
+ return fmt->wl_format;
+ }
+
+ if (renderer->exts.read_format_bgra_ext) {
+ return WL_SHM_FORMAT_XRGB8888;
+ }
+ return WL_SHM_FORMAT_XBGR8888;
+}
+
+static bool gles2_read_pixels(struct wlr_renderer *wlr_renderer,
+ enum wl_shm_format wl_fmt, uint32_t *flags, 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) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer_in_context(wlr_renderer);
+
+ const struct wlr_gles2_pixel_format *fmt = get_gles2_format_from_wl(wl_fmt);
+ if (fmt == NULL) {
+ wlr_log(WLR_ERROR, "Cannot read pixels: unsupported pixel format");
+ return false;
+ }
+
+ if (fmt->gl_format == GL_BGRA_EXT && !renderer->exts.read_format_bgra_ext) {
+ wlr_log(WLR_ERROR,
+ "Cannot read pixels: missing GL_EXT_read_format_bgra extension");
+ return false;
+ }
+
+ PUSH_GLES2_DEBUG;
+
+ // Make sure any pending drawing is finished before we try to read it
+ glFinish();
+
+ glGetError(); // Clear the error flag
+
+ unsigned char *p = data + dst_y * stride;
+ uint32_t pack_stride = width * fmt->bpp / 8;
+ if (pack_stride == stride && dst_x == 0 && flags != NULL) {
+ // Under these particular conditions, we can read the pixels with only
+ // one glReadPixels call
+ glReadPixels(src_x, renderer->viewport_height - height - src_y,
+ width, height, fmt->gl_format, fmt->gl_type, p);
+ *flags = WLR_RENDERER_READ_PIXELS_Y_INVERT;
+ } else {
+ // Unfortunately GLES2 doesn't support GL_PACK_*, so we have to read
+ // the lines out row by row
+ for (size_t i = src_y; i < src_y + height; ++i) {
+ glReadPixels(src_x, src_y + height - i - 1, width, 1, fmt->gl_format,
+ fmt->gl_type, p + i * stride + dst_x * fmt->bpp / 8);
+ }
+ if (flags != NULL) {
+ *flags = 0;
+ }
+ }
+
+ POP_GLES2_DEBUG;
+
+ return glGetError() == GL_NO_ERROR;
+}
+
+static struct wlr_texture *gles2_texture_from_pixels(
+ struct wlr_renderer *wlr_renderer, enum wl_shm_format wl_fmt,
+ uint32_t stride, uint32_t width, uint32_t height, const void *data) {
+ struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer);
+ return wlr_gles2_texture_from_pixels(renderer->egl, wl_fmt, stride, width,
+ height, data);
+}
+
+static struct wlr_texture *gles2_texture_from_wl_drm(
+ struct wlr_renderer *wlr_renderer, struct wl_resource *data) {
+ struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer);
+ return wlr_gles2_texture_from_wl_drm(renderer->egl, data);
+}
+
+static struct wlr_texture *gles2_texture_from_dmabuf(
+ struct wlr_renderer *wlr_renderer,
+ struct wlr_dmabuf_attributes *attribs) {
+ struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer);
+ return wlr_gles2_texture_from_dmabuf(renderer->egl, attribs);
+}
+
+static void gles2_init_wl_display(struct wlr_renderer *wlr_renderer,
+ struct wl_display *wl_display) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer(wlr_renderer);
+ if (!wlr_egl_bind_display(renderer->egl, wl_display)) {
+ wlr_log(WLR_INFO, "failed to bind wl_display to 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, EGL_NO_SURFACE, NULL);
+
+ PUSH_GLES2_DEBUG;
+ glDeleteProgram(renderer->shaders.quad.program);
+ glDeleteProgram(renderer->shaders.ellipse.program);
+ glDeleteProgram(renderer->shaders.tex_rgba.program);
+ glDeleteProgram(renderer->shaders.tex_rgbx.program);
+ glDeleteProgram(renderer->shaders.tex_ext.program);
+ POP_GLES2_DEBUG;
+
+ if (renderer->exts.debug_khr) {
+ glDisable(GL_DEBUG_OUTPUT_KHR);
+ glDebugMessageCallbackKHR(NULL, NULL);
+ }
+
+ free(renderer);
+}
+
+static const struct wlr_renderer_impl renderer_impl = {
+ .destroy = gles2_destroy,
+ .begin = gles2_begin,
+ .end = gles2_end,
+ .clear = gles2_clear,
+ .scissor = gles2_scissor,
+ .render_texture_with_matrix = gles2_render_texture_with_matrix,
+ .render_quad_with_matrix = gles2_render_quad_with_matrix,
+ .render_ellipse_with_matrix = gles2_render_ellipse_with_matrix,
+ .formats = gles2_renderer_formats,
+ .format_supported = gles2_format_supported,
+ .resource_is_wl_drm_buffer = gles2_resource_is_wl_drm_buffer,
+ .wl_drm_buffer_get_size = gles2_wl_drm_buffer_get_size,
+ .get_dmabuf_formats = gles2_get_dmabuf_formats,
+ .get_dmabuf_modifiers = gles2_get_dmabuf_modifiers,
+ .preferred_read_format = gles2_preferred_read_format,
+ .read_pixels = gles2_read_pixels,
+ .texture_from_pixels = gles2_texture_from_pixels,
+ .texture_from_wl_drm = gles2_texture_from_wl_drm,
+ .texture_from_dmabuf = gles2_texture_from_dmabuf,
+ .init_wl_display = gles2_init_wl_display,
+};
+
+void push_gles2_marker(const char *file, const char *func) {
+ if (!glPushDebugGroupKHR) {
+ return;
+ }
+
+ int len = snprintf(NULL, 0, "%s:%s", file, func) + 1;
+ char str[len];
+ snprintf(str, len, "%s:%s", file, func);
+ glPushDebugGroupKHR(GL_DEBUG_SOURCE_APPLICATION_KHR, 1, -1, str);
+}
+
+void pop_gles2_marker(void) {
+ if (glPopDebugGroupKHR) {
+ 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(GLuint type, const GLchar *src) {
+ PUSH_GLES2_DEBUG;
+
+ GLuint shader = glCreateShader(type);
+ glShaderSource(shader, 1, &src, NULL);
+ glCompileShader(shader);
+
+ GLint ok;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
+ if (ok == GL_FALSE) {
+ glDeleteShader(shader);
+ shader = 0;
+ }
+
+ POP_GLES2_DEBUG;
+ return shader;
+}
+
+static GLuint link_program(const GLchar *vert_src, const GLchar *frag_src) {
+ PUSH_GLES2_DEBUG;
+
+ GLuint vert = compile_shader(GL_VERTEX_SHADER, vert_src);
+ if (!vert) {
+ goto error;
+ }
+
+ GLuint frag = compile_shader(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) {
+ glDeleteProgram(prog);
+ goto error;
+ }
+
+ POP_GLES2_DEBUG;
+ return prog;
+
+error:
+ POP_GLES2_DEBUG;
+ 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;
+}
+
+extern const GLchar quad_vertex_src[];
+extern const GLchar quad_fragment_src[];
+extern const GLchar ellipse_fragment_src[];
+extern const GLchar tex_vertex_src[];
+extern const GLchar tex_fragment_src_rgba[];
+extern const GLchar tex_fragment_src_rgbx[];
+extern const GLchar tex_fragment_src_external[];
+
+struct wlr_renderer *wlr_gles2_renderer_create(struct wlr_egl *egl) {
+ if (!load_glapi()) {
+ return NULL;
+ }
+
+ struct wlr_gles2_renderer *renderer =
+ calloc(1, sizeof(struct wlr_gles2_renderer));
+ if (renderer == NULL) {
+ return NULL;
+ }
+ wlr_renderer_init(&renderer->wlr_renderer, &renderer_impl);
+
+ renderer->egl = egl;
+ if (!wlr_egl_make_current(renderer->egl, EGL_NO_SURFACE, NULL)) {
+ free(renderer);
+ return NULL;
+ }
+
+ renderer->exts_str = (const char *)glGetString(GL_EXTENSIONS);
+ wlr_log(WLR_INFO, "Using %s", glGetString(GL_VERSION));
+ wlr_log(WLR_INFO, "GL vendor: %s", glGetString(GL_VENDOR));
+ wlr_log(WLR_INFO, "Supported GLES2 extensions: %s", renderer->exts_str);
+
+ if (!check_gl_ext(renderer->exts_str, "GL_EXT_texture_format_BGRA8888")) {
+ wlr_log(WLR_ERROR, "BGRA8888 format not supported by GLES2");
+ free(renderer);
+ return NULL;
+ }
+
+ renderer->exts.read_format_bgra_ext =
+ check_gl_ext(renderer->exts_str, "GL_EXT_read_format_bgra");
+ renderer->exts.debug_khr =
+ check_gl_ext(renderer->exts_str, "GL_KHR_debug") &&
+ glDebugMessageCallbackKHR && glDebugMessageControlKHR;
+ renderer->exts.egl_image_external_oes =
+ check_gl_ext(renderer->exts_str, "GL_OES_EGL_image_external") &&
+ glEGLImageTargetTexture2DOES;
+
+ if (renderer->exts.debug_khr) {
+ glEnable(GL_DEBUG_OUTPUT_KHR);
+ glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR);
+ glDebugMessageCallbackKHR(gles2_log, NULL);
+
+ // Silence unwanted message types
+ glDebugMessageControlKHR(GL_DONT_CARE, GL_DEBUG_TYPE_POP_GROUP_KHR,
+ GL_DONT_CARE, 0, NULL, GL_FALSE);
+ glDebugMessageControlKHR(GL_DONT_CARE, GL_DEBUG_TYPE_PUSH_GROUP_KHR,
+ GL_DONT_CARE, 0, NULL, GL_FALSE);
+ }
+
+ PUSH_GLES2_DEBUG;
+
+ GLuint prog;
+ renderer->shaders.quad.program = prog =
+ link_program(quad_vertex_src, quad_fragment_src);
+ if (!renderer->shaders.quad.program) {
+ goto error;
+ }
+ renderer->shaders.quad.proj = glGetUniformLocation(prog, "proj");
+ renderer->shaders.quad.color = glGetUniformLocation(prog, "color");
+
+ renderer->shaders.ellipse.program = prog =
+ link_program(quad_vertex_src, ellipse_fragment_src);
+ if (!renderer->shaders.ellipse.program) {
+ goto error;
+ }
+ renderer->shaders.ellipse.proj = glGetUniformLocation(prog, "proj");
+ renderer->shaders.ellipse.color = glGetUniformLocation(prog, "color");
+
+ renderer->shaders.tex_rgba.program = prog =
+ link_program(tex_vertex_src, tex_fragment_src_rgba);
+ if (!renderer->shaders.tex_rgba.program) {
+ goto error;
+ }
+ renderer->shaders.tex_rgba.proj = glGetUniformLocation(prog, "proj");
+ renderer->shaders.tex_rgba.invert_y = glGetUniformLocation(prog, "invert_y");
+ renderer->shaders.tex_rgba.tex = glGetUniformLocation(prog, "tex");
+ renderer->shaders.tex_rgba.alpha = glGetUniformLocation(prog, "alpha");
+
+ renderer->shaders.tex_rgbx.program = prog =
+ link_program(tex_vertex_src, tex_fragment_src_rgbx);
+ if (!renderer->shaders.tex_rgbx.program) {
+ goto error;
+ }
+ renderer->shaders.tex_rgbx.proj = glGetUniformLocation(prog, "proj");
+ renderer->shaders.tex_rgbx.invert_y = glGetUniformLocation(prog, "invert_y");
+ renderer->shaders.tex_rgbx.tex = glGetUniformLocation(prog, "tex");
+ renderer->shaders.tex_rgbx.alpha = glGetUniformLocation(prog, "alpha");
+
+ if (renderer->exts.egl_image_external_oes) {
+ renderer->shaders.tex_ext.program = prog =
+ link_program(tex_vertex_src, tex_fragment_src_external);
+ if (!renderer->shaders.tex_ext.program) {
+ goto error;
+ }
+ renderer->shaders.tex_ext.proj = glGetUniformLocation(prog, "proj");
+ renderer->shaders.tex_ext.invert_y = glGetUniformLocation(prog, "invert_y");
+ renderer->shaders.tex_ext.tex = glGetUniformLocation(prog, "tex");
+ renderer->shaders.tex_ext.alpha = glGetUniformLocation(prog, "alpha");
+ }
+
+ POP_GLES2_DEBUG;
+
+ return &renderer->wlr_renderer;
+
+error:
+ glDeleteProgram(renderer->shaders.quad.program);
+ glDeleteProgram(renderer->shaders.ellipse.program);
+ glDeleteProgram(renderer->shaders.tex_rgba.program);
+ glDeleteProgram(renderer->shaders.tex_rgbx.program);
+ glDeleteProgram(renderer->shaders.tex_ext.program);
+
+ POP_GLES2_DEBUG;
+
+ if (renderer->exts.debug_khr) {
+ glDisable(GL_DEBUG_OUTPUT_KHR);
+ glDebugMessageCallbackKHR(NULL, NULL);
+ }
+
+ free(renderer);
+ return NULL;
+}
diff --git a/render/gles2/shaders.c b/render/gles2/shaders.c
new file mode 100644
index 00000000..01410d87
--- /dev/null
+++ b/render/gles2/shaders.c
@@ -0,0 +1,88 @@
+#include <GLES2/gl2.h>
+#include "render/gles2.h"
+
+// Colored quads
+const GLchar quad_vertex_src[] =
+"uniform mat3 proj;\n"
+"uniform vec4 color;\n"
+"attribute vec2 pos;\n"
+"attribute vec2 texcoord;\n"
+"varying vec4 v_color;\n"
+"varying vec2 v_texcoord;\n"
+"\n"
+"void main() {\n"
+" gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);\n"
+" v_color = color;\n"
+" v_texcoord = texcoord;\n"
+"}\n";
+
+const GLchar quad_fragment_src[] =
+"precision mediump float;\n"
+"varying vec4 v_color;\n"
+"varying vec2 v_texcoord;\n"
+"\n"
+"void main() {\n"
+" gl_FragColor = v_color;\n"
+"}\n";
+
+// Colored ellipses
+const GLchar ellipse_fragment_src[] =
+"precision mediump float;\n"
+"varying vec4 v_color;\n"
+"varying vec2 v_texcoord;\n"
+"\n"
+"void main() {\n"
+" float l = length(v_texcoord - vec2(0.5, 0.5));\n"
+" if (l > 0.5) {\n"
+" discard;\n"
+" }\n"
+" gl_FragColor = v_color;\n"
+"}\n";
+
+// Textured quads
+const GLchar tex_vertex_src[] =
+"uniform mat3 proj;\n"
+"uniform bool invert_y;\n"
+"attribute vec2 pos;\n"
+"attribute vec2 texcoord;\n"
+"varying vec2 v_texcoord;\n"
+"\n"
+"void main() {\n"
+" gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);\n"
+" if (invert_y) {\n"
+" v_texcoord = vec2(texcoord.s, 1.0 - texcoord.t);\n"
+" } else {\n"
+" v_texcoord = texcoord;\n"
+" }\n"
+"}\n";
+
+const GLchar tex_fragment_src_rgba[] =
+"precision mediump float;\n"
+"varying vec2 v_texcoord;\n"
+"uniform sampler2D tex;\n"
+"uniform float alpha;\n"
+"\n"
+"void main() {\n"
+" gl_FragColor = texture2D(tex, v_texcoord) * alpha;\n"
+"}\n";
+
+const GLchar tex_fragment_src_rgbx[] =
+"precision mediump float;\n"
+"varying vec2 v_texcoord;\n"
+"uniform sampler2D tex;\n"
+"uniform float alpha;\n"
+"\n"
+"void main() {\n"
+" gl_FragColor = vec4(texture2D(tex, v_texcoord).rgb, 1.0) * alpha;\n"
+"}\n";
+
+const GLchar tex_fragment_src_external[] =
+"#extension GL_OES_EGL_image_external : require\n\n"
+"precision mediump float;\n"
+"varying vec2 v_texcoord;\n"
+"uniform samplerExternalOES texture0;\n"
+"uniform float alpha;\n"
+"\n"
+"void main() {\n"
+" gl_FragColor = texture2D(texture0, v_texcoord) * alpha;\n"
+"}\n";
diff --git a/render/gles2/texture.c b/render/gles2/texture.c
new file mode 100644
index 00000000..d035841e
--- /dev/null
+++ b/render/gles2/texture.c
@@ -0,0 +1,304 @@
+#include <assert.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <wayland-server-protocol.h>
+#include <wayland-util.h>
+#include <wlr/render/wlr_texture.h>
+#include <wlr/render/egl.h>
+#include <wlr/render/interface.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/util/log.h>
+#include "glapi.h"
+#include "render/gles2.h"
+#include "util/signal.h"
+
+static const struct wlr_texture_impl texture_impl;
+
+struct wlr_gles2_texture *gles2_get_texture(
+ struct wlr_texture *wlr_texture) {
+ assert(wlr_texture->impl == &texture_impl);
+ return (struct wlr_gles2_texture *)wlr_texture;
+}
+
+struct wlr_gles2_texture *get_gles2_texture_in_context(
+ struct wlr_texture *wlr_texture) {
+ struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture);
+ if (!wlr_egl_is_current(texture->egl)) {
+ wlr_egl_make_current(texture->egl, EGL_NO_SURFACE, NULL);
+ }
+ return texture;
+}
+
+static void gles2_texture_get_size(struct wlr_texture *wlr_texture, int *width,
+ int *height) {
+ struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture);
+ *width = texture->width;
+ *height = texture->height;
+}
+
+static bool gles2_texture_is_opaque(struct wlr_texture *wlr_texture) {
+ struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture);
+ return !texture->has_alpha;
+}
+
+static bool gles2_texture_write_pixels(struct wlr_texture *wlr_texture,
+ 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,
+ const void *data) {
+ struct wlr_gles2_texture *texture =
+ get_gles2_texture_in_context(wlr_texture);
+
+ if (texture->type != WLR_GLES2_TEXTURE_GLTEX) {
+ wlr_log(WLR_ERROR, "Cannot write pixels to immutable texture");
+ return false;
+ }
+
+ const struct wlr_gles2_pixel_format *fmt =
+ get_gles2_format_from_wl(texture->wl_format);
+ assert(fmt);
+
+ // TODO: what if the unpack subimage extension isn't supported?
+ PUSH_GLES2_DEBUG;
+
+ glBindTexture(GL_TEXTURE_2D, texture->gl_tex);
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / (fmt->bpp / 8));
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, src_x);
+ glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, src_y);
+
+ glTexSubImage2D(GL_TEXTURE_2D, 0, dst_x, dst_y, 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);
+
+ POP_GLES2_DEBUG;
+ return true;
+}
+
+static bool gles2_texture_to_dmabuf(struct wlr_texture *wlr_texture,
+ struct wlr_dmabuf_attributes *attribs) {
+ struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture);
+
+ if (!texture->image) {
+ assert(texture->type == WLR_GLES2_TEXTURE_GLTEX);
+
+ if (!eglCreateImageKHR) {
+ return false;
+ }
+
+ texture->image = eglCreateImageKHR(texture->egl->display,
+ texture->egl->context, EGL_GL_TEXTURE_2D_KHR,
+ (EGLClientBuffer)(uintptr_t)texture->gl_tex, NULL);
+ if (texture->image == EGL_NO_IMAGE_KHR) {
+ return false;
+ }
+ }
+
+ uint32_t flags = 0;
+ if (texture->inverted_y) {
+ flags |= WLR_DMABUF_ATTRIBUTES_FLAGS_Y_INVERT;
+ }
+
+ return wlr_egl_export_image_to_dmabuf(texture->egl, texture->image,
+ texture->width, texture->height, flags, attribs);
+}
+
+static void gles2_texture_destroy(struct wlr_texture *wlr_texture) {
+ if (wlr_texture == NULL) {
+ return;
+ }
+
+ struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture);
+
+ wlr_egl_make_current(texture->egl, EGL_NO_SURFACE, NULL);
+
+ PUSH_GLES2_DEBUG;
+
+ if (texture->image_tex) {
+ glDeleteTextures(1, &texture->image_tex);
+ }
+ wlr_egl_destroy_image(texture->egl, texture->image);
+
+ if (texture->type == WLR_GLES2_TEXTURE_GLTEX) {
+ glDeleteTextures(1, &texture->gl_tex);
+ }
+
+ POP_GLES2_DEBUG;
+
+ free(texture);
+}
+
+static const struct wlr_texture_impl texture_impl = {
+ .get_size = gles2_texture_get_size,
+ .is_opaque = gles2_texture_is_opaque,
+ .write_pixels = gles2_texture_write_pixels,
+ .to_dmabuf = gles2_texture_to_dmabuf,
+ .destroy = gles2_texture_destroy,
+};
+
+struct wlr_texture *wlr_gles2_texture_from_pixels(struct wlr_egl *egl,
+ enum wl_shm_format wl_fmt, uint32_t stride, uint32_t width,
+ uint32_t height, const void *data) {
+ if (!wlr_egl_is_current(egl)) {
+ wlr_egl_make_current(egl, EGL_NO_SURFACE, NULL);
+ }
+
+ const struct wlr_gles2_pixel_format *fmt = get_gles2_format_from_wl(wl_fmt);
+ if (fmt == NULL) {
+ wlr_log(WLR_ERROR, "Unsupported pixel format %"PRIu32, wl_fmt);
+ return NULL;
+ }
+
+ struct wlr_gles2_texture *texture =
+ calloc(1, sizeof(struct wlr_gles2_texture));
+ if (texture == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed");
+ return NULL;
+ }
+ wlr_texture_init(&texture->wlr_texture, &texture_impl);
+ texture->egl = egl;
+ texture->width = width;
+ texture->height = height;
+ texture->type = WLR_GLES2_TEXTURE_GLTEX;
+ texture->has_alpha = fmt->has_alpha;
+ texture->wl_format = fmt->wl_format;
+
+ PUSH_GLES2_DEBUG;
+
+ glGenTextures(1, &texture->gl_tex);
+ glBindTexture(GL_TEXTURE_2D, texture->gl_tex);
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / (fmt->bpp / 8));
+ glTexImage2D(GL_TEXTURE_2D, 0, fmt->gl_format, width, height, 0,
+ fmt->gl_format, fmt->gl_type, data);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
+
+ POP_GLES2_DEBUG;
+ return &texture->wlr_texture;
+}
+
+struct wlr_texture *wlr_gles2_texture_from_wl_drm(struct wlr_egl *egl,
+ struct wl_resource *data) {
+ if (!wlr_egl_is_current(egl)) {
+ wlr_egl_make_current(egl, EGL_NO_SURFACE, NULL);
+ }
+
+ if (!glEGLImageTargetTexture2DOES) {
+ return NULL;
+ }
+
+ struct wlr_gles2_texture *texture =
+ calloc(1, sizeof(struct wlr_gles2_texture));
+ if (texture == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed");
+ return NULL;
+ }
+ wlr_texture_init(&texture->wlr_texture, &texture_impl);
+ texture->egl = egl;
+ texture->wl_drm = data;
+
+ EGLint fmt;
+ texture->wl_format = 0xFFFFFFFF; // texture can't be written anyways
+ texture->image = wlr_egl_create_image_from_wl_drm(egl, data, &fmt,
+ &texture->width, &texture->height, &texture->inverted_y);
+ if (texture->image == NULL) {
+ free(texture);
+ return NULL;
+ }
+
+ GLenum target;
+ switch (fmt) {
+ case EGL_TEXTURE_RGB:
+ case EGL_TEXTURE_RGBA:
+ target = GL_TEXTURE_2D;
+ texture->type = WLR_GLES2_TEXTURE_WL_DRM_GL;
+ texture->has_alpha = fmt == EGL_TEXTURE_RGBA;
+ break;
+ case EGL_TEXTURE_EXTERNAL_WL:
+ target = GL_TEXTURE_EXTERNAL_OES;
+ texture->type = WLR_GLES2_TEXTURE_WL_DRM_EXT;
+ texture->has_alpha = true;
+ break;
+ default:
+ wlr_log(WLR_ERROR, "Invalid or unsupported EGL buffer format");
+ free(texture);
+ return NULL;
+ }
+
+ PUSH_GLES2_DEBUG;
+
+ glGenTextures(1, &texture->image_tex);
+ glBindTexture(target, texture->image_tex);
+ glEGLImageTargetTexture2DOES(target, texture->image);
+
+ POP_GLES2_DEBUG;
+ return &texture->wlr_texture;
+}
+
+#ifndef DRM_FORMAT_BIG_ENDIAN
+#define DRM_FORMAT_BIG_ENDIAN 0x80000000
+#endif
+
+struct wlr_texture *wlr_gles2_texture_from_dmabuf(struct wlr_egl *egl,
+ struct wlr_dmabuf_attributes *attribs) {
+ if (!wlr_egl_is_current(egl)) {
+ wlr_egl_make_current(egl, EGL_NO_SURFACE, NULL);
+ }
+
+ if (!glEGLImageTargetTexture2DOES) {
+ return NULL;
+ }
+
+ if (!egl->exts.image_dmabuf_import_ext) {
+ wlr_log(WLR_ERROR, "Cannot create DMA-BUF texture: EGL extension "
+ "unavailable");
+ return NULL;
+ }
+
+ switch (attribs->format & ~DRM_FORMAT_BIG_ENDIAN) {
+ case WL_SHM_FORMAT_YUYV:
+ case WL_SHM_FORMAT_YVYU:
+ case WL_SHM_FORMAT_UYVY:
+ case WL_SHM_FORMAT_VYUY:
+ case WL_SHM_FORMAT_AYUV:
+ // TODO: YUV based formats not yet supported, require multiple images
+ return false;
+ default:
+ break;
+ }
+
+ struct wlr_gles2_texture *texture =
+ calloc(1, sizeof(struct wlr_gles2_texture));
+ if (texture == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed");
+ return NULL;
+ }
+ wlr_texture_init(&texture->wlr_texture, &texture_impl);
+ texture->egl = egl;
+ texture->width = attribs->width;
+ texture->height = attribs->height;
+ texture->type = WLR_GLES2_TEXTURE_DMABUF;
+ texture->has_alpha = true;
+ texture->wl_format = 0xFFFFFFFF; // texture can't be written anyways
+ texture->inverted_y =
+ (attribs->flags & WLR_DMABUF_ATTRIBUTES_FLAGS_Y_INVERT) != 0;
+
+ texture->image = wlr_egl_create_image_from_dmabuf(egl, attribs);
+ if (texture->image == NULL) {
+ free(texture);
+ return NULL;
+ }
+
+ PUSH_GLES2_DEBUG;
+
+ glGenTextures(1, &texture->image_tex);
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture->image_tex);
+ glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, texture->image);
+
+ POP_GLES2_DEBUG;
+ return &texture->wlr_texture;
+}
diff --git a/render/gles2/util.c b/render/gles2/util.c
new file mode 100644
index 00000000..3ac777ee
--- /dev/null
+++ b/render/gles2/util.c
@@ -0,0 +1,38 @@
+#include <GLES2/gl2.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wlr/util/log.h>
+#include "render/gles2.h"
+
+const char *gles2_strerror(GLenum err) {
+ switch (err) {
+ case GL_INVALID_ENUM:
+ return "Invalid enum";
+ case GL_INVALID_VALUE:
+ return "Invalid value";
+ case GL_INVALID_OPERATION:
+ return "Invalid operation";
+ case GL_OUT_OF_MEMORY:
+ return "Out of memory";
+ case GL_INVALID_FRAMEBUFFER_OPERATION:
+ return "Invalid framebuffer operation";
+ default:
+ return "Unknown error";
+ }
+}
+
+bool _gles2_flush_errors(const char *file, int line) {
+ GLenum err;
+ bool failure = false;
+ while ((err = glGetError()) != GL_NO_ERROR) {
+ failure = true;
+ if (err == GL_OUT_OF_MEMORY) {
+ // The OpenGL context is now undefined
+ _wlr_log(WLR_ERROR, "[%s:%d] Fatal GL error: out of memory", file, line);
+ exit(1);
+ } else {
+ _wlr_log(WLR_ERROR, "[%s:%d] GL error %d %s", file, line, err, gles2_strerror(err));
+ }
+ }
+ return failure;
+}
diff --git a/render/meson.build b/render/meson.build
new file mode 100644
index 00000000..e45ea90b
--- /dev/null
+++ b/render/meson.build
@@ -0,0 +1,37 @@
+glgen = find_program('../glgen.sh')
+
+glapi = custom_target(
+ 'glapi',
+ input: 'glapi.txt',
+ output: ['@BASENAME@.c', '@BASENAME@.h'],
+ command: [glgen, '@INPUT@', '@OUTDIR@'],
+)
+
+lib_wlr_render = static_library(
+ 'wlr_render',
+ files(
+ 'dmabuf.c',
+ 'egl.c',
+ 'gles2/pixel_format.c',
+ 'gles2/renderer.c',
+ 'gles2/shaders.c',
+ 'gles2/texture.c',
+ 'gles2/util.c',
+ 'wlr_renderer.c',
+ 'wlr_texture.c',
+ ),
+ glapi,
+ include_directories: wlr_inc,
+ dependencies: [
+ egl,
+ drm.partial_dependency(compile_args: true), # <drm_fourcc.h>
+ glesv2,
+ pixman,
+ wayland_server
+ ],
+)
+
+wlr_render = declare_dependency(
+ link_with: lib_wlr_render,
+ sources: glapi[1],
+)
diff --git a/render/wlr_renderer.c b/render/wlr_renderer.c
new file mode 100644
index 00000000..58731d7f
--- /dev/null
+++ b/render/wlr_renderer.c
@@ -0,0 +1,226 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wlr/render/gles2.h>
+#include <wlr/render/interface.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_linux_dmabuf_v1.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/util/log.h>
+#include "util/signal.h"
+
+void wlr_renderer_init(struct wlr_renderer *renderer,
+ const struct wlr_renderer_impl *impl) {
+ assert(impl->begin);
+ assert(impl->clear);
+ assert(impl->scissor);
+ assert(impl->render_texture_with_matrix);
+ assert(impl->render_quad_with_matrix);
+ assert(impl->render_ellipse_with_matrix);
+ assert(impl->formats);
+ assert(impl->format_supported);
+ assert(impl->texture_from_pixels);
+ renderer->impl = impl;
+
+ wl_signal_init(&renderer->events.destroy);
+}
+
+void wlr_renderer_destroy(struct wlr_renderer *r) {
+ if (!r) {
+ return;
+ }
+ wlr_signal_emit_safe(&r->events.destroy, r);
+
+ if (r->impl && r->impl->destroy) {
+ r->impl->destroy(r);
+ } else {
+ free(r);
+ }
+}
+
+void wlr_renderer_begin(struct wlr_renderer *r, int width, int height) {
+ r->impl->begin(r, width, height);
+}
+
+void wlr_renderer_end(struct wlr_renderer *r) {
+ if (r->impl->end) {
+ r->impl->end(r);
+ }
+}
+
+void wlr_renderer_clear(struct wlr_renderer *r, const float color[static 4]) {
+ r->impl->clear(r, color);
+}
+
+void wlr_renderer_scissor(struct wlr_renderer *r, struct wlr_box *box) {
+ r->impl->scissor(r, box);
+}
+
+bool wlr_render_texture(struct wlr_renderer *r, struct wlr_texture *texture,
+ const float projection[static 9], int x, int y, float alpha) {
+ struct wlr_box box = { .x = x, .y = y };
+ wlr_texture_get_size(texture, &box.width, &box.height);
+
+ float matrix[9];
+ wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0,
+ projection);
+
+ return wlr_render_texture_with_matrix(r, texture, matrix, alpha);
+}
+
+bool wlr_render_texture_with_matrix(struct wlr_renderer *r,
+ struct wlr_texture *texture, const float matrix[static 9],
+ float alpha) {
+ return r->impl->render_texture_with_matrix(r, texture, matrix, alpha);
+}
+
+void wlr_render_rect(struct wlr_renderer *r, const struct wlr_box *box,
+ const float color[static 4], const float projection[static 9]) {
+ float matrix[9];
+ wlr_matrix_project_box(matrix, box, WL_OUTPUT_TRANSFORM_NORMAL, 0,
+ projection);
+
+ wlr_render_quad_with_matrix(r, color, matrix);
+}
+
+void wlr_render_quad_with_matrix(struct wlr_renderer *r,
+ const float color[static 4], const float matrix[static 9]) {
+ r->impl->render_quad_with_matrix(r, color, matrix);
+}
+
+void wlr_render_ellipse(struct wlr_renderer *r, const struct wlr_box *box,
+ const float color[static 4], const float projection[static 9]) {
+ float matrix[9];
+ wlr_matrix_project_box(matrix, box, WL_OUTPUT_TRANSFORM_NORMAL, 0,
+ projection);
+
+ wlr_render_ellipse_with_matrix(r, color, matrix);
+}
+
+void wlr_render_ellipse_with_matrix(struct wlr_renderer *r,
+ const float color[static 4], const float matrix[static 9]) {
+ r->impl->render_ellipse_with_matrix(r, color, matrix);
+}
+
+const enum wl_shm_format *wlr_renderer_get_formats(
+ struct wlr_renderer *r, size_t *len) {
+ return r->impl->formats(r, len);
+}
+
+bool wlr_renderer_resource_is_wl_drm_buffer(struct wlr_renderer *r,
+ struct wl_resource *resource) {
+ if (!r->impl->resource_is_wl_drm_buffer) {
+ return false;
+ }
+ return r->impl->resource_is_wl_drm_buffer(r, resource);
+}
+
+void wlr_renderer_wl_drm_buffer_get_size(struct wlr_renderer *r,
+ struct wl_resource *buffer, int *width, int *height) {
+ if (!r->impl->wl_drm_buffer_get_size) {
+ return;
+ }
+ return r->impl->wl_drm_buffer_get_size(r, buffer, width, height);
+}
+
+int wlr_renderer_get_dmabuf_formats(struct wlr_renderer *r,
+ int **formats) {
+ if (!r->impl->get_dmabuf_formats) {
+ return -1;
+ }
+ return r->impl->get_dmabuf_formats(r, formats);
+}
+
+int wlr_renderer_get_dmabuf_modifiers(struct wlr_renderer *r, int format,
+ uint64_t **modifiers) {
+ if (!r->impl->get_dmabuf_modifiers) {
+ return -1;
+ }
+ return r->impl->get_dmabuf_modifiers(r, format, modifiers);
+}
+
+bool wlr_renderer_read_pixels(struct wlr_renderer *r, enum wl_shm_format fmt,
+ uint32_t *flags, 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) {
+ if (!r->impl->read_pixels) {
+ return false;
+ }
+ return r->impl->read_pixels(r, fmt, flags, stride, width, height,
+ src_x, src_y, dst_x, dst_y, data);
+}
+
+bool wlr_renderer_format_supported(struct wlr_renderer *r,
+ enum wl_shm_format fmt) {
+ return r->impl->format_supported(r, fmt);
+}
+
+void wlr_renderer_init_wl_display(struct wlr_renderer *r,
+ struct wl_display *wl_display) {
+ if (wl_display_init_shm(wl_display)) {
+ wlr_log(WLR_ERROR, "Failed to initialize shm");
+ return;
+ }
+
+ size_t len;
+ const enum wl_shm_format *formats = wlr_renderer_get_formats(r, &len);
+ if (formats == NULL) {
+ wlr_log(WLR_ERROR, "Failed to initialize shm: cannot get formats");
+ return;
+ }
+
+ for (size_t i = 0; i < len; ++i) {
+ // These formats are already added by default
+ if (formats[i] != WL_SHM_FORMAT_ARGB8888 &&
+ formats[i] != WL_SHM_FORMAT_XRGB8888) {
+ wl_display_add_shm_format(wl_display, formats[i]);
+ }
+ }
+
+ if (r->impl->texture_from_dmabuf) {
+ wlr_linux_dmabuf_v1_create(wl_display, r);
+ }
+
+ if (r->impl->init_wl_display) {
+ r->impl->init_wl_display(r, wl_display);
+ }
+}
+
+struct wlr_renderer *wlr_renderer_autocreate(struct wlr_egl *egl,
+ EGLenum platform, void *remote_display, EGLint *config_attribs,
+ EGLint visual_id) {
+ // Append GLES2-specific bits to the provided EGL config attributes
+ EGLint gles2_config_attribs[] = {
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_NONE,
+ };
+
+ size_t config_attribs_len = 0; // not including terminating EGL_NONE
+ while (config_attribs != NULL &&
+ config_attribs[config_attribs_len] != EGL_NONE) {
+ ++config_attribs_len;
+ }
+
+ size_t all_config_attribs_len = config_attribs_len +
+ sizeof(gles2_config_attribs) / sizeof(gles2_config_attribs[0]);
+ EGLint all_config_attribs[all_config_attribs_len];
+ if (config_attribs_len > 0) {
+ memcpy(all_config_attribs, config_attribs,
+ config_attribs_len * sizeof(EGLint));
+ }
+ memcpy(&all_config_attribs[config_attribs_len], gles2_config_attribs,
+ sizeof(gles2_config_attribs));
+
+ if (!wlr_egl_init(egl, platform, remote_display, all_config_attribs,
+ visual_id)) {
+ wlr_log(WLR_ERROR, "Could not initialize EGL");
+ return NULL;
+ }
+
+ struct wlr_renderer *renderer = wlr_gles2_renderer_create(egl);
+ if (!renderer) {
+ wlr_egl_finish(egl);
+ }
+
+ return renderer;
+}
diff --git a/render/wlr_texture.c b/render/wlr_texture.c
new file mode 100644
index 00000000..833032c9
--- /dev/null
+++ b/render/wlr_texture.c
@@ -0,0 +1,71 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wlr/render/interface.h>
+#include <wlr/render/wlr_texture.h>
+
+void wlr_texture_init(struct wlr_texture *texture,
+ const struct wlr_texture_impl *impl) {
+ assert(impl->get_size);
+ assert(impl->write_pixels);
+ texture->impl = impl;
+}
+
+void wlr_texture_destroy(struct wlr_texture *texture) {
+ if (texture && texture->impl && texture->impl->destroy) {
+ texture->impl->destroy(texture);
+ } else {
+ free(texture);
+ }
+}
+
+struct wlr_texture *wlr_texture_from_pixels(struct wlr_renderer *renderer,
+ enum wl_shm_format wl_fmt, uint32_t stride, uint32_t width,
+ uint32_t height, const void *data) {
+ return renderer->impl->texture_from_pixels(renderer, wl_fmt, stride, width,
+ height, data);
+}
+
+struct wlr_texture *wlr_texture_from_wl_drm(struct wlr_renderer *renderer,
+ struct wl_resource *data) {
+ if (!renderer->impl->texture_from_wl_drm) {
+ return NULL;
+ }
+ return renderer->impl->texture_from_wl_drm(renderer, data);
+}
+
+struct wlr_texture *wlr_texture_from_dmabuf(struct wlr_renderer *renderer,
+ struct wlr_dmabuf_attributes *attribs) {
+ if (!renderer->impl->texture_from_dmabuf) {
+ return NULL;
+ }
+ return renderer->impl->texture_from_dmabuf(renderer, attribs);
+}
+
+void wlr_texture_get_size(struct wlr_texture *texture, int *width,
+ int *height) {
+ return texture->impl->get_size(texture, width, height);
+}
+
+bool wlr_texture_is_opaque(struct wlr_texture *texture) {
+ if (!texture->impl->is_opaque) {
+ return false;
+ }
+ return texture->impl->is_opaque(texture);
+}
+
+bool wlr_texture_write_pixels(struct wlr_texture *texture,
+ 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,
+ const void *data) {
+ return texture->impl->write_pixels(texture, stride, width, height,
+ src_x, src_y, dst_x, dst_y, data);
+}
+
+bool wlr_texture_to_dmabuf(struct wlr_texture *texture,
+ struct wlr_dmabuf_attributes *attribs) {
+ if (!texture->impl->to_dmabuf) {
+ return false;
+ }
+ return texture->impl->to_dmabuf(texture, attribs);
+}
diff --git a/rootston/bindings.c b/rootston/bindings.c
new file mode 100644
index 00000000..9fdbb33b
--- /dev/null
+++ b/rootston/bindings.c
@@ -0,0 +1,107 @@
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <wlr/util/log.h>
+#include "rootston/bindings.h"
+
+static bool outputs_enabled = true;
+
+static const char *exec_prefix = "exec ";
+
+static void double_fork_shell_cmd(const char *shell_cmd) {
+ pid_t pid = fork();
+ if (pid < 0) {
+ wlr_log(WLR_ERROR, "cannot execute binding command: fork() failed");
+ return;
+ }
+
+ if (pid == 0) {
+ pid = fork();
+ if (pid == 0) {
+ execl("/bin/sh", "/bin/sh", "-c", shell_cmd, NULL);
+ _exit(EXIT_FAILURE);
+ } else {
+ _exit(pid == -1);
+ }
+ }
+
+ int status;
+ while (waitpid(pid, &status, 0) < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ wlr_log_errno(WLR_ERROR, "waitpid() on first child failed");
+ return;
+ }
+
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+ return;
+ }
+
+ wlr_log(WLR_ERROR, "first child failed to fork command");
+}
+
+void execute_binding_command(struct roots_seat *seat,
+ struct roots_input *input, const char *command) {
+ if (strcmp(command, "exit") == 0) {
+ wl_display_terminate(input->server->wl_display);
+ } else if (strcmp(command, "close") == 0) {
+ struct roots_view *focus = roots_seat_get_focus(seat);
+ if (focus != NULL) {
+ view_close(focus);
+ }
+ } else if (strcmp(command, "fullscreen") == 0) {
+ struct roots_view *focus = roots_seat_get_focus(seat);
+ if (focus != NULL) {
+ bool is_fullscreen = focus->fullscreen_output != NULL;
+ view_set_fullscreen(focus, !is_fullscreen, NULL);
+ }
+ } else if (strcmp(command, "next_window") == 0) {
+ roots_seat_cycle_focus(seat);
+ } else if (strcmp(command, "alpha") == 0) {
+ struct roots_view *focus = roots_seat_get_focus(seat);
+ if (focus != NULL) {
+ view_cycle_alpha(focus);
+ }
+ } else if (strncmp(exec_prefix, command, strlen(exec_prefix)) == 0) {
+ const char *shell_cmd = command + strlen(exec_prefix);
+ double_fork_shell_cmd(shell_cmd);
+ } else if (strcmp(command, "maximize") == 0) {
+ struct roots_view *focus = roots_seat_get_focus(seat);
+ if (focus != NULL) {
+ view_maximize(focus, !focus->maximized);
+ }
+ } else if (strcmp(command, "nop") == 0) {
+ wlr_log(WLR_DEBUG, "nop command");
+ } else if (strcmp(command, "toggle_outputs") == 0) {
+ outputs_enabled = !outputs_enabled;
+ struct roots_output *output;
+ wl_list_for_each(output, &input->server->desktop->outputs, link) {
+ wlr_output_enable(output->wlr_output, outputs_enabled);
+ }
+ } else if (strcmp(command, "toggle_decoration_mode") == 0) {
+ struct roots_view *focus = roots_seat_get_focus(seat);
+ if (focus != NULL && focus->type == ROOTS_XDG_SHELL_VIEW) {
+ struct roots_xdg_toplevel_decoration *decoration =
+ focus->roots_xdg_surface->xdg_toplevel_decoration;
+ if (decoration != NULL) {
+ enum wlr_xdg_toplevel_decoration_v1_mode mode =
+ decoration->wlr_decoration->current_mode;
+ mode = mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE
+ ? WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE
+ : WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
+ wlr_xdg_toplevel_decoration_v1_set_mode(
+ decoration->wlr_decoration, mode);
+ }
+ }
+ } else if (strcmp(command, "break_pointer_constraint") == 0) {
+ struct wl_list *list = &input->seats;
+ struct roots_seat *seat;
+ wl_list_for_each(seat, list, link) {
+ roots_cursor_constrain(seat->cursor, NULL, NAN, NAN);
+ }
+ } else {
+ wlr_log(WLR_ERROR, "unknown binding command: %s", command);
+ }
+}
diff --git a/rootston/config.c b/rootston/config.c
new file mode 100644
index 00000000..198aaba6
--- /dev/null
+++ b/rootston/config.c
@@ -0,0 +1,670 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#include <assert.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/util/log.h>
+#include "rootston/config.h"
+#include "rootston/ini.h"
+#include "rootston/input.h"
+#include "rootston/keyboard.h"
+
+static void usage(const char *name, int ret) {
+ fprintf(stderr,
+ "usage: %s [-C <FILE>] [-E <COMMAND>]\n"
+ "\n"
+ " -C <FILE> Path to the configuration file\n"
+ " (default: rootston.ini).\n"
+ " See `rootston.ini.example` for config\n"
+ " file documentation.\n"
+ " -E <COMMAND> Command that will be ran at startup.\n"
+ " -D Enable damage tracking debugging.\n"
+ " -l <LEVEL> Set log verbosity, where,\n"
+ " 0:SILENT, 1:ERROR, 2:INFO, 3+:DEBUG\n"
+ " (default: DEBUG)\n",
+ name);
+
+ exit(ret);
+}
+
+static struct wlr_box *parse_geometry(const char *str) {
+ // format: {width}x{height}+{x}+{y}
+ if (strlen(str) > 255) {
+ wlr_log(WLR_ERROR, "cannot parse geometry string, too long");
+ return NULL;
+ }
+
+ char *buf = strdup(str);
+ struct wlr_box *box = calloc(1, sizeof(struct wlr_box));
+
+ bool has_width = false;
+ bool has_height = false;
+ bool has_x = false;
+ bool has_y = false;
+
+ char *pch = strtok(buf, "x+");
+ while (pch != NULL) {
+ errno = 0;
+ char *endptr;
+ long val = strtol(pch, &endptr, 0);
+
+ if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) ||
+ (errno != 0 && val == 0)) {
+ goto invalid_input;
+ }
+
+ if (endptr == pch) {
+ goto invalid_input;
+ }
+
+ if (!has_width) {
+ box->width = val;
+ has_width = true;
+ } else if (!has_height) {
+ box->height = val;
+ has_height = true;
+ } else if (!has_x) {
+ box->x = val;
+ has_x = true;
+ } else if (!has_y) {
+ box->y = val;
+ has_y = true;
+ } else {
+ break;
+ }
+ pch = strtok(NULL, "x+");
+ }
+
+ if (!has_width || !has_height) {
+ goto invalid_input;
+ }
+
+ free(buf);
+ return box;
+
+invalid_input:
+ wlr_log(WLR_ERROR, "could not parse geometry string: %s", str);
+ free(buf);
+ free(box);
+ return NULL;
+}
+
+static uint32_t parse_modifier(const char *symname) {
+ if (strcmp(symname, "Shift") == 0) {
+ return WLR_MODIFIER_SHIFT;
+ } else if (strcmp(symname, "Caps") == 0) {
+ return WLR_MODIFIER_CAPS;
+ } else if (strcmp(symname, "Ctrl") == 0) {
+ return WLR_MODIFIER_CTRL;
+ } else if (strcmp(symname, "Alt") == 0) {
+ return WLR_MODIFIER_ALT;
+ } else if (strcmp(symname, "Mod2") == 0) {
+ return WLR_MODIFIER_MOD2;
+ } else if (strcmp(symname, "Mod3") == 0) {
+ return WLR_MODIFIER_MOD3;
+ } else if (strcmp(symname, "Logo") == 0) {
+ return WLR_MODIFIER_LOGO;
+ } else if (strcmp(symname, "Mod5") == 0) {
+ return WLR_MODIFIER_MOD5;
+ } else {
+ return 0;
+ }
+}
+
+static bool parse_modeline(const char *s, drmModeModeInfo *mode) {
+ char hsync[16];
+ char vsync[16];
+ float fclock;
+
+ mode->type = DRM_MODE_TYPE_USERDEF;
+
+ if (sscanf(s, "%f %hd %hd %hd %hd %hd %hd %hd %hd %15s %15s",
+ &fclock,
+ &mode->hdisplay,
+ &mode->hsync_start,
+ &mode->hsync_end,
+ &mode->htotal,
+ &mode->vdisplay,
+ &mode->vsync_start,
+ &mode->vsync_end,
+ &mode->vtotal, hsync, vsync) != 11) {
+ return false;
+ }
+
+ mode->clock = fclock * 1000;
+ mode->vrefresh = mode->clock * 1000.0 * 1000.0
+ / mode->htotal / mode->vtotal;
+ if (strcasecmp(hsync, "+hsync") == 0) {
+ mode->flags |= DRM_MODE_FLAG_PHSYNC;
+ } else if (strcasecmp(hsync, "-hsync") == 0) {
+ mode->flags |= DRM_MODE_FLAG_NHSYNC;
+ } else {
+ return false;
+ }
+
+ if (strcasecmp(vsync, "+vsync") == 0) {
+ mode->flags |= DRM_MODE_FLAG_PVSYNC;
+ } else if (strcasecmp(vsync, "-vsync") == 0) {
+ mode->flags |= DRM_MODE_FLAG_NVSYNC;
+ } else {
+ return false;
+ }
+
+ snprintf(mode->name, sizeof(mode->name), "%dx%d@%d",
+ mode->hdisplay, mode->vdisplay, mode->vrefresh / 1000);
+
+ return true;
+}
+
+void add_binding_config(struct wl_list *bindings, const char* combination,
+ const char* command) {
+ struct roots_binding_config *bc =
+ calloc(1, sizeof(struct roots_binding_config));
+
+ xkb_keysym_t keysyms[ROOTS_KEYBOARD_PRESSED_KEYSYMS_CAP];
+ char *symnames = strdup(combination);
+ char *symname = strtok(symnames, "+");
+ while (symname) {
+ uint32_t modifier = parse_modifier(symname);
+ if (modifier != 0) {
+ bc->modifiers |= modifier;
+ } else {
+ xkb_keysym_t sym = xkb_keysym_from_name(symname,
+ XKB_KEYSYM_NO_FLAGS);
+ if (sym == XKB_KEY_NoSymbol) {
+ wlr_log(WLR_ERROR, "got unknown key binding symbol: %s",
+ symname);
+ free(bc);
+ bc = NULL;
+ break;
+ }
+ keysyms[bc->keysyms_len] = sym;
+ bc->keysyms_len++;
+ }
+ symname = strtok(NULL, "+");
+ }
+ free(symnames);
+
+ if (bc) {
+ wl_list_insert(bindings, &bc->link);
+ bc->command = strdup(command);
+ bc->keysyms = malloc(bc->keysyms_len * sizeof(xkb_keysym_t));
+ memcpy(bc->keysyms, keysyms, bc->keysyms_len * sizeof(xkb_keysym_t));
+ }
+}
+
+void add_switch_config(struct wl_list *switches, const char *switch_name, const char *action,
+ const char* command) {
+ struct roots_switch_config *sc = calloc(1, sizeof(struct roots_switch_config));
+
+ if (strcmp(switch_name, "tablet") == 0) {
+ sc->switch_type = WLR_SWITCH_TYPE_TABLET_MODE;
+ } else if (strcmp(switch_name, "lid") == 0) {
+ sc->switch_type = WLR_SWITCH_TYPE_LID;
+ } else {
+ sc->switch_type = -1;
+ sc->name = strdup(switch_name);
+ }
+ if (strcmp(action, "on") == 0) {
+ sc->switch_state = WLR_SWITCH_STATE_ON;
+ } else if (strcmp(action, "off") == 0) {
+ sc->switch_state = WLR_SWITCH_STATE_OFF;
+ } else if (strcmp(action, "toggle") == 0) {
+ sc->switch_state = WLR_SWITCH_STATE_TOGGLE;
+ } else {
+ wlr_log(WLR_ERROR, "Invalid switch action %s/n for switch %s:%s",
+ action, switch_name, action);
+ return;
+ }
+ sc->command = strdup(command);
+ wl_list_insert(switches, &sc->link);
+}
+
+static void config_handle_cursor(struct roots_config *config,
+ const char *seat_name, const char *name, const char *value) {
+ struct roots_cursor_config *cc;
+ bool found = false;
+ wl_list_for_each(cc, &config->cursors, link) {
+ if (strcmp(cc->seat, seat_name) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ cc = calloc(1, sizeof(struct roots_cursor_config));
+ cc->seat = strdup(seat_name);
+ wl_list_insert(&config->cursors, &cc->link);
+ }
+
+ if (strcmp(name, "map-to-output") == 0) {
+ free(cc->mapped_output);
+ cc->mapped_output = strdup(value);
+ } else if (strcmp(name, "geometry") == 0) {
+ free(cc->mapped_box);
+ cc->mapped_box = parse_geometry(value);
+ } else if (strcmp(name, "theme") == 0) {
+ free(cc->theme);
+ cc->theme = strdup(value);
+ } else if (strcmp(name, "default-image") == 0) {
+ free(cc->default_image);
+ cc->default_image = strdup(value);
+ } else {
+ wlr_log(WLR_ERROR, "got unknown cursor config: %s", name);
+ }
+}
+
+static void config_handle_keyboard(struct roots_config *config,
+ const char *device_name, const char *name, const char *value) {
+ struct roots_keyboard_config *kc;
+ bool found = false;
+ wl_list_for_each(kc, &config->keyboards, link) {
+ if (strcmp(kc->name, device_name) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ kc = calloc(1, sizeof(struct roots_keyboard_config));
+ kc->name = strdup(device_name);
+ wl_list_insert(&config->keyboards, &kc->link);
+ }
+
+ if (strcmp(name, "meta-key") == 0) {
+ kc->meta_key = parse_modifier(value);
+ if (kc->meta_key == 0) {
+ wlr_log(WLR_ERROR, "got unknown meta key: %s", name);
+ }
+ } else if (strcmp(name, "rules") == 0) {
+ kc->rules = strdup(value);
+ } else if (strcmp(name, "model") == 0) {
+ kc->model = strdup(value);
+ } else if (strcmp(name, "layout") == 0) {
+ kc->layout = strdup(value);
+ } else if (strcmp(name, "variant") == 0) {
+ kc->variant = strdup(value);
+ } else if (strcmp(name, "options") == 0) {
+ kc->options = strdup(value);
+ } else if (strcmp(name, "repeat-rate") == 0) {
+ kc->repeat_rate = strtol(value, NULL, 10);
+ } else if (strcmp(name, "repeat-delay") == 0) {
+ kc->repeat_delay = strtol(value, NULL, 10);
+ } else {
+ wlr_log(WLR_ERROR, "got unknown keyboard config: %s", name);
+ }
+}
+
+static const char *output_prefix = "output:";
+static const char *device_prefix = "device:";
+static const char *keyboard_prefix = "keyboard:";
+static const char *cursor_prefix = "cursor:";
+static const char *switch_prefix = "switch:";
+
+static int config_ini_handler(void *user, const char *section, const char *name,
+ const char *value) {
+ struct roots_config *config = user;
+ if (strcmp(section, "core") == 0) {
+ if (strcmp(name, "xwayland") == 0) {
+ if (strcasecmp(value, "true") == 0) {
+ config->xwayland = true;
+ } else if (strcasecmp(value, "immediate") == 0) {
+ config->xwayland = true;
+ config->xwayland_lazy = false;
+ } else if (strcasecmp(value, "false") == 0) {
+ config->xwayland = false;
+ } else {
+ wlr_log(WLR_ERROR, "got unknown xwayland value: %s", value);
+ }
+ } else {
+ wlr_log(WLR_ERROR, "got unknown core config: %s", name);
+ }
+ } else if (strncmp(output_prefix, section, strlen(output_prefix)) == 0) {
+ const char *output_name = section + strlen(output_prefix);
+ struct roots_output_config *oc;
+ bool found = false;
+
+ wl_list_for_each(oc, &config->outputs, link) {
+ if (strcmp(oc->name, output_name) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ oc = calloc(1, sizeof(struct roots_output_config));
+ oc->name = strdup(output_name);
+ oc->transform = WL_OUTPUT_TRANSFORM_NORMAL;
+ oc->scale = 1;
+ oc->enable = true;
+ wl_list_init(&oc->modes);
+ wl_list_insert(&config->outputs, &oc->link);
+ }
+
+ if (strcmp(name, "enable") == 0) {
+ if (strcasecmp(value, "true") == 0) {
+ oc->enable = true;
+ } else if (strcasecmp(value, "false") == 0) {
+ oc->enable = false;
+ } else {
+ wlr_log(WLR_ERROR, "got invalid output enable value: %s", value);
+ }
+ } else if (strcmp(name, "x") == 0) {
+ oc->x = strtol(value, NULL, 10);
+ } else if (strcmp(name, "y") == 0) {
+ oc->y = strtol(value, NULL, 10);
+ } else if (strcmp(name, "scale") == 0) {
+ oc->scale = strtof(value, NULL);
+ assert(oc->scale > 0);
+ } else if (strcmp(name, "rotate") == 0) {
+ if (strcmp(value, "normal") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_NORMAL;
+ } else if (strcmp(value, "90") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_90;
+ } else if (strcmp(value, "180") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_180;
+ } else if (strcmp(value, "270") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_270;
+ } else if (strcmp(value, "flipped") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED;
+ } else if (strcmp(value, "flipped-90") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED_90;
+ } else if (strcmp(value, "flipped-180") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED_180;
+ } else if (strcmp(value, "flipped-270") == 0) {
+ oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED_270;
+ } else {
+ wlr_log(WLR_ERROR, "got unknown transform value: %s", value);
+ }
+ } else if (strcmp(name, "mode") == 0) {
+ char *end;
+ oc->mode.width = strtol(value, &end, 10);
+ assert(*end == 'x');
+ ++end;
+ oc->mode.height = strtol(end, &end, 10);
+ if (*end) {
+ assert(*end == '@');
+ ++end;
+ oc->mode.refresh_rate = strtof(end, &end);
+ assert(strcmp("Hz", end) == 0);
+ }
+ wlr_log(WLR_DEBUG, "Configured output %s with mode %dx%d@%f",
+ oc->name, oc->mode.width, oc->mode.height,
+ oc->mode.refresh_rate);
+ } else if (strcmp(name, "modeline") == 0) {
+ struct roots_output_mode_config *mode = calloc(1, sizeof(*mode));
+
+ if (parse_modeline(value, &mode->info)) {
+ wl_list_insert(&oc->modes, &mode->link);
+ } else {
+ free(mode);
+ wlr_log(WLR_ERROR, "Invalid modeline: %s", value);
+ }
+ }
+ } else if (strncmp(cursor_prefix, section, strlen(cursor_prefix)) == 0) {
+ const char *seat_name = section + strlen(cursor_prefix);
+ config_handle_cursor(config, seat_name, name, value);
+ } else if (strcmp(section, "cursor") == 0) {
+ config_handle_cursor(config, ROOTS_CONFIG_DEFAULT_SEAT_NAME, name,
+ value);
+ } else if (strncmp(device_prefix, section, strlen(device_prefix)) == 0) {
+ const char *device_name = section + strlen(device_prefix);
+
+ struct roots_device_config *dc;
+ bool found = false;
+ wl_list_for_each(dc, &config->devices, link) {
+ if (strcmp(dc->name, device_name) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ dc = calloc(1, sizeof(struct roots_device_config));
+ dc->name = strdup(device_name);
+ dc->seat = strdup(ROOTS_CONFIG_DEFAULT_SEAT_NAME);
+ wl_list_insert(&config->devices, &dc->link);
+ }
+
+ if (strcmp(name, "map-to-output") == 0) {
+ free(dc->mapped_output);
+ dc->mapped_output = strdup(value);
+ } else if (strcmp(name, "geometry") == 0) {
+ free(dc->mapped_box);
+ dc->mapped_box = parse_geometry(value);
+ } else if (strcmp(name, "seat") == 0) {
+ free(dc->seat);
+ dc->seat = strdup(value);
+ } else if (strcmp(name, "tap_enabled") == 0) {
+ if (strcasecmp(value, "true") == 0) {
+ dc->tap_enabled = true;
+ } else if (strcasecmp(value, "false") == 0) {
+ dc->tap_enabled = false;
+ } else {
+ wlr_log(WLR_ERROR,
+ "got unknown tap_enabled value: %s",
+ value);
+ }
+ } else {
+ wlr_log(WLR_ERROR, "got unknown device config: %s", name);
+ }
+ } else if (strcmp(section, "keyboard") == 0) {
+ config_handle_keyboard(config, "", name, value);
+ } else if (strncmp(keyboard_prefix,
+ section, strlen(keyboard_prefix)) == 0) {
+ const char *device_name = section + strlen(keyboard_prefix);
+ config_handle_keyboard(config, device_name, name, value);
+ } else if (strcmp(section, "bindings") == 0) {
+ add_binding_config(&config->bindings, name, value);
+ } else if (strncmp(switch_prefix, section, strlen(switch_prefix)) == 0) {
+ const char *switch_name = section + strlen(switch_prefix);
+ add_switch_config(&config->switches, switch_name, name, value);
+ } else {
+ wlr_log(WLR_ERROR, "got unknown config section: %s", section);
+ }
+
+ return 1;
+}
+
+struct roots_config *roots_config_create_from_args(int argc, char *argv[]) {
+ struct roots_config *config = calloc(1, sizeof(struct roots_config));
+ if (config == NULL) {
+ return NULL;
+ }
+
+ config->xwayland = true;
+ config->xwayland_lazy = true;
+ wl_list_init(&config->outputs);
+ wl_list_init(&config->devices);
+ wl_list_init(&config->keyboards);
+ wl_list_init(&config->cursors);
+ wl_list_init(&config->bindings);
+ wl_list_init(&config->switches);
+
+ int c;
+ unsigned int log_verbosity = WLR_DEBUG;
+ while ((c = getopt(argc, argv, "C:E:hDl:")) != -1) {
+ switch (c) {
+ case 'C':
+ config->config_path = strdup(optarg);
+ break;
+ case 'E':
+ config->startup_cmd = strdup(optarg);
+ break;
+ case 'D':
+ config->debug_damage_tracking = true;
+ break;
+ case 'l':
+ log_verbosity = strtoul(optarg, NULL, 10);
+ if (log_verbosity >= WLR_LOG_IMPORTANCE_LAST) {
+ log_verbosity = WLR_LOG_IMPORTANCE_LAST - 1;
+ }
+ break;
+ case 'h':
+ case '?':
+ usage(argv[0], c != 'h');
+ }
+ }
+ wlr_log_init(log_verbosity, NULL);
+
+ if (!config->config_path) {
+ // get the config path from the current directory
+ char cwd[MAXPATHLEN];
+ if (getcwd(cwd, sizeof(cwd)) != NULL) {
+ char buf[MAXPATHLEN];
+ if (snprintf(buf, MAXPATHLEN, "%s/%s", cwd, "rootston.ini") >= MAXPATHLEN) {
+ wlr_log(WLR_ERROR, "config path too long");
+ exit(1);
+ }
+ config->config_path = strdup(buf);
+ } else {
+ wlr_log(WLR_ERROR, "could not get cwd");
+ exit(1);
+ }
+ }
+
+ int result = ini_parse(config->config_path, config_ini_handler, config);
+
+ if (result == -1) {
+ wlr_log(WLR_DEBUG, "No config file found. Using sensible defaults.");
+ add_binding_config(&config->bindings, "Logo+Shift+E", "exit");
+ add_binding_config(&config->bindings, "Ctrl+q", "close");
+ add_binding_config(&config->bindings, "Alt+Tab", "next_window");
+ add_binding_config(&config->bindings, "Logo+Escape", "break_pointer_constraint");
+ struct roots_keyboard_config *kc =
+ calloc(1, sizeof(struct roots_keyboard_config));
+ kc->meta_key = WLR_MODIFIER_LOGO;
+ kc->name = strdup("");
+ wl_list_insert(&config->keyboards, &kc->link);
+ } else if (result == -2) {
+ wlr_log(WLR_ERROR, "Could not allocate memory to parse config file");
+ exit(1);
+ } else if (result != 0) {
+ wlr_log(WLR_ERROR, "Could not parse config file");
+ exit(1);
+ }
+
+ return config;
+}
+
+void roots_config_destroy(struct roots_config *config) {
+ struct roots_output_config *oc, *otmp = NULL;
+ wl_list_for_each_safe(oc, otmp, &config->outputs, link) {
+ struct roots_output_mode_config *omc, *omctmp = NULL;
+ wl_list_for_each_safe(omc, omctmp, &oc->modes, link) {
+ free(omc);
+ }
+ free(oc->name);
+ free(oc);
+ }
+
+ struct roots_device_config *dc, *dtmp = NULL;
+ wl_list_for_each_safe(dc, dtmp, &config->devices, link) {
+ free(dc->name);
+ free(dc->seat);
+ free(dc->mapped_output);
+ free(dc->mapped_box);
+ free(dc);
+ }
+
+ struct roots_keyboard_config *kc, *ktmp = NULL;
+ wl_list_for_each_safe(kc, ktmp, &config->keyboards, link) {
+ free(kc->name);
+ free(kc->rules);
+ free(kc->model);
+ free(kc->layout);
+ free(kc->variant);
+ free(kc->options);
+ free(kc);
+ }
+
+ struct roots_cursor_config *cc, *ctmp = NULL;
+ wl_list_for_each_safe(cc, ctmp, &config->cursors, link) {
+ free(cc->seat);
+ free(cc->mapped_output);
+ free(cc->mapped_box);
+ free(cc->theme);
+ free(cc->default_image);
+ free(cc);
+ }
+
+ struct roots_binding_config *bc, *btmp = NULL;
+ wl_list_for_each_safe(bc, btmp, &config->bindings, link) {
+ free(bc->keysyms);
+ free(bc->command);
+ free(bc);
+ }
+
+ free(config->config_path);
+ free(config);
+}
+
+struct roots_output_config *roots_config_get_output(struct roots_config *config,
+ struct wlr_output *output) {
+ char name[88];
+ snprintf(name, sizeof(name), "%s %s %s", output->make, output->model,
+ output->serial);
+
+ struct roots_output_config *oc;
+ wl_list_for_each(oc, &config->outputs, link) {
+ if (strcmp(oc->name, output->name) == 0 ||
+ strcmp(oc->name, name) == 0) {
+ return oc;
+ }
+ }
+
+ return NULL;
+}
+
+struct roots_device_config *roots_config_get_device(struct roots_config *config,
+ struct wlr_input_device *device) {
+ struct roots_device_config *d_config;
+ wl_list_for_each(d_config, &config->devices, link) {
+ if (strcmp(d_config->name, device->name) == 0) {
+ return d_config;
+ }
+ }
+
+ return NULL;
+}
+
+struct roots_keyboard_config *roots_config_get_keyboard(
+ struct roots_config *config, struct wlr_input_device *device) {
+ const char *device_name = "";
+ if (device != NULL) {
+ device_name = device->name;
+ }
+
+ struct roots_keyboard_config *kc;
+ wl_list_for_each(kc, &config->keyboards, link) {
+ if (strcmp(kc->name, device_name) == 0) {
+ return kc;
+ }
+ }
+
+ return NULL;
+}
+
+struct roots_cursor_config *roots_config_get_cursor(struct roots_config *config,
+ const char *seat_name) {
+ if (seat_name == NULL) {
+ seat_name = ROOTS_CONFIG_DEFAULT_SEAT_NAME;
+ }
+
+ struct roots_cursor_config *cc;
+ wl_list_for_each(cc, &config->cursors, link) {
+ if (strcmp(cc->seat, seat_name) == 0) {
+ return cc;
+ }
+ }
+
+ return NULL;
+}
diff --git a/rootston/cursor.c b/rootston/cursor.c
new file mode 100644
index 00000000..b9ded30e
--- /dev/null
+++ b/rootston/cursor.c
@@ -0,0 +1,615 @@
+#define _XOPEN_SOURCE 700
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <wlr/types/wlr_region.h>
+#include <wlr/types/wlr_xcursor_manager.h>
+#include <wlr/util/edges.h>
+#include <wlr/util/log.h>
+#include <wlr/util/region.h>
+#ifdef __linux__
+#include <linux/input-event-codes.h>
+#elif __FreeBSD__
+#include <dev/evdev/input-event-codes.h>
+#endif
+#include "rootston/cursor.h"
+#include "rootston/desktop.h"
+#include "rootston/view.h"
+#include "rootston/xcursor.h"
+
+struct roots_cursor *roots_cursor_create(struct roots_seat *seat) {
+ struct roots_cursor *cursor = calloc(1, sizeof(struct roots_cursor));
+ if (!cursor) {
+ return NULL;
+ }
+ cursor->cursor = wlr_cursor_create();
+ if (!cursor->cursor) {
+ free(cursor);
+ return NULL;
+ }
+ cursor->default_xcursor = ROOTS_XCURSOR_DEFAULT;
+ return cursor;
+}
+
+void roots_cursor_destroy(struct roots_cursor *cursor) {
+ // TODO
+}
+
+static void seat_view_deco_motion(struct roots_seat_view *view, double deco_sx, double deco_sy) {
+ struct roots_cursor *cursor = view->seat->cursor;
+
+ double sx = deco_sx;
+ double sy = deco_sy;
+ if (view->has_button_grab) {
+ sx = view->grab_sx;
+ sy = view->grab_sy;
+ }
+
+ enum roots_deco_part parts = view_get_deco_part(view->view, sx, sy);
+
+ bool is_titlebar = (parts & ROOTS_DECO_PART_TITLEBAR);
+ uint32_t edges = 0;
+ if (parts & ROOTS_DECO_PART_LEFT_BORDER) {
+ edges |= WLR_EDGE_LEFT;
+ } else if (parts & ROOTS_DECO_PART_RIGHT_BORDER) {
+ edges |= WLR_EDGE_RIGHT;
+ } else if (parts & ROOTS_DECO_PART_BOTTOM_BORDER) {
+ edges |= WLR_EDGE_BOTTOM;
+ } else if (parts & ROOTS_DECO_PART_TOP_BORDER) {
+ edges |= WLR_EDGE_TOP;
+ }
+
+ if (view->has_button_grab) {
+ if (is_titlebar) {
+ roots_seat_begin_move(view->seat, view->view);
+ } else if (edges) {
+ roots_seat_begin_resize(view->seat, view->view, edges);
+ }
+ view->has_button_grab = false;
+ } else {
+ if (is_titlebar) {
+ wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager,
+ cursor->default_xcursor, cursor->cursor);
+ } else if (edges) {
+ const char *resize_name = wlr_xcursor_get_resize_name(edges);
+ wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager,
+ resize_name, cursor->cursor);
+ }
+ }
+}
+
+static void seat_view_deco_leave(struct roots_seat_view *view) {
+ struct roots_cursor *cursor = view->seat->cursor;
+ wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager,
+ cursor->default_xcursor, cursor->cursor);
+ view->has_button_grab = false;
+}
+
+static void seat_view_deco_button(struct roots_seat_view *view, double sx,
+ double sy, uint32_t button, uint32_t state) {
+ if (button == BTN_LEFT && state == WLR_BUTTON_PRESSED) {
+ view->has_button_grab = true;
+ view->grab_sx = sx;
+ view->grab_sy = sy;
+ } else {
+ view->has_button_grab = false;
+ }
+
+ enum roots_deco_part parts = view_get_deco_part(view->view, sx, sy);
+ if (state == WLR_BUTTON_RELEASED && (parts & ROOTS_DECO_PART_TITLEBAR)) {
+ struct roots_cursor *cursor = view->seat->cursor;
+ wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager,
+ cursor->default_xcursor, cursor->cursor);
+ }
+}
+
+static void roots_passthrough_cursor(struct roots_cursor *cursor,
+ int64_t time) {
+ bool focus_changed;
+ double sx, sy;
+ struct roots_view *view = NULL;
+ struct roots_seat *seat = cursor->seat;
+ struct roots_desktop *desktop = seat->input->server->desktop;
+ struct wlr_surface *surface = desktop_surface_at(desktop,
+ cursor->cursor->x, cursor->cursor->y, &sx, &sy, &view);
+
+ struct wl_client *client = NULL;
+ if (surface) {
+ client = wl_resource_get_client(surface->resource);
+ }
+
+ if (surface && !roots_seat_allow_input(seat, surface->resource)) {
+ return;
+ }
+
+ if (cursor->cursor_client != client) {
+ wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager,
+ cursor->default_xcursor, cursor->cursor);
+ cursor->cursor_client = client;
+ }
+
+ if (view) {
+ struct roots_seat_view *seat_view =
+ roots_seat_view_from_view(seat, view);
+
+ if (cursor->pointer_view &&
+ !cursor->wlr_surface && (surface || seat_view != cursor->pointer_view)) {
+ seat_view_deco_leave(cursor->pointer_view);
+ }
+
+ cursor->pointer_view = seat_view;
+
+ if (!surface) {
+ seat_view_deco_motion(seat_view, sx, sy);
+ }
+ } else {
+ cursor->pointer_view = NULL;
+ }
+
+ cursor->wlr_surface = surface;
+
+ if (surface) {
+ focus_changed = (seat->seat->pointer_state.focused_surface != surface);
+ wlr_seat_pointer_notify_enter(seat->seat, surface, sx, sy);
+ if (!focus_changed && time > 0) {
+ wlr_seat_pointer_notify_motion(seat->seat, time, sx, sy);
+ }
+ } else {
+ wlr_seat_pointer_clear_focus(seat->seat);
+ }
+
+ struct roots_drag_icon *drag_icon;
+ wl_list_for_each(drag_icon, &seat->drag_icons, link) {
+ roots_drag_icon_update_position(drag_icon);
+ }
+}
+
+void roots_cursor_update_focus(struct roots_cursor *cursor) {
+ roots_passthrough_cursor(cursor, -1);
+}
+
+void roots_cursor_update_position(struct roots_cursor *cursor,
+ uint32_t time) {
+ struct roots_seat *seat = cursor->seat;
+ struct roots_view *view;
+ switch (cursor->mode) {
+ case ROOTS_CURSOR_PASSTHROUGH:
+ roots_passthrough_cursor(cursor, time);
+ break;
+ case ROOTS_CURSOR_MOVE:
+ view = roots_seat_get_focus(seat);
+ if (view != NULL) {
+ double dx = cursor->cursor->x - cursor->offs_x;
+ double dy = cursor->cursor->y - cursor->offs_y;
+ view_move(view, cursor->view_x + dx,
+ cursor->view_y + dy);
+ }
+ break;
+ case ROOTS_CURSOR_RESIZE:
+ view = roots_seat_get_focus(seat);
+ if (view != NULL) {
+ double dx = cursor->cursor->x - cursor->offs_x;
+ double dy = cursor->cursor->y - cursor->offs_y;
+ double x = view->box.x;
+ double y = view->box.y;
+ int width = cursor->view_width;
+ int height = cursor->view_height;
+ if (cursor->resize_edges & WLR_EDGE_TOP) {
+ y = cursor->view_y + dy;
+ height -= dy;
+ if (height < 1) {
+ y += height;
+ }
+ } else if (cursor->resize_edges & WLR_EDGE_BOTTOM) {
+ height += dy;
+ }
+ if (cursor->resize_edges & WLR_EDGE_LEFT) {
+ x = cursor->view_x + dx;
+ width -= dx;
+ if (width < 1) {
+ x += width;
+ }
+ } else if (cursor->resize_edges & WLR_EDGE_RIGHT) {
+ width += dx;
+ }
+ view_move_resize(view, x, y,
+ width < 1 ? 1 : width,
+ height < 1 ? 1 : height);
+ }
+ break;
+ case ROOTS_CURSOR_ROTATE:
+ view = roots_seat_get_focus(seat);
+ if (view != NULL) {
+ int ox = view->box.x + view->wlr_surface->current.width/2,
+ oy = view->box.y + view->wlr_surface->current.height/2;
+ int ux = cursor->offs_x - ox,
+ uy = cursor->offs_y - oy;
+ int vx = cursor->cursor->x - ox,
+ vy = cursor->cursor->y - oy;
+ float angle = atan2(ux*vy - uy*vx, vx*ux + vy*uy);
+ int steps = 12;
+ angle = round(angle/M_PI*steps) / (steps/M_PI);
+ view_rotate(view, cursor->view_rotation + angle);
+ }
+ break;
+ }
+}
+
+static void roots_cursor_press_button(struct roots_cursor *cursor,
+ struct wlr_input_device *device, uint32_t time, uint32_t button,
+ uint32_t state, double lx, double ly) {
+ struct roots_seat *seat = cursor->seat;
+ struct roots_desktop *desktop = seat->input->server->desktop;
+
+ bool is_touch = device->type == WLR_INPUT_DEVICE_TOUCH;
+
+ double sx, sy;
+ struct roots_view *view;
+ struct wlr_surface *surface = desktop_surface_at(desktop,
+ lx, ly, &sx, &sy, &view);
+
+ if (state == WLR_BUTTON_PRESSED && view &&
+ roots_seat_has_meta_pressed(seat)) {
+ roots_seat_set_focus(seat, view);
+
+ uint32_t edges;
+ switch (button) {
+ case BTN_LEFT:
+ roots_seat_begin_move(seat, view);
+ break;
+ case BTN_RIGHT:
+ edges = 0;
+ if (sx < view->wlr_surface->current.width/2) {
+ edges |= WLR_EDGE_LEFT;
+ } else {
+ edges |= WLR_EDGE_RIGHT;
+ }
+ if (sy < view->wlr_surface->current.height/2) {
+ edges |= WLR_EDGE_TOP;
+ } else {
+ edges |= WLR_EDGE_BOTTOM;
+ }
+ roots_seat_begin_resize(seat, view, edges);
+ break;
+ case BTN_MIDDLE:
+ roots_seat_begin_rotate(seat, view);
+ break;
+ }
+ } else {
+ if (view && !surface && cursor->pointer_view) {
+ seat_view_deco_button(cursor->pointer_view,
+ sx, sy, button, state);
+ }
+
+ if (state == WLR_BUTTON_RELEASED &&
+ cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ cursor->mode = ROOTS_CURSOR_PASSTHROUGH;
+ }
+
+ if (state == WLR_BUTTON_PRESSED) {
+ if (view) {
+ roots_seat_set_focus(seat, view);
+ }
+ if (surface && wlr_surface_is_layer_surface(surface)) {
+ struct wlr_layer_surface_v1 *layer =
+ wlr_layer_surface_v1_from_wlr_surface(surface);
+ if (layer->current.keyboard_interactive) {
+ roots_seat_set_focus_layer(seat, layer);
+ }
+ }
+ }
+ }
+
+ if (!is_touch) {
+ wlr_seat_pointer_notify_button(seat->seat, time, button, state);
+ }
+}
+
+void roots_cursor_handle_motion(struct roots_cursor *cursor,
+ struct wlr_event_pointer_motion *event) {
+ double dx = event->delta_x;
+ double dy = event->delta_y;
+
+ if (cursor->active_constraint) {
+ struct roots_view *view = cursor->pointer_view->view;
+ assert(view);
+
+ // TODO: handle rotated views
+ if (view->rotation == 0.0) {
+ double lx1 = cursor->cursor->x;
+ double ly1 = cursor->cursor->y;
+
+ double lx2 = lx1 + dx;
+ double ly2 = ly1 + dy;
+
+ double sx1 = lx1 - view->box.x;
+ double sy1 = ly1 - view->box.y;
+
+ double sx2 = lx2 - view->box.x;
+ double sy2 = ly2 - view->box.y;
+
+ double sx2_confined, sy2_confined;
+ if (!wlr_region_confine(&cursor->confine, sx1, sy1, sx2, sy2,
+ &sx2_confined, &sy2_confined)) {
+ return;
+ }
+
+ dx = sx2_confined - sx1;
+ dy = sy2_confined - sy1;
+ }
+ }
+
+ wlr_cursor_move(cursor->cursor, event->device, dx, dy);
+ roots_cursor_update_position(cursor, event->time_msec);
+}
+
+void roots_cursor_handle_motion_absolute(struct roots_cursor *cursor,
+ struct wlr_event_pointer_motion_absolute *event) {
+ double lx, ly;
+ wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device, event->x,
+ event->y, &lx, &ly);
+
+ if (cursor->pointer_view) {
+ struct roots_view *view = cursor->pointer_view->view;
+
+ if (cursor->active_constraint &&
+ !pixman_region32_contains_point(&cursor->confine,
+ floor(lx - view->box.x), floor(ly - view->box.y), NULL)) {
+ return;
+ }
+ }
+
+ wlr_cursor_warp_closest(cursor->cursor, event->device, lx, ly);
+ roots_cursor_update_position(cursor, event->time_msec);
+}
+
+void roots_cursor_handle_button(struct roots_cursor *cursor,
+ struct wlr_event_pointer_button *event) {
+ roots_cursor_press_button(cursor, event->device, event->time_msec,
+ event->button, event->state, cursor->cursor->x, cursor->cursor->y);
+}
+
+void roots_cursor_handle_axis(struct roots_cursor *cursor,
+ struct wlr_event_pointer_axis *event) {
+ wlr_seat_pointer_notify_axis(cursor->seat->seat, event->time_msec,
+ event->orientation, event->delta, event->delta_discrete, event->source);
+}
+
+void roots_cursor_handle_touch_down(struct roots_cursor *cursor,
+ struct wlr_event_touch_down *event) {
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ double lx, ly;
+ wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device,
+ event->x, event->y, &lx, &ly);
+
+ double sx, sy;
+ struct wlr_surface *surface = desktop_surface_at(
+ desktop, lx, ly, &sx, &sy, NULL);
+
+ uint32_t serial = 0;
+ if (surface && roots_seat_allow_input(cursor->seat, surface->resource)) {
+ serial = wlr_seat_touch_notify_down(cursor->seat->seat, surface,
+ event->time_msec, event->touch_id, sx, sy);
+ }
+
+ if (serial && wlr_seat_touch_num_points(cursor->seat->seat) == 1) {
+ cursor->seat->touch_id = event->touch_id;
+ cursor->seat->touch_x = lx;
+ cursor->seat->touch_y = ly;
+ roots_cursor_press_button(cursor, event->device, event->time_msec,
+ BTN_LEFT, 1, lx, ly);
+ }
+}
+
+void roots_cursor_handle_touch_up(struct roots_cursor *cursor,
+ struct wlr_event_touch_up *event) {
+ struct wlr_touch_point *point =
+ wlr_seat_touch_get_point(cursor->seat->seat, event->touch_id);
+ if (!point) {
+ return;
+ }
+
+ if (wlr_seat_touch_num_points(cursor->seat->seat) == 1) {
+ roots_cursor_press_button(cursor, event->device, event->time_msec,
+ BTN_LEFT, 0, cursor->seat->touch_x, cursor->seat->touch_y);
+ }
+
+ wlr_seat_touch_notify_up(cursor->seat->seat, event->time_msec,
+ event->touch_id);
+}
+
+void roots_cursor_handle_touch_motion(struct roots_cursor *cursor,
+ struct wlr_event_touch_motion *event) {
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ struct wlr_touch_point *point =
+ wlr_seat_touch_get_point(cursor->seat->seat, event->touch_id);
+ if (!point) {
+ return;
+ }
+
+ double lx, ly;
+ wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device,
+ event->x, event->y, &lx, &ly);
+
+ double sx, sy;
+ struct wlr_surface *surface = desktop_surface_at(
+ desktop, lx, ly, &sx, &sy, NULL);
+
+ if (surface && roots_seat_allow_input(cursor->seat, surface->resource)) {
+ wlr_seat_touch_point_focus(cursor->seat->seat, surface,
+ event->time_msec, event->touch_id, sx, sy);
+ wlr_seat_touch_notify_motion(cursor->seat->seat, event->time_msec,
+ event->touch_id, sx, sy);
+ } else {
+ wlr_seat_touch_point_clear_focus(cursor->seat->seat, event->time_msec,
+ event->touch_id);
+ }
+
+ if (event->touch_id == cursor->seat->touch_id) {
+ cursor->seat->touch_x = lx;
+ cursor->seat->touch_y = ly;
+ }
+}
+
+void roots_cursor_handle_tool_axis(struct roots_cursor *cursor,
+ struct wlr_event_tablet_tool_axis *event) {
+ double x = NAN, y = NAN;
+ if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X) &&
+ (event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) {
+ x = event->x;
+ y = event->y;
+ } else if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X)) {
+ x = event->x;
+ } else if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) {
+ y = event->y;
+ }
+
+ double lx, ly;
+ wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device,
+ x, y, &lx, &ly);
+
+
+ if (cursor->pointer_view) {
+ struct roots_view *view = cursor->pointer_view->view;
+
+ if (cursor->active_constraint &&
+ !pixman_region32_contains_point(&cursor->confine,
+ floor(lx - view->box.x), floor(ly - view->box.y), NULL)) {
+ return;
+ }
+ }
+
+ wlr_cursor_warp_closest(cursor->cursor, event->device, lx, ly);
+ roots_cursor_update_position(cursor, event->time_msec);
+}
+
+void roots_cursor_handle_tool_tip(struct roots_cursor *cursor,
+ struct wlr_event_tablet_tool_tip *event) {
+ roots_cursor_press_button(cursor, event->device,
+ event->time_msec, BTN_LEFT, event->state, cursor->cursor->x,
+ cursor->cursor->y);
+}
+
+void roots_cursor_handle_request_set_cursor(struct roots_cursor *cursor,
+ struct wlr_seat_pointer_request_set_cursor_event *event) {
+ struct wlr_surface *focused_surface =
+ event->seat_client->seat->pointer_state.focused_surface;
+ bool has_focused =
+ focused_surface != NULL && focused_surface->resource != NULL;
+ struct wl_client *focused_client = NULL;
+ if (has_focused) {
+ focused_client = wl_resource_get_client(focused_surface->resource);
+ }
+ if (event->seat_client->client != focused_client ||
+ cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ wlr_log(WLR_DEBUG, "Denying request to set cursor from unfocused client");
+ return;
+ }
+
+ wlr_cursor_set_surface(cursor->cursor, event->surface, event->hotspot_x,
+ event->hotspot_y);
+ cursor->cursor_client = event->seat_client->client;
+}
+
+void roots_cursor_handle_focus_change(struct roots_cursor *cursor,
+ struct wlr_seat_pointer_focus_change_event *event) {
+ double sx = event->sx;
+ double sy = event->sy;
+
+ double lx = cursor->cursor->x;
+ double ly = cursor->cursor->y;
+
+ wlr_log(WLR_DEBUG, "entered surface %p, lx: %f, ly: %f, sx: %f, sy: %f",
+ event->new_surface, lx, ly, sx, sy);
+
+ roots_cursor_constrain(cursor,
+ wlr_pointer_constraints_v1_constraint_for_surface(
+ cursor->seat->input->server->desktop->pointer_constraints,
+ event->new_surface, cursor->seat->seat),
+ sx, sy);
+}
+
+void roots_cursor_handle_constraint_commit(struct roots_cursor *cursor) {
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+
+ struct roots_view *view;
+ double sx, sy;
+ struct wlr_surface *surface = desktop_surface_at(desktop,
+ cursor->cursor->x, cursor->cursor->y, &sx, &sy, &view);
+ // This should never happen but views move around right when they're
+ // created from (0, 0) to their actual coordinates.
+ if (surface != cursor->active_constraint->surface) {
+ roots_cursor_update_focus(cursor);
+ } else {
+ roots_cursor_constrain(cursor, cursor->active_constraint, sx, sy);
+ }
+}
+
+static void handle_constraint_commit(struct wl_listener *listener,
+ void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, constraint_commit);
+ assert(cursor->active_constraint->surface == data);
+ roots_cursor_handle_constraint_commit(cursor);
+}
+
+void roots_cursor_constrain(struct roots_cursor *cursor,
+ struct wlr_pointer_constraint_v1 *constraint, double sx, double sy) {
+ if (cursor->active_constraint == constraint) {
+ return;
+ }
+
+ wlr_log(WLR_DEBUG, "roots_cursor_constrain(%p, %p)",
+ cursor, constraint);
+ wlr_log(WLR_DEBUG, "cursor->active_constraint: %p",
+ cursor->active_constraint);
+
+ wl_list_remove(&cursor->constraint_commit.link);
+ wl_list_init(&cursor->constraint_commit.link);
+ if (cursor->active_constraint) {
+ wlr_pointer_constraint_v1_send_deactivated(
+ cursor->active_constraint);
+ }
+
+ cursor->active_constraint = constraint;
+
+ if (constraint == NULL) {
+ return;
+ }
+
+ wlr_pointer_constraint_v1_send_activated(constraint);
+
+ wl_list_remove(&cursor->constraint_commit.link);
+ wl_signal_add(&constraint->surface->events.commit,
+ &cursor->constraint_commit);
+ cursor->constraint_commit.notify = handle_constraint_commit;
+
+ pixman_region32_clear(&cursor->confine);
+
+ pixman_region32_t *region = &constraint->region;
+
+ if (!pixman_region32_contains_point(region, floor(sx), floor(sy), NULL)) {
+ // Warp into region if possible
+ int nboxes;
+ pixman_box32_t *boxes = pixman_region32_rectangles(region, &nboxes);
+ if (nboxes > 0) {
+ struct roots_view *view = cursor->pointer_view->view;
+
+ double sx = (boxes[0].x1 + boxes[0].x2) / 2.;
+ double sy = (boxes[0].y1 + boxes[0].y2) / 2.;
+
+ rotate_child_position(&sx, &sy, 0, 0, view->box.width, view->box.height,
+ view->rotation);
+
+ double lx = view->box.x + sx;
+ double ly = view->box.y + sy;
+
+ wlr_cursor_warp_closest(cursor->cursor, NULL, lx, ly);
+ }
+ }
+
+ // A locked pointer will result in an empty region, thus disallowing all movement
+ if (constraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED) {
+ pixman_region32_copy(&cursor->confine, region);
+ }
+}
diff --git a/rootston/desktop.c b/rootston/desktop.c
new file mode 100644
index 00000000..48e2635c
--- /dev/null
+++ b/rootston/desktop.c
@@ -0,0 +1,1101 @@
+#define _POSIX_C_SOURCE 200112L
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <time.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/types/wlr_export_dmabuf_v1.h>
+#include <wlr/types/wlr_gamma_control.h>
+#include <wlr/types/wlr_gamma_control_v1.h>
+#include <wlr/types/wlr_idle_inhibit_v1.h>
+#include <wlr/types/wlr_idle.h>
+#include <wlr/types/wlr_input_inhibitor.h>
+#include <wlr/types/wlr_layer_shell_v1.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_pointer_constraints_v1.h>
+#include <wlr/types/wlr_gtk_primary_selection.h>
+#include <wlr/types/wlr_server_decoration.h>
+#include <wlr/types/wlr_wl_shell.h>
+#include <wlr/types/wlr_xcursor_manager.h>
+#include <wlr/types/wlr_xdg_output_v1.h>
+#include <wlr/types/wlr_xdg_shell_v6.h>
+#include <wlr/types/wlr_xdg_shell.h>
+#include <wlr/types/wlr_xdg_output_v1.h>
+#include <wlr/types/wlr_tablet_v2.h>
+#include <wlr/util/log.h>
+#include "rootston/layers.h"
+#include "rootston/seat.h"
+#include "rootston/server.h"
+#include "rootston/view.h"
+#include "rootston/virtual_keyboard.h"
+#include "rootston/xcursor.h"
+#include "wlr-layer-shell-unstable-v1-protocol.h"
+
+struct roots_view *view_create(struct roots_desktop *desktop) {
+ struct roots_view *view = calloc(1, sizeof(struct roots_view));
+ if (!view) {
+ return NULL;
+ }
+ view->desktop = desktop;
+ view->alpha = 1.0f;
+ wl_signal_init(&view->events.unmap);
+ wl_signal_init(&view->events.destroy);
+ wl_list_init(&view->children);
+ return view;
+}
+
+void view_get_box(const struct roots_view *view, struct wlr_box *box) {
+ box->x = view->box.x;
+ box->y = view->box.y;
+ box->width = view->box.width;
+ box->height = view->box.height;
+}
+
+void view_get_deco_box(const struct roots_view *view, struct wlr_box *box) {
+ view_get_box(view, box);
+ if (!view->decorated) {
+ return;
+ }
+
+ box->x -= view->border_width;
+ box->y -= (view->border_width + view->titlebar_height);
+ box->width += view->border_width * 2;
+ box->height += (view->border_width * 2 + view->titlebar_height);
+}
+
+enum roots_deco_part view_get_deco_part(struct roots_view *view, double sx,
+ double sy) {
+ if (!view->decorated) {
+ return ROOTS_DECO_PART_NONE;
+ }
+
+ int sw = view->wlr_surface->current.width;
+ int sh = view->wlr_surface->current.height;
+ int bw = view->border_width;
+ int titlebar_h = view->titlebar_height;
+
+ if (sx > 0 && sx < sw && sy < 0 && sy > -view->titlebar_height) {
+ return ROOTS_DECO_PART_TITLEBAR;
+ }
+
+ enum roots_deco_part parts = 0;
+ if (sy >= -(titlebar_h + bw) &&
+ sy <= sh + bw) {
+ if (sx < 0 && sx > -bw) {
+ parts |= ROOTS_DECO_PART_LEFT_BORDER;
+ } else if (sx > sw && sx < sw + bw) {
+ parts |= ROOTS_DECO_PART_RIGHT_BORDER;
+ }
+ }
+
+ if (sx >= -bw && sx <= sw + bw) {
+ if (sy > sh && sy <= sh + bw) {
+ parts |= ROOTS_DECO_PART_BOTTOM_BORDER;
+ } else if (sy >= -(titlebar_h + bw) && sy < 0) {
+ parts |= ROOTS_DECO_PART_TOP_BORDER;
+ }
+ }
+
+ // TODO corners
+
+ return parts;
+}
+
+static void view_update_output(const struct roots_view *view,
+ const struct wlr_box *before) {
+ struct roots_desktop *desktop = view->desktop;
+
+ if (view->wlr_surface == NULL) {
+ return;
+ }
+
+ struct wlr_box box;
+ view_get_box(view, &box);
+
+ struct roots_output *output;
+ wl_list_for_each(output, &desktop->outputs, link) {
+ bool intersected = before != NULL && wlr_output_layout_intersects(
+ desktop->layout, output->wlr_output, before);
+ bool intersects = wlr_output_layout_intersects(desktop->layout,
+ output->wlr_output, &box);
+ if (intersected && !intersects) {
+ wlr_surface_send_leave(view->wlr_surface, output->wlr_output);
+ if (view->toplevel_handle) {
+ wlr_foreign_toplevel_handle_v1_output_leave(
+ view->toplevel_handle, output->wlr_output);
+ }
+ }
+ if (!intersected && intersects) {
+ wlr_surface_send_enter(view->wlr_surface, output->wlr_output);
+ if (view->toplevel_handle) {
+ wlr_foreign_toplevel_handle_v1_output_enter(
+ view->toplevel_handle, output->wlr_output);
+ }
+ }
+ }
+}
+
+void view_move(struct roots_view *view, double x, double y) {
+ if (view->box.x == x && view->box.y == y) {
+ return;
+ }
+
+ struct wlr_box before;
+ view_get_box(view, &before);
+ if (view->move) {
+ view->move(view, x, y);
+ } else {
+ view_update_position(view, x, y);
+ }
+ view_update_output(view, &before);
+}
+
+void view_activate(struct roots_view *view, bool activate) {
+ if (view->activate) {
+ view->activate(view, activate);
+ }
+
+ if (view->toplevel_handle) {
+ wlr_foreign_toplevel_handle_v1_set_activated(view->toplevel_handle,
+ activate);
+ }
+}
+
+void view_resize(struct roots_view *view, uint32_t width, uint32_t height) {
+ struct wlr_box before;
+ view_get_box(view, &before);
+ if (view->resize) {
+ view->resize(view, width, height);
+ }
+ view_update_output(view, &before);
+}
+
+void view_move_resize(struct roots_view *view, double x, double y,
+ uint32_t width, uint32_t height) {
+ bool update_x = x != view->box.x;
+ bool update_y = y != view->box.y;
+ if (!update_x && !update_y) {
+ view_resize(view, width, height);
+ return;
+ }
+
+ if (view->move_resize) {
+ view->move_resize(view, x, y, width, height);
+ return;
+ }
+
+ view->pending_move_resize.update_x = update_x;
+ view->pending_move_resize.update_y = update_y;
+ view->pending_move_resize.x = x;
+ view->pending_move_resize.y = y;
+ view->pending_move_resize.width = width;
+ view->pending_move_resize.height = height;
+
+ view_resize(view, width, height);
+}
+
+static struct wlr_output *view_get_output(struct roots_view *view) {
+ struct wlr_box view_box;
+ view_get_box(view, &view_box);
+
+ double output_x, output_y;
+ wlr_output_layout_closest_point(view->desktop->layout, NULL,
+ view->box.x + (double)view_box.width/2,
+ view->box.y + (double)view_box.height/2,
+ &output_x, &output_y);
+ return wlr_output_layout_output_at(view->desktop->layout, output_x,
+ output_y);
+}
+
+void view_arrange_maximized(struct roots_view *view) {
+ struct wlr_box view_box;
+ view_get_box(view, &view_box);
+
+ struct wlr_output *output = view_get_output(view);
+ struct roots_output *roots_output = output->data;
+ struct wlr_box *output_box =
+ wlr_output_layout_get_box(view->desktop->layout, output);
+ struct wlr_box usable_area;
+ memcpy(&usable_area, &roots_output->usable_area,
+ sizeof(struct wlr_box));
+ usable_area.x += output_box->x;
+ usable_area.y += output_box->y;
+
+ view_move_resize(view, usable_area.x, usable_area.y,
+ usable_area.width, usable_area.height);
+ view_rotate(view, 0);
+}
+
+void view_maximize(struct roots_view *view, bool maximized) {
+ if (view->maximized == maximized) {
+ return;
+ }
+
+ if (view->maximize) {
+ view->maximize(view, maximized);
+ }
+
+ if (view->toplevel_handle) {
+ wlr_foreign_toplevel_handle_v1_set_maximized(view->toplevel_handle,
+ maximized);
+ }
+
+ if (!view->maximized && maximized) {
+ view->maximized = true;
+ view->saved.x = view->box.x;
+ view->saved.y = view->box.y;
+ view->saved.rotation = view->rotation;
+ view->saved.width = view->box.width;
+ view->saved.height = view->box.height;
+
+ view_arrange_maximized(view);
+ }
+
+ if (view->maximized && !maximized) {
+ view->maximized = false;
+
+ view_move_resize(view, view->saved.x, view->saved.y, view->saved.width,
+ view->saved.height);
+ view_rotate(view, view->saved.rotation);
+ }
+}
+
+void view_set_fullscreen(struct roots_view *view, bool fullscreen,
+ struct wlr_output *output) {
+ bool was_fullscreen = view->fullscreen_output != NULL;
+ if (was_fullscreen == fullscreen) {
+ // TODO: support changing the output?
+ return;
+ }
+
+ // TODO: check if client is focused?
+
+ if (view->set_fullscreen) {
+ view->set_fullscreen(view, fullscreen);
+ }
+
+ if (!was_fullscreen && fullscreen) {
+ if (output == NULL) {
+ output = view_get_output(view);
+ }
+ struct roots_output *roots_output =
+ desktop_output_from_wlr_output(view->desktop, output);
+ if (roots_output == NULL) {
+ return;
+ }
+
+ struct wlr_box view_box;
+ view_get_box(view, &view_box);
+
+ view->saved.x = view->box.x;
+ view->saved.y = view->box.y;
+ view->saved.rotation = view->rotation;
+ view->saved.width = view_box.width;
+ view->saved.height = view_box.height;
+
+ struct wlr_box *output_box =
+ wlr_output_layout_get_box(view->desktop->layout, output);
+ view_move_resize(view, output_box->x, output_box->y, output_box->width,
+ output_box->height);
+ view_rotate(view, 0);
+
+ roots_output->fullscreen_view = view;
+ view->fullscreen_output = roots_output;
+ output_damage_whole(roots_output);
+ }
+
+ if (was_fullscreen && !fullscreen) {
+ view_move_resize(view, view->saved.x, view->saved.y, view->saved.width,
+ view->saved.height);
+ view_rotate(view, view->saved.rotation);
+
+ output_damage_whole(view->fullscreen_output);
+ view->fullscreen_output->fullscreen_view = NULL;
+ view->fullscreen_output = NULL;
+ }
+}
+
+void view_rotate(struct roots_view *view, float rotation) {
+ if (view->rotation == rotation) {
+ return;
+ }
+
+ view_damage_whole(view);
+ view->rotation = rotation;
+ view_damage_whole(view);
+}
+
+void view_cycle_alpha(struct roots_view *view) {
+ view->alpha -= 0.05;
+ /* Don't go completely transparent */
+ if (view->alpha < 0.1) {
+ view->alpha = 1.0;
+ }
+ view_damage_whole(view);
+}
+
+void view_close(struct roots_view *view) {
+ if (view->close) {
+ view->close(view);
+ }
+}
+
+bool view_center(struct roots_view *view) {
+ struct wlr_box box;
+ view_get_box(view, &box);
+
+ struct roots_desktop *desktop = view->desktop;
+ struct roots_input *input = desktop->server->input;
+ struct roots_seat *seat = input_last_active_seat(input);
+ if (!seat) {
+ return false;
+ }
+
+ struct wlr_output *output =
+ wlr_output_layout_output_at(desktop->layout,
+ seat->cursor->cursor->x,
+ seat->cursor->cursor->y);
+ if (!output) {
+ // empty layout
+ return false;
+ }
+
+ const struct wlr_output_layout_output *l_output =
+ wlr_output_layout_get(desktop->layout, output);
+
+ int width, height;
+ wlr_output_effective_resolution(output, &width, &height);
+
+ double view_x = (double)(width - box.width) / 2 + l_output->x;
+ double view_y = (double)(height - box.height) / 2 + l_output->y;
+ view_move(view, view_x, view_y);
+
+ return true;
+}
+
+void view_child_finish(struct roots_view_child *child) {
+ if (child == NULL) {
+ return;
+ }
+ view_damage_whole(child->view);
+ wl_list_remove(&child->link);
+ wl_list_remove(&child->commit.link);
+ wl_list_remove(&child->new_subsurface.link);
+}
+
+static void view_child_handle_commit(struct wl_listener *listener,
+ void *data) {
+ struct roots_view_child *child = wl_container_of(listener, child, commit);
+ view_apply_damage(child->view);
+}
+
+static void view_child_handle_new_subsurface(struct wl_listener *listener,
+ void *data) {
+ struct roots_view_child *child =
+ wl_container_of(listener, child, new_subsurface);
+ struct wlr_subsurface *wlr_subsurface = data;
+ subsurface_create(child->view, wlr_subsurface);
+}
+
+void view_child_init(struct roots_view_child *child, struct roots_view *view,
+ struct wlr_surface *wlr_surface) {
+ assert(child->destroy);
+ child->view = view;
+ child->wlr_surface = wlr_surface;
+ child->commit.notify = view_child_handle_commit;
+ wl_signal_add(&wlr_surface->events.commit, &child->commit);
+ child->new_subsurface.notify = view_child_handle_new_subsurface;
+ wl_signal_add(&wlr_surface->events.new_subsurface, &child->new_subsurface);
+ wl_list_insert(&view->children, &child->link);
+}
+
+static void subsurface_destroy(struct roots_view_child *child) {
+ assert(child->destroy == subsurface_destroy);
+ struct roots_subsurface *subsurface = (struct roots_subsurface *)child;
+ if (subsurface == NULL) {
+ return;
+ }
+ wl_list_remove(&subsurface->destroy.link);
+ view_child_finish(&subsurface->view_child);
+ free(subsurface);
+}
+
+static void subsurface_handle_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_subsurface *subsurface =
+ wl_container_of(listener, subsurface, destroy);
+ subsurface_destroy((struct roots_view_child *)subsurface);
+}
+
+struct roots_subsurface *subsurface_create(struct roots_view *view,
+ struct wlr_subsurface *wlr_subsurface) {
+ struct roots_subsurface *subsurface =
+ calloc(1, sizeof(struct roots_subsurface));
+ if (subsurface == NULL) {
+ return NULL;
+ }
+ subsurface->wlr_subsurface = wlr_subsurface;
+ subsurface->view_child.destroy = subsurface_destroy;
+ view_child_init(&subsurface->view_child, view, wlr_subsurface->surface);
+ subsurface->destroy.notify = subsurface_handle_destroy;
+ wl_signal_add(&wlr_subsurface->events.destroy, &subsurface->destroy);
+ input_update_cursor_focus(view->desktop->server->input);
+ return subsurface;
+}
+
+void view_destroy(struct roots_view *view) {
+ if (view == NULL) {
+ return;
+ }
+
+ wl_signal_emit(&view->events.destroy, view);
+
+ if (view->wlr_surface != NULL) {
+ view_unmap(view);
+ }
+
+ // Can happen if fullscreened while unmapped, and hasn't been mapped
+ if (view->fullscreen_output != NULL) {
+ view->fullscreen_output->fullscreen_view = NULL;
+ }
+
+ if (view->destroy) {
+ view->destroy(view);
+ }
+
+ free(view);
+}
+
+static void view_handle_new_subsurface(struct wl_listener *listener,
+ void *data) {
+ struct roots_view *view = wl_container_of(listener, view, new_subsurface);
+ struct wlr_subsurface *wlr_subsurface = data;
+ subsurface_create(view, wlr_subsurface);
+}
+
+void view_map(struct roots_view *view, struct wlr_surface *surface) {
+ assert(view->wlr_surface == NULL);
+
+ view->wlr_surface = surface;
+
+ struct wlr_subsurface *subsurface;
+ wl_list_for_each(subsurface, &view->wlr_surface->subsurfaces,
+ parent_link) {
+ subsurface_create(view, subsurface);
+ }
+
+ view->new_subsurface.notify = view_handle_new_subsurface;
+ wl_signal_add(&view->wlr_surface->events.new_subsurface,
+ &view->new_subsurface);
+
+ wl_list_insert(&view->desktop->views, &view->link);
+ view_damage_whole(view);
+ input_update_cursor_focus(view->desktop->server->input);
+}
+
+void view_unmap(struct roots_view *view) {
+ assert(view->wlr_surface != NULL);
+
+ wl_signal_emit(&view->events.unmap, view);
+
+ view_damage_whole(view);
+ wl_list_remove(&view->link);
+
+ wl_list_remove(&view->new_subsurface.link);
+
+ struct roots_view_child *child, *tmp;
+ wl_list_for_each_safe(child, tmp, &view->children, link) {
+ child->destroy(child);
+ }
+
+ if (view->fullscreen_output != NULL) {
+ output_damage_whole(view->fullscreen_output);
+ view->fullscreen_output->fullscreen_view = NULL;
+ view->fullscreen_output = NULL;
+ }
+
+ view->wlr_surface = NULL;
+ view->box.width = view->box.height = 0;
+
+ if (view->toplevel_handle) {
+ wlr_foreign_toplevel_handle_v1_destroy(view->toplevel_handle);
+ view->toplevel_handle = NULL;
+ }
+}
+
+void view_initial_focus(struct roots_view *view) {
+ struct roots_input *input = view->desktop->server->input;
+ // TODO what seat gets focus? the one with the last input event?
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &input->seats, link) {
+ roots_seat_set_focus(seat, view);
+ }
+}
+
+void view_setup(struct roots_view *view) {
+ view_initial_focus(view);
+
+ if (view->fullscreen_output == NULL && !view->maximized) {
+ view_center(view);
+ }
+
+ view_create_foreign_toplevel_handle(view);
+ view_update_output(view, NULL);
+}
+
+void view_apply_damage(struct roots_view *view) {
+ struct roots_output *output;
+ wl_list_for_each(output, &view->desktop->outputs, link) {
+ output_damage_from_view(output, view);
+ }
+}
+
+void view_damage_whole(struct roots_view *view) {
+ struct roots_output *output;
+ wl_list_for_each(output, &view->desktop->outputs, link) {
+ output_damage_whole_view(output, view);
+ }
+}
+
+void view_update_position(struct roots_view *view, int x, int y) {
+ if (view->box.x == x && view->box.y == y) {
+ return;
+ }
+
+ view_damage_whole(view);
+ view->box.x = x;
+ view->box.y = y;
+ view_damage_whole(view);
+}
+
+void view_update_size(struct roots_view *view, int width, int height) {
+ if (view->box.width == width && view->box.height == height) {
+ return;
+ }
+
+ view_damage_whole(view);
+ view->box.width = width;
+ view->box.height = height;
+ view_damage_whole(view);
+}
+
+void view_update_decorated(struct roots_view *view, bool decorated) {
+ if (view->decorated == decorated) {
+ return;
+ }
+
+ view_damage_whole(view);
+ view->decorated = decorated;
+ if (decorated) {
+ view->border_width = 4;
+ view->titlebar_height = 12;
+ } else {
+ view->border_width = 0;
+ view->titlebar_height = 0;
+ }
+ view_damage_whole(view);
+}
+
+void view_set_title(struct roots_view *view, const char *title) {
+ if (view->toplevel_handle) {
+ wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle, title);
+ }
+}
+
+void view_set_app_id(struct roots_view *view, const char *app_id) {
+ if (view->toplevel_handle) {
+ wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle, app_id);
+ }
+}
+
+static void handle_toplevel_handle_request_maximize(struct wl_listener *listener,
+ void *data) {
+ struct roots_view *view = wl_container_of(listener, view,
+ toplevel_handle_request_maximize);
+ struct wlr_foreign_toplevel_handle_v1_maximized_event *event = data;
+ view_maximize(view, event->maximized);
+}
+
+static void handle_toplevel_handle_request_activate(struct wl_listener *listener,
+ void *data) {
+ struct roots_view *view =
+ wl_container_of(listener, view, toplevel_handle_request_activate);
+ struct wlr_foreign_toplevel_handle_v1_activated_event *event = data;
+
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &view->desktop->server->input->seats, link) {
+ if (event->seat == seat->seat) {
+ roots_seat_set_focus(seat, view);
+ }
+ }
+}
+
+static void handle_toplevel_handle_request_close(struct wl_listener *listener,
+ void *data) {
+ struct roots_view *view =
+ wl_container_of(listener, view, toplevel_handle_request_close);
+ view_close(view);
+}
+
+void view_create_foreign_toplevel_handle(struct roots_view *view) {
+ view->toplevel_handle =
+ wlr_foreign_toplevel_handle_v1_create(
+ view->desktop->foreign_toplevel_manager_v1);
+
+ view->toplevel_handle_request_maximize.notify =
+ handle_toplevel_handle_request_maximize;
+ wl_signal_add(&view->toplevel_handle->events.request_maximize,
+ &view->toplevel_handle_request_maximize);
+ view->toplevel_handle_request_activate.notify =
+ handle_toplevel_handle_request_activate;
+ wl_signal_add(&view->toplevel_handle->events.request_activate,
+ &view->toplevel_handle_request_activate);
+ view->toplevel_handle_request_close.notify =
+ handle_toplevel_handle_request_close;
+ wl_signal_add(&view->toplevel_handle->events.request_close,
+ &view->toplevel_handle_request_close);
+}
+
+static bool view_at(struct roots_view *view, double lx, double ly,
+ struct wlr_surface **surface, double *sx, double *sy) {
+ if (view->type == ROOTS_WL_SHELL_VIEW &&
+ view->wl_shell_surface->state == WLR_WL_SHELL_SURFACE_STATE_POPUP) {
+ return false;
+ }
+ if (view->wlr_surface == NULL) {
+ return false;
+ }
+
+ double view_sx = lx - view->box.x;
+ double view_sy = ly - view->box.y;
+
+ struct wlr_surface_state *state = &view->wlr_surface->current;
+ struct wlr_box box = {
+ .x = 0, .y = 0,
+ .width = state->width, .height = state->height,
+ };
+ if (view->rotation != 0.0) {
+ // Coordinates relative to the center of the view
+ double ox = view_sx - (double)box.width/2,
+ oy = view_sy - (double)box.height/2;
+ // Rotated coordinates
+ double rx = cos(view->rotation)*ox + sin(view->rotation)*oy,
+ ry = cos(view->rotation)*oy - sin(view->rotation)*ox;
+ view_sx = rx + (double)box.width/2;
+ view_sy = ry + (double)box.height/2;
+ }
+
+ double _sx, _sy;
+ struct wlr_surface *_surface = NULL;
+ switch (view->type) {
+ case ROOTS_XDG_SHELL_V6_VIEW:
+ _surface = wlr_xdg_surface_v6_surface_at(view->xdg_surface_v6,
+ view_sx, view_sy, &_sx, &_sy);
+ break;
+ case ROOTS_XDG_SHELL_VIEW:
+ _surface = wlr_xdg_surface_surface_at(view->xdg_surface,
+ view_sx, view_sy, &_sx, &_sy);
+ break;
+ case ROOTS_WL_SHELL_VIEW:
+ _surface = wlr_wl_shell_surface_surface_at(view->wl_shell_surface,
+ view_sx, view_sy, &_sx, &_sy);
+ break;
+#if WLR_HAS_XWAYLAND
+ case ROOTS_XWAYLAND_VIEW:
+ _surface = wlr_surface_surface_at(view->wlr_surface,
+ view_sx, view_sy, &_sx, &_sy);
+ break;
+#endif
+ }
+ if (_surface != NULL) {
+ *sx = _sx;
+ *sy = _sy;
+ *surface = _surface;
+ return true;
+ }
+
+ if (view_get_deco_part(view, view_sx, view_sy)) {
+ *sx = view_sx;
+ *sy = view_sy;
+ *surface = NULL;
+ return true;
+ }
+
+ return false;
+}
+
+static struct roots_view *desktop_view_at(struct roots_desktop *desktop,
+ double lx, double ly, struct wlr_surface **surface,
+ double *sx, double *sy) {
+ struct wlr_output *wlr_output =
+ wlr_output_layout_output_at(desktop->layout, lx, ly);
+ if (wlr_output != NULL) {
+ struct roots_output *output =
+ desktop_output_from_wlr_output(desktop, wlr_output);
+ if (output != NULL && output->fullscreen_view != NULL) {
+ if (view_at(output->fullscreen_view, lx, ly, surface, sx, sy)) {
+ return output->fullscreen_view;
+ } else {
+ return NULL;
+ }
+ }
+ }
+
+ struct roots_view *view;
+ wl_list_for_each(view, &desktop->views, link) {
+ if (view_at(view, lx, ly, surface, sx, sy)) {
+ return view;
+ }
+ }
+ return NULL;
+}
+
+static struct wlr_surface *layer_surface_at(struct roots_output *output,
+ struct wl_list *layer, double ox, double oy, double *sx, double *sy) {
+ struct roots_layer_surface *roots_surface;
+ wl_list_for_each_reverse(roots_surface, layer, link) {
+ double _sx = ox - roots_surface->geo.x;
+ double _sy = oy - roots_surface->geo.y;
+
+ struct wlr_surface *sub = wlr_layer_surface_v1_surface_at(
+ roots_surface->layer_surface, _sx, _sy, sx, sy);
+
+ if (sub) {
+ return sub;
+ }
+ }
+
+ return NULL;
+}
+
+struct wlr_surface *desktop_surface_at(struct roots_desktop *desktop,
+ double lx, double ly, double *sx, double *sy,
+ struct roots_view **view) {
+ struct wlr_surface *surface = NULL;
+ struct wlr_output *wlr_output =
+ wlr_output_layout_output_at(desktop->layout, lx, ly);
+ struct roots_output *roots_output = NULL;
+ double ox = lx, oy = ly;
+ if (view) {
+ *view = NULL;
+ }
+
+ if (wlr_output) {
+ roots_output = wlr_output->data;
+ wlr_output_layout_output_coords(desktop->layout, wlr_output, &ox, &oy);
+
+ if ((surface = layer_surface_at(roots_output,
+ &roots_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],
+ ox, oy, sx, sy))) {
+ return surface;
+ }
+ if ((surface = layer_surface_at(roots_output,
+ &roots_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
+ ox, oy, sx, sy))) {
+ return surface;
+ }
+ }
+
+ struct roots_view *_view;
+ if ((_view = desktop_view_at(desktop, lx, ly, &surface, sx, sy))) {
+ if (view) {
+ *view = _view;
+ }
+ return surface;
+ }
+
+ if (wlr_output) {
+ if ((surface = layer_surface_at(roots_output,
+ &roots_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
+ ox, oy, sx, sy))) {
+ return surface;
+ }
+ if ((surface = layer_surface_at(roots_output,
+ &roots_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
+ ox, oy, sx, sy))) {
+ return surface;
+ }
+ }
+ return NULL;
+}
+
+static void handle_layout_change(struct wl_listener *listener, void *data) {
+ struct roots_desktop *desktop =
+ wl_container_of(listener, desktop, layout_change);
+
+ struct wlr_output *center_output =
+ wlr_output_layout_get_center_output(desktop->layout);
+ if (center_output == NULL) {
+ return;
+ }
+
+ struct wlr_box *center_output_box =
+ wlr_output_layout_get_box(desktop->layout, center_output);
+ double center_x = center_output_box->x + center_output_box->width/2;
+ double center_y = center_output_box->y + center_output_box->height/2;
+
+ struct roots_view *view;
+ wl_list_for_each(view, &desktop->views, link) {
+ struct wlr_box box;
+ view_get_box(view, &box);
+
+ if (wlr_output_layout_intersects(desktop->layout, NULL, &box)) {
+ continue;
+ }
+
+ view_move(view, center_x - box.width/2, center_y - box.height/2);
+ }
+}
+
+static void input_inhibit_activate(struct wl_listener *listener, void *data) {
+ struct roots_desktop *desktop = wl_container_of(
+ listener, desktop, input_inhibit_activate);
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &desktop->server->input->seats, link) {
+ roots_seat_set_exclusive_client(seat,
+ desktop->input_inhibit->active_client);
+ }
+}
+
+static void input_inhibit_deactivate(struct wl_listener *listener, void *data) {
+ struct roots_desktop *desktop = wl_container_of(
+ listener, desktop, input_inhibit_deactivate);
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &desktop->server->input->seats, link) {
+ roots_seat_set_exclusive_client(seat, NULL);
+ }
+}
+
+static void handle_constraint_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_pointer_constraint *constraint =
+ wl_container_of(listener, constraint, destroy);
+ struct wlr_pointer_constraint_v1 *wlr_constraint = data;
+ struct roots_seat *seat = wlr_constraint->seat->data;
+
+ wl_list_remove(&constraint->destroy.link);
+
+ if (seat->cursor->active_constraint == wlr_constraint) {
+ wl_list_remove(&seat->cursor->constraint_commit.link);
+ wl_list_init(&seat->cursor->constraint_commit.link);
+ seat->cursor->active_constraint = NULL;
+
+ if (wlr_constraint->current.committed &
+ WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT &&
+ seat->cursor->pointer_view) {
+ double sx = wlr_constraint->current.cursor_hint.x;
+ double sy = wlr_constraint->current.cursor_hint.y;
+
+ struct roots_view *view = seat->cursor->pointer_view->view;
+ rotate_child_position(&sx, &sy, 0, 0, view->box.width, view->box.height,
+ view->rotation);
+ double lx = view->box.x + sx;
+ double ly = view->box.y + sy;
+
+ wlr_cursor_warp(seat->cursor->cursor, NULL, lx, ly);
+ }
+ }
+
+ free(constraint);
+}
+
+static void handle_pointer_constraint(struct wl_listener *listener,
+ void *data) {
+ struct wlr_pointer_constraint_v1 *wlr_constraint = data;
+ struct roots_seat *seat = wlr_constraint->seat->data;
+
+ struct roots_pointer_constraint *constraint =
+ calloc(1, sizeof(struct roots_pointer_constraint));
+ constraint->constraint = wlr_constraint;
+
+ constraint->destroy.notify = handle_constraint_destroy;
+ wl_signal_add(&wlr_constraint->events.destroy, &constraint->destroy);
+
+ double sx, sy;
+ struct wlr_surface *surface = desktop_surface_at(
+ seat->input->server->desktop,
+ seat->cursor->cursor->x, seat->cursor->cursor->y, &sx, &sy, NULL);
+
+ if (surface == wlr_constraint->surface) {
+ assert(!seat->cursor->active_constraint);
+ roots_cursor_constrain(seat->cursor, wlr_constraint, sx, sy);
+ }
+}
+
+struct roots_desktop *desktop_create(struct roots_server *server,
+ struct roots_config *config) {
+ wlr_log(WLR_DEBUG, "Initializing roots desktop");
+
+ struct roots_desktop *desktop = calloc(1, sizeof(struct roots_desktop));
+ if (desktop == NULL) {
+ return NULL;
+ }
+
+ wl_list_init(&desktop->views);
+ wl_list_init(&desktop->outputs);
+
+ desktop->new_output.notify = handle_new_output;
+ wl_signal_add(&server->backend->events.new_output, &desktop->new_output);
+
+ desktop->server = server;
+ desktop->config = config;
+
+ desktop->layout = wlr_output_layout_create();
+ wlr_xdg_output_manager_v1_create(server->wl_display, desktop->layout);
+ desktop->layout_change.notify = handle_layout_change;
+ wl_signal_add(&desktop->layout->events.change, &desktop->layout_change);
+
+ desktop->compositor = wlr_compositor_create(server->wl_display,
+ server->renderer);
+
+ desktop->xdg_shell_v6 = wlr_xdg_shell_v6_create(server->wl_display);
+ wl_signal_add(&desktop->xdg_shell_v6->events.new_surface,
+ &desktop->xdg_shell_v6_surface);
+ desktop->xdg_shell_v6_surface.notify = handle_xdg_shell_v6_surface;
+
+ desktop->xdg_shell = wlr_xdg_shell_create(server->wl_display);
+ wl_signal_add(&desktop->xdg_shell->events.new_surface,
+ &desktop->xdg_shell_surface);
+ desktop->xdg_shell_surface.notify = handle_xdg_shell_surface;
+
+ desktop->wl_shell = wlr_wl_shell_create(server->wl_display);
+ wl_signal_add(&desktop->wl_shell->events.new_surface,
+ &desktop->wl_shell_surface);
+ desktop->wl_shell_surface.notify = handle_wl_shell_surface;
+
+ desktop->layer_shell = wlr_layer_shell_v1_create(server->wl_display);
+ wl_signal_add(&desktop->layer_shell->events.new_surface,
+ &desktop->layer_shell_surface);
+ desktop->layer_shell_surface.notify = handle_layer_shell_surface;
+
+ desktop->tablet_v2 = wlr_tablet_v2_create(server->wl_display);
+
+ const char *cursor_theme = NULL;
+#if WLR_HAS_XWAYLAND
+ const char *cursor_default = ROOTS_XCURSOR_DEFAULT;
+#endif
+ struct roots_cursor_config *cc =
+ roots_config_get_cursor(config, ROOTS_CONFIG_DEFAULT_SEAT_NAME);
+ if (cc != NULL) {
+ cursor_theme = cc->theme;
+#if WLR_HAS_XWAYLAND
+ if (cc->default_image != NULL) {
+ cursor_default = cc->default_image;
+ }
+#endif
+ }
+
+ char cursor_size_fmt[16];
+ snprintf(cursor_size_fmt, sizeof(cursor_size_fmt),
+ "%d", ROOTS_XCURSOR_SIZE);
+ setenv("XCURSOR_SIZE", cursor_size_fmt, 1);
+ if (cursor_theme != NULL) {
+ setenv("XCURSOR_THEME", cursor_theme, 1);
+ }
+
+#if WLR_HAS_XWAYLAND
+ desktop->xcursor_manager = wlr_xcursor_manager_create(cursor_theme,
+ ROOTS_XCURSOR_SIZE);
+ if (desktop->xcursor_manager == NULL) {
+ wlr_log(WLR_ERROR, "Cannot create XCursor manager for theme %s",
+ cursor_theme);
+ free(desktop);
+ return NULL;
+ }
+
+ if (config->xwayland) {
+ desktop->xwayland = wlr_xwayland_create(server->wl_display,
+ desktop->compositor, config->xwayland_lazy);
+ wl_signal_add(&desktop->xwayland->events.new_surface,
+ &desktop->xwayland_surface);
+ desktop->xwayland_surface.notify = handle_xwayland_surface;
+
+ if (wlr_xcursor_manager_load(desktop->xcursor_manager, 1)) {
+ wlr_log(WLR_ERROR, "Cannot load XWayland XCursor theme");
+ }
+ struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(
+ desktop->xcursor_manager, cursor_default, 1);
+ if (xcursor != NULL) {
+ struct wlr_xcursor_image *image = xcursor->images[0];
+ wlr_xwayland_set_cursor(desktop->xwayland, image->buffer,
+ image->width * 4, image->width, image->height, image->hotspot_x,
+ image->hotspot_y);
+ }
+ }
+#endif
+
+ desktop->gamma_control_manager = wlr_gamma_control_manager_create(
+ server->wl_display);
+ desktop->gamma_control_manager_v1 = wlr_gamma_control_manager_v1_create(
+ server->wl_display);
+ desktop->screenshooter = wlr_screenshooter_create(server->wl_display);
+ desktop->export_dmabuf_manager_v1 =
+ wlr_export_dmabuf_manager_v1_create(server->wl_display);
+ desktop->server_decoration_manager =
+ wlr_server_decoration_manager_create(server->wl_display);
+ wlr_server_decoration_manager_set_default_mode(
+ desktop->server_decoration_manager,
+ WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT);
+ desktop->idle = wlr_idle_create(server->wl_display);
+ desktop->idle_inhibit = wlr_idle_inhibit_v1_create(server->wl_display);
+ desktop->primary_selection_device_manager =
+ wlr_gtk_primary_selection_device_manager_create(server->wl_display);
+ desktop->input_inhibit =
+ wlr_input_inhibit_manager_create(server->wl_display);
+ desktop->input_inhibit_activate.notify = input_inhibit_activate;
+ wl_signal_add(&desktop->input_inhibit->events.activate,
+ &desktop->input_inhibit_activate);
+ desktop->input_inhibit_deactivate.notify = input_inhibit_deactivate;
+ wl_signal_add(&desktop->input_inhibit->events.deactivate,
+ &desktop->input_inhibit_deactivate);
+
+ desktop->input_method =
+ wlr_input_method_manager_v2_create(server->wl_display);
+
+ desktop->text_input = wlr_text_input_manager_v3_create(server->wl_display);
+
+ desktop->virtual_keyboard = wlr_virtual_keyboard_manager_v1_create(
+ server->wl_display);
+ wl_signal_add(&desktop->virtual_keyboard->events.new_virtual_keyboard,
+ &desktop->virtual_keyboard_new);
+ desktop->virtual_keyboard_new.notify = handle_virtual_keyboard;
+
+ desktop->screencopy = wlr_screencopy_manager_v1_create(server->wl_display);
+
+ desktop->xdg_decoration_manager =
+ wlr_xdg_decoration_manager_v1_create(server->wl_display);
+ wl_signal_add(&desktop->xdg_decoration_manager->events.new_toplevel_decoration,
+ &desktop->xdg_toplevel_decoration);
+ desktop->xdg_toplevel_decoration.notify = handle_xdg_toplevel_decoration;
+
+ desktop->pointer_constraints =
+ wlr_pointer_constraints_v1_create(server->wl_display);
+ desktop->pointer_constraint.notify = handle_pointer_constraint;
+ wl_signal_add(&desktop->pointer_constraints->events.new_constraint,
+ &desktop->pointer_constraint);
+
+ desktop->presentation =
+ wlr_presentation_create(server->wl_display, server->backend);
+ desktop->foreign_toplevel_manager_v1 =
+ wlr_foreign_toplevel_manager_v1_create(server->wl_display);
+
+ return desktop;
+}
+
+void desktop_destroy(struct roots_desktop *desktop) {
+ // TODO
+}
+
+struct roots_output *desktop_output_from_wlr_output(
+ struct roots_desktop *desktop, struct wlr_output *wlr_output) {
+ struct roots_output *output;
+ wl_list_for_each(output, &desktop->outputs, link) {
+ if (output->wlr_output == wlr_output) {
+ return output;
+ }
+ }
+ return NULL;
+}
diff --git a/rootston/ini.c b/rootston/ini.c
new file mode 100644
index 00000000..f515dd38
--- /dev/null
+++ b/rootston/ini.c
@@ -0,0 +1,195 @@
+/* inih -- simple .INI file parser
+
+inih is released under the New BSD license (see LICENSE.txt). Go to the project
+home page for more info:
+
+https://github.com/benhoyt/inih
+
+*/
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "rootston/ini.h"
+
+#if !INI_USE_STACK
+#include <stdlib.h>
+#endif
+
+#define MAX_SECTION 50
+#define MAX_NAME 50
+
+/* Strip whitespace chars off end of given string, in place. Return s. */
+static char* rstrip(char* s)
+{
+ char* p = s + strlen(s);
+ while (p > s && isspace((unsigned char)(*--p)))
+ *p = '\0';
+ return s;
+}
+
+/* Return pointer to first non-whitespace char in given string. */
+static char* lskip(const char* s)
+{
+ while (*s && isspace((unsigned char)(*s)))
+ s++;
+ return (char*)s;
+}
+
+/* Return pointer to first char (of chars) or inline comment in given string,
+ or pointer to null at end of string if neither found. Inline comment must
+ be prefixed by a whitespace character to register as a comment. */
+static char* find_chars_or_comment(const char* s, const char* chars)
+{
+#if INI_ALLOW_INLINE_COMMENTS
+ int was_space = 0;
+ while (*s && (!chars || !strchr(chars, *s)) &&
+ !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
+ was_space = isspace((unsigned char)(*s));
+ s++;
+ }
+#else
+ while (*s && (!chars || !strchr(chars, *s))) {
+ s++;
+ }
+#endif
+ return (char*)s;
+}
+
+/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
+static char* strncpy0(char* dest, const char* src, size_t size)
+{
+ strncpy(dest, src, size-1);
+ dest[size - 1] = '\0';
+ return dest;
+}
+
+/* See documentation in header file. */
+int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
+ void* user)
+{
+ /* Uses a fair bit of stack (use heap instead if you need to) */
+#if INI_USE_STACK
+ char line[INI_MAX_LINE];
+#else
+ char* line;
+#endif
+ char section[MAX_SECTION] = "";
+ char prev_name[MAX_NAME] = "";
+
+ char* start;
+ char* end;
+ char* name;
+ char* value;
+ int lineno = 0;
+ int error = 0;
+
+#if !INI_USE_STACK
+ line = (char*)malloc(INI_MAX_LINE);
+ if (!line) {
+ return -2;
+ }
+#endif
+
+ /* Scan through stream line by line */
+ while (reader(line, INI_MAX_LINE, stream) != NULL) {
+ lineno++;
+
+ start = line;
+#if INI_ALLOW_BOM
+ if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
+ (unsigned char)start[1] == 0xBB &&
+ (unsigned char)start[2] == 0xBF) {
+ start += 3;
+ }
+#endif
+ start = lskip(rstrip(start));
+
+ if (*start == ';' || *start == '#') {
+ /* Per Python configparser, allow both ; and # comments at the
+ start of a line */
+ }
+#if INI_ALLOW_MULTILINE
+ else if (*prev_name && *start && start > line) {
+ /* Non-blank line with leading whitespace, treat as continuation
+ of previous name's value (as per Python configparser). */
+ if (!handler(user, section, prev_name, start) && !error)
+ error = lineno;
+ }
+#endif
+ else if (*start == '[') {
+ /* A "[section]" line */
+ end = find_chars_or_comment(start + 1, "]");
+ if (*end == ']') {
+ *end = '\0';
+ strncpy0(section, start + 1, sizeof(section));
+ *prev_name = '\0';
+ }
+ else if (!error) {
+ /* No ']' found on section line */
+ error = lineno;
+ }
+ }
+ else if (*start) {
+ /* Not a comment, must be a name[=:]value pair */
+ end = find_chars_or_comment(start, "=:");
+ if (*end == '=' || *end == ':') {
+ *end = '\0';
+ name = rstrip(start);
+ value = lskip(end + 1);
+#if INI_ALLOW_INLINE_COMMENTS
+ end = find_chars_or_comment(value, NULL);
+ if (*end)
+ *end = '\0';
+#endif
+ rstrip(value);
+
+ /* Valid name[=:]value pair found, call handler */
+ strncpy0(prev_name, name, sizeof(prev_name));
+ if (!handler(user, section, name, value) && !error)
+ error = lineno;
+ memset(value, 0, strlen(value));
+ }
+ else if (!error) {
+ /* No '=' or ':' found on name[=:]value line */
+ error = lineno;
+ }
+ }
+
+#if INI_STOP_ON_FIRST_ERROR
+ if (error)
+ break;
+#endif
+ }
+
+#if !INI_USE_STACK
+ free(line);
+#endif
+
+ return error;
+}
+
+/* See documentation in header file. */
+int ini_parse_file(FILE* file, ini_handler handler, void* user)
+{
+ return ini_parse_stream((ini_reader)fgets, file, handler, user);
+}
+
+/* See documentation in header file. */
+int ini_parse(const char* filename, ini_handler handler, void* user)
+{
+ FILE* file;
+ int error;
+
+ file = fopen(filename, "r");
+ if (!file)
+ return -1;
+ error = ini_parse_file(file, handler, user);
+ fclose(file);
+ return error;
+}
diff --git a/rootston/input.c b/rootston/input.c
new file mode 100644
index 00000000..a863b919
--- /dev/null
+++ b/rootston/input.c
@@ -0,0 +1,145 @@
+#define _POSIX_C_SOURCE 200112L
+#include <assert.h>
+#include <stdlib.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wlr/backend/libinput.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/util/log.h>
+#include <wlr/xcursor.h>
+#if WLR_HAS_XWAYLAND
+#include <wlr/xwayland.h>
+#endif
+#include "rootston/config.h"
+#include "rootston/input.h"
+#include "rootston/keyboard.h"
+#include "rootston/seat.h"
+#include "rootston/server.h"
+
+static const char *device_type(enum wlr_input_device_type type) {
+ switch (type) {
+ case WLR_INPUT_DEVICE_KEYBOARD:
+ return "keyboard";
+ case WLR_INPUT_DEVICE_POINTER:
+ return "pointer";
+ case WLR_INPUT_DEVICE_SWITCH:
+ return "switch";
+ case WLR_INPUT_DEVICE_TOUCH:
+ return "touch";
+ case WLR_INPUT_DEVICE_TABLET_TOOL:
+ return "tablet tool";
+ case WLR_INPUT_DEVICE_TABLET_PAD:
+ return "tablet pad";
+ }
+ return NULL;
+}
+
+struct roots_seat *input_get_seat(struct roots_input *input, char *name) {
+ struct roots_seat *seat = NULL;
+ wl_list_for_each(seat, &input->seats, link) {
+ if (strcmp(seat->seat->name, name) == 0) {
+ return seat;
+ }
+ }
+
+ seat = roots_seat_create(input, name);
+ return seat;
+}
+
+static void handle_new_input(struct wl_listener *listener, void *data) {
+ struct wlr_input_device *device = data;
+ struct roots_input *input = wl_container_of(listener, input, new_input);
+
+ char *seat_name = ROOTS_CONFIG_DEFAULT_SEAT_NAME;
+ struct roots_device_config *dc =
+ roots_config_get_device(input->config, device);
+ if (dc) {
+ seat_name = dc->seat;
+ }
+
+ struct roots_seat *seat = input_get_seat(input, seat_name);
+ if (!seat) {
+ wlr_log(WLR_ERROR, "could not create roots seat");
+ return;
+ }
+
+ wlr_log(WLR_DEBUG, "New input device: %s (%d:%d) %s seat:%s", device->name,
+ device->vendor, device->product, device_type(device->type), seat_name);
+
+ roots_seat_add_device(seat, device);
+
+ if (dc && wlr_input_device_is_libinput(device)) {
+ struct libinput_device *libinput_dev =
+ wlr_libinput_get_device_handle(device);
+
+ wlr_log(WLR_DEBUG, "input has config, tap_enabled: %d\n", dc->tap_enabled);
+ if (dc->tap_enabled) {
+ libinput_device_config_tap_set_enabled(libinput_dev,
+ LIBINPUT_CONFIG_TAP_ENABLED);
+ }
+ }
+}
+
+struct roots_input *input_create(struct roots_server *server,
+ struct roots_config *config) {
+ wlr_log(WLR_DEBUG, "Initializing roots input");
+ assert(server->desktop);
+
+ struct roots_input *input = calloc(1, sizeof(struct roots_input));
+ if (input == NULL) {
+ return NULL;
+ }
+
+ input->config = config;
+ input->server = server;
+
+ wl_list_init(&input->seats);
+
+ input->new_input.notify = handle_new_input;
+ wl_signal_add(&server->backend->events.new_input, &input->new_input);
+
+ return input;
+}
+
+void input_destroy(struct roots_input *input) {
+ // TODO
+}
+
+struct roots_seat *input_seat_from_wlr_seat(struct roots_input *input,
+ struct wlr_seat *wlr_seat) {
+ struct roots_seat *seat = NULL;
+ wl_list_for_each(seat, &input->seats, link) {
+ if (seat->seat == wlr_seat) {
+ return seat;
+ }
+ }
+ return seat;
+}
+
+bool input_view_has_focus(struct roots_input *input, struct roots_view *view) {
+ if (!view) {
+ return false;
+ }
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &input->seats, link) {
+ if (view == roots_seat_get_focus(seat)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static inline int64_t timespec_to_msec(const struct timespec *a) {
+ return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000;
+}
+
+void input_update_cursor_focus(struct roots_input *input) {
+ struct roots_seat *seat;
+ struct timespec now;
+ wl_list_for_each(seat, &input->seats, link) {
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ roots_cursor_update_position(seat->cursor, timespec_to_msec(&now));
+ }
+}
diff --git a/rootston/keyboard.c b/rootston/keyboard.c
new file mode 100644
index 00000000..9d3fadf6
--- /dev/null
+++ b/rootston/keyboard.c
@@ -0,0 +1,349 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/backend/session.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_pointer_constraints_v1.h>
+#include <wlr/types/wlr_pointer.h>
+#include <wlr/util/log.h>
+#include <xkbcommon/xkbcommon.h>
+#include "rootston/bindings.h"
+#include "rootston/input.h"
+#include "rootston/keyboard.h"
+#include "rootston/seat.h"
+
+static ssize_t pressed_keysyms_index(xkb_keysym_t *pressed_keysyms,
+ xkb_keysym_t keysym) {
+ for (size_t i = 0; i < ROOTS_KEYBOARD_PRESSED_KEYSYMS_CAP; ++i) {
+ if (pressed_keysyms[i] == keysym) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static size_t pressed_keysyms_length(xkb_keysym_t *pressed_keysyms) {
+ size_t n = 0;
+ for (size_t i = 0; i < ROOTS_KEYBOARD_PRESSED_KEYSYMS_CAP; ++i) {
+ if (pressed_keysyms[i] != XKB_KEY_NoSymbol) {
+ ++n;
+ }
+ }
+ return n;
+}
+
+static void pressed_keysyms_add(xkb_keysym_t *pressed_keysyms,
+ xkb_keysym_t keysym) {
+ ssize_t i = pressed_keysyms_index(pressed_keysyms, keysym);
+ if (i < 0) {
+ i = pressed_keysyms_index(pressed_keysyms, XKB_KEY_NoSymbol);
+ if (i >= 0) {
+ pressed_keysyms[i] = keysym;
+ }
+ }
+}
+
+static void pressed_keysyms_remove(xkb_keysym_t *pressed_keysyms,
+ xkb_keysym_t keysym) {
+ ssize_t i = pressed_keysyms_index(pressed_keysyms, keysym);
+ if (i >= 0) {
+ pressed_keysyms[i] = XKB_KEY_NoSymbol;
+ }
+}
+
+static bool keysym_is_modifier(xkb_keysym_t keysym) {
+ switch (keysym) {
+ case XKB_KEY_Shift_L: case XKB_KEY_Shift_R:
+ case XKB_KEY_Control_L: case XKB_KEY_Control_R:
+ case XKB_KEY_Caps_Lock:
+ case XKB_KEY_Shift_Lock:
+ case XKB_KEY_Meta_L: case XKB_KEY_Meta_R:
+ case XKB_KEY_Alt_L: case XKB_KEY_Alt_R:
+ case XKB_KEY_Super_L: case XKB_KEY_Super_R:
+ case XKB_KEY_Hyper_L: case XKB_KEY_Hyper_R:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void pressed_keysyms_update(xkb_keysym_t *pressed_keysyms,
+ const xkb_keysym_t *keysyms, size_t keysyms_len,
+ enum wlr_key_state state) {
+ for (size_t i = 0; i < keysyms_len; ++i) {
+ if (keysym_is_modifier(keysyms[i])) {
+ continue;
+ }
+ if (state == WLR_KEY_PRESSED) {
+ pressed_keysyms_add(pressed_keysyms, keysyms[i]);
+ } else { // WLR_KEY_RELEASED
+ pressed_keysyms_remove(pressed_keysyms, keysyms[i]);
+ }
+ }
+}
+
+static void keyboard_binding_execute(struct roots_keyboard *keyboard,
+ const char *command) {
+ execute_binding_command(keyboard->seat, keyboard->input, command);
+}
+
+/**
+ * Execute a built-in, hardcoded compositor binding. These are triggered from a
+ * single keysym.
+ *
+ * Returns true if the keysym was handled by a binding and false if the event
+ * should be propagated to clients.
+ */
+static bool keyboard_execute_compositor_binding(struct roots_keyboard *keyboard,
+ xkb_keysym_t keysym) {
+ if (keysym >= XKB_KEY_XF86Switch_VT_1 &&
+ keysym <= XKB_KEY_XF86Switch_VT_12) {
+ struct roots_server *server = keyboard->input->server;
+
+ struct wlr_session *session = wlr_backend_get_session(server->backend);
+ if (session) {
+ unsigned vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1;
+ wlr_session_change_vt(session, vt);
+ }
+
+ return true;
+ }
+
+ if (keysym == XKB_KEY_Escape) {
+ wlr_seat_pointer_end_grab(keyboard->seat->seat);
+ wlr_seat_keyboard_end_grab(keyboard->seat->seat);
+ roots_seat_end_compositor_grab(keyboard->seat);
+ }
+
+ return false;
+}
+
+/**
+ * Execute keyboard bindings. These include compositor bindings and user-defined
+ * bindings.
+ *
+ * Returns true if the keysym was handled by a binding and false if the event
+ * should be propagated to clients.
+ */
+static bool keyboard_execute_binding(struct roots_keyboard *keyboard,
+ xkb_keysym_t *pressed_keysyms, uint32_t modifiers,
+ const xkb_keysym_t *keysyms, size_t keysyms_len) {
+ for (size_t i = 0; i < keysyms_len; ++i) {
+ if (keyboard_execute_compositor_binding(keyboard, keysyms[i])) {
+ return true;
+ }
+ }
+
+ // User-defined bindings
+ size_t n = pressed_keysyms_length(pressed_keysyms);
+ struct wl_list *bindings = &keyboard->input->server->config->bindings;
+ struct roots_binding_config *bc;
+ wl_list_for_each(bc, bindings, link) {
+ if (modifiers ^ bc->modifiers || n != bc->keysyms_len) {
+ continue;
+ }
+
+ bool ok = true;
+ for (size_t i = 0; i < bc->keysyms_len; i++) {
+ ssize_t j = pressed_keysyms_index(pressed_keysyms, bc->keysyms[i]);
+ if (j < 0) {
+ ok = false;
+ break;
+ }
+ }
+
+ if (ok) {
+ keyboard_binding_execute(keyboard, bc->command);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Get keysyms and modifiers from the keyboard as xkb sees them.
+ *
+ * This uses the xkb keysyms translation based on pressed modifiers and clears
+ * the consumed modifiers from the list of modifiers passed to keybind
+ * detection.
+ *
+ * On US layout, pressing Alt+Shift+2 will trigger Alt+@.
+ */
+static size_t keyboard_keysyms_translated(struct roots_keyboard *keyboard,
+ xkb_keycode_t keycode, const xkb_keysym_t **keysyms,
+ uint32_t *modifiers) {
+ *modifiers = wlr_keyboard_get_modifiers(keyboard->device->keyboard);
+ xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods2(
+ keyboard->device->keyboard->xkb_state, keycode, XKB_CONSUMED_MODE_XKB);
+ *modifiers = *modifiers & ~consumed;
+
+ return xkb_state_key_get_syms(keyboard->device->keyboard->xkb_state,
+ keycode, keysyms);
+}
+
+/*
+ * Get keysyms and modifiers from the keyboard as if modifiers didn't change
+ * keysyms.
+ *
+ * This avoids the xkb keysym translation based on modifiers considered pressed
+ * in the state.
+ *
+ * This will trigger keybinds such as Alt+Shift+2.
+ */
+static size_t keyboard_keysyms_raw(struct roots_keyboard *keyboard,
+ xkb_keycode_t keycode, const xkb_keysym_t **keysyms,
+ uint32_t *modifiers) {
+ *modifiers = wlr_keyboard_get_modifiers(keyboard->device->keyboard);
+
+ xkb_layout_index_t layout_index = xkb_state_key_get_layout(
+ keyboard->device->keyboard->xkb_state, keycode);
+ return xkb_keymap_key_get_syms_by_level(keyboard->device->keyboard->keymap,
+ keycode, layout_index, 0, keysyms);
+}
+
+void roots_keyboard_handle_key(struct roots_keyboard *keyboard,
+ struct wlr_event_keyboard_key *event) {
+ xkb_keycode_t keycode = event->keycode + 8;
+
+ bool handled = false;
+ uint32_t modifiers;
+ const xkb_keysym_t *keysyms;
+ size_t keysyms_len;
+
+ // Handle translated keysyms
+
+ keysyms_len = keyboard_keysyms_translated(keyboard, keycode, &keysyms,
+ &modifiers);
+ pressed_keysyms_update(keyboard->pressed_keysyms_translated, keysyms,
+ keysyms_len, event->state);
+ if (event->state == WLR_KEY_PRESSED) {
+ handled = keyboard_execute_binding(keyboard,
+ keyboard->pressed_keysyms_translated, modifiers, keysyms,
+ keysyms_len);
+ }
+
+ // Handle raw keysyms
+ keysyms_len = keyboard_keysyms_raw(keyboard, keycode, &keysyms, &modifiers);
+ pressed_keysyms_update(keyboard->pressed_keysyms_raw, keysyms, keysyms_len,
+ event->state);
+ if (event->state == WLR_KEY_PRESSED && !handled) {
+ handled = keyboard_execute_binding(keyboard,
+ keyboard->pressed_keysyms_raw, modifiers, keysyms, keysyms_len);
+ }
+
+ if (!handled) {
+ wlr_seat_set_keyboard(keyboard->seat->seat, keyboard->device);
+ wlr_seat_keyboard_notify_key(keyboard->seat->seat, event->time_msec,
+ event->keycode, event->state);
+ }
+}
+
+void roots_keyboard_handle_modifiers(struct roots_keyboard *r_keyboard) {
+ struct wlr_seat *seat = r_keyboard->seat->seat;
+ wlr_seat_set_keyboard(seat, r_keyboard->device);
+ wlr_seat_keyboard_notify_modifiers(seat,
+ &r_keyboard->device->keyboard->modifiers);
+}
+
+static void keyboard_config_merge(struct roots_keyboard_config *config,
+ struct roots_keyboard_config *fallback) {
+ if (fallback == NULL) {
+ return;
+ }
+ if (config->rules == NULL) {
+ config->rules = fallback->rules;
+ }
+ if (config->model == NULL) {
+ config->model = fallback->model;
+ }
+ if (config->layout == NULL) {
+ config->layout = fallback->layout;
+ }
+ if (config->variant == NULL) {
+ config->variant = fallback->variant;
+ }
+ if (config->options == NULL) {
+ config->options = fallback->options;
+ }
+ if (config->meta_key == 0) {
+ config->meta_key = fallback->meta_key;
+ }
+ if (config->name == NULL) {
+ config->name = fallback->name;
+ }
+ if (config->repeat_rate <= 0) {
+ config->repeat_rate = fallback->repeat_rate;
+ }
+ if (config->repeat_delay <= 0) {
+ config->repeat_delay = fallback->repeat_delay;
+ }
+}
+
+struct roots_keyboard *roots_keyboard_create(struct wlr_input_device *device,
+ struct roots_input *input) {
+ struct roots_keyboard *keyboard = calloc(sizeof(struct roots_keyboard), 1);
+ if (keyboard == NULL) {
+ return NULL;
+ }
+ device->data = keyboard;
+ keyboard->device = device;
+ keyboard->input = input;
+
+ struct roots_keyboard_config *config =
+ calloc(1, sizeof(struct roots_keyboard_config));
+ if (config == NULL) {
+ free(keyboard);
+ return NULL;
+ }
+ keyboard_config_merge(config, roots_config_get_keyboard(input->config, device));
+ keyboard_config_merge(config, roots_config_get_keyboard(input->config, NULL));
+
+ struct roots_keyboard_config env_config = {
+ .rules = getenv("XKB_DEFAULT_RULES"),
+ .model = getenv("XKB_DEFAULT_MODEL"),
+ .layout = getenv("XKB_DEFAULT_LAYOUT"),
+ .variant = getenv("XKB_DEFAULT_VARIANT"),
+ .options = getenv("XKB_DEFAULT_OPTIONS"),
+ };
+ keyboard_config_merge(config, &env_config);
+ keyboard->config = config;
+
+ struct xkb_rule_names rules = { 0 };
+ rules.rules = config->rules;
+ rules.model = config->model;
+ rules.layout = config->layout;
+ rules.variant = config->variant;
+ rules.options = config->options;
+ struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ if (context == NULL) {
+ wlr_log(WLR_ERROR, "Cannot create XKB context");
+ return NULL;
+ }
+
+ struct xkb_keymap *keymap = xkb_map_new_from_names(context, &rules,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+ if (keymap == NULL) {
+ xkb_context_unref(context);
+ wlr_log(WLR_ERROR, "Cannot create XKB keymap");
+ return NULL;
+ }
+
+ wlr_keyboard_set_keymap(device->keyboard, keymap);
+ xkb_keymap_unref(keymap);
+ xkb_context_unref(context);
+
+ int repeat_rate = (config->repeat_rate > 0) ? config->repeat_rate : 25;
+ int repeat_delay = (config->repeat_delay > 0) ? config->repeat_delay : 600;
+ wlr_keyboard_set_repeat_info(device->keyboard, repeat_rate, repeat_delay);
+
+ return keyboard;
+}
+
+void roots_keyboard_destroy(struct roots_keyboard *keyboard) {
+ wl_list_remove(&keyboard->link);
+ free(keyboard->config);
+ free(keyboard);
+}
diff --git a/rootston/layer_shell.c b/rootston/layer_shell.c
new file mode 100644
index 00000000..4daa20d1
--- /dev/null
+++ b/rootston/layer_shell.c
@@ -0,0 +1,506 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200112L
+#endif
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/types/wlr_layer_shell_v1.h>
+#include <wlr/util/log.h>
+#include "rootston/desktop.h"
+#include "rootston/layers.h"
+#include "rootston/output.h"
+#include "rootston/server.h"
+
+static void apply_exclusive(struct wlr_box *usable_area,
+ uint32_t anchor, int32_t exclusive,
+ int32_t margin_top, int32_t margin_right,
+ int32_t margin_bottom, int32_t margin_left) {
+ if (exclusive <= 0) {
+ return;
+ }
+ struct {
+ uint32_t anchors;
+ int *positive_axis;
+ int *negative_axis;
+ int margin;
+ } edges[] = {
+ {
+ .anchors =
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP,
+ .positive_axis = &usable_area->y,
+ .negative_axis = &usable_area->height,
+ .margin = margin_top,
+ },
+ {
+ .anchors =
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
+ .positive_axis = NULL,
+ .negative_axis = &usable_area->height,
+ .margin = margin_bottom,
+ },
+ {
+ .anchors =
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
+ .positive_axis = &usable_area->x,
+ .negative_axis = &usable_area->width,
+ .margin = margin_left,
+ },
+ {
+ .anchors =
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
+ ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
+ .positive_axis = NULL,
+ .negative_axis = &usable_area->width,
+ .margin = margin_right,
+ },
+ };
+ for (size_t i = 0; i < sizeof(edges) / sizeof(edges[0]); ++i) {
+ if ((anchor & edges[i].anchors) == edges[i].anchors) {
+ if (edges[i].positive_axis) {
+ *edges[i].positive_axis += exclusive + edges[i].margin;
+ }
+ if (edges[i].negative_axis) {
+ *edges[i].negative_axis -= exclusive + edges[i].margin;
+ }
+ }
+ }
+}
+
+static void update_cursors(struct roots_layer_surface *roots_surface,
+ struct wl_list *seats /* struct roots_seat */) {
+ struct roots_seat *seat;
+ wl_list_for_each(seat, seats, link) {
+ double sx, sy;
+
+ struct wlr_surface *surface = desktop_surface_at(
+ seat->input->server->desktop,
+ seat->cursor->cursor->x, seat->cursor->cursor->y, &sx, &sy, NULL);
+
+ if (surface == roots_surface->layer_surface->surface) {
+ struct timespec time;
+ if (clock_gettime(CLOCK_MONOTONIC, &time) == 0) {
+ roots_cursor_update_position(seat->cursor,
+ time.tv_sec * 1000 + time.tv_nsec / 1000000);
+ } else {
+ wlr_log(WLR_ERROR, "Failed to get time, not updating"
+ "position. Errno: %s\n", strerror(errno));
+ }
+ }
+ }
+}
+
+static void arrange_layer(struct wlr_output *output,
+ struct wl_list *seats /* struct *roots_seat */,
+ struct wl_list *list /* struct *roots_layer_surface */,
+ struct wlr_box *usable_area, bool exclusive) {
+ struct roots_layer_surface *roots_surface;
+ struct wlr_box full_area = { 0 };
+ wlr_output_effective_resolution(output,
+ &full_area.width, &full_area.height);
+ wl_list_for_each_reverse(roots_surface, list, link) {
+ struct wlr_layer_surface_v1 *layer = roots_surface->layer_surface;
+ struct wlr_layer_surface_v1_state *state = &layer->current;
+ if (exclusive != (state->exclusive_zone > 0)) {
+ continue;
+ }
+ 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 axis
+ const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
+ | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
+ if ((state->anchor & both_horiz) && box.width == 0) {
+ box.x = bounds.x;
+ box.width = bounds.width;
+ } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) {
+ box.x = bounds.x;
+ } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) {
+ box.x = bounds.x + (bounds.width - box.width);
+ } else {
+ box.x = bounds.x + ((bounds.width / 2) - (box.width / 2));
+ }
+ // Vertical axis
+ const uint32_t both_vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
+ | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
+ if ((state->anchor & both_vert) && box.height == 0) {
+ box.y = bounds.y;
+ box.height = bounds.height;
+ } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) {
+ box.y = bounds.y;
+ } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) {
+ box.y = bounds.y + (bounds.height - box.height);
+ } else {
+ box.y = bounds.y + ((bounds.height / 2) - (box.height / 2));
+ }
+ // Margin
+ if ((state->anchor & both_horiz) == both_horiz) {
+ box.x += state->margin.left;
+ box.width -= state->margin.left + state->margin.right;
+ } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) {
+ box.x += state->margin.left;
+ } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) {
+ box.x -= state->margin.right;
+ }
+ if ((state->anchor & both_vert) == both_vert) {
+ box.y += state->margin.top;
+ box.height -= state->margin.top + state->margin.bottom;
+ } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) {
+ box.y += state->margin.top;
+ } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) {
+ box.y -= state->margin.bottom;
+ }
+ if (box.width < 0 || box.height < 0) {
+ // TODO: Bubble up a protocol error?
+ wlr_layer_surface_v1_close(layer);
+ continue;
+ }
+
+ // Apply
+ struct wlr_box old_geo = roots_surface->geo;
+ roots_surface->geo = box;
+ apply_exclusive(usable_area, state->anchor, state->exclusive_zone,
+ state->margin.top, state->margin.right,
+ state->margin.bottom, state->margin.left);
+ wlr_layer_surface_v1_configure(layer, box.width, box.height);
+
+ // Having a cursor newly end up over the moved layer will not
+ // automatically send a motion event to the surface. The event needs to
+ // be synthesized.
+ // Only update layer surfaces which kept their size (and so buffers) the
+ // same, because those with resized buffers will be handled separately.
+
+ if (roots_surface->geo.x != old_geo.x
+ || roots_surface->geo.y != old_geo.y) {
+ update_cursors(roots_surface, seats);
+ }
+ }
+}
+
+void arrange_layers(struct roots_output *output) {
+ struct wlr_box usable_area = { 0 };
+ wlr_output_effective_resolution(output->wlr_output,
+ &usable_area.width, &usable_area.height);
+
+ // Arrange exclusive surfaces from top->bottom
+ arrange_layer(output->wlr_output, &output->desktop->server->input->seats,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],
+ &usable_area, true);
+ arrange_layer(output->wlr_output, &output->desktop->server->input->seats,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
+ &usable_area, true);
+ arrange_layer(output->wlr_output, &output->desktop->server->input->seats,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
+ &usable_area, true);
+ arrange_layer(output->wlr_output, &output->desktop->server->input->seats,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
+ &usable_area, true);
+ memcpy(&output->usable_area, &usable_area, sizeof(struct wlr_box));
+
+ struct roots_view *view;
+ wl_list_for_each(view, &output->desktop->views, link) {
+ if (view->maximized) {
+ view_arrange_maximized(view);
+ }
+ }
+
+ // Arrange non-exlusive surfaces from top->bottom
+ arrange_layer(output->wlr_output, &output->desktop->server->input->seats,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],
+ &usable_area, false);
+ arrange_layer(output->wlr_output, &output->desktop->server->input->seats,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
+ &usable_area, false);
+ arrange_layer(output->wlr_output, &output->desktop->server->input->seats,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
+ &usable_area, false);
+ arrange_layer(output->wlr_output, &output->desktop->server->input->seats,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
+ &usable_area, false);
+
+ // Find topmost keyboard interactive layer, if such a layer exists
+ uint32_t layers_above_shell[] = {
+ ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY,
+ ZWLR_LAYER_SHELL_V1_LAYER_TOP,
+ };
+ size_t nlayers = sizeof(layers_above_shell) / sizeof(layers_above_shell[0]);
+ struct roots_layer_surface *layer, *topmost = NULL;
+ for (size_t i = 0; i < nlayers; ++i) {
+ wl_list_for_each_reverse(layer,
+ &output->layers[layers_above_shell[i]], link) {
+ if (layer->layer_surface->current.keyboard_interactive) {
+ topmost = layer;
+ break;
+ }
+ }
+ if (topmost != NULL) {
+ break;
+ }
+ }
+
+ struct roots_input *input = output->desktop->server->input;
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &input->seats, link) {
+ roots_seat_set_focus_layer(seat,
+ topmost ? topmost->layer_surface : NULL);
+ }
+}
+
+static void handle_output_destroy(struct wl_listener *listener, void *data) {
+ struct roots_layer_surface *layer =
+ wl_container_of(listener, layer, output_destroy);
+ layer->layer_surface->output = NULL;
+ wl_list_remove(&layer->output_destroy.link);
+ wlr_layer_surface_v1_close(layer->layer_surface);
+}
+
+static void handle_surface_commit(struct wl_listener *listener, void *data) {
+ struct roots_layer_surface *layer =
+ wl_container_of(listener, layer, surface_commit);
+ struct wlr_layer_surface_v1 *layer_surface = layer->layer_surface;
+ struct wlr_output *wlr_output = layer_surface->output;
+ if (wlr_output != NULL) {
+ struct roots_output *output = wlr_output->data;
+ struct wlr_box old_geo = layer->geo;
+ arrange_layers(output);
+
+ // Cursor changes which happen as a consequence of resizing a layer
+ // surface are applied in arrange_layers. Because the resize happens
+ // before the underlying surface changes, it will only receive a cursor
+ // update if the new cursor position crosses the *old* sized surface in
+ // the *new* layer surface.
+ // Another cursor move event is needed when the surface actually
+ // changes.
+ struct wlr_surface *surface = layer_surface->surface;
+ if (surface->previous.width != surface->current.width ||
+ surface->previous.height != surface->current.height) {
+ update_cursors(layer, &output->desktop->server->input->seats);
+ }
+
+ if (memcmp(&old_geo, &layer->geo, sizeof(struct wlr_box)) != 0) {
+ output_damage_whole_local_surface(output, layer_surface->surface,
+ old_geo.x, old_geo.y, 0);
+ output_damage_whole_local_surface(output, layer_surface->surface,
+ layer->geo.x, layer->geo.y, 0);
+ } else {
+ output_damage_from_local_surface(output, layer_surface->surface,
+ layer->geo.x, layer->geo.y, 0);
+ }
+ }
+}
+
+static void unmap(struct wlr_layer_surface_v1 *layer_surface) {
+ struct roots_layer_surface *layer = layer_surface->data;
+ struct wlr_output *wlr_output = layer_surface->output;
+ if (wlr_output != NULL) {
+ struct roots_output *output = wlr_output->data;
+ output_damage_whole_local_surface(output, layer_surface->surface,
+ layer->geo.x, layer->geo.y, 0);
+ }
+}
+
+static void handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_layer_surface *layer = wl_container_of(
+ listener, layer, destroy);
+ if (layer->layer_surface->mapped) {
+ unmap(layer->layer_surface);
+ }
+ wl_list_remove(&layer->link);
+ wl_list_remove(&layer->destroy.link);
+ wl_list_remove(&layer->map.link);
+ wl_list_remove(&layer->unmap.link);
+ wl_list_remove(&layer->surface_commit.link);
+ if (layer->layer_surface->output) {
+ wl_list_remove(&layer->output_destroy.link);
+ arrange_layers((struct roots_output *)layer->layer_surface->output->data);
+ }
+ free(layer);
+}
+
+static void handle_map(struct wl_listener *listener, void *data) {
+ struct wlr_layer_surface_v1 *layer_surface = data;
+ struct roots_layer_surface *layer = layer_surface->data;
+ struct wlr_output *wlr_output = layer_surface->output;
+ struct roots_output *output = wlr_output->data;
+ output_damage_whole_local_surface(output, layer_surface->surface,
+ layer->geo.x, layer->geo.y, 0);
+ wlr_surface_send_enter(layer_surface->surface, wlr_output);
+}
+
+static void handle_unmap(struct wl_listener *listener, void *data) {
+ struct roots_layer_surface *layer = wl_container_of(
+ listener, layer, unmap);
+ struct wlr_output *wlr_output = layer->layer_surface->output;
+ struct roots_output *output = wlr_output->data;
+ unmap(layer->layer_surface);
+ input_update_cursor_focus(output->desktop->server->input);
+}
+
+static void popup_handle_map(struct wl_listener *listener, void *data) {
+ struct roots_layer_popup *popup = wl_container_of(listener, popup, map);
+ struct roots_layer_surface *layer = popup->parent;
+ struct wlr_output *wlr_output = layer->layer_surface->output;
+ struct roots_output *output = wlr_output->data;
+ int ox = popup->wlr_popup->geometry.x + layer->geo.x;
+ int oy = popup->wlr_popup->geometry.y + layer->geo.y;
+ output_damage_whole_local_surface(output, popup->wlr_popup->base->surface,
+ ox, oy, 0);
+ input_update_cursor_focus(output->desktop->server->input);
+}
+
+static void popup_handle_unmap(struct wl_listener *listener, void *data) {
+ struct roots_layer_popup *popup = wl_container_of(listener, popup, unmap);
+ struct roots_layer_surface *layer = popup->parent;
+ struct wlr_output *wlr_output = layer->layer_surface->output;
+ struct roots_output *output = wlr_output->data;
+ int ox = popup->wlr_popup->geometry.x + layer->geo.x;
+ int oy = popup->wlr_popup->geometry.y + layer->geo.y;
+ output_damage_whole_local_surface(output, popup->wlr_popup->base->surface,
+ ox, oy, 0);
+}
+
+static void popup_handle_commit(struct wl_listener *listener, void *data) {
+ struct roots_layer_popup *popup = wl_container_of(listener, popup, commit);
+ struct roots_layer_surface *layer = popup->parent;
+ struct wlr_output *wlr_output = layer->layer_surface->output;
+ struct roots_output *output = wlr_output->data;
+ int ox = popup->wlr_popup->geometry.x + layer->geo.x;
+ int oy = popup->wlr_popup->geometry.y + layer->geo.y;
+ output_damage_from_local_surface(output, popup->wlr_popup->base->surface,
+ ox, oy, 0);
+}
+
+static void popup_handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_layer_popup *popup =
+ wl_container_of(listener, popup, destroy);
+
+ wl_list_remove(&popup->map.link);
+ wl_list_remove(&popup->unmap.link);
+ wl_list_remove(&popup->destroy.link);
+ wl_list_remove(&popup->commit.link);
+ free(popup);
+}
+
+static struct roots_layer_popup *popup_create(struct roots_layer_surface *parent,
+ struct wlr_xdg_popup *wlr_popup) {
+ struct roots_layer_popup *popup =
+ calloc(1, sizeof(struct roots_layer_popup));
+ if (popup == NULL) {
+ return NULL;
+ }
+ popup->wlr_popup = wlr_popup;
+ popup->parent = parent;
+ popup->map.notify = popup_handle_map;
+ wl_signal_add(&wlr_popup->base->events.map, &popup->map);
+ popup->unmap.notify = popup_handle_unmap;
+ wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap);
+ popup->destroy.notify = popup_handle_destroy;
+ wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy);
+ popup->commit.notify = popup_handle_commit;
+ wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit);
+ /* TODO: popups can have popups, see xdg_shell::popup_create */
+
+ return popup;
+}
+
+static void handle_new_popup(struct wl_listener *listener, void *data) {
+ struct roots_layer_surface *roots_layer_surface =
+ wl_container_of(listener, roots_layer_surface, new_popup);
+ struct wlr_xdg_popup *wlr_popup = data;
+ popup_create(roots_layer_surface, wlr_popup);
+}
+
+void handle_layer_shell_surface(struct wl_listener *listener, void *data) {
+ struct wlr_layer_surface_v1 *layer_surface = data;
+ struct roots_desktop *desktop =
+ wl_container_of(listener, desktop, layer_shell_surface);
+ wlr_log(WLR_DEBUG, "new layer surface: namespace %s layer %d anchor %d "
+ "size %dx%d margin %d,%d,%d,%d",
+ layer_surface->namespace, layer_surface->layer, layer_surface->layer,
+ layer_surface->client_pending.desired_width,
+ layer_surface->client_pending.desired_height,
+ layer_surface->client_pending.margin.top,
+ layer_surface->client_pending.margin.right,
+ layer_surface->client_pending.margin.bottom,
+ layer_surface->client_pending.margin.left);
+
+ if (!layer_surface->output) {
+ struct roots_input *input = desktop->server->input;
+ struct roots_seat *seat = input_last_active_seat(input);
+ assert(seat); // Technically speaking we should handle this case
+ struct wlr_output *output =
+ wlr_output_layout_output_at(desktop->layout,
+ seat->cursor->cursor->x,
+ seat->cursor->cursor->y);
+ if (!output) {
+ wlr_log(WLR_ERROR, "Couldn't find output at (%.0f,%.0f)",
+ seat->cursor->cursor->x,
+ seat->cursor->cursor->y);
+ output = wlr_output_layout_get_center_output(desktop->layout);
+ }
+ if (output) {
+ layer_surface->output = output;
+ } else {
+ wlr_layer_surface_v1_close(layer_surface);
+ return;
+ }
+ }
+
+ struct roots_layer_surface *roots_surface =
+ calloc(1, sizeof(struct roots_layer_surface));
+ if (!roots_surface) {
+ return;
+ }
+
+ roots_surface->surface_commit.notify = handle_surface_commit;
+ wl_signal_add(&layer_surface->surface->events.commit,
+ &roots_surface->surface_commit);
+
+ roots_surface->output_destroy.notify = handle_output_destroy;
+ wl_signal_add(&layer_surface->output->events.destroy,
+ &roots_surface->output_destroy);
+
+ roots_surface->destroy.notify = handle_destroy;
+ wl_signal_add(&layer_surface->events.destroy, &roots_surface->destroy);
+ roots_surface->map.notify = handle_map;
+ wl_signal_add(&layer_surface->events.map, &roots_surface->map);
+ roots_surface->unmap.notify = handle_unmap;
+ wl_signal_add(&layer_surface->events.unmap, &roots_surface->unmap);
+ roots_surface->new_popup.notify = handle_new_popup;
+ wl_signal_add(&layer_surface->events.new_popup, &roots_surface->new_popup);
+ // TODO: Listen for subsurfaces
+
+ roots_surface->layer_surface = layer_surface;
+ layer_surface->data = roots_surface;
+
+ struct roots_output *output = layer_surface->output->data;
+ wl_list_insert(&output->layers[layer_surface->layer], &roots_surface->link);
+
+ // Temporarily set the layer's current state to client_pending
+ // So that we can easily arrange it
+ struct wlr_layer_surface_v1_state old_state = layer_surface->current;
+ layer_surface->current = layer_surface->client_pending;
+
+ arrange_layers(output);
+
+ layer_surface->current = old_state;
+}
diff --git a/rootston/main.c b/rootston/main.c
new file mode 100644
index 00000000..7e25dab1
--- /dev/null
+++ b/rootston/main.c
@@ -0,0 +1,81 @@
+#define _POSIX_C_SOURCE 200112L
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/backend.h>
+#include <wlr/backend/headless.h>
+#include <wlr/backend/multi.h>
+#include <wlr/config.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/util/log.h>
+#include "rootston/config.h"
+#include "rootston/server.h"
+
+struct roots_server server = { 0 };
+
+int main(int argc, char **argv) {
+ wlr_log_init(WLR_DEBUG, NULL);
+ server.config = roots_config_create_from_args(argc, argv);
+ server.wl_display = wl_display_create();
+ server.wl_event_loop = wl_display_get_event_loop(server.wl_display);
+ assert(server.config && server.wl_display && server.wl_event_loop);
+
+ server.backend = wlr_backend_autocreate(server.wl_display, NULL);
+ if (server.backend == NULL) {
+ wlr_log(WLR_ERROR, "could not start backend");
+ return 1;
+ }
+
+ server.renderer = wlr_backend_get_renderer(server.backend);
+ assert(server.renderer);
+ server.data_device_manager =
+ wlr_data_device_manager_create(server.wl_display);
+ wlr_renderer_init_wl_display(server.renderer, server.wl_display);
+ server.desktop = desktop_create(&server, server.config);
+ server.input = input_create(&server, server.config);
+
+ const char *socket = wl_display_add_socket_auto(server.wl_display);
+ if (!socket) {
+ wlr_log_errno(WLR_ERROR, "Unable to open wayland socket");
+ wlr_backend_destroy(server.backend);
+ return 1;
+ }
+
+ wlr_log(WLR_INFO, "Running compositor on wayland display '%s'", socket);
+ setenv("_WAYLAND_DISPLAY", socket, true);
+
+ if (!wlr_backend_start(server.backend)) {
+ wlr_log(WLR_ERROR, "Failed to start backend");
+ wlr_backend_destroy(server.backend);
+ wl_display_destroy(server.wl_display);
+ return 1;
+ }
+
+ setenv("WAYLAND_DISPLAY", socket, true);
+#if WLR_HAS_XWAYLAND
+ if (server.desktop->xwayland != NULL) {
+ struct roots_seat *xwayland_seat =
+ input_get_seat(server.input, ROOTS_CONFIG_DEFAULT_SEAT_NAME);
+ wlr_xwayland_set_seat(server.desktop->xwayland, xwayland_seat->seat);
+ }
+#endif
+
+ if (server.config->startup_cmd != NULL) {
+ const char *cmd = server.config->startup_cmd;
+ pid_t pid = fork();
+ if (pid < 0) {
+ wlr_log(WLR_ERROR, "cannot execute binding command: fork() failed");
+ } else if (pid == 0) {
+ execl("/bin/sh", "/bin/sh", "-c", cmd, (void *)NULL);
+ }
+ }
+
+ wl_display_run(server.wl_display);
+#if WLR_HAS_XWAYLAND
+ wlr_xwayland_destroy(server.desktop->xwayland);
+#endif
+ wl_display_destroy_clients(server.wl_display);
+ wl_display_destroy(server.wl_display);
+ return 0;
+}
diff --git a/rootston/meson.build b/rootston/meson.build
new file mode 100644
index 00000000..db90a508
--- /dev/null
+++ b/rootston/meson.build
@@ -0,0 +1,30 @@
+sources = [
+ 'bindings.c',
+ 'config.c',
+ 'cursor.c',
+ 'desktop.c',
+ 'ini.c',
+ 'input.c',
+ 'keyboard.c',
+ 'layer_shell.c',
+ 'main.c',
+ 'output.c',
+ 'seat.c',
+ 'switch.c',
+ 'text_input.c',
+ 'virtual_keyboard.c',
+ 'wl_shell.c',
+ 'xdg_shell.c',
+ 'xdg_shell_v6.c',
+]
+
+if conf_data.get('WLR_HAS_XWAYLAND', 0) == 1
+ sources += 'xwayland.c'
+endif
+
+executable(
+ 'rootston',
+ sources,
+ dependencies: [wlroots, wlr_protos, pixman],
+ build_by_default: get_option('rootston'),
+)
diff --git a/rootston/output.c b/rootston/output.c
new file mode 100644
index 00000000..f950d4dc
--- /dev/null
+++ b/rootston/output.c
@@ -0,0 +1,918 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <time.h>
+#include <wlr/backend/drm.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_presentation_time.h>
+#include <wlr/types/wlr_wl_shell.h>
+#include <wlr/types/wlr_xdg_shell_v6.h>
+#include <wlr/types/wlr_xdg_shell.h>
+#include <wlr/util/log.h>
+#include <wlr/util/region.h>
+#include "rootston/config.h"
+#include "rootston/layers.h"
+#include "rootston/output.h"
+#include "rootston/server.h"
+
+/**
+ * Rotate a child's position relative to a parent. The parent size is (pw, ph),
+ * the child position is (*sx, *sy) and its size is (sw, sh).
+ */
+void rotate_child_position(double *sx, double *sy, double sw, double sh,
+ double pw, double ph, float rotation) {
+ if (rotation != 0.0) {
+ // Coordinates relative to the center of the subsurface
+ double ox = *sx - pw/2 + sw/2,
+ oy = *sy - ph/2 + sh/2;
+ // Rotated coordinates
+ double rx = cos(rotation)*ox - sin(rotation)*oy,
+ ry = cos(rotation)*oy + sin(rotation)*ox;
+ *sx = rx + pw/2 - sw/2;
+ *sy = ry + ph/2 - sh/2;
+ }
+}
+
+struct layout_data {
+ double x, y;
+ int width, height;
+ float rotation;
+};
+
+static void get_layout_position(struct layout_data *data, double *lx, double *ly,
+ const struct wlr_surface *surface, int sx, int sy) {
+ double _sx = sx, _sy = sy;
+ rotate_child_position(&_sx, &_sy, surface->current.width,
+ surface->current.height, data->width, data->height, data->rotation);
+ *lx = data->x + _sx;
+ *ly = data->y + _sy;
+}
+
+static void surface_for_each_surface(struct wlr_surface *surface,
+ double lx, double ly, float rotation, struct layout_data *layout_data,
+ wlr_surface_iterator_func_t iterator, void *user_data) {
+ layout_data->x = lx;
+ layout_data->y = ly;
+ layout_data->width = surface->current.width;
+ layout_data->height = surface->current.height;
+ layout_data->rotation = rotation;
+
+ wlr_surface_for_each_surface(surface, iterator, user_data);
+}
+
+static void view_for_each_surface(struct roots_view *view,
+ struct layout_data *layout_data, wlr_surface_iterator_func_t iterator,
+ void *user_data) {
+ layout_data->x = view->box.x;
+ layout_data->y = view->box.y;
+ layout_data->width = view->wlr_surface->current.width;
+ layout_data->height = view->wlr_surface->current.height;
+ layout_data->rotation = view->rotation;
+
+ switch (view->type) {
+ case ROOTS_XDG_SHELL_V6_VIEW:
+ wlr_xdg_surface_v6_for_each_surface(view->xdg_surface_v6, iterator,
+ user_data);
+ break;
+ case ROOTS_XDG_SHELL_VIEW:
+ wlr_xdg_surface_for_each_surface(view->xdg_surface, iterator,
+ user_data);
+ break;
+ case ROOTS_WL_SHELL_VIEW:
+ wlr_wl_shell_surface_for_each_surface(view->wl_shell_surface, iterator,
+ user_data);
+ break;
+#if WLR_HAS_XWAYLAND
+ case ROOTS_XWAYLAND_VIEW:
+ wlr_surface_for_each_surface(view->wlr_surface, iterator, user_data);
+ break;
+#endif
+ }
+}
+
+#if WLR_HAS_XWAYLAND
+static void xwayland_children_for_each_surface(
+ struct wlr_xwayland_surface *surface,
+ wlr_surface_iterator_func_t iterator, struct layout_data *layout_data,
+ void *user_data) {
+ struct wlr_xwayland_surface *child;
+ wl_list_for_each(child, &surface->children, parent_link) {
+ if (child->mapped) {
+ surface_for_each_surface(child->surface, child->x, child->y, 0,
+ layout_data, iterator, user_data);
+ }
+ xwayland_children_for_each_surface(child, iterator, layout_data,
+ user_data);
+ }
+}
+#endif
+
+static void drag_icons_for_each_surface(struct roots_input *input,
+ wlr_surface_iterator_func_t iterator, struct layout_data *layout_data,
+ void *user_data) {
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &input->seats, link) {
+ struct roots_drag_icon *drag_icon;
+ wl_list_for_each(drag_icon, &seat->drag_icons, link) {
+ if (!drag_icon->wlr_drag_icon->mapped) {
+ continue;
+ }
+ surface_for_each_surface(drag_icon->wlr_drag_icon->surface,
+ drag_icon->x, drag_icon->y, 0, layout_data,
+ iterator, user_data);
+ }
+ }
+}
+
+static void layer_for_each_surface(struct wl_list *layer,
+ const struct wlr_box *output_layout_box,
+ wlr_surface_iterator_func_t iterator, struct layout_data *layout_data,
+ void *user_data) {
+ struct roots_layer_surface *roots_surface;
+ wl_list_for_each(roots_surface, layer, link) {
+ struct wlr_layer_surface_v1 *layer = roots_surface->layer_surface;
+
+ layout_data->x = roots_surface->geo.x + output_layout_box->x;
+ layout_data->y = roots_surface->geo.y + output_layout_box->y;
+ layout_data->width = roots_surface->geo.width;
+ layout_data->height = roots_surface->geo.height;
+ layout_data->rotation = 0;
+ wlr_layer_surface_v1_for_each_surface(layer, iterator, user_data);
+ }
+}
+
+static void output_for_each_surface(struct roots_output *output,
+ wlr_surface_iterator_func_t iterator, struct layout_data *layout_data,
+ void *user_data) {
+ struct wlr_output *wlr_output = output->wlr_output;
+ struct roots_desktop *desktop = output->desktop;
+ struct roots_server *server = desktop->server;
+
+ const struct wlr_box *output_box =
+ wlr_output_layout_get_box(desktop->layout, wlr_output);
+
+ if (output->fullscreen_view != NULL) {
+ struct roots_view *view = output->fullscreen_view;
+
+ view_for_each_surface(view, layout_data, iterator, user_data);
+
+#if WLR_HAS_XWAYLAND
+ if (view->type == ROOTS_XWAYLAND_VIEW) {
+ xwayland_children_for_each_surface(view->xwayland_surface,
+ iterator, layout_data, user_data);
+ }
+#endif
+ } else {
+ struct roots_view *view;
+ wl_list_for_each_reverse(view, &desktop->views, link) {
+ view_for_each_surface(view, layout_data, iterator, user_data);
+ }
+
+ drag_icons_for_each_surface(server->input, iterator,
+ layout_data, user_data);
+ }
+
+ size_t len = sizeof(output->layers) / sizeof(output->layers[0]);
+ for (size_t i = 0; i < len; ++i) {
+ layer_for_each_surface(&output->layers[i], output_box,
+ iterator, layout_data, user_data);
+ }
+}
+
+
+struct render_data {
+ struct layout_data layout;
+ struct roots_output *output;
+ struct timespec *when;
+ pixman_region32_t *damage;
+ float alpha;
+};
+
+/**
+ * Checks whether a surface at (lx, ly) intersects an output. If `box` is not
+ * NULL, it populates it with the surface box in the output, in output-local
+ * coordinates.
+ */
+static bool surface_intersect_output(struct wlr_surface *surface,
+ struct wlr_output_layout *output_layout, struct wlr_output *wlr_output,
+ double lx, double ly, float rotation, struct wlr_box *box) {
+ double ox = lx, oy = ly;
+ wlr_output_layout_output_coords(output_layout, wlr_output, &ox, &oy);
+
+ ox += surface->sx;
+ oy += surface->sy;
+
+ if (box != NULL) {
+ box->x = ox * wlr_output->scale;
+ box->y = oy * wlr_output->scale;
+ box->width = surface->current.width * wlr_output->scale;
+ box->height = surface->current.height * wlr_output->scale;
+ }
+
+ struct wlr_box layout_box = {
+ .x = lx, .y = ly,
+ .width = surface->current.width, .height = surface->current.height,
+ };
+ wlr_box_rotated_bounds(&layout_box, &layout_box, rotation);
+ return wlr_output_layout_intersects(output_layout, wlr_output, &layout_box);
+}
+
+static void scissor_output(struct roots_output *output, pixman_box32_t *rect) {
+ struct wlr_output *wlr_output = output->wlr_output;
+ struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend);
+ assert(renderer);
+
+ struct wlr_box box = {
+ .x = rect->x1,
+ .y = rect->y1,
+ .width = rect->x2 - rect->x1,
+ .height = rect->y2 - rect->y1,
+ };
+
+ int ow, oh;
+ wlr_output_transformed_resolution(wlr_output, &ow, &oh);
+
+ enum wl_output_transform transform =
+ wlr_output_transform_invert(wlr_output->transform);
+ wlr_box_transform(&box, &box, transform, ow, oh);
+
+ wlr_renderer_scissor(renderer, &box);
+}
+
+static void render_surface(struct wlr_surface *surface, int sx, int sy,
+ void *_data) {
+ struct render_data *data = _data;
+ struct roots_output *output = data->output;
+ float rotation = data->layout.rotation;
+
+ struct wlr_texture *texture = wlr_surface_get_texture(surface);
+ if (texture == NULL) {
+ return;
+ }
+
+ struct wlr_renderer *renderer =
+ wlr_backend_get_renderer(output->wlr_output->backend);
+ assert(renderer);
+
+ double lx, ly;
+ get_layout_position(&data->layout, &lx, &ly, surface, sx, sy);
+
+ struct wlr_box box;
+ bool intersects = surface_intersect_output(surface, output->desktop->layout,
+ output->wlr_output, lx, ly, rotation, &box);
+ if (!intersects) {
+ return;
+ }
+
+ struct wlr_box rotated;
+ wlr_box_rotated_bounds(&rotated, &box, rotation);
+
+ pixman_region32_t damage;
+ pixman_region32_init(&damage);
+ pixman_region32_union_rect(&damage, &damage, rotated.x, rotated.y,
+ rotated.width, rotated.height);
+ pixman_region32_intersect(&damage, &damage, data->damage);
+ bool damaged = pixman_region32_not_empty(&damage);
+ if (!damaged) {
+ goto damage_finish;
+ }
+
+ float matrix[9];
+ enum wl_output_transform transform =
+ wlr_output_transform_invert(surface->current.transform);
+ wlr_matrix_project_box(matrix, &box, transform, rotation,
+ output->wlr_output->transform_matrix);
+
+ int nrects;
+ pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects);
+ for (int i = 0; i < nrects; ++i) {
+ scissor_output(output, &rects[i]);
+ wlr_render_texture_with_matrix(renderer, texture, matrix, data->alpha);
+ }
+
+damage_finish:
+ pixman_region32_fini(&damage);
+}
+
+static void get_decoration_box(struct roots_view *view,
+ struct roots_output *output, struct wlr_box *box) {
+ struct wlr_output *wlr_output = output->wlr_output;
+
+ struct wlr_box deco_box;
+ view_get_deco_box(view, &deco_box);
+ double sx = deco_box.x - view->box.x;
+ double sy = deco_box.y - view->box.y;
+ rotate_child_position(&sx, &sy, deco_box.width, deco_box.height,
+ view->wlr_surface->current.width,
+ view->wlr_surface->current.height, view->rotation);
+ double x = sx + view->box.x;
+ double y = sy + view->box.y;
+
+ wlr_output_layout_output_coords(output->desktop->layout, wlr_output, &x, &y);
+
+ box->x = x * wlr_output->scale;
+ box->y = y * wlr_output->scale;
+ box->width = deco_box.width * wlr_output->scale;
+ box->height = deco_box.height * wlr_output->scale;
+}
+
+static void render_decorations(struct roots_view *view,
+ struct render_data *data) {
+ if (!view->decorated || view->wlr_surface == NULL) {
+ return;
+ }
+
+ struct roots_output *output = data->output;
+ struct wlr_renderer *renderer =
+ wlr_backend_get_renderer(output->wlr_output->backend);
+ assert(renderer);
+
+ struct wlr_box box;
+ get_decoration_box(view, output, &box);
+
+ struct wlr_box rotated;
+ wlr_box_rotated_bounds(&rotated, &box, view->rotation);
+
+ pixman_region32_t damage;
+ pixman_region32_init(&damage);
+ pixman_region32_union_rect(&damage, &damage, rotated.x, rotated.y,
+ rotated.width, rotated.height);
+ pixman_region32_intersect(&damage, &damage, data->damage);
+ bool damaged = pixman_region32_not_empty(&damage);
+ if (!damaged) {
+ goto damage_finish;
+ }
+
+ float matrix[9];
+ wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL,
+ view->rotation, output->wlr_output->transform_matrix);
+ float color[] = { 0.2, 0.2, 0.2, view->alpha };
+
+ int nrects;
+ pixman_box32_t *rects =
+ pixman_region32_rectangles(&damage, &nrects);
+ for (int i = 0; i < nrects; ++i) {
+ scissor_output(output, &rects[i]);
+ wlr_render_quad_with_matrix(renderer, color, matrix);
+ }
+
+damage_finish:
+ pixman_region32_fini(&damage);
+}
+
+static void render_view(struct roots_view *view, struct render_data *data) {
+ // Do not render views fullscreened on other outputs
+ if (view->fullscreen_output != NULL &&
+ view->fullscreen_output != data->output) {
+ return;
+ }
+
+ data->alpha = view->alpha;
+ render_decorations(view, data);
+ view_for_each_surface(view, &data->layout, render_surface, data);
+}
+
+static void render_layer(struct roots_output *output,
+ const struct wlr_box *output_layout_box, struct render_data *data,
+ struct wl_list *layer) {
+ data->alpha = 1;
+ layer_for_each_surface(layer, output_layout_box, render_surface,
+ &data->layout, data);
+}
+
+static void surface_send_frame_done(struct wlr_surface *surface, int sx, int sy,
+ void *_data) {
+ struct render_data *data = _data;
+ struct roots_output *output = data->output;
+ struct timespec *when = data->when;
+ float rotation = data->layout.rotation;
+
+ double lx, ly;
+ get_layout_position(&data->layout, &lx, &ly, surface, sx, sy);
+
+ if (!surface_intersect_output(surface, output->desktop->layout,
+ output->wlr_output, lx, ly, rotation, NULL)) {
+ return;
+ }
+
+ wlr_surface_send_frame_done(surface, when);
+}
+
+static void render_output(struct roots_output *output) {
+ struct wlr_output *wlr_output = output->wlr_output;
+ struct roots_desktop *desktop = output->desktop;
+ struct roots_server *server = desktop->server;
+ struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend);
+ assert(renderer);
+
+ if (!wlr_output->enabled) {
+ return;
+ }
+
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ float clear_color[] = {0.25f, 0.25f, 0.25f, 1.0f};
+
+ const struct wlr_box *output_box =
+ wlr_output_layout_get_box(desktop->layout, wlr_output);
+
+ // Check if we can delegate the fullscreen surface to the output
+ if (output->fullscreen_view != NULL &&
+ output->fullscreen_view->wlr_surface != NULL) {
+ struct roots_view *view = output->fullscreen_view;
+
+ // Make sure the view is centered on screen
+ struct wlr_box view_box;
+ view_get_box(view, &view_box);
+ double view_x = (double)(output_box->width - view_box.width) / 2 +
+ output_box->x;
+ double view_y = (double)(output_box->height - view_box.height) / 2 +
+ output_box->y;
+ view_move(view, view_x, view_y);
+
+ // Fullscreen views are rendered on a black background
+ clear_color[0] = clear_color[1] = clear_color[2] = 0;
+ }
+
+ bool needs_swap;
+ pixman_region32_t damage;
+ pixman_region32_init(&damage);
+ if (!wlr_output_damage_make_current(output->damage, &needs_swap, &damage)) {
+ return;
+ }
+
+ struct render_data data = {
+ .output = output,
+ .when = &now,
+ .damage = &damage,
+ .alpha = 1.0,
+ };
+
+ if (!needs_swap) {
+ // Output doesn't need swap and isn't damaged, skip rendering completely
+ goto damage_finish;
+ }
+
+ wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height);
+
+ if (!pixman_region32_not_empty(&damage)) {
+ // Output isn't damaged but needs buffer swap
+ goto renderer_end;
+ }
+
+ if (server->config->debug_damage_tracking) {
+ wlr_renderer_clear(renderer, (float[]){1, 1, 0, 1});
+ }
+
+ int nrects;
+ pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects);
+ for (int i = 0; i < nrects; ++i) {
+ scissor_output(output, &rects[i]);
+ wlr_renderer_clear(renderer, clear_color);
+ }
+
+ render_layer(output, output_box, &data,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]);
+ render_layer(output, output_box, &data,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]);
+
+ // If a view is fullscreen on this output, render it
+ if (output->fullscreen_view != NULL) {
+ struct roots_view *view = output->fullscreen_view;
+
+ if (view->wlr_surface != NULL) {
+ view_for_each_surface(view, &data.layout, render_surface, &data);
+ }
+
+ // During normal rendering the xwayland window tree isn't traversed
+ // because all windows are rendered. Here we only want to render
+ // the fullscreen window's children so we have to traverse the tree.
+#if WLR_HAS_XWAYLAND
+ if (view->type == ROOTS_XWAYLAND_VIEW) {
+ xwayland_children_for_each_surface(view->xwayland_surface,
+ render_surface, &data.layout, &data);
+ }
+#endif
+ } else {
+ // Render all views
+ struct roots_view *view;
+ wl_list_for_each_reverse(view, &desktop->views, link) {
+ render_view(view, &data);
+ }
+ // Render top layer above shell views
+ render_layer(output, output_box, &data,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]);
+ }
+
+ // Render drag icons
+ data.alpha = 1.0;
+ drag_icons_for_each_surface(server->input, render_surface, &data.layout,
+ &data);
+
+ render_layer(output, output_box, &data,
+ &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]);
+
+renderer_end:
+ wlr_output_render_software_cursors(wlr_output, &damage);
+ wlr_renderer_scissor(renderer, NULL);
+ wlr_renderer_end(renderer);
+
+ int width, height;
+ wlr_output_transformed_resolution(wlr_output, &width, &height);
+
+ if (server->config->debug_damage_tracking) {
+ pixman_region32_union_rect(&damage, &damage, 0, 0, width, height);
+ }
+
+ enum wl_output_transform transform =
+ wlr_output_transform_invert(wlr_output->transform);
+ wlr_region_transform(&damage, &damage, transform, width, height);
+
+ if (!wlr_output_damage_swap_buffers(output->damage, &now, &damage)) {
+ goto damage_finish;
+ }
+ output->last_frame = desktop->last_frame = now;
+
+damage_finish:
+ pixman_region32_fini(&damage);
+
+ // Send frame done events to all surfaces
+ output_for_each_surface(output, surface_send_frame_done,
+ &data.layout, &data);
+}
+
+void output_damage_whole(struct roots_output *output) {
+ wlr_output_damage_add_whole(output->damage);
+}
+
+static bool view_accept_damage(struct roots_output *output,
+ struct roots_view *view) {
+ if (view->wlr_surface == NULL) {
+ return false;
+ }
+ if (output->fullscreen_view == NULL) {
+ return true;
+ }
+ if (output->fullscreen_view == view) {
+ return true;
+ }
+#if WLR_HAS_XWAYLAND
+ if (output->fullscreen_view->type == ROOTS_XWAYLAND_VIEW &&
+ view->type == ROOTS_XWAYLAND_VIEW) {
+ // Special case: accept damage from children
+ struct wlr_xwayland_surface *xsurface = view->xwayland_surface;
+ while (xsurface != NULL) {
+ if (output->fullscreen_view->xwayland_surface == xsurface) {
+ return true;
+ }
+ xsurface = xsurface->parent;
+ }
+ }
+#endif
+ return false;
+}
+
+struct damage_data {
+ struct layout_data layout;
+ struct roots_output *output;
+};
+
+static void damage_whole_surface(struct wlr_surface *surface, int sx, int sy,
+ void *_data) {
+ struct damage_data *data = _data;
+ struct roots_output *output = data->output;
+ float rotation = data->layout.rotation;
+
+ double lx, ly;
+ get_layout_position(&data->layout, &lx, &ly, surface, sx, sy);
+
+ if (!wlr_surface_has_buffer(surface)) {
+ return;
+ }
+
+ int ow, oh;
+ wlr_output_transformed_resolution(output->wlr_output, &ow, &oh);
+
+ struct wlr_box box;
+ bool intersects = surface_intersect_output(surface, output->desktop->layout,
+ output->wlr_output, lx, ly, rotation, &box);
+ if (!intersects) {
+ return;
+ }
+
+ wlr_box_rotated_bounds(&box, &box, rotation);
+
+ wlr_output_damage_add_box(output->damage, &box);
+}
+
+void output_damage_whole_local_surface(struct roots_output *output,
+ struct wlr_surface *surface, double ox, double oy, float rotation) {
+ struct wlr_output_layout_output *layout = wlr_output_layout_get(
+ output->desktop->layout, output->wlr_output);
+ struct damage_data data = { .output = output };
+ surface_for_each_surface(surface, ox + layout->x, oy + layout->y, 0,
+ &data.layout, damage_whole_surface, &data);
+}
+
+static void damage_whole_decoration(struct roots_view *view,
+ struct roots_output *output) {
+ if (!view->decorated || view->wlr_surface == NULL) {
+ return;
+ }
+
+ struct wlr_box box;
+ get_decoration_box(view, output, &box);
+
+ wlr_box_rotated_bounds(&box, &box, view->rotation);
+
+ wlr_output_damage_add_box(output->damage, &box);
+}
+
+void output_damage_whole_view(struct roots_output *output,
+ struct roots_view *view) {
+ if (!view_accept_damage(output, view)) {
+ return;
+ }
+
+ damage_whole_decoration(view, output);
+
+ struct damage_data data = { .output = output };
+ view_for_each_surface(view, &data.layout, damage_whole_surface, &data);
+}
+
+void output_damage_whole_drag_icon(struct roots_output *output,
+ struct roots_drag_icon *icon) {
+ struct damage_data data = { .output = output };
+ surface_for_each_surface(icon->wlr_drag_icon->surface, icon->x, icon->y, 0,
+ &data.layout, damage_whole_surface, &data);
+}
+
+static void damage_from_surface(struct wlr_surface *surface, int sx, int sy,
+ void *_data) {
+ struct damage_data *data = _data;
+ struct roots_output *output = data->output;
+ struct wlr_output *wlr_output = output->wlr_output;
+ float rotation = data->layout.rotation;
+
+ double lx, ly;
+ get_layout_position(&data->layout, &lx, &ly, surface, sx, sy);
+
+ if (!wlr_surface_has_buffer(surface)) {
+ return;
+ }
+
+ int ow, oh;
+ wlr_output_transformed_resolution(wlr_output, &ow, &oh);
+
+ struct wlr_box box;
+ surface_intersect_output(surface, output->desktop->layout,
+ wlr_output, lx, ly, rotation, &box);
+
+ int center_x = box.x + box.width/2;
+ int center_y = box.y + box.height/2;
+
+ pixman_region32_t damage;
+ pixman_region32_init(&damage);
+ wlr_surface_get_effective_damage(surface, &damage);
+
+ wlr_region_scale(&damage, &damage, wlr_output->scale);
+ if (ceil(wlr_output->scale) > surface->current.scale) {
+ // When scaling up a surface, it'll become blurry so we need to
+ // expand the damage region
+ wlr_region_expand(&damage, &damage,
+ ceil(wlr_output->scale) - surface->current.scale);
+ }
+ pixman_region32_translate(&damage, box.x, box.y);
+ wlr_region_rotated_bounds(&damage, &damage, rotation, center_x, center_y);
+ wlr_output_damage_add(output->damage, &damage);
+ pixman_region32_fini(&damage);
+}
+
+void output_damage_from_local_surface(struct roots_output *output,
+ struct wlr_surface *surface, double ox, double oy, float rotation) {
+ struct wlr_output_layout_output *layout = wlr_output_layout_get(
+ output->desktop->layout, output->wlr_output);
+ struct damage_data data = { .output = output };
+ surface_for_each_surface(surface, ox + layout->x, oy + layout->y, 0,
+ &data.layout, damage_from_surface, &data);
+}
+
+void output_damage_from_view(struct roots_output *output,
+ struct roots_view *view) {
+ if (!view_accept_damage(output, view)) {
+ return;
+ }
+
+ struct damage_data data = { .output = output };
+ view_for_each_surface(view, &data.layout, damage_from_surface, &data);
+}
+
+static void set_mode(struct wlr_output *output,
+ struct roots_output_config *oc) {
+ int mhz = (int)(oc->mode.refresh_rate * 1000);
+
+ if (wl_list_empty(&output->modes)) {
+ // Output has no mode, try setting a custom one
+ wlr_output_set_custom_mode(output, oc->mode.width, oc->mode.height, mhz);
+ return;
+ }
+
+ struct wlr_output_mode *mode, *best = NULL;
+ wl_list_for_each(mode, &output->modes, link) {
+ if (mode->width == oc->mode.width && mode->height == oc->mode.height) {
+ if (mode->refresh == mhz) {
+ best = mode;
+ break;
+ }
+ best = mode;
+ }
+ }
+ if (!best) {
+ wlr_log(WLR_ERROR, "Configured mode for %s not available", output->name);
+ } else {
+ wlr_log(WLR_DEBUG, "Assigning configured mode to %s", output->name);
+ wlr_output_set_mode(output, best);
+ }
+}
+
+static void output_destroy(struct roots_output *output) {
+ // TODO: cursor
+ //example_config_configure_cursor(sample->config, sample->cursor,
+ // sample->compositor);
+
+ wl_list_remove(&output->link);
+ wl_list_remove(&output->destroy.link);
+ wl_list_remove(&output->mode.link);
+ wl_list_remove(&output->transform.link);
+ wl_list_remove(&output->present.link);
+ wl_list_remove(&output->damage_frame.link);
+ wl_list_remove(&output->damage_destroy.link);
+ free(output);
+}
+
+static void output_handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_output *output = wl_container_of(listener, output, destroy);
+ output_destroy(output);
+}
+
+static void output_damage_handle_frame(struct wl_listener *listener,
+ void *data) {
+ struct roots_output *output =
+ wl_container_of(listener, output, damage_frame);
+ render_output(output);
+}
+
+static void output_damage_handle_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_output *output =
+ wl_container_of(listener, output, damage_destroy);
+ output_destroy(output);
+}
+
+static void output_handle_mode(struct wl_listener *listener, void *data) {
+ struct roots_output *output =
+ wl_container_of(listener, output, mode);
+ arrange_layers(output);
+}
+
+static void output_handle_transform(struct wl_listener *listener, void *data) {
+ struct roots_output *output =
+ wl_container_of(listener, output, transform);
+ arrange_layers(output);
+}
+
+struct presentation_data {
+ struct layout_data layout;
+ struct roots_output *output;
+ struct wlr_presentation_event *event;
+};
+
+static void surface_send_presented(struct wlr_surface *surface, int sx, int sy,
+ void *_data) {
+ struct presentation_data *data = _data;
+ struct roots_output *output = data->output;
+ float rotation = data->layout.rotation;
+
+ double lx, ly;
+ get_layout_position(&data->layout, &lx, &ly, surface, sx, sy);
+
+ if (!surface_intersect_output(surface, output->desktop->layout,
+ output->wlr_output, lx, ly, rotation, NULL)) {
+ return;
+ }
+
+ wlr_presentation_send_surface_presented(output->desktop->presentation,
+ surface, data->event);
+}
+
+static void output_handle_present(struct wl_listener *listener, void *data) {
+ struct roots_output *output =
+ wl_container_of(listener, output, present);
+ struct wlr_output_event_present *output_event = data;
+
+ struct wlr_presentation_event event = {
+ .output = output->wlr_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,
+ };
+
+ struct presentation_data presentation_data = {
+ .output = output,
+ .event = &event,
+ };
+ output_for_each_surface(output, surface_send_presented,
+ &presentation_data.layout, &presentation_data);
+}
+
+void handle_new_output(struct wl_listener *listener, void *data) {
+ struct roots_desktop *desktop = wl_container_of(listener, desktop,
+ new_output);
+ struct wlr_output *wlr_output = data;
+ struct roots_input *input = desktop->server->input;
+ struct roots_config *config = desktop->config;
+
+ wlr_log(WLR_DEBUG, "Output '%s' added", wlr_output->name);
+ wlr_log(WLR_DEBUG, "'%s %s %s' %"PRId32"mm x %"PRId32"mm", wlr_output->make,
+ wlr_output->model, wlr_output->serial, wlr_output->phys_width,
+ wlr_output->phys_height);
+
+ struct roots_output *output = calloc(1, sizeof(struct roots_output));
+ clock_gettime(CLOCK_MONOTONIC, &output->last_frame);
+ output->desktop = desktop;
+ output->wlr_output = wlr_output;
+ wlr_output->data = output;
+ wl_list_insert(&desktop->outputs, &output->link);
+
+ output->damage = wlr_output_damage_create(wlr_output);
+
+ output->destroy.notify = output_handle_destroy;
+ wl_signal_add(&wlr_output->events.destroy, &output->destroy);
+ output->mode.notify = output_handle_mode;
+ wl_signal_add(&wlr_output->events.mode, &output->mode);
+ output->transform.notify = output_handle_transform;
+ wl_signal_add(&wlr_output->events.transform, &output->transform);
+ output->present.notify = output_handle_present;
+ wl_signal_add(&wlr_output->events.present, &output->present);
+
+ output->damage_frame.notify = output_damage_handle_frame;
+ wl_signal_add(&output->damage->events.frame, &output->damage_frame);
+ output->damage_destroy.notify = output_damage_handle_destroy;
+ wl_signal_add(&output->damage->events.destroy, &output->damage_destroy);
+
+ size_t len = sizeof(output->layers) / sizeof(output->layers[0]);
+ for (size_t i = 0; i < len; ++i) {
+ wl_list_init(&output->layers[i]);
+ }
+
+ struct roots_output_config *output_config =
+ roots_config_get_output(config, wlr_output);
+
+ if ((!output_config || output_config->enable) && !wl_list_empty(&wlr_output->modes)) {
+ struct wlr_output_mode *mode =
+ wl_container_of(wlr_output->modes.prev, mode, link);
+ wlr_output_set_mode(wlr_output, mode);
+ }
+
+ if (output_config) {
+ if (output_config->enable) {
+ if (wlr_output_is_drm(wlr_output)) {
+ struct roots_output_mode_config *mode_config;
+ wl_list_for_each(mode_config, &output_config->modes, link) {
+ wlr_drm_connector_add_mode(wlr_output, &mode_config->info);
+ }
+ } else if (!wl_list_empty(&output_config->modes)) {
+ wlr_log(WLR_ERROR, "Can only add modes for DRM backend");
+ }
+
+ if (output_config->mode.width) {
+ set_mode(wlr_output, output_config);
+ }
+
+ wlr_output_set_scale(wlr_output, output_config->scale);
+ wlr_output_set_transform(wlr_output, output_config->transform);
+ wlr_output_layout_add(desktop->layout, wlr_output, output_config->x,
+ output_config->y);
+ } else {
+ wlr_output_enable(wlr_output, false);
+ }
+ } else {
+ wlr_output_layout_add_auto(desktop->layout, wlr_output);
+ }
+
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &input->seats, link) {
+ roots_seat_configure_cursor(seat);
+ roots_seat_configure_xcursor(seat);
+ }
+
+ arrange_layers(output);
+ output_damage_whole(output);
+}
diff --git a/rootston/rootston.ini.example b/rootston/rootston.ini.example
new file mode 100644
index 00000000..4b75e9c6
--- /dev/null
+++ b/rootston/rootston.ini.example
@@ -0,0 +1,63 @@
+[core]
+# X11 support
+# - true: enables X11, xwayland is started only when an X11 client connects
+# - immediate: enables X11, xwayland is started immediately
+# - false: disables xwayland
+xwayland=false
+
+# Single output configuration. String after colon must match output's name.
+[output:VGA-1]
+# Set logical (layout) coordinates for this screen
+x = 1920
+y = 0
+
+# Screen transformation
+# possible values are:
+# '90', '180' or '270' - rotate output by specified angle clockwise
+# 'flipped' - flip output horizontally
+# 'flipped-90', 'flipped-180', 'flipped-270' - flip output horizontally
+# and rotate by specified angle
+rotate = 90
+
+# Additional video mode to add
+# Format is generated by cvt and is documented in x.org.conf(5)
+modeline = 87.25 720 776 848 976 1440 1443 1453 1493 -hsync +vsync
+modeline = 65.13 768 816 896 1024 1024 1025 1028 1060 -HSync +VSync
+# Select one of the above modes
+mode = 768x1024
+
+[cursor]
+# Restrict cursor movements to single output
+map-to-output = VGA-1
+# Restrict cursor movements to concrete rectangle
+geometry = 2500x800
+# Load a custom XCursor theme
+theme = default
+
+# Single device configuration. String after colon must match device's name.
+[device:PixArt Dell MS116 USB Optical Mouse]
+# Restrict cursor movements for this mouse to single output
+map-to-output = VGA-1
+# Restrict cursor movements for this mouse to concrete rectangle
+geometry = 2500x800
+# tap_enabled=true
+
+[keyboard]
+meta-key = Logo
+
+# Keybindings
+# Maps key combinations with commands to execute
+# Commands include:
+# - "exit" to stop the compositor
+# - "exec" to execute a shell command
+# - "close" to close the current view
+# - "next_window" to cycle through windows
+# - "alpha" to cycle a window's alpha channel
+# - "break_pointer_constraint" to decline and deactivate all pointer constraints
+[bindings]
+Logo+Shift+e = exit
+Logo+q = close
+Logo+m = maximize
+Logo+Escape = break_pointer_constraint
+Alt+Tab = next_window
+Ctrl+Shift+a = alpha
diff --git a/rootston/seat.c b/rootston/seat.c
new file mode 100644
index 00000000..dda2f8df
--- /dev/null
+++ b/rootston/seat.c
@@ -0,0 +1,1473 @@
+#define _POSIX_C_SOURCE 200112L
+#include <assert.h>
+#include <libinput.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wlr/backend/libinput.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_idle.h>
+#include <wlr/types/wlr_layer_shell_v1.h>
+#include "wlr/types/wlr_switch.h"
+#include <wlr/types/wlr_tablet_v2.h>
+#include <wlr/types/wlr_xcursor_manager.h>
+#include <wlr/util/log.h>
+#include "rootston/cursor.h"
+#include "rootston/input.h"
+#include "rootston/keyboard.h"
+#include "rootston/seat.h"
+#include "rootston/text_input.h"
+#include "rootston/xcursor.h"
+
+
+static void handle_keyboard_key(struct wl_listener *listener, void *data) {
+ struct roots_keyboard *keyboard =
+ wl_container_of(listener, keyboard, keyboard_key);
+ struct roots_desktop *desktop = keyboard->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, keyboard->seat->seat);
+ struct wlr_event_keyboard_key *event = data;
+ roots_keyboard_handle_key(keyboard, event);
+}
+
+static void handle_keyboard_modifiers(struct wl_listener *listener,
+ void *data) {
+ struct roots_keyboard *keyboard =
+ wl_container_of(listener, keyboard, keyboard_modifiers);
+ struct roots_desktop *desktop = keyboard->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, keyboard->seat->seat);
+ roots_keyboard_handle_modifiers(keyboard);
+}
+
+static void handle_cursor_motion(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, motion);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_pointer_motion *event = data;
+ roots_cursor_handle_motion(cursor, event);
+}
+
+static void handle_cursor_motion_absolute(struct wl_listener *listener,
+ void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, motion_absolute);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_pointer_motion_absolute *event = data;
+ roots_cursor_handle_motion_absolute(cursor, event);
+}
+
+static void handle_cursor_button(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, button);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_pointer_button *event = data;
+ roots_cursor_handle_button(cursor, event);
+}
+
+static void handle_cursor_axis(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, axis);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_pointer_axis *event = data;
+ roots_cursor_handle_axis(cursor, event);
+}
+
+static void handle_switch_toggle(struct wl_listener *listener, void *data) {
+ struct roots_switch *lid_switch =
+ wl_container_of(listener, lid_switch, toggle);
+ struct roots_desktop *desktop = lid_switch->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, lid_switch->seat->seat);
+ struct wlr_event_switch_toggle *event = data;
+ roots_switch_handle_toggle(lid_switch, event);
+}
+
+static void handle_touch_down(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, touch_down);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_touch_down *event = data;
+ roots_cursor_handle_touch_down(cursor, event);
+}
+
+static void handle_touch_up(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, touch_up);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_touch_up *event = data;
+ roots_cursor_handle_touch_up(cursor, event);
+}
+
+static void handle_touch_motion(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, touch_motion);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_touch_motion *event = data;
+ roots_cursor_handle_touch_motion(cursor, event);
+}
+
+static void handle_tablet_tool_position(struct roots_cursor *cursor,
+ struct roots_tablet *tablet,
+ struct wlr_tablet_tool *tool,
+ bool change_x, bool change_y,
+ double x, double y, double dx, double dy) {
+ if (!change_x && !change_y) {
+ return;
+ }
+
+ switch (tool->type) {
+ case WLR_TABLET_TOOL_TYPE_MOUSE:
+ // They are 0 either way when they weren't modified
+ wlr_cursor_move(cursor->cursor, tablet->device, dx, dy);
+ break;
+ default:
+ wlr_cursor_warp_absolute(cursor->cursor, tablet->device,
+ change_x ? x : NAN, change_y ? y : NAN);
+ }
+
+ double sx, sy;
+ struct roots_view *view = NULL;
+ struct roots_seat *seat = cursor->seat;
+ struct roots_desktop *desktop = seat->input->server->desktop;
+ struct wlr_surface *surface = desktop_surface_at(desktop,
+ cursor->cursor->x, cursor->cursor->y, &sx, &sy, &view);
+ struct roots_tablet_tool *roots_tool = tool->data;
+
+ if (!surface) {
+ wlr_tablet_v2_tablet_tool_notify_proximity_out(roots_tool->tablet_v2_tool);
+ /* XXX: TODO: Fallback pointer semantics */
+ return;
+ }
+
+ if (!wlr_surface_accepts_tablet_v2(tablet->tablet_v2, surface)) {
+ wlr_tablet_v2_tablet_tool_notify_proximity_out(roots_tool->tablet_v2_tool);
+ /* XXX: TODO: Fallback pointer semantics */
+ return;
+ }
+
+ wlr_tablet_v2_tablet_tool_notify_proximity_in(roots_tool->tablet_v2_tool,
+ tablet->tablet_v2, surface);
+
+ wlr_tablet_v2_tablet_tool_notify_motion(roots_tool->tablet_v2_tool, sx, sy);
+}
+
+static void handle_tool_axis(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, tool_axis);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_tablet_tool_axis *event = data;
+ struct roots_tablet_tool *roots_tool = event->tool->data;
+
+ if (!roots_tool) { // Should this be an assert?
+ wlr_log(WLR_DEBUG, "Tool Axis, before proximity");
+ return;
+ }
+
+ /**
+ * We need to handle them ourselves, not pass it into the cursor
+ * without any consideration
+ */
+ handle_tablet_tool_position(cursor, event->device->data, event->tool,
+ event->updated_axes & WLR_TABLET_TOOL_AXIS_X,
+ event->updated_axes & WLR_TABLET_TOOL_AXIS_Y,
+ event->x, event->y, event->dx, event->dy);
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) {
+ wlr_tablet_v2_tablet_tool_notify_pressure(
+ roots_tool->tablet_v2_tool, event->pressure);
+ }
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) {
+ wlr_tablet_v2_tablet_tool_notify_distance(
+ roots_tool->tablet_v2_tool, event->distance);
+ }
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X) {
+ roots_tool->tilt_x = event->tilt_x;
+ }
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y) {
+ roots_tool->tilt_y = event->tilt_y;
+ }
+
+ if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) {
+ wlr_tablet_v2_tablet_tool_notify_tilt(
+ roots_tool->tablet_v2_tool,
+ roots_tool->tilt_x, roots_tool->tilt_y);
+ }
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) {
+ wlr_tablet_v2_tablet_tool_notify_rotation(
+ roots_tool->tablet_v2_tool, event->rotation);
+ }
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) {
+ wlr_tablet_v2_tablet_tool_notify_slider(
+ roots_tool->tablet_v2_tool, event->slider);
+ }
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) {
+ wlr_tablet_v2_tablet_tool_notify_wheel(
+ roots_tool->tablet_v2_tool, event->wheel_delta, 0);
+ }
+}
+
+static void handle_tool_tip(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, tool_tip);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_tablet_tool_tip *event = data;
+ struct roots_tablet_tool *roots_tool = event->tool->data;
+
+ if (event->state == WLR_TABLET_TOOL_TIP_DOWN) {
+ wlr_tablet_v2_tablet_tool_notify_down(roots_tool->tablet_v2_tool);
+ wlr_tablet_tool_v2_start_implicit_grab(roots_tool->tablet_v2_tool);
+ } else {
+ wlr_tablet_v2_tablet_tool_notify_up(roots_tool->tablet_v2_tool);
+ }
+}
+
+static void handle_tablet_tool_destroy(struct wl_listener *listener, void *data) {
+ struct roots_tablet_tool *tool =
+ wl_container_of(listener, tool, tool_destroy);
+
+ wl_list_remove(&tool->link);
+ wl_list_remove(&tool->tool_link);
+
+ wl_list_remove(&tool->tool_destroy.link);
+ wl_list_remove(&tool->set_cursor.link);
+
+ free(tool);
+}
+
+static void handle_tool_button(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, tool_button);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_tablet_tool_button *event = data;
+ struct roots_tablet_tool *roots_tool = event->tool->data;
+
+ wlr_tablet_v2_tablet_tool_notify_button(roots_tool->tablet_v2_tool,
+ (enum zwp_tablet_pad_v2_button_state)event->button,
+ (enum zwp_tablet_pad_v2_button_state)event->state);
+}
+
+static void handle_tablet_tool_set_cursor(struct wl_listener *listener, void *data) {
+ struct roots_tablet_tool *tool =
+ wl_container_of(listener, tool, set_cursor);
+ struct wlr_tablet_v2_event_cursor *evt = data;
+
+
+ struct wlr_seat_pointer_request_set_cursor_event event = {
+ .surface = evt->surface,
+ .hotspot_x = evt->hotspot_x,
+ .hotspot_y = evt->hotspot_y,
+ .serial = evt->serial,
+ .seat_client = evt->seat_client,
+ };
+
+ roots_cursor_handle_request_set_cursor(tool->seat->cursor, &event);
+}
+
+static void handle_tool_proximity(struct wl_listener *listener, void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, tool_proximity);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_event_tablet_tool_proximity *event = data;
+
+ struct wlr_tablet_tool *tool = event->tool;
+ if (!tool->data) {
+ struct roots_tablet_tool *roots_tool =
+ calloc(1, sizeof(struct roots_tablet_tool));
+ roots_tool->seat = cursor->seat;
+ tool->data = roots_tool;
+ roots_tool->tablet_v2_tool =
+ wlr_tablet_tool_create(desktop->tablet_v2,
+ cursor->seat->seat, tool);
+ roots_tool->tool_destroy.notify = handle_tablet_tool_destroy;
+ wl_signal_add(&tool->events.destroy, &roots_tool->tool_destroy);
+
+ roots_tool->set_cursor.notify = handle_tablet_tool_set_cursor;
+ wl_signal_add(&roots_tool->tablet_v2_tool->events.set_cursor,
+ &roots_tool->set_cursor);
+
+ wl_list_init(&roots_tool->link);
+ wl_list_init(&roots_tool->tool_link);
+ }
+
+ if (event->state == WLR_TABLET_TOOL_PROXIMITY_OUT) {
+ struct roots_tablet_tool *roots_tool = tool->data;
+ wlr_tablet_v2_tablet_tool_notify_proximity_out(roots_tool->tablet_v2_tool);
+ return;
+ }
+
+ handle_tablet_tool_position(cursor, event->device->data, event->tool,
+ true, true, event->x, event->y, 0, 0);
+}
+
+static void handle_request_set_cursor(struct wl_listener *listener,
+ void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, request_set_cursor);
+ struct roots_desktop *desktop = cursor->seat->input->server->desktop;
+ wlr_idle_notify_activity(desktop->idle, cursor->seat->seat);
+ struct wlr_seat_pointer_request_set_cursor_event *event = data;
+ roots_cursor_handle_request_set_cursor(cursor, event);
+}
+
+static void handle_pointer_focus_change(struct wl_listener *listener,
+ void *data) {
+ struct roots_cursor *cursor =
+ wl_container_of(listener, cursor, focus_change);
+ struct wlr_seat_pointer_focus_change_event *event = data;
+ roots_cursor_handle_focus_change(cursor, event);
+}
+
+static void seat_reset_device_mappings(struct roots_seat *seat,
+ struct wlr_input_device *device) {
+ struct wlr_cursor *cursor = seat->cursor->cursor;
+ struct roots_config *config = seat->input->config;
+
+ wlr_cursor_map_input_to_output(cursor, device, NULL);
+ struct roots_device_config *dconfig;
+ if ((dconfig = roots_config_get_device(config, device))) {
+ wlr_cursor_map_input_to_region(cursor, device, dconfig->mapped_box);
+ }
+}
+
+static void seat_set_device_output_mappings(struct roots_seat *seat,
+ struct wlr_input_device *device, struct wlr_output *output) {
+ struct wlr_cursor *cursor = seat->cursor->cursor;
+ struct roots_config *config = seat->input->config;
+ struct roots_device_config *dconfig =
+ roots_config_get_device(config, device);
+
+ const char *mapped_output = NULL;
+ if (dconfig != NULL) {
+ mapped_output = dconfig->mapped_output;
+ }
+ if (mapped_output == NULL) {
+ mapped_output = device->output_name;
+ }
+
+ if (mapped_output && strcmp(mapped_output, output->name) == 0) {
+ wlr_cursor_map_input_to_output(cursor, device, output);
+ }
+}
+
+void roots_seat_configure_cursor(struct roots_seat *seat) {
+ struct roots_config *config = seat->input->config;
+ struct roots_desktop *desktop = seat->input->server->desktop;
+ struct wlr_cursor *cursor = seat->cursor->cursor;
+
+ struct roots_pointer *pointer;
+ struct roots_touch *touch;
+ struct roots_tablet *tablet;
+ struct roots_output *output;
+
+ // reset mappings
+ wlr_cursor_map_to_output(cursor, NULL);
+ wl_list_for_each(pointer, &seat->pointers, link) {
+ seat_reset_device_mappings(seat, pointer->device);
+ }
+ wl_list_for_each(touch, &seat->touch, link) {
+ seat_reset_device_mappings(seat, touch->device);
+ }
+ wl_list_for_each(tablet, &seat->tablets, link) {
+ seat_reset_device_mappings(seat, tablet->device);
+ }
+
+ // configure device to output mappings
+ const char *mapped_output = NULL;
+ struct roots_cursor_config *cc =
+ roots_config_get_cursor(config, seat->seat->name);
+ if (cc != NULL) {
+ mapped_output = cc->mapped_output;
+ }
+ wl_list_for_each(output, &desktop->outputs, link) {
+ if (mapped_output &&
+ strcmp(mapped_output, output->wlr_output->name) == 0) {
+ wlr_cursor_map_to_output(cursor, output->wlr_output);
+ }
+
+ wl_list_for_each(pointer, &seat->pointers, link) {
+ seat_set_device_output_mappings(seat, pointer->device,
+ output->wlr_output);
+ }
+ wl_list_for_each(tablet, &seat->tablets, link) {
+ seat_set_device_output_mappings(seat, tablet->device,
+ output->wlr_output);
+ }
+ wl_list_for_each(touch, &seat->touch, link) {
+ seat_set_device_output_mappings(seat, touch->device,
+ output->wlr_output);
+ }
+ }
+}
+
+static void roots_seat_init_cursor(struct roots_seat *seat) {
+ seat->cursor = roots_cursor_create(seat);
+ if (!seat->cursor) {
+ return;
+ }
+ seat->cursor->seat = seat;
+ struct wlr_cursor *wlr_cursor = seat->cursor->cursor;
+ struct roots_desktop *desktop = seat->input->server->desktop;
+ wlr_cursor_attach_output_layout(wlr_cursor, desktop->layout);
+
+ roots_seat_configure_cursor(seat);
+ roots_seat_configure_xcursor(seat);
+
+ // add input signals
+ wl_signal_add(&wlr_cursor->events.motion, &seat->cursor->motion);
+ seat->cursor->motion.notify = handle_cursor_motion;
+
+ wl_signal_add(&wlr_cursor->events.motion_absolute,
+ &seat->cursor->motion_absolute);
+ seat->cursor->motion_absolute.notify = handle_cursor_motion_absolute;
+
+ wl_signal_add(&wlr_cursor->events.button, &seat->cursor->button);
+ seat->cursor->button.notify = handle_cursor_button;
+
+ wl_signal_add(&wlr_cursor->events.axis, &seat->cursor->axis);
+ seat->cursor->axis.notify = handle_cursor_axis;
+
+ wl_signal_add(&wlr_cursor->events.touch_down, &seat->cursor->touch_down);
+ seat->cursor->touch_down.notify = handle_touch_down;
+
+ wl_signal_add(&wlr_cursor->events.touch_up, &seat->cursor->touch_up);
+ seat->cursor->touch_up.notify = handle_touch_up;
+
+ wl_signal_add(&wlr_cursor->events.touch_motion,
+ &seat->cursor->touch_motion);
+ seat->cursor->touch_motion.notify = handle_touch_motion;
+
+ wl_signal_add(&wlr_cursor->events.tablet_tool_axis,
+ &seat->cursor->tool_axis);
+ seat->cursor->tool_axis.notify = handle_tool_axis;
+
+ wl_signal_add(&wlr_cursor->events.tablet_tool_tip, &seat->cursor->tool_tip);
+ seat->cursor->tool_tip.notify = handle_tool_tip;
+
+ wl_signal_add(&wlr_cursor->events.tablet_tool_proximity, &seat->cursor->tool_proximity);
+ seat->cursor->tool_proximity.notify = handle_tool_proximity;
+
+ wl_signal_add(&wlr_cursor->events.tablet_tool_button, &seat->cursor->tool_button);
+ seat->cursor->tool_button.notify = handle_tool_button;
+
+ wl_signal_add(&seat->seat->events.request_set_cursor,
+ &seat->cursor->request_set_cursor);
+ seat->cursor->request_set_cursor.notify = handle_request_set_cursor;
+
+ wl_signal_add(&seat->seat->pointer_state.events.focus_change,
+ &seat->cursor->focus_change);
+ seat->cursor->focus_change.notify = handle_pointer_focus_change;
+
+ wl_list_init(&seat->cursor->constraint_commit.link);
+}
+
+static void roots_drag_icon_handle_surface_commit(struct wl_listener *listener,
+ void *data) {
+ struct roots_drag_icon *icon =
+ wl_container_of(listener, icon, surface_commit);
+ roots_drag_icon_update_position(icon);
+}
+
+static void roots_drag_icon_handle_map(struct wl_listener *listener,
+ void *data) {
+ struct roots_drag_icon *icon =
+ wl_container_of(listener, icon, map);
+ roots_drag_icon_damage_whole(icon);
+}
+
+static void roots_drag_icon_handle_unmap(struct wl_listener *listener,
+ void *data) {
+ struct roots_drag_icon *icon =
+ wl_container_of(listener, icon, unmap);
+ roots_drag_icon_damage_whole(icon);
+}
+
+static void roots_drag_icon_handle_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_drag_icon *icon =
+ wl_container_of(listener, icon, destroy);
+ roots_drag_icon_damage_whole(icon);
+
+ wl_list_remove(&icon->link);
+ wl_list_remove(&icon->surface_commit.link);
+ wl_list_remove(&icon->unmap.link);
+ wl_list_remove(&icon->destroy.link);
+ free(icon);
+}
+
+static void roots_seat_handle_new_drag_icon(struct wl_listener *listener,
+ void *data) {
+ struct roots_seat *seat = wl_container_of(listener, seat, new_drag_icon);
+ struct wlr_drag_icon *wlr_drag_icon = data;
+
+ struct roots_drag_icon *icon = calloc(1, sizeof(struct roots_drag_icon));
+ if (icon == NULL) {
+ return;
+ }
+ icon->seat = seat;
+ icon->wlr_drag_icon = wlr_drag_icon;
+
+ icon->surface_commit.notify = roots_drag_icon_handle_surface_commit;
+ wl_signal_add(&wlr_drag_icon->surface->events.commit, &icon->surface_commit);
+ icon->unmap.notify = roots_drag_icon_handle_unmap;
+ wl_signal_add(&wlr_drag_icon->events.unmap, &icon->unmap);
+ icon->map.notify = roots_drag_icon_handle_map;
+ wl_signal_add(&wlr_drag_icon->events.map, &icon->map);
+ icon->destroy.notify = roots_drag_icon_handle_destroy;
+ wl_signal_add(&wlr_drag_icon->events.destroy, &icon->destroy);
+
+ wl_list_insert(&seat->drag_icons, &icon->link);
+
+ roots_drag_icon_update_position(icon);
+}
+
+void roots_drag_icon_update_position(struct roots_drag_icon *icon) {
+ roots_drag_icon_damage_whole(icon);
+
+ struct wlr_drag_icon *wlr_icon = icon->wlr_drag_icon;
+ struct roots_seat *seat = icon->seat;
+ struct wlr_cursor *cursor = seat->cursor->cursor;
+ if (wlr_icon->is_pointer) {
+ icon->x = cursor->x;
+ icon->y = cursor->y;
+ } else {
+ struct wlr_touch_point *point =
+ wlr_seat_touch_get_point(seat->seat, wlr_icon->touch_id);
+ if (point == NULL) {
+ return;
+ }
+ icon->x = seat->touch_x;
+ icon->y = seat->touch_y;
+ }
+
+ roots_drag_icon_damage_whole(icon);
+}
+
+void roots_drag_icon_damage_whole(struct roots_drag_icon *icon) {
+ struct roots_output *output;
+ wl_list_for_each(output, &icon->seat->input->server->desktop->outputs,
+ link) {
+ output_damage_whole_drag_icon(output, icon);
+ }
+}
+
+static void seat_view_destroy(struct roots_seat_view *seat_view);
+
+static void roots_seat_handle_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_seat *seat = wl_container_of(listener, seat, destroy);
+
+ // TODO: probably more to be freed here
+ wl_list_remove(&seat->destroy.link);
+
+ struct roots_seat_view *view, *nview;
+ wl_list_for_each_safe(view, nview, &seat->views, link) {
+ seat_view_destroy(view);
+ }
+}
+
+void roots_seat_destroy(struct roots_seat *seat) {
+ roots_seat_handle_destroy(&seat->destroy, seat->seat);
+ wlr_seat_destroy(seat->seat);
+}
+
+struct roots_seat *roots_seat_create(struct roots_input *input, char *name) {
+ struct roots_seat *seat = calloc(1, sizeof(struct roots_seat));
+ if (!seat) {
+ return NULL;
+ }
+
+ wl_list_init(&seat->keyboards);
+ wl_list_init(&seat->pointers);
+ wl_list_init(&seat->touch);
+ wl_list_init(&seat->tablets);
+ wl_list_init(&seat->tablet_pads);
+ wl_list_init(&seat->switches);
+ wl_list_init(&seat->views);
+ wl_list_init(&seat->drag_icons);
+
+ seat->input = input;
+
+ seat->seat = wlr_seat_create(input->server->wl_display, name);
+ if (!seat->seat) {
+ free(seat);
+ return NULL;
+ }
+ seat->seat->data = seat;
+
+ roots_seat_init_cursor(seat);
+ if (!seat->cursor) {
+ wlr_seat_destroy(seat->seat);
+ free(seat);
+ return NULL;
+ }
+
+ roots_input_method_relay_init(seat, &seat->im_relay);
+
+ wl_list_insert(&input->seats, &seat->link);
+
+ seat->new_drag_icon.notify = roots_seat_handle_new_drag_icon;
+ wl_signal_add(&seat->seat->events.new_drag_icon, &seat->new_drag_icon);
+ seat->destroy.notify = roots_seat_handle_destroy;
+ wl_signal_add(&seat->seat->events.destroy, &seat->destroy);
+
+ return seat;
+}
+
+static void seat_update_capabilities(struct roots_seat *seat) {
+ uint32_t caps = 0;
+ if (!wl_list_empty(&seat->keyboards)) {
+ caps |= WL_SEAT_CAPABILITY_KEYBOARD;
+ }
+ if (!wl_list_empty(&seat->pointers) || !wl_list_empty(&seat->tablets)) {
+ caps |= WL_SEAT_CAPABILITY_POINTER;
+ }
+ if (!wl_list_empty(&seat->touch)) {
+ caps |= WL_SEAT_CAPABILITY_TOUCH;
+ }
+ wlr_seat_set_capabilities(seat->seat, caps);
+
+ // Hide cursor if seat doesn't have pointer capability
+ if ((caps & WL_SEAT_CAPABILITY_POINTER) == 0) {
+ wlr_cursor_set_image(seat->cursor->cursor, NULL, 0, 0, 0, 0, 0, 0);
+ } else {
+ wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager,
+ seat->cursor->default_xcursor, seat->cursor->cursor);
+ }
+}
+
+static void handle_keyboard_destroy(struct wl_listener *listener, void *data) {
+ struct roots_keyboard *keyboard =
+ wl_container_of(listener, keyboard, device_destroy);
+ struct roots_seat *seat = keyboard->seat;
+ wl_list_remove(&keyboard->device_destroy.link);
+ wl_list_remove(&keyboard->keyboard_key.link);
+ wl_list_remove(&keyboard->keyboard_modifiers.link);
+ roots_keyboard_destroy(keyboard);
+ seat_update_capabilities(seat);
+}
+
+static void seat_add_keyboard(struct roots_seat *seat,
+ struct wlr_input_device *device) {
+ assert(device->type == WLR_INPUT_DEVICE_KEYBOARD);
+ struct roots_keyboard *keyboard =
+ roots_keyboard_create(device, seat->input);
+ if (keyboard == NULL) {
+ wlr_log(WLR_ERROR, "could not allocate keyboard for seat");
+ return;
+ }
+
+ keyboard->seat = seat;
+
+ wl_list_insert(&seat->keyboards, &keyboard->link);
+
+ keyboard->device_destroy.notify = handle_keyboard_destroy;
+ wl_signal_add(&keyboard->device->events.destroy, &keyboard->device_destroy);
+ keyboard->keyboard_key.notify = handle_keyboard_key;
+ wl_signal_add(&keyboard->device->keyboard->events.key,
+ &keyboard->keyboard_key);
+ keyboard->keyboard_modifiers.notify = handle_keyboard_modifiers;
+ wl_signal_add(&keyboard->device->keyboard->events.modifiers,
+ &keyboard->keyboard_modifiers);
+
+ wlr_seat_set_keyboard(seat->seat, device);
+}
+
+static void handle_pointer_destroy(struct wl_listener *listener, void *data) {
+ struct roots_pointer *pointer =
+ wl_container_of(listener, pointer, device_destroy);
+ struct roots_seat *seat = pointer->seat;
+
+ wl_list_remove(&pointer->link);
+ wlr_cursor_detach_input_device(seat->cursor->cursor, pointer->device);
+ wl_list_remove(&pointer->device_destroy.link);
+ free(pointer);
+
+ seat_update_capabilities(seat);
+}
+
+static void seat_add_pointer(struct roots_seat *seat,
+ struct wlr_input_device *device) {
+ struct roots_pointer *pointer = calloc(1, sizeof(struct roots_pointer));
+ if (!pointer) {
+ wlr_log(WLR_ERROR, "could not allocate pointer for seat");
+ return;
+ }
+
+ device->data = pointer;
+ pointer->device = device;
+ pointer->seat = seat;
+ wl_list_insert(&seat->pointers, &pointer->link);
+
+ pointer->device_destroy.notify = handle_pointer_destroy;
+ wl_signal_add(&pointer->device->events.destroy, &pointer->device_destroy);
+
+ wlr_cursor_attach_input_device(seat->cursor->cursor, device);
+ roots_seat_configure_cursor(seat);
+}
+
+static void handle_switch_destroy(struct wl_listener *listener, void *data) {
+ struct roots_switch *lid_switch =
+ wl_container_of(listener, lid_switch, device_destroy);
+ struct roots_seat *seat = lid_switch->seat;
+
+ wl_list_remove(&lid_switch->link);
+ wl_list_remove(&lid_switch->device_destroy.link);
+ free(lid_switch);
+
+ seat_update_capabilities(seat);
+}
+
+static void seat_add_switch(struct roots_seat *seat,
+ struct wlr_input_device *device) {
+ assert(device->type == WLR_INPUT_DEVICE_SWITCH);
+ struct roots_switch *lid_switch = calloc(1, sizeof(struct roots_switch));
+ if (!lid_switch) {
+ wlr_log(WLR_ERROR, "could not allocate switch for seat");
+ return;
+ }
+ device->data = lid_switch;
+ lid_switch->device = device;
+ lid_switch->seat = seat;
+ wl_list_insert(&seat->switches, &lid_switch->link);
+ lid_switch->device_destroy.notify = handle_switch_destroy;
+
+ lid_switch->toggle.notify = handle_switch_toggle;
+ wl_signal_add(&lid_switch->device->lid_switch->events.toggle, &lid_switch->toggle);
+}
+
+static void handle_touch_destroy(struct wl_listener *listener, void *data) {
+ struct roots_pointer *touch =
+ wl_container_of(listener, touch, device_destroy);
+ struct roots_seat *seat = touch->seat;
+
+ wl_list_remove(&touch->link);
+ wlr_cursor_detach_input_device(seat->cursor->cursor, touch->device);
+ wl_list_remove(&touch->device_destroy.link);
+ free(touch);
+
+ seat_update_capabilities(seat);
+}
+
+static void seat_add_touch(struct roots_seat *seat,
+ struct wlr_input_device *device) {
+ struct roots_touch *touch = calloc(1, sizeof(struct roots_touch));
+ if (!touch) {
+ wlr_log(WLR_ERROR, "could not allocate touch for seat");
+ return;
+ }
+
+ device->data = touch;
+ touch->device = device;
+ touch->seat = seat;
+ wl_list_insert(&seat->touch, &touch->link);
+
+ touch->device_destroy.notify = handle_touch_destroy;
+ wl_signal_add(&touch->device->events.destroy, &touch->device_destroy);
+
+ wlr_cursor_attach_input_device(seat->cursor->cursor, device);
+ roots_seat_configure_cursor(seat);
+}
+
+static void handle_tablet_pad_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_tablet_pad *tablet_pad =
+ wl_container_of(listener, tablet_pad, device_destroy);
+ struct roots_seat *seat = tablet_pad->seat;
+
+ wl_list_remove(&tablet_pad->device_destroy.link);
+ wl_list_remove(&tablet_pad->tablet_destroy.link);
+ wl_list_remove(&tablet_pad->attach.link);
+ wl_list_remove(&tablet_pad->link);
+
+ wl_list_remove(&tablet_pad->button.link);
+ wl_list_remove(&tablet_pad->strip.link);
+ wl_list_remove(&tablet_pad->ring.link);
+ free(tablet_pad);
+
+ seat_update_capabilities(seat);
+}
+
+static void handle_pad_tool_destroy(struct wl_listener *listener, void *data) {
+ struct roots_tablet_pad *pad =
+ wl_container_of(listener, pad, tablet_destroy);
+
+ pad->tablet = NULL;
+
+ wl_list_remove(&pad->tablet_destroy.link);
+ wl_list_init(&pad->tablet_destroy.link);
+}
+
+static void attach_tablet_pad(struct roots_tablet_pad *pad,
+ struct roots_tablet *tool) {
+ wlr_log(WLR_DEBUG, "Attaching tablet pad \"%s\" to tablet tool \"%s\"",
+ pad->device->name, tool->device->name);
+
+ pad->tablet = tool;
+
+ wl_list_remove(&pad->tablet_destroy.link);
+ pad->tablet_destroy.notify = handle_pad_tool_destroy;
+ wl_signal_add(&tool->device->events.destroy, &pad->tablet_destroy);
+}
+
+static void handle_tablet_pad_attach(struct wl_listener *listener, void *data) {
+ struct roots_tablet_pad *pad =
+ wl_container_of(listener, pad, attach);
+ struct wlr_tablet_tool *wlr_tool = data;
+ struct roots_tablet *tool = wlr_tool->data;
+
+ attach_tablet_pad(pad, tool);
+}
+
+static void handle_tablet_pad_ring(struct wl_listener *listener, void *data) {
+ struct roots_tablet_pad *pad =
+ wl_container_of(listener, pad, ring);
+ struct wlr_event_tablet_pad_ring *event = data;
+
+ wlr_tablet_v2_tablet_pad_notify_ring(pad->tablet_v2_pad,
+ event->ring, event->position,
+ event->source == WLR_TABLET_PAD_RING_SOURCE_FINGER,
+ event->time_msec);
+}
+
+static void handle_tablet_pad_strip(struct wl_listener *listener, void *data) {
+ struct roots_tablet_pad *pad =
+ wl_container_of(listener, pad, strip);
+ struct wlr_event_tablet_pad_strip *event = data;
+
+ wlr_tablet_v2_tablet_pad_notify_strip(pad->tablet_v2_pad,
+ event->strip, event->position,
+ event->source == WLR_TABLET_PAD_STRIP_SOURCE_FINGER,
+ event->time_msec);
+}
+
+static void handle_tablet_pad_button(struct wl_listener *listener, void *data) {
+ struct roots_tablet_pad *pad =
+ wl_container_of(listener, pad, button);
+ struct wlr_event_tablet_pad_button *event = data;
+
+ wlr_tablet_v2_tablet_pad_notify_mode(pad->tablet_v2_pad,
+ event->group, event->mode, event->time_msec);
+
+ wlr_tablet_v2_tablet_pad_notify_button(pad->tablet_v2_pad,
+ event->button, event->time_msec,
+ (enum zwp_tablet_pad_v2_button_state)event->state);
+}
+
+static void seat_add_tablet_pad(struct roots_seat *seat,
+ struct wlr_input_device *device) {
+ struct roots_tablet_pad *tablet_pad =
+ calloc(1, sizeof(struct roots_tablet_pad));
+ if (!tablet_pad) {
+ wlr_log(WLR_ERROR, "could not allocate tablet_pad for seat");
+ return;
+ }
+
+ device->data = tablet_pad;
+ tablet_pad->device = device;
+ tablet_pad->seat = seat;
+ wl_list_insert(&seat->tablet_pads, &tablet_pad->link);
+
+ tablet_pad->device_destroy.notify = handle_tablet_pad_destroy;
+ wl_signal_add(&tablet_pad->device->events.destroy,
+ &tablet_pad->device_destroy);
+
+ tablet_pad->attach.notify = handle_tablet_pad_attach;
+ wl_signal_add(&tablet_pad->device->tablet_pad->events.attach_tablet,
+ &tablet_pad->attach);
+
+ tablet_pad->button.notify = handle_tablet_pad_button;
+ wl_signal_add(&tablet_pad->device->tablet_pad->events.button, &tablet_pad->button);
+
+ tablet_pad->strip.notify = handle_tablet_pad_strip;
+ wl_signal_add(&tablet_pad->device->tablet_pad->events.strip, &tablet_pad->strip);
+
+ tablet_pad->ring.notify = handle_tablet_pad_ring;
+ wl_signal_add(&tablet_pad->device->tablet_pad->events.ring, &tablet_pad->ring);
+
+ wl_list_init(&tablet_pad->tablet_destroy.link);
+
+ struct roots_desktop *desktop = seat->input->server->desktop;
+ tablet_pad->tablet_v2_pad =
+ wlr_tablet_pad_create(desktop->tablet_v2, seat->seat, device);
+
+ /* Search for a sibling tablet */
+ if (!wlr_input_device_is_libinput(device)) {
+ /* We can only do this on libinput devices */
+ return;
+ }
+
+ struct libinput_device_group *group =
+ libinput_device_get_device_group(wlr_libinput_get_device_handle(device));
+ struct roots_tablet *tool;
+ wl_list_for_each(tool, &seat->tablets, link) {
+ if (!wlr_input_device_is_libinput(tool->device)) {
+ continue;
+ }
+
+ struct libinput_device *li_dev =
+ wlr_libinput_get_device_handle(tool->device);
+ if (libinput_device_get_device_group(li_dev) == group) {
+ attach_tablet_pad(tablet_pad, tool);
+ break;
+ }
+ }
+}
+
+static void handle_tablet_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_tablet *tablet =
+ wl_container_of(listener, tablet, device_destroy);
+ struct roots_seat *seat = tablet->seat;
+
+ wlr_cursor_detach_input_device(seat->cursor->cursor, tablet->device);
+ wl_list_remove(&tablet->device_destroy.link);
+ wl_list_remove(&tablet->link);
+ free(tablet);
+
+ seat_update_capabilities(seat);
+}
+
+static void seat_add_tablet_tool(struct roots_seat *seat,
+ struct wlr_input_device *device) {
+ struct roots_tablet *tablet =
+ calloc(1, sizeof(struct roots_tablet));
+ if (!tablet) {
+ wlr_log(WLR_ERROR, "could not allocate tablet for seat");
+ return;
+ }
+
+ device->data = tablet;
+ tablet->device = device;
+ tablet->seat = seat;
+ wl_list_insert(&seat->tablets, &tablet->link);
+
+ tablet->device_destroy.notify = handle_tablet_destroy;
+ wl_signal_add(&tablet->device->events.destroy,
+ &tablet->device_destroy);
+
+ wlr_cursor_attach_input_device(seat->cursor->cursor, device);
+ roots_seat_configure_cursor(seat);
+
+ struct roots_desktop *desktop = seat->input->server->desktop;
+
+ tablet->tablet_v2 =
+ wlr_tablet_create(desktop->tablet_v2, seat->seat, device);
+
+ struct libinput_device_group *group =
+ libinput_device_get_device_group(wlr_libinput_get_device_handle(device));
+ struct roots_tablet_pad *pad;
+ wl_list_for_each(pad, &seat->tablet_pads, link) {
+ if (!wlr_input_device_is_libinput(pad->device)) {
+ continue;
+ }
+
+ struct libinput_device *li_dev =
+ wlr_libinput_get_device_handle(pad->device);
+ if (libinput_device_get_device_group(li_dev) == group) {
+ attach_tablet_pad(pad, tablet);
+ }
+ }
+}
+
+void roots_seat_add_device(struct roots_seat *seat,
+ struct wlr_input_device *device) {
+ switch (device->type) {
+ case WLR_INPUT_DEVICE_KEYBOARD:
+ seat_add_keyboard(seat, device);
+ break;
+ case WLR_INPUT_DEVICE_POINTER:
+ seat_add_pointer(seat, device);
+ break;
+ case WLR_INPUT_DEVICE_SWITCH:
+ seat_add_switch(seat, device);
+ break;
+ case WLR_INPUT_DEVICE_TOUCH:
+ seat_add_touch(seat, device);
+ break;
+ case WLR_INPUT_DEVICE_TABLET_PAD:
+ seat_add_tablet_pad(seat, device);
+ break;
+ case WLR_INPUT_DEVICE_TABLET_TOOL:
+ seat_add_tablet_tool(seat, device);
+ break;
+ }
+
+ seat_update_capabilities(seat);
+}
+
+void roots_seat_configure_xcursor(struct roots_seat *seat) {
+ const char *cursor_theme = NULL;
+ struct roots_cursor_config *cc =
+ roots_config_get_cursor(seat->input->config, seat->seat->name);
+ if (cc != NULL) {
+ cursor_theme = cc->theme;
+ if (cc->default_image != NULL) {
+ seat->cursor->default_xcursor = cc->default_image;
+ }
+ }
+
+ if (!seat->cursor->xcursor_manager) {
+ seat->cursor->xcursor_manager =
+ wlr_xcursor_manager_create(cursor_theme, ROOTS_XCURSOR_SIZE);
+ if (seat->cursor->xcursor_manager == NULL) {
+ wlr_log(WLR_ERROR, "Cannot create XCursor manager for theme %s",
+ cursor_theme);
+ return;
+ }
+ }
+
+ struct roots_output *output;
+ wl_list_for_each(output, &seat->input->server->desktop->outputs, link) {
+ float scale = output->wlr_output->scale;
+ if (wlr_xcursor_manager_load(seat->cursor->xcursor_manager, scale)) {
+ wlr_log(WLR_ERROR, "Cannot load xcursor theme for output '%s' "
+ "with scale %f", output->wlr_output->name, scale);
+ }
+ }
+
+ wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager,
+ seat->cursor->default_xcursor, seat->cursor->cursor);
+ wlr_cursor_warp(seat->cursor->cursor, NULL, seat->cursor->cursor->x,
+ seat->cursor->cursor->y);
+}
+
+bool roots_seat_has_meta_pressed(struct roots_seat *seat) {
+ struct roots_keyboard *keyboard;
+ wl_list_for_each(keyboard, &seat->keyboards, link) {
+ if (!keyboard->config->meta_key) {
+ continue;
+ }
+
+ uint32_t modifiers =
+ wlr_keyboard_get_modifiers(keyboard->device->keyboard);
+ if ((modifiers ^ keyboard->config->meta_key) == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+struct roots_view *roots_seat_get_focus(struct roots_seat *seat) {
+ if (!seat->has_focus || wl_list_empty(&seat->views)) {
+ return NULL;
+ }
+ struct roots_seat_view *seat_view =
+ wl_container_of(seat->views.next, seat_view, link);
+ return seat_view->view;
+}
+
+static void seat_view_destroy(struct roots_seat_view *seat_view) {
+ struct roots_seat *seat = seat_view->seat;
+
+ if (seat_view->view == roots_seat_get_focus(seat)) {
+ seat->has_focus = false;
+ seat->cursor->mode = ROOTS_CURSOR_PASSTHROUGH;
+ }
+
+ if (seat_view == seat->cursor->pointer_view) {
+ seat->cursor->pointer_view = NULL;
+ }
+
+ wl_list_remove(&seat_view->view_unmap.link);
+ wl_list_remove(&seat_view->view_destroy.link);
+ wl_list_remove(&seat_view->link);
+ free(seat_view);
+
+ // Focus first view
+ if (!wl_list_empty(&seat->views)) {
+ struct roots_seat_view *first_seat_view = wl_container_of(
+ seat->views.next, first_seat_view, link);
+ roots_seat_set_focus(seat, first_seat_view->view);
+ }
+}
+
+static void seat_view_handle_unmap(struct wl_listener *listener, void *data) {
+ struct roots_seat_view *seat_view =
+ wl_container_of(listener, seat_view, view_unmap);
+ seat_view_destroy(seat_view);
+}
+
+static void seat_view_handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_seat_view *seat_view =
+ wl_container_of(listener, seat_view, view_destroy);
+ seat_view_destroy(seat_view);
+}
+
+static struct roots_seat_view *seat_add_view(struct roots_seat *seat,
+ struct roots_view *view) {
+ struct roots_seat_view *seat_view =
+ calloc(1, sizeof(struct roots_seat_view));
+ if (seat_view == NULL) {
+ return NULL;
+ }
+ seat_view->seat = seat;
+ seat_view->view = view;
+
+ wl_list_insert(seat->views.prev, &seat_view->link);
+
+ seat_view->view_unmap.notify = seat_view_handle_unmap;
+ wl_signal_add(&view->events.unmap, &seat_view->view_unmap);
+ seat_view->view_destroy.notify = seat_view_handle_destroy;
+ wl_signal_add(&view->events.destroy, &seat_view->view_destroy);
+
+ return seat_view;
+}
+
+struct roots_seat_view *roots_seat_view_from_view(
+ struct roots_seat *seat, struct roots_view *view) {
+ if (view == NULL) {
+ return NULL;
+ }
+
+ bool found = false;
+ struct roots_seat_view *seat_view = NULL;
+ wl_list_for_each(seat_view, &seat->views, link) {
+ if (seat_view->view == view) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ seat_view = seat_add_view(seat, view);
+ if (seat_view == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed");
+ return NULL;
+ }
+ }
+
+ return seat_view;
+}
+
+bool roots_seat_allow_input(struct roots_seat *seat,
+ struct wl_resource *resource) {
+ return !seat->exclusive_client ||
+ wl_resource_get_client(resource) == seat->exclusive_client;
+}
+
+void roots_seat_set_focus(struct roots_seat *seat, struct roots_view *view) {
+ if (view && !roots_seat_allow_input(seat, view->wlr_surface->resource)) {
+ return;
+ }
+
+ // Make sure the view will be rendered on top of others, even if it's
+ // already focused in this seat
+ if (view != NULL) {
+ wl_list_remove(&view->link);
+ wl_list_insert(&seat->input->server->desktop->views, &view->link);
+ }
+
+ bool unfullscreen = true;
+
+#if WLR_HAS_XWAYLAND
+ if (view && view->type == ROOTS_XWAYLAND_VIEW &&
+ view->xwayland_surface->override_redirect) {
+ unfullscreen = false;
+ }
+#endif
+
+ if (view && unfullscreen) {
+ struct roots_desktop *desktop = view->desktop;
+ struct roots_output *output;
+ struct wlr_box box;
+ view_get_box(view, &box);
+ wl_list_for_each(output, &desktop->outputs, link) {
+ if (output->fullscreen_view &&
+ output->fullscreen_view != view &&
+ wlr_output_layout_intersects(
+ desktop->layout,
+ output->wlr_output, &box)) {
+ view_set_fullscreen(output->fullscreen_view,
+ false, NULL);
+ }
+ }
+ }
+
+ struct roots_view *prev_focus = roots_seat_get_focus(seat);
+ if (view == prev_focus) {
+ return;
+ }
+
+#if WLR_HAS_XWAYLAND
+ if (view && view->type == ROOTS_XWAYLAND_VIEW &&
+ !wlr_xwayland_or_surface_wants_focus(
+ view->xwayland_surface)) {
+ return;
+ }
+#endif
+ struct roots_seat_view *seat_view = NULL;
+ if (view != NULL) {
+ seat_view = roots_seat_view_from_view(seat, view);
+ if (seat_view == NULL) {
+ return;
+ }
+ }
+
+ seat->has_focus = false;
+
+ // Deactivate the old view if it is not focused by some other seat
+ if (prev_focus != NULL && !input_view_has_focus(seat->input, prev_focus)) {
+ view_activate(prev_focus, false);
+ }
+
+ if (view == NULL) {
+ seat->cursor->mode = ROOTS_CURSOR_PASSTHROUGH;
+ wlr_seat_keyboard_clear_focus(seat->seat);
+ roots_input_method_relay_set_focus(&seat->im_relay, NULL);
+ return;
+ }
+
+ wl_list_remove(&seat_view->link);
+ wl_list_insert(&seat->views, &seat_view->link);
+
+ view_damage_whole(view);
+
+ if (seat->focused_layer) {
+ return;
+ }
+
+ view_activate(view, true);
+ seat->has_focus = true;
+
+ // An existing keyboard grab might try to deny setting focus, so cancel it
+ wlr_seat_keyboard_end_grab(seat->seat);
+
+ struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->seat);
+ if (keyboard != NULL) {
+ wlr_seat_keyboard_notify_enter(seat->seat, view->wlr_surface,
+ keyboard->keycodes, keyboard->num_keycodes,
+ &keyboard->modifiers);
+ /* FIXME: Move this to a better place */
+ struct roots_tablet_pad *pad;
+ wl_list_for_each(pad, &seat->tablet_pads, link) {
+ if (pad->tablet) {
+ wlr_tablet_v2_tablet_pad_notify_enter(pad->tablet_v2_pad, pad->tablet->tablet_v2, view->wlr_surface);
+ }
+ }
+ } else {
+ wlr_seat_keyboard_notify_enter(seat->seat, view->wlr_surface,
+ NULL, 0, NULL);
+ }
+
+ if (seat->cursor) {
+ roots_cursor_update_focus(seat->cursor);
+ }
+
+ roots_input_method_relay_set_focus(&seat->im_relay, view->wlr_surface);
+}
+
+/**
+ * Focus semantics of layer surfaces are somewhat detached from the normal focus
+ * flow. For layers above the shell layer, for example, you cannot unfocus them.
+ * You also cannot alt-tab between layer surfaces and shell surfaces.
+ */
+void roots_seat_set_focus_layer(struct roots_seat *seat,
+ struct wlr_layer_surface_v1 *layer) {
+ if (!layer) {
+ seat->focused_layer = NULL;
+ return;
+ }
+ struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->seat);
+ if (!roots_seat_allow_input(seat, layer->resource)) {
+ return;
+ }
+ if (seat->has_focus) {
+ struct roots_view *prev_focus = roots_seat_get_focus(seat);
+ wlr_seat_keyboard_clear_focus(seat->seat);
+ view_activate(prev_focus, false);
+ }
+ seat->has_focus = false;
+ if (layer->layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) {
+ seat->focused_layer = layer;
+ }
+ if (keyboard != NULL) {
+ wlr_seat_keyboard_notify_enter(seat->seat, layer->surface,
+ keyboard->keycodes, keyboard->num_keycodes,
+ &keyboard->modifiers);
+ } else {
+ wlr_seat_keyboard_notify_enter(seat->seat, layer->surface,
+ NULL, 0, NULL);
+ }
+
+
+ if (seat->cursor) {
+ roots_cursor_update_focus(seat->cursor);
+ }
+}
+
+void roots_seat_set_exclusive_client(struct roots_seat *seat,
+ struct wl_client *client) {
+ if (!client) {
+ seat->exclusive_client = client;
+ // Triggers a refocus of the topmost surface layer if necessary
+ // TODO: Make layer surface focus per-output based on cursor position
+ struct roots_output *output;
+ wl_list_for_each(output, &seat->input->server->desktop->outputs, link) {
+ arrange_layers(output);
+ }
+ return;
+ }
+ if (seat->focused_layer) {
+ if (wl_resource_get_client(seat->focused_layer->resource) != client) {
+ roots_seat_set_focus_layer(seat, NULL);
+ }
+ }
+ if (seat->has_focus) {
+ struct roots_view *focus = roots_seat_get_focus(seat);
+ if (wl_resource_get_client(focus->wlr_surface->resource) != client) {
+ roots_seat_set_focus(seat, NULL);
+ }
+ }
+ if (seat->seat->pointer_state.focused_client) {
+ if (seat->seat->pointer_state.focused_client->client != client) {
+ wlr_seat_pointer_clear_focus(seat->seat);
+ }
+ }
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ struct wlr_touch_point *point;
+ wl_list_for_each(point, &seat->seat->touch_state.touch_points, link) {
+ if (point->client->client != client) {
+ wlr_seat_touch_point_clear_focus(seat->seat,
+ now.tv_nsec / 1000, point->touch_id);
+ }
+ }
+ seat->exclusive_client = client;
+}
+
+void roots_seat_cycle_focus(struct roots_seat *seat) {
+ if (wl_list_empty(&seat->views)) {
+ return;
+ }
+
+ struct roots_seat_view *first_seat_view = wl_container_of(
+ seat->views.next, first_seat_view, link);
+ if (!seat->has_focus) {
+ roots_seat_set_focus(seat, first_seat_view->view);
+ return;
+ }
+ if (wl_list_length(&seat->views) < 2) {
+ return;
+ }
+
+ // Focus the next view
+ struct roots_seat_view *next_seat_view = wl_container_of(
+ first_seat_view->link.next, next_seat_view, link);
+ roots_seat_set_focus(seat, next_seat_view->view);
+
+ // Move the first view to the end of the list
+ wl_list_remove(&first_seat_view->link);
+ wl_list_insert(seat->views.prev, &first_seat_view->link);
+}
+
+void roots_seat_begin_move(struct roots_seat *seat, struct roots_view *view) {
+ struct roots_cursor *cursor = seat->cursor;
+ cursor->mode = ROOTS_CURSOR_MOVE;
+ cursor->offs_x = cursor->cursor->x;
+ cursor->offs_y = cursor->cursor->y;
+ if (view->maximized) {
+ cursor->view_x = view->saved.x;
+ cursor->view_y = view->saved.y;
+ } else {
+ cursor->view_x = view->box.x;
+ cursor->view_y = view->box.y;
+ }
+ view_maximize(view, false);
+ wlr_seat_pointer_clear_focus(seat->seat);
+
+ wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager,
+ ROOTS_XCURSOR_MOVE, seat->cursor->cursor);
+}
+
+void roots_seat_begin_resize(struct roots_seat *seat, struct roots_view *view,
+ uint32_t edges) {
+ struct roots_cursor *cursor = seat->cursor;
+ cursor->mode = ROOTS_CURSOR_RESIZE;
+ cursor->offs_x = cursor->cursor->x;
+ cursor->offs_y = cursor->cursor->y;
+ if (view->maximized) {
+ cursor->view_x = view->saved.x;
+ cursor->view_y = view->saved.y;
+ cursor->view_width = view->saved.width;
+ cursor->view_height = view->saved.height;
+ } else {
+ cursor->view_x = view->box.x;
+ cursor->view_y = view->box.y;
+ struct wlr_box box;
+ view_get_box(view, &box);
+ cursor->view_width = box.width;
+ cursor->view_height = box.height;
+ }
+ cursor->resize_edges = edges;
+ view_maximize(view, false);
+ wlr_seat_pointer_clear_focus(seat->seat);
+
+ const char *resize_name = wlr_xcursor_get_resize_name(edges);
+ wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager,
+ resize_name, seat->cursor->cursor);
+}
+
+void roots_seat_begin_rotate(struct roots_seat *seat, struct roots_view *view) {
+ struct roots_cursor *cursor = seat->cursor;
+ cursor->mode = ROOTS_CURSOR_ROTATE;
+ cursor->offs_x = cursor->cursor->x;
+ cursor->offs_y = cursor->cursor->y;
+ cursor->view_rotation = view->rotation;
+ view_maximize(view, false);
+ wlr_seat_pointer_clear_focus(seat->seat);
+
+ wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager,
+ ROOTS_XCURSOR_ROTATE, seat->cursor->cursor);
+}
+
+void roots_seat_end_compositor_grab(struct roots_seat *seat) {
+ struct roots_cursor *cursor = seat->cursor;
+ struct roots_view *view = roots_seat_get_focus(seat);
+
+ if (view == NULL) {
+ return;
+ }
+
+ switch(cursor->mode) {
+ case ROOTS_CURSOR_MOVE:
+ view_move(view, cursor->view_x, cursor->view_y);
+ break;
+ case ROOTS_CURSOR_RESIZE:
+ view_move_resize(view, cursor->view_x, cursor->view_y, cursor->view_width, cursor->view_height);
+ break;
+ case ROOTS_CURSOR_ROTATE:
+ view->rotation = cursor->view_rotation;
+ break;
+ case ROOTS_CURSOR_PASSTHROUGH:
+ break;
+ }
+
+ cursor->mode = ROOTS_CURSOR_PASSTHROUGH;
+}
+
+struct roots_seat *input_last_active_seat(struct roots_input *input) {
+ struct roots_seat *seat = NULL, *_seat;
+ wl_list_for_each(_seat, &input->seats, link) {
+ if (!seat || (seat->seat->last_event.tv_sec > _seat->seat->last_event.tv_sec &&
+ seat->seat->last_event.tv_nsec > _seat->seat->last_event.tv_nsec)) {
+ seat = _seat;
+ }
+ }
+ return seat;
+}
diff --git a/rootston/switch.c b/rootston/switch.c
new file mode 100644
index 00000000..65c5e627
--- /dev/null
+++ b/rootston/switch.c
@@ -0,0 +1,26 @@
+#include <stdlib.h>
+
+#include <wlr/util/log.h>
+
+#include "rootston/bindings.h"
+#include "rootston/config.h"
+#include "rootston/input.h"
+#include "rootston/seat.h"
+#include "rootston/switch.h"
+
+void roots_switch_handle_toggle(struct roots_switch *lid_switch,
+ struct wlr_event_switch_toggle *event) {
+ struct wl_list *bound_switches = &lid_switch->seat->input->server->config->switches;
+ struct roots_switch_config *sc;
+ wl_list_for_each(sc, bound_switches, link) {
+ if ((sc->name != NULL && strcmp(event->device->name, sc->name) != 0) &&
+ (sc->name == NULL && event->switch_type != sc->switch_type)) {
+ continue;
+ }
+ if (sc->switch_state != WLR_SWITCH_STATE_TOGGLE &&
+ event->switch_state != sc->switch_state) {
+ continue;
+ }
+ execute_binding_command(lid_switch->seat, lid_switch->seat->input, sc->command);
+ }
+}
diff --git a/rootston/text_input.c b/rootston/text_input.c
new file mode 100644
index 00000000..70c92761
--- /dev/null
+++ b/rootston/text_input.c
@@ -0,0 +1,310 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <wlr/util/log.h>
+#include "rootston/seat.h"
+#include "rootston/text_input.h"
+
+static struct roots_text_input *relay_get_focusable_text_input(
+ struct roots_input_method_relay *relay) {
+ struct roots_text_input *text_input = NULL;
+ wl_list_for_each(text_input, &relay->text_inputs, link) {
+ if (text_input->pending_focused_surface) {
+ return text_input;
+ }
+ }
+ return NULL;
+}
+
+static struct roots_text_input *relay_get_focused_text_input(
+ struct roots_input_method_relay *relay) {
+ struct roots_text_input *text_input = NULL;
+ wl_list_for_each(text_input, &relay->text_inputs, link) {
+ if (text_input->input->focused_surface) {
+ return text_input;
+ }
+ }
+ return NULL;
+}
+
+static void handle_im_commit(struct wl_listener *listener, void *data) {
+ struct roots_input_method_relay *relay = wl_container_of(listener, relay,
+ input_method_commit);
+
+ struct roots_text_input *text_input = relay_get_focused_text_input(relay);
+ if (!text_input) {
+ return;
+ }
+ struct wlr_input_method_v2 *context = data;
+ assert(context == relay->input_method);
+ if (context->current.preedit.text) {
+ wlr_text_input_v3_send_preedit_string(text_input->input,
+ context->current.preedit.text,
+ context->current.preedit.cursor_begin,
+ context->current.preedit.cursor_end);
+ }
+ if (context->current.commit_text) {
+ wlr_text_input_v3_send_commit_string(text_input->input,
+ context->current.commit_text);
+ }
+ if (context->current.delete.before_length
+ || context->current.delete.after_length) {
+ wlr_text_input_v3_send_delete_surrounding_text(text_input->input,
+ context->current.delete.before_length,
+ context->current.delete.after_length);
+ }
+ wlr_text_input_v3_send_done(text_input->input);
+}
+
+static void text_input_set_pending_focused_surface(
+ struct roots_text_input *text_input, struct wlr_surface *surface) {
+ text_input->pending_focused_surface = surface;
+ wl_signal_add(&surface->events.destroy,
+ &text_input->pending_focused_surface_destroy);
+}
+
+static void text_input_clear_pending_focused_surface(
+ struct roots_text_input *text_input) {
+ wl_list_remove(&text_input->pending_focused_surface_destroy.link);
+ wl_list_init(&text_input->pending_focused_surface_destroy.link);
+ text_input->pending_focused_surface = NULL;
+}
+
+static void handle_im_destroy(struct wl_listener *listener, void *data) {
+ struct roots_input_method_relay *relay = wl_container_of(listener, relay,
+ input_method_destroy);
+ struct wlr_input_method_v2 *context = data;
+ assert(context == relay->input_method);
+ relay->input_method = NULL;
+ struct roots_text_input *text_input = relay_get_focused_text_input(relay);
+ if (text_input) {
+ // keyboard focus is still there, so keep the surface at hand in case
+ // the input method returns
+ text_input_set_pending_focused_surface(text_input,
+ text_input->input->focused_surface);
+ wlr_text_input_v3_send_leave(text_input->input);
+ }
+}
+
+static void relay_send_im_done(struct roots_input_method_relay *relay,
+ struct wlr_text_input_v3 *input) {
+ struct wlr_input_method_v2 *input_method = relay->input_method;
+ if (!input_method) {
+ wlr_log(WLR_INFO, "Sending IM_DONE but im is gone");
+ return;
+ }
+ // TODO: only send each of those if they were modified
+ wlr_input_method_v2_send_surrounding_text(input_method,
+ input->current.surrounding.text, input->current.surrounding.cursor,
+ input->current.surrounding.anchor);
+ wlr_input_method_v2_send_text_change_cause(input_method,
+ input->current.text_change_cause);
+ wlr_input_method_v2_send_content_type(input_method,
+ input->current.content_type.hint, input->current.content_type.purpose);
+ wlr_input_method_v2_send_done(input_method);
+ // TODO: pass intent, display popup size
+}
+
+static struct roots_text_input *text_input_to_roots(
+ struct roots_input_method_relay *relay,
+ struct wlr_text_input_v3 *text_input) {
+ struct roots_text_input *roots_text_input = NULL;
+ wl_list_for_each(roots_text_input, &relay->text_inputs, link) {
+ if (roots_text_input->input == text_input) {
+ return roots_text_input;
+ }
+ }
+ return NULL;
+}
+
+static void handle_text_input_enable(struct wl_listener *listener, void *data) {
+ struct roots_input_method_relay *relay = wl_container_of(listener, relay,
+ text_input_enable);
+ if (relay->input_method == NULL) {
+ wlr_log(WLR_INFO, "Enabling text input when input method is gone");
+ return;
+ }
+ struct roots_text_input *text_input = text_input_to_roots(relay,
+ (struct wlr_text_input_v3*)data);
+ wlr_input_method_v2_send_activate(relay->input_method);
+ relay_send_im_done(relay, text_input->input);
+}
+
+static void handle_text_input_commit(struct wl_listener *listener,
+ void *data) {
+ struct roots_input_method_relay *relay = wl_container_of(listener, relay,
+ text_input_commit);
+ struct roots_text_input *text_input = text_input_to_roots(relay,
+ (struct wlr_text_input_v3*)data);
+ if (!text_input->input->current_enabled) {
+ wlr_log(WLR_INFO, "Inactive text input tried to commit an update");
+ return;
+ }
+ wlr_log(WLR_DEBUG, "Text input committed update");
+ if (relay->input_method == NULL) {
+ wlr_log(WLR_INFO, "Text input committed, but input method is gone");
+ return;
+ }
+ relay_send_im_done(relay, text_input->input);
+}
+
+static void relay_disable_text_input(struct roots_input_method_relay *relay,
+ struct roots_text_input *text_input) {
+ if (relay->input_method == NULL) {
+ wlr_log(WLR_DEBUG, "Disabling text input, but input method is gone");
+ return;
+ }
+ wlr_input_method_v2_send_deactivate(relay->input_method);
+ relay_send_im_done(relay, text_input->input);
+}
+
+static void handle_text_input_disable(struct wl_listener *listener,
+ void *data) {
+ struct roots_input_method_relay *relay = wl_container_of(listener, relay,
+ text_input_disable);
+ struct roots_text_input *text_input = text_input_to_roots(relay,
+ (struct wlr_text_input_v3*)data);
+ relay_disable_text_input(relay, text_input);
+}
+
+static void handle_text_input_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_input_method_relay *relay = wl_container_of(listener, relay,
+ text_input_destroy);
+ struct roots_text_input *text_input = text_input_to_roots(relay,
+ (struct wlr_text_input_v3*)data);
+
+ if (text_input->input->current_enabled) {
+ relay_disable_text_input(relay, text_input);
+ }
+ text_input_clear_pending_focused_surface(text_input);
+ wl_list_remove(&text_input->link);
+ text_input->input = NULL;
+ free(text_input);
+}
+
+static void handle_pending_focused_surface_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_text_input *text_input = wl_container_of(listener, text_input,
+ pending_focused_surface_destroy);
+ struct wlr_surface *surface = data;
+ assert(text_input->pending_focused_surface == surface);
+ text_input->pending_focused_surface = NULL;
+}
+
+struct roots_text_input *roots_text_input_create(
+ struct roots_input_method_relay *relay,
+ struct wlr_text_input_v3 *text_input) {
+ struct roots_text_input *input = calloc(1, sizeof(struct roots_text_input));
+ if (!input) {
+ return NULL;
+ }
+ input->input = text_input;
+ input->relay = relay;
+
+ wl_signal_add(&text_input->events.enable, &relay->text_input_enable);
+ relay->text_input_enable.notify = handle_text_input_enable;
+
+ wl_signal_add(&text_input->events.commit, &relay->text_input_commit);
+ relay->text_input_commit.notify = handle_text_input_commit;
+
+ wl_signal_add(&text_input->events.disable, &relay->text_input_disable);
+ relay->text_input_disable.notify = handle_text_input_disable;
+
+ wl_signal_add(&text_input->events.destroy, &relay->text_input_destroy);
+ relay->text_input_destroy.notify = handle_text_input_destroy;
+
+ input->pending_focused_surface_destroy.notify =
+ handle_pending_focused_surface_destroy;
+ wl_list_init(&input->pending_focused_surface_destroy.link);
+ return input;
+}
+
+static void relay_handle_text_input(struct wl_listener *listener,
+ void *data) {
+ struct roots_input_method_relay *relay = wl_container_of(listener, relay,
+ text_input_new);
+ struct wlr_text_input_v3 *wlr_text_input = data;
+ if (relay->seat->seat != wlr_text_input->seat) {
+ return;
+ }
+
+ struct roots_text_input *text_input = roots_text_input_create(relay,
+ wlr_text_input);
+ if (!text_input) {
+ return;
+ }
+ wl_list_insert(&relay->text_inputs, &text_input->link);
+}
+
+static void relay_handle_input_method(struct wl_listener *listener,
+ void *data) {
+ struct roots_input_method_relay *relay = wl_container_of(listener, relay,
+ input_method_new);
+ struct wlr_input_method_v2 *input_method = data;
+ if (relay->seat->seat != input_method->seat) {
+ return;
+ }
+
+ if (relay->input_method != NULL) {
+ wlr_log(WLR_INFO, "Attempted to connect second input method to a seat");
+ wlr_input_method_v2_send_unavailable(input_method);
+ return;
+ }
+
+ relay->input_method = input_method;
+ wl_signal_add(&relay->input_method->events.commit,
+ &relay->input_method_commit);
+ relay->input_method_commit.notify = handle_im_commit;
+ wl_signal_add(&relay->input_method->events.destroy,
+ &relay->input_method_destroy);
+ relay->input_method_destroy.notify = handle_im_destroy;
+
+ struct roots_text_input *text_input = relay_get_focusable_text_input(relay);
+ if (text_input) {
+ wlr_text_input_v3_send_enter(text_input->input,
+ text_input->pending_focused_surface);
+ text_input_clear_pending_focused_surface(text_input);
+ }
+}
+
+void roots_input_method_relay_init(struct roots_seat *seat,
+ struct roots_input_method_relay *relay) {
+ relay->seat = seat;
+ wl_list_init(&relay->text_inputs);
+
+ relay->text_input_new.notify = relay_handle_text_input;
+ wl_signal_add(&seat->input->server->desktop->text_input->events.text_input,
+ &relay->text_input_new);
+
+ relay->input_method_new.notify = relay_handle_input_method;
+ wl_signal_add(
+ &seat->input->server->desktop->input_method->events.input_method,
+ &relay->input_method_new);
+}
+
+void roots_input_method_relay_set_focus(struct roots_input_method_relay *relay,
+ struct wlr_surface *surface) {
+ struct roots_text_input *text_input;
+ wl_list_for_each(text_input, &relay->text_inputs, link) {
+ if (text_input->pending_focused_surface) {
+ assert(text_input->input->focused_surface == NULL);
+ if (surface != text_input->pending_focused_surface) {
+ text_input_clear_pending_focused_surface(text_input);
+ }
+ } else if (text_input->input->focused_surface) {
+ assert(text_input->pending_focused_surface == NULL);
+ if (surface != text_input->input->focused_surface) {
+ relay_disable_text_input(relay, text_input);
+ wlr_text_input_v3_send_leave(text_input->input);
+ }
+ } else if (surface
+ && wl_resource_get_client(text_input->input->resource)
+ == wl_resource_get_client(surface->resource)) {
+ if (relay->input_method) {
+ wlr_text_input_v3_send_enter(text_input->input, surface);
+ } else {
+ text_input_set_pending_focused_surface(text_input, surface);
+ }
+ }
+ }
+}
diff --git a/rootston/virtual_keyboard.c b/rootston/virtual_keyboard.c
new file mode 100644
index 00000000..e862caf4
--- /dev/null
+++ b/rootston/virtual_keyboard.c
@@ -0,0 +1,21 @@
+#define _POSIX_C_SOURCE 199309L
+
+#include <wlr/util/log.h>
+#include <wlr/types/wlr_virtual_keyboard_v1.h>
+#include "rootston/virtual_keyboard.h"
+#include "rootston/seat.h"
+
+void handle_virtual_keyboard(struct wl_listener *listener, void *data) {
+ struct roots_desktop *desktop =
+ wl_container_of(listener, desktop, virtual_keyboard_new);
+ struct wlr_virtual_keyboard_v1 *keyboard = data;
+
+ struct roots_seat *seat = input_seat_from_wlr_seat(desktop->server->input,
+ keyboard->seat);
+ if (!seat) {
+ wlr_log(WLR_ERROR, "could not find roots seat");
+ return;
+ }
+
+ roots_seat_add_device(seat, &keyboard->input_device);
+}
diff --git a/rootston/wl_shell.c b/rootston/wl_shell.c
new file mode 100644
index 00000000..2bf4f4c2
--- /dev/null
+++ b/rootston/wl_shell.c
@@ -0,0 +1,295 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/types/wlr_wl_shell.h>
+#include <wlr/util/log.h>
+#include "rootston/desktop.h"
+#include "rootston/input.h"
+#include "rootston/server.h"
+
+static void popup_destroy(struct roots_view_child *child) {
+ assert(child->destroy == popup_destroy);
+ struct roots_wl_shell_popup *popup = (struct roots_wl_shell_popup *)child;
+ if (popup == NULL) {
+ return;
+ }
+ wl_list_remove(&popup->destroy.link);
+ wl_list_remove(&popup->set_state.link);
+ wl_list_remove(&popup->new_popup.link);
+ view_child_finish(&popup->view_child);
+ free(popup);
+}
+
+static void popup_handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_popup *popup =
+ wl_container_of(listener, popup, destroy);
+ popup_destroy((struct roots_view_child *)popup);
+}
+
+static void popup_handle_set_state(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_popup *popup =
+ wl_container_of(listener, popup, set_state);
+ popup_destroy((struct roots_view_child *)popup);
+}
+
+static struct roots_wl_shell_popup *popup_create(struct roots_view *view,
+ struct wlr_wl_shell_surface *wlr_wl_shell_surface);
+
+static void popup_handle_new_popup(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_popup *popup =
+ wl_container_of(listener, popup, new_popup);
+ struct wlr_wl_shell_surface *wlr_wl_shell_surface = data;
+ popup_create(popup->view_child.view, wlr_wl_shell_surface);
+}
+
+static struct roots_wl_shell_popup *popup_create(struct roots_view *view,
+ struct wlr_wl_shell_surface *wlr_wl_shell_surface) {
+ struct roots_wl_shell_popup *popup =
+ calloc(1, sizeof(struct roots_wl_shell_popup));
+ if (popup == NULL) {
+ return NULL;
+ }
+ popup->wlr_wl_shell_surface = wlr_wl_shell_surface;
+ popup->view_child.destroy = popup_destroy;
+ view_child_init(&popup->view_child, view, wlr_wl_shell_surface->surface);
+ popup->destroy.notify = popup_handle_destroy;
+ wl_signal_add(&wlr_wl_shell_surface->events.destroy, &popup->destroy);
+ popup->set_state.notify = popup_handle_set_state;
+ wl_signal_add(&wlr_wl_shell_surface->events.set_state, &popup->set_state);
+ popup->new_popup.notify = popup_handle_new_popup;
+ wl_signal_add(&wlr_wl_shell_surface->events.new_popup, &popup->new_popup);
+ return popup;
+}
+
+
+static void resize(struct roots_view *view, uint32_t width, uint32_t height) {
+ assert(view->type == ROOTS_WL_SHELL_VIEW);
+ struct wlr_wl_shell_surface *surf = view->wl_shell_surface;
+ wlr_wl_shell_surface_configure(surf, WL_SHELL_SURFACE_RESIZE_NONE, width,
+ height);
+}
+
+static void close(struct roots_view *view) {
+ assert(view->type == ROOTS_WL_SHELL_VIEW);
+ struct wlr_wl_shell_surface *surf = view->wl_shell_surface;
+ wl_client_destroy(surf->client);
+}
+
+static void destroy(struct roots_view *view) {
+ assert(view->type == ROOTS_WL_SHELL_VIEW);
+ struct roots_wl_shell_surface *roots_surface = view->roots_wl_shell_surface;
+ wl_list_remove(&roots_surface->destroy.link);
+ wl_list_remove(&roots_surface->request_move.link);
+ wl_list_remove(&roots_surface->request_resize.link);
+ wl_list_remove(&roots_surface->request_maximize.link);
+ wl_list_remove(&roots_surface->request_fullscreen.link);
+ wl_list_remove(&roots_surface->set_state.link);
+ wl_list_remove(&roots_surface->set_title.link);
+ wl_list_remove(&roots_surface->set_class.link);
+ wl_list_remove(&roots_surface->surface_commit.link);
+ free(roots_surface);
+}
+
+static void handle_request_move(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_move);
+ struct roots_view *view = roots_surface->view;
+ struct roots_input *input = view->desktop->server->input;
+ struct wlr_wl_shell_surface_move_event *e = data;
+ struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat);
+ if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ return;
+ }
+ roots_seat_begin_move(seat, view);
+}
+
+static void handle_request_resize(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_resize);
+ struct roots_view *view = roots_surface->view;
+ struct roots_input *input = view->desktop->server->input;
+ struct wlr_wl_shell_surface_resize_event *e = data;
+ struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat);
+ if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ return;
+ }
+ roots_seat_begin_resize(seat, view, e->edges);
+}
+
+static void handle_request_maximize(struct wl_listener *listener,
+ void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_maximize);
+ struct roots_view *view = roots_surface->view;
+ //struct wlr_wl_shell_surface_maximize_event *e = data;
+ view_maximize(view, true);
+}
+
+static void handle_request_fullscreen(struct wl_listener *listener,
+ void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_fullscreen);
+ struct roots_view *view = roots_surface->view;
+ struct wlr_wl_shell_surface_set_fullscreen_event *e = data;
+ view_set_fullscreen(view, true, e->output);
+}
+
+static void handle_set_state(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, set_state);
+ struct roots_view *view = roots_surface->view;
+ struct wlr_wl_shell_surface *surface = view->wl_shell_surface;
+ if (view->maximized &&
+ surface->state != WLR_WL_SHELL_SURFACE_STATE_MAXIMIZED) {
+ view_maximize(view, false);
+ }
+ if (view->fullscreen_output != NULL &&
+ surface->state != WLR_WL_SHELL_SURFACE_STATE_FULLSCREEN) {
+ view_set_fullscreen(view, false, NULL);
+ }
+}
+
+static void handle_set_title(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, set_state);
+ view_set_title(roots_surface->view,
+ roots_surface->view->wl_shell_surface->title);
+}
+
+static void handle_set_class(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, set_state);
+ view_set_app_id(roots_surface->view,
+ roots_surface->view->wl_shell_surface->class);
+}
+
+static void handle_surface_commit(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, surface_commit);
+ struct roots_view *view = roots_surface->view;
+ struct wlr_surface *wlr_surface = view->wlr_surface;
+
+ view_apply_damage(view);
+
+ int width = wlr_surface->current.width;
+ int height = wlr_surface->current.height;
+ view_update_size(view, width, height);
+
+ double x = view->box.x;
+ double y = view->box.y;
+ if (view->pending_move_resize.update_x) {
+ x = view->pending_move_resize.x + view->pending_move_resize.width -
+ width;
+ view->pending_move_resize.update_x = false;
+ }
+ if (view->pending_move_resize.update_y) {
+ y = view->pending_move_resize.y + view->pending_move_resize.height -
+ height;
+ view->pending_move_resize.update_y = false;
+ }
+ view_update_position(view, x, y);
+}
+
+static void handle_new_popup(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, new_popup);
+ struct wlr_wl_shell_surface *wlr_wl_shell_surface = data;
+ popup_create(roots_surface->view, wlr_wl_shell_surface);
+}
+
+static void handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_wl_shell_surface *roots_surface =
+ wl_container_of(listener, roots_surface, destroy);
+ view_destroy(roots_surface->view);
+}
+
+void handle_wl_shell_surface(struct wl_listener *listener, void *data) {
+ struct roots_desktop *desktop =
+ wl_container_of(listener, desktop, wl_shell_surface);
+ struct wlr_wl_shell_surface *surface = data;
+
+ if (surface->state == WLR_WL_SHELL_SURFACE_STATE_POPUP) {
+ wlr_log(WLR_DEBUG, "new wl shell popup");
+ return;
+ }
+
+ wlr_log(WLR_DEBUG, "new wl shell surface: title=%s, class=%s",
+ surface->title, surface->class);
+ wlr_wl_shell_surface_ping(surface);
+
+ struct roots_wl_shell_surface *roots_surface =
+ calloc(1, sizeof(struct roots_wl_shell_surface));
+ if (!roots_surface) {
+ return;
+ }
+ roots_surface->destroy.notify = handle_destroy;
+ wl_signal_add(&surface->events.destroy, &roots_surface->destroy);
+ roots_surface->new_popup.notify = handle_new_popup;
+ wl_signal_add(&surface->events.new_popup, &roots_surface->new_popup);
+ roots_surface->request_move.notify = handle_request_move;
+ wl_signal_add(&surface->events.request_move, &roots_surface->request_move);
+ roots_surface->request_resize.notify = handle_request_resize;
+ wl_signal_add(&surface->events.request_resize,
+ &roots_surface->request_resize);
+ roots_surface->request_maximize.notify = handle_request_maximize;
+ wl_signal_add(&surface->events.request_maximize,
+ &roots_surface->request_maximize);
+ roots_surface->request_fullscreen.notify =
+ handle_request_fullscreen;
+ wl_signal_add(&surface->events.request_fullscreen,
+ &roots_surface->request_fullscreen);
+
+ roots_surface->set_state.notify = handle_set_state;
+ wl_signal_add(&surface->events.set_state, &roots_surface->set_state);
+ roots_surface->set_title.notify = handle_set_title;
+ wl_signal_add(&surface->events.set_title, &roots_surface->set_title);
+ roots_surface->set_class.notify = handle_set_class;
+ wl_signal_add(&surface->events.set_class, &roots_surface->set_class);
+ roots_surface->surface_commit.notify = handle_surface_commit;
+ wl_signal_add(&surface->surface->events.commit, &roots_surface->surface_commit);
+
+ struct roots_view *view = view_create(desktop);
+ if (!view) {
+ free(roots_surface);
+ return;
+ }
+ view->type = ROOTS_WL_SHELL_VIEW;
+ view->box.width = surface->surface->current.width;
+ view->box.height = surface->surface->current.height;
+
+ view->wl_shell_surface = surface;
+ view->roots_wl_shell_surface = roots_surface;
+ view->resize = resize;
+ view->close = close;
+ view->destroy = destroy;
+ roots_surface->view = view;
+
+ view_map(view, surface->surface);
+ view_setup(view);
+
+ wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle,
+ view->wl_shell_surface->title ?: "none");
+ wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle,
+ view->wl_shell_surface->class ?: "none");
+
+ if (surface->state == WLR_WL_SHELL_SURFACE_STATE_TRANSIENT) {
+ // We need to map it relative to the parent
+ bool found = false;
+ struct roots_view *parent;
+ wl_list_for_each(parent, &desktop->views, link) {
+ if (parent->type == ROOTS_WL_SHELL_VIEW &&
+ parent->wl_shell_surface == surface->parent) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ view_move(view,
+ parent->box.x + surface->transient_state->x,
+ parent->box.y + surface->transient_state->y);
+ }
+ }
+}
diff --git a/rootston/xdg_shell.c b/rootston/xdg_shell.c
new file mode 100644
index 00000000..da8909ba
--- /dev/null
+++ b/rootston/xdg_shell.c
@@ -0,0 +1,565 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/types/wlr_xdg_shell.h>
+#include <wlr/util/log.h>
+#include "rootston/cursor.h"
+#include "rootston/desktop.h"
+#include "rootston/input.h"
+#include "rootston/server.h"
+
+static void popup_destroy(struct roots_view_child *child) {
+ assert(child->destroy == popup_destroy);
+ struct roots_xdg_popup *popup = (struct roots_xdg_popup *)child;
+ if (popup == NULL) {
+ return;
+ }
+ wl_list_remove(&popup->destroy.link);
+ wl_list_remove(&popup->new_popup.link);
+ wl_list_remove(&popup->map.link);
+ wl_list_remove(&popup->unmap.link);
+ view_child_finish(&popup->view_child);
+ free(popup);
+}
+
+static void popup_handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_xdg_popup *popup =
+ wl_container_of(listener, popup, destroy);
+ popup_destroy((struct roots_view_child *)popup);
+}
+
+static void popup_handle_map(struct wl_listener *listener, void *data) {
+ struct roots_xdg_popup *popup = wl_container_of(listener, popup, map);
+ view_damage_whole(popup->view_child.view);
+ input_update_cursor_focus(popup->view_child.view->desktop->server->input);
+}
+
+static void popup_handle_unmap(struct wl_listener *listener, void *data) {
+ struct roots_xdg_popup *popup = wl_container_of(listener, popup, unmap);
+ view_damage_whole(popup->view_child.view);
+}
+
+static struct roots_xdg_popup *popup_create(struct roots_view *view,
+ struct wlr_xdg_popup *wlr_popup);
+
+static void popup_handle_new_popup(struct wl_listener *listener, void *data) {
+ struct roots_xdg_popup *popup =
+ wl_container_of(listener, popup, new_popup);
+ struct wlr_xdg_popup *wlr_popup = data;
+ popup_create(popup->view_child.view, wlr_popup);
+}
+
+static void popup_unconstrain(struct roots_xdg_popup *popup) {
+ // get the output of the popup's positioner anchor point and convert it to
+ // the toplevel parent's coordinate system and then pass it to
+ // wlr_xdg_popup_v6_unconstrain_from_box
+
+ // TODO: unconstrain popups for rotated windows
+ if (popup->view_child.view->rotation != 0.0) {
+ return;
+ }
+
+ struct roots_view *view = popup->view_child.view;
+ struct wlr_output_layout *layout = view->desktop->layout;
+ struct wlr_xdg_popup *wlr_popup = popup->wlr_popup;
+
+ int anchor_lx, anchor_ly;
+ wlr_xdg_popup_get_anchor_point(wlr_popup, &anchor_lx, &anchor_ly);
+
+ int popup_lx, popup_ly;
+ wlr_xdg_popup_get_toplevel_coords(wlr_popup, wlr_popup->geometry.x,
+ wlr_popup->geometry.y, &popup_lx, &popup_ly);
+ popup_lx += view->box.x;
+ popup_ly += view->box.y;
+
+ anchor_lx += popup_lx;
+ anchor_ly += popup_ly;
+
+ double dest_x = 0, dest_y = 0;
+ wlr_output_layout_closest_point(layout, NULL, anchor_lx, anchor_ly,
+ &dest_x, &dest_y);
+
+ struct wlr_output *output =
+ wlr_output_layout_output_at(layout, dest_x, dest_y);
+
+ if (output == NULL) {
+ return;
+ }
+
+ int width = 0, height = 0;
+ wlr_output_effective_resolution(output, &width, &height);
+
+ // the output box expressed in the coordinate system of the toplevel parent
+ // of the popup
+ struct wlr_box output_toplevel_sx_box = {
+ .x = output->lx - view->box.x,
+ .y = output->ly - view->box.y,
+ .width = width,
+ .height = height
+ };
+
+ wlr_xdg_popup_unconstrain_from_box(
+ popup->wlr_popup, &output_toplevel_sx_box);
+}
+
+static struct roots_xdg_popup *popup_create(struct roots_view *view,
+ struct wlr_xdg_popup *wlr_popup) {
+ struct roots_xdg_popup *popup =
+ calloc(1, sizeof(struct roots_xdg_popup));
+ if (popup == NULL) {
+ return NULL;
+ }
+ popup->wlr_popup = wlr_popup;
+ popup->view_child.destroy = popup_destroy;
+ view_child_init(&popup->view_child, view, wlr_popup->base->surface);
+ popup->destroy.notify = popup_handle_destroy;
+ wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy);
+ popup->map.notify = popup_handle_map;
+ wl_signal_add(&wlr_popup->base->events.map, &popup->map);
+ popup->unmap.notify = popup_handle_unmap;
+ wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap);
+ popup->new_popup.notify = popup_handle_new_popup;
+ wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup);
+
+ popup_unconstrain(popup);
+
+ return popup;
+}
+
+
+static void get_size(const struct roots_view *view, struct wlr_box *box) {
+ assert(view->type == ROOTS_XDG_SHELL_VIEW);
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+
+ struct wlr_box geo_box;
+ wlr_xdg_surface_get_geometry(surface, &geo_box);
+ box->width = geo_box.width;
+ box->height = geo_box.height;
+}
+
+static void activate(struct roots_view *view, bool active) {
+ assert(view->type == ROOTS_XDG_SHELL_VIEW);
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+ if (surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
+ wlr_xdg_toplevel_set_activated(surface, active);
+ }
+}
+
+static void apply_size_constraints(struct wlr_xdg_surface *surface,
+ uint32_t width, uint32_t height, uint32_t *dest_width,
+ uint32_t *dest_height) {
+ *dest_width = width;
+ *dest_height = height;
+
+ struct wlr_xdg_toplevel_state *state = &surface->toplevel->current;
+ if (width < state->min_width) {
+ *dest_width = state->min_width;
+ } else if (state->max_width > 0 &&
+ width > state->max_width) {
+ *dest_width = state->max_width;
+ }
+ if (height < state->min_height) {
+ *dest_height = state->min_height;
+ } else if (state->max_height > 0 &&
+ height > state->max_height) {
+ *dest_height = state->max_height;
+ }
+}
+
+static void resize(struct roots_view *view, uint32_t width, uint32_t height) {
+ assert(view->type == ROOTS_XDG_SHELL_VIEW);
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+ if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ uint32_t constrained_width, constrained_height;
+ apply_size_constraints(surface, width, height, &constrained_width,
+ &constrained_height);
+
+ wlr_xdg_toplevel_set_size(surface, constrained_width,
+ constrained_height);
+}
+
+static void move_resize(struct roots_view *view, double x, double y,
+ uint32_t width, uint32_t height) {
+ assert(view->type == ROOTS_XDG_SHELL_VIEW);
+ struct roots_xdg_surface *roots_surface = view->roots_xdg_surface;
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+ if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ bool update_x = x != view->box.x;
+ bool update_y = y != view->box.y;
+
+ uint32_t constrained_width, constrained_height;
+ apply_size_constraints(surface, width, height, &constrained_width,
+ &constrained_height);
+
+ if (update_x) {
+ x = x + width - constrained_width;
+ }
+ if (update_y) {
+ y = y + height - constrained_height;
+ }
+
+ view->pending_move_resize.update_x = update_x;
+ view->pending_move_resize.update_y = update_y;
+ view->pending_move_resize.x = x;
+ view->pending_move_resize.y = y;
+ view->pending_move_resize.width = constrained_width;
+ view->pending_move_resize.height = constrained_height;
+
+ uint32_t serial = wlr_xdg_toplevel_set_size(surface, constrained_width,
+ constrained_height);
+ if (serial > 0) {
+ roots_surface->pending_move_resize_configure_serial = serial;
+ } else if (roots_surface->pending_move_resize_configure_serial == 0) {
+ view_update_position(view, x, y);
+ }
+}
+
+static void maximize(struct roots_view *view, bool maximized) {
+ assert(view->type == ROOTS_XDG_SHELL_VIEW);
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+ if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ wlr_xdg_toplevel_set_maximized(surface, maximized);
+}
+
+static void set_fullscreen(struct roots_view *view, bool fullscreen) {
+ assert(view->type == ROOTS_XDG_SHELL_VIEW);
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+ if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ wlr_xdg_toplevel_set_fullscreen(surface, fullscreen);
+}
+
+static void close(struct roots_view *view) {
+ assert(view->type == ROOTS_XDG_SHELL_VIEW);
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+ struct wlr_xdg_popup *popup = NULL;
+ wl_list_for_each(popup, &surface->popups, link) {
+ wlr_xdg_surface_send_close(popup->base);
+ }
+ wlr_xdg_surface_send_close(surface);
+}
+
+static void destroy(struct roots_view *view) {
+ assert(view->type == ROOTS_XDG_SHELL_VIEW);
+ struct roots_xdg_surface *roots_xdg_surface = view->roots_xdg_surface;
+ wl_list_remove(&roots_xdg_surface->surface_commit.link);
+ wl_list_remove(&roots_xdg_surface->destroy.link);
+ wl_list_remove(&roots_xdg_surface->new_popup.link);
+ wl_list_remove(&roots_xdg_surface->map.link);
+ wl_list_remove(&roots_xdg_surface->unmap.link);
+ wl_list_remove(&roots_xdg_surface->request_move.link);
+ wl_list_remove(&roots_xdg_surface->request_resize.link);
+ wl_list_remove(&roots_xdg_surface->request_maximize.link);
+ wl_list_remove(&roots_xdg_surface->request_fullscreen.link);
+ wl_list_remove(&roots_xdg_surface->set_title.link);
+ wl_list_remove(&roots_xdg_surface->set_app_id.link);
+ roots_xdg_surface->view->xdg_surface->data = NULL;
+ free(roots_xdg_surface);
+}
+
+static void handle_request_move(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, request_move);
+ struct roots_view *view = roots_xdg_surface->view;
+ struct roots_input *input = view->desktop->server->input;
+ struct wlr_xdg_toplevel_move_event *e = data;
+ struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat);
+ // TODO verify event serial
+ if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ return;
+ }
+ roots_seat_begin_move(seat, view);
+}
+
+static void handle_request_resize(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, request_resize);
+ struct roots_view *view = roots_xdg_surface->view;
+ struct roots_input *input = view->desktop->server->input;
+ struct wlr_xdg_toplevel_resize_event *e = data;
+ // TODO verify event serial
+ struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat);
+ assert(seat);
+ if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ return;
+ }
+ roots_seat_begin_resize(seat, view, e->edges);
+}
+
+static void handle_request_maximize(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, request_maximize);
+ struct roots_view *view = roots_xdg_surface->view;
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+
+ if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ view_maximize(view, surface->toplevel->client_pending.maximized);
+}
+
+static void handle_request_fullscreen(struct wl_listener *listener,
+ void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, request_fullscreen);
+ struct roots_view *view = roots_xdg_surface->view;
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+ struct wlr_xdg_toplevel_set_fullscreen_event *e = data;
+
+ if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ view_set_fullscreen(view, e->fullscreen, e->output);
+}
+
+static void handle_set_title(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, set_title);
+
+ view_set_title(roots_xdg_surface->view,
+ roots_xdg_surface->view->xdg_surface->toplevel->title);
+}
+
+static void handle_set_app_id(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, set_app_id);
+
+ view_set_app_id(roots_xdg_surface->view,
+ roots_xdg_surface->view->xdg_surface->toplevel->app_id);
+}
+
+static void handle_surface_commit(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_surface =
+ wl_container_of(listener, roots_surface, surface_commit);
+ struct roots_view *view = roots_surface->view;
+ struct wlr_xdg_surface *surface = view->xdg_surface;
+
+ if (!surface->mapped) {
+ return;
+ }
+
+ view_apply_damage(view);
+
+ struct wlr_box size;
+ get_size(view, &size);
+ view_update_size(view, size.width, size.height);
+
+ uint32_t pending_serial =
+ roots_surface->pending_move_resize_configure_serial;
+ if (pending_serial > 0 && pending_serial >= surface->configure_serial) {
+ double x = view->box.x;
+ double y = view->box.y;
+ if (view->pending_move_resize.update_x) {
+ x = view->pending_move_resize.x + view->pending_move_resize.width -
+ size.width;
+ }
+ if (view->pending_move_resize.update_y) {
+ y = view->pending_move_resize.y + view->pending_move_resize.height -
+ size.height;
+ }
+ view_update_position(view, x, y);
+
+ if (pending_serial == surface->configure_serial) {
+ roots_surface->pending_move_resize_configure_serial = 0;
+ }
+ }
+}
+
+static void handle_new_popup(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, new_popup);
+ struct wlr_xdg_popup *wlr_popup = data;
+ popup_create(roots_xdg_surface->view, wlr_popup);
+}
+
+static void handle_map(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, map);
+ struct roots_view *view = roots_xdg_surface->view;
+
+ struct wlr_box box;
+ get_size(view, &box);
+ view->box.width = box.width;
+ view->box.height = box.height;
+
+ view_map(view, view->xdg_surface->surface);
+ view_setup(view);
+
+ wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle,
+ view->xdg_surface->toplevel->title ?: "none");
+ wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle,
+ view->xdg_surface->toplevel->app_id ?: "none");
+}
+
+static void handle_unmap(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, unmap);
+ view_unmap(roots_xdg_surface->view);
+}
+
+static void handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, destroy);
+ view_destroy(roots_xdg_surface->view);
+}
+
+void handle_xdg_shell_surface(struct wl_listener *listener, void *data) {
+ struct wlr_xdg_surface *surface = data;
+ assert(surface->role != WLR_XDG_SURFACE_ROLE_NONE);
+
+ if (surface->role == WLR_XDG_SURFACE_ROLE_POPUP) {
+ wlr_log(WLR_DEBUG, "new xdg popup");
+ return;
+ }
+
+ struct roots_desktop *desktop =
+ wl_container_of(listener, desktop, xdg_shell_surface);
+
+ wlr_log(WLR_DEBUG, "new xdg toplevel: title=%s, app_id=%s",
+ surface->toplevel->title, surface->toplevel->app_id);
+ wlr_xdg_surface_ping(surface);
+
+ struct roots_xdg_surface *roots_surface =
+ calloc(1, sizeof(struct roots_xdg_surface));
+ if (!roots_surface) {
+ return;
+ }
+ roots_surface->surface_commit.notify = handle_surface_commit;
+ wl_signal_add(&surface->surface->events.commit,
+ &roots_surface->surface_commit);
+ roots_surface->destroy.notify = handle_destroy;
+ wl_signal_add(&surface->events.destroy, &roots_surface->destroy);
+ roots_surface->map.notify = handle_map;
+ wl_signal_add(&surface->events.map, &roots_surface->map);
+ roots_surface->unmap.notify = handle_unmap;
+ wl_signal_add(&surface->events.unmap, &roots_surface->unmap);
+ roots_surface->request_move.notify = handle_request_move;
+ wl_signal_add(&surface->toplevel->events.request_move,
+ &roots_surface->request_move);
+ roots_surface->request_resize.notify = handle_request_resize;
+ wl_signal_add(&surface->toplevel->events.request_resize,
+ &roots_surface->request_resize);
+ roots_surface->request_maximize.notify = handle_request_maximize;
+ wl_signal_add(&surface->toplevel->events.request_maximize,
+ &roots_surface->request_maximize);
+ roots_surface->request_fullscreen.notify = handle_request_fullscreen;
+ wl_signal_add(&surface->toplevel->events.request_fullscreen,
+ &roots_surface->request_fullscreen);
+ roots_surface->set_title.notify = handle_set_title;
+ wl_signal_add(&surface->toplevel->events.set_title, &roots_surface->set_title);
+ roots_surface->set_app_id.notify = handle_set_app_id;
+ wl_signal_add(&surface->toplevel->events.set_app_id,
+ &roots_surface->set_app_id);
+ roots_surface->new_popup.notify = handle_new_popup;
+ wl_signal_add(&surface->events.new_popup, &roots_surface->new_popup);
+ surface->data = roots_surface;
+
+ struct roots_view *view = view_create(desktop);
+ if (!view) {
+ free(roots_surface);
+ return;
+ }
+ view->type = ROOTS_XDG_SHELL_VIEW;
+
+ view->xdg_surface = surface;
+ view->roots_xdg_surface = roots_surface;
+ view->activate = activate;
+ view->resize = resize;
+ view->move_resize = move_resize;
+ view->maximize = maximize;
+ view->set_fullscreen = set_fullscreen;
+ view->close = close;
+ view->destroy = destroy;
+ roots_surface->view = view;
+
+ if (surface->toplevel->client_pending.maximized) {
+ view_maximize(view, true);
+ }
+ if (surface->toplevel->client_pending.fullscreen) {
+ view_set_fullscreen(view, true, NULL);
+ }
+}
+
+
+
+static void decoration_handle_destroy(struct wl_listener *listener,
+ void *data) {
+ struct roots_xdg_toplevel_decoration *decoration =
+ wl_container_of(listener, decoration, destroy);
+
+ decoration->surface->xdg_toplevel_decoration = NULL;
+ view_update_decorated(decoration->surface->view, false);
+ wl_list_remove(&decoration->destroy.link);
+ wl_list_remove(&decoration->request_mode.link);
+ wl_list_remove(&decoration->surface_commit.link);
+ free(decoration);
+}
+
+static void decoration_handle_request_mode(struct wl_listener *listener,
+ void *data) {
+ struct roots_xdg_toplevel_decoration *decoration =
+ wl_container_of(listener, decoration, request_mode);
+
+ enum wlr_xdg_toplevel_decoration_v1_mode mode =
+ decoration->wlr_decoration->client_pending_mode;
+ if (mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE) {
+ mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
+ }
+ wlr_xdg_toplevel_decoration_v1_set_mode(decoration->wlr_decoration, mode);
+}
+
+static void decoration_handle_surface_commit(struct wl_listener *listener,
+ void *data) {
+ struct roots_xdg_toplevel_decoration *decoration =
+ wl_container_of(listener, decoration, surface_commit);
+
+ bool decorated = decoration->wlr_decoration->current_mode ==
+ WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
+ view_update_decorated(decoration->surface->view, decorated);
+}
+
+void handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data) {
+ struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration = data;
+
+ wlr_log(WLR_DEBUG, "new xdg toplevel decoration");
+
+ struct roots_xdg_surface *xdg_surface = wlr_decoration->surface->data;
+ assert(xdg_surface != NULL);
+ struct wlr_xdg_surface *wlr_xdg_surface = xdg_surface->view->xdg_surface;
+
+ struct roots_xdg_toplevel_decoration *decoration =
+ calloc(1, sizeof(struct roots_xdg_toplevel_decoration));
+ if (decoration == NULL) {
+ return;
+ }
+ decoration->wlr_decoration = wlr_decoration;
+ decoration->surface = xdg_surface;
+ xdg_surface->xdg_toplevel_decoration = decoration;
+
+ decoration->destroy.notify = decoration_handle_destroy;
+ wl_signal_add(&wlr_decoration->events.destroy, &decoration->destroy);
+ decoration->request_mode.notify = decoration_handle_request_mode;
+ wl_signal_add(&wlr_decoration->events.request_mode,
+ &decoration->request_mode);
+ decoration->surface_commit.notify = decoration_handle_surface_commit;
+ wl_signal_add(&wlr_xdg_surface->surface->events.commit,
+ &decoration->surface_commit);
+
+ decoration_handle_request_mode(&decoration->request_mode, wlr_decoration);
+}
diff --git a/rootston/xdg_shell_v6.c b/rootston/xdg_shell_v6.c
new file mode 100644
index 00000000..8d989aef
--- /dev/null
+++ b/rootston/xdg_shell_v6.c
@@ -0,0 +1,495 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/types/wlr_xdg_shell_v6.h>
+#include <wlr/util/log.h>
+#include "rootston/desktop.h"
+#include "rootston/input.h"
+#include "rootston/server.h"
+
+static void popup_destroy(struct roots_view_child *child) {
+ assert(child->destroy == popup_destroy);
+ struct roots_xdg_popup_v6 *popup = (struct roots_xdg_popup_v6 *)child;
+ if (popup == NULL) {
+ return;
+ }
+ wl_list_remove(&popup->destroy.link);
+ wl_list_remove(&popup->new_popup.link);
+ wl_list_remove(&popup->map.link);
+ wl_list_remove(&popup->unmap.link);
+ view_child_finish(&popup->view_child);
+ free(popup);
+}
+
+static void popup_handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_xdg_popup_v6 *popup =
+ wl_container_of(listener, popup, destroy);
+ popup_destroy((struct roots_view_child *)popup);
+}
+
+static void popup_handle_map(struct wl_listener *listener, void *data) {
+ struct roots_xdg_popup_v6 *popup =
+ wl_container_of(listener, popup, map);
+ view_damage_whole(popup->view_child.view);
+ input_update_cursor_focus(popup->view_child.view->desktop->server->input);
+}
+
+static void popup_handle_unmap(struct wl_listener *listener, void *data) {
+ struct roots_xdg_popup_v6 *popup =
+ wl_container_of(listener, popup, unmap);
+ view_damage_whole(popup->view_child.view);
+}
+
+static struct roots_xdg_popup_v6 *popup_create(struct roots_view *view,
+ struct wlr_xdg_popup_v6 *wlr_popup);
+
+static void popup_handle_new_popup(struct wl_listener *listener, void *data) {
+ struct roots_xdg_popup_v6 *popup =
+ wl_container_of(listener, popup, new_popup);
+ struct wlr_xdg_popup_v6 *wlr_popup = data;
+ popup_create(popup->view_child.view, wlr_popup);
+}
+
+static void popup_unconstrain(struct roots_xdg_popup_v6 *popup) {
+ // get the output of the popup's positioner anchor point and convert it to
+ // the toplevel parent's coordinate system and then pass it to
+ // wlr_xdg_popup_v6_unconstrain_from_box
+
+ // TODO: unconstrain popups for rotated windows
+ if (popup->view_child.view->rotation != 0.0) {
+ return;
+ }
+
+ struct roots_view *view = popup->view_child.view;
+ struct wlr_output_layout *layout = view->desktop->layout;
+ struct wlr_xdg_popup_v6 *wlr_popup = popup->wlr_popup;
+
+ int anchor_lx, anchor_ly;
+ wlr_xdg_popup_v6_get_anchor_point(wlr_popup, &anchor_lx, &anchor_ly);
+
+ int popup_lx, popup_ly;
+ wlr_xdg_popup_v6_get_toplevel_coords(wlr_popup, wlr_popup->geometry.x,
+ wlr_popup->geometry.y, &popup_lx, &popup_ly);
+ popup_lx += view->box.x;
+ popup_ly += view->box.y;
+
+ anchor_lx += popup_lx;
+ anchor_ly += popup_ly;
+
+ double dest_x = 0, dest_y = 0;
+ wlr_output_layout_closest_point(layout, NULL, anchor_lx, anchor_ly,
+ &dest_x, &dest_y);
+
+ struct wlr_output *output =
+ wlr_output_layout_output_at(layout, dest_x, dest_y);
+
+ if (output == NULL) {
+ return;
+ }
+
+ int width = 0, height = 0;
+ wlr_output_effective_resolution(output, &width, &height);
+
+ // the output box expressed in the coordinate system of the toplevel parent
+ // of the popup
+ struct wlr_box output_toplevel_sx_box = {
+ .x = output->lx - view->box.x,
+ .y = output->ly - view->box.y,
+ .width = width,
+ .height = height
+ };
+
+ wlr_xdg_popup_v6_unconstrain_from_box(popup->wlr_popup, &output_toplevel_sx_box);
+}
+
+static struct roots_xdg_popup_v6 *popup_create(struct roots_view *view,
+ struct wlr_xdg_popup_v6 *wlr_popup) {
+ struct roots_xdg_popup_v6 *popup =
+ calloc(1, sizeof(struct roots_xdg_popup_v6));
+ if (popup == NULL) {
+ return NULL;
+ }
+ popup->wlr_popup = wlr_popup;
+ popup->view_child.destroy = popup_destroy;
+ view_child_init(&popup->view_child, view, wlr_popup->base->surface);
+ popup->destroy.notify = popup_handle_destroy;
+ wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy);
+ popup->map.notify = popup_handle_map;
+ wl_signal_add(&wlr_popup->base->events.map, &popup->map);
+ popup->unmap.notify = popup_handle_unmap;
+ wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap);
+ popup->new_popup.notify = popup_handle_new_popup;
+ wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup);
+
+ popup_unconstrain(popup);
+
+ return popup;
+}
+
+
+static void get_size(const struct roots_view *view, struct wlr_box *box) {
+ assert(view->type == ROOTS_XDG_SHELL_V6_VIEW);
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+
+ struct wlr_box geo_box;
+ wlr_xdg_surface_v6_get_geometry(surface, &geo_box);
+ box->width = geo_box.width;
+ box->height = geo_box.height;
+}
+
+static void activate(struct roots_view *view, bool active) {
+ assert(view->type == ROOTS_XDG_SHELL_V6_VIEW);
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+ if (surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) {
+ wlr_xdg_toplevel_v6_set_activated(surface, active);
+ }
+}
+
+static void apply_size_constraints(struct wlr_xdg_surface_v6 *surface,
+ uint32_t width, uint32_t height, uint32_t *dest_width,
+ uint32_t *dest_height) {
+ *dest_width = width;
+ *dest_height = height;
+
+ struct wlr_xdg_toplevel_v6_state *state = &surface->toplevel->current;
+ if (width < state->min_width) {
+ *dest_width = state->min_width;
+ } else if (state->max_width > 0 &&
+ width > state->max_width) {
+ *dest_width = state->max_width;
+ }
+ if (height < state->min_height) {
+ *dest_height = state->min_height;
+ } else if (state->max_height > 0 &&
+ height > state->max_height) {
+ *dest_height = state->max_height;
+ }
+}
+
+static void resize(struct roots_view *view, uint32_t width, uint32_t height) {
+ assert(view->type == ROOTS_XDG_SHELL_V6_VIEW);
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+ if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ uint32_t constrained_width, constrained_height;
+ apply_size_constraints(surface, width, height, &constrained_width,
+ &constrained_height);
+
+ wlr_xdg_toplevel_v6_set_size(surface, constrained_width,
+ constrained_height);
+}
+
+static void move_resize(struct roots_view *view, double x, double y,
+ uint32_t width, uint32_t height) {
+ assert(view->type == ROOTS_XDG_SHELL_V6_VIEW);
+ struct roots_xdg_surface_v6 *roots_surface = view->roots_xdg_surface_v6;
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+ if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ bool update_x = x != view->box.x;
+ bool update_y = y != view->box.y;
+
+ uint32_t constrained_width, constrained_height;
+ apply_size_constraints(surface, width, height, &constrained_width,
+ &constrained_height);
+
+ if (update_x) {
+ x = x + width - constrained_width;
+ }
+ if (update_y) {
+ y = y + height - constrained_height;
+ }
+
+ view->pending_move_resize.update_x = update_x;
+ view->pending_move_resize.update_y = update_y;
+ view->pending_move_resize.x = x;
+ view->pending_move_resize.y = y;
+ view->pending_move_resize.width = constrained_width;
+ view->pending_move_resize.height = constrained_height;
+
+ uint32_t serial = wlr_xdg_toplevel_v6_set_size(surface, constrained_width,
+ constrained_height);
+ if (serial > 0) {
+ roots_surface->pending_move_resize_configure_serial = serial;
+ } else if (roots_surface->pending_move_resize_configure_serial == 0) {
+ view_update_position(view, x, y);
+ }
+}
+
+static void maximize(struct roots_view *view, bool maximized) {
+ assert(view->type == ROOTS_XDG_SHELL_V6_VIEW);
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+ if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ wlr_xdg_toplevel_v6_set_maximized(surface, maximized);
+}
+
+static void set_fullscreen(struct roots_view *view, bool fullscreen) {
+ assert(view->type == ROOTS_XDG_SHELL_V6_VIEW);
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+ if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ wlr_xdg_toplevel_v6_set_fullscreen(surface, fullscreen);
+}
+
+static void close(struct roots_view *view) {
+ assert(view->type == ROOTS_XDG_SHELL_V6_VIEW);
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+ struct wlr_xdg_popup_v6 *popup = NULL;
+ wl_list_for_each(popup, &surface->popups, link) {
+ wlr_xdg_surface_v6_send_close(popup->base);
+ }
+ wlr_xdg_surface_v6_send_close(surface);
+}
+
+static void destroy(struct roots_view *view) {
+ assert(view->type == ROOTS_XDG_SHELL_V6_VIEW);
+ struct roots_xdg_surface_v6 *roots_xdg_surface = view->roots_xdg_surface_v6;
+ wl_list_remove(&roots_xdg_surface->surface_commit.link);
+ wl_list_remove(&roots_xdg_surface->destroy.link);
+ wl_list_remove(&roots_xdg_surface->new_popup.link);
+ wl_list_remove(&roots_xdg_surface->map.link);
+ wl_list_remove(&roots_xdg_surface->unmap.link);
+ wl_list_remove(&roots_xdg_surface->request_move.link);
+ wl_list_remove(&roots_xdg_surface->request_resize.link);
+ wl_list_remove(&roots_xdg_surface->request_maximize.link);
+ wl_list_remove(&roots_xdg_surface->request_fullscreen.link);
+ wl_list_remove(&roots_xdg_surface->set_title.link);
+ wl_list_remove(&roots_xdg_surface->set_app_id.link);
+ free(roots_xdg_surface);
+}
+
+static void handle_request_move(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, request_move);
+ struct roots_view *view = roots_xdg_surface->view;
+ struct roots_input *input = view->desktop->server->input;
+ struct wlr_xdg_toplevel_v6_move_event *e = data;
+ struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat);
+ // TODO verify event serial
+ if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ return;
+ }
+ roots_seat_begin_move(seat, view);
+}
+
+static void handle_request_resize(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, request_resize);
+ struct roots_view *view = roots_xdg_surface->view;
+ struct roots_input *input = view->desktop->server->input;
+ struct wlr_xdg_toplevel_v6_resize_event *e = data;
+ // TODO verify event serial
+ struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat);
+ assert(seat);
+ if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ return;
+ }
+ roots_seat_begin_resize(seat, view, e->edges);
+}
+
+static void handle_request_maximize(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, request_maximize);
+ struct roots_view *view = roots_xdg_surface->view;
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+
+ if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ view_maximize(view, surface->toplevel->client_pending.maximized);
+}
+
+static void handle_request_fullscreen(struct wl_listener *listener,
+ void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, request_fullscreen);
+ struct roots_view *view = roots_xdg_surface->view;
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+ struct wlr_xdg_toplevel_v6_set_fullscreen_event *e = data;
+
+ if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) {
+ return;
+ }
+
+ view_set_fullscreen(view, e->fullscreen, e->output);
+}
+
+static void handle_set_title(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, set_title);
+
+ view_set_title(roots_xdg_surface->view,
+ roots_xdg_surface->view->xdg_surface_v6->toplevel->title);
+}
+
+static void handle_set_app_id(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, set_app_id);
+
+ view_set_app_id(roots_xdg_surface->view,
+ roots_xdg_surface->view->xdg_surface_v6->toplevel->app_id);
+}
+
+static void handle_surface_commit(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_surface =
+ wl_container_of(listener, roots_surface, surface_commit);
+ struct roots_view *view = roots_surface->view;
+ struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6;
+
+ if (!surface->mapped) {
+ return;
+ }
+
+ view_apply_damage(view);
+
+ struct wlr_box size;
+ get_size(view, &size);
+ view_update_size(view, size.width, size.height);
+
+ uint32_t pending_serial =
+ roots_surface->pending_move_resize_configure_serial;
+ if (pending_serial > 0 && pending_serial >= surface->configure_serial) {
+ double x = view->box.x;
+ double y = view->box.y;
+ if (view->pending_move_resize.update_x) {
+ x = view->pending_move_resize.x + view->pending_move_resize.width -
+ size.width;
+ }
+ if (view->pending_move_resize.update_y) {
+ y = view->pending_move_resize.y + view->pending_move_resize.height -
+ size.height;
+ }
+ view_update_position(view, x, y);
+
+ if (pending_serial == surface->configure_serial) {
+ roots_surface->pending_move_resize_configure_serial = 0;
+ }
+ }
+}
+
+static void handle_new_popup(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, new_popup);
+ struct wlr_xdg_popup_v6 *wlr_popup = data;
+ popup_create(roots_xdg_surface->view, wlr_popup);
+}
+
+static void handle_map(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, map);
+ struct roots_view *view = roots_xdg_surface->view;
+
+ struct wlr_box box;
+ get_size(view, &box);
+ view->box.width = box.width;
+ view->box.height = box.height;
+
+ view_map(view, view->xdg_surface_v6->surface);
+ view_setup(view);
+
+ wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle,
+ view->xdg_surface_v6->toplevel->title ?: "none");
+ wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle,
+ view->xdg_surface_v6->toplevel->app_id ?: "none");
+}
+
+static void handle_unmap(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, unmap);
+ view_unmap(roots_xdg_surface->view);
+}
+
+static void handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_xdg_surface_v6 *roots_xdg_surface =
+ wl_container_of(listener, roots_xdg_surface, destroy);
+ view_destroy(roots_xdg_surface->view);
+}
+
+void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data) {
+ struct wlr_xdg_surface_v6 *surface = data;
+ assert(surface->role != WLR_XDG_SURFACE_V6_ROLE_NONE);
+
+ if (surface->role == WLR_XDG_SURFACE_V6_ROLE_POPUP) {
+ wlr_log(WLR_DEBUG, "new xdg popup");
+ return;
+ }
+
+ struct roots_desktop *desktop =
+ wl_container_of(listener, desktop, xdg_shell_v6_surface);
+
+ wlr_log(WLR_DEBUG, "new xdg toplevel: title=%s, app_id=%s",
+ surface->toplevel->title, surface->toplevel->app_id);
+ wlr_xdg_surface_v6_ping(surface);
+
+ struct roots_xdg_surface_v6 *roots_surface =
+ calloc(1, sizeof(struct roots_xdg_surface_v6));
+ if (!roots_surface) {
+ return;
+ }
+ roots_surface->surface_commit.notify = handle_surface_commit;
+ wl_signal_add(&surface->surface->events.commit,
+ &roots_surface->surface_commit);
+ roots_surface->destroy.notify = handle_destroy;
+ wl_signal_add(&surface->events.destroy, &roots_surface->destroy);
+ roots_surface->map.notify = handle_map;
+ wl_signal_add(&surface->events.map, &roots_surface->map);
+ roots_surface->unmap.notify = handle_unmap;
+ wl_signal_add(&surface->events.unmap, &roots_surface->unmap);
+ roots_surface->request_move.notify = handle_request_move;
+ wl_signal_add(&surface->toplevel->events.request_move,
+ &roots_surface->request_move);
+ roots_surface->request_resize.notify = handle_request_resize;
+ wl_signal_add(&surface->toplevel->events.request_resize,
+ &roots_surface->request_resize);
+ roots_surface->request_maximize.notify = handle_request_maximize;
+ wl_signal_add(&surface->toplevel->events.request_maximize,
+ &roots_surface->request_maximize);
+ roots_surface->request_fullscreen.notify = handle_request_fullscreen;
+ wl_signal_add(&surface->toplevel->events.request_fullscreen,
+ &roots_surface->request_fullscreen);
+ roots_surface->set_title.notify = handle_set_title;
+ wl_signal_add(&surface->toplevel->events.set_title, &roots_surface->set_title);
+ roots_surface->set_app_id.notify = handle_set_app_id;
+ wl_signal_add(&surface->toplevel->events.set_app_id,
+ &roots_surface->set_app_id);
+ roots_surface->new_popup.notify = handle_new_popup;
+ wl_signal_add(&surface->events.new_popup, &roots_surface->new_popup);
+
+ struct roots_view *view = view_create(desktop);
+ if (!view) {
+ free(roots_surface);
+ return;
+ }
+ view->type = ROOTS_XDG_SHELL_V6_VIEW;
+
+ view->xdg_surface_v6 = surface;
+ view->roots_xdg_surface_v6 = roots_surface;
+ view->activate = activate;
+ view->resize = resize;
+ view->move_resize = move_resize;
+ view->maximize = maximize;
+ view->set_fullscreen = set_fullscreen;
+ view->close = close;
+ view->destroy = destroy;
+ roots_surface->view = view;
+
+ if (surface->toplevel->client_pending.maximized) {
+ view_maximize(view, true);
+ }
+ if (surface->toplevel->client_pending.fullscreen) {
+ view_set_fullscreen(view, true, NULL);
+ }
+}
diff --git a/rootston/xwayland.c b/rootston/xwayland.c
new file mode 100644
index 00000000..f3f962e8
--- /dev/null
+++ b/rootston/xwayland.c
@@ -0,0 +1,351 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/util/log.h>
+#include <wlr/xwayland.h>
+#include "rootston/server.h"
+#include "rootston/server.h"
+
+static void activate(struct roots_view *view, bool active) {
+ assert(view->type == ROOTS_XWAYLAND_VIEW);
+ wlr_xwayland_surface_activate(view->xwayland_surface, active);
+}
+
+static void move(struct roots_view *view, double x, double y) {
+ assert(view->type == ROOTS_XWAYLAND_VIEW);
+ struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface;
+ view_update_position(view, x, y);
+ wlr_xwayland_surface_configure(xwayland_surface, x, y,
+ xwayland_surface->width, xwayland_surface->height);
+}
+
+static void apply_size_constraints(
+ struct wlr_xwayland_surface *xwayland_surface, uint32_t width,
+ uint32_t height, uint32_t *dest_width, uint32_t *dest_height) {
+ *dest_width = width;
+ *dest_height = height;
+
+ struct wlr_xwayland_surface_size_hints *size_hints =
+ xwayland_surface->size_hints;
+ if (size_hints != NULL) {
+ if (width < (uint32_t)size_hints->min_width) {
+ *dest_width = size_hints->min_width;
+ } else if (size_hints->max_width > 0 &&
+ width > (uint32_t)size_hints->max_width) {
+ *dest_width = size_hints->max_width;
+ }
+ if (height < (uint32_t)size_hints->min_height) {
+ *dest_height = size_hints->min_height;
+ } else if (size_hints->max_height > 0 &&
+ height > (uint32_t)size_hints->max_height) {
+ *dest_height = size_hints->max_height;
+ }
+ }
+}
+
+static void resize(struct roots_view *view, uint32_t width, uint32_t height) {
+ assert(view->type == ROOTS_XWAYLAND_VIEW);
+ struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface;
+
+ uint32_t constrained_width, constrained_height;
+ apply_size_constraints(xwayland_surface, width, height, &constrained_width,
+ &constrained_height);
+
+ wlr_xwayland_surface_configure(xwayland_surface, xwayland_surface->x,
+ xwayland_surface->y, constrained_width, constrained_height);
+}
+
+static void move_resize(struct roots_view *view, double x, double y,
+ uint32_t width, uint32_t height) {
+ assert(view->type == ROOTS_XWAYLAND_VIEW);
+ struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface;
+
+ bool update_x = x != view->box.x;
+ bool update_y = y != view->box.y;
+
+ uint32_t constrained_width, constrained_height;
+ apply_size_constraints(xwayland_surface, width, height, &constrained_width,
+ &constrained_height);
+
+ if (update_x) {
+ x = x + width - constrained_width;
+ }
+ if (update_y) {
+ y = y + height - constrained_height;
+ }
+
+ view->pending_move_resize.update_x = update_x;
+ view->pending_move_resize.update_y = update_y;
+ view->pending_move_resize.x = x;
+ view->pending_move_resize.y = y;
+ view->pending_move_resize.width = constrained_width;
+ view->pending_move_resize.height = constrained_height;
+
+ wlr_xwayland_surface_configure(xwayland_surface, x, y, constrained_width,
+ constrained_height);
+}
+
+static void close(struct roots_view *view) {
+ assert(view->type == ROOTS_XWAYLAND_VIEW);
+ wlr_xwayland_surface_close(view->xwayland_surface);
+}
+
+static void maximize(struct roots_view *view, bool maximized) {
+ assert(view->type == ROOTS_XWAYLAND_VIEW);
+
+ wlr_xwayland_surface_set_maximized(view->xwayland_surface, maximized);
+}
+
+static void set_fullscreen(struct roots_view *view, bool fullscreen) {
+ assert(view->type == ROOTS_XWAYLAND_VIEW);
+
+ wlr_xwayland_surface_set_fullscreen(view->xwayland_surface, fullscreen);
+}
+
+static void destroy(struct roots_view *view) {
+ assert(view->type == ROOTS_XWAYLAND_VIEW);
+ struct roots_xwayland_surface *roots_surface = view->roots_xwayland_surface;
+ wl_list_remove(&roots_surface->destroy.link);
+ wl_list_remove(&roots_surface->request_configure.link);
+ wl_list_remove(&roots_surface->request_move.link);
+ wl_list_remove(&roots_surface->request_resize.link);
+ wl_list_remove(&roots_surface->request_maximize.link);
+ wl_list_remove(&roots_surface->set_title.link);
+ wl_list_remove(&roots_surface->set_class.link);
+ wl_list_remove(&roots_surface->map.link);
+ wl_list_remove(&roots_surface->unmap.link);
+ free(roots_surface);
+}
+
+static void handle_destroy(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, destroy);
+ view_destroy(roots_surface->view);
+}
+
+static void handle_request_configure(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_configure);
+ struct wlr_xwayland_surface *xwayland_surface =
+ roots_surface->view->xwayland_surface;
+ struct wlr_xwayland_surface_configure_event *event = data;
+
+ view_update_position(roots_surface->view, event->x, event->y);
+
+ wlr_xwayland_surface_configure(xwayland_surface, event->x, event->y,
+ event->width, event->height);
+}
+
+static struct roots_seat *guess_seat_for_view(struct roots_view *view) {
+ // the best we can do is to pick the first seat that has the surface focused
+ // for the pointer
+ struct roots_input *input = view->desktop->server->input;
+ struct roots_seat *seat;
+ wl_list_for_each(seat, &input->seats, link) {
+ if (seat->seat->pointer_state.focused_surface == view->wlr_surface) {
+ return seat;
+ }
+ }
+ return NULL;
+}
+
+static void handle_request_move(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_move);
+ struct roots_view *view = roots_surface->view;
+ struct roots_seat *seat = guess_seat_for_view(view);
+
+ if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ return;
+ }
+
+ roots_seat_begin_move(seat, view);
+}
+
+static void handle_request_resize(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_resize);
+ struct roots_view *view = roots_surface->view;
+ struct roots_seat *seat = guess_seat_for_view(view);
+ struct wlr_xwayland_resize_event *e = data;
+
+ if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) {
+ return;
+ }
+ roots_seat_begin_resize(seat, view, e->edges);
+}
+
+static void handle_request_maximize(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_maximize);
+ struct roots_view *view = roots_surface->view;
+ struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface;
+
+ bool maximized = xwayland_surface->maximized_vert &&
+ xwayland_surface->maximized_horz;
+ view_maximize(view, maximized);
+}
+
+static void handle_request_fullscreen(struct wl_listener *listener,
+ void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, request_fullscreen);
+ struct roots_view *view = roots_surface->view;
+ struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface;
+
+ view_set_fullscreen(view, xwayland_surface->fullscreen, NULL);
+}
+
+static void handle_set_title(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, set_title);
+
+ view_set_title(roots_surface->view,
+ roots_surface->view->xwayland_surface->title);
+}
+
+static void handle_set_class(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, set_class);
+
+ view_set_app_id(roots_surface->view,
+ roots_surface->view->xwayland_surface->class);
+}
+
+static void handle_surface_commit(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, surface_commit);
+ struct roots_view *view = roots_surface->view;
+ struct wlr_surface *wlr_surface = view->wlr_surface;
+
+ view_apply_damage(view);
+
+ int width = wlr_surface->current.width;
+ int height = wlr_surface->current.height;
+ view_update_size(view, width, height);
+
+ double x = view->box.x;
+ double y = view->box.y;
+ if (view->pending_move_resize.update_x) {
+ x = view->pending_move_resize.x + view->pending_move_resize.width -
+ width;
+ view->pending_move_resize.update_x = false;
+ }
+ if (view->pending_move_resize.update_y) {
+ y = view->pending_move_resize.y + view->pending_move_resize.height -
+ height;
+ view->pending_move_resize.update_y = false;
+ }
+ view_update_position(view, x, y);
+}
+
+static void handle_map(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, map);
+ struct wlr_xwayland_surface *surface = data;
+ struct roots_view *view = roots_surface->view;
+
+ view->box.x = surface->x;
+ view->box.y = surface->y;
+ view->box.width = surface->surface->current.width;
+ view->box.height = surface->surface->current.height;
+
+ roots_surface->surface_commit.notify = handle_surface_commit;
+ wl_signal_add(&surface->surface->events.commit,
+ &roots_surface->surface_commit);
+
+ view_map(view, surface->surface);
+
+ if (!surface->override_redirect) {
+ if (surface->decorations == WLR_XWAYLAND_SURFACE_DECORATIONS_ALL) {
+ view->decorated = true;
+ view->border_width = 4;
+ view->titlebar_height = 12;
+ }
+
+ view_setup(view);
+
+ wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle,
+ view->xwayland_surface->title ?: "none");
+ wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle,
+ view->xwayland_surface->class ?: "none");
+ } else {
+ view_initial_focus(view);
+ }
+}
+
+static void handle_unmap(struct wl_listener *listener, void *data) {
+ struct roots_xwayland_surface *roots_surface =
+ wl_container_of(listener, roots_surface, unmap);
+ struct roots_view *view = roots_surface->view;
+
+ wl_list_remove(&roots_surface->surface_commit.link);
+ view_unmap(view);
+}
+
+void handle_xwayland_surface(struct wl_listener *listener, void *data) {
+ struct roots_desktop *desktop =
+ wl_container_of(listener, desktop, xwayland_surface);
+
+ struct wlr_xwayland_surface *surface = data;
+ wlr_log(WLR_DEBUG, "new xwayland surface: title=%s, class=%s, instance=%s",
+ surface->title, surface->class, surface->instance);
+ wlr_xwayland_surface_ping(surface);
+
+ struct roots_xwayland_surface *roots_surface =
+ calloc(1, sizeof(struct roots_xwayland_surface));
+ if (roots_surface == NULL) {
+ return;
+ }
+
+ roots_surface->destroy.notify = handle_destroy;
+ wl_signal_add(&surface->events.destroy, &roots_surface->destroy);
+ roots_surface->request_configure.notify = handle_request_configure;
+ wl_signal_add(&surface->events.request_configure,
+ &roots_surface->request_configure);
+ roots_surface->map.notify = handle_map;
+ wl_signal_add(&surface->events.map, &roots_surface->map);
+ roots_surface->unmap.notify = handle_unmap;
+ wl_signal_add(&surface->events.unmap, &roots_surface->unmap);
+ roots_surface->request_move.notify = handle_request_move;
+ wl_signal_add(&surface->events.request_move, &roots_surface->request_move);
+ roots_surface->request_resize.notify = handle_request_resize;
+ wl_signal_add(&surface->events.request_resize,
+ &roots_surface->request_resize);
+ roots_surface->request_maximize.notify = handle_request_maximize;
+ wl_signal_add(&surface->events.request_maximize,
+ &roots_surface->request_maximize);
+ roots_surface->request_fullscreen.notify = handle_request_fullscreen;
+ wl_signal_add(&surface->events.request_fullscreen,
+ &roots_surface->request_fullscreen);
+ roots_surface->set_title.notify = handle_set_title;
+ wl_signal_add(&surface->events.set_title, &roots_surface->set_title);
+ roots_surface->set_class.notify = handle_set_class;
+ wl_signal_add(&surface->events.set_class,
+ &roots_surface->set_class);
+
+ struct roots_view *view = view_create(desktop);
+ if (view == NULL) {
+ free(roots_surface);
+ return;
+ }
+ view->type = ROOTS_XWAYLAND_VIEW;
+ view->box.x = surface->x;
+ view->box.y = surface->y;
+
+ view->xwayland_surface = surface;
+ view->roots_xwayland_surface = roots_surface;
+ view->activate = activate;
+ view->resize = resize;
+ view->move = move;
+ view->move_resize = move_resize;
+ view->maximize = maximize;
+ view->set_fullscreen = set_fullscreen;
+ view->close = close;
+ view->destroy = destroy;
+ roots_surface->view = view;
+}
diff --git a/types/data_device/wlr_data_device.c b/types/data_device/wlr_data_device.c
new file mode 100644
index 00000000..f868ea37
--- /dev/null
+++ b/types/data_device/wlr_data_device.c
@@ -0,0 +1,284 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/util/log.h>
+#include "types/wlr_data_device.h"
+#include "util/signal.h"
+
+#define DATA_DEVICE_MANAGER_VERSION 3
+
+static const struct wl_data_device_interface data_device_impl;
+
+static 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);
+
+ struct wlr_client_data_source *source = NULL;
+ if (source_resource != NULL) {
+ source = client_data_source_from_resource(source_resource);
+ }
+
+ struct wlr_data_source *wlr_source =
+ source != NULL ? &source->source : NULL;
+ wlr_seat_set_selection(seat_client->seat, wlr_source, serial);
+
+ if (source != NULL) {
+ source->finalized = true;
+ }
+}
+
+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);
+ 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, NULL,
+ icon_resource, WL_DATA_DEVICE_ERROR_ROLE)) {
+ return;
+ }
+ }
+
+ struct wlr_data_source *wlr_source =
+ source != NULL ? &source->source : NULL;
+ if (!seat_client_start_drag(seat_client, wlr_source, icon,
+ origin, serial)) {
+ wl_resource_post_no_memory(device_resource);
+ return;
+ }
+
+ if (source != NULL) {
+ source->finalized = true;
+ }
+}
+
+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));
+}
+
+
+void wlr_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;
+ }
+
+ struct wl_resource *device_resource;
+ wl_resource_for_each(device_resource, &seat_client->data_devices) {
+ if (source != NULL) {
+ struct wlr_data_offer *offer =
+ data_source_send_offer(source, device_resource);
+ 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);
+ }
+ }
+}
+
+static void seat_client_selection_source_destroy(
+ struct wl_listener *listener, void *data) {
+ struct wlr_seat *seat =
+ wl_container_of(listener, seat, selection_source_destroy);
+ struct wlr_seat_client *seat_client = seat->keyboard_state.focused_client;
+
+ if (seat_client && seat->keyboard_state.focused_surface) {
+ struct wl_resource *resource;
+ wl_resource_for_each(resource, &seat_client->data_devices) {
+ wl_data_device_send_selection(resource, NULL);
+ }
+ }
+
+ seat->selection_source = NULL;
+
+ wlr_signal_emit_safe(&seat->events.selection, seat);
+}
+
+void wlr_seat_set_selection(struct wlr_seat *seat,
+ struct wlr_data_source *source, uint32_t serial) {
+ if (seat->selection_source &&
+ seat->selection_serial - serial < UINT32_MAX / 2) {
+ return;
+ }
+
+ if (seat->selection_source) {
+ wl_list_remove(&seat->selection_source_destroy.link);
+ wlr_data_source_cancel(seat->selection_source);
+ seat->selection_source = NULL;
+ }
+
+ seat->selection_source = source;
+ seat->selection_serial = serial;
+
+ struct wlr_seat_client *focused_client =
+ seat->keyboard_state.focused_client;
+
+ if (focused_client) {
+ wlr_seat_client_send_selection(focused_client);
+ }
+
+ wlr_signal_emit_safe(&seat->events.selection, seat);
+
+ if (source) {
+ seat->selection_source_destroy.notify =
+ seat_client_selection_source_destroy;
+ wl_signal_add(&source->events.destroy,
+ &seat->selection_source_destroy);
+ }
+}
+
+
+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);
+
+ struct wl_resource *resource = wl_resource_create(client,
+ &wl_data_device_interface, wl_resource_get_version(manager_resource),
+ 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);
+ wl_list_insert(&seat_client->data_devices, wl_resource_get_link(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_handle_resource_destroy(
+ struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+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, data_device_manager_handle_resource_destroy);
+
+ wl_list_insert(&manager->resources, wl_resource_get_link(resource));
+}
+
+void wlr_data_device_manager_destroy(struct wlr_data_device_manager *manager) {
+ if (!manager) {
+ return;
+ }
+ wlr_signal_emit_safe(&manager->events.destroy, manager);
+ wl_list_remove(&manager->display_destroy.link);
+ wl_global_destroy(manager->global);
+ struct wl_resource *resource, *tmp;
+ wl_resource_for_each_safe(resource, tmp, &manager->resources) {
+ wl_resource_destroy(resource);
+ }
+ wl_resource_for_each_safe(resource, tmp, &manager->data_sources) {
+ wl_resource_destroy(resource);
+ }
+ free(manager);
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_data_device_manager *manager =
+ wl_container_of(listener, manager, display_destroy);
+ wlr_data_device_manager_destroy(manager);
+}
+
+struct wlr_data_device_manager *wlr_data_device_manager_create(
+ struct wl_display *display) {
+ struct wlr_data_device_manager *manager =
+ calloc(1, sizeof(struct wlr_data_device_manager));
+ if (manager == NULL) {
+ wlr_log(WLR_ERROR, "could not create data device manager");
+ return NULL;
+ }
+
+ wl_list_init(&manager->resources);
+ 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 00000000..b8cec091
--- /dev/null
+++ b/types/data_device/wlr_data_offer.c
@@ -0,0 +1,222 @@
+#define _XOPEN_SOURCE 700
+#include <assert.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/util/log.h>
+#include "types/wlr_data_device.h"
+#include "util/signal.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) {
+ 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;
+ }
+
+ 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_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) {
+ struct wlr_data_offer *offer = data_offer_from_resource(resource);
+ if (offer == NULL) {
+ goto out;
+ }
+
+ // 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_dnd_finish(offer);
+ }
+
+out:
+ 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;
+ }
+
+ data_offer_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;
+ }
+
+ 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);
+
+ // 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);
+ data_offer_destroy(offer);
+}
+
+struct wlr_data_offer *data_offer_create(struct wl_client *client,
+ struct wlr_data_source *source, uint32_t version) {
+ struct wlr_data_offer *offer = calloc(1, sizeof(struct wlr_data_offer));
+ if (offer == NULL) {
+ return NULL;
+ }
+ offer->source = source;
+
+ 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);
+
+ offer->source_destroy.notify = data_offer_handle_source_destroy;
+ wl_signal_add(&source->events.destroy, &offer->source_destroy);
+
+ 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 00000000..413f461a
--- /dev/null
+++ b/types/data_device/wlr_data_source.c
@@ -0,0 +1,265 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/util/log.h>
+#include "types/wlr_data_device.h"
+#include "util/signal.h"
+
+struct wlr_data_offer *data_source_send_offer(struct wlr_data_source *source,
+ struct wl_resource *device_resource) {
+ struct wl_client *client = wl_resource_get_client(device_resource);
+ uint32_t version = wl_resource_get_version(device_resource);
+ struct wlr_data_offer *offer = data_offer_create(client, source, version);
+ if (offer == NULL) {
+ return NULL;
+ }
+
+ 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;
+}
+
+void wlr_data_source_init(struct wlr_data_source *source,
+ const struct wlr_data_source_impl *impl) {
+ assert(impl->send);
+
+ source->impl = impl;
+ wl_array_init(&source->mime_types);
+ wl_signal_init(&source->events.destroy);
+ source->actions = -1;
+}
+
+void wlr_data_source_finish(struct wlr_data_source *source) {
+ if (source == NULL) {
+ return;
+ }
+
+ wlr_signal_emit_safe(&source->events.destroy, source);
+
+ char **p;
+ wl_array_for_each(p, &source->mime_types) {
+ free(*p);
+ }
+ wl_array_release(&source->mime_types);
+}
+
+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_cancel(struct wlr_data_source *source) {
+ if (source->impl->cancel) {
+ source->impl->cancel(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);
+ return (struct wlr_client_data_source *)wlr_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_cancel(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);
+}
+
+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->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);
+
+ char **p = wl_array_add(&source->source.mime_types, sizeof(*p));
+ if (p) {
+ *p = strdup(mime_type);
+ }
+ if (!p || !*p) {
+ if (p) {
+ source->source.mime_types.size -= sizeof(*p);
+ }
+ wl_resource_post_no_memory(resource);
+ }
+}
+
+static const struct wl_data_source_interface data_source_impl = {
+ .offer = data_source_offer,
+ .destroy = data_source_destroy,
+ .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);
+ wlr_data_source_finish(&source->source);
+ wl_list_remove(wl_resource_get_link(source->resource));
+ free(source);
+}
+
+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(struct wlr_client_data_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.cancel = client_data_source_cancel;
+
+ 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 00000000..8e737597
--- /dev/null
+++ b/types/data_device/wlr_drag.c
@@ -0,0 +1,490 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/util/log.h>
+#include "types/wlr_data_device.h"
+#include "util/signal.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);
+
+ 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 || !surface->resource) {
+ return;
+ }
+
+ if (!drag->source &&
+ wl_resource_get_client(surface->resource) !=
+ drag->seat_client->client) {
+ return;
+ }
+
+ struct wlr_seat_client *focus_client = wlr_seat_client_for_wl_client(
+ drag->seat_client->seat, wl_resource_get_client(surface->resource));
+ if (!focus_client) {
+ return;
+ }
+
+ if (drag->source != NULL) {
+ drag->source->accepted = false;
+
+ uint32_t serial =
+ wl_display_next_serial(drag->seat_client->seat->display);
+
+ struct wl_resource *device_resource;
+ wl_resource_for_each(device_resource, &focus_client->data_devices) {
+ struct wlr_data_offer *offer =
+ data_source_send_offer(drag->source, device_resource);
+ 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);
+
+ wlr_signal_emit_safe(&drag->events.focus, drag);
+}
+
+static void drag_icon_set_mapped(struct wlr_drag_icon *icon, bool mapped) {
+ if (mapped && !icon->mapped) {
+ icon->mapped = true;
+ wlr_signal_emit_safe(&icon->events.map, icon);
+ } else if (!mapped && icon->mapped) {
+ icon->mapped = false;
+ wlr_signal_emit_safe(&icon->events.unmap, icon);
+ }
+}
+
+static void drag_end(struct wlr_drag *drag) {
+ if (!drag->cancelling) {
+ drag->cancelling = true;
+ if (drag->is_pointer_grab) {
+ wlr_seat_pointer_end_grab(drag->seat);
+ } else {
+ wlr_seat_touch_end_grab(drag->seat);
+ }
+ wlr_seat_keyboard_end_grab(drag->seat);
+
+ if (drag->source) {
+ wl_list_remove(&drag->source_destroy.link);
+ }
+
+ drag_set_focus(drag, NULL, 0, 0);
+
+ if (drag->icon) {
+ wl_list_remove(&drag->icon_destroy.link);
+ drag_icon_set_mapped(drag->icon, false);
+ }
+
+ wlr_signal_emit_safe(&drag->events.destroy, drag);
+ 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_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,
+ };
+ wlr_signal_emit_safe(&drag->events.motion, &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) {
+ struct wl_resource *resource;
+ wl_resource_for_each(resource, &drag->focus_client->data_devices) {
+ wl_data_device_send_drop(resource);
+ }
+ wlr_data_source_dnd_drop(drag->source);
+
+ struct wlr_drag_drop_event event = {
+ .drag = drag,
+ .time = time,
+ };
+ wlr_signal_emit_safe(&drag->events.drop, &event);
+ } else if (drag->source->impl->dnd_finish) {
+ wlr_data_source_cancel(drag->source);
+ }
+ }
+
+ if (grab->seat->pointer_state.button_count == 0 &&
+ state == WL_POINTER_BUTTON_STATE_RELEASED) {
+ drag_end(drag);
+ }
+
+ return 0;
+}
+
+static void drag_handle_pointer_axis(struct wlr_seat_pointer_grab *grab,
+ uint32_t time, enum wlr_axis_orientation orientation, double value,
+ int32_t value_discrete, enum wlr_axis_source source) {
+ // 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_end(drag);
+}
+
+static const struct wlr_pointer_grab_interface
+ data_device_pointer_drag_interface = {
+ .enter = drag_handle_pointer_enter,
+ .motion = drag_handle_pointer_motion,
+ .button = drag_handle_pointer_button,
+ .axis = drag_handle_pointer_axis,
+ .cancel = drag_handle_pointer_cancel,
+};
+
+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) {
+ struct wl_resource *resource;
+ wl_resource_for_each(resource, &drag->focus_client->data_devices) {
+ wl_data_device_send_drop(resource);
+ }
+ }
+
+ drag_end(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_end(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, uint32_t keycodes[], size_t num_keycodes,
+ struct wlr_keyboard_modifiers *modifiers) {
+ // 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,
+ 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_end(drag);
+}
+
+static const struct wlr_keyboard_grab_interface
+ data_device_keyboard_drag_interface = {
+ .enter = drag_handle_keyboard_enter,
+ .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_end(drag);
+}
+
+
+static void drag_icon_destroy(struct wlr_drag_icon *icon) {
+ if (icon == NULL) {
+ return;
+ }
+ drag_icon_set_mapped(icon, false);
+ wlr_signal_emit_safe(&icon->events.destroy, icon);
+ icon->surface->role_data = NULL;
+ wl_list_remove(&icon->surface_destroy.link);
+ wl_list_remove(&icon->seat_client_destroy.link);
+ wl_list_remove(&icon->link);
+ free(icon);
+}
+
+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 void drag_icon_surface_role_commit(struct wlr_surface *surface) {
+ assert(surface->role == &drag_icon_surface_role);
+ struct wlr_drag_icon *icon = surface->role_data;
+ if (icon == NULL) {
+ return;
+ }
+
+ drag_icon_set_mapped(icon, wlr_surface_has_buffer(surface));
+}
+
+const struct wlr_surface_role drag_icon_surface_role = {
+ .name = "wl_data_device-icon",
+ .commit = drag_icon_surface_role_commit,
+};
+
+static void drag_icon_handle_seat_client_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_drag_icon *icon =
+ wl_container_of(listener, icon, seat_client_destroy);
+
+ drag_icon_destroy(icon);
+}
+
+static struct wlr_drag_icon *drag_icon_create(
+ struct wlr_surface *icon_surface, struct wlr_seat_client *client,
+ bool is_pointer, int32_t touch_id) {
+ struct wlr_drag_icon *icon = calloc(1, sizeof(struct wlr_drag_icon));
+ if (!icon) {
+ return NULL;
+ }
+
+ icon->surface = icon_surface;
+ icon->client = client;
+ icon->is_pointer = is_pointer;
+ icon->touch_id = touch_id;
+
+ wl_signal_init(&icon->events.map);
+ wl_signal_init(&icon->events.unmap);
+ wl_signal_init(&icon->events.destroy);
+
+ wl_signal_add(&icon->surface->events.destroy, &icon->surface_destroy);
+ icon->surface_destroy.notify = drag_icon_handle_surface_destroy;
+
+ wl_signal_add(&client->events.destroy, &icon->seat_client_destroy);
+ icon->seat_client_destroy.notify = drag_icon_handle_seat_client_destroy;
+
+ icon->surface->role_data = icon;
+
+ wl_list_insert(&client->seat->drag_icons, &icon->link);
+ wlr_signal_emit_safe(&client->seat->events.new_drag_icon, icon);
+
+ if (wlr_surface_has_buffer(icon_surface)) {
+ drag_icon_set_mapped(icon, true);
+ }
+
+ return icon;
+}
+
+
+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;
+}
+
+bool seat_client_start_drag(struct wlr_seat_client *client,
+ struct wlr_data_source *source, struct wlr_surface *icon_surface,
+ struct wlr_surface *origin, uint32_t serial) {
+ struct wlr_drag *drag = calloc(1, sizeof(struct wlr_drag));
+ if (drag == NULL) {
+ return false;
+ }
+
+ wl_signal_init(&drag->events.focus);
+ wl_signal_init(&drag->events.motion);
+ wl_signal_init(&drag->events.drop);
+ wl_signal_init(&drag->events.destroy);
+
+ struct wlr_seat *seat = client->seat;
+ drag->seat = seat;
+
+ drag->is_pointer_grab = !wl_list_empty(&client->pointers) &&
+ seat->pointer_state.button_count == 1 &&
+ seat->pointer_state.grab_serial == serial &&
+ seat->pointer_state.focused_surface &&
+ seat->pointer_state.focused_surface == origin;
+
+ bool is_touch_grab = !wl_list_empty(&client->touches) &&
+ wlr_seat_touch_num_points(seat) == 1 &&
+ seat->touch_state.grab_serial == serial;
+
+ // set in the iteration
+ struct wlr_touch_point *point = NULL;
+ if (is_touch_grab) {
+ wl_list_for_each(point, &seat->touch_state.touch_points, link) {
+ is_touch_grab = point->surface && point->surface == origin;
+ break;
+ }
+ }
+
+ if (!drag->is_pointer_grab && !is_touch_grab) {
+ free(drag);
+ return true;
+ }
+
+ if (icon_surface) {
+ int32_t touch_id = (point ? point->touch_id : 0);
+ struct wlr_drag_icon *icon =
+ drag_icon_create(icon_surface, client, drag->is_pointer_grab,
+ touch_id);
+ if (!icon) {
+ free(drag);
+ return false;
+ }
+
+ 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->seat_client = client;
+ 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->grab_touch_id = seat->touch_state.grab_id;
+
+ drag->keyboard_grab.data = drag;
+ drag->keyboard_grab.interface = &data_device_keyboard_drag_interface;
+
+ wlr_seat_keyboard_start_grab(seat, &drag->keyboard_grab);
+
+ if (drag->is_pointer_grab) {
+ wlr_seat_pointer_clear_focus(seat);
+ wlr_seat_pointer_start_grab(seat, &drag->pointer_grab);
+ } else {
+ assert(point);
+ wlr_seat_touch_start_grab(seat, &drag->touch_grab);
+ drag_set_focus(drag, point->surface, point->sx, point->sy);
+ }
+
+ seat->drag = drag; // TODO: unset this thing somewhere
+ seat->drag_serial = serial;
+
+ seat->drag_source = source;
+ if (source != NULL) {
+ seat->drag_source_destroy.notify = seat_handle_drag_source_destroy;
+ wl_signal_add(&source->events.destroy, &seat->drag_source_destroy);
+ }
+
+ wlr_signal_emit_safe(&seat->events.start_drag, drag);
+
+ return true;
+}
diff --git a/types/meson.build b/types/meson.build
new file mode 100644
index 00000000..1813b144
--- /dev/null
+++ b/types/meson.build
@@ -0,0 +1,70 @@
+lib_wlr_types = static_library(
+ 'wlr_types',
+ files(
+ 'data_device/wlr_data_device.c',
+ 'data_device/wlr_data_offer.c',
+ 'data_device/wlr_data_source.c',
+ 'data_device/wlr_drag.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_v6/wlr_xdg_popup_v6.c',
+ 'xdg_shell_v6/wlr_xdg_positioner_v6.c',
+ 'xdg_shell_v6/wlr_xdg_shell_v6.c',
+ 'xdg_shell_v6/wlr_xdg_surface_v6.c',
+ 'xdg_shell_v6/wlr_xdg_toplevel_v6.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',
+ 'wlr_box.c',
+ 'wlr_buffer.c',
+ 'wlr_compositor.c',
+ 'wlr_cursor.c',
+ 'wlr_export_dmabuf_v1.c',
+ 'wlr_foreign_toplevel_management_v1.c',
+ 'wlr_gamma_control_v1.c',
+ 'wlr_gamma_control.c',
+ 'wlr_gtk_primary_selection.c',
+ 'wlr_idle_inhibit_v1.c',
+ 'wlr_idle.c',
+ 'wlr_input_device.c',
+ 'wlr_input_inhibitor.c',
+ 'wlr_input_method_v2.c',
+ 'wlr_keyboard.c',
+ 'wlr_layer_shell_v1.c',
+ 'wlr_linux_dmabuf_v1.c',
+ 'wlr_list.c',
+ 'wlr_matrix.c',
+ 'wlr_output_damage.c',
+ 'wlr_output_layout.c',
+ 'wlr_output.c',
+ 'wlr_pointer_constraints_v1.c',
+ 'wlr_pointer.c',
+ 'wlr_presentation_time.c',
+ 'wlr_primary_selection.c',
+ 'wlr_region.c',
+ 'wlr_screencopy_v1.c',
+ 'wlr_screenshooter.c',
+ 'wlr_server_decoration.c',
+ 'wlr_surface.c',
+ 'wlr_switch.c',
+ 'wlr_tablet_pad.c',
+ 'wlr_tablet_tool.c',
+ 'wlr_text_input_v3.c',
+ 'wlr_touch.c',
+ 'wlr_virtual_keyboard_v1.c',
+ 'wlr_wl_shell.c',
+ 'wlr_xcursor_manager.c',
+ 'wlr_xdg_decoration_v1.c',
+ 'wlr_xdg_output_v1.c',
+ ),
+ include_directories: wlr_inc,
+ dependencies: [pixman, xkbcommon, wayland_server, wlr_protos, libinput],
+)
diff --git a/types/seat/wlr_seat.c b/types/seat/wlr_seat.c
new file mode 100644
index 00000000..8b1d67fd
--- /dev/null
+++ b/types/seat/wlr_seat.c
@@ -0,0 +1,364 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_primary_selection.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/util/log.h>
+#include "types/wlr_seat.h"
+#include "util/signal.h"
+
+#define SEAT_VERSION 6
+
+static void seat_handle_get_pointer(struct wl_client *client,
+ struct wl_resource *seat_resource, uint32_t id) {
+ struct wlr_seat_client *seat_client =
+ wlr_seat_client_from_resource(seat_resource);
+ if (!(seat_client->seat->capabilities & WL_SEAT_CAPABILITY_POINTER)) {
+ wlr_log(WLR_ERROR, "Client sent get_pointer on seat without the "
+ "pointer capability");
+ return;
+ }
+
+ uint32_t version = wl_resource_get_version(seat_resource);
+ 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) {
+ struct wlr_seat_client *seat_client =
+ wlr_seat_client_from_resource(seat_resource);
+ if (!(seat_client->seat->capabilities & WL_SEAT_CAPABILITY_KEYBOARD)) {
+ wlr_log(WLR_ERROR, "Client sent get_keyboard on seat without the "
+ "keyboard capability");
+ return;
+ }
+
+ uint32_t version = wl_resource_get_version(seat_resource);
+ 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) {
+ struct wlr_seat_client *seat_client =
+ wlr_seat_client_from_resource(seat_resource);
+ if (!(seat_client->seat->capabilities & WL_SEAT_CAPABILITY_TOUCH)) {
+ wlr_log(WLR_ERROR, "Client sent get_touch on seat without the "
+ "touch capability");
+ return;
+ }
+
+ uint32_t version = wl_resource_get_version(seat_resource);
+ seat_client_create_touch(seat_client, version, id);
+}
+
+static void seat_client_handle_resource_destroy(
+ struct wl_resource *seat_resource) {
+ struct wlr_seat_client *client =
+ wlr_seat_client_from_resource(seat_resource);
+
+ wl_list_remove(wl_resource_get_link(seat_resource));
+ if (!wl_list_empty(&client->resources)) {
+ return;
+ }
+
+ wlr_signal_emit_safe(&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;
+ }
+
+ struct wl_resource *resource, *tmp;
+ wl_resource_for_each_safe(resource, tmp, &client->pointers) {
+ wl_resource_destroy(resource);
+ }
+ wl_resource_for_each_safe(resource, tmp, &client->keyboards) {
+ wl_resource_destroy(resource);
+ }
+ wl_resource_for_each_safe(resource, tmp, &client->touches) {
+ wl_resource_destroy(resource);
+ }
+ wl_resource_for_each_safe(resource, tmp, &client->data_devices) {
+ wl_resource_destroy(resource);
+ }
+
+ wl_list_remove(&client->link);
+ free(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 void seat_handle_bind(struct wl_client *client, void *_wlr_seat,
+ uint32_t version, uint32_t id) {
+ struct wlr_seat *wlr_seat = _wlr_seat;
+ assert(client && 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;
+ }
+
+ struct wlr_seat_client *seat_client =
+ wlr_seat_client_for_wl_client(wlr_seat, client);
+ if (seat_client == NULL) {
+ seat_client = calloc(1, sizeof(struct wlr_seat_client));
+ if (seat_client == NULL) {
+ wl_resource_destroy(wl_resource);
+ wl_client_post_no_memory(client);
+ return;
+ }
+
+ 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);
+ }
+
+ wl_resource_set_implementation(wl_resource, &seat_impl,
+ seat_client, seat_client_handle_resource_destroy);
+ 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_signal_emit_safe(&seat->events.destroy, seat);
+
+ wl_list_remove(&seat->display_destroy.link);
+
+ if (seat->selection_source) {
+ wl_list_remove(&seat->selection_source_destroy.link);
+ wlr_data_source_cancel(seat->selection_source);
+ seat->selection_source = NULL;
+ }
+
+ wlr_seat_set_primary_selection(seat, NULL);
+
+ struct wlr_seat_client *client, *tmp;
+ wl_list_for_each_safe(client, tmp, &seat->clients, link) {
+ struct wl_resource *resource, *next;
+ /* wl_resource_for_each_safe isn't safe to use here, because the last
+ * wl_resource_destroy will also destroy the head we cannot do the last
+ * 'next' update that usually is harmless here.
+ * Work around this by breaking one step ahead
+ */
+ wl_resource_for_each_safe(resource, next, &client->resources) {
+ // will destroy other resources as well
+ wl_resource_destroy(resource);
+ if (wl_resource_get_link(next) == &client->resources) {
+ break;
+ }
+ }
+ }
+
+ wl_global_destroy(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(struct wlr_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(struct wlr_seat_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(struct wlr_seat_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(struct wlr_seat_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->drag_icons);
+
+ wl_signal_init(&seat->events.start_drag);
+ wl_signal_init(&seat->events.new_drag_icon);
+
+ wl_signal_init(&seat->events.request_set_cursor);
+
+ wl_signal_init(&seat->events.selection);
+ wl_signal_init(&seat->events.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) {
+ wlr_seat->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 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 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);
+}
+
+bool wlr_seat_validate_grab_serial(struct wlr_seat *seat, uint32_t serial) {
+ // TODO
+ //return serial == seat->pointer_state.grab_serial ||
+ // serial == seat->touch_state.grab_serial;
+ return true;
+}
diff --git a/types/seat/wlr_seat_keyboard.c b/types/seat/wlr_seat_keyboard.c
new file mode 100644
index 00000000..96176b6d
--- /dev/null
+++ b/types/seat/wlr_seat_keyboard.c
@@ -0,0 +1,422 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_gtk_primary_selection.h>
+#include <wlr/util/log.h>
+#include "types/wlr_seat.h"
+#include "util/signal.h"
+#include "util/shm.h"
+
+static void default_keyboard_enter(struct wlr_seat_keyboard_grab *grab,
+ struct wlr_surface *surface, uint32_t keycodes[], size_t num_keycodes,
+ struct wlr_keyboard_modifiers *modifiers) {
+ wlr_seat_keyboard_enter(grab->seat, surface, keycodes, num_keycodes, modifiers);
+}
+
+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,
+ 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,
+ .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) {
+ wl_list_remove(wl_resource_get_link(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 = wl_display_next_serial(wlr_seat->display);
+ 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_input_device *device) {
+ // 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.
+ struct wlr_keyboard *keyboard = (device ? device->keyboard : NULL);
+ 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) {
+ assert(device->type == WLR_INPUT_DEVICE_KEYBOARD);
+ seat->keyboard_state.keyboard = keyboard;
+
+ wl_signal_add(&device->events.destroy,
+ &seat->keyboard_state.keyboard_destroy);
+ seat->keyboard_state.keyboard_destroy.notify = handle_keyboard_destroy;
+ wl_signal_add(&device->keyboard->events.keymap,
+ &seat->keyboard_state.keyboard_keymap);
+ seat->keyboard_state.keyboard_keymap.notify = handle_keyboard_keymap;
+ wl_signal_add(&device->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;
+
+ wlr_signal_emit_safe(&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;
+ wlr_signal_emit_safe(&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,
+ struct wlr_keyboard_modifiers *modifiers) {
+ struct wlr_seat_client *client = seat->keyboard_state.focused_client;
+ if (client == NULL) {
+ return;
+ }
+
+ uint32_t serial = wl_display_next_serial(seat->display);
+ 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 wlr_seat_keyboard_enter(struct wlr_seat *seat,
+ struct wlr_surface *surface, uint32_t keycodes[], size_t num_keycodes,
+ 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) {
+ uint32_t serial = wl_display_next_serial(seat->display);
+ struct wl_resource *resource;
+ wl_resource_for_each(resource, &focused_client->keyboards) {
+ if (seat_client_from_keyboard_resource(resource) == NULL) {
+ continue;
+ }
+ wl_keyboard_send_leave(resource, serial, focused_surface->resource);
+ }
+ }
+
+ // enter the current surface
+ if (client != NULL) {
+ 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: %d\n",
+ keycodes[i]);
+ continue;
+ }
+ *p = keycodes[i];
+ }
+ uint32_t serial = wl_display_next_serial(seat->display);
+ 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);
+ }
+ wl_array_release(&keys);
+
+ wlr_seat_client_send_selection(client);
+ }
+
+ // 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);
+ }
+
+ struct wlr_seat_keyboard_focus_change_event event = {
+ .seat = seat,
+ .old_surface = focused_surface,
+ .new_surface = surface,
+ };
+ wlr_signal_emit_safe(&seat->keyboard_state.events.focus_change, &event);
+}
+
+void wlr_seat_keyboard_notify_enter(struct wlr_seat *seat,
+ struct wlr_surface *surface, uint32_t keycodes[], size_t num_keycodes,
+ struct wlr_keyboard_modifiers *modifiers) {
+ 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) {
+ // TODO respect grabs here?
+ wlr_seat_keyboard_enter(seat, NULL, NULL, 0, NULL);
+}
+
+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,
+ 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;
+ }
+
+ // 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;
+ }
+
+ 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);
+ continue;
+ }
+
+ 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);
+ continue;
+ }
+
+ strcpy(ptr, keyboard->keymap_string);
+ munmap(ptr, keyboard->keymap_size);
+
+ wl_keyboard_send_keymap(resource,
+ WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, keymap_fd,
+ keyboard->keymap_size);
+
+ close(keymap_fd);
+ }
+}
+
+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));
+
+ struct wlr_keyboard *keyboard = seat_client->seat->keyboard_state.keyboard;
+ seat_client_send_keymap(seat_client, keyboard);
+ seat_client_send_repeat_info(seat_client, keyboard);
+
+ // TODO possibly handle the case where this keyboard needs an enter
+ // right away
+}
+
+void seat_client_destroy_keyboard(struct wl_resource *resource) {
+ struct wlr_seat_client *seat_client =
+ seat_client_from_keyboard_resource(resource);
+ if (seat_client == NULL) {
+ return;
+ }
+ 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 00000000..594a5b81
--- /dev/null
+++ b/types/seat/wlr_seat_pointer.c
@@ -0,0 +1,364 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/util/log.h>
+#include "types/wlr_seat.h"
+#include "util/signal.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_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, uint32_t 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 wlr_axis_orientation orientation, double value,
+ int32_t value_discrete, enum wlr_axis_source source) {
+ wlr_seat_pointer_send_axis(grab->seat, time, orientation, value,
+ value_discrete, source);
+}
+
+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,
+ .motion = default_pointer_motion,
+ .button = default_pointer_button,
+ .axis = default_pointer_axis,
+ .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 const struct wlr_surface_role pointer_cursor_surface_role = {
+ .name = "wl_pointer-cursor",
+};
+
+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, NULL,
+ surface_resource, WL_POINTER_ERROR_ROLE)) {
+ return;
+ }
+ }
+
+ 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,
+ };
+ wlr_signal_emit_safe(&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) {
+ wl_list_remove(wl_resource_get_link(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 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) {
+ uint32_t serial = wl_display_next_serial(wlr_seat->display);
+ struct wl_resource *resource;
+ wl_resource_for_each(resource, &focused_client->pointers) {
+ if (wlr_seat_client_from_pointer_resource(resource) == NULL) {
+ continue;
+ }
+
+ wl_pointer_send_leave(resource, serial, focused_surface->resource);
+ pointer_send_frame(resource);
+ }
+ }
+
+ // enter the current surface
+ if (client != NULL && surface != NULL) {
+ uint32_t serial = wl_display_next_serial(wlr_seat->display);
+ 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;
+
+ struct wlr_seat_pointer_focus_change_event event = {
+ .seat = wlr_seat,
+ .new_surface = surface,
+ .old_surface = focused_surface,
+ .sx = sx,
+ .sy = sy,
+ };
+ wlr_signal_emit_safe(&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_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;
+ }
+
+ 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, wl_fixed_from_double(sx),
+ wl_fixed_from_double(sy));
+ pointer_send_frame(resource);
+ }
+}
+
+uint32_t wlr_seat_pointer_send_button(struct wlr_seat *wlr_seat, uint32_t time,
+ uint32_t button, uint32_t state) {
+ struct wlr_seat_client *client = wlr_seat->pointer_state.focused_client;
+ if (client == NULL) {
+ return 0;
+ }
+
+ uint32_t serial = wl_display_next_serial(wlr_seat->display);
+ 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);
+ pointer_send_frame(resource);
+ }
+ return serial;
+}
+
+void wlr_seat_pointer_send_axis(struct wlr_seat *wlr_seat, uint32_t time,
+ enum wlr_axis_orientation orientation, double value,
+ int32_t value_discrete, enum wlr_axis_source source) {
+ struct wlr_seat_client *client = wlr_seat->pointer_state.focused_client;
+ if (client == NULL) {
+ return;
+ }
+
+ 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_SOURCE_SINCE_VERSION) {
+ wl_pointer_send_axis_source(resource, source);
+ }
+ if (value) {
+ if (value_discrete &&
+ version >= WL_POINTER_AXIS_DISCRETE_SINCE_VERSION) {
+ wl_pointer_send_axis_discrete(resource, orientation,
+ value_discrete);
+ }
+
+ 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);
+ }
+ 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;
+
+ wlr_signal_emit_safe(&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;
+ wlr_signal_emit_safe(&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) {
+ struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab;
+ grab->interface->enter(grab, surface, sx, sy);
+}
+
+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, uint32_t state) {
+ clock_gettime(CLOCK_MONOTONIC, &wlr_seat->last_event);
+ if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
+ if (wlr_seat->pointer_state.button_count == 0) {
+ wlr_seat->pointer_state.grab_button = button;
+ wlr_seat->pointer_state.grab_time = time;
+ }
+ wlr_seat->pointer_state.button_count++;
+ } else {
+ wlr_seat->pointer_state.button_count--;
+ }
+
+ struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab;
+ uint32_t serial = grab->interface->button(grab, time, button, state);
+
+ if (serial && wlr_seat->pointer_state.button_count == 1) {
+ wlr_seat->pointer_state.grab_serial = serial;
+ }
+
+ return serial;
+}
+
+void wlr_seat_pointer_notify_axis(struct wlr_seat *wlr_seat, uint32_t time,
+ enum wlr_axis_orientation orientation, double value,
+ int32_t value_discrete, enum wlr_axis_source source) {
+ 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);
+}
+
+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));
+}
+
+void seat_client_destroy_pointer(struct wl_resource *resource) {
+ struct wlr_seat_client *seat_client =
+ wlr_seat_client_from_pointer_resource(resource);
+ if (seat_client == NULL) {
+ return;
+ }
+ wl_resource_set_user_data(resource, NULL);
+}
diff --git a/types/seat/wlr_seat_touch.c b/types/seat/wlr_seat_touch.c
new file mode 100644
index 00000000..d4fa6f0b
--- /dev/null
+++ b/types/seat/wlr_seat_touch.c
@@ -0,0 +1,361 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/util/log.h>
+#include "types/wlr_seat.h"
+#include "util/signal.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_cancel(struct wlr_seat_touch_grab *grab) {
+ // cannot be cancelled
+}
+
+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,
+ .cancel = default_touch_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) {
+ wl_list_remove(wl_resource_get_link(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;
+
+ wlr_signal_emit_safe(&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;
+ wlr_signal_emit_safe(&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) {
+ wlr_signal_emit_safe(&point->events.destroy, point);
+
+ touch_point_clear_focus(point);
+ wl_list_remove(&point->surface_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_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(struct wlr_touch_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_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 && 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);
+}
+
+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 = wl_display_next_serial(seat->display);
+ 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));
+ wl_touch_send_frame(resource);
+ }
+
+ 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 = wl_display_next_serial(seat->display);
+ 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);
+ wl_touch_send_frame(resource);
+ }
+}
+
+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));
+ wl_touch_send_frame(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));
+}
+
+void seat_client_destroy_touch(struct wl_resource *resource) {
+ struct wlr_seat_client *seat_client =
+ seat_client_from_touch_resource(resource);
+ if (seat_client == NULL) {
+ return;
+ }
+ wl_resource_set_user_data(resource, NULL);
+}
diff --git a/types/tablet_v2/wlr_tablet_v2.c b/types/tablet_v2/wlr_tablet_v2.c
new file mode 100644
index 00000000..d0bc799d
--- /dev/null
+++ b/types/tablet_v2/wlr_tablet_v2.c
@@ -0,0 +1,322 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+
+#include <assert.h>
+#include <libinput.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-server.h>
+#include <types/wlr_tablet_v2.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/types/wlr_tablet_tool.h>
+#include <wlr/types/wlr_tablet_v2.h>
+#include <wlr/util/log.h>
+#include "tablet-unstable-v2-protocol.h"
+#include "util/signal.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(struct wlr_tablet_seat_v2));
+ 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 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 wlr_seat_client *seat = wlr_seat_client_from_resource(seat_resource);
+ 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(struct wlr_tablet_seat_client_v2));
+ if (seat_client == NULL) {
+ wl_client_post_no_memory(wl_client);
+ return;
+ }
+
+ seat_client->resource =
+ wl_resource_create(wl_client, &zwp_tablet_seat_v2_interface, TABLET_MANAGER_VERSION, id);
+ if (seat_client->resource == NULL) {
+ free(seat_client);
+ wl_client_post_no_memory(wl_client);
+ return;
+ }
+ wl_resource_set_implementation(seat_client->resource, &seat_impl, seat_client,
+ tablet_seat_client_v2_destroy);
+
+
+ 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);
+
+ 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 allready 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 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;
+ assert(wl_client && manager);
+
+ struct wlr_tablet_manager_client_v2 *client =
+ calloc(1, sizeof(struct wlr_tablet_manager_client_v2));
+ 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 *tablet =
+ wl_container_of(listener, tablet, display_destroy);
+ wlr_tablet_v2_destroy(tablet);
+}
+
+void wlr_tablet_v2_destroy(struct wlr_tablet_manager_v2 *manager) {
+ struct wlr_tablet_manager_client_v2 *client, *client_tmp;
+ wl_list_for_each_safe(client, client_tmp, &manager->clients, link) {
+ wlr_tablet_manager_v2_destroy(client->resource);
+ }
+
+ struct wlr_tablet_seat_v2 *seat, *seat_tmp;
+ wl_list_for_each_safe(seat, seat_tmp, &manager->seats, link) {
+ tablet_seat_destroy(seat);
+ }
+
+ wlr_signal_emit_safe(&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(struct wlr_tablet_manager_v2));
+ 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 00000000..578eef06
--- /dev/null
+++ b/types/tablet_v2/wlr_tablet_v2_pad.c
@@ -0,0 +1,697 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+
+#include "tablet-unstable-v2-protocol.h"
+#include <assert.h>
+#include <stdlib.h>
+#include <types/wlr_tablet_v2.h>
+#include <wayland-util.h>
+#include <wlr/types/wlr_tablet_tool.h>
+#include <wlr/types/wlr_tablet_v2.h>
+#include <wlr/util/log.h>
+
+static 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(&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 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(&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 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(&pad->pad->events.button_feedback, &evt);
+}
+
+static 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);
+
+ 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 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(struct tablet_pad_auxiliary_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(struct tablet_pad_auxiliary_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(struct tablet_pad_auxiliary_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(struct wlr_tablet_pad_client_v2));
+ if (!client) {
+ wl_client_post_no_memory(seat->wl_client);
+ return;
+ }
+ client->pad = pad;
+
+ client->groups = calloc(wl_list_length(&pad->wlr_pad->groups), sizeof(struct wl_resource*));
+ if (!client->groups) {
+ wl_client_post_no_memory(seat->wl_client);
+ free(client);
+ return;
+ }
+
+ client->rings = calloc(pad->wlr_pad->ring_count, sizeof(struct wl_resource*));
+ 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(struct wl_resource*));
+ if (!client->strips) {
+ wl_client_post_no_memory(seat->wl_client);
+ free(client->groups);
+ free(client->rings);
+ free(client);
+ return;
+ }
+
+ client->resource =
+ wl_resource_create(seat->wl_client, &zwp_tablet_pad_v2_interface, 1, 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);
+ }
+ for (size_t i = 0; i < pad->wlr_pad->paths.length; ++i) {
+ zwp_tablet_pad_v2_send_path(client->resource,
+ pad->wlr_pad->paths.items[i]);
+ }
+ 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_device->tablet_pad;
+ struct wlr_tablet_v2_tablet_pad *pad = calloc(1, sizeof(struct wlr_tablet_v2_tablet_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 = wl_display_next_serial(wl_client_get_display(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) {
+ 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, 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 = wl_display_next_serial(wl_client_get_display(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;
+
+ struct wl_client *client = wl_resource_get_client(pad->current_client->resource);
+ uint32_t serial = wl_display_next_serial(wl_client_get_display(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 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 00000000..0d1b5aa3
--- /dev/null
+++ b/types/tablet_v2/wlr_tablet_v2_tablet.c
@@ -0,0 +1,132 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <types/wlr_tablet_v2.h>
+#include <wayland-util.h>
+#include <wlr/types/wlr_tablet_tool.h>
+#include <wlr/types/wlr_tablet_v2.h>
+#include <wlr/util/log.h>
+
+#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 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, tool_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->tool_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_TOOL);
+ struct wlr_tablet_seat_v2 *seat = get_or_create_tablet_seat(manager, wlr_seat);
+ if (!seat) {
+ return NULL;
+ }
+ struct wlr_tablet *wlr_tablet = wlr_device->tablet;
+ struct wlr_tablet_v2_tablet *tablet = calloc(1, sizeof(struct wlr_tablet_v2_tablet));
+ if (!tablet) {
+ return NULL;
+ }
+
+ tablet->wlr_tablet = wlr_tablet;
+ tablet->wlr_device = wlr_device;
+ wl_list_init(&tablet->clients);
+
+
+ tablet->tool_destroy.notify = handle_wlr_tablet_destroy;
+ wl_signal_add(&wlr_device->events.destroy, &tablet->tool_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(struct wlr_tablet_client_v2));
+ 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);
+
+ // Send the expected events
+ if (tablet->wlr_tablet->name) {
+ zwp_tablet_v2_send_name(client->resource,
+ tablet->wlr_tablet->name);
+ }
+ zwp_tablet_v2_send_id(client->resource,
+ tablet->wlr_device->vendor, tablet->wlr_device->product);
+ for (size_t i = 0; i < tablet->wlr_tablet->paths.length; ++i) {
+ zwp_tablet_v2_send_path(client->resource,
+ tablet->wlr_tablet->paths.items[i]);
+ }
+ 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 00000000..81f13aa1
--- /dev/null
+++ b/types/tablet_v2/wlr_tablet_v2_tool.c
@@ -0,0 +1,834 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+
+#include "tablet-unstable-v2-protocol.h"
+#include "util/array.h"
+#include <assert.h>
+#include <stdlib.h>
+#include <types/wlr_tablet_v2.h>
+#include <wayland-util.h>
+#include <wlr/types/wlr_tablet_tool.h>
+#include <wlr/types/wlr_tablet_v2.h>
+#include <wlr/util/log.h>
+
+static const struct wlr_tablet_tool_v2_grab_interface default_tool_grab_interface;
+
+static const struct wlr_surface_role tablet_tool_cursor_surface_role = {
+ .name = "wp_tablet_tool-cursor",
+};
+
+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) {
+ 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, NULL,
+ surface_resource, ZWP_TABLET_TOOL_V2_ERROR_ROLE)) {
+ return;
+ }
+ }
+
+ 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(&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 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;
+ }
+
+ assert(false && "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(struct wlr_tablet_tool_client_v2));
+ if (!client) {
+ return;
+ }
+ client->tool = tool;
+ client->seat = seat;
+
+ client->resource =
+ wl_resource_create(seat->wl_client, &zwp_tablet_tool_v2_interface, 1, 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);
+ 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) {
+ 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(struct wlr_tablet_v2_tablet_tool));
+ if (!tool) {
+ return NULL;
+ }
+
+ tool->wlr_tool = wlr_tool;
+ wl_list_init(&tool->clients);
+ 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);
+}
+
+
+/* Actual protocol foo */
+
+// Button 0 is KEY_RESERVED in input-event-codes on linux (and freebsd)
+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) {
+ bool found = false;
+ size_t i = 0;
+ for (; i < tool->num_buttons; ++i) {
+ if (tool->pressed_buttons[i] == button) {
+ found = true;
+ wlr_log(WLR_DEBUG, "Found the button \\o/: %u", button);
+ break;
+
+ }
+ }
+
+ if (state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED && found) {
+ /* Already have the button saved, durr */
+ return -1;
+ }
+
+ if (state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED && !found) {
+ if (tool->num_buttons < WLR_TABLET_V2_TOOL_BUTTONS_CAP) {
+ i = tool->num_buttons++;
+ tool->pressed_buttons[i] = button;
+ tool->pressed_serials[i] = -1;
+ } else {
+ i = -1;
+ wlr_log(WLR_ERROR, "You pressed more than %d tablet tool buttons. This is currently not supporte by wlroots. Please report this with a description of your tablet, since this is either a bug, or fancy hardware",
+ WLR_TABLET_V2_TOOL_BUTTONS_CAP);
+ }
+ }
+ if (state == ZWP_TABLET_PAD_V2_BUTTON_STATE_RELEASED && found) {
+ wlr_log(WLR_DEBUG, "Removed the button \\o/: %u", button);
+ tool->pressed_buttons[i] = 0;
+ tool->pressed_serials[i] = 0;
+ tool->num_buttons = push_zeroes_to_end(tool->pressed_buttons, WLR_TABLET_V2_TOOL_BUTTONS_CAP);
+ tool->num_buttons = push_zeroes_to_end(tool->pressed_serials, WLR_TABLET_V2_TOOL_BUTTONS_CAP);
+ }
+
+ assert(tool->num_buttons <= WLR_TABLET_V2_TOOL_BUTTONS_CAP);
+ return i;
+}
+
+static inline int64_t timespec_to_msec(const struct timespec *a) {
+ return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000;
+}
+
+static void send_tool_frame(void *data) {
+ struct wlr_tablet_tool_client_v2 *tool = data;
+
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ zwp_tablet_tool_v2_send_frame(tool->resource, timespec_to_msec(&now));
+ 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);
+ }
+}
+
+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;
+ }
+
+ 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;
+ }
+
+ tool->current_client = tool_client;
+
+ uint32_t serial = wl_display_next_serial(wl_client_get_display(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);
+
+ 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) {
+ struct wl_client *client =
+ wl_resource_get_client(tool->current_client->resource);
+ uint32_t serial = wl_display_next_serial(wl_client_get_display(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,
+ clicks, degrees);
+
+ 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) {
+ struct wl_client *client =
+ wl_resource_get_client(tool->current_client->resource);
+ uint32_t serial = wl_display_next_serial(wl_client_get_display(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);
+}
+
+/* Only send the motion event, when we are over the surface for now */
+static void implicit_tool_motion(
+ struct wlr_tablet_tool_v2_grab *grab, double x, double y) {
+ struct implicit_grab_state *state = grab->data;
+ if (state->focused != state->original) {
+ return;
+ }
+
+ wlr_send_tablet_v2_tablet_tool_motion(grab->tool, x, y);
+}
+
+
+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 = implicit_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,
+};
+
+static bool tool_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 (tool_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(struct wlr_tablet_tool_v2_grab));
+ if (!grab) {
+ return;
+ }
+
+ grab->interface = &implicit_tool_grab_interface;
+ grab->tool = tool;
+ struct implicit_grab_state *state = calloc(1, sizeof(struct implicit_grab_state));
+ if (!state) {
+ free(grab);
+ return;
+ }
+
+ state->original = tool->focused_surface;
+ grab->data = state;
+
+ wlr_tablet_tool_v2_start_grab(tool, grab);
+}
diff --git a/types/wlr_box.c b/types/wlr_box.c
new file mode 100644
index 00000000..0020b7a4
--- /dev/null
+++ b/types/wlr_box.c
@@ -0,0 +1,153 @@
+#include <limits.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wayland-server-protocol.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/util/log.h>
+
+void wlr_box_closest_point(const struct wlr_box *box, double x, double y,
+ double *dest_x, double *dest_y) {
+ // 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->x = 0;
+ dest->y = 0;
+ dest->width = -100;
+ dest->height = -100;
+ 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 = *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 = src.y;
+ dest->y = width - src.x - src.width;
+ 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 = height - src.y - src.height;
+ dest->y = src.x;
+ 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 = height - src.y - src.height;
+ dest->y = width - src.x - src.width;
+ 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 = src.y;
+ dest->y = src.x;
+ break;
+ }
+}
+
+void wlr_box_rotated_bounds(struct wlr_box *dest, const struct wlr_box *box,
+ float rotation) {
+ if (rotation == 0) {
+ *dest = *box;
+ return;
+ }
+
+ double ox = box->x + (double)box->width/2;
+ double oy = box->y + (double)box->height/2;
+
+ double c = fabs(cos(rotation));
+ double s = fabs(sin(rotation));
+
+ double x1 = ox + (box->x - ox) * c + (box->y - oy) * s;
+ double x2 = ox +
+ (box->x + box->width - ox) * c +
+ (box->y + box->height - oy) * s;
+
+ double y1 = oy + (box->x - ox) * s + (box->y - oy) * c;
+ double y2 = oy +
+ (box->x + box->width - ox) * s +
+ (box->y + box->height - oy) * c;
+
+ dest->x = floor(fmin(x1, x2));
+ dest->width = ceil(fmax(x1, x2) - fmin(x1, x2));
+ dest->y = floor(fmin(y1, y2));
+ dest->height = ceil(fmax(y1, y2) - fmin(y1, y2));
+}
+
+void wlr_box_from_pixman_box32(struct wlr_box *dest, const pixman_box32_t box) {
+ *dest = (struct wlr_box){
+ .x = box.x1,
+ .y = box.y1,
+ .width = box.x2 - box.x1,
+ .height = box.y2 - box.y1,
+ };
+}
diff --git a/types/wlr_buffer.c b/types/wlr_buffer.c
new file mode 100644
index 00000000..cec3475c
--- /dev/null
+++ b/types/wlr_buffer.c
@@ -0,0 +1,205 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_buffer.h>
+#include <wlr/types/wlr_linux_dmabuf_v1.h>
+#include <wlr/util/log.h>
+
+bool wlr_resource_is_buffer(struct wl_resource *resource) {
+ return strcmp(wl_resource_get_class(resource), wl_buffer_interface.name) == 0;
+}
+
+bool wlr_buffer_get_resource_size(struct wl_resource *resource,
+ struct wlr_renderer *renderer, int *width, int *height) {
+ assert(wlr_resource_is_buffer(resource));
+
+ struct wl_shm_buffer *shm_buf = wl_shm_buffer_get(resource);
+ if (shm_buf != NULL) {
+ *width = wl_shm_buffer_get_width(shm_buf);
+ *height = wl_shm_buffer_get_height(shm_buf);
+ } else if (wlr_renderer_resource_is_wl_drm_buffer(renderer,
+ resource)) {
+ wlr_renderer_wl_drm_buffer_get_size(renderer, resource,
+ width, height);
+ } else if (wlr_dmabuf_v1_resource_is_buffer(resource)) {
+ struct wlr_dmabuf_v1_buffer *dmabuf =
+ wlr_dmabuf_v1_buffer_from_buffer_resource(resource);
+ *width = dmabuf->attributes.width;
+ *height = dmabuf->attributes.height;
+ } else {
+ *width = *height = 0;
+ return false;
+ }
+
+ return true;
+}
+
+
+static void buffer_resource_handle_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_buffer *buffer =
+ wl_container_of(listener, buffer, resource_destroy);
+ wl_list_remove(&buffer->resource_destroy.link);
+ wl_list_init(&buffer->resource_destroy.link);
+ buffer->resource = NULL;
+
+ // At this point, if the wl_buffer comes from linux-dmabuf or wl_drm, we
+ // still haven't released it (ie. we'll read it in the future) but the
+ // client destroyed it. Reading the texture itself should be fine because
+ // we still hold a reference to the DMA-BUF via the texture. However the
+ // client could decide to re-use the same DMA-BUF for something else, in
+ // which case we'll read garbage. We decide to accept this risk.
+}
+
+struct wlr_buffer *wlr_buffer_create(struct wlr_renderer *renderer,
+ struct wl_resource *resource) {
+ assert(wlr_resource_is_buffer(resource));
+
+ struct wlr_texture *texture = NULL;
+ bool released = false;
+
+ struct wl_shm_buffer *shm_buf = wl_shm_buffer_get(resource);
+ if (shm_buf != NULL) {
+ enum wl_shm_format fmt = wl_shm_buffer_get_format(shm_buf);
+ int32_t stride = wl_shm_buffer_get_stride(shm_buf);
+ int32_t width = wl_shm_buffer_get_width(shm_buf);
+ int32_t height = wl_shm_buffer_get_height(shm_buf);
+
+ wl_shm_buffer_begin_access(shm_buf);
+ void *data = wl_shm_buffer_get_data(shm_buf);
+ texture = wlr_texture_from_pixels(renderer, fmt, stride,
+ width, height, data);
+ wl_shm_buffer_end_access(shm_buf);
+
+ // We have uploaded the data, we don't need to access the wl_buffer
+ // anymore
+ wl_buffer_send_release(resource);
+ released = true;
+ } else if (wlr_renderer_resource_is_wl_drm_buffer(renderer, resource)) {
+ texture = wlr_texture_from_wl_drm(renderer, resource);
+ } else if (wlr_dmabuf_v1_resource_is_buffer(resource)) {
+ struct wlr_dmabuf_v1_buffer *dmabuf =
+ wlr_dmabuf_v1_buffer_from_buffer_resource(resource);
+ texture = wlr_texture_from_dmabuf(renderer, &dmabuf->attributes);
+
+ // We have imported the DMA-BUF, but we need to prevent the client from
+ // re-using the same DMA-BUF for the next frames, so we don't release
+ // the buffer yet.
+ } else {
+ wlr_log(WLR_ERROR, "Cannot upload texture: unknown buffer type");
+
+ // Instead of just logging the error, also disconnect the client with a
+ // fatal protocol error so that it's clear something went wrong.
+ wl_resource_post_error(resource, 0, "unknown buffer type");
+ return NULL;
+ }
+
+ if (texture == NULL) {
+ wlr_log(WLR_ERROR, "Failed to upload texture");
+ return NULL;
+ }
+
+ struct wlr_buffer *buffer = calloc(1, sizeof(struct wlr_buffer));
+ if (buffer == NULL) {
+ wlr_texture_destroy(texture);
+ return NULL;
+ }
+ buffer->resource = resource;
+ buffer->texture = texture;
+ buffer->released = released;
+ buffer->n_refs = 1;
+
+ wl_resource_add_destroy_listener(resource, &buffer->resource_destroy);
+ buffer->resource_destroy.notify = buffer_resource_handle_destroy;
+
+ return buffer;
+}
+
+struct wlr_buffer *wlr_buffer_ref(struct wlr_buffer *buffer) {
+ buffer->n_refs++;
+ return buffer;
+}
+
+void wlr_buffer_unref(struct wlr_buffer *buffer) {
+ if (buffer == NULL) {
+ return;
+ }
+
+ assert(buffer->n_refs > 0);
+ buffer->n_refs--;
+ if (buffer->n_refs > 0) {
+ return;
+ }
+
+ if (!buffer->released && buffer->resource != NULL) {
+ wl_buffer_send_release(buffer->resource);
+ }
+
+ wl_list_remove(&buffer->resource_destroy.link);
+ wlr_texture_destroy(buffer->texture);
+ free(buffer);
+}
+
+struct wlr_buffer *wlr_buffer_apply_damage(struct wlr_buffer *buffer,
+ struct wl_resource *resource, pixman_region32_t *damage) {
+ assert(wlr_resource_is_buffer(resource));
+
+ if (buffer->n_refs > 1) {
+ // Someone else still has a reference to the buffer
+ return NULL;
+ }
+
+ struct wl_shm_buffer *shm_buf = wl_shm_buffer_get(resource);
+ struct wl_shm_buffer *old_shm_buf = wl_shm_buffer_get(buffer->resource);
+ if (shm_buf == NULL || old_shm_buf == NULL) {
+ // Uploading only damaged regions only works for wl_shm buffers and
+ // mutable textures (created from wl_shm buffer)
+ return NULL;
+ }
+
+ enum wl_shm_format new_fmt = wl_shm_buffer_get_format(shm_buf);
+ enum wl_shm_format old_fmt = wl_shm_buffer_get_format(old_shm_buf);
+ if (new_fmt != old_fmt) {
+ // Uploading to textures can't change the format
+ return NULL;
+ }
+
+ int32_t stride = wl_shm_buffer_get_stride(shm_buf);
+ int32_t width = wl_shm_buffer_get_width(shm_buf);
+ int32_t height = wl_shm_buffer_get_height(shm_buf);
+
+ int32_t texture_width, texture_height;
+ wlr_texture_get_size(buffer->texture, &texture_width, &texture_height);
+ if (width != texture_width || height != texture_height) {
+ return NULL;
+ }
+
+ wl_shm_buffer_begin_access(shm_buf);
+ void *data = wl_shm_buffer_get_data(shm_buf);
+
+ int n;
+ pixman_box32_t *rects = pixman_region32_rectangles(damage, &n);
+ for (int i = 0; i < n; ++i) {
+ pixman_box32_t *r = &rects[i];
+ if (!wlr_texture_write_pixels(buffer->texture, stride,
+ r->x2 - r->x1, r->y2 - r->y1, r->x1, r->y1,
+ r->x1, r->y1, data)) {
+ wl_shm_buffer_end_access(shm_buf);
+ return NULL;
+ }
+ }
+
+ wl_shm_buffer_end_access(shm_buf);
+
+ // We have uploaded the data, we don't need to access the wl_buffer
+ // anymore
+ wl_buffer_send_release(resource);
+
+ wl_list_remove(&buffer->resource_destroy.link);
+ wl_resource_add_destroy_listener(resource, &buffer->resource_destroy);
+ buffer->resource_destroy.notify = buffer_resource_handle_destroy;
+
+ buffer->resource = resource;
+ buffer->released = true;
+ return buffer;
+}
diff --git a/types/wlr_compositor.c b/types/wlr_compositor.c
new file mode 100644
index 00000000..7702a778
--- /dev/null
+++ b/types/wlr_compositor.c
@@ -0,0 +1,245 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_region.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/util/log.h>
+#include "util/signal.h"
+
+#define COMPOSITOR_VERSION 4
+#define SUBCOMPOSITOR_VERSION 1
+
+extern const struct wlr_surface_role subsurface_role;
+
+bool wlr_surface_is_subsurface(struct wlr_surface *surface) {
+ return surface->role == &subsurface_role;
+}
+
+struct wlr_subsurface *wlr_subsurface_from_wlr_surface(
+ struct wlr_surface *surface) {
+ assert(wlr_surface_is_subsurface(surface));
+ return (struct wlr_subsurface *)surface->role_data;
+}
+
+static const struct wl_subcompositor_interface subcompositor_impl;
+
+static struct wlr_subcompositor *subcompositor_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &wl_subcompositor_interface,
+ &subcompositor_impl));
+ return wl_resource_get_user_data(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_subcompositor *subcompositor =
+ subcompositor_from_resource(resource);
+ struct wlr_surface *surface = wlr_surface_from_resource(surface_resource);
+ struct wlr_surface *parent = wlr_surface_from_resource(parent_resource);
+
+ static const char msg[] = "get_subsurface: wl_subsurface@";
+
+ if (surface == parent) {
+ wl_resource_post_error(resource,
+ WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE,
+ "%s%d: wl_surface@%d cannot be its own parent",
+ msg, id, wl_resource_get_id(surface_resource));
+ return;
+ }
+
+ if (wlr_surface_is_subsurface(surface) &&
+ wlr_subsurface_from_wlr_surface(surface) != NULL) {
+ wl_resource_post_error(resource,
+ WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE,
+ "%s%d: wl_surface@%d is already a sub-surface",
+ msg, id, wl_resource_get_id(surface_resource));
+ return;
+ }
+
+ if (wlr_surface_get_root_surface(parent) == surface) {
+ wl_resource_post_error(resource,
+ WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE,
+ "%s%d: wl_surface@%d is an ancestor of parent",
+ msg, id, wl_resource_get_id(surface_resource));
+ return;
+ }
+
+ if (!wlr_surface_set_role(surface, &subsurface_role, NULL,
+ resource, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE)) {
+ return;
+ }
+
+ wlr_subsurface_create(surface, parent, wl_resource_get_version(resource),
+ id, &subcompositor->subsurface_resources);
+}
+
+static const struct wl_subcompositor_interface subcompositor_impl = {
+ .destroy = subcompositor_handle_destroy,
+ .get_subsurface = subcompositor_handle_get_subsurface,
+};
+
+static void subcompositor_resource_destroy(struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+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, subcompositor_resource_destroy);
+ wl_list_insert(&subcompositor->resources, wl_resource_get_link(resource));
+}
+
+static void subcompositor_init(struct wlr_subcompositor *subcompositor,
+ struct wl_display *display) {
+ subcompositor->global = wl_global_create(display,
+ &wl_subcompositor_interface, SUBCOMPOSITOR_VERSION, subcompositor,
+ subcompositor_bind);
+ if (subcompositor->global == NULL) {
+ wlr_log_errno(WLR_ERROR, "Could not allocate subcompositor global");
+ return;
+ }
+ wl_list_init(&subcompositor->resources);
+ wl_list_init(&subcompositor->subsurface_resources);
+}
+
+static void subcompositor_finish(struct wlr_subcompositor *subcompositor) {
+ wl_global_destroy(subcompositor->global);
+ struct wl_resource *resource, *tmp;
+ wl_resource_for_each_safe(resource, tmp,
+ &subcompositor->subsurface_resources) {
+ wl_resource_destroy(resource);
+ }
+ wl_resource_for_each_safe(resource, tmp, &subcompositor->resources) {
+ wl_resource_destroy(resource);
+ }
+}
+
+
+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 = wlr_surface_create(client,
+ wl_resource_get_version(resource), id, compositor->renderer,
+ &compositor->surface_resources);
+ if (surface == NULL) {
+ return;
+ }
+
+ wlr_signal_emit_safe(&compositor->events.new_surface, surface);
+}
+
+static void compositor_create_region(struct wl_client *client,
+ struct wl_resource *resource, uint32_t id) {
+ struct wlr_compositor *compositor = compositor_from_resource(resource);
+
+ wlr_region_create(client, 1, id, &compositor->region_resources);
+}
+
+static const struct wl_compositor_interface compositor_impl = {
+ .create_surface = compositor_create_surface,
+ .create_region = compositor_create_region,
+};
+
+static void compositor_resource_destroy(struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+static void compositor_bind(struct wl_client *wl_client, void *data,
+ uint32_t version, uint32_t id) {
+ struct wlr_compositor *compositor = data;
+ assert(wl_client && compositor);
+
+ 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, compositor_resource_destroy);
+ wl_list_insert(&compositor->resources, wl_resource_get_link(resource));
+}
+
+void wlr_compositor_destroy(struct wlr_compositor *compositor) {
+ if (compositor == NULL) {
+ return;
+ }
+ wlr_signal_emit_safe(&compositor->events.destroy, compositor);
+ subcompositor_finish(&compositor->subcompositor);
+ wl_list_remove(&compositor->display_destroy.link);
+ wl_global_destroy(compositor->global);
+ struct wl_resource *resource, *tmp;
+ wl_resource_for_each_safe(resource, tmp, &compositor->surface_resources) {
+ wl_resource_destroy(resource);
+ }
+ wl_resource_for_each_safe(resource, tmp, &compositor->region_resources) {
+ wl_resource_destroy(resource);
+ }
+ wl_resource_for_each_safe(resource, tmp, &compositor->resources) {
+ wl_resource_destroy(resource);
+ }
+ free(compositor);
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_compositor *compositor =
+ wl_container_of(listener, compositor, display_destroy);
+ wlr_compositor_destroy(compositor);
+}
+
+struct wlr_compositor *wlr_compositor_create(struct wl_display *display,
+ struct wlr_renderer *renderer) {
+ struct wlr_compositor *compositor =
+ calloc(1, sizeof(struct wlr_compositor));
+ if (!compositor) {
+ wlr_log_errno(WLR_ERROR, "Could not allocate wlr compositor");
+ return NULL;
+ }
+
+ compositor->global = wl_global_create(display, &wl_compositor_interface,
+ COMPOSITOR_VERSION, compositor, compositor_bind);
+ if (!compositor->global) {
+ free(compositor);
+ wlr_log_errno(WLR_ERROR, "Could not allocate compositor global");
+ return NULL;
+ }
+ compositor->renderer = renderer;
+
+ wl_list_init(&compositor->resources);
+ wl_list_init(&compositor->surface_resources);
+ wl_list_init(&compositor->region_resources);
+ wl_signal_init(&compositor->events.new_surface);
+ wl_signal_init(&compositor->events.destroy);
+
+ subcompositor_init(&compositor->subcompositor, display);
+
+ compositor->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &compositor->display_destroy);
+
+ return compositor;
+}
diff --git a/types/wlr_cursor.c b/types/wlr_cursor.c
new file mode 100644
index 00000000..8094ff18
--- /dev/null
+++ b/types/wlr_cursor.c
@@ -0,0 +1,741 @@
+#include <assert.h>
+#include <limits.h>
+#include <math.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/util/log.h>
+#include "util/signal.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;
+
+ struct wl_listener motion;
+ struct wl_listener motion_absolute;
+ struct wl_listener button;
+ struct wl_listener axis;
+
+ struct wl_listener touch_down;
+ struct wl_listener touch_up;
+ struct wl_listener touch_motion;
+ struct wl_listener touch_cancel;
+
+ 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;
+};
+
+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;
+
+ struct wl_listener layout_add;
+ struct wl_listener layout_change;
+ struct wl_listener layout_destroy;
+};
+
+struct wlr_cursor *wlr_cursor_create(void) {
+ struct wlr_cursor *cur = calloc(1, sizeof(struct wlr_cursor));
+ if (!cur) {
+ wlr_log(WLR_ERROR, "Failed to allocate wlr_cursor");
+ return NULL;
+ }
+
+ cur->state = calloc(1, sizeof(struct wlr_cursor_state));
+ if (!cur->state) {
+ wlr_log(WLR_ERROR, "Failed to allocate wlr_cursor_state");
+ free(cur);
+ return NULL;
+ }
+
+ cur->state->cursor = cur;
+ cur->state->mapped_output = NULL;
+
+ 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);
+
+ // 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);
+
+ // 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);
+
+ cur->x = 100;
+ cur->y = 100;
+
+ return cur;
+}
+
+static void output_cursor_destroy(
+ struct wlr_cursor_output_cursor *output_cursor) {
+ wl_list_remove(&output_cursor->layout_output_destroy.link);
+ wl_list_remove(&output_cursor->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;
+ if (dev->type == 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);
+ } else if (dev->type == 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);
+ } else if (dev->type == WLR_INPUT_DEVICE_TABLET_TOOL) {
+ 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);
+ }
+
+ wl_list_remove(&c_device->link);
+ wl_list_remove(&c_device->destroy.link);
+ free(c_device);
+}
+
+void wlr_cursor_destroy(struct wlr_cursor *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);
+ free(cur);
+}
+
+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 cursor_warp_unchecked(struct wlr_cursor *cur,
+ double lx, double ly) {
+ assert(cur->state->layout);
+
+ struct wlr_cursor_output_cursor *output_cursor;
+ wl_list_for_each(output_cursor, &cur->state->output_cursors, link) {
+ double output_x = lx, output_y = ly;
+ 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);
+ }
+
+ cur->x = lx;
+ cur->y = ly;
+}
+
+/**
+ * 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, returns NULL and absolute movement should be
+ * relative to the extents of the layout.
+ */
+static struct wlr_box *get_mapping(struct wlr_cursor *cur,
+ struct wlr_input_device *dev) {
+ assert(cur->state->layout);
+ struct wlr_cursor_device *c_device = get_cursor_device(cur, dev);
+
+ if (c_device) {
+ if (c_device->mapped_box) {
+ return c_device->mapped_box;
+ }
+ if (c_device->mapped_output) {
+ return wlr_output_layout_get_box(cur->state->layout,
+ c_device->mapped_output);
+ }
+ }
+
+ if (cur->state->mapped_box) {
+ return cur->state->mapped_box;
+ }
+ if (cur->state->mapped_output) {
+ return wlr_output_layout_get_box(cur->state->layout,
+ cur->state->mapped_output);
+ }
+
+ return NULL;
+}
+
+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);
+ if (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);
+ if (mapping) {
+ wlr_box_closest_point(mapping, lx, ly, &lx, &ly);
+ } else {
+ wlr_output_layout_closest_point(cur->state->layout, NULL, lx, ly,
+ &lx, &ly);
+ }
+
+ 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);
+ if (!mapping) {
+ mapping = wlr_output_layout_get_box(cur->state->layout, NULL);
+ }
+
+ *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);
+}
+
+void wlr_cursor_set_image(struct wlr_cursor *cur, const uint8_t *pixels,
+ int32_t stride, uint32_t width, uint32_t height, int32_t hotspot_x,
+ int32_t hotspot_y, float scale) {
+ struct wlr_cursor_output_cursor *output_cursor;
+ wl_list_for_each(output_cursor, &cur->state->output_cursors, link) {
+ float output_scale = output_cursor->output_cursor->output->scale;
+ if (scale > 0 && output_scale != scale) {
+ continue;
+ }
+
+ wlr_output_cursor_set_image(output_cursor->output_cursor, pixels,
+ stride, width, height, hotspot_x, hotspot_y);
+ }
+}
+
+void wlr_cursor_set_surface(struct wlr_cursor *cur, struct wlr_surface *surface,
+ int32_t hotspot_x, int32_t hotspot_y) {
+ struct wlr_cursor_output_cursor *output_cursor;
+ wl_list_for_each(output_cursor, &cur->state->output_cursors, link) {
+ wlr_output_cursor_set_surface(output_cursor->output_cursor, surface,
+ hotspot_x, hotspot_y);
+ }
+}
+
+static void handle_pointer_motion(struct wl_listener *listener, void *data) {
+ struct wlr_event_pointer_motion *event = data;
+ struct wlr_cursor_device *device =
+ wl_container_of(listener, device, motion);
+ wlr_signal_emit_safe(&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 = *y;
+ dy = width - *x;
+ break;
+ case WL_OUTPUT_TRANSFORM_180:
+ dx = width - *x;
+ dy = height - *y;
+ break;
+ case WL_OUTPUT_TRANSFORM_270:
+ dx = height - *y;
+ dy = *x;
+ break;
+ case WL_OUTPUT_TRANSFORM_FLIPPED:
+ dx = width - *x;
+ dy = *y;
+ break;
+ case WL_OUTPUT_TRANSFORM_FLIPPED_90:
+ dx = height - *y;
+ dy = width - *x;
+ break;
+ case WL_OUTPUT_TRANSFORM_FLIPPED_180:
+ dx = *x;
+ dy = height - *y;
+ break;
+ case WL_OUTPUT_TRANSFORM_FLIPPED_270:
+ dx = *y;
+ dy = *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_event_pointer_motion_absolute *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);
+ }
+ wlr_signal_emit_safe(&device->cursor->events.motion_absolute, event);
+}
+
+static void handle_pointer_button(struct wl_listener *listener, void *data) {
+ struct wlr_event_pointer_button *event = data;
+ struct wlr_cursor_device *device =
+ wl_container_of(listener, device, button);
+ wlr_signal_emit_safe(&device->cursor->events.button, event);
+}
+
+static void handle_pointer_axis(struct wl_listener *listener, void *data) {
+ struct wlr_event_pointer_axis *event = data;
+ struct wlr_cursor_device *device = wl_container_of(listener, device, axis);
+ wlr_signal_emit_safe(&device->cursor->events.axis, event);
+}
+
+static void handle_touch_up(struct wl_listener *listener, void *data) {
+ struct wlr_event_touch_up *event = data;
+ struct wlr_cursor_device *device;
+ device = wl_container_of(listener, device, touch_up);
+ wlr_signal_emit_safe(&device->cursor->events.touch_up, event);
+}
+
+static void handle_touch_down(struct wl_listener *listener, void *data) {
+ struct wlr_event_touch_down *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);
+ }
+ wlr_signal_emit_safe(&device->cursor->events.touch_down, event);
+}
+
+static void handle_touch_motion(struct wl_listener *listener, void *data) {
+ struct wlr_event_touch_motion *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);
+ }
+ wlr_signal_emit_safe(&device->cursor->events.touch_motion, event);
+}
+
+static void handle_touch_cancel(struct wl_listener *listener, void *data) {
+ struct wlr_event_touch_cancel *event = data;
+ struct wlr_cursor_device *device;
+ device = wl_container_of(listener, device, touch_cancel);
+ wlr_signal_emit_safe(&device->cursor->events.touch_cancel, event);
+}
+
+static void handle_tablet_tool_tip(struct wl_listener *listener, void *data) {
+ struct wlr_event_tablet_tool_tip *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);
+ }
+ wlr_signal_emit_safe(&device->cursor->events.tablet_tool_tip, event);
+}
+
+static void handle_tablet_tool_axis(struct wl_listener *listener, void *data) {
+ struct wlr_event_tablet_tool_axis *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) {
+ apply_output_transform(&event->x, &event->y, output->transform);
+ }
+ wlr_signal_emit_safe(&device->cursor->events.tablet_tool_axis, event);
+}
+
+static void handle_tablet_tool_button(struct wl_listener *listener,
+ void *data) {
+ struct wlr_event_tablet_tool_button *event = data;
+ struct wlr_cursor_device *device;
+ device = wl_container_of(listener, device, tablet_tool_button);
+ wlr_signal_emit_safe(&device->cursor->events.tablet_tool_button, event);
+}
+
+static void handle_tablet_tool_proximity(struct wl_listener *listener,
+ void *data) {
+ struct wlr_event_tablet_tool_proximity *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);
+ }
+ wlr_signal_emit_safe(&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(struct wlr_cursor_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;
+
+ if (device->type == WLR_INPUT_DEVICE_POINTER) {
+ wl_signal_add(&device->pointer->events.motion, &c_device->motion);
+ c_device->motion.notify = handle_pointer_motion;
+
+ wl_signal_add(&device->pointer->events.motion_absolute,
+ &c_device->motion_absolute);
+ c_device->motion_absolute.notify = handle_pointer_motion_absolute;
+
+ wl_signal_add(&device->pointer->events.button, &c_device->button);
+ c_device->button.notify = handle_pointer_button;
+
+ wl_signal_add(&device->pointer->events.axis, &c_device->axis);
+ c_device->axis.notify = handle_pointer_axis;
+ } else if (device->type == WLR_INPUT_DEVICE_TOUCH) {
+ wl_signal_add(&device->touch->events.motion, &c_device->touch_motion);
+ c_device->touch_motion.notify = handle_touch_motion;
+
+ wl_signal_add(&device->touch->events.down, &c_device->touch_down);
+ c_device->touch_down.notify = handle_touch_down;
+
+ wl_signal_add(&device->touch->events.up, &c_device->touch_up);
+ c_device->touch_up.notify = handle_touch_up;
+
+ wl_signal_add(&device->touch->events.cancel, &c_device->touch_cancel);
+ c_device->touch_cancel.notify = handle_touch_cancel;
+ } else if (device->type == WLR_INPUT_DEVICE_TABLET_TOOL) {
+ wl_signal_add(&device->tablet->events.tip,
+ &c_device->tablet_tool_tip);
+ c_device->tablet_tool_tip.notify = handle_tablet_tool_tip;
+
+ wl_signal_add(&device->tablet->events.proximity,
+ &c_device->tablet_tool_proximity);
+ c_device->tablet_tool_proximity.notify = handle_tablet_tool_proximity;
+
+ wl_signal_add(&device->tablet->events.axis,
+ &c_device->tablet_tool_axis);
+ c_device->tablet_tool_axis.notify = handle_tablet_tool_axis;
+
+ wl_signal_add(&device->tablet->events.button,
+ &c_device->tablet_tool_button);
+ c_device->tablet_tool_button.notify = handle_tablet_tool_button;
+ }
+
+ 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) {
+ if (dev->type != WLR_INPUT_DEVICE_POINTER &&
+ dev->type != WLR_INPUT_DEVICE_TOUCH &&
+ dev->type != WLR_INPUT_DEVICE_TABLET_TOOL) {
+ 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);
+ //struct wlr_output_layout_output *l_output = data;
+ 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(struct wlr_cursor_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);
+}
+
+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)) {
+ // the output we were on has gone away so go to the closest boundary
+ // point
+ 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,
+ struct wlr_box *box) {
+ if (box && wlr_box_empty(box)) {
+ wlr_log(WLR_ERROR, "cannot map cursor to an empty region");
+ return;
+ }
+
+ cur->state->mapped_box = box;
+}
+
+void wlr_cursor_map_input_to_region(struct wlr_cursor *cur,
+ struct wlr_input_device *dev, struct wlr_box *box) {
+ if (box && wlr_box_empty(box)) {
+ wlr_log(WLR_ERROR, "cannot map device \"%s\" input to an empty region",
+ dev->name);
+ return;
+ }
+
+ 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 = box;
+}
diff --git a/types/wlr_export_dmabuf_v1.c b/types/wlr_export_dmabuf_v1.c
new file mode 100644
index 00000000..3af7e69f
--- /dev/null
+++ b/types/wlr_export_dmabuf_v1.c
@@ -0,0 +1,228 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <wlr/interfaces/wlr_output.h>
+#include <wlr/render/dmabuf.h>
+#include <wlr/types/wlr_export_dmabuf_v1.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/util/log.h>
+#include "util/signal.h"
+#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->cursor_locked) {
+ wlr_output_lock_software_cursors(frame->output, false);
+ }
+ wl_list_remove(&frame->link);
+ wl_list_remove(&frame->output_swap_buffers.link);
+ wlr_dmabuf_attributes_finish(&frame->attribs);
+ // 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_swap_buffers(struct wl_listener *listener,
+ void *data) {
+ struct wlr_export_dmabuf_frame_v1 *frame =
+ wl_container_of(listener, frame, output_swap_buffers);
+ struct wlr_output_event_swap_buffers *event = data;
+
+ wl_list_remove(&frame->output_swap_buffers.link);
+ wl_list_init(&frame->output_swap_buffers.link);
+
+ 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 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(struct wlr_export_dmabuf_frame_v1));
+ if (frame == NULL) {
+ wl_resource_post_no_memory(manager_resource);
+ return;
+ }
+ frame->manager = manager;
+ frame->output = output;
+ wl_list_init(&frame->output_swap_buffers.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->impl->export_dmabuf) {
+ zwlr_export_dmabuf_frame_v1_send_cancel(frame->resource,
+ ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT);
+ frame_destroy(frame);
+ return;
+ }
+
+ struct wlr_dmabuf_attributes *attribs = &frame->attribs;
+ if (!wlr_output_export_dmabuf(output, attribs)) {
+ zwlr_export_dmabuf_frame_v1_send_cancel(frame->resource,
+ ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_TEMPORARY);
+ frame_destroy(frame);
+ return;
+ }
+
+ if (overlay_cursor) {
+ wlr_output_lock_software_cursors(frame->output, true);
+ frame->cursor_locked = true;
+ }
+
+ 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,
+ output->width, output->height, 0, 0, attribs->flags, 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);
+ }
+
+ wl_list_remove(&frame->output_swap_buffers.link);
+ wl_signal_add(&output->events.swap_buffers, &frame->output_swap_buffers);
+ frame->output_swap_buffers.notify = frame_output_handle_swap_buffers;
+}
+
+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_handle_resource_destroy(struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+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,
+ manager_handle_resource_destroy);
+
+ wl_list_insert(&manager->resources, wl_resource_get_link(resource));
+}
+
+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);
+ wlr_export_dmabuf_manager_v1_destroy(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(struct wlr_export_dmabuf_manager_v1));
+ if (manager == NULL) {
+ return NULL;
+ }
+ wl_list_init(&manager->resources);
+ 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;
+}
+
+void wlr_export_dmabuf_manager_v1_destroy(
+ struct wlr_export_dmabuf_manager_v1 *manager) {
+ if (manager == NULL) {
+ return;
+ }
+ wlr_signal_emit_safe(&manager->events.destroy, manager);
+ wl_list_remove(&manager->display_destroy.link);
+ wl_global_destroy(manager->global);
+ struct wl_resource *resource, *resource_tmp;
+ wl_resource_for_each_safe(resource, resource_tmp, &manager->resources) {
+ wl_resource_destroy(resource);
+ }
+ struct wlr_export_dmabuf_frame_v1 *frame, *frame_tmp;
+ wl_list_for_each_safe(frame, frame_tmp, &manager->frames, link) {
+ wl_resource_destroy(frame->resource);
+ }
+ free(manager);
+}
diff --git a/types/wlr_foreign_toplevel_management_v1.c b/types/wlr_foreign_toplevel_management_v1.c
new file mode 100644
index 00000000..7570716c
--- /dev/null
+++ b/types/wlr_foreign_toplevel_management_v1.c
@@ -0,0 +1,578 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <wlr/types/wlr_foreign_toplevel_management_v1.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/util/log.h>
+#include "util/signal.h"
+#include "wlr-foreign-toplevel-management-unstable-v1-protocol.h"
+
+#define FOREIGN_TOPLEVEL_MANAGEMENT_V1_VERSION 1
+
+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,
+ };
+ wlr_signal_emit_safe(&toplevel->events.request_maximize, &event);
+}
+
+void foreign_toplevel_handle_set_maximized(struct wl_client *client,
+ struct wl_resource *resource) {
+ toplevel_handle_send_maximized_event(resource, true);
+}
+
+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,
+ };
+ wlr_signal_emit_safe(&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 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);
+ struct wlr_foreign_toplevel_handle_v1_activated_event event = {
+ .toplevel = toplevel,
+ .seat = seat_client->seat,
+ };
+ wlr_signal_emit_safe(&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;
+ }
+ wlr_signal_emit_safe(&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,
+ };
+ wlr_signal_emit_safe(&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
+};
+
+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);
+
+ 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);
+
+ 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_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(struct wlr_foreign_toplevel_handle_v1_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_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_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;
+ }
+
+ 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;
+ } 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;
+ } 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;
+ } else {
+ toplevel->state &= ~WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED;
+ }
+ toplevel_send_state(toplevel);
+}
+
+void wlr_foreign_toplevel_handle_v1_destroy(
+ struct wlr_foreign_toplevel_handle_v1 *toplevel) {
+ if (!toplevel) {
+ return;
+ }
+
+ wlr_signal_emit_safe(&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);
+
+ 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(struct wlr_foreign_toplevel_handle_v1));
+ 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_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);
+
+ 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;
+ wl_list_for_each_safe(toplevel, tmp, &manager->toplevels, link) {
+ struct wl_resource *toplevel_resource =
+ create_toplevel_resource_for_resource(toplevel, resource);
+ toplevel_send_details_to_toplevel_resource(toplevel,
+ toplevel_resource);
+ }
+}
+
+void wlr_foreign_toplevel_manager_v1_destroy(
+ struct wlr_foreign_toplevel_manager_v1 *manager) {
+ if (!manager) {
+ return;
+ }
+
+ struct wlr_foreign_toplevel_handle_v1 *toplevel, *tmp_toplevel;
+ wl_list_for_each_safe(toplevel, tmp_toplevel, &manager->toplevels, link) {
+ wlr_foreign_toplevel_handle_v1_destroy(toplevel);
+ }
+
+ struct wl_resource *resource, *tmp_resource;
+ wl_resource_for_each_safe(resource, tmp_resource, &manager->resources) {
+ wl_resource_destroy(resource);
+ }
+
+ wlr_signal_emit_safe(&manager->events.destroy, manager);
+ wl_list_remove(&manager->display_destroy.link);
+
+ wl_global_destroy(manager->global);
+ free(manager);
+}
+
+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);
+ wlr_foreign_toplevel_manager_v1_destroy(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(struct wlr_foreign_toplevel_manager_v1));
+ 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_gamma_control.c b/types/wlr_gamma_control.c
new file mode 100644
index 00000000..40ca2aca
--- /dev/null
+++ b/types/wlr_gamma_control.c
@@ -0,0 +1,197 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_gamma_control.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/util/log.h>
+#include "gamma-control-protocol.h"
+#include "util/signal.h"
+
+static void resource_destroy(struct wl_client *client,
+ struct wl_resource *resource) {
+ wl_resource_destroy(resource);
+}
+
+static void gamma_control_destroy(struct wlr_gamma_control *gamma_control) {
+ if (gamma_control == NULL) {
+ return;
+ }
+ wlr_signal_emit_safe(&gamma_control->events.destroy, gamma_control);
+ wl_list_remove(&gamma_control->output_destroy_listener.link);
+ wl_resource_set_user_data(gamma_control->resource, NULL);
+ wl_list_remove(&gamma_control->link);
+ free(gamma_control);
+}
+
+static const struct gamma_control_interface gamma_control_impl;
+
+static struct wlr_gamma_control *gamma_control_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &gamma_control_interface,
+ &gamma_control_impl));
+ return wl_resource_get_user_data(resource);
+}
+
+static void gamma_control_destroy_resource(struct wl_resource *resource) {
+ struct wlr_gamma_control *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 *gamma_control =
+ wl_container_of(listener, gamma_control, output_destroy_listener);
+ gamma_control_destroy(gamma_control);
+}
+
+static void gamma_control_set_gamma(struct wl_client *client,
+ struct wl_resource *gamma_control_resource, struct wl_array *red,
+ struct wl_array *green, struct wl_array *blue) {
+ struct wlr_gamma_control *gamma_control =
+ gamma_control_from_resource(gamma_control_resource);
+
+ if (gamma_control == NULL) {
+ return;
+ }
+
+ if (red->size != green->size || red->size != blue->size) {
+ wl_resource_post_error(gamma_control_resource,
+ GAMMA_CONTROL_ERROR_INVALID_GAMMA,
+ "The gamma ramps don't have the same size");
+ return;
+ }
+
+ uint32_t size = red->size / sizeof(uint16_t);
+ uint16_t *r = (uint16_t *)red->data;
+ uint16_t *g = (uint16_t *)green->data;
+ uint16_t *b = (uint16_t *)blue->data;
+
+ wlr_output_set_gamma(gamma_control->output, size, r, g, b);
+}
+
+static void gamma_control_reset_gamma(struct wl_client *client,
+ struct wl_resource *gamma_control_resource) {
+ // TODO
+}
+
+static const struct gamma_control_interface gamma_control_impl = {
+ .destroy = resource_destroy,
+ .set_gamma = gamma_control_set_gamma,
+ .reset_gamma = gamma_control_reset_gamma,
+};
+
+static const struct gamma_control_manager_interface gamma_control_manager_impl;
+
+static struct wlr_gamma_control_manager *gamma_control_manager_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &gamma_control_manager_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 *gamma_control_manager_resource, uint32_t id,
+ struct wl_resource *output_resource) {
+ struct wlr_gamma_control_manager *manager =
+ gamma_control_manager_from_resource(gamma_control_manager_resource);
+ struct wlr_output *output = wlr_output_from_resource(output_resource);
+
+ struct wlr_gamma_control *gamma_control =
+ calloc(1, sizeof(struct wlr_gamma_control));
+ if (gamma_control == NULL) {
+ wl_client_post_no_memory(client);
+ return;
+ }
+ gamma_control->output = output;
+
+ int version = wl_resource_get_version(gamma_control_manager_resource);
+ gamma_control->resource = wl_resource_create(client,
+ &gamma_control_interface, version, id);
+ if (gamma_control->resource == NULL) {
+ free(gamma_control);
+ wl_client_post_no_memory(client);
+ return;
+ }
+ wlr_log(WLR_DEBUG, "new gamma_control %p (res %p)", gamma_control,
+ gamma_control->resource);
+ wl_resource_set_implementation(gamma_control->resource,
+ &gamma_control_impl, gamma_control, gamma_control_destroy_resource);
+
+ wl_signal_init(&gamma_control->events.destroy);
+
+ 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);
+
+ gamma_control_send_gamma_size(gamma_control->resource,
+ wlr_output_get_gamma_size(output));
+}
+
+static const struct gamma_control_manager_interface gamma_control_manager_impl = {
+ .get_gamma_control = gamma_control_manager_get_gamma_control,
+};
+
+static void gamma_control_manager_bind(struct wl_client *client, void *data,
+ uint32_t version, uint32_t id) {
+ struct wlr_gamma_control_manager *manager = data;
+ assert(client && manager);
+
+ struct wl_resource *resource = wl_resource_create(client,
+ &gamma_control_manager_interface, version, id);
+ if (resource == NULL) {
+ wl_client_post_no_memory(client);
+ return;
+ }
+ wl_resource_set_implementation(resource, &gamma_control_manager_impl,
+ manager, NULL);
+}
+
+void wlr_gamma_control_manager_destroy(
+ struct wlr_gamma_control_manager *manager) {
+ if (!manager) {
+ return;
+ }
+ struct wlr_gamma_control *gamma_control, *tmp;
+ wl_list_for_each_safe(gamma_control, tmp, &manager->controls, link) {
+ gamma_control_destroy(gamma_control);
+ }
+ wlr_signal_emit_safe(&manager->events.destroy, manager);
+ wl_list_remove(&manager->display_destroy.link);
+ wl_global_destroy(manager->global);
+ free(manager);
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_gamma_control_manager *manager =
+ wl_container_of(listener, manager, display_destroy);
+ wlr_gamma_control_manager_destroy(manager);
+}
+
+struct wlr_gamma_control_manager *wlr_gamma_control_manager_create(
+ struct wl_display *display) {
+ struct wlr_gamma_control_manager *manager =
+ calloc(1, sizeof(struct wlr_gamma_control_manager));
+ if (!manager) {
+ return NULL;
+ }
+ struct wl_global *global = wl_global_create(display,
+ &gamma_control_manager_interface, 1, manager,
+ gamma_control_manager_bind);
+ if (!global) {
+ free(manager);
+ return NULL;
+ }
+ manager->global = global;
+
+ wl_signal_init(&manager->events.destroy);
+ wl_list_init(&manager->controls);
+
+ manager->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &manager->display_destroy);
+
+ return manager;
+}
diff --git a/types/wlr_gamma_control_v1.c b/types/wlr_gamma_control_v1.c
new file mode 100644
index 00000000..8ccc96e5
--- /dev/null
+++ b/types/wlr_gamma_control_v1.c
@@ -0,0 +1,272 @@
+#include <assert.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/interfaces/wlr_output.h>
+#include <wlr/types/wlr_gamma_control_v1.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/util/log.h>
+#include "util/signal.h"
+#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;
+ }
+ wlr_output_set_gamma(gamma_control->output, 0, NULL, NULL, NULL);
+ 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);
+}
+
+static void gamma_control_send_failed(
+ struct wlr_gamma_control_v1 *gamma_control) {
+ zwlr_gamma_control_v1_send_failed(gamma_control->resource);
+ gamma_control_destroy(gamma_control);
+}
+
+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");
+ gamma_control_send_failed(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");
+ gamma_control_send_failed(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 = read(fd, table, table_size);
+ if (n_read < 0) {
+ wlr_log_errno(WLR_ERROR, "failed to read gamma table");
+ gamma_control_send_failed(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);
+ fd = -1;
+
+ uint16_t *r = table;
+ uint16_t *g = table + ramp_size;
+ uint16_t *b = table + 2 * ramp_size;
+
+ bool ok = wlr_output_set_gamma(gamma_control->output, ramp_size, r, g, b);
+ if (!ok) {
+ gamma_control_send_failed(gamma_control);
+ goto error_table;
+ }
+ free(table);
+
+ 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);
+
+ struct wlr_gamma_control_v1 *gamma_control =
+ calloc(1, sizeof(struct wlr_gamma_control_v1));
+ if (gamma_control == NULL) {
+ wl_client_post_no_memory(client);
+ return;
+ }
+ gamma_control->output = output;
+
+ uint32_t version = wl_resource_get_version(manager_resource);
+ gamma_control->resource = wl_resource_create(client,
+ &zwlr_gamma_control_v1_interface, version, id);
+ if (gamma_control->resource == NULL) {
+ free(gamma_control);
+ wl_client_post_no_memory(client);
+ return;
+ }
+ wl_resource_set_implementation(gamma_control->resource, &gamma_control_impl,
+ gamma_control, gamma_control_handle_resource_destroy);
+
+ wl_signal_add(&output->events.destroy,
+ &gamma_control->output_destroy_listener);
+ gamma_control->output_destroy_listener.notify =
+ gamma_control_handle_output_destroy;
+
+ wl_list_init(&gamma_control->link);
+
+ if (!output->impl->set_gamma) {
+ zwlr_gamma_control_v1_send_failed(gamma_control->resource);
+ gamma_control_destroy(gamma_control);
+ return;
+ }
+
+ struct wlr_gamma_control_v1 *gc;
+ wl_list_for_each(gc, &manager->controls, link) {
+ if (gc->output == output) {
+ zwlr_gamma_control_v1_send_failed(gc->resource);
+ gamma_control_destroy(gc);
+ return;
+ }
+ }
+
+ wl_list_remove(&gamma_control->link);
+ wl_list_insert(&manager->controls, &gamma_control->link);
+ zwlr_gamma_control_v1_send_gamma_size(gamma_control->resource,
+ wlr_output_get_gamma_size(output));
+}
+
+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_handle_resource_destroy(
+ struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+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, gamma_control_manager_handle_resource_destroy);
+ wl_list_insert(&manager->resources, wl_resource_get_link(resource));
+}
+
+void wlr_gamma_control_manager_v1_destroy(
+ struct wlr_gamma_control_manager_v1 *manager) {
+ if (!manager) {
+ return;
+ }
+ struct wlr_gamma_control_v1 *gamma_control, *tmp;
+ wl_list_for_each_safe(gamma_control, tmp, &manager->controls, link) {
+ wl_resource_destroy(gamma_control->resource);
+ }
+ wlr_signal_emit_safe(&manager->events.destroy, manager);
+ wl_list_remove(&manager->display_destroy.link);
+ struct wl_resource *resource, *resource_tmp;
+ wl_resource_for_each_safe(resource, resource_tmp, &manager->resources) {
+ wl_resource_destroy(resource);
+ }
+ wl_global_destroy(manager->global);
+ free(manager);
+}
+
+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);
+ wlr_gamma_control_manager_v1_destroy(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(struct wlr_gamma_control_manager_v1));
+ 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_list_init(&manager->resources);
+ wl_list_init(&manager->controls);
+
+ manager->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &manager->display_destroy);
+
+ return manager;
+}
diff --git a/types/wlr_gtk_primary_selection.c b/types/wlr_gtk_primary_selection.c
new file mode 100644
index 00000000..18bc624a
--- /dev/null
+++ b/types/wlr_gtk_primary_selection.c
@@ -0,0 +1,501 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wlr/types/wlr_gtk_primary_selection.h>
+#include <wlr/types/wlr_primary_selection.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/util/log.h>
+#include "gtk-primary-selection-protocol.h"
+#include "util/signal.h"
+
+#define DEVICE_MANAGER_VERSION 1
+
+static const struct gtk_primary_selection_offer_interface offer_impl;
+
+static struct wlr_gtk_primary_selection_device *device_from_offer_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource,
+ &gtk_primary_selection_offer_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_gtk_primary_selection_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 gtk_primary_selection_offer_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_gtk_primary_selection_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_gtk_primary_selection_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,
+ &gtk_primary_selection_offer_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));
+
+ gtk_primary_selection_device_send_data_offer(device_resource, resource);
+
+ char **p;
+ wl_array_for_each(p, &source->mime_types) {
+ gtk_primary_selection_offer_send_offer(resource, *p);
+ }
+
+ gtk_primary_selection_device_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 = (struct client_data_source *)wlr_source;
+ gtk_primary_selection_source_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 = (struct client_data_source *)wlr_source;
+ gtk_primary_selection_source_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 gtk_primary_selection_source_interface source_impl;
+
+static struct client_data_source *client_data_source_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource,
+ &gtk_primary_selection_source_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");
+ }
+
+ 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 gtk_primary_selection_source_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 gtk_primary_selection_device_interface device_impl;
+
+static struct wlr_gtk_primary_selection_device *device_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource,
+ &gtk_primary_selection_device_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_gtk_primary_selection_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;
+ }
+
+ // TODO: improve serial validation
+ if (device->seat->primary_selection_source != NULL &&
+ device->selection_serial - serial < UINT32_MAX / 2) {
+ wlr_log(WLR_DEBUG, "Rejecting set_selection request, invalid serial "
+ "(%"PRIu32" <= %"PRIu32")", serial, device->selection_serial);
+ return;
+ }
+ device->selection_serial = serial;
+
+ wlr_seat_set_primary_selection(device->seat, source);
+}
+
+static void device_handle_destroy(struct wl_client *client,
+ struct wl_resource *resource) {
+ wl_resource_destroy(resource);
+}
+
+static const struct gtk_primary_selection_device_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 {
+ gtk_primary_selection_device_send_selection(resource, NULL);
+ }
+}
+
+static void device_send_selection(
+ struct wlr_gtk_primary_selection_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_gtk_primary_selection_device *device);
+
+static void device_handle_seat_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_gtk_primary_selection_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_gtk_primary_selection_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_primary_selection(struct wl_listener *listener,
+ void *data) {
+ struct wlr_gtk_primary_selection_device *device =
+ wl_container_of(listener, device, seat_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_gtk_primary_selection_device *get_or_create_device(
+ struct wlr_gtk_primary_selection_device_manager *manager,
+ struct wlr_seat *seat) {
+ struct wlr_gtk_primary_selection_device *device;
+ wl_list_for_each(device, &manager->devices, link) {
+ if (device->seat == seat) {
+ return device;
+ }
+ }
+
+ device = calloc(1, sizeof(struct wlr_gtk_primary_selection_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_primary_selection.notify =
+ device_handle_seat_primary_selection;
+ wl_signal_add(&seat->events.primary_selection,
+ &device->seat_primary_selection);
+
+ return device;
+}
+
+static void device_destroy(struct wlr_gtk_primary_selection_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_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 gtk_primary_selection_device_manager_interface
+ device_manager_impl;
+
+struct wlr_gtk_primary_selection_device_manager *manager_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource,
+ &gtk_primary_selection_device_manager_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(struct client_data_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,
+ &gtk_primary_selection_source_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);
+}
+
+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_gtk_primary_selection_device_manager *manager =
+ manager_from_resource(manager_resource);
+
+ struct wlr_gtk_primary_selection_device *device =
+ get_or_create_device(manager, seat_client->seat);
+ if (device == NULL) {
+ wl_resource_post_no_memory(manager_resource);
+ return;
+ }
+
+ uint32_t version = wl_resource_get_version(manager_resource);
+ struct wl_resource *resource = wl_resource_create(client,
+ &gtk_primary_selection_device_interface, version, id);
+ if (resource == NULL) {
+ wl_resource_post_no_memory(manager_resource);
+ return;
+ }
+ wl_resource_set_implementation(resource, &device_impl, device,
+ device_handle_resource_destroy);
+ 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 gtk_primary_selection_device_manager_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 device_manager_handle_resource_destroy(
+ struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+
+static void primary_selection_device_manager_bind(struct wl_client *client,
+ void *data, uint32_t version, uint32_t id) {
+ struct wlr_gtk_primary_selection_device_manager *manager = data;
+
+ struct wl_resource *resource = wl_resource_create(client,
+ &gtk_primary_selection_device_manager_interface, version, id);
+ if (resource == NULL) {
+ wl_client_post_no_memory(client);
+ return;
+ }
+ wl_resource_set_implementation(resource, &device_manager_impl, manager,
+ device_manager_handle_resource_destroy);
+
+ wl_list_insert(&manager->resources, wl_resource_get_link(resource));
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_gtk_primary_selection_device_manager *manager =
+ wl_container_of(listener, manager, display_destroy);
+ wlr_gtk_primary_selection_device_manager_destroy(manager);
+}
+
+struct wlr_gtk_primary_selection_device_manager *
+ wlr_gtk_primary_selection_device_manager_create(
+ struct wl_display *display) {
+ struct wlr_gtk_primary_selection_device_manager *manager =
+ calloc(1, sizeof(struct wlr_gtk_primary_selection_device_manager));
+ if (manager == NULL) {
+ return NULL;
+ }
+ manager->global = wl_global_create(display,
+ &gtk_primary_selection_device_manager_interface, DEVICE_MANAGER_VERSION,
+ manager, primary_selection_device_manager_bind);
+ if (manager->global == NULL) {
+ free(manager);
+ return NULL;
+ }
+
+ wl_list_init(&manager->resources);
+ 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;
+}
+
+void wlr_gtk_primary_selection_device_manager_destroy(
+ struct wlr_gtk_primary_selection_device_manager *manager) {
+ if (manager == NULL) {
+ return;
+ }
+ wlr_signal_emit_safe(&manager->events.destroy, manager);
+ wl_list_remove(&manager->display_destroy.link);
+ struct wl_resource *resource, *resource_tmp;
+ wl_resource_for_each_safe(resource, resource_tmp, &manager->resources) {
+ wl_resource_destroy(resource);
+ }
+ wl_global_destroy(manager->global);
+ free(manager);
+}
diff --git a/types/wlr_idle.c b/types/wlr_idle.c
new file mode 100644
index 00000000..29367ceb
--- /dev/null
+++ b/types/wlr_idle.c
@@ -0,0 +1,248 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_idle.h>
+#include <wlr/util/log.h>
+#include "idle-protocol.h"
+#include "util/signal.h"
+
+static const struct org_kde_kwin_idle_timeout_interface idle_timeout_impl;
+
+static struct wlr_idle_timeout *idle_timeout_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource,
+ &org_kde_kwin_idle_timeout_interface, &idle_timeout_impl));
+ return wl_resource_get_user_data(resource);
+}
+
+static void idle_timeout_destroy(struct wlr_idle_timeout *timer) {
+ wl_list_remove(&timer->input_listener.link);
+ wl_list_remove(&timer->seat_destroy.link);
+ wl_event_source_remove(timer->idle_source);
+ wl_list_remove(&timer->link);
+ wl_resource_set_user_data(timer->resource, NULL);
+ free(timer);
+}
+
+static int idle_notify(void *data) {
+ struct wlr_idle_timeout *timer = data;
+ if (timer->idle_state) {
+ return 0;
+ }
+ timer->idle_state = true;
+ org_kde_kwin_idle_timeout_send_idle(timer->resource);
+ return 1;
+}
+
+static void handle_activity(struct wlr_idle_timeout *timer) {
+ if (!timer->enabled) {
+ return;
+ }
+
+ // in case the previous state was sleeping send a resume event and switch state
+ if (timer->idle_state) {
+ timer->idle_state = false;
+ org_kde_kwin_idle_timeout_send_resumed(timer->resource);
+ }
+
+ // rearm the timer
+ wl_event_source_timer_update(timer->idle_source, timer->timeout);
+ if (timer->timeout == 0) {
+ idle_notify(timer);
+ }
+}
+
+static void handle_timer_resource_destroy(struct wl_resource *timer_resource) {
+ struct wlr_idle_timeout *timer = idle_timeout_from_resource(timer_resource);
+ if (timer != NULL) {
+ idle_timeout_destroy(timer);
+ }
+}
+
+static void handle_seat_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_idle_timeout *timer = wl_container_of(listener, timer, seat_destroy);
+ if (timer != NULL) {
+ idle_timeout_destroy(timer);
+ }
+}
+
+static void release_idle_timeout(struct wl_client *client,
+ struct wl_resource *resource){
+ handle_timer_resource_destroy(resource);
+}
+
+static void simulate_activity(struct wl_client *client,
+ struct wl_resource *resource){
+ struct wlr_idle_timeout *timer = idle_timeout_from_resource(resource);
+ handle_activity(timer);
+}
+
+static const struct org_kde_kwin_idle_timeout_interface idle_timeout_impl = {
+ .release = release_idle_timeout,
+ .simulate_user_activity = simulate_activity,
+};
+
+static const struct org_kde_kwin_idle_interface idle_impl;
+
+static struct wlr_idle *idle_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &org_kde_kwin_idle_interface,
+ &idle_impl));
+ return wl_resource_get_user_data(resource);
+}
+
+static void handle_input_notification(struct wl_listener *listener, void *data) {
+ struct wlr_idle_timeout *timer =
+ wl_container_of(listener, timer, input_listener);
+ struct wlr_seat *seat = data;
+ if (timer->seat == seat) {
+ handle_activity(timer);
+ }
+}
+
+static void create_idle_timer(struct wl_client *client,
+ struct wl_resource *idle_resource, uint32_t id,
+ struct wl_resource *seat_resource, uint32_t timeout) {
+ struct wlr_idle *idle = idle_from_resource(idle_resource);
+ struct wlr_seat_client *client_seat =
+ wlr_seat_client_from_resource(seat_resource);
+
+ struct wlr_idle_timeout *timer =
+ calloc(1, sizeof(struct wlr_idle_timeout));
+ if (!timer) {
+ wl_resource_post_no_memory(idle_resource);
+ return;
+ }
+ timer->seat = client_seat->seat;
+ timer->timeout = timeout;
+ timer->idle_state = false;
+ timer->enabled = idle->enabled;
+ timer->resource = wl_resource_create(client,
+ &org_kde_kwin_idle_timeout_interface,
+ wl_resource_get_version(idle_resource), id);
+ if (timer->resource == NULL) {
+ free(timer);
+ wl_resource_post_no_memory(idle_resource);
+ return;
+ }
+ wl_resource_set_implementation(timer->resource, &idle_timeout_impl, timer,
+ handle_timer_resource_destroy);
+ wl_list_insert(&idle->idle_timers, &timer->link);
+
+ timer->seat_destroy.notify = handle_seat_destroy;
+ wl_signal_add(&timer->seat->events.destroy, &timer->seat_destroy);
+
+ timer->input_listener.notify = handle_input_notification;
+ wl_signal_add(&idle->events.activity_notify, &timer->input_listener);
+ // create the timer
+ timer->idle_source =
+ wl_event_loop_add_timer(idle->event_loop, idle_notify, timer);
+ if (timer->idle_source == NULL) {
+ wl_list_remove(&timer->link);
+ wl_list_remove(&timer->input_listener.link);
+ wl_list_remove(&timer->seat_destroy.link);
+ wl_resource_set_user_data(timer->resource, NULL);
+ free(timer);
+ wl_resource_post_no_memory(idle_resource);
+ return;
+ }
+ if (timer->enabled) {
+ // arm the timer
+ wl_event_source_timer_update(timer->idle_source, timer->timeout);
+ if (timer->timeout == 0) {
+ idle_notify(timer);
+ }
+ }
+}
+
+static const struct org_kde_kwin_idle_interface idle_impl = {
+ .get_idle_timeout = create_idle_timer,
+};
+
+void wlr_idle_set_enabled(struct wlr_idle *idle, struct wlr_seat *seat,
+ bool enabled) {
+ if (idle->enabled == enabled) {
+ return;
+ }
+ wlr_log(WLR_DEBUG, "%s idle timers for %s",
+ enabled ? "Enabling" : "Disabling",
+ seat ? seat->name : "all seats");
+ idle->enabled = enabled;
+ struct wlr_idle_timeout *timer;
+ wl_list_for_each(timer, &idle->idle_timers, link) {
+ if (seat != NULL && timer->seat != seat) {
+ continue;
+ }
+ int timeout = enabled ? timer->timeout : 0;
+ wl_event_source_timer_update(timer->idle_source, timeout);
+ timer->enabled = enabled;
+ }
+}
+
+static void idle_bind(struct wl_client *wl_client, void *data,
+ uint32_t version, uint32_t id) {
+ struct wlr_idle *idle = data;
+ assert(wl_client && idle);
+
+ struct wl_resource *wl_resource = wl_resource_create(wl_client,
+ &org_kde_kwin_idle_interface, version, id);
+ if (wl_resource == NULL) {
+ wl_client_post_no_memory(wl_client);
+ return;
+ }
+ wl_resource_set_implementation(wl_resource, &idle_impl, idle, NULL);
+}
+
+void wlr_idle_destroy(struct wlr_idle *idle) {
+ if (!idle) {
+ return;
+ }
+ wlr_signal_emit_safe(&idle->events.destroy, idle);
+ wl_list_remove(&idle->display_destroy.link);
+ struct wlr_idle_timeout *timer, *tmp;
+ wl_list_for_each_safe(timer, tmp, &idle->idle_timers, link) {
+ idle_timeout_destroy(timer);
+ }
+ wl_global_destroy(idle->global);
+ free(idle);
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_idle *idle = wl_container_of(listener, idle, display_destroy);
+ wlr_idle_destroy(idle);
+}
+
+struct wlr_idle *wlr_idle_create(struct wl_display *display) {
+ struct wlr_idle *idle = calloc(1, sizeof(struct wlr_idle));
+ if (!idle) {
+ return NULL;
+ }
+ wl_list_init(&idle->idle_timers);
+ wl_signal_init(&idle->events.activity_notify);
+ wl_signal_init(&idle->events.destroy);
+ idle->enabled = true;
+
+ idle->event_loop = wl_display_get_event_loop(display);
+ if (idle->event_loop == NULL) {
+ free(idle);
+ return NULL;
+ }
+
+ idle->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &idle->display_destroy);
+
+ idle->global = wl_global_create(display, &org_kde_kwin_idle_interface,
+ 1, idle, idle_bind);
+ if (idle->global == NULL){
+ wl_list_remove(&idle->display_destroy.link);
+ free(idle);
+ return NULL;
+ }
+ wlr_log(WLR_DEBUG, "idle manager created");
+ return idle;
+}
+
+void wlr_idle_notify_activity(struct wlr_idle *idle, struct wlr_seat *seat) {
+ wlr_signal_emit_safe(&idle->events.activity_notify, seat);
+}
diff --git a/types/wlr_idle_inhibit_v1.c b/types/wlr_idle_inhibit_v1.c
new file mode 100644
index 00000000..5f3d3d40
--- /dev/null
+++ b/types/wlr_idle_inhibit_v1.c
@@ -0,0 +1,194 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <wlr/util/log.h>
+#include <util/signal.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/types/wlr_idle_inhibit_v1.h>
+#include "wayland-util.h"
+#include "wayland-server.h"
+#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 *resource) {
+ assert(wl_resource_instance_of(resource, &zwp_idle_inhibit_manager_v1_interface,
+ &idle_inhibit_impl));
+ return wl_resource_get_user_data(resource);
+}
+
+static struct wlr_idle_inhibitor_v1 *
+wlr_idle_inhibitor_v1_from_resource(struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &zwp_idle_inhibitor_v1_interface,
+ &idle_inhibitor_impl));
+ return wl_resource_get_user_data(resource);
+}
+
+static void idle_inhibitor_v1_destroy(struct wlr_idle_inhibitor_v1 *inhibitor) {
+ if (!inhibitor) {
+ return;
+ }
+
+ wlr_signal_emit_safe(&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 *resource) {
+ struct wlr_idle_inhibitor_v1 *inhibitor =
+ wlr_idle_inhibitor_v1_from_resource(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 *manager_resource) {
+ wl_resource_destroy(manager_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 *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(resource);
+
+ struct wlr_idle_inhibitor_v1 *inhibitor =
+ calloc(1, sizeof(struct wlr_idle_inhibitor_v1));
+ if (!inhibitor) {
+ wl_client_post_no_memory(client);
+ return;
+ }
+
+ struct wl_resource *wl_resource = wl_resource_create(client,
+ &zwp_idle_inhibitor_v1_interface, 1, id);
+ if (!wl_resource) {
+ wl_client_post_no_memory(client);
+ free(inhibitor);
+ return;
+ }
+
+ inhibitor->resource = wl_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(wl_resource, &idle_inhibitor_impl,
+ inhibitor, idle_inhibitor_v1_handle_resource_destroy);
+
+ wl_list_insert(&manager->inhibitors, &inhibitor->link);
+ wlr_signal_emit_safe(&manager->events.new_inhibitor, inhibitor);
+}
+
+static void idle_inhibit_manager_v1_handle_resource_destroy(
+ struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+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 *idle_inhibit =
+ wl_container_of(listener, idle_inhibit, display_destroy);
+
+ wlr_idle_inhibit_v1_destroy(idle_inhibit);
+}
+
+static void idle_inhibit_bind(struct wl_client *wl_client, void *data,
+ uint32_t version, uint32_t id) {
+ struct wlr_idle_inhibit_manager_v1 *idle_inhibit = data;
+
+ struct wl_resource *wl_resource = wl_resource_create(wl_client,
+ &zwp_idle_inhibit_manager_v1_interface, version, id);
+
+ if (!wl_resource) {
+ wl_client_post_no_memory(wl_client);
+ return;
+ }
+
+ wl_list_insert(&idle_inhibit->resources, wl_resource_get_link(wl_resource));
+
+ wl_resource_set_implementation(wl_resource, &idle_inhibit_impl,
+ idle_inhibit, idle_inhibit_manager_v1_handle_resource_destroy);
+ wlr_log(WLR_DEBUG, "idle_inhibit bound");
+}
+
+void wlr_idle_inhibit_v1_destroy(struct wlr_idle_inhibit_manager_v1 *idle_inhibit) {
+ if (!idle_inhibit) {
+ return;
+ }
+
+ struct wlr_idle_inhibitor_v1 *inhibitor;
+ struct wlr_idle_inhibitor_v1 *tmp;
+ wl_list_for_each_safe(inhibitor, tmp, &idle_inhibit->inhibitors, link) {
+ idle_inhibitor_v1_destroy(inhibitor);
+ }
+
+ wlr_signal_emit_safe(&idle_inhibit->events.destroy, idle_inhibit);
+ wl_list_remove(&idle_inhibit->display_destroy.link);
+
+ struct wl_resource *resource;
+ struct wl_resource *tmp_resource;
+ wl_resource_for_each_safe(resource, tmp_resource, &idle_inhibit->resources) {
+ wl_resource_destroy(resource);
+ }
+
+ wl_global_destroy(idle_inhibit->global);
+ free(idle_inhibit);
+}
+
+struct wlr_idle_inhibit_manager_v1 *wlr_idle_inhibit_v1_create(struct wl_display *display) {
+ struct wlr_idle_inhibit_manager_v1 *idle_inhibit =
+ calloc(1, sizeof(struct wlr_idle_inhibit_manager_v1));
+
+ if (!idle_inhibit) {
+ return NULL;
+ }
+
+ wl_list_init(&idle_inhibit->resources);
+ wl_list_init(&idle_inhibit->inhibitors);
+ idle_inhibit->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &idle_inhibit->display_destroy);
+ wl_signal_init(&idle_inhibit->events.new_inhibitor);
+ wl_signal_init(&idle_inhibit->events.destroy);
+
+ idle_inhibit->global = wl_global_create(display,
+ &zwp_idle_inhibit_manager_v1_interface, 1,
+ idle_inhibit, idle_inhibit_bind);
+
+ if (!idle_inhibit->global) {
+ wl_list_remove(&idle_inhibit->display_destroy.link);
+ free(idle_inhibit);
+ return NULL;
+ }
+
+ wlr_log(WLR_DEBUG, "idle_inhibit manager created");
+
+ return idle_inhibit;
+}
diff --git a/types/wlr_input_device.c b/types/wlr_input_device.c
new file mode 100644
index 00000000..f7e5f04a
--- /dev/null
+++ b/types/wlr_input_device.c
@@ -0,0 +1,69 @@
+#define _POSIX_C_SOURCE 200809L
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-server.h>
+#include <wlr/interfaces/wlr_input_device.h>
+#include <wlr/interfaces/wlr_keyboard.h>
+#include <wlr/interfaces/wlr_pointer.h>
+#include <wlr/interfaces/wlr_switch.h>
+#include <wlr/interfaces/wlr_tablet_pad.h>
+#include <wlr/interfaces/wlr_tablet_tool.h>
+#include <wlr/interfaces/wlr_touch.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/util/log.h>
+#include "util/signal.h"
+
+void wlr_input_device_init(struct wlr_input_device *dev,
+ enum wlr_input_device_type type,
+ const struct wlr_input_device_impl *impl,
+ const char *name, int vendor, int product) {
+ dev->type = type;
+ dev->impl = impl;
+ dev->name = strdup(name);
+ dev->vendor = vendor;
+ dev->product = product;
+
+ wl_signal_init(&dev->events.destroy);
+}
+
+void wlr_input_device_destroy(struct wlr_input_device *dev) {
+ if (!dev) {
+ return;
+ }
+
+ wlr_signal_emit_safe(&dev->events.destroy, dev);
+
+ if (dev->_device) {
+ switch (dev->type) {
+ case WLR_INPUT_DEVICE_KEYBOARD:
+ wlr_keyboard_destroy(dev->keyboard);
+ break;
+ case WLR_INPUT_DEVICE_POINTER:
+ wlr_pointer_destroy(dev->pointer);
+ break;
+ case WLR_INPUT_DEVICE_SWITCH:
+ wlr_switch_destroy(dev->lid_switch);
+ break;
+ case WLR_INPUT_DEVICE_TOUCH:
+ wlr_touch_destroy(dev->touch);
+ break;
+ case WLR_INPUT_DEVICE_TABLET_TOOL:
+ wlr_tablet_destroy(dev->tablet);
+ break;
+ case WLR_INPUT_DEVICE_TABLET_PAD:
+ wlr_tablet_pad_destroy(dev->tablet_pad);
+ break;
+ default:
+ wlr_log(WLR_DEBUG, "Warning: leaking memory %p %p %d",
+ dev->_device, dev, dev->type);
+ break;
+ }
+ }
+ free(dev->name);
+ free(dev->output_name);
+ if (dev->impl && dev->impl->destroy) {
+ dev->impl->destroy(dev);
+ } else {
+ free(dev);
+ }
+}
diff --git a/types/wlr_input_inhibitor.c b/types/wlr_input_inhibitor.c
new file mode 100644
index 00000000..fa692d6c
--- /dev/null
+++ b/types/wlr_input_inhibitor.c
@@ -0,0 +1,153 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include "wlr/types/wlr_input_inhibitor.h"
+#include "wlr-input-inhibitor-unstable-v1-protocol.h"
+#include "util/signal.h"
+
+static const struct zwlr_input_inhibit_manager_v1_interface inhibit_manager_implementation;
+static struct zwlr_input_inhibitor_v1_interface input_inhibitor_implementation;
+
+static struct wlr_input_inhibit_manager *input_inhibit_manager_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource,
+ &zwlr_input_inhibit_manager_v1_interface,
+ &inhibit_manager_implementation)
+ || wl_resource_instance_of(resource,
+ &zwlr_input_inhibitor_v1_interface,
+ &input_inhibitor_implementation));
+ return wl_resource_get_user_data(resource);
+}
+
+static void input_inhibit_manager_deactivate(
+ struct wlr_input_inhibit_manager *manager) {
+ if (manager->active_client == NULL && manager->active_inhibitor == NULL) {
+ return;
+ }
+ manager->active_client = NULL;
+ manager->active_inhibitor = NULL;
+ wlr_signal_emit_safe(&manager->events.deactivate, manager);
+}
+
+static void input_inhibitor_destroy(struct wl_client *client,
+ struct wl_resource *resource) {
+ struct wlr_input_inhibit_manager *manager =
+ input_inhibit_manager_from_resource(resource);
+ input_inhibit_manager_deactivate(manager);
+ wl_resource_destroy(resource);
+}
+
+static void input_inhibitor_resource_destroy(struct wl_resource *resource) {
+ struct wlr_input_inhibit_manager *manager =
+ input_inhibit_manager_from_resource(resource);
+ input_inhibit_manager_deactivate(manager);
+}
+
+static struct zwlr_input_inhibitor_v1_interface input_inhibitor_implementation = {
+ .destroy = input_inhibitor_destroy,
+};
+
+static void inhibit_manager_get_inhibitor(struct wl_client *client,
+ struct wl_resource *resource, uint32_t id) {
+ struct wlr_input_inhibit_manager *manager =
+ input_inhibit_manager_from_resource(resource);
+ if (manager->active_client || manager->active_inhibitor) {
+ wl_resource_post_error(resource,
+ ZWLR_INPUT_INHIBIT_MANAGER_V1_ERROR_ALREADY_INHIBITED,
+ "this compositor already has input inhibited");
+ return;
+ }
+
+ struct wl_resource *wl_resource = wl_resource_create(client,
+ &zwlr_input_inhibitor_v1_interface,
+ wl_resource_get_version(resource), id);
+ if (!wl_resource) {
+ wl_client_post_no_memory(client);
+ }
+ wl_resource_set_implementation(wl_resource, &input_inhibitor_implementation,
+ manager, input_inhibitor_resource_destroy);
+
+ manager->active_client = client;
+ manager->active_inhibitor = wl_resource;
+
+ wlr_signal_emit_safe(&manager->events.activate, manager);
+}
+
+static const struct zwlr_input_inhibit_manager_v1_interface inhibit_manager_implementation = {
+ .get_inhibitor = inhibit_manager_get_inhibitor
+};
+
+static void input_manager_resource_destroy(struct wl_resource *resource) {
+ struct wlr_input_inhibit_manager *manager =
+ input_inhibit_manager_from_resource(resource);
+ struct wl_client *client = wl_resource_get_client(resource);
+ if (manager->active_client == client) {
+ input_inhibit_manager_deactivate(manager);
+ }
+}
+
+static void inhibit_manager_bind(struct wl_client *wl_client, void *data,
+ uint32_t version, uint32_t id) {
+ struct wlr_input_inhibit_manager *manager = data;
+ assert(wl_client && manager);
+
+ struct wl_resource *wl_resource = wl_resource_create(wl_client,
+ &zwlr_input_inhibit_manager_v1_interface, version, id);
+ if (wl_resource == NULL) {
+ wl_client_post_no_memory(wl_client);
+ return;
+ }
+ wl_resource_set_implementation(wl_resource,
+ &inhibit_manager_implementation, manager,
+ input_manager_resource_destroy);
+}
+
+void wlr_input_inhibit_manager_destroy(
+ struct wlr_input_inhibit_manager *manager) {
+ if (!manager) {
+ return;
+ }
+ if (manager->active_client) {
+ input_inhibitor_destroy(manager->active_client,
+ manager->active_inhibitor);
+ }
+ wlr_signal_emit_safe(&manager->events.destroy, manager);
+ wl_list_remove(&manager->display_destroy.link);
+ wl_global_destroy(manager->global);
+ free(manager);
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_input_inhibit_manager *manager =
+ wl_container_of(listener, manager, display_destroy);
+ wlr_input_inhibit_manager_destroy(manager);
+}
+
+struct wlr_input_inhibit_manager *wlr_input_inhibit_manager_create(
+ struct wl_display *display) {
+ // TODO: Client destroy
+ struct wlr_input_inhibit_manager *manager =
+ calloc(1, sizeof(struct wlr_input_inhibit_manager));
+ if (!manager) {
+ return NULL;
+ }
+
+ manager->global = wl_global_create(display,
+ &zwlr_input_inhibit_manager_v1_interface,
+ 1, manager, inhibit_manager_bind);
+ if (manager->global == NULL){
+ wl_list_remove(&manager->display_destroy.link);
+ free(manager);
+ return NULL;
+ }
+
+ wl_signal_init(&manager->events.activate);
+ wl_signal_init(&manager->events.deactivate);
+ 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_input_method_v2.c b/types/wlr_input_method_v2.c
new file mode 100644
index 00000000..c68ff88b
--- /dev/null
+++ b/types/wlr_input_method_v2.c
@@ -0,0 +1,298 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#include <assert.h>
+#include <string.h>
+#include <wayland-util.h>
+#include <wlr/types/wlr_input_method_v2.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/util/log.h>
+#include <xkbcommon/xkbcommon.h>
+#include "input-method-unstable-v2-protocol.h"
+#include "util/signal.h"
+
+static const struct zwp_input_method_v2_interface input_method_impl;
+
+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 void input_method_destroy(struct wlr_input_method_v2 *input_method) {
+ wlr_signal_emit_safe(&input_method->events.destroy, input_method);
+ wl_list_remove(&input_method->seat_destroy.link);
+ free(input_method->pending.commit_text);
+ free(input_method->pending.preedit.text);
+ free(input_method->current.commit_text);
+ free(input_method->current.preedit.text);
+ 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;
+ }
+ input_method->current = input_method->pending;
+ input_method->current_serial = serial;
+ struct wlr_input_method_v2_state default_state = {0};
+ input_method->pending = default_state;
+ wlr_signal_emit_safe(&input_method->events.commit, (void*)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);
+}
+
+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);
+}
+
+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;
+}
+
+static void im_get_input_popup_surface(struct wl_client *client,
+ struct wl_resource *resource, uint32_t id,
+ struct wl_resource *surface) {
+ wlr_log(WLR_INFO, "Stub: zwp_input_method_v2::get_input_popup_surface");
+}
+
+
+static void im_grab_keyboard(struct wl_client *client,
+ struct wl_resource *resource, uint32_t keyboard) {
+ wlr_log(WLR_INFO, "Stub: zwp_input_method_v2::grab_keyboard");
+}
+
+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++;
+}
+
+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_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_input_method_v2 *input_method = wl_container_of(listener,
+ input_method, seat_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_input_method_v2 *input_method = calloc(1,
+ sizeof(struct wlr_input_method_v2));
+ if (!input_method) {
+ wl_client_post_no_memory(client);
+ return;
+ }
+ wl_signal_init(&input_method->events.commit);
+ wl_signal_init(&input_method->events.destroy);
+ 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) {
+ free(input_method);
+ wl_client_post_no_memory(client);
+ return;
+ }
+ wl_resource_set_implementation(im_resource, &input_method_impl,
+ input_method, input_method_resource_destroy);
+
+ struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat);
+ wl_signal_add(&seat_client->events.destroy,
+ &input_method->seat_destroy);
+ input_method->seat_destroy.notify =
+ input_method_handle_seat_destroy;
+
+ input_method->resource = im_resource;
+ input_method->seat = seat_client->seat;
+ wl_list_insert(&im_manager->input_methods,
+ wl_resource_get_link(input_method->resource));
+ wlr_signal_emit_safe(&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_unbind(struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+static void input_method_manager_bind(struct wl_client *wl_client, void *data,
+ uint32_t version, uint32_t id) {
+ assert(wl_client);
+ 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, input_method_manager_unbind);
+ wl_list_insert(&im_manager->bound_resources,
+ wl_resource_get_link(bound_resource));
+}
+
+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);
+ wlr_input_method_manager_v2_destroy(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(struct wlr_input_method_manager_v2));
+ 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->bound_resources);
+ wl_list_init(&im_manager->input_methods);
+
+ im_manager->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &im_manager->display_destroy);
+
+ im_manager->global = wl_global_create(display,
+ &zwp_input_method_manager_v2_interface, 1, im_manager,
+ input_method_manager_bind);
+ return im_manager;
+}
+
+void wlr_input_method_manager_v2_destroy(
+ struct wlr_input_method_manager_v2 *manager) {
+ wlr_signal_emit_safe(&manager->events.destroy, manager);
+ wl_list_remove(&manager->display_destroy.link);
+
+ struct wl_resource *resource, *resource_tmp;
+ wl_resource_for_each_safe(resource, resource_tmp,
+ &manager->bound_resources) {
+ wl_resource_destroy(resource);
+ }
+ struct wlr_input_method_v2 *im, *im_tmp;
+ wl_list_for_each_safe(im, im_tmp, &manager->input_methods, link) {
+ wl_resource_destroy(im->resource);
+ }
+ wl_global_destroy(manager->global);
+ free(manager);
+}
diff --git a/types/wlr_keyboard.c b/types/wlr_keyboard.c
new file mode 100644
index 00000000..89c7ca50
--- /dev/null
+++ b/types/wlr_keyboard.c
@@ -0,0 +1,237 @@
+#include "util/array.h"
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-server.h>
+#include <wlr/interfaces/wlr_keyboard.h>
+#include <wlr/types/wlr_keyboard.h>
+#include <wlr/util/log.h>
+#include "util/signal.h"
+
+static 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.
+ */
+static 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_mod_mask_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;
+}
+
+static void keyboard_key_update(struct wlr_keyboard *keyboard,
+ struct wlr_event_keyboard_key *event) {
+ bool found = false;
+ size_t i = 0;
+ for (; i < keyboard->num_keycodes; ++i) {
+ if (keyboard->keycodes[i] == event->keycode) {
+ found = true;
+ break;
+ }
+ }
+
+ if (event->state == WLR_KEY_PRESSED && !found &&
+ keyboard->num_keycodes < WLR_KEYBOARD_KEYS_CAP) {
+ keyboard->keycodes[keyboard->num_keycodes++] = event->keycode;
+ }
+ if (event->state == WLR_KEY_RELEASED && found) {
+ keyboard->keycodes[i] = 0;
+ keyboard->num_keycodes = push_zeroes_to_end(keyboard->keycodes, WLR_KEYBOARD_KEYS_CAP);
+ }
+
+ 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) {
+ wlr_signal_emit_safe(&keyboard->events.modifiers, keyboard);
+ }
+}
+
+void wlr_keyboard_notify_key(struct wlr_keyboard *keyboard,
+ struct wlr_event_keyboard_key *event) {
+ if (keyboard->xkb_state == NULL) {
+ return;
+ }
+
+ keyboard_key_update(keyboard, event);
+ wlr_signal_emit_safe(&keyboard->events.key, event);
+
+ if (event->update_state) {
+ uint32_t keycode = event->keycode + 8;
+ xkb_state_update_key(keyboard->xkb_state, keycode,
+ event->state == WLR_KEY_PRESSED ? XKB_KEY_DOWN : XKB_KEY_UP);
+ }
+ keyboard_led_update(keyboard);
+
+ bool updated = keyboard_modifier_update(keyboard);
+ if (updated) {
+ wlr_signal_emit_safe(&keyboard->events.modifiers, keyboard);
+ }
+}
+
+void wlr_keyboard_init(struct wlr_keyboard *kb,
+ const struct wlr_keyboard_impl *impl) {
+ kb->impl = impl;
+ 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);
+
+ // Sane defaults
+ kb->repeat_info.rate = 25;
+ kb->repeat_info.delay = 600;
+}
+
+void wlr_keyboard_destroy(struct wlr_keyboard *kb) {
+ if (kb == NULL) {
+ return;
+ }
+ xkb_state_unref(kb->xkb_state);
+ xkb_keymap_unref(kb->keymap);
+ free(kb->keymap_string);
+ if (kb->impl && kb->impl->destroy) {
+ kb->impl->destroy(kb);
+ } else {
+ wl_list_remove(&kb->events.key.listener_list);
+ free(kb);
+ }
+}
+
+void wlr_keyboard_led_update(struct wlr_keyboard *kb, uint32_t leds) {
+ if (kb->impl && kb->impl->led_update) {
+ kb->impl->led_update(kb, leds);
+ }
+}
+
+void wlr_keyboard_set_keymap(struct wlr_keyboard *kb,
+ struct xkb_keymap *keymap) {
+ xkb_keymap_unref(kb->keymap);
+ kb->keymap = xkb_keymap_ref(keymap);
+
+ xkb_state_unref(kb->xkb_state);
+ kb->xkb_state = xkb_state_new(kb->keymap);
+ if (kb->xkb_state == NULL) {
+ wlr_log(WLR_ERROR, "Failed to create XKB state");
+ goto err;
+ }
+
+ 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]);
+ }
+
+ char *tmp_keymap_string = xkb_keymap_get_as_string(kb->keymap,
+ XKB_KEYMAP_FORMAT_TEXT_V1);
+ if (tmp_keymap_string == NULL) {
+ wlr_log(WLR_ERROR, "Failed to get string version of keymap");
+ goto err;
+ }
+ free(kb->keymap_string);
+ kb->keymap_string = tmp_keymap_string;
+ kb->keymap_size = strlen(kb->keymap_string) + 1;
+
+ 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);
+
+ wlr_signal_emit_safe(&kb->events.keymap, kb);
+ return;
+
+err:
+ xkb_state_unref(kb->xkb_state);
+ kb->xkb_state = NULL;
+ xkb_keymap_unref(keymap);
+ kb->keymap = NULL;
+ free(kb->keymap_string);
+ kb->keymap_string = NULL;
+}
+
+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;
+ wlr_signal_emit_safe(&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;
+}
diff --git a/types/wlr_layer_shell_v1.c b/types/wlr_layer_shell_v1.c
new file mode 100644
index 00000000..aa959d89
--- /dev/null
+++ b/types/wlr_layer_shell_v1.c
@@ -0,0 +1,564 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_layer_shell_v1.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/types/wlr_xdg_shell.h>
+#include <wlr/util/log.h>
+#include "util/signal.h"
+#include "wlr-layer-shell-unstable-v1-protocol.h"
+
+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 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);
+}
+
+static struct wlr_layer_surface_v1 *layer_surface_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;
+
+bool wlr_surface_is_layer_surface(struct wlr_surface *surface) {
+ return surface->role == &layer_surface_role;
+}
+
+struct wlr_layer_surface_v1 *wlr_layer_surface_v1_from_wlr_surface(
+ struct wlr_surface *surface) {
+ assert(wlr_surface_is_layer_surface(surface));
+ return (struct wlr_layer_surface_v1 *)surface->role_data;
+}
+
+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_handle_ack_configure(struct wl_client *client,
+ struct wl_resource *resource, uint32_t serial) {
+ struct wlr_layer_surface_v1 *surface = layer_surface_from_resource(resource);
+
+ bool found = false;
+ struct wlr_layer_surface_v1_configure *configure, *tmp;
+ wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) {
+ if (configure->serial < serial) {
+ layer_surface_configure_destroy(configure);
+ } else if (configure->serial == serial) {
+ found = true;
+ break;
+ } else {
+ break;
+ }
+ }
+ if (!found) {
+ wl_resource_post_error(resource,
+ ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SURFACE_STATE,
+ "wrong configure serial: %u", serial);
+ return;
+ }
+
+ if (surface->acked_configure) {
+ layer_surface_configure_destroy(surface->acked_configure);
+ }
+ surface->acked_configure = configure;
+ wl_list_remove(&configure->link);
+ wl_list_init(&configure->link);
+}
+
+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 = layer_surface_from_resource(resource);
+ surface->client_pending.desired_width = width;
+ surface->client_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 %d", anchor);
+ }
+ struct wlr_layer_surface_v1 *surface = layer_surface_from_resource(resource);
+ surface->client_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 = layer_surface_from_resource(resource);
+ surface->client_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 = layer_surface_from_resource(resource);
+ surface->client_pending.margin.top = top;
+ surface->client_pending.margin.right = right;
+ surface->client_pending.margin.bottom = bottom;
+ surface->client_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 = layer_surface_from_resource(resource);
+ surface->client_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 =
+ layer_surface_from_resource(layer_resource);
+ struct wlr_xdg_surface *popup_surface =
+ wlr_xdg_surface_from_popup_resource(popup_resource);
+
+ assert(popup_surface->role == WLR_XDG_SURFACE_ROLE_POPUP);
+ struct wlr_xdg_popup *popup = popup_surface->popup;
+ popup->parent = parent->surface;
+ wl_list_insert(&parent->popups, &popup->link);
+ wlr_signal_emit_safe(&parent->events.new_popup, popup);
+}
+
+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,
+};
+
+static void layer_surface_unmap(struct wlr_layer_surface_v1 *surface) {
+ // TODO: probably need to ungrab before this event
+ wlr_signal_emit_safe(&surface->events.unmap, surface);
+
+ struct wlr_layer_surface_v1_configure *configure, *tmp;
+ wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) {
+ layer_surface_configure_destroy(configure);
+ }
+
+ surface->configured = surface->mapped = false;
+ surface->configure_serial = 0;
+ if (surface->configure_idle) {
+ wl_event_source_remove(surface->configure_idle);
+ surface->configure_idle = NULL;
+ }
+ surface->configure_next_serial = 0;
+}
+
+static void layer_surface_destroy(struct wlr_layer_surface_v1 *surface) {
+ if (surface->configured && surface->mapped) {
+ layer_surface_unmap(surface);
+ }
+ wlr_signal_emit_safe(&surface->events.destroy, surface);
+ wl_resource_set_user_data(surface->resource, NULL);
+ surface->surface->role_data = NULL;
+ wl_list_remove(&surface->surface_destroy.link);
+ wl_list_remove(&surface->link);
+ free(surface->namespace);
+ free(surface);
+}
+
+static void layer_surface_resource_destroy(struct wl_resource *resource) {
+ struct wlr_layer_surface_v1 *surface =
+ layer_surface_from_resource(resource);
+ if (surface != NULL) {
+ layer_surface_destroy(surface);
+ }
+}
+
+static bool layer_surface_state_changed(struct wlr_layer_surface_v1 *surface) {
+ struct wlr_layer_surface_v1_state *state;
+ if (wl_list_empty(&surface->configure_list)) {
+ if (surface->acked_configure) {
+ state = &surface->acked_configure->state;
+ } else if (!surface->configured) {
+ return true;
+ } else {
+ state = &surface->current;
+ }
+ } else {
+ struct wlr_layer_surface_v1_configure *configure =
+ wl_container_of(surface->configure_list.prev, configure, link);
+ state = &configure->state;
+ }
+
+ bool changed = state->actual_width != surface->server_pending.actual_width
+ || state->actual_height != surface->server_pending.actual_height;
+ return changed;
+}
+
+void wlr_layer_surface_v1_configure(struct wlr_layer_surface_v1 *surface,
+ uint32_t width, uint32_t height) {
+ surface->server_pending.actual_width = width;
+ surface->server_pending.actual_height = height;
+ if (layer_surface_state_changed(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(struct wlr_layer_surface_v1_configure));
+ if (configure == NULL) {
+ wl_client_post_no_memory(wl_resource_get_client(surface->resource));
+ return;
+ }
+ surface->configure_next_serial = wl_display_next_serial(display);
+ wl_list_insert(surface->configure_list.prev, &configure->link);
+ configure->state.actual_width = width;
+ configure->state.actual_height = height;
+ configure->serial = surface->configure_next_serial;
+ zwlr_layer_surface_v1_send_configure(surface->resource,
+ configure->serial, configure->state.actual_width,
+ configure->state.actual_height);
+ }
+}
+
+void wlr_layer_surface_v1_close(struct wlr_layer_surface_v1 *surface) {
+ if (surface->closed) {
+ return;
+ }
+ surface->closed = true;
+ layer_surface_unmap(surface);
+ zwlr_layer_surface_v1_send_closed(surface->resource);
+}
+
+static void layer_surface_role_commit(struct wlr_surface *wlr_surface) {
+ struct wlr_layer_surface_v1 *surface =
+ wlr_layer_surface_v1_from_wlr_surface(wlr_surface);
+ if (surface == NULL) {
+ return;
+ }
+
+ if (surface->closed) {
+ // Ignore commits after the compositor has closed it
+ return;
+ }
+
+ if (surface->acked_configure) {
+ struct wlr_layer_surface_v1_configure *configure =
+ surface->acked_configure;
+ surface->configured = true;
+ surface->configure_serial = configure->serial;
+ surface->current.actual_width = configure->state.actual_width;
+ surface->current.actual_height = configure->state.actual_height;
+ layer_surface_configure_destroy(configure);
+ surface->acked_configure = NULL;
+ }
+
+ if (wlr_surface_has_buffer(surface->surface) && !surface->configured) {
+ wl_resource_post_error(surface->resource,
+ ZWLR_LAYER_SHELL_V1_ERROR_ALREADY_CONSTRUCTED,
+ "layer_surface has never been configured");
+ return;
+ }
+
+ surface->current.anchor = surface->client_pending.anchor;
+ surface->current.exclusive_zone = surface->client_pending.exclusive_zone;
+ surface->current.margin = surface->client_pending.margin;
+ surface->current.keyboard_interactive =
+ surface->client_pending.keyboard_interactive;
+ surface->current.desired_width = surface->client_pending.desired_width;
+ surface->current.desired_height = surface->client_pending.desired_height;
+
+ if (!surface->added) {
+ surface->added = true;
+ wlr_signal_emit_safe(&surface->shell->events.new_surface,
+ surface);
+ // either the compositor found a suitable output or it must
+ // have closed the surface
+ assert(surface->output || surface->closed);
+ }
+ if (surface->configured && wlr_surface_has_buffer(surface->surface) &&
+ !surface->mapped) {
+ surface->mapped = true;
+ wlr_signal_emit_safe(&surface->events.map, surface);
+ }
+ if (surface->configured && !wlr_surface_has_buffer(surface->surface) &&
+ surface->mapped) {
+ layer_surface_unmap(surface);
+ }
+}
+
+static const struct wlr_surface_role layer_surface_role = {
+ .name = "zwlr_layer_surface_v1",
+ .commit = layer_surface_role_commit,
+};
+
+static void handle_surface_destroyed(struct wl_listener *listener,
+ void *data) {
+ struct wlr_layer_surface_v1 *layer_surface =
+ wl_container_of(listener, layer_surface, surface_destroy);
+ layer_surface_destroy(layer_surface);
+}
+
+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);
+
+ struct wlr_layer_surface_v1 *surface =
+ calloc(1, sizeof(struct wlr_layer_surface_v1));
+ if (surface == NULL) {
+ wl_client_post_no_memory(wl_client);
+ return;
+ }
+
+ if (!wlr_surface_set_role(wlr_surface, &layer_surface_role, surface,
+ 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->layer = layer;
+ if (layer > ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY) {
+ free(surface);
+ wl_resource_post_error(client_resource,
+ ZWLR_LAYER_SHELL_V1_ERROR_INVALID_LAYER,
+ "Invalid layer %d", layer);
+ return;
+ }
+ surface->namespace = strdup(namespace);
+ if (surface->namespace == NULL) {
+ free(surface);
+ wl_client_post_no_memory(wl_client);
+ return;
+ }
+ surface->resource = wl_resource_create(wl_client,
+ &zwlr_layer_surface_v1_interface,
+ wl_resource_get_version(client_resource),
+ id);
+ if (surface->resource == NULL) {
+ free(surface->namespace);
+ free(surface);
+ wl_client_post_no_memory(wl_client);
+ return;
+ }
+
+ wl_list_init(&surface->configure_list);
+ wl_list_init(&surface->popups);
+
+ wl_signal_init(&surface->events.destroy);
+ wl_signal_init(&surface->events.map);
+ wl_signal_init(&surface->events.unmap);
+ wl_signal_init(&surface->events.new_popup);
+
+ wl_signal_add(&surface->surface->events.destroy,
+ &surface->surface_destroy);
+ surface->surface_destroy.notify = handle_surface_destroyed;
+
+ wlr_log(WLR_DEBUG, "new layer_surface %p (res %p)",
+ surface, surface->resource);
+ wl_resource_set_implementation(surface->resource,
+ &layer_surface_implementation, surface, layer_surface_resource_destroy);
+ wl_list_insert(&shell->surfaces, &surface->link);
+}
+
+static const struct zwlr_layer_shell_v1_interface layer_shell_implementation = {
+ .get_layer_surface = layer_shell_handle_get_layer_surface,
+};
+
+static void client_handle_destroy(struct wl_resource *resource) {
+ struct wl_client *client = wl_resource_get_client(resource);
+ struct wlr_layer_shell_v1 *shell = layer_shell_from_resource(resource);
+ struct wlr_layer_surface_v1 *surface, *tmp = NULL;
+ wl_list_for_each_safe(surface, tmp, &shell->surfaces, link) {
+ if (wl_resource_get_client(surface->resource) == client) {
+ layer_surface_destroy(surface);
+ }
+ }
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+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;
+ assert(wl_client && layer_shell);
+
+ 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, client_handle_destroy);
+ wl_list_insert(&layer_shell->resources, wl_resource_get_link(resource));
+}
+
+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);
+ wlr_layer_shell_v1_destroy(layer_shell);
+}
+
+struct wlr_layer_shell_v1 *wlr_layer_shell_v1_create(struct wl_display *display) {
+ struct wlr_layer_shell_v1 *layer_shell =
+ calloc(1, sizeof(struct wlr_layer_shell_v1));
+ if (!layer_shell) {
+ return NULL;
+ }
+
+ wl_list_init(&layer_shell->resources);
+ wl_list_init(&layer_shell->surfaces);
+
+ struct wl_global *global = wl_global_create(display,
+ &zwlr_layer_shell_v1_interface, 1, 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;
+}
+
+void wlr_layer_shell_v1_destroy(struct wlr_layer_shell_v1 *layer_shell) {
+ if (!layer_shell) {
+ return;
+ }
+ struct wl_resource *resource, *tmp;
+ wl_resource_for_each_safe(resource, tmp, &layer_shell->resources) {
+ wl_resource_destroy(resource);
+ }
+ wlr_signal_emit_safe(&layer_shell->events.destroy, layer_shell);
+ wl_list_remove(&layer_shell->display_destroy.link);
+ wl_global_destroy(layer_shell->global);
+ free(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);
+}
+
+static void xdg_surface_for_each_surface(struct wlr_xdg_surface *surface,
+ int x, int y, wlr_surface_iterator_func_t iterator, void *user_data) {
+ struct layer_surface_iterator_data data = {
+ .user_iterator = iterator,
+ .user_data = user_data,
+ .x = x, .y = y,
+ };
+ wlr_surface_for_each_surface(
+ surface->surface, layer_surface_iterator, &data);
+
+ struct wlr_xdg_popup *popup_state;
+ wl_list_for_each(popup_state, &surface->popups, link) {
+ struct wlr_xdg_surface *popup = popup_state->base;
+ if (!popup->configured) {
+ continue;
+ }
+
+ double popup_sx = popup_state->geometry.x - popup_state->base->geometry.x;
+ double popup_sy = popup_state->geometry.y - popup_state->base->geometry.y;
+
+ xdg_surface_for_each_surface(popup,
+ x + popup_sx,
+ y + popup_sy,
+ iterator, user_data);
+ }
+}
+
+static void layer_surface_for_each_surface(struct wlr_layer_surface_v1 *surface,
+ int x, int y, wlr_surface_iterator_func_t iterator, void *user_data) {
+ struct layer_surface_iterator_data data = {
+ .user_iterator = iterator,
+ .user_data = user_data,
+ .x = x, .y = y,
+ };
+ wlr_surface_for_each_surface(surface->surface,
+ layer_surface_iterator, &data);
+
+ struct wlr_xdg_popup *popup_state;
+ wl_list_for_each(popup_state, &surface->popups, link) {
+ struct wlr_xdg_surface *popup = popup_state->base;
+ if (!popup->configured) {
+ continue;
+ }
+
+ double popup_sx, popup_sy;
+ popup_sx = popup->popup->geometry.x - popup->geometry.x;
+ popup_sy = popup->popup->geometry.y - popup->geometry.y;
+
+ xdg_surface_for_each_surface(popup,
+ popup_sx, popup_sy, iterator, 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) {
+ layer_surface_for_each_surface(surface, 0, 0, iterator, user_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_xdg_popup *popup_state;
+ wl_list_for_each(popup_state, &surface->popups, link) {
+ struct wlr_xdg_surface *popup = popup_state->base;
+
+ double popup_sx = popup_state->geometry.x - popup->geometry.x;
+ double popup_sy = popup_state->geometry.y - popup->geometry.y;
+
+ struct wlr_surface *sub = wlr_xdg_surface_surface_at(popup,
+ sx - popup_sx,
+ sy - popup_sy,
+ sub_x, sub_y);
+ if (sub != NULL) {
+ return sub;
+ }
+ }
+
+ return wlr_surface_surface_at(surface->surface, sx, sy, sub_x, sub_y);
+}
diff --git a/types/wlr_linux_dmabuf_v1.c b/types/wlr_linux_dmabuf_v1.c
new file mode 100644
index 00000000..eb7b2f9e
--- /dev/null
+++ b/types/wlr_linux_dmabuf_v1.c
@@ -0,0 +1,509 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_linux_dmabuf_v1.h>
+#include <wlr/util/log.h>
+#include "linux-dmabuf-unstable-v1-protocol.h"
+#include "util/signal.h"
+
+#define LINUX_DMABUF_VERSION 3
+
+static void buffer_handle_destroy(struct wl_client *client,
+ struct wl_resource *resource) {
+ wl_resource_destroy(resource);
+}
+
+static const struct wl_buffer_interface buffer_impl = {
+ .destroy = buffer_handle_destroy,
+};
+
+bool wlr_dmabuf_v1_resource_is_buffer(struct wl_resource *buffer_resource) {
+ if (!wl_resource_instance_of(buffer_resource, &wl_buffer_interface,
+ &buffer_impl)) {
+ return false;
+ }
+
+ struct wlr_dmabuf_v1_buffer *buffer =
+ wl_resource_get_user_data(buffer_resource);
+ if (buffer && buffer->buffer_resource && !buffer->params_resource &&
+ buffer->buffer_resource == buffer_resource) {
+ return true;
+ }
+
+ return false;
+}
+
+struct wlr_dmabuf_v1_buffer *wlr_dmabuf_v1_buffer_from_buffer_resource(
+ struct wl_resource *buffer_resource) {
+ assert(wl_resource_instance_of(buffer_resource, &wl_buffer_interface,
+ &buffer_impl));
+
+ struct wlr_dmabuf_v1_buffer *buffer =
+ wl_resource_get_user_data(buffer_resource);
+ assert(buffer);
+ assert(buffer->buffer_resource);
+ assert(!buffer->params_resource);
+ assert(buffer->buffer_resource == buffer_resource);
+
+ return buffer;
+}
+
+static void linux_dmabuf_buffer_destroy(struct wlr_dmabuf_v1_buffer *buffer) {
+ wlr_dmabuf_attributes_finish(&buffer->attributes);
+ free(buffer);
+}
+
+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_dmabuf_v1_buffer *buffer =
+ wlr_dmabuf_v1_buffer_from_params_resource(params_resource);
+
+ if (!buffer) {
+ 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 (buffer->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",
+ buffer->attributes.fd[plane_idx], plane_idx);
+ close(fd);
+ return;
+ }
+
+ uint64_t modifier = ((uint64_t)modifier_hi << 32) | modifier_lo;
+ if (buffer->has_modifier && modifier != buffer->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, buffer->attributes.modifier);
+ close(fd);
+ return;
+ }
+
+ buffer->attributes.modifier = modifier;
+ buffer->has_modifier = true;
+
+ buffer->attributes.fd[plane_idx] = fd;
+ buffer->attributes.offset[plane_idx] = offset;
+ buffer->attributes.stride[plane_idx] = stride;
+ buffer->attributes.n_planes++;
+}
+
+static void buffer_handle_resource_destroy(struct wl_resource *buffer_resource) {
+ struct wlr_dmabuf_v1_buffer *buffer =
+ wlr_dmabuf_v1_buffer_from_buffer_resource(buffer_resource);
+ linux_dmabuf_buffer_destroy(buffer);
+}
+
+static bool check_import_dmabuf(struct wlr_dmabuf_v1_buffer *buffer) {
+ struct wlr_texture *texture =
+ wlr_texture_from_dmabuf(buffer->renderer, &buffer->attributes);
+ if (texture == NULL) {
+ return false;
+ }
+
+ // We can import the image, good. No need to keep it since wlr_surface will
+ // import it again on commit.
+ wlr_texture_destroy(texture);
+ return true;
+}
+
+static void params_create_common(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) {
+ if (!wl_resource_get_user_data(params_resource)) {
+ 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_v1_buffer *buffer =
+ wlr_dmabuf_v1_buffer_from_params_resource(params_resource);
+
+ /* Switch the linux_dmabuf_buffer object from params resource to
+ * eventually wl_buffer resource. */
+ wl_resource_set_user_data(buffer->params_resource, NULL);
+ buffer->params_resource = NULL;
+
+ if (!buffer->attributes.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 (buffer->attributes.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 ((buffer->attributes.fd[3] >= 0 || buffer->attributes.fd[2] >= 0) &&
+ (buffer->attributes.fd[2] == -1 || buffer->attributes.fd[1] == -1)) {
+ wl_resource_post_error(params_resource,
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
+ "gap in dmabuf planes");
+ goto err_out;
+ }
+
+ buffer->attributes.width = width;
+ buffer->attributes.height = height;
+ buffer->attributes.format = format;
+ buffer->attributes.flags = flags;
+
+ 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 < buffer->attributes.n_planes; i++) {
+ if ((uint64_t)buffer->attributes.offset[i]
+ + buffer->attributes.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)buffer->attributes.offset[i]
+ + (uint64_t)buffer->attributes.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(buffer->attributes.fd[i], 0, SEEK_END);
+ if (size == -1) {
+ // Skip checks if kernel does no support seek on buffer
+ continue;
+ }
+ if (buffer->attributes.offset[i] > size) {
+ wl_resource_post_error(params_resource,
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
+ "invalid offset %i for plane %d",
+ buffer->attributes.offset[i], i);
+ goto err_out;
+ }
+
+ if (buffer->attributes.offset[i] + buffer->attributes.stride[i] > size ||
+ buffer->attributes.stride[i] == 0) {
+ wl_resource_post_error(params_resource,
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
+ "invalid stride %i for plane %d",
+ buffer->attributes.stride[i], i);
+ goto err_out;
+ }
+
+ // planes > 0 might be subsampled according to fourcc format
+ if (i == 0 && buffer->attributes.offset[i] +
+ buffer->attributes.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;
+ }
+ }
+
+ /* reject unknown flags */
+ if (buffer->attributes.flags & ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT) {
+ wl_resource_post_error(params_resource,
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT,
+ "Unknown dmabuf flags %"PRIu32, buffer->attributes.flags);
+ goto err_out;
+ }
+
+ /* Check if dmabuf is usable */
+ if (!check_import_dmabuf(buffer)) {
+ goto err_failed;
+ }
+
+ buffer->buffer_resource = wl_resource_create(client, &wl_buffer_interface,
+ 1, buffer_id);
+ if (!buffer->buffer_resource) {
+ wl_resource_post_no_memory(params_resource);
+ goto err_failed;
+ }
+
+ wl_resource_set_implementation(buffer->buffer_resource,
+ &buffer_impl, buffer, buffer_handle_resource_destroy);
+
+ /* 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->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:
+ linux_dmabuf_buffer_destroy(buffer);
+}
+
+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(client, 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(client, params_resource, buffer_id, width, height,
+ format, flags);
+}
+
+static const struct zwp_linux_buffer_params_v1_interface
+ linux_buffer_params_impl = {
+ .destroy = params_destroy,
+ .add = params_add,
+ .create = params_create,
+ .create_immed = params_create_immed,
+};
+
+struct wlr_dmabuf_v1_buffer *wlr_dmabuf_v1_buffer_from_params_resource(
+ struct wl_resource *params_resource) {
+ assert(wl_resource_instance_of(params_resource,
+ &zwp_linux_buffer_params_v1_interface,
+ &linux_buffer_params_impl));
+
+ struct wlr_dmabuf_v1_buffer *buffer =
+ wl_resource_get_user_data(params_resource);
+ assert(buffer);
+ assert(buffer->params_resource);
+ assert(!buffer->buffer_resource);
+ assert(buffer->params_resource == params_resource);
+
+ return buffer;
+}
+
+static void handle_params_destroy(struct wl_resource *params_resource) {
+ /* Check for NULL since wlr_dmabuf_v1_buffer_from_params_resource will choke */
+ if (!wl_resource_get_user_data(params_resource)) {
+ return;
+ }
+
+ struct wlr_dmabuf_v1_buffer *buffer =
+ wlr_dmabuf_v1_buffer_from_params_resource(params_resource);
+ linux_dmabuf_buffer_destroy(buffer);
+}
+
+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 =
+ wlr_linux_dmabuf_v1_from_resource(linux_dmabuf_resource);
+
+ uint32_t version = wl_resource_get_version(linux_dmabuf_resource);
+ struct wlr_dmabuf_v1_buffer *buffer = calloc(1, sizeof *buffer);
+ if (!buffer) {
+ goto err;
+ }
+
+ for (int i = 0; i < WLR_DMABUF_MAX_PLANES; i++) {
+ buffer->attributes.fd[i] = -1;
+ }
+
+ buffer->renderer = linux_dmabuf->renderer;
+ buffer->params_resource = wl_resource_create(client,
+ &zwp_linux_buffer_params_v1_interface, version, params_id);
+ if (!buffer->params_resource) {
+ goto err_free;
+ }
+
+ wl_resource_set_implementation(buffer->params_resource,
+ &linux_buffer_params_impl, buffer, handle_params_destroy);
+ return;
+
+err_free:
+ free(buffer);
+err:
+ wl_resource_post_no_memory(linux_dmabuf_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,
+};
+
+struct wlr_linux_dmabuf_v1 *wlr_linux_dmabuf_v1_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_send_formats(struct wlr_linux_dmabuf_v1 *linux_dmabuf,
+ struct wl_resource *resource, uint32_t version) {
+ struct wlr_renderer *renderer = linux_dmabuf->renderer;
+ uint64_t modifier_invalid = DRM_FORMAT_MOD_INVALID;
+ int *formats = NULL;
+ int num_formats = wlr_renderer_get_dmabuf_formats(renderer, &formats);
+ if (num_formats < 0) {
+ return;
+ }
+
+ for (int i = 0; i < num_formats; i++) {
+ uint64_t *modifiers = NULL;
+ int num_modifiers = wlr_renderer_get_dmabuf_modifiers(renderer,
+ formats[i], &modifiers);
+ if (num_modifiers < 0) {
+ return;
+ }
+ /* send DRM_FORMAT_MOD_INVALID token when no modifiers are supported
+ * for this format */
+ if (num_modifiers == 0) {
+ num_modifiers = 1;
+ modifiers = &modifier_invalid;
+ }
+ for (int j = 0; j < num_modifiers; j++) {
+ if (version >= ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) {
+ uint32_t modifier_lo = modifiers[j] & 0xFFFFFFFF;
+ uint32_t modifier_hi = modifiers[j] >> 32;
+ zwp_linux_dmabuf_v1_send_modifier(resource,
+ formats[i],
+ modifier_hi,
+ modifier_lo);
+ } else if (modifiers[j] == DRM_FORMAT_MOD_LINEAR ||
+ modifiers == &modifier_invalid) {
+ zwp_linux_dmabuf_v1_send_format(resource, formats[i]);
+ }
+ }
+ if (modifiers != &modifier_invalid) {
+ free(modifiers);
+ }
+ }
+ free(formats);
+}
+
+static void linux_dmabuf_resource_destroy(struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+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, linux_dmabuf_resource_destroy);
+ wl_list_insert(&linux_dmabuf->resources, wl_resource_get_link(resource));
+ linux_dmabuf_send_formats(linux_dmabuf, resource, version);
+}
+
+void wlr_linux_dmabuf_v1_destroy(struct wlr_linux_dmabuf_v1 *linux_dmabuf) {
+ if (!linux_dmabuf) {
+ return;
+ }
+
+ wlr_signal_emit_safe(&linux_dmabuf->events.destroy, linux_dmabuf);
+
+ wl_list_remove(&linux_dmabuf->display_destroy.link);
+ wl_list_remove(&linux_dmabuf->renderer_destroy.link);
+
+ struct wl_resource *resource, *tmp;
+ wl_resource_for_each_safe(resource, tmp, &linux_dmabuf->resources) {
+ wl_resource_destroy(resource);
+ }
+
+ 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);
+ wlr_linux_dmabuf_v1_destroy(linux_dmabuf);
+}
+
+static void handle_renderer_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_linux_dmabuf_v1 *linux_dmabuf =
+ wl_container_of(listener, linux_dmabuf, renderer_destroy);
+ wlr_linux_dmabuf_v1_destroy(linux_dmabuf);
+}
+
+struct wlr_linux_dmabuf_v1 *wlr_linux_dmabuf_v1_create(struct wl_display *display,
+ struct wlr_renderer *renderer) {
+ struct wlr_linux_dmabuf_v1 *linux_dmabuf =
+ calloc(1, sizeof(struct wlr_linux_dmabuf_v1));
+ if (linux_dmabuf == NULL) {
+ wlr_log(WLR_ERROR, "could not create simple dmabuf manager");
+ return NULL;
+ }
+ linux_dmabuf->renderer = renderer;
+
+ wl_list_init(&linux_dmabuf->resources);
+ wl_signal_init(&linux_dmabuf->events.destroy);
+
+ linux_dmabuf->global =
+ wl_global_create(display, &zwp_linux_dmabuf_v1_interface,
+ LINUX_DMABUF_VERSION, linux_dmabuf, linux_dmabuf_bind);
+ if (!linux_dmabuf->global) {
+ wlr_log(WLR_ERROR, "could not create linux dmabuf v1 wl global");
+ free(linux_dmabuf);
+ return NULL;
+ }
+
+ linux_dmabuf->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &linux_dmabuf->display_destroy);
+
+ linux_dmabuf->renderer_destroy.notify = handle_renderer_destroy;
+ wl_signal_add(&renderer->events.destroy, &linux_dmabuf->renderer_destroy);
+
+ return linux_dmabuf;
+}
diff --git a/types/wlr_list.c b/types/wlr_list.c
new file mode 100644
index 00000000..2be0912a
--- /dev/null
+++ b/types/wlr_list.c
@@ -0,0 +1,109 @@
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <wlr/types/wlr_list.h>
+
+bool wlr_list_init(struct wlr_list *list) {
+ list->capacity = 10;
+ list->length = 0;
+ list->items = malloc(sizeof(void *) * list->capacity);
+ if (list->items == NULL) {
+ return false;
+ }
+ return true;
+}
+
+static bool list_resize(struct wlr_list *list) {
+ if (list->length == list->capacity) {
+ void *new_items = realloc(list->items,
+ sizeof(void *) * (list->capacity + 10));
+ if (!new_items) {
+ return false;
+ }
+ list->capacity += 10;
+ list->items = new_items;
+ }
+ return true;
+}
+
+void wlr_list_finish(struct wlr_list *list) {
+ free(list->items);
+}
+
+void wlr_list_for_each(struct wlr_list *list, void (*callback)(void *item)) {
+ for (size_t i = 0; i < list->length; i++) {
+ callback(list->items[i]);
+ }
+}
+
+ssize_t wlr_list_push(struct wlr_list *list, void *item) {
+ if (!list_resize(list)) {
+ return -1;
+ }
+ list->items[list->length++] = item;
+ return list->length;
+}
+
+ssize_t wlr_list_insert(struct wlr_list *list, size_t index, void *item) {
+ if (!list_resize(list)) {
+ return -1;
+ }
+ memmove(&list->items[index + 1], &list->items[index],
+ sizeof(void *) * (list->length - index));
+ list->length++;
+ list->items[index] = item;
+ return list->length;
+}
+
+void wlr_list_del(struct wlr_list *list, size_t index) {
+ list->length--;
+ memmove(&list->items[index], &list->items[index + 1],
+ sizeof(void *) * (list->length - index));
+}
+
+void *wlr_list_pop(struct wlr_list *list) {
+ if (list->length == 0) {
+ return NULL;
+ }
+ void *last = list->items[list->length - 1];
+ wlr_list_del(list, list->length - 1);
+ return last;
+}
+
+void *wlr_list_peek(struct wlr_list *list) {
+ if (list->length == 0) {
+ return NULL;
+ }
+ return list->items[list->length - 1];
+}
+
+ssize_t wlr_list_cat(struct wlr_list *list, const struct wlr_list *source) {
+ size_t old_len = list->length;
+ size_t i;
+ for (i = 0; i < source->length; ++i) {
+ if (wlr_list_push(list, source->items[i]) == -1) {
+ list->length = old_len;
+ return -1;
+ }
+ }
+ return list->length;
+}
+
+void wlr_list_qsort(struct wlr_list *list,
+ int compare(const void *left, const void *right)) {
+ qsort(list->items, list->length, sizeof(void *), compare);
+}
+
+ssize_t wlr_list_find(struct wlr_list *list,
+ int compare(const void *item, const void *data), const void *data) {
+ for (size_t i = 0; i < list->length; i++) {
+ void *item = list->items[i];
+ if (compare(item, data) == 0) {
+ return i;
+ }
+ }
+ return -1;
+}
diff --git a/types/wlr_matrix.c b/types/wlr_matrix.c
new file mode 100644
index 00000000..2c896313
--- /dev/null
+++ b/types/wlr_matrix.c
@@ -0,0 +1,169 @@
+#include <math.h>
+#include <string.h>
+#include <wayland-server-protocol.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_output.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]);
+}
+
+// Equivalent to glOrtho(0, width, 0, height, 1, -1) with the transform applied
+void wlr_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.c b/types/wlr_output.c
new file mode 100644
index 00000000..9a21196e
--- /dev/null
+++ b/types/wlr_output.c
@@ -0,0 +1,921 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <tgmath.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wlr/interfaces/wlr_output.h>
+#include <wlr/render/interface.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/util/log.h>
+#include <wlr/util/region.h>
+#include "util/signal.h"
+
+#define OUTPUT_VERSION 3
+
+static void output_send_to_resource(struct wl_resource *resource) {
+ struct wlr_output *output = wlr_output_from_resource(resource);
+ const uint32_t version = wl_resource_get_version(resource);
+ if (version >= WL_OUTPUT_GEOMETRY_SINCE_VERSION) {
+ wl_output_send_geometry(resource, output->lx, output->ly,
+ output->phys_width, output->phys_height, output->subpixel,
+ output->make, output->model, output->transform);
+ }
+ if (version >= WL_OUTPUT_MODE_SINCE_VERSION) {
+ struct wlr_output_mode *mode;
+ wl_list_for_each(mode, &output->modes, link) {
+ uint32_t flags = mode->flags & WL_OUTPUT_MODE_PREFERRED;
+ if (output->current_mode == mode) {
+ flags |= WL_OUTPUT_MODE_CURRENT;
+ }
+ wl_output_send_mode(resource, flags, mode->width, mode->height,
+ mode->refresh);
+ }
+
+ if (wl_list_length(&output->modes) == 0) {
+ // Output has no mode, send the current width/height
+ wl_output_send_mode(resource, WL_OUTPUT_MODE_CURRENT,
+ output->width, output->height, output->refresh);
+ }
+ }
+ if (version >= WL_OUTPUT_SCALE_SINCE_VERSION) {
+ wl_output_send_scale(resource, (uint32_t)ceil(output->scale));
+ }
+ if (version >= WL_OUTPUT_DONE_SINCE_VERSION) {
+ wl_output_send_done(resource);
+ }
+}
+
+static void output_send_current_mode_to_resource(
+ struct wl_resource *resource) {
+ struct wlr_output *output = wlr_output_from_resource(resource);
+ const uint32_t version = wl_resource_get_version(resource);
+ if (version < WL_OUTPUT_MODE_SINCE_VERSION) {
+ return;
+ }
+ if (output->current_mode != NULL) {
+ struct wlr_output_mode *mode = output->current_mode;
+ uint32_t flags = mode->flags & WL_OUTPUT_MODE_PREFERRED;
+ wl_output_send_mode(resource, flags | 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);
+ }
+ 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) {
+ 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);
+ wl_list_insert(&output->resources, wl_resource_get_link(resource));
+ output_send_to_resource(resource);
+}
+
+void wlr_output_create_global(struct wlr_output *output) {
+ if (output->global != NULL) {
+ return;
+ }
+ output->global = wl_global_create(output->display,
+ &wl_output_interface, OUTPUT_VERSION, output, output_bind);
+ if (output->global == NULL) {
+ wlr_log(WLR_ERROR, "Failed to allocate wl_output global");
+ }
+}
+
+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_global_destroy(output->global);
+ output->global = NULL;
+}
+
+void wlr_output_update_enabled(struct wlr_output *output, bool enabled) {
+ if (output->enabled == enabled) {
+ return;
+ }
+
+ output->enabled = enabled;
+ wlr_signal_emit_safe(&output->events.enable, output);
+}
+
+static void output_update_matrix(struct wlr_output *output) {
+ wlr_matrix_projection(output->transform_matrix, output->width,
+ output->height, output->transform);
+}
+
+bool wlr_output_enable(struct wlr_output *output, bool enable) {
+ if (output->enabled == enable) {
+ return true;
+ }
+
+ if (output->impl->enable) {
+ return output->impl->enable(output, enable);
+ }
+ return false;
+}
+
+bool wlr_output_set_mode(struct wlr_output *output,
+ struct wlr_output_mode *mode) {
+ if (!output->impl || !output->impl->set_mode) {
+ return false;
+ }
+ return output->impl->set_mode(output, mode);
+}
+
+bool wlr_output_set_custom_mode(struct wlr_output *output, int32_t width,
+ int32_t height, int32_t refresh) {
+ if (!output->impl || !output->impl->set_custom_mode) {
+ return false;
+ }
+ return output->impl->set_custom_mode(output, width, height, refresh);
+}
+
+void wlr_output_update_mode(struct wlr_output *output,
+ struct wlr_output_mode *mode) {
+ output->current_mode = mode;
+ wlr_output_update_custom_mode(output, mode->width, mode->height,
+ mode->refresh);
+}
+
+void wlr_output_update_custom_mode(struct wlr_output *output, int32_t width,
+ int32_t height, int32_t refresh) {
+ if (output->width == width && output->height == height &&
+ output->refresh == refresh) {
+ return;
+ }
+
+ output->width = width;
+ output->height = height;
+ output_update_matrix(output);
+
+ output->refresh = refresh;
+
+ struct wl_resource *resource;
+ wl_resource_for_each(resource, &output->resources) {
+ output_send_current_mode_to_resource(resource);
+ }
+
+ wlr_signal_emit_safe(&output->events.mode, output);
+}
+
+void wlr_output_set_transform(struct wlr_output *output,
+ enum wl_output_transform transform) {
+ output->impl->transform(output, transform);
+ output_update_matrix(output);
+
+ // TODO: only send geometry and done
+ struct wl_resource *resource;
+ wl_resource_for_each(resource, &output->resources) {
+ output_send_to_resource(resource);
+ }
+
+ wlr_signal_emit_safe(&output->events.transform, output);
+}
+
+void wlr_output_set_position(struct wlr_output *output, int32_t lx,
+ int32_t ly) {
+ if (lx == output->lx && ly == output->ly) {
+ return;
+ }
+
+ output->lx = lx;
+ output->ly = ly;
+
+ // TODO: only send geometry and done
+ struct wl_resource *resource;
+ wl_resource_for_each(resource, &output->resources) {
+ output_send_to_resource(resource);
+ }
+}
+
+void wlr_output_set_scale(struct wlr_output *output, float scale) {
+ if (output->scale == scale) {
+ return;
+ }
+
+ output->scale = scale;
+
+ // TODO: only send mode and done
+ struct wl_resource *resource;
+ wl_resource_for_each(resource, &output->resources) {
+ output_send_to_resource(resource);
+ }
+
+ wlr_signal_emit_safe(&output->events.scale, output);
+}
+
+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_init(struct wlr_output *output, struct wlr_backend *backend,
+ const struct wlr_output_impl *impl, struct wl_display *display) {
+ assert(impl->make_current && impl->swap_buffers && impl->transform);
+ if (impl->set_cursor || impl->move_cursor) {
+ assert(impl->set_cursor && impl->move_cursor);
+ }
+ output->backend = backend;
+ output->impl = impl;
+ output->display = display;
+ wl_list_init(&output->modes);
+ output->transform = WL_OUTPUT_TRANSFORM_NORMAL;
+ output->scale = 1;
+ wl_list_init(&output->cursors);
+ wl_list_init(&output->resources);
+ wl_signal_init(&output->events.frame);
+ wl_signal_init(&output->events.needs_swap);
+ wl_signal_init(&output->events.swap_buffers);
+ wl_signal_init(&output->events.present);
+ wl_signal_init(&output->events.enable);
+ wl_signal_init(&output->events.mode);
+ wl_signal_init(&output->events.scale);
+ wl_signal_init(&output->events.transform);
+ wl_signal_init(&output->events.destroy);
+ pixman_region32_init(&output->damage);
+
+ const char *no_hardware_cursors = getenv("WLR_NO_HARDWARE_CURSORS");
+ if (no_hardware_cursors != NULL && strcmp(no_hardware_cursors, "1") == 0) {
+ wlr_log(WLR_DEBUG,
+ "WLR_NO_HARDWARE_CURSORS set, forcing software cursors");
+ output->software_cursor_locks = 1;
+ }
+
+ output->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &output->display_destroy);
+
+ output->frame_pending = true;
+}
+
+void wlr_output_destroy(struct wlr_output *output) {
+ if (!output) {
+ return;
+ }
+
+ wl_list_remove(&output->display_destroy.link);
+ wlr_output_destroy_global(output);
+
+ wlr_signal_emit_safe(&output->events.destroy, output);
+
+ // 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);
+ }
+
+ if (output->idle_frame != NULL) {
+ wl_event_source_remove(output->idle_frame);
+ }
+
+ pixman_region32_fini(&output->damage);
+
+ 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;
+}
+
+bool wlr_output_make_current(struct wlr_output *output, int *buffer_age) {
+ return output->impl->make_current(output, buffer_age);
+}
+
+bool wlr_output_preferred_read_format(struct wlr_output *output,
+ enum wl_shm_format *fmt) {
+ if (!wlr_output_make_current(output, NULL)) {
+ return false;
+ }
+
+ struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend);
+ if (!renderer->impl->preferred_read_format || !renderer->impl->read_pixels) {
+ return false;
+ }
+ *fmt = renderer->impl->preferred_read_format(renderer);
+ return true;
+}
+
+bool wlr_output_swap_buffers(struct wlr_output *output, struct timespec *when,
+ pixman_region32_t *damage) {
+ if (output->frame_pending) {
+ wlr_log(WLR_ERROR, "Tried to swap buffers when a frame is pending");
+ return false;
+ }
+ if (output->idle_frame != NULL) {
+ wl_event_source_remove(output->idle_frame);
+ output->idle_frame = NULL;
+ }
+
+ struct timespec now;
+ if (when == NULL) {
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ when = &now;
+ }
+
+ struct wlr_output_event_swap_buffers event = {
+ .output = output,
+ .when = when,
+ .damage = damage,
+ };
+ wlr_signal_emit_safe(&output->events.swap_buffers, &event);
+
+ pixman_region32_t render_damage;
+ pixman_region32_init(&render_damage);
+ pixman_region32_union_rect(&render_damage, &render_damage, 0, 0,
+ output->width, output->height);
+ if (damage != NULL) {
+ // Damage tracking supported
+ pixman_region32_intersect(&render_damage, &render_damage, damage);
+ }
+
+ if (!output->impl->swap_buffers(output, damage ? &render_damage : NULL)) {
+ pixman_region32_fini(&render_damage);
+ return false;
+ }
+
+ pixman_region32_fini(&render_damage);
+
+ struct wlr_output_cursor *cursor;
+ wl_list_for_each(cursor, &output->cursors, link) {
+ if (!cursor->enabled || !cursor->visible || cursor->surface == NULL) {
+ continue;
+ }
+ wlr_surface_send_frame_done(cursor->surface, when);
+ }
+
+ output->frame_pending = true;
+ output->needs_swap = false;
+ pixman_region32_clear(&output->damage);
+ return true;
+}
+
+void wlr_output_send_frame(struct wlr_output *output) {
+ output->frame_pending = false;
+ wlr_signal_emit_safe(&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 && output->impl->schedule_frame) {
+ // Ask the backend to send a frame event when appropriate
+ if (output->impl->schedule_frame(output)) {
+ output->frame_pending = true;
+ }
+ }
+}
+
+void wlr_output_schedule_frame(struct wlr_output *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
+ struct wl_event_loop *ev = wl_display_get_event_loop(output->display);
+ output->idle_frame =
+ wl_event_loop_add_idle(ev, schedule_frame_handle_idle_timer, output);
+}
+
+void wlr_output_send_present(struct wlr_output *output,
+ struct wlr_output_event_present *event) {
+ struct wlr_output_event_present _event = {0};
+ if (event == NULL) {
+ event = &_event;
+ }
+
+ event->output = output;
+
+ struct timespec now;
+ if (event->when == NULL) {
+ clockid_t clock = wlr_backend_get_presentation_clock(output->backend);
+ errno = 0;
+ if (clock_gettime(clock, &now) != 0) {
+ wlr_log_errno(WLR_ERROR, "failed to send output present event: "
+ "failed to read clock");
+ return;
+ }
+ event->when = &now;
+ }
+
+ wlr_signal_emit_safe(&output->events.present, event);
+}
+
+bool wlr_output_set_gamma(struct wlr_output *output, size_t size,
+ const uint16_t *r, const uint16_t *g, const uint16_t *b) {
+ if (!output->impl->set_gamma) {
+ return false;
+ }
+ return output->impl->set_gamma(output, size, r, g, b);
+}
+
+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);
+}
+
+bool wlr_output_export_dmabuf(struct wlr_output *output,
+ struct wlr_dmabuf_attributes *attribs) {
+ if (!output->impl->export_dmabuf) {
+ return false;
+ }
+ return output->impl->export_dmabuf(output, attribs);
+}
+
+void wlr_output_update_needs_swap(struct wlr_output *output) {
+ output->needs_swap = true;
+ wlr_signal_emit_safe(&output->events.needs_swap, output);
+}
+
+void wlr_output_damage_whole(struct wlr_output *output) {
+ int width, height;
+ wlr_output_transformed_resolution(output, &width, &height);
+
+ pixman_region32_union_rect(&output->damage, &output->damage, 0, 0,
+ width, height);
+ wlr_output_update_needs_swap(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);
+}
+
+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) {
+ assert(output->impl->set_cursor);
+ output->impl->set_cursor(output, NULL, 1,
+ WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, true);
+ 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.
+}
+
+static void output_scissor(struct wlr_output *output, pixman_box32_t *rect) {
+ struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend);
+ assert(renderer);
+
+ struct wlr_box box = {
+ .x = rect->x1,
+ .y = rect->y1,
+ .width = rect->x2 - rect->x1,
+ .height = rect->y2 - rect->y1,
+ };
+
+ int ow, oh;
+ wlr_output_transformed_resolution(output, &ow, &oh);
+
+ enum wl_output_transform transform =
+ wlr_output_transform_invert(output->transform);
+ wlr_box_transform(&box, &box, transform, ow, oh);
+
+ wlr_renderer_scissor(renderer, &box);
+}
+
+static void output_cursor_get_box(struct wlr_output_cursor *cursor,
+ struct wlr_box *box);
+
+static void output_cursor_render(struct wlr_output_cursor *cursor,
+ pixman_region32_t *damage) {
+ struct wlr_renderer *renderer =
+ wlr_backend_get_renderer(cursor->output->backend);
+ assert(renderer);
+
+ struct wlr_texture *texture = cursor->texture;
+ if (cursor->surface != NULL) {
+ texture = wlr_surface_get_texture(cursor->surface);
+ }
+ if (texture == NULL) {
+ return;
+ }
+
+ struct wlr_box box;
+ output_cursor_get_box(cursor, &box);
+
+ pixman_region32_t surface_damage;
+ pixman_region32_init(&surface_damage);
+ pixman_region32_union_rect(&surface_damage, &surface_damage, box.x, box.y,
+ box.width, box.height);
+ pixman_region32_intersect(&surface_damage, &surface_damage, damage);
+ if (!pixman_region32_not_empty(&surface_damage)) {
+ goto surface_damage_finish;
+ }
+
+ float matrix[9];
+ wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0,
+ cursor->output->transform_matrix);
+
+ int nrects;
+ pixman_box32_t *rects = pixman_region32_rectangles(&surface_damage, &nrects);
+ for (int i = 0; i < nrects; ++i) {
+ output_scissor(cursor->output, &rects[i]);
+ wlr_render_texture_with_matrix(renderer, texture, matrix, 1.0f);
+ }
+ wlr_renderer_scissor(renderer, NULL);
+
+surface_damage_finish:
+ pixman_region32_fini(&surface_damage);
+}
+
+void wlr_output_render_software_cursors(struct wlr_output *output,
+ pixman_region32_t *damage) {
+ int width, height;
+ wlr_output_transformed_resolution(output, &width, &height);
+
+ pixman_region32_t render_damage;
+ pixman_region32_init(&render_damage);
+ pixman_region32_union_rect(&render_damage, &render_damage, 0, 0,
+ width, height);
+ if (damage != NULL) {
+ // Damage tracking supported
+ pixman_region32_intersect(&render_damage, &render_damage, damage);
+ }
+
+ if (pixman_region32_not_empty(&render_damage)) {
+ struct wlr_output_cursor *cursor;
+ wl_list_for_each(cursor, &output->cursors, link) {
+ if (!cursor->enabled || !cursor->visible ||
+ output->hardware_cursor == cursor) {
+ continue;
+ }
+ output_cursor_render(cursor, &render_damage);
+ }
+ }
+
+ pixman_region32_fini(&render_damage);
+}
+
+
+/**
+ * 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;
+}
+
+static void output_cursor_damage_whole(struct wlr_output_cursor *cursor) {
+ struct wlr_box box;
+ output_cursor_get_box(cursor, &box);
+ pixman_region32_union_rect(&cursor->output->damage, &cursor->output->damage,
+ box.x, box.y, box.width, box.height);
+ wlr_output_update_needs_swap(cursor->output);
+}
+
+static void output_cursor_reset(struct wlr_output_cursor *cursor) {
+ if (cursor->output->hardware_cursor != cursor) {
+ output_cursor_damage_whole(cursor);
+ }
+ if (cursor->surface != NULL) {
+ wl_list_remove(&cursor->surface_commit.link);
+ wl_list_remove(&cursor->surface_destroy.link);
+ cursor->surface = NULL;
+ }
+}
+
+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;
+ bool visible =
+ wlr_box_intersection(&intersection, &output_box, &cursor_box);
+
+ if (cursor->surface != NULL) {
+ if (cursor->visible && !visible) {
+ wlr_surface_send_leave(cursor->surface, cursor->output);
+ }
+ if (!cursor->visible && visible) {
+ wlr_surface_send_enter(cursor->surface, cursor->output);
+ }
+ }
+
+ cursor->visible = visible;
+}
+
+static bool output_cursor_attempt_hardware(struct wlr_output_cursor *cursor) {
+ int32_t scale = cursor->output->scale;
+ enum wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
+ struct wlr_texture *texture = cursor->texture;
+ if (cursor->surface != NULL) {
+ texture = wlr_surface_get_texture(cursor->surface);
+ scale = cursor->surface->current.scale;
+ transform = cursor->surface->current.transform;
+ }
+
+ if (cursor->output->software_cursor_locks > 0) {
+ return false;
+ }
+
+ struct wlr_output_cursor *hwcur = cursor->output->hardware_cursor;
+ if (cursor->output->impl->set_cursor && (hwcur == NULL || hwcur == cursor)) {
+ // If the cursor was hidden or was a software cursor, the hardware
+ // cursor position is outdated
+ assert(cursor->output->impl->move_cursor);
+ cursor->output->impl->move_cursor(cursor->output,
+ (int)cursor->x, (int)cursor->y);
+ if (cursor->output->impl->set_cursor(cursor->output, texture,
+ scale, transform, cursor->hotspot_x, cursor->hotspot_y, true)) {
+ cursor->output->hardware_cursor = cursor;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool wlr_output_cursor_set_image(struct wlr_output_cursor *cursor,
+ const uint8_t *pixels, int32_t stride, uint32_t width, uint32_t height,
+ int32_t hotspot_x, int32_t hotspot_y) {
+ struct wlr_renderer *renderer =
+ wlr_backend_get_renderer(cursor->output->backend);
+ assert(renderer);
+
+ output_cursor_reset(cursor);
+
+ cursor->width = width;
+ cursor->height = height;
+ cursor->hotspot_x = hotspot_x;
+ cursor->hotspot_y = hotspot_y;
+ output_cursor_update_visible(cursor);
+
+ wlr_texture_destroy(cursor->texture);
+ cursor->texture = NULL;
+
+ cursor->enabled = false;
+ if (pixels != NULL) {
+ cursor->texture = wlr_texture_from_pixels(renderer,
+ WL_SHM_FORMAT_ARGB8888, stride, width, height, pixels);
+ if (cursor->texture == NULL) {
+ return false;
+ }
+ cursor->enabled = true;
+ }
+
+ 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;
+}
+
+static void output_cursor_commit(struct wlr_output_cursor *cursor,
+ bool update_hotspot) {
+ if (cursor->output->hardware_cursor != cursor) {
+ output_cursor_damage_whole(cursor);
+ }
+
+ struct wlr_surface *surface = cursor->surface;
+ assert(surface != NULL);
+
+ // Some clients commit a cursor surface with a NULL buffer to hide it.
+ cursor->enabled = wlr_surface_has_buffer(surface);
+ cursor->width = surface->current.width * cursor->output->scale;
+ cursor->height = surface->current.height * cursor->output->scale;
+ output_cursor_update_visible(cursor);
+ if (update_hotspot) {
+ cursor->hotspot_x -= surface->current.dx * cursor->output->scale;
+ cursor->hotspot_y -= surface->current.dy * cursor->output->scale;
+ }
+
+ if (output_cursor_attempt_hardware(cursor)) {
+ return;
+ }
+
+ // Fallback to software cursor
+ output_cursor_damage_whole(cursor);
+}
+
+static void output_cursor_handle_commit(struct wl_listener *listener,
+ void *data) {
+ struct wlr_output_cursor *cursor =
+ wl_container_of(listener, cursor, surface_commit);
+ output_cursor_commit(cursor, true);
+}
+
+static void output_cursor_handle_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_output_cursor *cursor = wl_container_of(listener, cursor,
+ surface_destroy);
+ output_cursor_reset(cursor);
+}
+
+void wlr_output_cursor_set_surface(struct wlr_output_cursor *cursor,
+ struct wlr_surface *surface, int32_t hotspot_x, int32_t hotspot_y) {
+ hotspot_x *= cursor->output->scale;
+ hotspot_y *= cursor->output->scale;
+
+ if (surface && surface == cursor->surface) {
+ // Only update the hotspot: surface hasn't changed
+
+ if (cursor->output->hardware_cursor != cursor) {
+ output_cursor_damage_whole(cursor);
+ }
+ cursor->hotspot_x = hotspot_x;
+ cursor->hotspot_y = hotspot_y;
+ if (cursor->output->hardware_cursor != cursor) {
+ output_cursor_damage_whole(cursor);
+ } else {
+ assert(cursor->output->impl->set_cursor);
+ cursor->output->impl->set_cursor(cursor->output, NULL,
+ 1, WL_OUTPUT_TRANSFORM_NORMAL, hotspot_x, hotspot_y, false);
+ }
+ return;
+ }
+
+ output_cursor_reset(cursor);
+
+ cursor->surface = surface;
+ cursor->hotspot_x = hotspot_x;
+ cursor->hotspot_y = hotspot_y;
+
+ if (surface != NULL) {
+ wl_signal_add(&surface->events.commit, &cursor->surface_commit);
+ wl_signal_add(&surface->events.destroy, &cursor->surface_destroy);
+
+ cursor->visible = false;
+ output_cursor_commit(cursor, false);
+ } else {
+ cursor->enabled = false;
+ cursor->width = 0;
+ cursor->height = 0;
+
+ if (cursor->output->hardware_cursor == cursor) {
+ assert(cursor->output->impl->set_cursor);
+ cursor->output->impl->set_cursor(cursor->output, NULL, 1,
+ WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, true);
+ }
+ }
+}
+
+bool wlr_output_cursor_move(struct wlr_output_cursor *cursor,
+ double x, double y) {
+ if (cursor->x == x && cursor->y == y) {
+ return true;
+ }
+
+ if (cursor->output->hardware_cursor != cursor) {
+ output_cursor_damage_whole(cursor);
+ }
+
+ bool was_visible = cursor->visible;
+ x *= cursor->output->scale;
+ y *= cursor->output->scale;
+ cursor->x = x;
+ cursor->y = y;
+ 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(struct wlr_output_cursor));
+ if (cursor == NULL) {
+ return NULL;
+ }
+ cursor->output = output;
+ wl_signal_init(&cursor->events.destroy);
+ wl_list_init(&cursor->surface_commit.link);
+ cursor->surface_commit.notify = output_cursor_handle_commit;
+ wl_list_init(&cursor->surface_destroy.link);
+ cursor->surface_destroy.notify = output_cursor_handle_destroy;
+ 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);
+ wlr_signal_emit_safe(&cursor->events.destroy, cursor);
+ if (cursor->output->hardware_cursor == cursor) {
+ // If this cursor was the hardware cursor, disable it
+ if (cursor->output->impl->set_cursor) {
+ cursor->output->impl->set_cursor(cursor->output, NULL, 1,
+ WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, true);
+ }
+ cursor->output->hardware_cursor = NULL;
+ }
+ wlr_texture_destroy(cursor->texture);
+ wl_list_remove(&cursor->link);
+ free(cursor);
+}
+
+
+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 rotated =
+ (tr_a + tr_b) & (WL_OUTPUT_TRANSFORM_90 | WL_OUTPUT_TRANSFORM_180);
+ return flipped | rotated;
+}
diff --git a/types/wlr_output_damage.c b/types/wlr_output_damage.c
new file mode 100644
index 00000000..bf3a671d
--- /dev/null
+++ b/types/wlr_output_damage.c
@@ -0,0 +1,192 @@
+#include <stddef.h>
+#include <stdlib.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_output_damage.h>
+#include <wlr/types/wlr_output.h>
+#include "util/signal.h"
+
+static void output_handle_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_output_damage *output_damage =
+ wl_container_of(listener, output_damage, output_destroy);
+ wlr_output_damage_destroy(output_damage);
+}
+
+static void output_handle_mode(struct wl_listener *listener, void *data) {
+ struct wlr_output_damage *output_damage =
+ wl_container_of(listener, output_damage, output_mode);
+ wlr_output_damage_add_whole(output_damage);
+}
+
+static void output_handle_transform(struct wl_listener *listener, void *data) {
+ struct wlr_output_damage *output_damage =
+ wl_container_of(listener, output_damage, output_transform);
+ wlr_output_damage_add_whole(output_damage);
+}
+
+static void output_handle_scale(struct wl_listener *listener, void *data) {
+ struct wlr_output_damage *output_damage =
+ wl_container_of(listener, output_damage, output_scale);
+ wlr_output_damage_add_whole(output_damage);
+}
+
+static void output_handle_needs_swap(struct wl_listener *listener, void *data) {
+ struct wlr_output_damage *output_damage =
+ wl_container_of(listener, output_damage, output_needs_swap);
+ pixman_region32_union(&output_damage->current, &output_damage->current,
+ &output_damage->output->damage);
+ wlr_output_schedule_frame(output_damage->output);
+}
+
+static void output_handle_frame(struct wl_listener *listener, void *data) {
+ struct wlr_output_damage *output_damage =
+ wl_container_of(listener, output_damage, output_frame);
+
+ if (!output_damage->output->enabled) {
+ return;
+ }
+
+ wlr_signal_emit_safe(&output_damage->events.frame, output_damage);
+}
+
+struct wlr_output_damage *wlr_output_damage_create(struct wlr_output *output) {
+ struct wlr_output_damage *output_damage =
+ calloc(1, sizeof(struct wlr_output_damage));
+ if (output_damage == NULL) {
+ return NULL;
+ }
+
+ output_damage->output = output;
+ output_damage->max_rects = 20;
+ wl_signal_init(&output_damage->events.frame);
+ wl_signal_init(&output_damage->events.destroy);
+
+ pixman_region32_init(&output_damage->current);
+ for (size_t i = 0; i < WLR_OUTPUT_DAMAGE_PREVIOUS_LEN; ++i) {
+ pixman_region32_init(&output_damage->previous[i]);
+ }
+
+ wl_signal_add(&output->events.destroy, &output_damage->output_destroy);
+ output_damage->output_destroy.notify = output_handle_destroy;
+ wl_signal_add(&output->events.mode, &output_damage->output_mode);
+ output_damage->output_mode.notify = output_handle_mode;
+ wl_signal_add(&output->events.transform, &output_damage->output_transform);
+ output_damage->output_transform.notify = output_handle_transform;
+ wl_signal_add(&output->events.scale, &output_damage->output_scale);
+ output_damage->output_scale.notify = output_handle_scale;
+ wl_signal_add(&output->events.needs_swap, &output_damage->output_needs_swap);
+ output_damage->output_needs_swap.notify = output_handle_needs_swap;
+ wl_signal_add(&output->events.frame, &output_damage->output_frame);
+ output_damage->output_frame.notify = output_handle_frame;
+
+ return output_damage;
+}
+
+void wlr_output_damage_destroy(struct wlr_output_damage *output_damage) {
+ if (output_damage == NULL) {
+ return;
+ }
+ wlr_signal_emit_safe(&output_damage->events.destroy, output_damage);
+ wl_list_remove(&output_damage->output_destroy.link);
+ wl_list_remove(&output_damage->output_mode.link);
+ wl_list_remove(&output_damage->output_transform.link);
+ wl_list_remove(&output_damage->output_scale.link);
+ wl_list_remove(&output_damage->output_needs_swap.link);
+ wl_list_remove(&output_damage->output_frame.link);
+ pixman_region32_fini(&output_damage->current);
+ for (size_t i = 0; i < WLR_OUTPUT_DAMAGE_PREVIOUS_LEN; ++i) {
+ pixman_region32_fini(&output_damage->previous[i]);
+ }
+ free(output_damage);
+}
+
+bool wlr_output_damage_make_current(struct wlr_output_damage *output_damage,
+ bool *needs_swap, pixman_region32_t *damage) {
+ struct wlr_output *output = output_damage->output;
+
+ int buffer_age = -1;
+ if (!wlr_output_make_current(output, &buffer_age)) {
+ return false;
+ }
+
+ // Check if we can use damage tracking
+ if (buffer_age <= 0 || buffer_age - 1 > WLR_OUTPUT_DAMAGE_PREVIOUS_LEN) {
+ int width, height;
+ wlr_output_transformed_resolution(output, &width, &height);
+
+ // Buffer new or too old, damage the whole output
+ pixman_region32_union_rect(damage, damage, 0, 0, width, height);
+ } else {
+ pixman_region32_copy(damage, &output_damage->current);
+
+ // Accumulate damage from old buffers
+ size_t idx = output_damage->previous_idx;
+ for (int i = 0; i < buffer_age - 1; ++i) {
+ int j = (idx + i) % WLR_OUTPUT_DAMAGE_PREVIOUS_LEN;
+ pixman_region32_union(damage, damage, &output_damage->previous[j]);
+ }
+
+ // Check the number of rectangles
+ int n_rects = pixman_region32_n_rects(damage);
+ if (n_rects > output_damage->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);
+ }
+ }
+
+ *needs_swap = output->needs_swap || pixman_region32_not_empty(damage);
+ return true;
+}
+
+bool wlr_output_damage_swap_buffers(struct wlr_output_damage *output_damage,
+ struct timespec *when, pixman_region32_t *damage) {
+ if (!wlr_output_swap_buffers(output_damage->output, when, damage)) {
+ return false;
+ }
+
+ // same as decrementing, but works on unsigned integers
+ output_damage->previous_idx += WLR_OUTPUT_DAMAGE_PREVIOUS_LEN - 1;
+ output_damage->previous_idx %= WLR_OUTPUT_DAMAGE_PREVIOUS_LEN;
+
+ pixman_region32_copy(&output_damage->previous[output_damage->previous_idx],
+ &output_damage->current);
+ pixman_region32_clear(&output_damage->current);
+
+ return true;
+}
+
+void wlr_output_damage_add(struct wlr_output_damage *output_damage,
+ pixman_region32_t *damage) {
+ int width, height;
+ wlr_output_transformed_resolution(output_damage->output, &width, &height);
+
+ pixman_region32_union(&output_damage->current, &output_damage->current,
+ damage);
+ pixman_region32_intersect_rect(&output_damage->current,
+ &output_damage->current, 0, 0, width, height);
+ wlr_output_schedule_frame(output_damage->output);
+}
+
+void wlr_output_damage_add_whole(struct wlr_output_damage *output_damage) {
+ int width, height;
+ wlr_output_transformed_resolution(output_damage->output, &width, &height);
+
+ pixman_region32_union_rect(&output_damage->current, &output_damage->current,
+ 0, 0, width, height);
+
+ wlr_output_schedule_frame(output_damage->output);
+}
+
+void wlr_output_damage_add_box(struct wlr_output_damage *output_damage,
+ struct wlr_box *box) {
+ int width, height;
+ wlr_output_transformed_resolution(output_damage->output, &width, &height);
+
+ pixman_region32_union_rect(&output_damage->current, &output_damage->current,
+ box->x, box->y, box->width, box->height);
+ pixman_region32_intersect_rect(&output_damage->current,
+ &output_damage->current, 0, 0, width, height);
+ wlr_output_schedule_frame(output_damage->output);
+}
diff --git a/types/wlr_output_layout.c b/types/wlr_output_layout.c
new file mode 100644
index 00000000..553275d7
--- /dev/null
+++ b/types/wlr_output_layout.c
@@ -0,0 +1,521 @@
+#include <assert.h>
+#include <float.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/util/log.h>
+#include "util/signal.h"
+
+struct wlr_output_layout_state {
+ struct wlr_box _box; // should never be read directly, use the getter
+};
+
+struct wlr_output_layout_output_state {
+ struct wlr_output_layout *layout;
+ struct wlr_output_layout_output *l_output;
+
+ struct wlr_box _box; // should never be read directly, use the getter
+ bool auto_configured;
+
+ struct wl_listener mode;
+ struct wl_listener scale;
+ struct wl_listener transform;
+ struct wl_listener output_destroy;
+};
+
+struct wlr_output_layout *wlr_output_layout_create(void) {
+ struct wlr_output_layout *layout =
+ calloc(1, sizeof(struct wlr_output_layout));
+ if (layout == NULL) {
+ return NULL;
+ }
+ layout->state = calloc(1, sizeof(struct wlr_output_layout_state));
+ if (layout->state == NULL) {
+ free(layout);
+ return NULL;
+ }
+ wl_list_init(&layout->outputs);
+
+ wl_signal_init(&layout->events.add);
+ wl_signal_init(&layout->events.change);
+ wl_signal_init(&layout->events.destroy);
+
+ return layout;
+}
+
+static void output_layout_output_destroy(
+ struct wlr_output_layout_output *l_output) {
+ wlr_signal_emit_safe(&l_output->events.destroy, l_output);
+ wlr_output_destroy_global(l_output->output);
+ wl_list_remove(&l_output->state->mode.link);
+ wl_list_remove(&l_output->state->scale.link);
+ wl_list_remove(&l_output->state->transform.link);
+ wl_list_remove(&l_output->state->output_destroy.link);
+ wl_list_remove(&l_output->link);
+ free(l_output->state);
+ free(l_output);
+}
+
+void wlr_output_layout_destroy(struct wlr_output_layout *layout) {
+ if (!layout) {
+ return;
+ }
+
+ wlr_signal_emit_safe(&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);
+ }
+
+ free(layout->state);
+ free(layout);
+}
+
+static struct wlr_box *output_layout_output_get_box(
+ struct wlr_output_layout_output *l_output) {
+ l_output->state->_box.x = l_output->x;
+ l_output->state->_box.y = l_output->y;
+ int width, height;
+ wlr_output_effective_resolution(l_output->output, &width, &height);
+ l_output->state->_box.width = width;
+ l_output->state->_box.height = height;
+ return &l_output->state->_box;
+}
+
+/**
+ * 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;
+ wl_list_for_each(l_output, &layout->outputs, link) {
+ if (l_output->state->auto_configured) {
+ continue;
+ }
+
+ struct wlr_box *box = output_layout_output_get_box(l_output);
+ if (box->x + box->width > max_x) {
+ max_x = box->x + box->width;
+ max_x_y = 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->state->auto_configured) {
+ continue;
+ }
+ struct wlr_box *box = output_layout_output_get_box(l_output);
+ l_output->x = max_x;
+ l_output->y = max_x_y;
+ max_x += box->width;
+ }
+
+ wl_list_for_each(l_output, &layout->outputs, link) {
+ wlr_output_set_position(l_output->output, l_output->x, l_output->y);
+ }
+
+ wlr_signal_emit_safe(&layout->events.change, layout);
+}
+
+static void output_update_global(struct wlr_output *output) {
+ // Don't expose the output if it doesn't have a current mode
+ if (wl_list_empty(&output->modes) || output->current_mode != NULL) {
+ wlr_output_create_global(output);
+ } else {
+ wlr_output_destroy_global(output);
+ }
+}
+
+static void handle_output_mode(struct wl_listener *listener, void *data) {
+ struct wlr_output_layout_output_state *state =
+ wl_container_of(listener, state, mode);
+ output_layout_reconfigure(state->layout);
+ output_update_global(state->l_output->output);
+}
+
+static void handle_output_scale(struct wl_listener *listener, void *data) {
+ struct wlr_output_layout_output_state *state =
+ wl_container_of(listener, state, scale);
+ output_layout_reconfigure(state->layout);
+}
+
+static void handle_output_transform(struct wl_listener *listener, void *data) {
+ struct wlr_output_layout_output_state *state =
+ wl_container_of(listener, state, transform);
+ output_layout_reconfigure(state->layout);
+}
+
+static void handle_output_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_output_layout_output_state *state =
+ wl_container_of(listener, state, output_destroy);
+ struct wlr_output_layout *layout = state->layout;
+ output_layout_output_destroy(state->l_output);
+ output_layout_reconfigure(layout);
+}
+
+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(struct wlr_output_layout_output));
+ if (l_output == NULL) {
+ return NULL;
+ }
+ l_output->state = calloc(1, sizeof(struct wlr_output_layout_output_state));
+ if (l_output->state == NULL) {
+ free(l_output);
+ return NULL;
+ }
+ l_output->state->l_output = l_output;
+ l_output->state->layout = layout;
+ l_output->output = output;
+ wl_signal_init(&l_output->events.destroy);
+ wl_list_insert(&layout->outputs, &l_output->link);
+
+ wl_signal_add(&output->events.mode, &l_output->state->mode);
+ l_output->state->mode.notify = handle_output_mode;
+ wl_signal_add(&output->events.scale, &l_output->state->scale);
+ l_output->state->scale.notify = handle_output_scale;
+ wl_signal_add(&output->events.transform, &l_output->state->transform);
+ l_output->state->transform.notify = handle_output_transform;
+ wl_signal_add(&output->events.destroy, &l_output->state->output_destroy);
+ l_output->state->output_destroy.notify = handle_output_destroy;
+
+ return l_output;
+}
+
+void wlr_output_layout_add(struct wlr_output_layout *layout,
+ struct wlr_output *output, int lx, int ly) {
+ struct wlr_output_layout_output *l_output =
+ wlr_output_layout_get(layout, output);
+ bool is_new = l_output == NULL;
+ if (!l_output) {
+ l_output = output_layout_output_create(layout, output);
+ if (!l_output) {
+ wlr_log(WLR_ERROR, "Failed to create wlr_output_layout_output");
+ return;
+ }
+ }
+
+ l_output->x = lx;
+ l_output->y = ly;
+ l_output->state->auto_configured = false;
+ output_layout_reconfigure(layout);
+ output_update_global(output);
+
+ if (is_new) {
+ wlr_signal_emit_safe(&layout->events.add, l_output);
+ }
+}
+
+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;
+ wl_list_for_each(l_output, &layout->outputs, link) {
+ if (l_output->output == reference) {
+ return l_output;
+ }
+ }
+ return NULL;
+}
+
+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);
+ struct wlr_box *box = output_layout_output_get_box(l_output);
+ return wlr_box_contains_point(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);
+ 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);
+ 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 *box = output_layout_output_get_box(l_output);
+ if (wlr_box_contains_point(box, lx, ly)) {
+ return l_output->output;
+ }
+ }
+ return NULL;
+}
+
+void wlr_output_layout_move(struct wlr_output_layout *layout,
+ struct wlr_output *output, int lx, int ly) {
+ struct wlr_output_layout_output *l_output =
+ wlr_output_layout_get(layout, output);
+ if (l_output) {
+ l_output->x = lx;
+ l_output->y = ly;
+ l_output->state->auto_configured = false;
+ output_layout_reconfigure(layout);
+ } else {
+ wlr_log(WLR_ERROR, "output not found in this layout: %s", output->name);
+ }
+}
+
+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) {
+ output_layout_output_destroy(l_output);
+ output_layout_reconfigure(layout);
+ }
+}
+
+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 = DBL_MAX, min_y = DBL_MAX, 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 *box = output_layout_output_get_box(l_output);
+ wlr_box_closest_point(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;
+ }
+}
+
+struct wlr_box *wlr_output_layout_get_box(
+ struct wlr_output_layout *layout, struct wlr_output *reference) {
+ struct wlr_output_layout_output *l_output;
+ if (reference) {
+ // output extents
+ l_output = wlr_output_layout_get(layout, reference);
+
+ if (l_output) {
+ return output_layout_output_get_box(l_output);
+ } else {
+ return NULL;
+ }
+ } else {
+ // layout extents
+ int min_x = INT_MAX, min_y = INT_MAX;
+ int max_x = INT_MIN, max_y = INT_MIN;
+ wl_list_for_each(l_output, &layout->outputs, link) {
+ struct wlr_box *box = output_layout_output_get_box(l_output);
+
+ if (box->x < min_x) {
+ min_x = box->x;
+ }
+ if (box->y < min_y) {
+ min_y = box->y;
+ }
+ if (box->x + box->width > max_x) {
+ max_x = box->x + box->width;
+ }
+ if (box->y + box->height > max_y) {
+ max_y = box->y + box->height;
+ }
+ }
+
+ layout->state->_box.x = min_x;
+ layout->state->_box.y = min_y;
+ layout->state->_box.width = max_x - min_x;
+ layout->state->_box.height = max_y - min_y;
+
+ return &layout->state->_box;
+ }
+
+ // not reached
+}
+
+void wlr_output_layout_add_auto(struct wlr_output_layout *layout,
+ struct wlr_output *output) {
+ struct wlr_output_layout_output *l_output =
+ wlr_output_layout_get(layout, output);
+ bool is_new = l_output == NULL;
+ if (!l_output) {
+ l_output = output_layout_output_create(layout, output);
+ if (!l_output) {
+ wlr_log(WLR_ERROR, "Failed to create wlr_output_layout_output");
+ return;
+ }
+ }
+
+ l_output->state->auto_configured = true;
+ output_layout_reconfigure(layout);
+ output_update_global(output);
+
+ if (is_new) {
+ wlr_signal_emit_safe(&layout->events.add, l_output);
+ }
+}
+
+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);
+ 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
+};
+
+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);
+
+ 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);
+
+ 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_pointer.c b/types/wlr_pointer.c
new file mode 100644
index 00000000..20e75731
--- /dev/null
+++ b/types/wlr_pointer.c
@@ -0,0 +1,29 @@
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-server.h>
+#include <wlr/interfaces/wlr_pointer.h>
+#include <wlr/types/wlr_pointer.h>
+
+void wlr_pointer_init(struct wlr_pointer *pointer,
+ const struct wlr_pointer_impl *impl) {
+ pointer->impl = impl;
+ 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);
+}
+
+void wlr_pointer_destroy(struct wlr_pointer *pointer) {
+ if (!pointer) {
+ return;
+ }
+ if (pointer->impl && pointer->impl->destroy) {
+ pointer->impl->destroy(pointer);
+ } else {
+ wl_list_remove(&pointer->events.motion.listener_list);
+ wl_list_remove(&pointer->events.motion_absolute.listener_list);
+ wl_list_remove(&pointer->events.button.listener_list);
+ wl_list_remove(&pointer->events.axis.listener_list);
+ free(pointer);
+ }
+}
diff --git a/types/wlr_pointer_constraints_v1.c b/types/wlr_pointer_constraints_v1.c
new file mode 100644
index 00000000..196af92e
--- /dev/null
+++ b/types/wlr_pointer_constraints_v1.c
@@ -0,0 +1,372 @@
+#include <assert.h>
+#include <limits.h>
+#include <pixman.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/types/wlr_pointer_constraints_v1.h>
+#include <wlr/types/wlr_region.h>
+#include <wlr/util/log.h>
+#include "util/signal.h"
+
+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);
+
+ wlr_signal_emit_safe(&constraint->events.destroy, constraint);
+
+ wl_resource_set_user_data(constraint->resource, NULL);
+ 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) {
+ 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.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) {
+ if (constraint->pending.committed &
+ WLR_POINTER_CONSTRAINT_V1_STATE_REGION) {
+ pixman_region32_copy(&constraint->current.region,
+ &constraint->pending.region);
+ }
+ if (constraint->pending.committed &
+ WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT) {
+ constraint->current.cursor_hint = constraint->pending.cursor_hint;
+ }
+ constraint->current.committed |= constraint->pending.committed;
+
+ constraint->pending.committed = 0;
+
+ 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);
+ }
+}
+
+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 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 *seat =
+ wlr_seat_client_from_pointer_resource(pointer_resource)->seat;
+
+ if (wlr_pointer_constraints_v1_constraint_for_surface(pointer_constraints,
+ surface, seat)) {
+ 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;
+ }
+
+ 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;
+ }
+
+ struct wlr_pointer_constraint_v1 *constraint = calloc(1, sizeof(*constraint));
+ if (constraint == NULL) {
+ 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.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);
+
+ void *impl = locked_pointer ?
+ (void *)&locked_pointer_impl : (void *)&confined_pointer_impl;
+ wl_resource_set_implementation(constraint->resource, impl, constraint,
+ pointer_constraint_destroy_resource);
+
+ 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);
+
+ wlr_signal_emit_safe(&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_destroy(struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+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;
+ assert(client && pointer_constraints);
+
+ 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_list_insert(&pointer_constraints->resources,
+ wl_resource_get_link(resource));
+ wl_resource_set_implementation(resource, &pointer_constraints_impl,
+ pointer_constraints, pointer_constraints_destroy);
+}
+
+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->resources);
+ wl_list_init(&pointer_constraints->constraints);
+ wl_signal_init(&pointer_constraints->events.new_constraint);
+
+ return pointer_constraints;
+}
+
+void wlr_pointer_constraints_v1_destroy(
+ struct wlr_pointer_constraints_v1 *pointer_constraints) {
+ struct wl_resource *resource, *_tmp_res;
+ wl_resource_for_each_safe(resource, _tmp_res,
+ &pointer_constraints->resources) {
+ wl_resource_destroy(resource);
+ }
+
+ struct wlr_pointer_constraint_v1 *constraint, *_tmp_cons;
+ wl_list_for_each_safe(constraint, _tmp_cons,
+ &pointer_constraints->constraints, link) {
+ wl_resource_destroy(constraint->resource);
+ }
+
+ wl_global_destroy(pointer_constraints->global);
+ free(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_presentation_time.c b/types/wlr_presentation_time.c
new file mode 100644
index 00000000..eeba47b6
--- /dev/null
+++ b/types/wlr_presentation_time.c
@@ -0,0 +1,223 @@
+#define _POSIX_C_SOURCE 199309L
+#include <assert.h>
+#include <stdlib.h>
+#include <wlr/types/wlr_presentation_time.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/backend.h>
+#include "presentation-time-protocol.h"
+#include "util/signal.h"
+
+#define PRESENTATION_VERSION 1
+
+static struct wlr_presentation_feedback *presentation_feedback_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource,
+ &wp_presentation_feedback_interface, NULL));
+ return wl_resource_get_user_data(resource);
+}
+
+static void feedback_handle_resource_destroy(struct wl_resource *resource) {
+ struct wlr_presentation_feedback *feedback =
+ presentation_feedback_from_resource(resource);
+ wl_list_remove(&feedback->surface_commit.link);
+ wl_list_remove(&feedback->surface_destroy.link);
+ wl_list_remove(&feedback->link);
+ free(feedback);
+}
+
+// Destroys the feedback
+static void feedback_send_presented(struct wlr_presentation_feedback *feedback,
+ struct wlr_presentation_event *event) {
+ struct wl_client *client = wl_resource_get_client(feedback->resource);
+ struct wl_resource *resource;
+ wl_resource_for_each(resource, &event->output->resources) {
+ if (wl_resource_get_client(resource) == client) {
+ wp_presentation_feedback_send_sync_output(feedback->resource,
+ 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);
+}
+
+// Destroys the feedback
+static void feedback_send_discarded(
+ struct wlr_presentation_feedback *feedback) {
+ wp_presentation_feedback_send_discarded(feedback->resource);
+ wl_resource_destroy(feedback->resource);
+}
+
+static void feedback_handle_surface_commit(struct wl_listener *listener,
+ void *data) {
+ struct wlr_presentation_feedback *feedback =
+ wl_container_of(listener, feedback, surface_commit);
+
+ if (feedback->committed) {
+ // The content update has been superseded
+ feedback_send_discarded(feedback);
+ } else {
+ feedback->committed = true;
+ }
+}
+
+static void feedback_handle_surface_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_presentation_feedback *feedback =
+ wl_container_of(listener, feedback, surface_destroy);
+ feedback_send_discarded(feedback);
+}
+
+static const struct wp_presentation_interface presentation_impl;
+
+static struct wlr_presentation *presentation_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &wp_presentation_interface,
+ &presentation_impl));
+ return wl_resource_get_user_data(resource);
+}
+
+static void presentation_handle_feedback(struct wl_client *client,
+ struct wl_resource *resource, struct wl_resource *surface_resource,
+ uint32_t id) {
+ struct wlr_presentation *presentation =
+ presentation_from_resource(resource);
+ struct wlr_surface *surface = wlr_surface_from_resource(surface_resource);
+
+ struct wlr_presentation_feedback *feedback =
+ calloc(1, sizeof(struct wlr_presentation_feedback));
+ if (feedback == NULL) {
+ wl_client_post_no_memory(client);
+ return;
+ }
+
+ uint32_t version = wl_resource_get_version(resource);
+ feedback->resource = wl_resource_create(client,
+ &wp_presentation_feedback_interface, version, id);
+ if (feedback->resource == NULL) {
+ free(feedback);
+ wl_client_post_no_memory(client);
+ return;
+ }
+ wl_resource_set_implementation(feedback->resource, NULL, feedback,
+ feedback_handle_resource_destroy);
+
+ feedback->surface = surface;
+
+ feedback->surface_commit.notify = feedback_handle_surface_commit;
+ wl_signal_add(&surface->events.commit, &feedback->surface_commit);
+
+ feedback->surface_destroy.notify = feedback_handle_surface_destroy;
+ wl_signal_add(&surface->events.destroy, &feedback->surface_destroy);
+
+ wl_list_insert(&presentation->feedbacks, &feedback->link);
+}
+
+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_handle_resource_destroy(struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+static void presentation_bind(struct wl_client *client, void *data,
+ uint32_t version, uint32_t id) {
+ struct wlr_presentation *presentation = data;
+
+ 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, presentation,
+ presentation_handle_resource_destroy);
+ wl_list_insert(&presentation->resources, wl_resource_get_link(resource));
+
+ wp_presentation_send_clock_id(resource, (uint32_t)presentation->clock);
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_presentation *presentation =
+ wl_container_of(listener, presentation, display_destroy);
+ wlr_presentation_destroy(presentation);
+}
+
+struct wlr_presentation *wlr_presentation_create(struct wl_display *display,
+ struct wlr_backend *backend) {
+ struct wlr_presentation *presentation =
+ calloc(1, sizeof(struct wlr_presentation));
+ if (presentation == NULL) {
+ return NULL;
+ }
+
+ presentation->global = wl_global_create(display, &wp_presentation_interface,
+ PRESENTATION_VERSION, presentation, presentation_bind);
+ if (presentation->global == NULL) {
+ free(presentation);
+ return NULL;
+ }
+
+ presentation->clock = wlr_backend_get_presentation_clock(backend);
+
+ wl_list_init(&presentation->resources);
+ wl_list_init(&presentation->feedbacks);
+ 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_destroy(struct wlr_presentation *presentation) {
+ if (presentation == NULL) {
+ return;
+ }
+
+ wlr_signal_emit_safe(&presentation->events.destroy, presentation);
+
+ wl_global_destroy(presentation->global);
+
+ struct wlr_presentation_feedback *feedback, *feedback_tmp;
+ wl_list_for_each_safe(feedback, feedback_tmp, &presentation->feedbacks,
+ link) {
+ wl_resource_destroy(feedback->resource);
+ }
+
+ struct wl_resource *resource, *resource_tmp;
+ wl_resource_for_each_safe(resource, resource_tmp,
+ &presentation->resources) {
+ wl_resource_destroy(resource);
+ }
+
+ wl_list_remove(&presentation->display_destroy.link);
+ free(presentation);
+}
+
+void wlr_presentation_send_surface_presented(
+ struct wlr_presentation *presentation, struct wlr_surface *surface,
+ struct wlr_presentation_event *event) {
+ // TODO: maybe use a hashtable to optimize this function
+ struct wlr_presentation_feedback *feedback, *feedback_tmp;
+ wl_list_for_each_safe(feedback, feedback_tmp,
+ &presentation->feedbacks, link) {
+ if (feedback->surface == surface && feedback->committed) {
+ feedback_send_presented(feedback, event);
+ }
+ }
+}
diff --git a/types/wlr_primary_selection.c b/types/wlr_primary_selection.c
new file mode 100644
index 00000000..921f44ee
--- /dev/null
+++ b/types/wlr_primary_selection.c
@@ -0,0 +1,69 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <wlr/types/wlr_primary_selection.h>
+#include "util/signal.h"
+
+void wlr_primary_selection_source_init(
+ struct wlr_primary_selection_source *source,
+ const struct wlr_primary_selection_source_impl *impl) {
+ assert(impl->send);
+ wl_array_init(&source->mime_types);
+ wl_signal_init(&source->events.destroy);
+ source->impl = impl;
+}
+
+void wlr_primary_selection_source_destroy(
+ struct wlr_primary_selection_source *source) {
+ if (source == NULL) {
+ return;
+ }
+
+ wlr_signal_emit_safe(&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);
+}
+
+
+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;
+ wlr_signal_emit_safe(&seat->events.primary_selection, seat);
+}
+
+void wlr_seat_set_primary_selection(struct wlr_seat *seat,
+ struct wlr_primary_selection_source *source) {
+ 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;
+ 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);
+ }
+
+ wlr_signal_emit_safe(&seat->events.primary_selection, seat);
+}
diff --git a/types/wlr_region.c b/types/wlr_region.c
new file mode 100644
index 00000000..6996ab14
--- /dev/null
+++ b/types/wlr_region.c
@@ -0,0 +1,78 @@
+#include <assert.h>
+#include <pixman.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_region.h>
+
+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 = wlr_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 = wlr_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 = wlr_region_from_resource(resource);
+
+ wl_list_remove(wl_resource_get_link(resource));
+
+ pixman_region32_fini(reg);
+ free(reg);
+}
+
+struct wl_resource *wlr_region_create(struct wl_client *client,
+ uint32_t version, uint32_t id, struct wl_list *resource_list) {
+ pixman_region32_t *region = calloc(1, sizeof(pixman_region32_t));
+ 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, &region_impl, region,
+ region_handle_resource_destroy);
+
+ struct wl_list *resource_link = wl_resource_get_link(region_resource);
+ if (resource_list != NULL) {
+ wl_list_insert(resource_list, resource_link);
+ } else {
+ wl_list_init(resource_link);
+ }
+
+ return region_resource;
+}
+
+pixman_region32_t *wlr_region_from_resource(struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &wl_region_interface,
+ &region_impl));
+ return wl_resource_get_user_data(resource);
+}
diff --git a/types/wlr_screencopy_v1.c b/types/wlr_screencopy_v1.c
new file mode 100644
index 00000000..8beb650f
--- /dev/null
+++ b/types/wlr_screencopy_v1.c
@@ -0,0 +1,349 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_screencopy_v1.h>
+#include <wlr/backend.h>
+#include <wlr/util/log.h>
+#include "wlr-screencopy-unstable-v1-protocol.h"
+#include "util/signal.h"
+
+#define SCREENCOPY_MANAGER_VERSION 1
+
+static const struct zwlr_screencopy_frame_v1_interface frame_impl;
+
+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->cursor_locked) {
+ wlr_output_lock_software_cursors(frame->output, false);
+ }
+ wl_list_remove(&frame->link);
+ wl_list_remove(&frame->output_swap_buffers.link);
+ wl_list_remove(&frame->buffer_destroy.link);
+ // Make the frame resource inert
+ wl_resource_set_user_data(frame->resource, NULL);
+ free(frame);
+}
+
+static void frame_handle_output_swap_buffers(struct wl_listener *listener,
+ void *_data) {
+ struct wlr_screencopy_frame_v1 *frame =
+ wl_container_of(listener, frame, output_swap_buffers);
+ struct wlr_output_event_swap_buffers *event = _data;
+ struct wlr_output *output = frame->output;
+ struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend);
+ assert(renderer);
+
+ wl_list_remove(&frame->output_swap_buffers.link);
+ wl_list_init(&frame->output_swap_buffers.link);
+
+ int x = frame->box.x;
+ int y = frame->box.y;
+
+ struct wl_shm_buffer *buffer = frame->buffer;
+ assert(buffer != NULL);
+
+ enum wl_shm_format fmt = wl_shm_buffer_get_format(buffer);
+ int32_t width = wl_shm_buffer_get_width(buffer);
+ int32_t height = wl_shm_buffer_get_height(buffer);
+ int32_t stride = wl_shm_buffer_get_stride(buffer);
+
+ wl_shm_buffer_begin_access(buffer);
+ void *data = wl_shm_buffer_get_data(buffer);
+ uint32_t flags = 0;
+ bool ok = wlr_renderer_read_pixels(renderer, fmt, &flags, stride,
+ width, height, x, y, 0, 0, data);
+ wl_shm_buffer_end_access(buffer);
+
+ if (!ok) {
+ zwlr_screencopy_frame_v1_send_failed(frame->resource);
+ frame_destroy(frame);
+ return;
+ }
+
+ zwlr_screencopy_frame_v1_send_flags(frame->resource, flags);
+
+ 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_screencopy_frame_v1_send_ready(frame->resource,
+ tv_sec_hi, tv_sec_lo, event->when->tv_nsec);
+
+ frame_destroy(frame);
+}
+
+static void frame_handle_buffer_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_screencopy_frame_v1 *frame =
+ wl_container_of(listener, frame, buffer_destroy);
+ zwlr_screencopy_frame_v1_send_failed(frame->resource);
+ frame_destroy(frame);
+}
+
+static void frame_handle_copy(struct wl_client *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;
+
+ struct wl_shm_buffer *buffer = wl_shm_buffer_get(buffer_resource);
+ if (buffer == NULL) {
+ wl_resource_post_error(frame->resource,
+ ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER,
+ "unsupported buffer type");
+ return;
+ }
+
+ enum wl_shm_format fmt = wl_shm_buffer_get_format(buffer);
+ int32_t width = wl_shm_buffer_get_width(buffer);
+ int32_t height = wl_shm_buffer_get_height(buffer);
+ int32_t stride = wl_shm_buffer_get_stride(buffer);
+ if (fmt != frame->format || width != frame->box.width ||
+ height != frame->box.height || stride != frame->stride) {
+ wl_resource_post_error(frame->resource,
+ ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER,
+ "invalid buffer attributes");
+ return;
+ }
+
+ if (!wl_list_empty(&frame->output_swap_buffers.link) ||
+ frame->buffer != NULL) {
+ wl_resource_post_error(frame->resource,
+ ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED,
+ "frame already used");
+ return;
+ }
+
+ frame->buffer = buffer;
+
+ wl_signal_add(&output->events.swap_buffers, &frame->output_swap_buffers);
+ frame->output_swap_buffers.notify = frame_handle_output_swap_buffers;
+
+ wl_resource_add_destroy_listener(buffer_resource, &frame->buffer_destroy);
+ frame->buffer_destroy.notify = frame_handle_buffer_destroy;
+
+ // Schedule a buffer swap
+ output->needs_swap = true;
+ wlr_output_schedule_frame(output);
+
+ if (frame->overlay_cursor) {
+ wlr_output_lock_software_cursors(output, true);
+ frame->cursor_locked = true;
+ }
+}
+
+static void frame_handle_destroy(struct wl_client *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,
+};
+
+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_manager_v1 *manager_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 *client,
+ struct wlr_screencopy_manager_v1 *manager, uint32_t version, uint32_t id,
+ int32_t overlay_cursor, struct wlr_output *output,
+ const struct wlr_box *box) {
+ 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, output->transform, ow, oh);
+ buffer_box.x *= output->scale;
+ buffer_box.y *= output->scale;
+ buffer_box.width *= output->scale;
+ buffer_box.height *= output->scale;
+ }
+
+ struct wlr_screencopy_frame_v1 *frame =
+ calloc(1, sizeof(struct wlr_screencopy_frame_v1));
+ if (frame == NULL) {
+ wl_client_post_no_memory(client);
+ return;
+ }
+ frame->manager = manager;
+ frame->output = output;
+ frame->overlay_cursor = !!overlay_cursor;
+
+ frame->resource = wl_resource_create(client,
+ &zwlr_screencopy_frame_v1_interface, version, id);
+ if (frame->resource == NULL) {
+ free(frame);
+ wl_client_post_no_memory(client);
+ return;
+ }
+ wl_resource_set_implementation(frame->resource, &frame_impl, frame,
+ frame_handle_resource_destroy);
+
+ wl_list_insert(&manager->frames, &frame->link);
+
+ wl_list_init(&frame->output_swap_buffers.link);
+ wl_list_init(&frame->buffer_destroy.link);
+
+ struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend);
+ assert(renderer);
+
+ if (!wlr_output_preferred_read_format(frame->output, &frame->format)) {
+ wlr_log(WLR_ERROR,
+ "Failed to capture output: no read format supported by renderer");
+ goto error;
+ }
+
+ frame->box = buffer_box;
+ frame->stride = 4 * buffer_box.width; // TODO: depends on read format
+
+ zwlr_screencopy_frame_v1_send_buffer(frame->resource, frame->format,
+ buffer_box.width, buffer_box.height, frame->stride);
+ return;
+
+error:
+ zwlr_screencopy_frame_v1_send_failed(frame->resource);
+ frame_destroy(frame);
+}
+
+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_screencopy_manager_v1 *manager =
+ manager_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(client, manager, version, id, overlay_cursor, output, NULL);
+}
+
+static void manager_handle_capture_output_region(struct wl_client *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_manager_v1 *manager =
+ manager_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(client, manager, version, id, overlay_cursor, output, &box);
+}
+
+static void manager_handle_destroy(struct wl_client *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,
+};
+
+void manager_handle_resource_destroy(struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+static void manager_bind(struct wl_client *client, void *data, uint32_t version,
+ uint32_t id) {
+ struct wlr_screencopy_manager_v1 *manager = data;
+
+ struct wl_resource *resource = wl_resource_create(client,
+ &zwlr_screencopy_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));
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_screencopy_manager_v1 *manager =
+ wl_container_of(listener, manager, display_destroy);
+ wlr_screencopy_manager_v1_destroy(manager);
+}
+
+struct wlr_screencopy_manager_v1 *wlr_screencopy_manager_v1_create(
+ struct wl_display *display) {
+ struct wlr_screencopy_manager_v1 *manager =
+ calloc(1, sizeof(struct wlr_screencopy_manager_v1));
+ 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->resources);
+ 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;
+}
+
+void wlr_screencopy_manager_v1_destroy(
+ struct wlr_screencopy_manager_v1 *manager) {
+ if (manager == NULL) {
+ return;
+ }
+ wlr_signal_emit_safe(&manager->events.destroy, manager);
+ wl_list_remove(&manager->display_destroy.link);
+ struct wlr_screencopy_frame_v1 *frame, *tmp_frame;
+ wl_list_for_each_safe(frame, tmp_frame, &manager->frames, link) {
+ wl_resource_destroy(frame->resource);
+ }
+ struct wl_resource *resource, *tmp_resource;
+ wl_resource_for_each_safe(resource, tmp_resource, &manager->resources) {
+ wl_resource_destroy(resource);
+ }
+ wl_global_destroy(manager->global);
+ free(manager);
+}
diff --git a/types/wlr_screenshooter.c b/types/wlr_screenshooter.c
new file mode 100644
index 00000000..c85e6ba5
--- /dev/null
+++ b/types/wlr_screenshooter.c
@@ -0,0 +1,213 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-server.h>
+#include <wlr/backend.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_screenshooter.h>
+#include <wlr/util/log.h>
+#include "screenshooter-protocol.h"
+#include "util/signal.h"
+
+static struct wlr_screenshot *screenshot_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &orbital_screenshot_interface,
+ NULL));
+ return wl_resource_get_user_data(resource);
+}
+
+struct screenshot_state {
+ struct wl_shm_buffer *shm_buffer;
+ struct wlr_screenshot *screenshot;
+ struct wl_listener frame_listener;
+};
+
+static void screenshot_destroy(struct wlr_screenshot *screenshot) {
+ wl_list_remove(&screenshot->link);
+ wl_resource_set_user_data(screenshot->resource, NULL);
+ free(screenshot);
+}
+
+static void handle_screenshot_resource_destroy(
+ struct wl_resource *screenshot_resource) {
+ struct wlr_screenshot *screenshot =
+ screenshot_from_resource(screenshot_resource);
+ if (screenshot != NULL) {
+ screenshot_destroy(screenshot);
+ }
+}
+
+static void output_handle_frame(struct wl_listener *listener, void *_data) {
+ struct screenshot_state *state = wl_container_of(listener, state,
+ frame_listener);
+ struct wlr_output *output = state->screenshot->output;
+ struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend);
+ struct wl_shm_buffer *shm_buffer = state->shm_buffer;
+
+ enum wl_shm_format format = wl_shm_buffer_get_format(shm_buffer);
+ int32_t width = wl_shm_buffer_get_width(shm_buffer);
+ int32_t height = wl_shm_buffer_get_height(shm_buffer);
+ int32_t stride = wl_shm_buffer_get_stride(shm_buffer);
+ wl_shm_buffer_begin_access(shm_buffer);
+ void *data = wl_shm_buffer_get_data(shm_buffer);
+ bool ok = wlr_renderer_read_pixels(renderer, format, NULL, stride,
+ width, height, 0, 0, 0, 0, data);
+ wl_shm_buffer_end_access(shm_buffer);
+
+ if (!ok) {
+ wlr_log(WLR_ERROR, "Cannot read pixels");
+ goto cleanup;
+ }
+
+ orbital_screenshot_send_done(state->screenshot->resource);
+
+cleanup:
+ wl_list_remove(&listener->link);
+ free(state);
+}
+
+static const struct orbital_screenshooter_interface screenshooter_impl;
+
+static struct wlr_screenshooter *screenshooter_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &orbital_screenshooter_interface,
+ &screenshooter_impl));
+ return wl_resource_get_user_data(resource);
+}
+
+static void screenshooter_shoot(struct wl_client *client,
+ struct wl_resource *screenshooter_resource, uint32_t id,
+ struct wl_resource *output_resource,
+ struct wl_resource *buffer_resource) {
+ struct wlr_screenshooter *screenshooter =
+ screenshooter_from_resource(screenshooter_resource);
+ struct wlr_output *output = wlr_output_from_resource(output_resource);
+
+ struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend);
+ if (renderer == NULL) {
+ wlr_log(WLR_ERROR, "Backend doesn't have a renderer");
+ return;
+ }
+
+ struct wl_shm_buffer *shm_buffer = wl_shm_buffer_get(buffer_resource);
+ if (shm_buffer == NULL) {
+ wlr_log(WLR_ERROR, "Invalid buffer: not a shared memory buffer");
+ return;
+ }
+
+ int32_t width = wl_shm_buffer_get_width(shm_buffer);
+ int32_t height = wl_shm_buffer_get_height(shm_buffer);
+ if (width < output->width || height < output->height) {
+ wlr_log(WLR_ERROR, "Invalid buffer: too small");
+ return;
+ }
+
+ uint32_t format = wl_shm_buffer_get_format(shm_buffer);
+ if (!wlr_renderer_format_supported(renderer, format)) {
+ wlr_log(WLR_ERROR, "Invalid buffer: unsupported format");
+ return;
+ }
+
+ struct wlr_screenshot *screenshot =
+ calloc(1, sizeof(struct wlr_screenshot));
+ if (!screenshot) {
+ wl_resource_post_no_memory(screenshooter_resource);
+ return;
+ }
+ screenshot->output_resource = output_resource;
+ screenshot->output = output;
+ screenshot->screenshooter = screenshooter;
+ screenshot->resource = wl_resource_create(client,
+ &orbital_screenshot_interface,
+ wl_resource_get_version(screenshooter_resource), id);
+ if (screenshot->resource == NULL) {
+ free(screenshot);
+ wl_resource_post_no_memory(screenshooter_resource);
+ return;
+ }
+ wl_resource_set_implementation(screenshot->resource, NULL, screenshot,
+ handle_screenshot_resource_destroy);
+ wl_list_insert(&screenshooter->screenshots, &screenshot->link);
+
+ wlr_log(WLR_DEBUG, "new screenshot %p (res %p)", screenshot,
+ screenshot->resource);
+
+ struct screenshot_state *state = calloc(1, sizeof(struct screenshot_state));
+ if (!state) {
+ wl_resource_destroy(screenshot->resource);
+ free(screenshot);
+ wl_resource_post_no_memory(screenshooter_resource);
+ return;
+ }
+ state->shm_buffer = shm_buffer;
+ state->screenshot = screenshot;
+ state->frame_listener.notify = output_handle_frame;
+ wl_signal_add(&output->events.swap_buffers, &state->frame_listener);
+
+ // Schedule a buffer swap
+ output->needs_swap = true;
+ wlr_output_schedule_frame(output);
+}
+
+static const struct orbital_screenshooter_interface screenshooter_impl = {
+ .shoot = screenshooter_shoot,
+};
+
+static void screenshooter_bind(struct wl_client *wl_client, void *data,
+ uint32_t version, uint32_t id) {
+ struct wlr_screenshooter *screenshooter = data;
+ assert(wl_client && screenshooter);
+
+ struct wl_resource *wl_resource = wl_resource_create(wl_client,
+ &orbital_screenshooter_interface, version, id);
+ if (wl_resource == NULL) {
+ wl_client_post_no_memory(wl_client);
+ return;
+ }
+ wl_resource_set_implementation(wl_resource, &screenshooter_impl,
+ screenshooter, NULL);
+}
+
+void wlr_screenshooter_destroy(struct wlr_screenshooter *screenshooter) {
+ if (!screenshooter) {
+ return;
+ }
+ wl_list_remove(&screenshooter->display_destroy.link);
+ struct wlr_screenshot *screenshot, *tmp;
+ wl_list_for_each_safe(screenshot, tmp, &screenshooter->screenshots, link) {
+ screenshot_destroy(screenshot);
+ }
+ wlr_signal_emit_safe(&screenshooter->events.destroy, screenshooter);
+ wl_global_destroy(screenshooter->global);
+ free(screenshooter);
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_screenshooter *screenshooter =
+ wl_container_of(listener, screenshooter, display_destroy);
+ wlr_screenshooter_destroy(screenshooter);
+}
+
+struct wlr_screenshooter *wlr_screenshooter_create(struct wl_display *display) {
+ struct wlr_screenshooter *screenshooter =
+ calloc(1, sizeof(struct wlr_screenshooter));
+ if (!screenshooter) {
+ return NULL;
+ }
+
+ wl_list_init(&screenshooter->screenshots);
+ wl_signal_init(&screenshooter->events.destroy);
+
+ screenshooter->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &screenshooter->display_destroy);
+
+ screenshooter->global = wl_global_create(display,
+ &orbital_screenshooter_interface, 1, screenshooter, screenshooter_bind);
+ if (screenshooter->global == NULL) {
+ free(screenshooter);
+ return NULL;
+ }
+
+ return screenshooter;
+}
diff --git a/types/wlr_server_decoration.c b/types/wlr_server_decoration.c
new file mode 100644
index 00000000..fd92fd04
--- /dev/null
+++ b/types/wlr_server_decoration.c
@@ -0,0 +1,215 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <wlr/types/wlr_server_decoration.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/util/log.h>
+#include "server-decoration-protocol.h"
+#include "util/signal.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->mode == mode) {
+ return;
+ }
+ decoration->mode = mode;
+ wlr_signal_emit_safe(&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) {
+ wlr_signal_emit_safe(&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(struct wlr_server_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);
+
+ wlr_signal_emit_safe(&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);
+ }
+}
+
+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;
+ assert(client && manager);
+
+ 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);
+}
+
+void wlr_server_decoration_manager_destroy(
+ struct wlr_server_decoration_manager *manager) {
+ if (manager == NULL) {
+ return;
+ }
+ struct wlr_server_decoration *decoration, *tmp_decoration;
+ wl_list_for_each_safe(decoration, tmp_decoration, &manager->decorations,
+ link) {
+ server_decoration_destroy(decoration);
+ }
+ wlr_signal_emit_safe(&manager->events.destroy, manager);
+ wl_list_remove(&manager->display_destroy.link);
+ struct wl_resource *resource, *tmp_resource;
+ wl_resource_for_each_safe(resource, tmp_resource, &manager->resources) {
+ server_decoration_manager_destroy_resource(resource);
+ }
+ wl_global_destroy(manager->global);
+ free(manager);
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_server_decoration_manager *manager =
+ wl_container_of(listener, manager, display_destroy);
+ wlr_server_decoration_manager_destroy(manager);
+}
+
+struct wlr_server_decoration_manager *wlr_server_decoration_manager_create(
+ struct wl_display *display) {
+ struct wlr_server_decoration_manager *manager =
+ calloc(1, sizeof(struct wlr_server_decoration_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_surface.c b/types/wlr_surface.c
new file mode 100644
index 00000000..27176ef0
--- /dev/null
+++ b/types/wlr_surface.c
@@ -0,0 +1,1061 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wlr/render/interface.h>
+#include <wlr/types/wlr_buffer.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/types/wlr_region.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/util/log.h>
+#include <wlr/util/region.h>
+#include "util/signal.h"
+
+#define CALLBACK_VERSION 1
+#define SURFACE_VERSION 4
+#define SUBSURFACE_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 surface_state_reset_buffer(struct wlr_surface_state *state) {
+ if (state->buffer_resource) {
+ wl_list_remove(&state->buffer_destroy.link);
+ state->buffer_resource = NULL;
+ }
+}
+
+static void surface_handle_buffer_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_surface_state *state =
+ wl_container_of(listener, state, buffer_destroy);
+ surface_state_reset_buffer(state);
+}
+
+static void surface_state_set_buffer(struct wlr_surface_state *state,
+ struct wl_resource *buffer_resource) {
+ surface_state_reset_buffer(state);
+
+ state->buffer_resource = buffer_resource;
+ if (buffer_resource != NULL) {
+ wl_resource_add_destroy_listener(buffer_resource,
+ &state->buffer_destroy);
+ state->buffer_destroy.notify = surface_handle_buffer_destroy;
+ }
+}
+
+static void surface_destroy(struct wl_client *client,
+ struct wl_resource *resource) {
+ wl_resource_destroy(resource);
+}
+
+static void surface_attach(struct wl_client *client,
+ struct wl_resource *resource,
+ struct wl_resource *buffer, int32_t dx, int32_t dy) {
+ struct wlr_surface *surface = wlr_surface_from_resource(resource);
+
+ surface->pending.committed |= WLR_SURFACE_STATE_BUFFER;
+ surface->pending.dx = dx;
+ surface->pending.dy = dy;
+ surface_state_set_buffer(&surface->pending, buffer);
+}
+
+static void surface_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_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_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) {
+ 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_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) {
+ 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_finalize(struct wlr_surface *surface,
+ struct wlr_surface_state *state) {
+ if ((state->committed & WLR_SURFACE_STATE_BUFFER)) {
+ if (state->buffer_resource != NULL) {
+ wlr_buffer_get_resource_size(state->buffer_resource,
+ surface->renderer, &state->buffer_width, &state->buffer_height);
+ } else {
+ state->buffer_width = state->buffer_height = 0;
+ }
+ }
+
+ int width = state->buffer_width / state->scale;
+ int height = state->buffer_height / state->scale;
+ if ((state->transform & WL_OUTPUT_TRANSFORM_90) != 0) {
+ int tmp = width;
+ width = height;
+ height = tmp;
+ }
+ state->width = width;
+ state->height = height;
+
+ pixman_region32_intersect_rect(&state->surface_damage,
+ &state->surface_damage, 0, 0, state->width, state->height);
+
+ pixman_region32_intersect_rect(&state->buffer_damage,
+ &state->buffer_damage, 0, 0, state->buffer_width,
+ state->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);
+
+ if (pending->width != current->width ||
+ pending->height != current->height) {
+ // Damage the whole buffer on resize
+ pixman_region32_union_rect(buffer_damage, buffer_damage, 0, 0,
+ pending->buffer_width, pending->buffer_height);
+ } else {
+ // Copy over surface damage + buffer damage
+ pixman_region32_t surface_damage;
+ pixman_region32_init(&surface_damage);
+
+ pixman_region32_copy(&surface_damage, &pending->surface_damage);
+ wlr_region_transform(&surface_damage, &surface_damage,
+ wlr_output_transform_invert(pending->transform),
+ pending->width, pending->height);
+ wlr_region_scale(&surface_damage, &surface_damage, pending->scale);
+
+ pixman_region32_union(buffer_damage,
+ &pending->buffer_damage, &surface_damage);
+
+ pixman_region32_fini(&surface_damage);
+ }
+}
+
+static void surface_state_copy(struct wlr_surface_state *state,
+ struct wlr_surface_state *next) {
+ 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_BUFFER) {
+ state->dx = next->dx;
+ state->dy = next->dy;
+ } else {
+ state->dx = state->dy = 0;
+ }
+ if (next->committed & WLR_SURFACE_STATE_SURFACE_DAMAGE) {
+ pixman_region32_copy(&state->surface_damage, &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);
+ } 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);
+ }
+
+ state->committed |= next->committed;
+}
+
+/**
+ * Append pending state to current state and clear pending state.
+ */
+static void surface_state_move(struct wlr_surface_state *state,
+ struct wlr_surface_state *next) {
+ surface_state_copy(state, next);
+
+ if (next->committed & WLR_SURFACE_STATE_BUFFER) {
+ surface_state_set_buffer(state, next->buffer_resource);
+ surface_state_reset_buffer(next);
+ next->dx = next->dy = 0;
+ }
+ if (next->committed & WLR_SURFACE_STATE_SURFACE_DAMAGE) {
+ pixman_region32_clear(&next->surface_damage);
+ }
+ if (next->committed & WLR_SURFACE_STATE_BUFFER_DAMAGE) {
+ pixman_region32_clear(&next->buffer_damage);
+ }
+ 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);
+ }
+
+ next->committed = 0;
+}
+
+static void surface_damage_subsurfaces(struct wlr_subsurface *subsurface) {
+ // XXX: This is probably the wrong way to do it, because this damage should
+ // come from the client, but weston doesn't do it correctly either and it
+ // seems to work ok. See the comment on weston_surface_damage for more info
+ // about a better approach.
+ struct wlr_surface *surface = subsurface->surface;
+ pixman_region32_union_rect(&surface->buffer_damage,
+ &surface->buffer_damage, 0, 0,
+ surface->current.buffer_width, surface->current.buffer_height);
+
+ subsurface->reordered = false;
+
+ struct wlr_subsurface *child;
+ wl_list_for_each(child, &subsurface->surface->subsurfaces, parent_link) {
+ surface_damage_subsurfaces(child);
+ }
+}
+
+static void surface_apply_damage(struct wlr_surface *surface) {
+ struct wl_resource *resource = surface->current.buffer_resource;
+ if (resource == NULL) {
+ // NULL commit
+ wlr_buffer_unref(surface->buffer);
+ surface->buffer = NULL;
+ return;
+ }
+
+ if (surface->buffer != NULL && surface->buffer->released) {
+ struct wlr_buffer *updated_buffer = wlr_buffer_apply_damage(
+ surface->buffer, resource, &surface->buffer_damage);
+ if (updated_buffer != NULL) {
+ surface->buffer = updated_buffer;
+ return;
+ }
+ }
+
+ wlr_buffer_unref(surface->buffer);
+ surface->buffer = NULL;
+
+ struct wlr_buffer *buffer = wlr_buffer_create(surface->renderer, resource);
+ if (buffer == NULL) {
+ wlr_log(WLR_ERROR, "Failed to upload buffer");
+ return;
+ }
+
+ surface->buffer = buffer;
+}
+
+static void surface_update_opaque_region(struct wlr_surface *surface) {
+ struct wlr_texture *texture = wlr_surface_get_texture(surface);
+ if (texture == NULL) {
+ pixman_region32_clear(&surface->opaque_region);
+ return;
+ }
+
+ if (wlr_texture_is_opaque(texture)) {
+ 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 void surface_commit_pending(struct wlr_surface *surface) {
+ surface_state_finalize(surface, &surface->pending);
+
+ if (surface->role && surface->role->precommit) {
+ surface->role->precommit(surface);
+ }
+
+ bool invalid_buffer = surface->pending.committed & WLR_SURFACE_STATE_BUFFER;
+
+ surface->sx += surface->pending.dx;
+ surface->sy += surface->pending.dy;
+ surface_update_damage(&surface->buffer_damage,
+ &surface->current, &surface->pending);
+
+ surface_state_copy(&surface->previous, &surface->current);
+ surface_state_move(&surface->current, &surface->pending);
+
+ if (invalid_buffer) {
+ surface_apply_damage(surface);
+ }
+ surface_update_opaque_region(surface);
+ surface_update_input_region(surface);
+
+ // commit subsurface order
+ struct wlr_subsurface *subsurface;
+ wl_list_for_each_reverse(subsurface, &surface->subsurface_pending_list,
+ parent_pending_link) {
+ wl_list_remove(&subsurface->parent_link);
+ wl_list_insert(&surface->subsurfaces, &subsurface->parent_link);
+
+ if (subsurface->reordered) {
+ // TODO: damage all the subsurfaces
+ surface_damage_subsurfaces(subsurface);
+ }
+ }
+
+ if (surface->role && surface->role->commit) {
+ surface->role->commit(surface);
+ }
+
+ wlr_signal_emit_safe(&surface->events.commit, surface);
+}
+
+static bool subsurface_is_synchronized(struct wlr_subsurface *subsurface) {
+ while (subsurface != NULL) {
+ if (subsurface->synchronized) {
+ return true;
+ }
+
+ if (!subsurface->parent) {
+ return false;
+ }
+
+ if (!wlr_surface_is_subsurface(subsurface->parent)) {
+ break;
+ }
+ subsurface = wlr_subsurface_from_wlr_surface(subsurface->parent);
+ }
+
+ return false;
+}
+
+/**
+ * Recursive function to commit the effectively synchronized children.
+ */
+static void subsurface_parent_commit(struct wlr_subsurface *subsurface,
+ bool synchronized) {
+ struct wlr_surface *surface = subsurface->surface;
+ if (synchronized || subsurface->synchronized) {
+ if (subsurface->has_cache) {
+ surface_state_move(&surface->pending, &subsurface->cached);
+ surface_commit_pending(surface);
+ subsurface->has_cache = false;
+ subsurface->cached.committed = 0;
+ }
+
+ struct wlr_subsurface *subsurface;
+ wl_list_for_each(subsurface, &surface->subsurfaces, parent_link) {
+ subsurface_parent_commit(subsurface, true);
+ }
+ }
+}
+
+static void subsurface_commit(struct wlr_subsurface *subsurface) {
+ struct wlr_surface *surface = subsurface->surface;
+
+ if (subsurface_is_synchronized(subsurface)) {
+ surface_state_move(&subsurface->cached, &surface->pending);
+ subsurface->has_cache = true;
+ } else {
+ if (subsurface->has_cache) {
+ surface_state_move(&surface->pending, &subsurface->cached);
+ surface_commit_pending(surface);
+ subsurface->has_cache = false;
+ } else {
+ surface_commit_pending(surface);
+ }
+ }
+}
+
+static void surface_commit(struct wl_client *client,
+ struct wl_resource *resource) {
+ struct wlr_surface *surface = wlr_surface_from_resource(resource);
+
+ struct wlr_subsurface *subsurface = wlr_surface_is_subsurface(surface) ?
+ wlr_subsurface_from_wlr_surface(surface) : NULL;
+ if (subsurface != NULL) {
+ subsurface_commit(subsurface);
+ } else {
+ surface_commit_pending(surface);
+ }
+
+ wl_list_for_each(subsurface, &surface->subsurfaces, parent_link) {
+ subsurface_parent_commit(subsurface, false);
+ }
+}
+
+static void surface_set_buffer_transform(struct wl_client *client,
+ struct wl_resource *resource, int transform) {
+ struct wlr_surface *surface = wlr_surface_from_resource(resource);
+ surface->pending.committed |= WLR_SURFACE_STATE_TRANSFORM;
+ surface->pending.transform = transform;
+}
+
+static void surface_set_buffer_scale(struct wl_client *client,
+ struct wl_resource *resource,
+ int32_t scale) {
+ struct wlr_surface *surface = wlr_surface_from_resource(resource);
+ surface->pending.committed |= WLR_SURFACE_STATE_SCALE;
+ surface->pending.scale = scale;
+}
+
+static void surface_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 const struct wl_surface_interface surface_interface = {
+ .destroy = surface_destroy,
+ .attach = surface_attach,
+ .damage = surface_damage,
+ .frame = surface_frame,
+ .set_opaque_region = surface_set_opaque_region,
+ .set_input_region = surface_set_input_region,
+ .commit = surface_commit,
+ .set_buffer_transform = surface_set_buffer_transform,
+ .set_buffer_scale = surface_set_buffer_scale,
+ .damage_buffer = surface_damage_buffer
+};
+
+struct wlr_surface *wlr_surface_from_resource(struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &wl_surface_interface,
+ &surface_interface));
+ return wl_resource_get_user_data(resource);
+}
+
+static void surface_state_init(struct wlr_surface_state *state) {
+ state->scale = 1;
+ state->transform = WL_OUTPUT_TRANSFORM_NORMAL;
+
+ 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);
+}
+
+static void surface_state_finish(struct wlr_surface_state *state) {
+ surface_state_reset_buffer(state);
+
+ 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);
+}
+
+static void subsurface_destroy(struct wlr_subsurface *subsurface) {
+ if (subsurface == NULL) {
+ return;
+ }
+
+ wlr_signal_emit_safe(&subsurface->events.destroy, subsurface);
+
+ wl_list_remove(&subsurface->surface_destroy.link);
+ surface_state_finish(&subsurface->cached);
+
+ if (subsurface->parent) {
+ wl_list_remove(&subsurface->parent_link);
+ wl_list_remove(&subsurface->parent_pending_link);
+ wl_list_remove(&subsurface->parent_destroy.link);
+ }
+
+ wl_resource_set_user_data(subsurface->resource, NULL);
+ if (subsurface->surface) {
+ subsurface->surface->role_data = NULL;
+ }
+ free(subsurface);
+}
+
+static void surface_handle_resource_destroy(struct wl_resource *resource) {
+ struct wlr_surface *surface = wlr_surface_from_resource(resource);
+
+ wlr_signal_emit_safe(&surface->events.destroy, surface);
+
+ wl_list_remove(wl_resource_get_link(surface->resource));
+
+ wl_list_remove(&surface->renderer_destroy.link);
+ surface_state_finish(&surface->pending);
+ surface_state_finish(&surface->current);
+ surface_state_finish(&surface->previous);
+ pixman_region32_fini(&surface->buffer_damage);
+ pixman_region32_fini(&surface->opaque_region);
+ pixman_region32_fini(&surface->input_region);
+ wlr_buffer_unref(surface->buffer);
+ 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);
+}
+
+struct wlr_surface *wlr_surface_create(struct wl_client *client,
+ uint32_t version, uint32_t id, struct wlr_renderer *renderer,
+ struct wl_list *resource_list) {
+ assert(version <= SURFACE_VERSION);
+
+ struct wlr_surface *surface = calloc(1, sizeof(struct wlr_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_interface,
+ 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_state_init(&surface->pending);
+ surface_state_init(&surface->previous);
+
+ wl_signal_init(&surface->events.commit);
+ wl_signal_init(&surface->events.destroy);
+ wl_signal_init(&surface->events.new_subsurface);
+ wl_list_init(&surface->subsurfaces);
+ wl_list_init(&surface->subsurface_pending_list);
+ pixman_region32_init(&surface->buffer_damage);
+ pixman_region32_init(&surface->opaque_region);
+ pixman_region32_init(&surface->input_region);
+
+ wl_signal_add(&renderer->events.destroy, &surface->renderer_destroy);
+ surface->renderer_destroy.notify = surface_handle_renderer_destroy;
+
+ struct wl_list *resource_link = wl_resource_get_link(surface->resource);
+ if (resource_list != NULL) {
+ wl_list_insert(resource_list, resource_link);
+ } else {
+ wl_list_init(resource_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_get_texture(surface) != NULL;
+}
+
+bool wlr_surface_set_role(struct wlr_surface *surface,
+ const struct wlr_surface_role *role, void *role_data,
+ 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@%d, already has role %s\n",
+ role->name, wl_resource_get_id(surface->resource),
+ surface->role->name);
+ }
+ return false;
+ }
+
+ assert(surface->role_data == NULL);
+ surface->role = role;
+ surface->role_data = role_data;
+ return true;
+}
+
+static const struct wl_subsurface_interface subsurface_implementation;
+
+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_resource_destroy(struct wl_resource *resource) {
+ struct wlr_subsurface *subsurface = subsurface_from_resource(resource);
+ wl_list_remove(wl_resource_get_link(resource));
+ subsurface_destroy(subsurface);
+}
+
+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->subsurfaces, parent_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 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@%d is not a parent or sibling",
+ "place_above", wl_resource_get_id(sibling_surface->resource));
+ return;
+ }
+
+ wl_list_remove(&subsurface->parent_pending_link);
+ wl_list_insert(&sibling->parent_pending_link,
+ &subsurface->parent_pending_link);
+
+ subsurface->reordered = true;
+}
+
+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 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@%d is not a parent or sibling",
+ "place_below", wl_resource_get_id(sibling_surface->resource));
+ return;
+ }
+
+ wl_list_remove(&subsurface->parent_pending_link);
+ wl_list_insert(sibling->parent_pending_link.prev,
+ &subsurface->parent_pending_link);
+
+ subsurface->reordered = true;
+}
+
+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)) {
+ // TODO: do a synchronized commit to flush the cache
+ subsurface_parent_commit(subsurface, true);
+ }
+ }
+}
+
+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,
+};
+
+static void subsurface_role_commit(struct wlr_surface *surface) {
+ struct wlr_subsurface *subsurface =
+ wlr_subsurface_from_wlr_surface(surface);
+ if (subsurface == NULL) {
+ return;
+ }
+
+ if (subsurface->current.x != subsurface->pending.x ||
+ subsurface->current.y != subsurface->pending.y) {
+ // Subsurface has moved
+ int dx = subsurface->current.x - subsurface->pending.x;
+ int dy = subsurface->current.y - subsurface->pending.y;
+
+ subsurface->current.x = subsurface->pending.x;
+ subsurface->current.y = subsurface->pending.y;
+
+ if ((surface->current.transform & WL_OUTPUT_TRANSFORM_90) != 0) {
+ int tmp = dx;
+ dx = dy;
+ dy = tmp;
+ }
+
+ pixman_region32_union_rect(&surface->buffer_damage,
+ &surface->buffer_damage,
+ dx * surface->previous.scale, dy * surface->previous.scale,
+ surface->previous.buffer_width, surface->previous.buffer_height);
+ pixman_region32_union_rect(&surface->buffer_damage,
+ &surface->buffer_damage, 0, 0,
+ surface->current.buffer_width, surface->current.buffer_height);
+ }
+}
+
+const struct wlr_surface_role subsurface_role = {
+ .name = "wl_subsurface",
+ .commit = subsurface_role_commit,
+};
+
+static void subsurface_handle_parent_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_subsurface *subsurface =
+ wl_container_of(listener, subsurface, parent_destroy);
+ wl_list_remove(&subsurface->parent_link);
+ wl_list_remove(&subsurface->parent_pending_link);
+ wl_list_remove(&subsurface->parent_destroy.link);
+ subsurface->parent = NULL;
+}
+
+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);
+}
+
+struct wlr_subsurface *wlr_subsurface_create(struct wlr_surface *surface,
+ struct wlr_surface *parent, uint32_t version, uint32_t id,
+ struct wl_list *resource_list) {
+ assert(version <= SUBSURFACE_VERSION);
+
+ struct wl_client *client = wl_resource_get_client(surface->resource);
+
+ struct wlr_subsurface *subsurface =
+ calloc(1, sizeof(struct wlr_subsurface));
+ if (!subsurface) {
+ wl_client_post_no_memory(client);
+ return NULL;
+ }
+ surface_state_init(&subsurface->cached);
+ subsurface->synchronized = true;
+ subsurface->surface = surface;
+ subsurface->resource =
+ wl_resource_create(client, &wl_subsurface_interface, version, id);
+ if (subsurface->resource == NULL) {
+ surface_state_finish(&subsurface->cached);
+ free(subsurface);
+ wl_client_post_no_memory(client);
+ return NULL;
+ }
+ wl_resource_set_implementation(subsurface->resource,
+ &subsurface_implementation, subsurface,
+ subsurface_resource_destroy);
+
+ wl_signal_init(&subsurface->events.destroy);
+
+ wl_signal_add(&surface->events.destroy, &subsurface->surface_destroy);
+ subsurface->surface_destroy.notify = subsurface_handle_surface_destroy;
+
+ // link parent
+ subsurface->parent = parent;
+ wl_signal_add(&parent->events.destroy, &subsurface->parent_destroy);
+ subsurface->parent_destroy.notify = subsurface_handle_parent_destroy;
+ wl_list_insert(parent->subsurfaces.prev, &subsurface->parent_link);
+ wl_list_insert(parent->subsurface_pending_list.prev,
+ &subsurface->parent_pending_link);
+
+ surface->role_data = subsurface;
+
+ struct wl_list *resource_link = wl_resource_get_link(subsurface->resource);
+ if (resource_list != NULL) {
+ wl_list_insert(resource_list, resource_link);
+ } else {
+ wl_list_init(resource_link);
+ }
+
+ wlr_signal_emit_safe(&parent->events.new_subsurface, subsurface);
+
+ return subsurface;
+}
+
+
+struct wlr_surface *wlr_surface_get_root_surface(struct wlr_surface *surface) {
+ while (wlr_surface_is_subsurface(surface)) {
+ struct wlr_subsurface *subsurface =
+ wlr_subsurface_from_wlr_surface(surface);
+ if (subsurface == NULL) {
+ break;
+ }
+ 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->current.input, 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->subsurfaces, parent_link) {
+ 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)) {
+ *sub_x = sx;
+ *sub_y = sy;
+ return surface;
+ }
+
+ return NULL;
+}
+
+void wlr_surface_send_enter(struct wlr_surface *surface,
+ struct wlr_output *output) {
+ struct wl_client *client = wl_resource_get_client(surface->resource);
+ struct wl_resource *resource;
+ 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 wl_resource *resource;
+ wl_resource_for_each(resource, &output->resources) {
+ if (client == wl_resource_get_client(resource)) {
+ wl_surface_send_leave(surface->resource, resource);
+ }
+ }
+}
+
+static inline int64_t timespec_to_msec(const struct timespec *a) {
+ return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000;
+}
+
+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) {
+ iterator(surface, x, y, user_data);
+
+ struct wlr_subsurface *subsurface;
+ wl_list_for_each(subsurface, &surface->subsurfaces, parent_link) {
+ struct wlr_subsurface_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;
+}
+
+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);
+
+ // On resize, damage the previous bounds of the surface. The current bounds
+ // have already been damaged in surface_update_damage.
+ if (surface->previous.width > surface->current.width ||
+ surface->previous.height > surface->current.height) {
+ pixman_region32_union_rect(damage, damage, 0, 0,
+ surface->previous.width, surface->previous.height);
+ }
+
+ // On move, damage where the surface was with its old dimensions.
+ if (surface->current.dx != 0 || surface->current.dy != 0) {
+ int prev_x = -surface->current.dx;
+ int prev_y = -surface->current.dy;
+ if ((surface->previous.transform & WL_OUTPUT_TRANSFORM_90) != 0) {
+ int temp = prev_x;
+ prev_x = prev_y;
+ prev_y = temp;
+ }
+ pixman_region32_union_rect(damage, damage, prev_x, prev_y,
+ surface->previous.width, surface->previous.height);
+ }
+}
diff --git a/types/wlr_switch.c b/types/wlr_switch.c
new file mode 100644
index 00000000..68cdde9e
--- /dev/null
+++ b/types/wlr_switch.c
@@ -0,0 +1,22 @@
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-server.h>
+#include <wlr/interfaces/wlr_switch.h>
+#include <wlr/types/wlr_switch.h>
+
+void wlr_switch_init(struct wlr_switch *lid_switch,
+ struct wlr_switch_impl *impl) {
+ lid_switch->impl = impl;
+ wl_signal_init(&lid_switch->events.toggle);
+}
+
+void wlr_switch_destroy(struct wlr_switch *lid_switch) {
+ if (!lid_switch) {
+ return;
+ }
+ if (lid_switch->impl && lid_switch->impl->destroy) {
+ lid_switch->impl->destroy(lid_switch);
+ } else {
+ free(lid_switch);
+ }
+}
diff --git a/types/wlr_tablet_pad.c b/types/wlr_tablet_pad.c
new file mode 100644
index 00000000..804d6910
--- /dev/null
+++ b/types/wlr_tablet_pad.c
@@ -0,0 +1,29 @@
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-server.h>
+#include <wlr/interfaces/wlr_tablet_pad.h>
+#include <wlr/types/wlr_tablet_pad.h>
+
+void wlr_tablet_pad_init(struct wlr_tablet_pad *pad,
+ struct wlr_tablet_pad_impl *impl) {
+ pad->impl = impl;
+ 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);
+}
+
+void wlr_tablet_pad_destroy(struct wlr_tablet_pad *pad) {
+ if (!pad) {
+ return;
+ }
+
+ wlr_list_for_each(&pad->paths, free);
+ wlr_list_finish(&pad->paths);
+
+ if (pad->impl && pad->impl->destroy) {
+ pad->impl->destroy(pad);
+ } else {
+ free(pad);
+ }
+}
diff --git a/types/wlr_tablet_tool.c b/types/wlr_tablet_tool.c
new file mode 100644
index 00000000..ca92d4e2
--- /dev/null
+++ b/types/wlr_tablet_tool.c
@@ -0,0 +1,29 @@
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-server.h>
+#include <wlr/interfaces/wlr_tablet_tool.h>
+#include <wlr/types/wlr_tablet_tool.h>
+
+void wlr_tablet_init(struct wlr_tablet *tablet,
+ struct wlr_tablet_impl *impl) {
+ tablet->impl = impl;
+ wl_signal_init(&tablet->events.axis);
+ wl_signal_init(&tablet->events.proximity);
+ wl_signal_init(&tablet->events.tip);
+ wl_signal_init(&tablet->events.button);
+}
+
+void wlr_tablet_destroy(struct wlr_tablet *tablet) {
+ if (!tablet) {
+ return;
+ }
+
+ wlr_list_for_each(&tablet->paths, free);
+ wlr_list_finish(&tablet->paths);
+
+ if (tablet->impl && tablet->impl->destroy) {
+ tablet->impl->destroy(tablet);
+ } else {
+ free(tablet);
+ }
+}
diff --git a/types/wlr_text_input_v3.c b/types/wlr_text_input_v3.c
new file mode 100644
index 00000000..e8f7a613
--- /dev/null
+++ b/types/wlr_text_input_v3.c
@@ -0,0 +1,337 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#include <assert.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <wlr/types/wlr_text_input_v3.h>
+#include <wlr/util/log.h>
+#include "text-input-unstable-v3-protocol.h"
+#include "util/signal.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, uint32_t cursor_begin, uint32_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) {
+ wlr_signal_emit_safe(&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.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.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.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);
+ }
+
+ 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) {
+ wlr_signal_emit_safe(&text_input->events.enable, text_input);
+ } else if (old_enabled && !text_input->current_enabled) {
+ wlr_signal_emit_safe(&text_input->events.disable, text_input);
+ } else { // including never enabled
+ wlr_signal_emit_safe(&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) {
+ struct wlr_text_input_v3 *text_input =
+ calloc(1, sizeof(struct wlr_text_input_v3));
+ 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);
+
+ 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) {
+ free(text_input);
+ wl_client_post_no_memory(client);
+ return;
+ }
+ text_input->resource = text_input_resource;
+
+ wl_resource_set_implementation(text_input->resource, &text_input_impl,
+ text_input, text_input_resource_destroy);
+
+ struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat);
+ 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);
+
+ wlr_signal_emit_safe(&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_unbind(struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+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;
+ assert(wl_client && manager);
+
+ 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_list_insert(&manager->bound_resources, wl_resource_get_link(resource));
+ wl_resource_set_implementation(resource, &text_input_manager_impl,
+ manager, text_input_manager_unbind);
+}
+
+struct wlr_text_input_manager_v3 *wlr_text_input_manager_v3_create(
+ struct wl_display *wl_display) {
+ struct wlr_text_input_manager_v3 *manager =
+ calloc(1, sizeof(struct wlr_text_input_manager_v3));
+ wl_list_init(&manager->bound_resources);
+ wl_list_init(&manager->text_inputs);
+ wl_signal_init(&manager->events.text_input);
+ manager->global = wl_global_create(wl_display,
+ &zwp_text_input_manager_v3_interface, 1, manager,
+ text_input_manager_bind);
+ if (!manager->global) {
+ free(manager);
+ return NULL;
+ }
+ return manager;
+}
+
+void wlr_text_input_manager_v3_destroy(
+ struct wlr_text_input_manager_v3 *manager) {
+ wlr_signal_emit_safe(&manager->events.destroy, manager);
+ wl_list_remove(&manager->display_destroy.link);
+
+ struct wl_resource *resource, *resource_tmp;
+ wl_resource_for_each_safe(resource, resource_tmp,
+ &manager->bound_resources) {
+ wl_resource_destroy(resource);
+ }
+ struct wlr_text_input_v3 *text_input, *text_input_tmp;
+ wl_list_for_each_safe(text_input, text_input_tmp, &manager->text_inputs,
+ link) {
+ wl_resource_destroy(text_input->resource);
+ }
+ wl_global_destroy(manager->global);
+ free(manager);
+}
diff --git a/types/wlr_touch.c b/types/wlr_touch.c
new file mode 100644
index 00000000..ba7ffac7
--- /dev/null
+++ b/types/wlr_touch.c
@@ -0,0 +1,22 @@
+#include <stdlib.h>
+#include <string.h>
+#include <wayland-server.h>
+#include <wlr/interfaces/wlr_touch.h>
+#include <wlr/types/wlr_touch.h>
+
+void wlr_touch_init(struct wlr_touch *touch,
+ struct wlr_touch_impl *impl) {
+ touch->impl = impl;
+ wl_signal_init(&touch->events.down);
+ wl_signal_init(&touch->events.up);
+ wl_signal_init(&touch->events.motion);
+ wl_signal_init(&touch->events.cancel);
+}
+
+void wlr_touch_destroy(struct wlr_touch *touch) {
+ if (touch && touch->impl && touch->impl->destroy) {
+ touch->impl->destroy(touch);
+ } else {
+ free(touch);
+ }
+}
diff --git a/types/wlr_virtual_keyboard_v1.c b/types/wlr_virtual_keyboard_v1.c
new file mode 100644
index 00000000..e5463295
--- /dev/null
+++ b/types/wlr_virtual_keyboard_v1.c
@@ -0,0 +1,248 @@
+#define _POSIX_C_SOURCE 199309L
+#include <assert.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/types/wlr_virtual_keyboard_v1.h>
+#include <wlr/util/log.h>
+#include <xkbcommon/xkbcommon.h>
+#include "util/signal.h"
+#include "virtual-keyboard-unstable-v1-protocol.h"
+
+
+static void keyboard_led_update(struct wlr_keyboard *wlr_kb, uint32_t leds) {
+ // unsupported by virtual keyboard protocol
+}
+
+static void keyboard_destroy(struct wlr_keyboard *wlr_kb) {
+ // safe to ignore - keyboard will be destroyed only iff associated virtual
+ // keyboard is torn down, no need to tear down the keyboard separately
+}
+
+static const struct wlr_keyboard_impl keyboard_impl = {
+ .destroy = keyboard_destroy,
+ .led_update = keyboard_led_update
+};
+
+static void input_device_destroy(struct wlr_input_device *dev) {
+}
+
+static const struct wlr_input_device_impl input_device_impl = {
+ .destroy = input_device_destroy
+};
+
+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);
+}
+
+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);
+
+ 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) {
+ 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->input_device.keyboard, keymap);
+ xkb_keymap_unref(keymap);
+ xkb_context_unref(context);
+ return;
+keymap_fail:
+fd_fail:
+ xkb_context_unref(context);
+context_fail:
+ wl_client_post_no_memory(client);
+}
+
+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);
+ struct wlr_event_keyboard_key event = {
+ .time_msec = time,
+ .keycode = key,
+ .update_state = false,
+ .state = state,
+ };
+ wlr_keyboard_notify_key(keyboard->input_device.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);
+ wlr_keyboard_notify_modifiers(keyboard->input_device.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);
+ wlr_signal_emit_safe(&keyboard->events.destroy, keyboard);
+ wl_list_remove(&keyboard->link);
+ wlr_input_device_destroy(&keyboard->input_device);
+ 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_virtual_keyboard_v1 *virtual_keyboard = calloc(1,
+ sizeof(struct wlr_virtual_keyboard_v1));
+ if (!virtual_keyboard) {
+ wl_client_post_no_memory(client);
+ return;
+ }
+
+ struct wlr_keyboard* keyboard = calloc(1, sizeof(struct wlr_keyboard));
+ if (!keyboard) {
+ wlr_log(WLR_ERROR, "Cannot allocate wlr_keyboard");
+ free(virtual_keyboard);
+ wl_client_post_no_memory(client);
+ return;
+ }
+ wlr_keyboard_init(keyboard, &keyboard_impl);
+
+ struct wl_resource *keyboard_resource = wl_resource_create(client,
+ &zwp_virtual_keyboard_v1_interface, wl_resource_get_version(resource),
+ id);
+ if (!keyboard_resource) {
+ free(keyboard);
+ free(virtual_keyboard);
+ wl_client_post_no_memory(client);
+ return;
+ }
+
+ wl_resource_set_implementation(keyboard_resource, &virtual_keyboard_impl,
+ virtual_keyboard, virtual_keyboard_destroy_resource);
+
+ wlr_input_device_init(&virtual_keyboard->input_device,
+ WLR_INPUT_DEVICE_KEYBOARD, &input_device_impl, "virtual keyboard",
+ 0x0, 0x0);
+
+ struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat);
+
+ virtual_keyboard->input_device.keyboard = keyboard;
+ virtual_keyboard->resource = keyboard_resource;
+ virtual_keyboard->seat = seat_client->seat;
+ wl_signal_init(&virtual_keyboard->events.destroy);
+
+ wl_list_insert(&manager->virtual_keyboards, &virtual_keyboard->link);
+
+ wlr_signal_emit_safe(&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 handle_manager_unbind(struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+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,
+ handle_manager_unbind);
+ wl_list_insert(&manager->resources, wl_resource_get_link(resource));
+}
+
+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);
+ wlr_virtual_keyboard_manager_v1_destroy(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(struct wlr_virtual_keyboard_manager_v1));
+ if (!manager) {
+ return NULL;
+ }
+
+ manager->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &manager->display_destroy);
+
+ wl_list_init(&manager->resources);
+ wl_list_init(&manager->virtual_keyboards);
+
+ wl_signal_init(&manager->events.new_virtual_keyboard);
+ wl_signal_init(&manager->events.destroy);
+ manager->global = wl_global_create(display,
+ &zwp_virtual_keyboard_manager_v1_interface, 1, manager,
+ virtual_keyboard_manager_bind);
+ return manager;
+}
+
+void wlr_virtual_keyboard_manager_v1_destroy(
+ struct wlr_virtual_keyboard_manager_v1 *manager) {
+ wlr_signal_emit_safe(&manager->events.destroy, manager);
+ wl_list_remove(&manager->display_destroy.link);
+ wl_global_destroy(manager->global);
+ struct wl_resource *resource, *resource_tmp;
+ wl_resource_for_each_safe(resource, resource_tmp, &manager->resources) {
+ wl_resource_destroy(resource);
+ }
+ struct wlr_virtual_keyboard_v1 *keyboard, *keyboard_tmp;
+ wl_list_for_each_safe(keyboard, keyboard_tmp, &manager->virtual_keyboards,
+ link) {
+ wl_resource_destroy(keyboard->resource);
+ }
+ free(manager);
+}
diff --git a/types/wlr_wl_shell.c b/types/wlr_wl_shell.c
new file mode 100644
index 00000000..3543b287
--- /dev/null
+++ b/types/wlr_wl_shell.c
@@ -0,0 +1,731 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#include <assert.h>
+#include <stdlib.h>
+#include <wayland-server-protocol.h>
+#include <wayland-server.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/types/wlr_wl_shell.h>
+#include <wlr/util/log.h>
+#include "util/signal.h"
+
+static const struct wlr_surface_role shell_surface_role;
+
+bool wlr_surface_is_wl_shell_surface(struct wlr_surface *surface) {
+ return surface->role == &shell_surface_role;
+}
+
+struct wlr_wl_shell_surface *wlr_wl_shell_surface_from_wlr_surface(
+ struct wlr_surface *surface) {
+ assert(wlr_surface_is_wl_shell_surface(surface));
+ return (struct wlr_wl_shell_surface *)surface->role_data;
+}
+
+static void shell_pointer_grab_end(struct wlr_seat_pointer_grab *grab) {
+ struct wlr_wl_shell_popup_grab *popup_grab = grab->data;
+
+ struct wlr_wl_shell_surface *popup, *tmp = NULL;
+ wl_list_for_each_safe(popup, tmp, &popup_grab->popups, grab_link) {
+ if (popup->popup_mapped) {
+ wl_shell_surface_send_popup_done(popup->resource);
+ popup->popup_mapped = false;
+ }
+ }
+
+ if (grab->seat->pointer_state.grab == grab) {
+ wlr_seat_pointer_end_grab(grab->seat);
+ }
+}
+
+static void shell_pointer_grab_maybe_end(struct wlr_seat_pointer_grab *grab) {
+ struct wlr_wl_shell_popup_grab *popup_grab = grab->data;
+
+ if (grab->seat->pointer_state.grab != grab) {
+ return;
+ }
+
+ bool has_mapped = false;
+
+ struct wlr_wl_shell_surface *popup, *tmp = NULL;
+ wl_list_for_each_safe(popup, tmp, &popup_grab->popups, grab_link) {
+ if (popup->popup_mapped) {
+ has_mapped = true;
+ break;
+ }
+ }
+
+ if (!has_mapped) {
+ shell_pointer_grab_end(grab);
+ }
+}
+
+static void shell_pointer_grab_enter(struct wlr_seat_pointer_grab *grab,
+ struct wlr_surface *surface, double sx, double sy) {
+ struct wlr_wl_shell_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 shell_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 shell_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 {
+ shell_pointer_grab_end(grab);
+ return 0;
+ }
+}
+
+static void shell_pointer_grab_cancel(struct wlr_seat_pointer_grab *grab) {
+ shell_pointer_grab_end(grab);
+}
+
+static void shell_pointer_grab_axis(struct wlr_seat_pointer_grab *grab,
+ uint32_t time, enum wlr_axis_orientation orientation, double value,
+ int32_t value_discrete, enum wlr_axis_source source) {
+ wlr_seat_pointer_send_axis(grab->seat, time, orientation, value,
+ value_discrete, source);
+}
+
+static const struct wlr_pointer_grab_interface shell_pointer_grab_impl = {
+ .enter = shell_pointer_grab_enter,
+ .motion = shell_pointer_grab_motion,
+ .button = shell_pointer_grab_button,
+ .cancel = shell_pointer_grab_cancel,
+ .axis = shell_pointer_grab_axis,
+};
+
+static const struct wl_shell_surface_interface shell_surface_impl;
+
+static struct wlr_wl_shell_surface *shell_surface_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &wl_shell_surface_interface,
+ &shell_surface_impl));
+ return wl_resource_get_user_data(resource);
+}
+
+static void shell_surface_protocol_pong(struct wl_client *client,
+ struct wl_resource *resource, uint32_t serial) {
+ wlr_log(WLR_DEBUG, "got shell surface pong");
+ struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource);
+ if (surface->ping_serial != serial) {
+ return;
+ }
+
+ wl_event_source_timer_update(surface->ping_timer, 0);
+ surface->ping_serial = 0;
+}
+
+static void shell_surface_protocol_move(struct wl_client *client,
+ struct wl_resource *resource, struct wl_resource *seat_resource,
+ uint32_t serial) {
+ struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource);
+ struct wlr_seat_client *seat = wlr_seat_client_from_resource(seat_resource);
+
+ if (!wlr_seat_validate_grab_serial(seat->seat, serial)) {
+ wlr_log(WLR_DEBUG, "invalid serial for grab");
+ return;
+ }
+
+ struct wlr_wl_shell_surface_move_event event = {
+ .surface = surface,
+ .seat = seat,
+ .serial = serial,
+ };
+
+ wlr_signal_emit_safe(&surface->events.request_move, &event);
+}
+
+static struct wlr_wl_shell_popup_grab *shell_popup_grab_from_seat(
+ struct wlr_wl_shell *shell, struct wlr_seat *seat) {
+ struct wlr_wl_shell_popup_grab *shell_grab;
+ wl_list_for_each(shell_grab, &shell->popup_grabs, link) {
+ if (shell_grab->seat == seat) {
+ return shell_grab;
+ }
+ }
+
+ shell_grab = calloc(1, sizeof(struct wlr_wl_shell_popup_grab));
+ if (!shell_grab) {
+ return NULL;
+ }
+
+ shell_grab->pointer_grab.data = shell_grab;
+ shell_grab->pointer_grab.interface = &shell_pointer_grab_impl;
+
+ wl_list_init(&shell_grab->popups);
+
+ wl_list_insert(&shell->popup_grabs, &shell_grab->link);
+ shell_grab->seat = seat;
+
+ return shell_grab;
+}
+
+static void shell_surface_destroy_popup_state(
+ struct wlr_wl_shell_surface *surface) {
+ if (surface->popup_state) {
+ wl_list_remove(&surface->grab_link);
+ struct wlr_wl_shell_popup_grab *grab =
+ shell_popup_grab_from_seat(surface->shell,
+ surface->popup_state->seat);
+ if (wl_list_empty(&grab->popups)) {
+ if (grab->seat->pointer_state.grab == &grab->pointer_grab) {
+ wlr_seat_pointer_end_grab(grab->seat);
+ }
+ }
+ free(surface->popup_state);
+ surface->popup_state = NULL;
+ }
+}
+
+static void shell_surface_protocol_resize(struct wl_client *client,
+ struct wl_resource *resource, struct wl_resource *seat_resource,
+ uint32_t serial, enum wl_shell_surface_resize edges) {
+ struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource);
+ struct wlr_seat_client *seat = wlr_seat_client_from_resource(seat_resource);
+
+ if (!wlr_seat_validate_grab_serial(seat->seat, serial)) {
+ wlr_log(WLR_DEBUG, "invalid serial for grab");
+ return;
+ }
+
+ struct wlr_wl_shell_surface_resize_event event = {
+ .surface = surface,
+ .seat = seat,
+ .serial = serial,
+ .edges = edges,
+ };
+
+ wlr_signal_emit_safe(&surface->events.request_resize, &event);
+}
+
+static void shell_surface_set_state(struct wlr_wl_shell_surface *surface,
+ enum wlr_wl_shell_surface_state state,
+ struct wlr_wl_shell_surface_transient_state *transient_state,
+ struct wlr_wl_shell_surface_popup_state *popup_state) {
+ surface->state = state;
+ free(surface->transient_state);
+ surface->transient_state = transient_state;
+ shell_surface_destroy_popup_state(surface);
+ surface->popup_state = popup_state;
+
+ wlr_signal_emit_safe(&surface->events.set_state, surface);
+}
+
+static void shell_surface_protocol_set_toplevel(struct wl_client *client,
+ struct wl_resource *resource) {
+ wlr_log(WLR_DEBUG, "got shell surface toplevel");
+ struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource);
+ shell_surface_set_state(surface, WLR_WL_SHELL_SURFACE_STATE_TOPLEVEL, NULL,
+ NULL);
+}
+
+static void shell_surface_popup_set_parent(struct wlr_wl_shell_surface *surface,
+ struct wlr_wl_shell_surface *parent) {
+ assert(surface && surface->state == WLR_WL_SHELL_SURFACE_STATE_POPUP);
+ if (surface->parent == parent) {
+ return;
+ }
+ surface->parent = parent;
+ if (parent) {
+ wl_list_remove(&surface->popup_link);
+ wl_list_insert(&parent->popups, &surface->popup_link);
+ wlr_signal_emit_safe(&parent->events.new_popup, surface);
+ }
+}
+
+static struct wlr_wl_shell_surface *shell_find_shell_surface(
+ struct wlr_wl_shell *shell, struct wlr_surface *surface) {
+ if (surface) {
+ struct wlr_wl_shell_surface *wl_surface;
+ wl_list_for_each(wl_surface, &shell->surfaces, link) {
+ if (wl_surface->surface == surface) {
+ return wl_surface;
+ }
+ }
+ }
+ return NULL;
+}
+
+static void shell_surface_protocol_set_transient(struct wl_client *client,
+ struct wl_resource *resource, struct wl_resource *parent_resource,
+ int32_t x, int32_t y, enum wl_shell_surface_transient flags) {
+ wlr_log(WLR_DEBUG, "got shell surface transient");
+ struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource);
+ struct wlr_surface *parent = wlr_surface_from_resource(parent_resource);
+ // TODO: check if parent_resource == NULL?
+
+ struct wlr_wl_shell_surface *wl_parent =
+ shell_find_shell_surface(surface->shell, parent);
+
+ if (!wl_parent) {
+ return;
+ }
+
+ struct wlr_wl_shell_surface_transient_state *transient_state =
+ calloc(1, sizeof(struct wlr_wl_shell_surface_transient_state));
+ if (transient_state == NULL) {
+ wl_client_post_no_memory(client);
+ return;
+ }
+
+ surface->parent = wl_parent;
+ transient_state->x = x;
+ transient_state->y = y;
+ transient_state->flags = flags;
+
+ shell_surface_set_state(surface, WLR_WL_SHELL_SURFACE_STATE_TRANSIENT,
+ transient_state, NULL);
+}
+
+static void shell_surface_protocol_set_fullscreen(struct wl_client *client,
+ struct wl_resource *resource,
+ enum wl_shell_surface_fullscreen_method method, uint32_t framerate,
+ struct wl_resource *output_resource) {
+ struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource);
+ struct wlr_output *output = NULL;
+ if (output_resource != NULL) {
+ output = wlr_output_from_resource(output_resource);
+ }
+
+ shell_surface_set_state(surface, WLR_WL_SHELL_SURFACE_STATE_FULLSCREEN,
+ NULL, NULL);
+
+ struct wlr_wl_shell_surface_set_fullscreen_event event = {
+ .surface = surface,
+ .method = method,
+ .framerate = framerate,
+ .output = output,
+ };
+
+ wlr_signal_emit_safe(&surface->events.request_fullscreen, &event);
+}
+
+static void shell_surface_protocol_set_popup(struct wl_client *client,
+ struct wl_resource *resource, struct wl_resource *seat_resource,
+ uint32_t serial, struct wl_resource *parent_resource, int32_t x,
+ int32_t y, enum wl_shell_surface_transient flags) {
+ struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource);
+ struct wlr_seat_client *seat_client =
+ wlr_seat_client_from_resource(seat_resource);
+ struct wlr_surface *parent = wlr_surface_from_resource(parent_resource);
+ struct wlr_wl_shell_popup_grab *grab =
+ shell_popup_grab_from_seat(surface->shell, seat_client->seat);
+ if (!grab) {
+ wl_client_post_no_memory(client);
+ return;
+ }
+
+ struct wlr_wl_shell_surface *wl_parent =
+ shell_find_shell_surface(surface->shell, parent);
+
+ if (surface->state == WLR_WL_SHELL_SURFACE_STATE_POPUP) {
+ surface->transient_state->x = x;
+ surface->transient_state->y = y;
+ shell_surface_popup_set_parent(surface, wl_parent);
+ grab->client = surface->client;
+ surface->popup_mapped = true;
+ wlr_seat_pointer_start_grab(seat_client->seat, &grab->pointer_grab);
+ return;
+ }
+
+ struct wlr_wl_shell_surface_transient_state *transient_state =
+ calloc(1, sizeof(struct wlr_wl_shell_surface_transient_state));
+ if (transient_state == NULL) {
+ wl_client_post_no_memory(client);
+ return;
+ }
+ transient_state->x = x;
+ transient_state->y = y;
+ transient_state->flags = flags;
+
+ struct wlr_wl_shell_surface_popup_state *popup_state =
+ calloc(1, sizeof(struct wlr_wl_shell_surface_popup_state));
+ if (popup_state == NULL) {
+ free(transient_state);
+ wl_client_post_no_memory(client);
+ return;
+ }
+ popup_state->seat = seat_client->seat;
+ popup_state->serial = serial;
+
+ shell_surface_set_state(surface, WLR_WL_SHELL_SURFACE_STATE_POPUP,
+ transient_state, popup_state);
+
+ shell_surface_popup_set_parent(surface, wl_parent);
+ grab->client = surface->client;
+ wl_list_insert(&grab->popups, &surface->grab_link);
+ surface->popup_mapped = true;
+ wlr_seat_pointer_start_grab(seat_client->seat, &grab->pointer_grab);
+}
+
+static void shell_surface_protocol_set_maximized(struct wl_client *client,
+ struct wl_resource *resource, struct wl_resource *output_resource) {
+ struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource);
+ struct wlr_output *output = NULL;
+ if (output_resource != NULL) {
+ output = wlr_output_from_resource(output_resource);
+ }
+
+ shell_surface_set_state(surface, WLR_WL_SHELL_SURFACE_STATE_MAXIMIZED,
+ NULL, NULL);
+
+ struct wlr_wl_shell_surface_maximize_event event = {
+ .surface = surface,
+ .output = output,
+ };
+
+ wlr_signal_emit_safe(&surface->events.request_maximize, &event);
+}
+
+static void shell_surface_protocol_set_title(struct wl_client *client,
+ struct wl_resource *resource, const char *title) {
+ wlr_log(WLR_DEBUG, "new shell surface title: %s", title);
+ struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource);
+
+ char *tmp = strdup(title);
+ if (tmp == NULL) {
+ return;
+ }
+
+ free(surface->title);
+ surface->title = tmp;
+
+ wlr_signal_emit_safe(&surface->events.set_title, surface);
+}
+
+static void shell_surface_protocol_set_class(struct wl_client *client,
+ struct wl_resource *resource, const char *class) {
+ wlr_log(WLR_DEBUG, "new shell surface class: %s", class);
+ struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource);
+
+ char *tmp = strdup(class);
+ if (tmp == NULL) {
+ return;
+ }
+
+ free(surface->class);
+ surface->class = tmp;
+
+ wlr_signal_emit_safe(&surface->events.set_class, surface);
+}
+
+static const struct wl_shell_surface_interface shell_surface_impl = {
+ .pong = shell_surface_protocol_pong,
+ .move = shell_surface_protocol_move,
+ .resize = shell_surface_protocol_resize,
+ .set_toplevel = shell_surface_protocol_set_toplevel,
+ .set_transient = shell_surface_protocol_set_transient,
+ .set_fullscreen = shell_surface_protocol_set_fullscreen,
+ .set_popup = shell_surface_protocol_set_popup,
+ .set_maximized = shell_surface_protocol_set_maximized,
+ .set_title = shell_surface_protocol_set_title,
+ .set_class = shell_surface_protocol_set_class,
+};
+
+static void shell_surface_destroy(struct wlr_wl_shell_surface *surface) {
+ wlr_signal_emit_safe(&surface->events.destroy, surface);
+ shell_surface_destroy_popup_state(surface);
+ wl_resource_set_user_data(surface->resource, NULL);
+
+ struct wlr_wl_shell_surface *child;
+ wl_list_for_each(child, &surface->popups, popup_link) {
+ shell_surface_popup_set_parent(child, NULL);
+ }
+ wl_list_remove(&surface->popup_link);
+
+ surface->surface->role_data = NULL;
+ wl_list_remove(&surface->link);
+ wl_list_remove(&surface->surface_destroy.link);
+ wl_event_source_remove(surface->ping_timer);
+ free(surface->transient_state);
+ free(surface->title);
+ free(surface->class);
+ free(surface);
+}
+
+static void shell_surface_resource_destroy(struct wl_resource *resource) {
+ struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource);
+ if (surface != NULL) {
+ shell_surface_destroy(surface);
+ }
+}
+
+static void shell_surface_handle_surface_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_wl_shell_surface *surface =
+ wl_container_of(listener, surface, surface_destroy);
+ shell_surface_destroy(surface);
+}
+
+static void shell_surface_role_commit(struct wlr_surface *wlr_surface) {
+ struct wlr_wl_shell_surface *surface =
+ wlr_wl_shell_surface_from_wlr_surface(wlr_surface);
+ if (surface == NULL) {
+ return;
+ }
+
+ if (!surface->configured &&
+ wlr_surface_has_buffer(surface->surface) &&
+ surface->state != WLR_WL_SHELL_SURFACE_STATE_NONE) {
+ surface->configured = true;
+ wlr_signal_emit_safe(&surface->shell->events.new_surface, surface);
+ }
+
+ if (surface->popup_mapped &&
+ surface->state == WLR_WL_SHELL_SURFACE_STATE_POPUP &&
+ !wlr_surface_has_buffer(surface->surface)) {
+ surface->popup_mapped = false;
+ struct wlr_wl_shell_popup_grab *grab =
+ shell_popup_grab_from_seat(surface->shell,
+ surface->popup_state->seat);
+ shell_pointer_grab_maybe_end(&grab->pointer_grab);
+ }
+}
+
+static const struct wlr_surface_role shell_surface_role = {
+ .name = "wl_shell_surface",
+ .commit = shell_surface_role_commit,
+};
+
+static int shell_surface_ping_timeout(void *user_data) {
+ struct wlr_wl_shell_surface *surface = user_data;
+ wlr_signal_emit_safe(&surface->events.ping_timeout, surface);
+
+ surface->ping_serial = 0;
+ return 1;
+}
+
+static const struct wl_shell_interface shell_impl;
+
+static struct wlr_wl_shell *shell_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &wl_shell_interface, &shell_impl));
+ return wl_resource_get_user_data(resource);
+}
+
+static void shell_protocol_get_shell_surface(struct wl_client *client,
+ struct wl_resource *shell_resource, uint32_t id,
+ struct wl_resource *surface_resource) {
+ struct wlr_surface *surface = wlr_surface_from_resource(surface_resource);
+
+ struct wlr_wl_shell *wl_shell = shell_from_resource(shell_resource);
+ struct wlr_wl_shell_surface *wl_surface =
+ calloc(1, sizeof(struct wlr_wl_shell_surface));
+ if (wl_surface == NULL) {
+ wl_resource_post_no_memory(shell_resource);
+ return;
+ }
+
+ if (!wlr_surface_set_role(surface, &shell_surface_role, wl_surface,
+ shell_resource, WL_SHELL_ERROR_ROLE)) {
+ free(wl_surface);
+ return;
+ }
+
+ wl_list_init(&wl_surface->grab_link);
+ wl_list_init(&wl_surface->popup_link);
+ wl_list_init(&wl_surface->popups);
+
+ wl_surface->shell = wl_shell;
+ wl_surface->client = client;
+ wl_surface->surface = surface;
+
+ wl_surface->resource = wl_resource_create(client,
+ &wl_shell_surface_interface, wl_resource_get_version(shell_resource),
+ id);
+ if (wl_surface->resource == NULL) {
+ free(wl_surface);
+ wl_resource_post_no_memory(shell_resource);
+ return;
+ }
+ wl_resource_set_implementation(wl_surface->resource,
+ &shell_surface_impl, wl_surface,
+ shell_surface_resource_destroy);
+
+ wlr_log(WLR_DEBUG, "new wl_shell %p (res %p)", wl_surface,
+ wl_surface->resource);
+
+ wl_signal_init(&wl_surface->events.destroy);
+ wl_signal_init(&wl_surface->events.ping_timeout);
+ wl_signal_init(&wl_surface->events.new_popup);
+ wl_signal_init(&wl_surface->events.request_move);
+ wl_signal_init(&wl_surface->events.request_resize);
+ wl_signal_init(&wl_surface->events.request_fullscreen);
+ wl_signal_init(&wl_surface->events.request_maximize);
+ wl_signal_init(&wl_surface->events.set_state);
+ wl_signal_init(&wl_surface->events.set_title);
+ wl_signal_init(&wl_surface->events.set_class);
+
+ wl_signal_add(&wl_surface->surface->events.destroy,
+ &wl_surface->surface_destroy);
+ wl_surface->surface_destroy.notify = shell_surface_handle_surface_destroy;
+
+ struct wl_display *display = wl_client_get_display(client);
+ struct wl_event_loop *loop = wl_display_get_event_loop(display);
+ wl_surface->ping_timer = wl_event_loop_add_timer(loop,
+ shell_surface_ping_timeout, wl_surface);
+ if (wl_surface->ping_timer == NULL) {
+ wl_client_post_no_memory(client);
+ }
+
+ wl_list_insert(&wl_shell->surfaces, &wl_surface->link);
+}
+
+static const struct wl_shell_interface shell_impl = {
+ .get_shell_surface = shell_protocol_get_shell_surface
+};
+
+static void shell_destroy(struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+static void shell_bind(struct wl_client *wl_client, void *data,
+ uint32_t version, uint32_t id) {
+ struct wlr_wl_shell *wl_shell = data;
+ assert(wl_client && wl_shell);
+
+ struct wl_resource *wl_resource = wl_resource_create(wl_client,
+ &wl_shell_interface, version, id);
+ if (wl_resource == NULL) {
+ wl_client_post_no_memory(wl_client);
+ return;
+ }
+ wl_resource_set_implementation(wl_resource, &shell_impl, wl_shell,
+ shell_destroy);
+ wl_list_insert(&wl_shell->resources, wl_resource_get_link(wl_resource));
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_wl_shell *wl_shell =
+ wl_container_of(listener, wl_shell, display_destroy);
+ wlr_wl_shell_destroy(wl_shell);
+}
+
+struct wlr_wl_shell *wlr_wl_shell_create(struct wl_display *display) {
+ struct wlr_wl_shell *wl_shell = calloc(1, sizeof(struct wlr_wl_shell));
+ if (!wl_shell) {
+ return NULL;
+ }
+ wl_shell->ping_timeout = 10000;
+ struct wl_global *global = wl_global_create(display, &wl_shell_interface,
+ 1, wl_shell, shell_bind);
+ if (!global) {
+ free(wl_shell);
+ return NULL;
+ }
+ wl_shell->global = global;
+ wl_list_init(&wl_shell->resources);
+ wl_list_init(&wl_shell->surfaces);
+ wl_list_init(&wl_shell->popup_grabs);
+ wl_signal_init(&wl_shell->events.new_surface);
+
+ wl_shell->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &wl_shell->display_destroy);
+
+ return wl_shell;
+}
+
+void wlr_wl_shell_destroy(struct wlr_wl_shell *wlr_wl_shell) {
+ if (!wlr_wl_shell) {
+ return;
+ }
+ wl_list_remove(&wlr_wl_shell->display_destroy.link);
+ struct wl_resource *resource = NULL, *temp = NULL;
+ wl_resource_for_each_safe(resource, temp, &wlr_wl_shell->resources) {
+ // shell_destroy will remove the resource from the list
+ wl_resource_destroy(resource);
+ }
+ // TODO: destroy surfaces
+ wl_global_destroy(wlr_wl_shell->global);
+ free(wlr_wl_shell);
+}
+
+void wlr_wl_shell_surface_ping(struct wlr_wl_shell_surface *surface) {
+ if (surface->ping_serial != 0) {
+ // already pinged
+ return;
+ }
+
+ surface->ping_serial =
+ wl_display_next_serial(wl_client_get_display(surface->client));
+ wl_event_source_timer_update(surface->ping_timer,
+ surface->shell->ping_timeout);
+ wl_shell_surface_send_ping(surface->resource, surface->ping_serial);
+}
+
+void wlr_wl_shell_surface_configure(struct wlr_wl_shell_surface *surface,
+ uint32_t edges, int32_t width, int32_t height) {
+ wl_shell_surface_send_configure(surface->resource, edges, width, height);
+}
+
+struct wlr_surface *wlr_wl_shell_surface_surface_at(
+ struct wlr_wl_shell_surface *surface, double sx, double sy,
+ double *sub_sx, double *sub_sy) {
+ struct wlr_wl_shell_surface *popup;
+ wl_list_for_each(popup, &surface->popups, popup_link) {
+ if (!popup->popup_mapped) {
+ continue;
+ }
+
+ double popup_sx = popup->transient_state->x;
+ double popup_sy = popup->transient_state->y;
+ struct wlr_surface *sub = wlr_wl_shell_surface_surface_at(popup,
+ sx - popup_sx, sy - popup_sy, sub_sx, sub_sy);
+ if (sub != NULL) {
+ return sub;
+ }
+ }
+
+ return wlr_surface_surface_at(surface->surface, sx, sy, sub_sx, sub_sy);
+}
+
+struct wl_shell_surface_iterator_data {
+ wlr_surface_iterator_func_t user_iterator;
+ void *user_data;
+ int x, y;
+};
+
+static void wl_shell_surface_iterator(struct wlr_surface *surface,
+ int sx, int sy, void *data) {
+ struct wl_shell_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 wl_shell_surface_for_each_surface(
+ struct wlr_wl_shell_surface *surface, int x, int y,
+ wlr_surface_iterator_func_t iterator, void *user_data) {
+ struct wl_shell_surface_iterator_data data = {
+ .user_iterator = iterator,
+ .user_data = user_data,
+ .x = x, .y = y,
+ };
+ wlr_surface_for_each_surface(surface->surface, wl_shell_surface_iterator,
+ &data);
+
+ struct wlr_wl_shell_surface *popup;
+ wl_list_for_each(popup, &surface->popups, popup_link) {
+ double popup_x = popup->transient_state->x;
+ double popup_y = popup->transient_state->y;
+
+ wl_shell_surface_for_each_surface(popup, x + popup_x, y + popup_y,
+ iterator, user_data);
+ }
+}
+
+void wlr_wl_shell_surface_for_each_surface(struct wlr_wl_shell_surface *surface,
+ wlr_surface_iterator_func_t iterator, void *user_data) {
+ wl_shell_surface_for_each_surface(surface, 0, 0, iterator, user_data);
+}
diff --git a/types/wlr_xcursor_manager.c b/types/wlr_xcursor_manager.c
new file mode 100644
index 00000000..7333a7a1
--- /dev/null
+++ b/types/wlr_xcursor_manager.c
@@ -0,0 +1,84 @@
+#define _POSIX_C_SOURCE 200809L
+#include <stdlib.h>
+#include <string.h>
+#include <wlr/types/wlr_xcursor_manager.h>
+
+struct wlr_xcursor_manager *wlr_xcursor_manager_create(const char *name,
+ uint32_t size) {
+ struct wlr_xcursor_manager *manager =
+ calloc(1, sizeof(struct wlr_xcursor_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);
+}
+
+int 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 0;
+ }
+ }
+
+ theme = calloc(1, sizeof(struct wlr_xcursor_manager_theme));
+ if (theme == NULL) {
+ return 1;
+ }
+ theme->scale = scale;
+ theme->theme = wlr_xcursor_theme_load(manager->name, manager->size * scale);
+ if (theme->theme == NULL) {
+ free(theme);
+ return 1;
+ }
+ wl_list_insert(&manager->scaled_themes, &theme->link);
+ return 0;
+}
+
+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;
+}
+
+void wlr_xcursor_manager_set_cursor_image(struct wlr_xcursor_manager *manager,
+ const char *name, struct wlr_cursor *cursor) {
+ struct wlr_xcursor_manager_theme *theme;
+ wl_list_for_each(theme, &manager->scaled_themes, link) {
+ struct wlr_xcursor *xcursor =
+ wlr_xcursor_theme_get_cursor(theme->theme, name);
+ if (xcursor == NULL) {
+ continue;
+ }
+
+ struct wlr_xcursor_image *image = xcursor->images[0];
+ wlr_cursor_set_image(cursor, image->buffer, image->width * 4,
+ image->width, image->height, image->hotspot_x, image->hotspot_y,
+ theme->scale);
+ }
+}
diff --git a/types/wlr_xdg_decoration_v1.c b/types/wlr_xdg_decoration_v1.c
new file mode 100644
index 00000000..607b75e4
--- /dev/null
+++ b/types/wlr_xdg_decoration_v1.c
@@ -0,0 +1,310 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wlr/types/wlr_xdg_decoration_v1.h>
+#include <wlr/util/log.h>
+#include "util/signal.h"
+#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->client_pending_mode =
+ (enum wlr_xdg_toplevel_decoration_v1_mode)mode;
+ wlr_signal_emit_safe(&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->client_pending_mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE;
+ wlr_signal_emit_safe(&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->server_pending_mode = mode;
+ return wlr_xdg_surface_schedule_configure(decoration->surface);
+}
+
+static void toplevel_decoration_handle_resource_destroy(
+ struct wl_resource *resource) {
+ struct wlr_xdg_toplevel_decoration_v1 *decoration =
+ toplevel_decoration_from_resource(resource);
+ wlr_signal_emit_safe(&decoration->events.destroy, decoration);
+ wl_list_remove(&decoration->surface_commit.link);
+ wl_list_remove(&decoration->surface_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_surface_destroy(
+ struct wl_listener *listener, void *data) {
+ struct wlr_xdg_toplevel_decoration_v1 *decoration =
+ wl_container_of(listener, decoration, surface_destroy);
+ 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->current_mode == decoration->server_pending_mode) {
+ return;
+ }
+
+ struct wlr_xdg_toplevel_decoration_v1_configure *configure =
+ calloc(1, sizeof(struct wlr_xdg_toplevel_decoration_v1_configure));
+ if (configure == NULL) {
+ return;
+ }
+ configure->surface_configure = surface_configure;
+ configure->mode = decoration->server_pending_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;
+
+ bool found = false;
+ struct wlr_xdg_toplevel_decoration_v1_configure *configure;
+ wl_list_for_each(configure, &decoration->configure_list, link) {
+ if (configure->surface_configure == surface_configure) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return;
+ }
+
+ decoration->current_mode = configure->mode;
+
+ wl_list_remove(&configure->link);
+ free(configure);
+}
+
+static void toplevel_decoration_handle_surface_commit(
+ struct wl_listener *listener, void *data) {
+ struct wlr_xdg_toplevel_decoration_v1 *decoration =
+ wl_container_of(listener, decoration, surface_commit);
+ struct wlr_xdg_decoration_manager_v1 *manager = decoration->manager;
+
+ if (decoration->surface->added) {
+ wl_list_remove(&decoration->surface_commit.link);
+ wl_list_init(&decoration->surface_commit.link);
+
+ decoration->added = true;
+ wlr_signal_emit_safe(&manager->events.new_toplevel_decoration,
+ decoration);
+ }
+}
+
+
+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_surface *surface =
+ wlr_xdg_surface_from_toplevel_resource(toplevel_resource);
+ assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
+
+ if (wlr_surface_has_buffer(surface->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 *decoration =
+ calloc(1, sizeof(struct wlr_xdg_toplevel_decoration_v1));
+ if (decoration == NULL) {
+ wl_client_post_no_memory(client);
+ return;
+ }
+ decoration->manager = manager;
+ decoration->surface = surface;
+
+ 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) {
+ 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(&surface->events.destroy, &decoration->surface_destroy);
+ decoration->surface_destroy.notify =
+ toplevel_decoration_handle_surface_destroy;
+ wl_signal_add(&surface->events.configure, &decoration->surface_configure);
+ decoration->surface_configure.notify =
+ toplevel_decoration_handle_surface_configure;
+ wl_signal_add(&surface->events.ack_configure,
+ &decoration->surface_ack_configure);
+ decoration->surface_ack_configure.notify =
+ toplevel_decoration_handle_surface_ack_configure;
+ wl_list_init(&decoration->surface_commit.link);
+
+ wl_list_insert(&manager->decorations, &decoration->link);
+
+ if (surface->added) {
+ decoration->added = true;
+ wlr_signal_emit_safe(&manager->events.new_toplevel_decoration,
+ decoration);
+ } else {
+ wl_list_remove(&decoration->surface_commit.link);
+ wl_signal_add(&surface->surface->events.commit,
+ &decoration->surface_commit);
+ decoration->surface_commit.notify =
+ toplevel_decoration_handle_surface_commit;
+ }
+}
+
+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,
+};
+
+void decoration_manager_handle_resource_destroy(struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+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, decoration_manager_handle_resource_destroy);
+
+ wl_list_insert(&manager->resources, wl_resource_get_link(resource));
+}
+
+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);
+ wlr_xdg_decoration_manager_v1_destroy(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(struct wlr_xdg_decoration_manager_v1));
+ 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->resources);
+ 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;
+}
+
+void wlr_xdg_decoration_manager_v1_destroy(
+ struct wlr_xdg_decoration_manager_v1 *manager) {
+ if (manager == NULL) {
+ return;
+ }
+ wlr_signal_emit_safe(&manager->events.destroy, manager);
+ wl_list_remove(&manager->display_destroy.link);
+ struct wlr_xdg_toplevel_decoration_v1 *decoration, *tmp_decoration;
+ wl_list_for_each_safe(decoration, tmp_decoration, &manager->decorations,
+ link) {
+ wl_resource_destroy(decoration->resource);
+ }
+ struct wl_resource *resource, *tmp_resource;
+ wl_resource_for_each_safe(resource, tmp_resource, &manager->resources) {
+ wl_resource_destroy(resource);
+ }
+ wl_global_destroy(manager->global);
+ free(manager);
+}
diff --git a/types/wlr_xdg_output_v1.c b/types/wlr_xdg_output_v1.c
new file mode 100644
index 00000000..5e8419ae
--- /dev/null
+++ b/types/wlr_xdg_output_v1.c
@@ -0,0 +1,256 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_xdg_output_v1.h>
+#include <wlr/util/log.h>
+#include "xdg-output-unstable-v1-protocol.h"
+#include "util/signal.h"
+
+#define OUTPUT_MANAGER_VERSION 2
+
+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) {
+ struct wlr_output *output = xdg_output->layout_output->output;
+
+ 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);
+
+ uint32_t version = wl_resource_get_version(resource);
+ if (version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) {
+ zxdg_output_v1_send_name(resource, output->name);
+ }
+ if (version >= ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION) {
+ char description[128];
+ snprintf(description, sizeof(description), "%s %s %s (%s)",
+ output->make, output->model, output->serial, output->name);
+ zxdg_output_v1_send_description(resource, description);
+ }
+
+ 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);
+ }
+ }
+}
+
+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->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 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);
+
+ 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);
+
+ wl_list_insert(&xdg_output->resources,
+ wl_resource_get_link(xdg_output_resource));
+
+ output_send_details(xdg_output, xdg_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_handle_resource_destroy(
+ struct wl_resource *resource) {
+ wl_list_remove(wl_resource_get_link(resource));
+}
+
+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, output_manager_handle_resource_destroy);
+ wl_list_insert(&manager->resources, wl_resource_get_link(resource));
+}
+
+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 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(struct wlr_xdg_output_v1));
+ 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);
+ 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 handle_layout_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_xdg_output_manager_v1 *manager =
+ wl_container_of(listener, manager, layout_destroy);
+ wlr_xdg_output_manager_v1_destroy(manager);
+}
+
+struct wlr_xdg_output_manager_v1 *wlr_xdg_output_manager_v1_create(
+ struct wl_display *display, struct wlr_output_layout *layout) {
+ assert(display && layout);
+ struct wlr_xdg_output_manager_v1 *manager =
+ calloc(1, sizeof(struct wlr_xdg_output_manager_v1));
+ 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->resources);
+ 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);
+ return manager;
+}
+
+void wlr_xdg_output_manager_v1_destroy(struct wlr_xdg_output_manager_v1 *manager) {
+ struct wlr_xdg_output_v1 *output, *output_tmp;
+ wl_list_for_each_safe(output, output_tmp, &manager->outputs, link) {
+ output_destroy(output);
+ }
+ struct wl_resource *resource, *resource_tmp;
+ wl_resource_for_each_safe(resource, resource_tmp, &manager->resources) {
+ wl_resource_destroy(resource);
+ }
+ wlr_signal_emit_safe(&manager->events.destroy, manager);
+ wl_list_remove(&manager->layout_add.link);
+ wl_list_remove(&manager->layout_change.link);
+ wl_list_remove(&manager->layout_destroy.link);
+ free(manager);
+}
diff --git a/types/xdg_shell/wlr_xdg_popup.c b/types/xdg_shell/wlr_xdg_popup.c
new file mode 100644
index 00000000..02dbec73
--- /dev/null
+++ b/types/xdg_shell/wlr_xdg_popup.c
@@ -0,0 +1,512 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include "types/wlr_xdg_shell.h"
+#include "util/signal.h"
+
+static void xdg_pointer_grab_end(struct wlr_seat_pointer_grab *grab) {
+ struct wlr_xdg_popup_grab *popup_grab = grab->data;
+
+ 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(grab->seat);
+ wlr_seat_keyboard_end_grab(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_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_pointer_grab_end(grab);
+ return 0;
+ }
+}
+
+static void xdg_pointer_grab_axis(struct wlr_seat_pointer_grab *grab,
+ uint32_t time, enum wlr_axis_orientation orientation, double value,
+ int32_t value_discrete, enum wlr_axis_source source) {
+ wlr_seat_pointer_send_axis(grab->seat, time, orientation, value,
+ value_discrete, source);
+}
+
+static void xdg_pointer_grab_cancel(struct wlr_seat_pointer_grab *grab) {
+ xdg_pointer_grab_end(grab);
+}
+
+static const struct wlr_pointer_grab_interface xdg_pointer_grab_impl = {
+ .enter = xdg_pointer_grab_enter,
+ .motion = xdg_pointer_grab_motion,
+ .button = xdg_pointer_grab_button,
+ .cancel = xdg_pointer_grab_cancel,
+ .axis = xdg_pointer_grab_axis,
+};
+
+static void xdg_keyboard_grab_enter(struct wlr_seat_keyboard_grab *grab,
+ struct wlr_surface *surface, uint32_t keycodes[], size_t num_keycodes,
+ struct wlr_keyboard_modifiers *modifiers) {
+ // 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,
+ 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,
+ .key = xdg_keyboard_grab_key,
+ .modifiers = xdg_keyboard_grab_modifiers,
+ .cancel = xdg_keyboard_grab_cancel,
+};
+
+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);
+
+ wl_list_remove(&xdg_grab->seat_destroy.link);
+
+ struct wlr_xdg_popup *popup, *next;
+ wl_list_for_each_safe(popup, next, &xdg_grab->popups, grab_link) {
+ destroy_xdg_surface(popup->base);
+ }
+
+ wl_list_remove(&xdg_grab->link);
+ free(xdg_grab);
+}
+
+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(struct wlr_xdg_popup_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;
+
+ 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;
+}
+
+
+static const struct xdg_popup_interface xdg_popup_implementation;
+
+struct wlr_xdg_surface *wlr_xdg_surface_from_popup_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &xdg_popup_interface,
+ &xdg_popup_implementation));
+ return wl_resource_get_user_data(resource);
+}
+
+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_surface *surface =
+ wlr_xdg_surface_from_popup_resource(resource);
+ struct wlr_seat_client *seat_client =
+ wlr_seat_client_from_resource(seat_resource);
+ if (!surface) {
+ return;
+ }
+
+ if (surface->popup->committed) {
+ wl_resource_post_error(surface->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(
+ surface->client->shell, seat_client->seat);
+
+ if (!wl_list_empty(&surface->popups)) {
+ wl_resource_post_error(surface->client->resource,
+ XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP,
+ "xdg_popup was not created on the topmost popup");
+ return;
+ }
+
+ popup_grab->client = surface->client->client;
+ surface->popup->seat = seat_client->seat;
+
+ wl_list_insert(&popup_grab->popups, &surface->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);
+}
+
+static void xdg_popup_handle_destroy(struct wl_client *client,
+ struct wl_resource *resource) {
+ struct wlr_xdg_surface *surface =
+ wlr_xdg_surface_from_popup_resource(resource);
+
+ if (surface && !wl_list_empty(&surface->popups)) {
+ wl_resource_post_error(surface->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,
+};
+
+static void xdg_popup_handle_resource_destroy(struct wl_resource *resource) {
+ struct wlr_xdg_surface *surface =
+ wlr_xdg_surface_from_popup_resource(resource);
+ destroy_xdg_popup(surface);
+}
+
+const struct wlr_surface_role xdg_popup_surface_role = {
+ .name = "xdg_popup",
+ .commit = handle_xdg_surface_commit,
+ .precommit = handle_xdg_surface_precommit,
+};
+
+void create_xdg_popup(struct wlr_xdg_surface *xdg_surface,
+ struct wlr_xdg_surface *parent,
+ struct wlr_xdg_positioner_resource *positioner, int32_t id) {
+ if (positioner->attrs.size.width == 0 ||
+ positioner->attrs.anchor_rect.width == 0) {
+ wl_resource_post_error(xdg_surface->resource,
+ XDG_WM_BASE_ERROR_INVALID_POSITIONER,
+ "positioner object is not complete");
+ return;
+ }
+
+ if (!wlr_surface_set_role(xdg_surface->surface, &xdg_popup_surface_role,
+ xdg_surface, xdg_surface->resource, XDG_WM_BASE_ERROR_ROLE)) {
+ return;
+ }
+
+ if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_NONE) {
+ wl_resource_post_error(xdg_surface->resource,
+ XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED,
+ "xdg-surface has already been constructed");
+ return;
+ }
+
+ assert(xdg_surface->popup == NULL);
+ xdg_surface->popup = calloc(1, sizeof(struct wlr_xdg_popup));
+ if (!xdg_surface->popup) {
+ wl_resource_post_no_memory(xdg_surface->resource);
+ return;
+ }
+ xdg_surface->popup->base = xdg_surface;
+
+ xdg_surface->popup->resource = wl_resource_create(
+ xdg_surface->client->client, &xdg_popup_interface,
+ wl_resource_get_version(xdg_surface->resource), id);
+ if (xdg_surface->popup->resource == NULL) {
+ free(xdg_surface->popup);
+ wl_resource_post_no_memory(xdg_surface->resource);
+ return;
+ }
+ wl_resource_set_implementation(xdg_surface->popup->resource,
+ &xdg_popup_implementation, xdg_surface,
+ xdg_popup_handle_resource_destroy);
+
+ xdg_surface->role = WLR_XDG_SURFACE_ROLE_POPUP;
+
+ // positioner properties
+ memcpy(&xdg_surface->popup->positioner, &positioner->attrs,
+ sizeof(struct wlr_xdg_positioner));
+ xdg_surface->popup->geometry =
+ wlr_xdg_positioner_get_geometry(&positioner->attrs);
+
+ if (parent) {
+ xdg_surface->popup->parent = parent->surface;
+ wl_list_insert(&parent->popups, &xdg_surface->popup->link);
+ wlr_signal_emit_safe(&parent->events.new_popup, xdg_surface->popup);
+ } else {
+ wl_list_init(&xdg_surface->popup->link);
+ }
+}
+
+void destroy_xdg_popup(struct wlr_xdg_surface *xdg_surface) {
+ if (xdg_surface == NULL) {
+ return;
+ }
+ assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP);
+ reset_xdg_surface(xdg_surface);
+}
+
+void wlr_xdg_popup_get_anchor_point(struct wlr_xdg_popup *popup,
+ int *root_sx, int *root_sy) {
+ struct wlr_box rect = popup->positioner.anchor_rect;
+ enum xdg_positioner_anchor anchor = popup->positioner.anchor;
+ int sx = 0, sy = 0;
+
+ if (anchor == XDG_POSITIONER_ANCHOR_NONE) {
+ sx = (rect.x + rect.width) / 2;
+ sy = (rect.y + rect.height) / 2;
+ } else if (anchor == XDG_POSITIONER_ANCHOR_TOP) {
+ sx = (rect.x + rect.width) / 2;
+ sy = rect.y;
+ } else if (anchor == XDG_POSITIONER_ANCHOR_BOTTOM) {
+ sx = (rect.x + rect.width) / 2;
+ sy = rect.y + rect.height;
+ } else if (anchor == XDG_POSITIONER_ANCHOR_LEFT) {
+ sx = rect.x;
+ sy = (rect.y + rect.height) / 2;
+ } else if (anchor == XDG_POSITIONER_ANCHOR_RIGHT) {
+ sx = rect.x + rect.width;
+ sy = (rect.y + rect.height) / 2;
+ } else if (anchor == XDG_POSITIONER_ANCHOR_TOP_LEFT) {
+ sx = rect.x;
+ sy = rect.y;
+ } else if (anchor == XDG_POSITIONER_ANCHOR_TOP_RIGHT) {
+ sx = rect.x + rect.width;
+ sy = rect.y;
+ } else if (anchor == XDG_POSITIONER_ANCHOR_BOTTOM_LEFT) {
+ sx = rect.x;
+ sy = rect.y + rect.height;
+ } else if (anchor == XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT) {
+ sx = rect.x + rect.width;
+ sy = rect.y + rect.height;
+ }
+
+ *root_sx = sx;
+ *root_sy = sy;
+}
+
+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;
+ while (wlr_surface_is_xdg_surface(parent)) {
+ struct wlr_xdg_surface *xdg_surface =
+ wlr_xdg_surface_from_wlr_surface(parent);
+
+ if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) {
+ popup_sx += xdg_surface->popup->geometry.x;
+ popup_sy += xdg_surface->popup->geometry.y;
+ parent = xdg_surface->popup->parent;
+ } else {
+ popup_sx += xdg_surface->geometry.x;
+ popup_sy += xdg_surface->geometry.y;
+ break;
+ }
+ }
+ assert(parent);
+
+ *toplevel_sx = popup_sx;
+ *toplevel_sy = popup_sy;
+}
+
+static void xdg_popup_box_constraints(struct wlr_xdg_popup *popup,
+ struct wlr_box *toplevel_sx_box, int *offset_x, int *offset_y) {
+ int popup_width = popup->geometry.width;
+ int popup_height = popup->geometry.height;
+ int anchor_sx = 0, anchor_sy = 0;
+ wlr_xdg_popup_get_anchor_point(popup, &anchor_sx, &anchor_sy);
+ int popup_sx = 0, popup_sy = 0;
+ wlr_xdg_popup_get_toplevel_coords(popup, popup->geometry.x,
+ popup->geometry.y, &popup_sx, &popup_sy);
+ *offset_x = 0, *offset_y = 0;
+
+ if (popup_sx < toplevel_sx_box->x) {
+ *offset_x = toplevel_sx_box->x - popup_sx;
+ } else if (popup_sx + popup_width >
+ toplevel_sx_box->x + toplevel_sx_box->width) {
+ *offset_x = toplevel_sx_box->x + toplevel_sx_box->width -
+ (popup_sx + popup_width);
+ }
+
+ if (popup_sy < toplevel_sx_box->y) {
+ *offset_y = toplevel_sx_box->y - popup_sy;
+ } else if (popup_sy + popup_height >
+ toplevel_sx_box->y + toplevel_sx_box->height) {
+ *offset_y = toplevel_sx_box->y + toplevel_sx_box->height -
+ (popup_sy + popup_height);
+ }
+}
+
+static bool xdg_popup_unconstrain_flip(struct wlr_xdg_popup *popup,
+ struct wlr_box *toplevel_sx_box) {
+ int offset_x = 0, offset_y = 0;
+ xdg_popup_box_constraints(popup, toplevel_sx_box,
+ &offset_x, &offset_y);
+
+ if (!offset_x && !offset_y) {
+ return true;
+ }
+
+ bool flip_x = offset_x &&
+ (popup->positioner.constraint_adjustment &
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X);
+
+ bool flip_y = offset_y &&
+ (popup->positioner.constraint_adjustment &
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y);
+
+ if (flip_x) {
+ wlr_positioner_invert_x(&popup->positioner);
+ }
+ if (flip_y) {
+ wlr_positioner_invert_y(&popup->positioner);
+ }
+
+ popup->geometry =
+ wlr_xdg_positioner_get_geometry(&popup->positioner);
+
+ xdg_popup_box_constraints(popup, toplevel_sx_box,
+ &offset_x, &offset_y);
+
+ if (!offset_x && !offset_y) {
+ // no longer constrained
+ return true;
+ }
+
+ // revert the positioner back if it didn't fix it and go to the next part
+ if (flip_x) {
+ wlr_positioner_invert_x(&popup->positioner);
+ }
+ if (flip_y) {
+ wlr_positioner_invert_y(&popup->positioner);
+ }
+
+ popup->geometry =
+ wlr_xdg_positioner_get_geometry(&popup->positioner);
+
+ return false;
+}
+
+static bool xdg_popup_unconstrain_slide(struct wlr_xdg_popup *popup,
+ struct wlr_box *toplevel_sx_box) {
+ int offset_x = 0, offset_y = 0;
+ xdg_popup_box_constraints(popup, toplevel_sx_box,
+ &offset_x, &offset_y);
+
+ if (!offset_x && !offset_y) {
+ return true;
+ }
+
+ bool slide_x = offset_x &&
+ (popup->positioner.constraint_adjustment &
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X);
+
+ bool slide_y = offset_y &&
+ (popup->positioner.constraint_adjustment &
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y);
+
+ if (slide_x) {
+ popup->geometry.x += offset_x;
+ }
+
+ if (slide_y) {
+ popup->geometry.y += offset_y;
+ }
+
+ int toplevel_x = 0, toplevel_y = 0;
+ wlr_xdg_popup_get_toplevel_coords(popup, popup->geometry.x,
+ popup->geometry.y, &toplevel_x, &toplevel_y);
+
+ if (slide_x && toplevel_x < toplevel_sx_box->x) {
+ popup->geometry.x += toplevel_sx_box->x - toplevel_x;
+ }
+ if (slide_y && toplevel_y < toplevel_sx_box->y) {
+ popup->geometry.y += toplevel_sx_box->y - toplevel_y;
+ }
+
+ xdg_popup_box_constraints(popup, toplevel_sx_box,
+ &offset_x, &offset_y);
+
+ return !offset_x && !offset_y;
+}
+
+static bool xdg_popup_unconstrain_resize(struct wlr_xdg_popup *popup,
+ struct wlr_box *toplevel_sx_box) {
+ int offset_x, offset_y;
+ xdg_popup_box_constraints(popup, toplevel_sx_box,
+ &offset_x, &offset_y);
+
+ if (!offset_x && !offset_y) {
+ return true;
+ }
+
+ bool resize_x = offset_x &&
+ (popup->positioner.constraint_adjustment &
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X);
+
+ bool resize_y = offset_y &&
+ (popup->positioner.constraint_adjustment &
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y);
+
+ if (resize_x) {
+ popup->geometry.width -= offset_x;
+ }
+ if (resize_y) {
+ popup->geometry.height -= offset_y;
+ }
+
+ xdg_popup_box_constraints(popup, toplevel_sx_box,
+ &offset_x, &offset_y);
+
+ return !offset_x && !offset_y;
+}
+
+void wlr_xdg_popup_unconstrain_from_box(struct wlr_xdg_popup *popup,
+ struct wlr_box *toplevel_sx_box) {
+ if (xdg_popup_unconstrain_flip(popup, toplevel_sx_box)) {
+ return;
+ }
+ if (xdg_popup_unconstrain_slide(popup, toplevel_sx_box)) {
+ return;
+ }
+ if (xdg_popup_unconstrain_resize(popup, toplevel_sx_box)) {
+ return;
+ }
+}
diff --git a/types/xdg_shell/wlr_xdg_positioner.c b/types/xdg_shell/wlr_xdg_positioner.c
new file mode 100644
index 00000000..6f902f92
--- /dev/null
+++ b/types/xdg_shell/wlr_xdg_positioner.c
@@ -0,0 +1,311 @@
+#include <assert.h>
+#include <stdlib.h>
+#include "types/wlr_xdg_shell.h"
+
+static const struct xdg_positioner_interface xdg_positioner_implementation;
+
+struct wlr_xdg_positioner_resource *get_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_resource *positioner =
+ get_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->attrs.size.width = width;
+ positioner->attrs.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_resource *positioner =
+ get_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->attrs.anchor_rect.x = x;
+ positioner->attrs.anchor_rect.y = y;
+ positioner->attrs.anchor_rect.width = width;
+ positioner->attrs.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_resource *positioner =
+ get_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->attrs.anchor = anchor;
+}
+
+static void xdg_positioner_handle_set_gravity(struct wl_client *client,
+ struct wl_resource *resource, uint32_t gravity) {
+ struct wlr_xdg_positioner_resource *positioner =
+ get_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->attrs.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_resource *positioner =
+ get_xdg_positioner_from_resource(resource);
+
+ positioner->attrs.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_resource *positioner =
+ get_xdg_positioner_from_resource(resource);
+
+ positioner->attrs.offset.x = x;
+ positioner->attrs.offset.y = y;
+}
+
+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,
+};
+
+static void xdg_positioner_handle_resource_destroy(
+ struct wl_resource *resource) {
+ struct wlr_xdg_positioner_resource *positioner =
+ get_xdg_positioner_from_resource(resource);
+ free(positioner);
+}
+
+void create_xdg_positioner(struct wlr_xdg_client *client, uint32_t id) {
+ struct wlr_xdg_positioner_resource *positioner =
+ calloc(1, sizeof(struct wlr_xdg_positioner_resource));
+ 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);
+}
+
+void handle_xdg_surface_popup_committed(struct wlr_xdg_surface *surface) {
+ assert(surface->role == WLR_XDG_SURFACE_ROLE_POPUP);
+
+ if (!surface->popup->parent) {
+ wl_resource_post_error(surface->resource,
+ XDG_SURFACE_ERROR_NOT_CONSTRUCTED,
+ "xdg_popup has no parent");
+ return;
+ }
+
+ if (!surface->popup->committed) {
+ schedule_xdg_surface_configure(surface);
+ surface->popup->committed = true;
+ }
+}
+
+static bool positioner_anchor_has_edge(enum xdg_positioner_anchor anchor,
+ enum xdg_positioner_anchor edge) {
+ switch (edge) {
+ case XDG_POSITIONER_ANCHOR_TOP:
+ return anchor == XDG_POSITIONER_ANCHOR_TOP ||
+ anchor == XDG_POSITIONER_ANCHOR_TOP_LEFT ||
+ anchor == XDG_POSITIONER_ANCHOR_TOP_RIGHT;
+ case XDG_POSITIONER_ANCHOR_BOTTOM:
+ return anchor == XDG_POSITIONER_ANCHOR_BOTTOM ||
+ anchor == XDG_POSITIONER_ANCHOR_BOTTOM_LEFT ||
+ anchor == XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT;
+ case XDG_POSITIONER_ANCHOR_LEFT:
+ return anchor == XDG_POSITIONER_ANCHOR_LEFT ||
+ anchor == XDG_POSITIONER_ANCHOR_TOP_LEFT ||
+ anchor == XDG_POSITIONER_ANCHOR_BOTTOM_LEFT;
+ case XDG_POSITIONER_ANCHOR_RIGHT:
+ return anchor == XDG_POSITIONER_ANCHOR_RIGHT ||
+ anchor == XDG_POSITIONER_ANCHOR_TOP_RIGHT ||
+ anchor == XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT;
+ default:
+ assert(false); // not reached
+ }
+}
+
+static bool positioner_gravity_has_edge(enum xdg_positioner_gravity gravity,
+ enum xdg_positioner_gravity edge) {
+ // gravity and edge enums are the same
+ return positioner_anchor_has_edge((enum xdg_positioner_anchor)gravity,
+ (enum xdg_positioner_anchor)edge);
+}
+
+struct wlr_box wlr_xdg_positioner_get_geometry(
+ struct wlr_xdg_positioner *positioner) {
+ struct wlr_box geometry = {
+ .x = positioner->offset.x,
+ .y = positioner->offset.y,
+ .width = positioner->size.width,
+ .height = positioner->size.height,
+ };
+
+ if (positioner_anchor_has_edge(positioner->anchor,
+ XDG_POSITIONER_ANCHOR_TOP)) {
+ geometry.y += positioner->anchor_rect.y;
+ } else if (positioner_anchor_has_edge(positioner->anchor,
+ XDG_POSITIONER_ANCHOR_BOTTOM)) {
+ geometry.y +=
+ positioner->anchor_rect.y + positioner->anchor_rect.height;
+ } else {
+ geometry.y +=
+ positioner->anchor_rect.y + positioner->anchor_rect.height / 2;
+ }
+
+ if (positioner_anchor_has_edge(positioner->anchor,
+ XDG_POSITIONER_ANCHOR_LEFT)) {
+ geometry.x += positioner->anchor_rect.x;
+ } else if (positioner_anchor_has_edge(positioner->anchor,
+ XDG_POSITIONER_ANCHOR_RIGHT)) {
+ geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width;
+ } else {
+ geometry.x +=
+ positioner->anchor_rect.x + positioner->anchor_rect.width / 2;
+ }
+
+ if (positioner_gravity_has_edge(positioner->gravity,
+ XDG_POSITIONER_GRAVITY_TOP)) {
+ geometry.y -= geometry.height;
+ } else if (positioner_gravity_has_edge(positioner->gravity,
+ XDG_POSITIONER_GRAVITY_BOTTOM)) {
+ geometry.y = geometry.y;
+ } else {
+ geometry.y -= geometry.height / 2;
+ }
+
+ if (positioner_gravity_has_edge(positioner->gravity,
+ XDG_POSITIONER_GRAVITY_LEFT)) {
+ geometry.x -= geometry.width;
+ } else if (positioner_gravity_has_edge(positioner->gravity,
+ XDG_POSITIONER_GRAVITY_RIGHT)) {
+ geometry.x = geometry.x;
+ } else {
+ geometry.x -= geometry.width / 2;
+ }
+
+ if (positioner->constraint_adjustment ==
+ XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE) {
+ return geometry;
+ }
+
+ return geometry;
+}
+
+static enum xdg_positioner_anchor 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 positioner_gravity_invert_x(
+ enum xdg_positioner_gravity gravity) {
+ // gravity and edge enums are the same
+ return (enum xdg_positioner_gravity)positioner_anchor_invert_x(
+ (enum xdg_positioner_anchor)gravity);
+}
+
+static enum xdg_positioner_anchor 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_BOTTOM_RIGHT:
+ return XDG_POSITIONER_ANCHOR_TOP_RIGHT;
+ default:
+ return anchor;
+ }
+}
+
+static enum xdg_positioner_gravity positioner_gravity_invert_y(
+ enum xdg_positioner_gravity gravity) {
+ // gravity and edge enums are the same
+ return (enum xdg_positioner_gravity)positioner_anchor_invert_y(
+ (enum xdg_positioner_anchor)gravity);
+}
+
+
+void wlr_positioner_invert_x(struct wlr_xdg_positioner *positioner) {
+ positioner->anchor = positioner_anchor_invert_x(positioner->anchor);
+ positioner->gravity = positioner_gravity_invert_x(positioner->gravity);
+}
+
+void wlr_positioner_invert_y(struct wlr_xdg_positioner *positioner) {
+ positioner->anchor = positioner_anchor_invert_y(positioner->anchor);
+ positioner->gravity = positioner_gravity_invert_y(positioner->gravity);
+}
diff --git a/types/xdg_shell/wlr_xdg_shell.c b/types/xdg_shell/wlr_xdg_shell.c
new file mode 100644
index 00000000..58dc376c
--- /dev/null
+++ b/types/xdg_shell/wlr_xdg_shell.c
@@ -0,0 +1,174 @@
+#include <assert.h>
+#include <stdlib.h>
+#include "types/wlr_xdg_shell.h"
+#include "util/signal.h"
+
+#define WM_BASE_VERSION 2
+
+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) {
+ wlr_signal_emit_safe(&surface->events.ping_timeout, surface);
+ }
+
+ 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;
+ assert(wl_client && xdg_shell);
+
+ struct wlr_xdg_client *client =
+ calloc(1, sizeof(struct wlr_xdg_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);
+ wlr_xdg_shell_destroy(xdg_shell);
+}
+
+struct wlr_xdg_shell *wlr_xdg_shell_create(struct wl_display *display) {
+ struct wlr_xdg_shell *xdg_shell =
+ calloc(1, sizeof(struct wlr_xdg_shell));
+ if (!xdg_shell) {
+ return NULL;
+ }
+
+ 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, WM_BASE_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.destroy);
+
+ xdg_shell->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &xdg_shell->display_destroy);
+
+ return xdg_shell;
+}
+
+void wlr_xdg_shell_destroy(struct wlr_xdg_shell *xdg_shell) {
+ if (!xdg_shell) {
+ return;
+ }
+ wlr_signal_emit_safe(&xdg_shell->events.destroy, xdg_shell);
+ wl_list_remove(&xdg_shell->display_destroy.link);
+ wl_global_destroy(xdg_shell->global);
+ free(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 00000000..17edbe47
--- /dev/null
+++ b/types/xdg_shell/wlr_xdg_surface.c
@@ -0,0 +1,651 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wlr/util/log.h>
+#include "types/wlr_xdg_shell.h"
+#include "util/signal.h"
+
+bool wlr_surface_is_xdg_surface(struct wlr_surface *surface) {
+ return surface->role == &xdg_toplevel_surface_role ||
+ surface->role == &xdg_popup_surface_role;
+}
+
+struct wlr_xdg_surface *wlr_xdg_surface_from_wlr_surface(
+ struct wlr_surface *surface) {
+ assert(wlr_surface_is_xdg_surface(surface));
+ return (struct wlr_xdg_surface *)surface->role_data;
+}
+
+static void xdg_surface_configure_destroy(
+ struct wlr_xdg_surface_configure *configure) {
+ if (configure == NULL) {
+ return;
+ }
+ wl_list_remove(&configure->link);
+ free(configure->toplevel_state);
+ free(configure);
+}
+
+void unmap_xdg_surface(struct wlr_xdg_surface *surface) {
+ assert(surface->role != WLR_XDG_SURFACE_ROLE_NONE);
+
+ // TODO: probably need to ungrab before this event
+ if (surface->mapped) {
+ wlr_signal_emit_safe(&surface->events.unmap, surface);
+ }
+
+ switch (surface->role) {
+ case WLR_XDG_SURFACE_ROLE_TOPLEVEL:
+ free(surface->toplevel->title);
+ surface->toplevel->title = NULL;
+ free(surface->toplevel->app_id);
+ surface->toplevel->app_id = NULL;
+ break;
+ case WLR_XDG_SURFACE_ROLE_POPUP:
+ if (surface->popup->seat != NULL) {
+ struct wlr_xdg_popup_grab *grab =
+ get_xdg_shell_popup_grab_from_seat(surface->client->shell,
+ surface->popup->seat);
+
+ wl_list_remove(&surface->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);
+ }
+ }
+
+ surface->popup->seat = NULL;
+ }
+ break;
+ case WLR_XDG_SURFACE_ROLE_NONE:
+ assert(false && "not reached");
+ }
+
+ struct wlr_xdg_surface_configure *configure, *tmp;
+ wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) {
+ xdg_surface_configure_destroy(configure);
+ }
+
+ surface->configured = surface->mapped = false;
+ surface->configure_serial = 0;
+ if (surface->configure_idle) {
+ wl_event_source_remove(surface->configure_idle);
+ surface->configure_idle = NULL;
+ }
+ surface->configure_next_serial = 0;
+
+ surface->has_next_geometry = false;
+ memset(&surface->geometry, 0, sizeof(struct wlr_box));
+ memset(&surface->next_geometry, 0, sizeof(struct wlr_box));
+}
+
+
+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->role == WLR_XDG_SURFACE_ROLE_NONE) {
+ wl_resource_post_error(surface->resource,
+ XDG_SURFACE_ERROR_NOT_CONSTRUCTED,
+ "xdg_surface must have a role");
+ return;
+ }
+
+ bool found = false;
+ struct wlr_xdg_surface_configure *configure, *tmp;
+ wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) {
+ if (configure->serial < serial) {
+ wlr_signal_emit_safe(&surface->events.ack_configure, configure);
+ xdg_surface_configure_destroy(configure);
+ } else if (configure->serial == serial) {
+ found = true;
+ break;
+ } else {
+ break;
+ }
+ }
+ if (!found) {
+ wl_resource_post_error(surface->client->resource,
+ XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE,
+ "wrong configure serial: %u", serial);
+ return;
+ }
+
+ switch (surface->role) {
+ case WLR_XDG_SURFACE_ROLE_NONE:
+ assert(0 && "not reached");
+ break;
+ case WLR_XDG_SURFACE_ROLE_TOPLEVEL:
+ handle_xdg_toplevel_ack_configure(surface, configure);
+ break;
+ case WLR_XDG_SURFACE_ROLE_POPUP:
+ break;
+ }
+
+ surface->configured = true;
+ surface->configure_serial = serial;
+
+ wlr_signal_emit_safe(&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(struct wlr_xdg_surface_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->configure_next_serial;
+ configure->surface = surface;
+
+ switch (surface->role) {
+ case WLR_XDG_SURFACE_ROLE_NONE:
+ assert(0 && "not reached");
+ break;
+ case WLR_XDG_SURFACE_ROLE_TOPLEVEL:
+ send_xdg_toplevel_configure(surface, configure);
+ break;
+ case WLR_XDG_SURFACE_ROLE_POPUP:
+ xdg_popup_send_configure(surface->popup->resource,
+ surface->popup->geometry.x,
+ surface->popup->geometry.y,
+ surface->popup->geometry.width,
+ surface->popup->geometry.height);
+ break;
+ }
+
+ wlr_signal_emit_safe(&surface->events.configure, configure);
+
+ xdg_surface_send_configure(surface->resource, configure->serial);
+}
+
+static uint32_t schedule_configure(struct wlr_xdg_surface *surface,
+ bool pending_same) {
+ struct wl_display *display = wl_client_get_display(surface->client->client);
+ struct wl_event_loop *loop = wl_display_get_event_loop(display);
+
+ if (surface->configure_idle != NULL) {
+ if (!pending_same) {
+ // configure request already scheduled
+ return surface->configure_next_serial;
+ }
+
+ // configure request not necessary anymore
+ wl_event_source_remove(surface->configure_idle);
+ surface->configure_idle = NULL;
+ return 0;
+ } else {
+ if (pending_same) {
+ // configure request not necessary
+ return 0;
+ }
+
+ surface->configure_next_serial = wl_display_next_serial(display);
+ surface->configure_idle = wl_event_loop_add_idle(loop,
+ surface_send_configure, surface);
+ return surface->configure_next_serial;
+ }
+}
+
+uint32_t schedule_xdg_surface_configure(struct wlr_xdg_surface *surface) {
+ bool pending_same = false;
+
+ switch (surface->role) {
+ case WLR_XDG_SURFACE_ROLE_NONE:
+ assert(0 && "not reached");
+ break;
+ case WLR_XDG_SURFACE_ROLE_TOPLEVEL:
+ pending_same = compare_xdg_surface_toplevel_state(surface->toplevel);
+ break;
+ case WLR_XDG_SURFACE_ROLE_POPUP:
+ break;
+ }
+
+ return schedule_configure(surface, pending_same);
+}
+
+uint32_t wlr_xdg_surface_schedule_configure(struct wlr_xdg_surface *surface) {
+ return schedule_configure(surface, false);
+}
+
+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);
+ struct wlr_xdg_surface *parent =
+ wlr_xdg_surface_from_resource(parent_resource);
+ struct wlr_xdg_positioner_resource *positioner =
+ get_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);
+ 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);
+
+ 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) {
+ wlr_log(WLR_ERROR, "Client tried to set invalid geometry");
+ //XXX: Switch to the proper error value once available
+ wl_resource_post_error(resource, -1, "Tried to set invalid xdg-surface geometry");
+ return;
+ }
+
+ surface->has_next_geometry = true;
+ surface->next_geometry.height = height;
+ surface->next_geometry.width = width;
+ surface->next_geometry.x = x;
+ surface->next_geometry.y = y;
+}
+
+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->role != WLR_XDG_SURFACE_ROLE_NONE) {
+ wlr_log(WLR_ERROR, "Tried to destroy an xdg_surface 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_handle_resource_destroy(struct wl_resource *resource) {
+ struct wlr_xdg_surface *surface =
+ wlr_xdg_surface_from_resource(resource);
+ if (surface != NULL) {
+ destroy_xdg_surface(surface);
+ }
+}
+
+static void xdg_surface_handle_surface_commit(struct wl_listener *listener,
+ void *data) {
+ struct wlr_xdg_surface *surface =
+ wl_container_of(listener, surface, surface_commit);
+
+ if (wlr_surface_has_buffer(surface->surface) && !surface->configured) {
+ wl_resource_post_error(surface->resource,
+ XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER,
+ "xdg_surface has never been configured");
+ 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;
+ }
+}
+
+void handle_xdg_surface_commit(struct wlr_surface *wlr_surface) {
+ struct wlr_xdg_surface *surface =
+ wlr_xdg_surface_from_wlr_surface(wlr_surface);
+ if (surface == NULL) {
+ return;
+ }
+
+ if (surface->has_next_geometry) {
+ surface->has_next_geometry = false;
+ surface->geometry.x = surface->next_geometry.x;
+ surface->geometry.y = surface->next_geometry.y;
+ surface->geometry.width = surface->next_geometry.width;
+ surface->geometry.height = surface->next_geometry.height;
+ }
+
+ switch (surface->role) {
+ case WLR_XDG_SURFACE_ROLE_NONE:
+ assert(false);
+ case WLR_XDG_SURFACE_ROLE_TOPLEVEL:
+ handle_xdg_surface_toplevel_committed(surface);
+ break;
+ case WLR_XDG_SURFACE_ROLE_POPUP:
+ handle_xdg_surface_popup_committed(surface);
+ break;
+ }
+
+ if (!surface->added) {
+ surface->added = true;
+ wlr_signal_emit_safe(&surface->client->shell->events.new_surface,
+ surface);
+ }
+ if (surface->configured && wlr_surface_has_buffer(surface->surface) &&
+ !surface->mapped) {
+ surface->mapped = true;
+ wlr_signal_emit_safe(&surface->events.map, surface);
+ }
+ if (surface->configured && !wlr_surface_has_buffer(surface->surface) &&
+ surface->mapped) {
+ unmap_xdg_surface(surface);
+ }
+}
+
+void handle_xdg_surface_precommit(struct wlr_surface *wlr_surface) {
+ struct wlr_xdg_surface *surface =
+ wlr_xdg_surface_from_wlr_surface(wlr_surface);
+ if (surface == NULL) {
+ return;
+ }
+
+ if (wlr_surface->pending.committed & WLR_SURFACE_STATE_BUFFER &&
+ wlr_surface->pending.buffer_resource == NULL) {
+ // This is a NULL commit
+ if (surface->configured && surface->mapped) {
+ unmap_xdg_surface(surface);
+ }
+ }
+}
+
+static void xdg_surface_handle_surface_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_xdg_surface *xdg_surface =
+ wl_container_of(listener, xdg_surface, surface_destroy);
+ destroy_xdg_surface(xdg_surface);
+}
+
+struct wlr_xdg_surface *create_xdg_surface(
+ struct wlr_xdg_client *client, struct wlr_surface *surface,
+ uint32_t id) {
+ struct wlr_xdg_surface *xdg_surface =
+ calloc(1, sizeof(struct wlr_xdg_surface));
+ if (xdg_surface == NULL) {
+ wl_client_post_no_memory(client->client);
+ return NULL;
+ }
+
+ xdg_surface->client = client;
+ xdg_surface->role = WLR_XDG_SURFACE_ROLE_NONE;
+ xdg_surface->surface = surface;
+ xdg_surface->resource = wl_resource_create(client->client,
+ &xdg_surface_interface, wl_resource_get_version(client->resource),
+ id);
+ if (xdg_surface->resource == NULL) {
+ free(xdg_surface);
+ wl_client_post_no_memory(client->client);
+ return NULL;
+ }
+
+ if (wlr_surface_has_buffer(xdg_surface->surface)) {
+ wl_resource_destroy(xdg_surface->resource);
+ free(xdg_surface);
+ wl_resource_post_error(client->resource,
+ XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER,
+ "xdg_surface must not have a buffer at creation");
+ return NULL;
+ }
+
+ wl_list_init(&xdg_surface->configure_list);
+ wl_list_init(&xdg_surface->popups);
+
+ wl_signal_init(&xdg_surface->events.destroy);
+ wl_signal_init(&xdg_surface->events.ping_timeout);
+ wl_signal_init(&xdg_surface->events.new_popup);
+ wl_signal_init(&xdg_surface->events.map);
+ wl_signal_init(&xdg_surface->events.unmap);
+ wl_signal_init(&xdg_surface->events.configure);
+ wl_signal_init(&xdg_surface->events.ack_configure);
+
+ wl_signal_add(&xdg_surface->surface->events.destroy,
+ &xdg_surface->surface_destroy);
+ xdg_surface->surface_destroy.notify = xdg_surface_handle_surface_destroy;
+
+ wl_signal_add(&xdg_surface->surface->events.commit,
+ &xdg_surface->surface_commit);
+ xdg_surface->surface_commit.notify = xdg_surface_handle_surface_commit;
+
+ wlr_log(WLR_DEBUG, "new xdg_surface %p (res %p)", xdg_surface,
+ xdg_surface->resource);
+ wl_resource_set_implementation(xdg_surface->resource,
+ &xdg_surface_implementation, xdg_surface,
+ xdg_surface_handle_resource_destroy);
+ wl_list_insert(&client->surfaces, &xdg_surface->link);
+
+ return xdg_surface;
+}
+
+void reset_xdg_surface(struct wlr_xdg_surface *xdg_surface) {
+ if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_NONE) {
+ unmap_xdg_surface(xdg_surface);
+ }
+
+ if (xdg_surface->added) {
+ wlr_signal_emit_safe(&xdg_surface->events.destroy, xdg_surface);
+ xdg_surface->added = false;
+ }
+
+ switch (xdg_surface->role) {
+ case WLR_XDG_SURFACE_ROLE_TOPLEVEL:
+ wl_resource_set_user_data(xdg_surface->toplevel->resource, NULL);
+ xdg_surface->toplevel->resource = NULL;
+
+ free(xdg_surface->toplevel);
+ xdg_surface->toplevel = NULL;
+ break;
+ case WLR_XDG_SURFACE_ROLE_POPUP:
+ wl_resource_set_user_data(xdg_surface->popup->resource, NULL);
+ xdg_surface->toplevel->resource = NULL;
+
+ wl_list_remove(&xdg_surface->popup->link);
+
+ free(xdg_surface->popup);
+ xdg_surface->popup = NULL;
+ break;
+ case WLR_XDG_SURFACE_ROLE_NONE:
+ // This space is intentionally left blank
+ break;
+ }
+
+ xdg_surface->role = WLR_XDG_SURFACE_ROLE_NONE;
+}
+
+void destroy_xdg_surface(struct wlr_xdg_surface *surface) {
+ reset_xdg_surface(surface);
+
+ struct wlr_xdg_popup *popup_state, *next;
+ wl_list_for_each_safe(popup_state, next, &surface->popups, link) {
+ xdg_popup_send_popup_done(popup_state->resource);
+ destroy_xdg_popup(popup_state->base);
+ }
+
+ wl_resource_set_user_data(surface->resource, NULL);
+ surface->surface->role_data = NULL;
+
+ wl_list_remove(&surface->link);
+ wl_list_remove(&surface->surface_destroy.link);
+ wl_list_remove(&surface->surface_commit.link);
+ free(surface);
+}
+
+struct wlr_xdg_surface *wlr_xdg_surface_from_resource(
+ struct wl_resource *resource) {
+ // TODO: Double check that all of the callers can deal with NULL
+ if (!resource) {
+ return NULL;
+ }
+ 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_surface_send_close(struct wlr_xdg_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) {
+ xdg_toplevel_send_close(surface->toplevel->resource);
+ }
+ break;
+ case WLR_XDG_SURFACE_ROLE_POPUP:
+ if (surface->popup) {
+ xdg_popup_send_popup_done(surface->popup->resource);
+ }
+ break;
+ }
+}
+
+static void xdg_popup_get_position(struct wlr_xdg_popup *popup,
+ double *popup_sx, double *popup_sy) {
+ struct wlr_xdg_surface *parent =
+ wlr_xdg_surface_from_wlr_surface(popup->parent);
+ struct wlr_box parent_geo;
+ wlr_xdg_surface_get_geometry(parent, &parent_geo);
+ *popup_sx = parent_geo.x + popup->geometry.x -
+ popup->base->geometry.x;
+ *popup_sy = parent_geo.y + popup->geometry.y -
+ popup->base->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_xdg_popup *popup_state;
+ wl_list_for_each(popup_state, &surface->popups, link) {
+ struct wlr_xdg_surface *popup = popup_state->base;
+
+ double popup_sx, popup_sy;
+ xdg_popup_get_position(popup_state, &popup_sx, &popup_sy);
+
+ struct wlr_surface *sub = wlr_xdg_surface_surface_at(popup,
+ sx - popup_sx,
+ sy - popup_sy,
+ sub_x, sub_y);
+ if (sub != NULL) {
+ return sub;
+ }
+ }
+
+ return wlr_surface_surface_at(surface->surface, sx, sy, sub_x, sub_y);
+}
+
+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_surface(struct wlr_xdg_surface *surface,
+ int x, int y, wlr_surface_iterator_func_t iterator, void *user_data) {
+ struct xdg_surface_iterator_data data = {
+ .user_iterator = iterator,
+ .user_data = user_data,
+ .x = x, .y = y,
+ };
+ wlr_surface_for_each_surface(surface->surface, xdg_surface_iterator,
+ &data);
+
+ struct wlr_xdg_popup *popup_state;
+ wl_list_for_each(popup_state, &surface->popups, link) {
+ struct wlr_xdg_surface *popup = popup_state->base;
+ if (!popup->configured) {
+ continue;
+ }
+
+ double popup_sx, popup_sy;
+ xdg_popup_get_position(popup_state, &popup_sx, &popup_sy);
+
+ xdg_surface_for_each_surface(popup,
+ x + popup_sx,
+ y + popup_sy,
+ iterator, user_data);
+ }
+}
+
+static void xdg_surface_for_each_popup(struct wlr_xdg_surface *surface,
+ int x, int y, wlr_surface_iterator_func_t iterator, void *user_data) {
+ struct wlr_xdg_popup *popup_state;
+ wl_list_for_each(popup_state, &surface->popups, link) {
+ struct wlr_xdg_surface *popup = popup_state->base;
+ if (!popup->configured) {
+ continue;
+ }
+
+ double popup_sx, popup_sy;
+ xdg_popup_get_position(popup_state, &popup_sx, &popup_sy);
+ iterator(popup->surface, x + popup_sx, y + popup_sy, user_data);
+
+ xdg_surface_for_each_popup(popup,
+ 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) {
+ xdg_surface_for_each_surface(surface, 0, 0, iterator, user_data);
+}
+
+void wlr_xdg_surface_for_each_popup(struct wlr_xdg_surface *surface,
+ wlr_surface_iterator_func_t iterator, void *user_data) {
+ xdg_surface_for_each_popup(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 (!surface->geometry.width) {
+ return;
+ }
+
+ wlr_box_intersection(box, &surface->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 00000000..d3ee0cb7
--- /dev/null
+++ b/types/xdg_shell/wlr_xdg_toplevel.c
@@ -0,0 +1,558 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wlr/util/log.h>
+#include <wlr/util/edges.h>
+#include "types/wlr_xdg_shell.h"
+#include "util/signal.h"
+
+void handle_xdg_toplevel_ack_configure(
+ struct wlr_xdg_surface *surface,
+ struct wlr_xdg_surface_configure *configure) {
+ assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
+ assert(configure->toplevel_state != NULL);
+
+ surface->toplevel->current.maximized =
+ configure->toplevel_state->maximized;
+ surface->toplevel->current.fullscreen =
+ configure->toplevel_state->fullscreen;
+ surface->toplevel->current.resizing =
+ configure->toplevel_state->resizing;
+ surface->toplevel->current.activated =
+ configure->toplevel_state->activated;
+ surface->toplevel->current.tiled =
+ configure->toplevel_state->tiled;
+}
+
+bool compare_xdg_surface_toplevel_state(struct wlr_xdg_toplevel *state) {
+ struct {
+ struct wlr_xdg_toplevel_state state;
+ uint32_t width, height;
+ } configured;
+
+ // is pending state different from current state?
+ if (!state->base->configured) {
+ return false;
+ }
+
+ if (wl_list_empty(&state->base->configure_list)) {
+ // last configure is actually the current state, just use it
+ configured.state = state->current;
+ configured.width = state->base->surface->current.width;
+ configured.height = state->base->surface->current.height;
+ } else {
+ struct wlr_xdg_surface_configure *configure =
+ wl_container_of(state->base->configure_list.prev, configure, link);
+ configured.state = *configure->toplevel_state;
+ configured.width = configure->toplevel_state->width;
+ configured.height = configure->toplevel_state->height;
+ }
+
+ if (state->server_pending.activated != configured.state.activated) {
+ return false;
+ }
+ if (state->server_pending.fullscreen != configured.state.fullscreen) {
+ return false;
+ }
+ if (state->server_pending.maximized != configured.state.maximized) {
+ return false;
+ }
+ if (state->server_pending.resizing != configured.state.resizing) {
+ return false;
+ }
+ if (state->server_pending.tiled != configured.state.tiled) {
+ return false;
+ }
+
+ if (state->server_pending.width == configured.width &&
+ state->server_pending.height == configured.height) {
+ return true;
+ }
+
+ if (state->server_pending.width == 0 && state->server_pending.height == 0) {
+ return true;
+ }
+
+ return false;
+}
+
+void send_xdg_toplevel_configure(struct wlr_xdg_surface *surface,
+ struct wlr_xdg_surface_configure *configure) {
+ assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
+
+ configure->toplevel_state = malloc(sizeof(*configure->toplevel_state));
+ if (configure->toplevel_state == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed");
+ wl_resource_post_no_memory(surface->toplevel->resource);
+ return;
+ }
+ *configure->toplevel_state = surface->toplevel->server_pending;
+
+ struct wl_array states;
+ wl_array_init(&states);
+ if (surface->toplevel->server_pending.maximized) {
+ uint32_t *s = wl_array_add(&states, sizeof(uint32_t));
+ if (!s) {
+ wlr_log(WLR_ERROR, "Could not allocate state for maximized xdg_toplevel");
+ goto error_out;
+ }
+ *s = XDG_TOPLEVEL_STATE_MAXIMIZED;
+ }
+ if (surface->toplevel->server_pending.fullscreen) {
+ uint32_t *s = wl_array_add(&states, sizeof(uint32_t));
+ if (!s) {
+ wlr_log(WLR_ERROR, "Could not allocate state for fullscreen xdg_toplevel");
+ goto error_out;
+ }
+ *s = XDG_TOPLEVEL_STATE_FULLSCREEN;
+ }
+ if (surface->toplevel->server_pending.resizing) {
+ uint32_t *s = wl_array_add(&states, sizeof(uint32_t));
+ if (!s) {
+ wlr_log(WLR_ERROR, "Could not allocate state for resizing xdg_toplevel");
+ goto error_out;
+ }
+ *s = XDG_TOPLEVEL_STATE_RESIZING;
+ }
+ if (surface->toplevel->server_pending.activated) {
+ uint32_t *s = wl_array_add(&states, sizeof(uint32_t));
+ if (!s) {
+ wlr_log(WLR_ERROR, "Could not allocate state for activated xdg_toplevel");
+ goto error_out;
+ }
+ *s = XDG_TOPLEVEL_STATE_ACTIVATED;
+ }
+ if (surface->toplevel->server_pending.tiled) {
+ if (wl_resource_get_version(surface->resource) >=
+ 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 ((surface->toplevel->server_pending.tiled &
+ tiled[i].edge) == 0) {
+ continue;
+ }
+
+ uint32_t *s = wl_array_add(&states, sizeof(uint32_t));
+ if (!s) {
+ wlr_log(WLR_ERROR,
+ "Could not allocate state for tiled xdg_toplevel");
+ goto error_out;
+ }
+ *s = tiled[i].state;
+ }
+ } else if (!surface->toplevel->server_pending.maximized) {
+ // This version doesn't support tiling, best we can do is make the
+ // toplevel maximized
+ uint32_t *s = wl_array_add(&states, sizeof(uint32_t));
+ if (!s) {
+ wlr_log(WLR_ERROR,
+ "Could not allocate state for maximized xdg_toplevel");
+ goto error_out;
+ }
+ *s = XDG_TOPLEVEL_STATE_MAXIMIZED;
+ }
+ }
+
+ uint32_t width = surface->toplevel->server_pending.width;
+ uint32_t height = surface->toplevel->server_pending.height;
+ xdg_toplevel_send_configure(surface->toplevel->resource, width, height,
+ &states);
+
+ wl_array_release(&states);
+ return;
+
+error_out:
+ wl_array_release(&states);
+ wl_resource_post_no_memory(surface->toplevel->resource);
+}
+
+void handle_xdg_surface_toplevel_committed(struct wlr_xdg_surface *surface) {
+ assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
+
+ if (!surface->toplevel->added) {
+ // on the first commit, send a configure request to tell the client it
+ // is added
+ schedule_xdg_surface_configure(surface);
+ surface->toplevel->added = true;
+ return;
+ }
+
+ // update state that doesn't need compositor approval
+ surface->toplevel->current.max_width =
+ surface->toplevel->client_pending.max_width;
+ surface->toplevel->current.min_width =
+ surface->toplevel->client_pending.min_width;
+ surface->toplevel->current.max_height =
+ surface->toplevel->client_pending.max_height;
+ surface->toplevel->current.min_height =
+ surface->toplevel->client_pending.min_height;
+}
+
+static const struct xdg_toplevel_interface xdg_toplevel_implementation;
+
+struct wlr_xdg_surface *wlr_xdg_surface_from_toplevel_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &xdg_toplevel_interface,
+ &xdg_toplevel_implementation));
+ return wl_resource_get_user_data(resource);
+}
+
+static void xdg_toplevel_handle_set_parent(struct wl_client *client,
+ struct wl_resource *resource, struct wl_resource *parent_resource) {
+ struct wlr_xdg_surface *surface =
+ wlr_xdg_surface_from_toplevel_resource(resource);
+ struct wlr_xdg_surface *parent = NULL;
+
+ if (parent_resource != NULL) {
+ parent = wlr_xdg_surface_from_toplevel_resource(parent_resource);
+ }
+
+ surface->toplevel->parent = parent;
+ wlr_signal_emit_safe(&surface->toplevel->events.set_parent, surface);
+}
+
+static void xdg_toplevel_handle_set_title(struct wl_client *client,
+ struct wl_resource *resource, const char *title) {
+ struct wlr_xdg_surface *surface =
+ wlr_xdg_surface_from_toplevel_resource(resource);
+ char *tmp;
+
+ tmp = strdup(title);
+ if (tmp == NULL) {
+ return;
+ }
+
+ free(surface->toplevel->title);
+ surface->toplevel->title = tmp;
+ wlr_signal_emit_safe(&surface->toplevel->events.set_title, surface);
+}
+
+static void xdg_toplevel_handle_set_app_id(struct wl_client *client,
+ struct wl_resource *resource, const char *app_id) {
+ struct wlr_xdg_surface *surface =
+ wlr_xdg_surface_from_toplevel_resource(resource);
+ char *tmp;
+
+ tmp = strdup(app_id);
+ if (tmp == NULL) {
+ return;
+ }
+
+ free(surface->toplevel->app_id);
+ surface->toplevel->app_id = tmp;
+ wlr_signal_emit_safe(&surface->toplevel->events.set_app_id, surface);
+}
+
+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_surface *surface =
+ wlr_xdg_surface_from_toplevel_resource(resource);
+ struct wlr_seat_client *seat =
+ wlr_seat_client_from_resource(seat_resource);
+
+ if (!surface->configured) {
+ wl_resource_post_error(surface->toplevel->resource,
+ XDG_SURFACE_ERROR_NOT_CONSTRUCTED,
+ "surface has not been configured yet");
+ return;
+ }
+
+ if (!wlr_seat_validate_grab_serial(seat->seat, serial)) {
+ wlr_log(WLR_DEBUG, "invalid serial for grab");
+ return;
+ }
+
+ struct wlr_xdg_toplevel_show_window_menu_event event = {
+ .surface = surface,
+ .seat = seat,
+ .serial = serial,
+ .x = x,
+ .y = y,
+ };
+
+ wlr_signal_emit_safe(&surface->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_surface *surface =
+ wlr_xdg_surface_from_toplevel_resource(resource);
+ struct wlr_seat_client *seat =
+ wlr_seat_client_from_resource(seat_resource);
+
+ if (!surface->configured) {
+ wl_resource_post_error(surface->toplevel->resource,
+ XDG_SURFACE_ERROR_NOT_CONSTRUCTED,
+ "surface has not been configured yet");
+ return;
+ }
+
+ if (!wlr_seat_validate_grab_serial(seat->seat, serial)) {
+ wlr_log(WLR_DEBUG, "invalid serial for grab");
+ return;
+ }
+
+ struct wlr_xdg_toplevel_move_event event = {
+ .surface = surface,
+ .seat = seat,
+ .serial = serial,
+ };
+
+ wlr_signal_emit_safe(&surface->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_surface *surface =
+ wlr_xdg_surface_from_toplevel_resource(resource);
+ struct wlr_seat_client *seat =
+ wlr_seat_client_from_resource(seat_resource);
+
+ if (!surface->configured) {
+ wl_resource_post_error(surface->toplevel->resource,
+ XDG_SURFACE_ERROR_NOT_CONSTRUCTED,
+ "surface has not been configured yet");
+ return;
+ }
+
+ if (!wlr_seat_validate_grab_serial(seat->seat, serial)) {
+ wlr_log(WLR_DEBUG, "invalid serial for grab");
+ return;
+ }
+
+ struct wlr_xdg_toplevel_resize_event event = {
+ .surface = surface,
+ .seat = seat,
+ .serial = serial,
+ .edges = edges,
+ };
+
+ wlr_signal_emit_safe(&surface->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_surface *surface =
+ wlr_xdg_surface_from_toplevel_resource(resource);
+ surface->toplevel->client_pending.max_width = width;
+ surface->toplevel->client_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_surface *surface =
+ wlr_xdg_surface_from_toplevel_resource(resource);
+ surface->toplevel->client_pending.min_width = width;
+ surface->toplevel->client_pending.min_height = height;
+}
+
+static void xdg_toplevel_handle_set_maximized(struct wl_client *client,
+ struct wl_resource *resource) {
+ struct wlr_xdg_surface *surface =
+ wlr_xdg_surface_from_toplevel_resource(resource);
+ surface->toplevel->client_pending.maximized = true;
+ wlr_signal_emit_safe(&surface->toplevel->events.request_maximize, surface);
+}
+
+static void xdg_toplevel_handle_unset_maximized(struct wl_client *client,
+ struct wl_resource *resource) {
+ struct wlr_xdg_surface *surface =
+ wlr_xdg_surface_from_toplevel_resource(resource);
+ surface->toplevel->client_pending.maximized = false;
+ wlr_signal_emit_safe(&surface->toplevel->events.request_maximize, surface);
+}
+
+static void xdg_toplevel_handle_set_fullscreen(struct wl_client *client,
+ struct wl_resource *resource, struct wl_resource *output_resource) {
+ struct wlr_xdg_surface *surface =
+ wlr_xdg_surface_from_toplevel_resource(resource);
+
+ struct wlr_output *output = NULL;
+ if (output_resource != NULL) {
+ output = wlr_output_from_resource(output_resource);
+ }
+
+ surface->toplevel->client_pending.fullscreen = true;
+
+ struct wlr_xdg_toplevel_set_fullscreen_event event = {
+ .surface = surface,
+ .fullscreen = true,
+ .output = output,
+ };
+
+ wlr_signal_emit_safe(&surface->toplevel->events.request_fullscreen, &event);
+}
+
+static void xdg_toplevel_handle_unset_fullscreen(struct wl_client *client,
+ struct wl_resource *resource) {
+ struct wlr_xdg_surface *surface =
+ wlr_xdg_surface_from_toplevel_resource(resource);
+
+ surface->toplevel->client_pending.fullscreen = false;
+
+ struct wlr_xdg_toplevel_set_fullscreen_event event = {
+ .surface = surface,
+ .fullscreen = false,
+ .output = NULL,
+ };
+
+ wlr_signal_emit_safe(&surface->toplevel->events.request_fullscreen, &event);
+}
+
+static void xdg_toplevel_handle_set_minimized(struct wl_client *client,
+ struct wl_resource *resource) {
+ struct wlr_xdg_surface *surface =
+ wlr_xdg_surface_from_toplevel_resource(resource);
+ wlr_signal_emit_safe(&surface->toplevel->events.request_minimize, surface);
+}
+
+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 void xdg_toplevel_handle_resource_destroy(struct wl_resource *resource) {
+ struct wlr_xdg_surface *surface =
+ wlr_xdg_surface_from_toplevel_resource(resource);
+ destroy_xdg_toplevel(surface);
+}
+
+const struct wlr_surface_role xdg_toplevel_surface_role = {
+ .name = "xdg_toplevel",
+ .commit = handle_xdg_surface_commit,
+ .precommit = handle_xdg_surface_precommit,
+};
+
+void create_xdg_toplevel(struct wlr_xdg_surface *xdg_surface,
+ uint32_t id) {
+ if (!wlr_surface_set_role(xdg_surface->surface, &xdg_toplevel_surface_role,
+ xdg_surface, xdg_surface->resource, XDG_WM_BASE_ERROR_ROLE)) {
+ return;
+ }
+
+ if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_NONE) {
+ wl_resource_post_error(xdg_surface->resource,
+ XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED,
+ "xdg-surface has already been constructed");
+ return;
+ }
+
+ assert(xdg_surface->toplevel == NULL);
+ xdg_surface->toplevel = calloc(1, sizeof(struct wlr_xdg_toplevel));
+ if (xdg_surface->toplevel == NULL) {
+ wl_resource_post_no_memory(xdg_surface->resource);
+ return;
+ }
+ xdg_surface->toplevel->base = xdg_surface;
+
+ wl_signal_init(&xdg_surface->toplevel->events.request_maximize);
+ wl_signal_init(&xdg_surface->toplevel->events.request_fullscreen);
+ wl_signal_init(&xdg_surface->toplevel->events.request_minimize);
+ wl_signal_init(&xdg_surface->toplevel->events.request_move);
+ wl_signal_init(&xdg_surface->toplevel->events.request_resize);
+ wl_signal_init(&xdg_surface->toplevel->events.request_show_window_menu);
+ wl_signal_init(&xdg_surface->toplevel->events.set_parent);
+ wl_signal_init(&xdg_surface->toplevel->events.set_title);
+ wl_signal_init(&xdg_surface->toplevel->events.set_app_id);
+
+ xdg_surface->toplevel->resource = wl_resource_create(
+ xdg_surface->client->client, &xdg_toplevel_interface,
+ wl_resource_get_version(xdg_surface->resource), id);
+ if (xdg_surface->toplevel->resource == NULL) {
+ free(xdg_surface->toplevel);
+ wl_resource_post_no_memory(xdg_surface->resource);
+ return;
+ }
+ wl_resource_set_implementation(xdg_surface->toplevel->resource,
+ &xdg_toplevel_implementation, xdg_surface,
+ xdg_toplevel_handle_resource_destroy);
+
+ xdg_surface->role = WLR_XDG_SURFACE_ROLE_TOPLEVEL;
+}
+
+void destroy_xdg_toplevel(struct wlr_xdg_surface *xdg_surface) {
+ if (xdg_surface == NULL) {
+ return;
+ }
+ assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
+ reset_xdg_surface(xdg_surface);
+}
+
+uint32_t wlr_xdg_toplevel_set_size(struct wlr_xdg_surface *surface,
+ uint32_t width, uint32_t height) {
+ assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
+ surface->toplevel->server_pending.width = width;
+ surface->toplevel->server_pending.height = height;
+
+ return schedule_xdg_surface_configure(surface);
+}
+
+uint32_t wlr_xdg_toplevel_set_activated(struct wlr_xdg_surface *surface,
+ bool activated) {
+ assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
+ surface->toplevel->server_pending.activated = activated;
+
+ return schedule_xdg_surface_configure(surface);
+}
+
+uint32_t wlr_xdg_toplevel_set_maximized(struct wlr_xdg_surface *surface,
+ bool maximized) {
+ assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
+ surface->toplevel->server_pending.maximized = maximized;
+
+ return schedule_xdg_surface_configure(surface);
+}
+
+uint32_t wlr_xdg_toplevel_set_fullscreen(struct wlr_xdg_surface *surface,
+ bool fullscreen) {
+ assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
+ surface->toplevel->server_pending.fullscreen = fullscreen;
+
+ return schedule_xdg_surface_configure(surface);
+}
+
+uint32_t wlr_xdg_toplevel_set_resizing(struct wlr_xdg_surface *surface,
+ bool resizing) {
+ assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
+ surface->toplevel->server_pending.resizing = resizing;
+
+ return schedule_xdg_surface_configure(surface);
+}
+
+uint32_t wlr_xdg_toplevel_set_tiled(struct wlr_xdg_surface *surface,
+ uint32_t tiled) {
+ assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
+ surface->toplevel->server_pending.tiled = tiled;
+
+ return schedule_xdg_surface_configure(surface);
+}
diff --git a/types/xdg_shell_v6/wlr_xdg_popup_v6.c b/types/xdg_shell_v6/wlr_xdg_popup_v6.c
new file mode 100644
index 00000000..097e0253
--- /dev/null
+++ b/types/xdg_shell_v6/wlr_xdg_popup_v6.c
@@ -0,0 +1,527 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include "types/wlr_xdg_shell_v6.h"
+#include "util/signal.h"
+
+static struct wlr_xdg_surface_v6 *xdg_popup_grab_get_topmost(
+ struct wlr_xdg_popup_grab_v6 *grab) {
+ struct wlr_xdg_popup_v6 *popup;
+ wl_list_for_each(popup, &grab->popups, grab_link) {
+ return popup->base;
+ }
+
+ return NULL;
+}
+
+static void xdg_pointer_grab_end(struct wlr_seat_pointer_grab *grab) {
+ struct wlr_xdg_popup_grab_v6 *popup_grab = grab->data;
+
+ struct wlr_xdg_popup_v6 *popup, *tmp;
+ wl_list_for_each_safe(popup, tmp, &popup_grab->popups, grab_link) {
+ zxdg_popup_v6_send_popup_done(popup->resource);
+ }
+
+ wlr_seat_pointer_end_grab(grab->seat);
+ wlr_seat_keyboard_end_grab(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_v6 *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_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_pointer_grab_end(grab);
+ return 0;
+ }
+}
+
+static void xdg_pointer_grab_axis(struct wlr_seat_pointer_grab *grab,
+ uint32_t time, enum wlr_axis_orientation orientation, double value,
+ int32_t value_discrete, enum wlr_axis_source source) {
+ wlr_seat_pointer_send_axis(grab->seat, time, orientation, value,
+ value_discrete, source);
+}
+
+static void xdg_pointer_grab_cancel(struct wlr_seat_pointer_grab *grab) {
+ xdg_pointer_grab_end(grab);
+}
+
+static const struct wlr_pointer_grab_interface xdg_pointer_grab_impl = {
+ .enter = xdg_pointer_grab_enter,
+ .motion = xdg_pointer_grab_motion,
+ .button = xdg_pointer_grab_button,
+ .cancel = xdg_pointer_grab_cancel,
+ .axis = xdg_pointer_grab_axis,
+};
+
+static void xdg_keyboard_grab_enter(struct wlr_seat_keyboard_grab *grab,
+ struct wlr_surface *surface, uint32_t keycodes[], size_t num_keycodes,
+ struct wlr_keyboard_modifiers *modifiers) {
+ // 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,
+ 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,
+ .key = xdg_keyboard_grab_key,
+ .modifiers = xdg_keyboard_grab_modifiers,
+ .cancel = xdg_keyboard_grab_cancel,
+};
+
+static void xdg_popup_grab_handle_seat_destroy(
+ struct wl_listener *listener, void *data) {
+ struct wlr_xdg_popup_grab_v6 *xdg_grab =
+ wl_container_of(listener, xdg_grab, seat_destroy);
+
+ wl_list_remove(&xdg_grab->seat_destroy.link);
+
+ struct wlr_xdg_popup_v6 *popup, *next;
+ wl_list_for_each_safe(popup, next, &xdg_grab->popups, grab_link) {
+ destroy_xdg_surface_v6(popup->base);
+ }
+
+ wl_list_remove(&xdg_grab->link);
+ free(xdg_grab);
+}
+
+struct wlr_xdg_popup_grab_v6 *get_xdg_shell_v6_popup_grab_from_seat(
+ struct wlr_xdg_shell_v6 *shell, struct wlr_seat *seat) {
+ struct wlr_xdg_popup_grab_v6 *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(struct wlr_xdg_popup_grab_v6));
+ 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;
+
+ 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;
+}
+
+
+static const struct zxdg_popup_v6_interface zxdg_popup_v6_implementation;
+
+static struct wlr_xdg_surface_v6 *xdg_surface_from_xdg_popup_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &zxdg_popup_v6_interface,
+ &zxdg_popup_v6_implementation));
+ return wl_resource_get_user_data(resource);
+}
+
+void destroy_xdg_popup_v6(struct wlr_xdg_surface_v6 *surface) {
+ assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_POPUP);
+ unmap_xdg_surface_v6(surface);
+
+ wl_resource_set_user_data(surface->popup->resource, NULL);
+ wl_list_remove(&surface->popup->link);
+ free(surface->popup);
+ surface->popup = NULL;
+
+ surface->role = WLR_XDG_SURFACE_V6_ROLE_NONE;
+}
+
+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_surface_v6 *surface =
+ xdg_surface_from_xdg_popup_resource(resource);
+ struct wlr_seat_client *seat_client =
+ wlr_seat_client_from_resource(seat_resource);
+ if (!surface) {
+ return;
+ }
+
+ if (surface->popup->committed) {
+ wl_resource_post_error(surface->popup->resource,
+ ZXDG_POPUP_V6_ERROR_INVALID_GRAB,
+ "xdg_popup is already mapped");
+ return;
+ }
+
+ struct wlr_xdg_popup_grab_v6 *popup_grab =
+ get_xdg_shell_v6_popup_grab_from_seat(surface->client->shell,
+ seat_client->seat);
+
+ struct wlr_xdg_surface_v6 *topmost = xdg_popup_grab_get_topmost(popup_grab);
+ bool parent_is_toplevel =
+ surface->popup->parent->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL;
+
+ if ((topmost == NULL && !parent_is_toplevel) ||
+ (topmost != NULL && topmost != surface->popup->parent)) {
+ wl_resource_post_error(surface->client->resource,
+ ZXDG_SHELL_V6_ERROR_NOT_THE_TOPMOST_POPUP,
+ "xdg_popup was not created on the topmost popup");
+ return;
+ }
+
+ popup_grab->client = surface->client->client;
+ surface->popup->seat = seat_client->seat;
+
+ wl_list_insert(&popup_grab->popups, &surface->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);
+}
+
+static void xdg_popup_handle_destroy(struct wl_client *client,
+ struct wl_resource *resource) {
+ struct wlr_xdg_surface_v6 *surface =
+ xdg_surface_from_xdg_popup_resource(resource);
+
+ if (surface && !wl_list_empty(&surface->popups)) {
+ wl_resource_post_error(surface->client->resource,
+ ZXDG_SHELL_V6_ERROR_NOT_THE_TOPMOST_POPUP,
+ "xdg_popup was destroyed while it was not the topmost popup");
+ return;
+ }
+
+ wl_resource_destroy(resource);
+}
+
+static const struct zxdg_popup_v6_interface zxdg_popup_v6_implementation = {
+ .destroy = xdg_popup_handle_destroy,
+ .grab = xdg_popup_handle_grab,
+};
+
+static void xdg_popup_handle_resource_destroy(struct wl_resource *resource) {
+ struct wlr_xdg_surface_v6 *surface =
+ xdg_surface_from_xdg_popup_resource(resource);
+ if (surface != NULL) {
+ destroy_xdg_popup_v6(surface);
+ }
+}
+
+void handle_xdg_surface_v6_popup_committed(struct wlr_xdg_surface_v6 *surface) {
+ assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_POPUP);
+
+ if (!surface->popup->committed) {
+ schedule_xdg_surface_v6_configure(surface);
+ surface->popup->committed = true;
+ }
+}
+
+const struct wlr_surface_role xdg_popup_v6_surface_role = {
+ .name = "xdg_popup_v6",
+ .commit = handle_xdg_surface_v6_commit,
+ .precommit = handle_xdg_surface_v6_precommit,
+};
+
+void create_xdg_popup_v6(struct wlr_xdg_surface_v6 *xdg_surface,
+ struct wlr_xdg_surface_v6 *parent,
+ struct wlr_xdg_positioner_v6_resource *positioner, int32_t id) {
+ if (positioner->attrs.size.width == 0 ||
+ positioner->attrs.anchor_rect.width == 0) {
+ wl_resource_post_error(xdg_surface->resource,
+ ZXDG_SHELL_V6_ERROR_INVALID_POSITIONER,
+ "positioner object is not complete");
+ return;
+ }
+
+ if (!wlr_surface_set_role(xdg_surface->surface, &xdg_popup_v6_surface_role,
+ xdg_surface, xdg_surface->resource, ZXDG_SHELL_V6_ERROR_ROLE)) {
+ return;
+ }
+
+ xdg_surface->popup = calloc(1, sizeof(struct wlr_xdg_popup_v6));
+ if (!xdg_surface->popup) {
+ wl_resource_post_no_memory(xdg_surface->resource);
+ return;
+ }
+
+ xdg_surface->popup->resource =
+ wl_resource_create(xdg_surface->client->client, &zxdg_popup_v6_interface,
+ wl_resource_get_version(xdg_surface->resource), id);
+ if (xdg_surface->popup->resource == NULL) {
+ free(xdg_surface->popup);
+ wl_resource_post_no_memory(xdg_surface->resource);
+ return;
+ }
+ wl_resource_set_implementation(xdg_surface->popup->resource,
+ &zxdg_popup_v6_implementation, xdg_surface,
+ xdg_popup_handle_resource_destroy);
+
+ xdg_surface->role = WLR_XDG_SURFACE_V6_ROLE_POPUP;
+ xdg_surface->popup->base = xdg_surface;
+ xdg_surface->popup->parent = parent;
+ xdg_surface->popup->geometry =
+ wlr_xdg_positioner_v6_get_geometry(&positioner->attrs);
+
+ // positioner properties
+ memcpy(&xdg_surface->popup->positioner, &positioner->attrs,
+ sizeof(struct wlr_xdg_positioner_v6));
+
+ wl_list_insert(&parent->popups, &xdg_surface->popup->link);
+
+ wlr_signal_emit_safe(&parent->events.new_popup, xdg_surface->popup);
+}
+
+void wlr_xdg_popup_v6_get_anchor_point(struct wlr_xdg_popup_v6 *popup,
+ int *root_sx, int *root_sy) {
+ struct wlr_box rect = popup->positioner.anchor_rect;
+ enum zxdg_positioner_v6_anchor anchor = popup->positioner.anchor;
+ int sx = 0, sy = 0;
+
+ if (anchor == ZXDG_POSITIONER_V6_ANCHOR_NONE) {
+ sx = (rect.x + rect.width) / 2;
+ sy = (rect.y + rect.height) / 2;
+ } else if (anchor == ZXDG_POSITIONER_V6_ANCHOR_TOP) {
+ sx = (rect.x + rect.width) / 2;
+ sy = rect.y;
+ } else if (anchor == ZXDG_POSITIONER_V6_ANCHOR_BOTTOM) {
+ sx = (rect.x + rect.width) / 2;
+ sy = rect.y + rect.height;
+ } else if (anchor == ZXDG_POSITIONER_V6_ANCHOR_LEFT) {
+ sx = rect.x;
+ sy = (rect.y + rect.height) / 2;
+ } else if (anchor == ZXDG_POSITIONER_V6_ANCHOR_RIGHT) {
+ sx = rect.x + rect.width;
+ sy = (rect.y + rect.height) / 2;
+ } else if (anchor == (ZXDG_POSITIONER_V6_ANCHOR_TOP |
+ ZXDG_POSITIONER_V6_ANCHOR_LEFT)) {
+ sx = rect.x;
+ sy = rect.y;
+ } else if (anchor == (ZXDG_POSITIONER_V6_ANCHOR_TOP |
+ ZXDG_POSITIONER_V6_ANCHOR_RIGHT)) {
+ sx = rect.x + rect.width;
+ sy = rect.y;
+ } else if (anchor == (ZXDG_POSITIONER_V6_ANCHOR_BOTTOM |
+ ZXDG_POSITIONER_V6_ANCHOR_LEFT)) {
+ sx = rect.x;
+ sy = rect.y + rect.height;
+ } else if (anchor == (ZXDG_POSITIONER_V6_ANCHOR_BOTTOM |
+ ZXDG_POSITIONER_V6_ANCHOR_RIGHT)) {
+ sx = rect.x + rect.width;
+ sy = rect.y + rect.height;
+ }
+
+ *root_sx = sx;
+ *root_sy = sy;
+}
+
+void wlr_xdg_popup_v6_get_toplevel_coords(struct wlr_xdg_popup_v6 *popup,
+ int popup_sx, int popup_sy, int *toplevel_sx, int *toplevel_sy) {
+ struct wlr_xdg_surface_v6 *parent = popup->parent;
+ while (parent != NULL && parent->role == WLR_XDG_SURFACE_V6_ROLE_POPUP) {
+ popup_sx += parent->popup->geometry.x;
+ popup_sy += parent->popup->geometry.y;
+ parent = parent->popup->parent;
+ }
+ assert(parent);
+
+ *toplevel_sx = popup_sx + parent->geometry.x;
+ *toplevel_sy = popup_sy + parent->geometry.y;
+}
+
+static void xdg_popup_v6_box_constraints(struct wlr_xdg_popup_v6 *popup,
+ struct wlr_box *toplevel_sx_box, int *offset_x, int *offset_y) {
+ int popup_width = popup->geometry.width;
+ int popup_height = popup->geometry.height;
+ int anchor_sx = 0, anchor_sy = 0;
+ wlr_xdg_popup_v6_get_anchor_point(popup, &anchor_sx, &anchor_sy);
+ int popup_sx = 0, popup_sy = 0;
+ wlr_xdg_popup_v6_get_toplevel_coords(popup, popup->geometry.x,
+ popup->geometry.y, &popup_sx, &popup_sy);
+ *offset_x = 0, *offset_y = 0;
+
+ if (popup_sx < toplevel_sx_box->x) {
+ *offset_x = toplevel_sx_box->x - popup_sx;
+ } else if (popup_sx + popup_width >
+ toplevel_sx_box->x + toplevel_sx_box->width) {
+ *offset_x = toplevel_sx_box->x + toplevel_sx_box->width -
+ (popup_sx + popup_width);
+ }
+
+ if (popup_sy < toplevel_sx_box->y) {
+ *offset_y = toplevel_sx_box->y - popup_sy;
+ } else if (popup_sy + popup_height >
+ toplevel_sx_box->y + toplevel_sx_box->height) {
+ *offset_y = toplevel_sx_box->y + toplevel_sx_box->height -
+ (popup_sy + popup_height);
+ }
+}
+
+static bool xdg_popup_v6_unconstrain_flip(struct wlr_xdg_popup_v6 *popup,
+ struct wlr_box *toplevel_sx_box) {
+ int offset_x = 0, offset_y = 0;
+ xdg_popup_v6_box_constraints(popup, toplevel_sx_box,
+ &offset_x, &offset_y);
+
+ if (!offset_x && !offset_y) {
+ return true;
+ }
+
+ bool flip_x = offset_x &&
+ (popup->positioner.constraint_adjustment &
+ ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X);
+
+ bool flip_y = offset_y &&
+ (popup->positioner.constraint_adjustment &
+ ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y);
+
+ if (flip_x) {
+ wlr_positioner_v6_invert_x(&popup->positioner);
+ }
+ if (flip_y) {
+ wlr_positioner_v6_invert_y(&popup->positioner);
+ }
+
+ popup->geometry =
+ wlr_xdg_positioner_v6_get_geometry(&popup->positioner);
+
+ xdg_popup_v6_box_constraints(popup, toplevel_sx_box,
+ &offset_x, &offset_y);
+
+ if (!offset_x && !offset_y) {
+ // no longer constrained
+ return true;
+ }
+
+ // revert the positioner back if it didn't fix it and go to the next part
+ if (flip_x) {
+ wlr_positioner_v6_invert_x(&popup->positioner);
+ }
+ if (flip_y) {
+ wlr_positioner_v6_invert_y(&popup->positioner);
+ }
+
+ popup->geometry =
+ wlr_xdg_positioner_v6_get_geometry(&popup->positioner);
+
+ return false;
+}
+
+static bool xdg_popup_v6_unconstrain_slide(struct wlr_xdg_popup_v6 *popup,
+ struct wlr_box *toplevel_sx_box) {
+ int offset_x = 0, offset_y = 0;
+ xdg_popup_v6_box_constraints(popup, toplevel_sx_box,
+ &offset_x, &offset_y);
+
+ if (!offset_x && !offset_y) {
+ return true;
+ }
+
+ bool slide_x = offset_x &&
+ (popup->positioner.constraint_adjustment &
+ ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X);
+
+ bool slide_y = offset_y &&
+ (popup->positioner.constraint_adjustment &
+ ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y);
+
+ if (slide_x) {
+ popup->geometry.x += offset_x;
+ }
+
+ if (slide_y) {
+ popup->geometry.y += offset_y;
+ }
+
+ int toplevel_x = 0, toplevel_y = 0;
+ wlr_xdg_popup_v6_get_toplevel_coords(popup, popup->geometry.x,
+ popup->geometry.y, &toplevel_x, &toplevel_y);
+
+ if (slide_x && toplevel_x < toplevel_sx_box->x) {
+ popup->geometry.x += toplevel_sx_box->x - toplevel_x;
+ }
+ if (slide_y && toplevel_y < toplevel_sx_box->y) {
+ popup->geometry.y += toplevel_sx_box->y - toplevel_y;
+ }
+
+ xdg_popup_v6_box_constraints(popup, toplevel_sx_box,
+ &offset_x, &offset_y);
+
+ return !offset_x && !offset_y;
+}
+
+static bool xdg_popup_v6_unconstrain_resize(struct wlr_xdg_popup_v6 *popup,
+ struct wlr_box *toplevel_sx_box) {
+ int offset_x, offset_y;
+ xdg_popup_v6_box_constraints(popup, toplevel_sx_box,
+ &offset_x, &offset_y);
+
+ if (!offset_x && !offset_y) {
+ return true;
+ }
+
+ bool resize_x = offset_x &&
+ (popup->positioner.constraint_adjustment &
+ ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_X);
+
+ bool resize_y = offset_y &&
+ (popup->positioner.constraint_adjustment &
+ ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_Y);
+
+ if (resize_x) {
+ popup->geometry.width -= offset_x;
+ }
+ if (resize_y) {
+ popup->geometry.height -= offset_y;
+ }
+
+ xdg_popup_v6_box_constraints(popup, toplevel_sx_box,
+ &offset_x, &offset_y);
+
+ return !offset_x && !offset_y;
+}
+
+void wlr_xdg_popup_v6_unconstrain_from_box(struct wlr_xdg_popup_v6 *popup,
+ struct wlr_box *toplevel_sx_box) {
+ if (xdg_popup_v6_unconstrain_flip(popup, toplevel_sx_box)) {
+ return;
+ }
+ if (xdg_popup_v6_unconstrain_slide(popup, toplevel_sx_box)) {
+ return;
+ }
+ if (xdg_popup_v6_unconstrain_resize(popup, toplevel_sx_box)) {
+ return;
+ }
+}
diff --git a/types/xdg_shell_v6/wlr_xdg_positioner_v6.c b/types/xdg_shell_v6/wlr_xdg_positioner_v6.c
new file mode 100644
index 00000000..837f9290
--- /dev/null
+++ b/types/xdg_shell_v6/wlr_xdg_positioner_v6.c
@@ -0,0 +1,234 @@
+#include <assert.h>
+#include <stdlib.h>
+#include "types/wlr_xdg_shell_v6.h"
+
+static const struct zxdg_positioner_v6_interface
+ zxdg_positioner_v6_implementation;
+
+struct wlr_xdg_positioner_v6_resource *get_xdg_positioner_v6_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &zxdg_positioner_v6_interface,
+ &zxdg_positioner_v6_implementation));
+ return wl_resource_get_user_data(resource);
+}
+
+static void xdg_positioner_destroy(struct wl_resource *resource) {
+ struct wlr_xdg_positioner_v6_resource *positioner =
+ get_xdg_positioner_v6_from_resource(resource);
+ free(positioner);
+}
+
+static void xdg_positioner_handle_destroy(struct wl_client *client,
+ struct wl_resource *resource) {
+ wl_resource_destroy(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_v6_resource *positioner =
+ get_xdg_positioner_v6_from_resource(resource);
+
+ if (width < 1 || height < 1) {
+ wl_resource_post_error(resource,
+ ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT,
+ "width and height must be positive and non-zero");
+ return;
+ }
+
+ positioner->attrs.size.width = width;
+ positioner->attrs.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_v6_resource *positioner =
+ get_xdg_positioner_v6_from_resource(resource);
+
+ if (width < 1 || height < 1) {
+ wl_resource_post_error(resource,
+ ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT,
+ "width and height must be positive and non-zero");
+ return;
+ }
+
+ positioner->attrs.anchor_rect.x = x;
+ positioner->attrs.anchor_rect.y = y;
+ positioner->attrs.anchor_rect.width = width;
+ positioner->attrs.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_v6_resource *positioner =
+ get_xdg_positioner_v6_from_resource(resource);
+
+ if (((anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP ) &&
+ (anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM)) ||
+ ((anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT) &&
+ (anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT))) {
+ wl_resource_post_error(resource,
+ ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT,
+ "same-axis values are not allowed");
+ return;
+ }
+
+ positioner->attrs.anchor = anchor;
+}
+
+static void xdg_positioner_handle_set_gravity(struct wl_client *client,
+ struct wl_resource *resource, uint32_t gravity) {
+ struct wlr_xdg_positioner_v6_resource *positioner =
+ get_xdg_positioner_v6_from_resource(resource);
+
+ if (((gravity & ZXDG_POSITIONER_V6_GRAVITY_TOP) &&
+ (gravity & ZXDG_POSITIONER_V6_GRAVITY_BOTTOM)) ||
+ ((gravity & ZXDG_POSITIONER_V6_GRAVITY_LEFT) &&
+ (gravity & ZXDG_POSITIONER_V6_GRAVITY_RIGHT))) {
+ wl_resource_post_error(resource,
+ ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT,
+ "same-axis values are not allowed");
+ return;
+ }
+
+ positioner->attrs.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_v6_resource *positioner =
+ get_xdg_positioner_v6_from_resource(resource);
+
+ positioner->attrs.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_v6_resource *positioner =
+ get_xdg_positioner_v6_from_resource(resource);
+
+ positioner->attrs.offset.x = x;
+ positioner->attrs.offset.y = y;
+}
+
+static const struct zxdg_positioner_v6_interface
+ zxdg_positioner_v6_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,
+};
+
+struct wlr_box wlr_xdg_positioner_v6_get_geometry(
+ struct wlr_xdg_positioner_v6 *positioner) {
+ struct wlr_box geometry = {
+ .x = positioner->offset.x,
+ .y = positioner->offset.y,
+ .width = positioner->size.width,
+ .height = positioner->size.height,
+ };
+
+ if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP) {
+ geometry.y += positioner->anchor_rect.y;
+ } else if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM) {
+ geometry.y +=
+ positioner->anchor_rect.y + positioner->anchor_rect.height;
+ } else {
+ geometry.y +=
+ positioner->anchor_rect.y + positioner->anchor_rect.height / 2;
+ }
+
+ if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT) {
+ geometry.x += positioner->anchor_rect.x;
+ } else if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT) {
+ geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width;
+ } else {
+ geometry.x +=
+ positioner->anchor_rect.x + positioner->anchor_rect.width / 2;
+ }
+
+ if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_TOP) {
+ geometry.y -= geometry.height;
+ } else if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_BOTTOM) {
+ geometry.y = geometry.y;
+ } else {
+ geometry.y -= geometry.height / 2;
+ }
+
+ if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_LEFT) {
+ geometry.x -= geometry.width;
+ } else if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_RIGHT) {
+ geometry.x = geometry.x;
+ } else {
+ geometry.x -= geometry.width / 2;
+ }
+
+ if (positioner->constraint_adjustment ==
+ ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_NONE) {
+ return geometry;
+ }
+
+ return geometry;
+}
+
+void wlr_positioner_v6_invert_x(struct wlr_xdg_positioner_v6 *positioner) {
+ if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT) {
+ positioner->anchor &= ~ZXDG_POSITIONER_V6_ANCHOR_LEFT;
+ positioner->anchor |= ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+ } else if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT) {
+ positioner->anchor &= ~ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
+ positioner->anchor |= ZXDG_POSITIONER_V6_ANCHOR_LEFT;
+ }
+
+ if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_RIGHT) {
+ positioner->gravity &= ~ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
+ positioner->gravity |= ZXDG_POSITIONER_V6_GRAVITY_LEFT;
+ } else if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_LEFT) {
+ positioner->gravity &= ~ZXDG_POSITIONER_V6_GRAVITY_LEFT;
+ positioner->gravity |= ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
+ }
+}
+
+void wlr_positioner_v6_invert_y(
+ struct wlr_xdg_positioner_v6 *positioner) {
+ if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP) {
+ positioner->anchor &= ~ZXDG_POSITIONER_V6_ANCHOR_TOP;
+ positioner->anchor |= ZXDG_POSITIONER_V6_ANCHOR_BOTTOM;
+ } else if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM) {
+ positioner->anchor &= ~ZXDG_POSITIONER_V6_ANCHOR_BOTTOM;
+ positioner->anchor |= ZXDG_POSITIONER_V6_ANCHOR_TOP;
+ }
+
+ if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_TOP) {
+ positioner->gravity &= ~ZXDG_POSITIONER_V6_GRAVITY_TOP;
+ positioner->gravity |= ZXDG_POSITIONER_V6_GRAVITY_BOTTOM;
+ } else if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_BOTTOM) {
+ positioner->gravity &= ~ZXDG_POSITIONER_V6_GRAVITY_BOTTOM;
+ positioner->gravity |= ZXDG_POSITIONER_V6_GRAVITY_TOP;
+ }
+}
+
+void create_xdg_positioner_v6(struct wlr_xdg_client_v6 *client, uint32_t id) {
+ struct wlr_xdg_positioner_v6_resource *positioner =
+ calloc(1, sizeof(struct wlr_xdg_positioner_v6_resource));
+ if (positioner == NULL) {
+ wl_client_post_no_memory(client->client);
+ return;
+ }
+
+ positioner->resource = wl_resource_create(client->client,
+ &zxdg_positioner_v6_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,
+ &zxdg_positioner_v6_implementation, positioner, xdg_positioner_destroy);
+}
diff --git a/types/xdg_shell_v6/wlr_xdg_shell_v6.c b/types/xdg_shell_v6/wlr_xdg_shell_v6.c
new file mode 100644
index 00000000..fce8e96a
--- /dev/null
+++ b/types/xdg_shell_v6/wlr_xdg_shell_v6.c
@@ -0,0 +1,175 @@
+#include <assert.h>
+#include <stdlib.h>
+#include "types/wlr_xdg_shell_v6.h"
+#include "util/signal.h"
+
+#define SHELL_VERSION 1
+
+static const struct zxdg_shell_v6_interface xdg_shell_impl;
+
+static struct wlr_xdg_client_v6 *xdg_client_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &zxdg_shell_v6_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_v6 *client =
+ xdg_client_from_resource(resource);
+ create_xdg_positioner_v6(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_v6 *client =
+ xdg_client_from_resource(client_resource);
+ struct wlr_surface *surface = wlr_surface_from_resource(surface_resource);
+ create_xdg_surface_v6(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_v6 *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_v6 *client = xdg_client_from_resource(resource);
+
+ if (!wl_list_empty(&client->surfaces)) {
+ wl_resource_post_error(client->resource,
+ ZXDG_SHELL_V6_ERROR_DEFUNCT_SURFACES,
+ "xdg_wm_base was destroyed before children");
+ return;
+ }
+
+ wl_resource_destroy(resource);
+}
+
+static const struct zxdg_shell_v6_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_v6_handle_resource_destroy(
+ struct wl_resource *resource) {
+ struct wlr_xdg_client_v6 *client = xdg_client_from_resource(resource);
+
+ struct wlr_xdg_surface_v6 *surface, *tmp = NULL;
+ wl_list_for_each_safe(surface, tmp, &client->surfaces, link) {
+ wl_resource_destroy(surface->resource);
+ }
+
+ if (client->ping_timer != NULL) {
+ wl_event_source_remove(client->ping_timer);
+ }
+
+ wl_list_remove(&client->link);
+ free(client);
+}
+
+static int xdg_client_v6_ping_timeout(void *user_data) {
+ struct wlr_xdg_client_v6 *client = user_data;
+
+ struct wlr_xdg_surface_v6 *surface;
+ wl_list_for_each(surface, &client->surfaces, link) {
+ wlr_signal_emit_safe(&surface->events.ping_timeout, surface);
+ }
+
+ 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_v6 *xdg_shell = data;
+ assert(wl_client && xdg_shell);
+
+ struct wlr_xdg_client_v6 *client =
+ calloc(1, sizeof(struct wlr_xdg_client_v6));
+ if (client == NULL) {
+ wl_client_post_no_memory(wl_client);
+ return;
+ }
+
+ wl_list_init(&client->surfaces);
+
+ client->resource =
+ wl_resource_create(wl_client, &zxdg_shell_v6_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_v6_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_v6_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_v6 *xdg_shell =
+ wl_container_of(listener, xdg_shell, display_destroy);
+ wlr_xdg_shell_v6_destroy(xdg_shell);
+}
+
+struct wlr_xdg_shell_v6 *wlr_xdg_shell_v6_create(struct wl_display *display) {
+ struct wlr_xdg_shell_v6 *xdg_shell =
+ calloc(1, sizeof(struct wlr_xdg_shell_v6));
+ if (!xdg_shell) {
+ return NULL;
+ }
+
+ 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,
+ &zxdg_shell_v6_interface, SHELL_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.destroy);
+
+ xdg_shell->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &xdg_shell->display_destroy);
+
+ return xdg_shell;
+}
+
+void wlr_xdg_shell_v6_destroy(struct wlr_xdg_shell_v6 *xdg_shell) {
+ if (!xdg_shell) {
+ return;
+ }
+ wlr_signal_emit_safe(&xdg_shell->events.destroy, xdg_shell);
+ wl_list_remove(&xdg_shell->display_destroy.link);
+ wl_global_destroy(xdg_shell->global);
+ free(xdg_shell);
+}
diff --git a/types/xdg_shell_v6/wlr_xdg_surface_v6.c b/types/xdg_shell_v6/wlr_xdg_surface_v6.c
new file mode 100644
index 00000000..ed87d6fe
--- /dev/null
+++ b/types/xdg_shell_v6/wlr_xdg_surface_v6.c
@@ -0,0 +1,602 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wlr/util/log.h>
+#include "types/wlr_xdg_shell_v6.h"
+#include "util/signal.h"
+
+bool wlr_surface_is_xdg_surface_v6(struct wlr_surface *surface) {
+ return surface->role == &xdg_toplevel_v6_surface_role ||
+ surface->role == &xdg_popup_v6_surface_role;
+}
+
+struct wlr_xdg_surface_v6 *wlr_xdg_surface_v6_from_wlr_surface(
+ struct wlr_surface *surface) {
+ assert(wlr_surface_is_xdg_surface_v6(surface));
+ return (struct wlr_xdg_surface_v6 *)surface->role_data;
+}
+
+static const struct zxdg_surface_v6_interface zxdg_surface_v6_implementation;
+
+static struct wlr_xdg_surface_v6 *xdg_surface_from_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &zxdg_surface_v6_interface,
+ &zxdg_surface_v6_implementation));
+ return wl_resource_get_user_data(resource);
+}
+
+static void xdg_surface_configure_destroy(
+ struct wlr_xdg_surface_v6_configure *configure) {
+ if (configure == NULL) {
+ return;
+ }
+ wl_list_remove(&configure->link);
+ free(configure->toplevel_state);
+ free(configure);
+}
+
+void unmap_xdg_surface_v6(struct wlr_xdg_surface_v6 *surface) {
+ assert(surface->role != WLR_XDG_SURFACE_V6_ROLE_NONE);
+
+ // TODO: probably need to ungrab before this event
+ if (surface->mapped) {
+ wlr_signal_emit_safe(&surface->events.unmap, surface);
+ }
+
+ switch (surface->role) {
+ case WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL:
+ free(surface->toplevel->title);
+ surface->toplevel->title = NULL;
+ free(surface->toplevel->app_id);
+ surface->toplevel->app_id = NULL;
+ break;
+ case WLR_XDG_SURFACE_V6_ROLE_POPUP:
+ if (surface->popup->seat != NULL) {
+ struct wlr_xdg_popup_grab_v6 *grab =
+ get_xdg_shell_v6_popup_grab_from_seat(surface->client->shell,
+ surface->popup->seat);
+
+ wl_list_remove(&surface->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);
+ }
+ }
+
+ surface->popup->seat = NULL;
+ }
+ break;
+ case WLR_XDG_SURFACE_V6_ROLE_NONE:
+ assert(false && "not reached");
+ }
+
+ struct wlr_xdg_surface_v6_configure *configure, *tmp;
+ wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) {
+ xdg_surface_configure_destroy(configure);
+ }
+
+ surface->configured = surface->mapped = false;
+ surface->configure_serial = 0;
+ if (surface->configure_idle) {
+ wl_event_source_remove(surface->configure_idle);
+ surface->configure_idle = NULL;
+ }
+ surface->configure_next_serial = 0;
+
+ surface->has_next_geometry = false;
+ memset(&surface->geometry, 0, sizeof(struct wlr_box));
+ memset(&surface->next_geometry, 0, sizeof(struct wlr_box));
+}
+
+void destroy_xdg_surface_v6(struct wlr_xdg_surface_v6 *surface) {
+ if (surface->role != WLR_XDG_SURFACE_V6_ROLE_NONE) {
+ unmap_xdg_surface_v6(surface);
+ }
+
+ wlr_signal_emit_safe(&surface->events.destroy, surface);
+
+ struct wlr_xdg_popup_v6 *popup_state, *next;
+ wl_list_for_each_safe(popup_state, next, &surface->popups, link) {
+ zxdg_popup_v6_send_popup_done(popup_state->resource);
+ destroy_xdg_popup_v6(popup_state->base);
+ }
+
+ switch (surface->role) {
+ case WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL:
+ destroy_xdg_toplevel_v6(surface);
+ break;
+ case WLR_XDG_SURFACE_V6_ROLE_POPUP:
+ destroy_xdg_popup_v6(surface);
+ break;
+ case WLR_XDG_SURFACE_V6_ROLE_NONE:
+ // This space is intentionally left blank
+ break;
+ }
+
+ wl_resource_set_user_data(surface->resource, NULL);
+ surface->surface->role_data = NULL;
+ wl_list_remove(&surface->link);
+ wl_list_remove(&surface->surface_destroy.link);
+ wl_list_remove(&surface->surface_commit.link);
+ free(surface);
+}
+
+static void xdg_surface_handle_get_toplevel(struct wl_client *client,
+ struct wl_resource *resource, uint32_t id) {
+ struct wlr_xdg_surface_v6 *xdg_surface = xdg_surface_from_resource(resource);
+ create_xdg_toplevel_v6(xdg_surface, id);
+}
+
+static void xdg_surface_handle_get_popup(struct wl_client *wl_client,
+ struct wl_resource *resource, uint32_t id,
+ struct wl_resource *parent_resource,
+ struct wl_resource *positioner_resource) {
+ struct wlr_xdg_surface_v6 *xdg_surface =
+ xdg_surface_from_resource(resource);
+ struct wlr_xdg_surface_v6 *parent =
+ xdg_surface_from_resource(parent_resource);
+ struct wlr_xdg_positioner_v6_resource *positioner =
+ get_xdg_positioner_v6_from_resource(positioner_resource);
+
+ create_xdg_popup_v6(xdg_surface, parent, positioner, id);
+}
+
+static void xdg_surface_handle_ack_configure(struct wl_client *client,
+ struct wl_resource *resource, uint32_t serial) {
+ struct wlr_xdg_surface_v6 *surface = xdg_surface_from_resource(resource);
+
+ if (surface->role == WLR_XDG_SURFACE_V6_ROLE_NONE) {
+ wl_resource_post_error(surface->resource,
+ ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED,
+ "xdg_surface must have a role");
+ return;
+ }
+
+ bool found = false;
+ struct wlr_xdg_surface_v6_configure *configure, *tmp;
+ wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) {
+ if (configure->serial < serial) {
+ xdg_surface_configure_destroy(configure);
+ } else if (configure->serial == serial) {
+ found = true;
+ break;
+ } else {
+ break;
+ }
+ }
+ if (!found) {
+ wl_resource_post_error(surface->client->resource,
+ ZXDG_SHELL_V6_ERROR_INVALID_SURFACE_STATE,
+ "wrong configure serial: %u", serial);
+ return;
+ }
+
+ switch (surface->role) {
+ case WLR_XDG_SURFACE_V6_ROLE_NONE:
+ assert(0 && "not reached");
+ break;
+ case WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL:
+ handle_xdg_toplevel_v6_ack_configure(surface, configure);
+ break;
+ case WLR_XDG_SURFACE_V6_ROLE_POPUP:
+ break;
+ }
+
+ surface->configured = true;
+ surface->configure_serial = serial;
+
+ xdg_surface_configure_destroy(configure);
+}
+
+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_v6 *surface = xdg_surface_from_resource(resource);
+
+ if (surface->role == WLR_XDG_SURFACE_V6_ROLE_NONE) {
+ wl_resource_post_error(surface->resource,
+ ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED,
+ "xdg_surface must have a role");
+ return;
+ }
+
+ if (width <= 0 || height <= 0) {
+ wlr_log(WLR_ERROR, "Client tried to set invalid geometry");
+ wl_resource_post_error(resource, -1, "Tried to set invalid xdg-surface geometry");
+ }
+
+
+ surface->has_next_geometry = true;
+ surface->next_geometry.height = height;
+ surface->next_geometry.width = width;
+ surface->next_geometry.x = x;
+ surface->next_geometry.y = y;
+}
+
+static void xdg_surface_handle_destroy(struct wl_client *client,
+ struct wl_resource *resource) {
+ struct wlr_xdg_surface_v6 *surface = xdg_surface_from_resource(resource);
+
+ if (surface->role != WLR_XDG_SURFACE_V6_ROLE_NONE) {
+ wlr_log(WLR_ERROR, "Tried to destroy an xdg_surface before its role "
+ "object");
+ return;
+ }
+
+ wl_resource_destroy(resource);
+}
+
+static const struct zxdg_surface_v6_interface zxdg_surface_v6_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_send_configure(void *user_data) {
+ struct wlr_xdg_surface_v6 *surface = user_data;
+
+ surface->configure_idle = NULL;
+
+ struct wlr_xdg_surface_v6_configure *configure =
+ calloc(1, sizeof(struct wlr_xdg_surface_v6_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->configure_next_serial;
+
+ switch (surface->role) {
+ case WLR_XDG_SURFACE_V6_ROLE_NONE:
+ assert(0 && "not reached");
+ break;
+ case WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL:
+ send_xdg_toplevel_v6_configure(surface, configure);
+ break;
+ case WLR_XDG_SURFACE_V6_ROLE_POPUP:
+ zxdg_popup_v6_send_configure(surface->popup->resource,
+ surface->popup->geometry.x,
+ surface->popup->geometry.y,
+ surface->popup->geometry.width,
+ surface->popup->geometry.height);
+ break;
+ }
+
+ zxdg_surface_v6_send_configure(surface->resource, configure->serial);
+}
+
+uint32_t schedule_xdg_surface_v6_configure(struct wlr_xdg_surface_v6 *surface) {
+ struct wl_display *display = wl_client_get_display(surface->client->client);
+ struct wl_event_loop *loop = wl_display_get_event_loop(display);
+ bool pending_same = false;
+
+ switch (surface->role) {
+ case WLR_XDG_SURFACE_V6_ROLE_NONE:
+ assert(0 && "not reached");
+ break;
+ case WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL:
+ pending_same =
+ compare_xdg_surface_v6_toplevel_state(surface->toplevel);
+ break;
+ case WLR_XDG_SURFACE_V6_ROLE_POPUP:
+ break;
+ }
+
+ if (surface->configure_idle != NULL) {
+ if (!pending_same) {
+ // configure request already scheduled
+ return surface->configure_next_serial;
+ }
+
+ // configure request not necessary anymore
+ wl_event_source_remove(surface->configure_idle);
+ surface->configure_idle = NULL;
+ return 0;
+ } else {
+ if (pending_same) {
+ // configure request not necessary
+ return 0;
+ }
+
+ surface->configure_next_serial = wl_display_next_serial(display);
+ surface->configure_idle = wl_event_loop_add_idle(loop,
+ xdg_surface_send_configure, surface);
+ return surface->configure_next_serial;
+ }
+}
+
+void wlr_xdg_surface_v6_ping(struct wlr_xdg_surface_v6 *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);
+ zxdg_shell_v6_send_ping(surface->client->resource,
+ surface->client->ping_serial);
+}
+
+void wlr_xdg_surface_v6_send_close(struct wlr_xdg_surface_v6 *surface) {
+ switch (surface->role) {
+ case WLR_XDG_SURFACE_V6_ROLE_NONE:
+ assert(0 && "not reached");
+ break;
+ case WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL:
+ if (surface->toplevel) {
+ zxdg_toplevel_v6_send_close(surface->toplevel->resource);
+ }
+ break;
+ case WLR_XDG_SURFACE_V6_ROLE_POPUP:
+ if (surface->popup) {
+ zxdg_popup_v6_send_popup_done(surface->popup->resource);
+ }
+ break;
+ }
+}
+
+static void xdg_surface_handle_surface_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_xdg_surface_v6 *xdg_surface =
+ wl_container_of(listener, xdg_surface, surface_destroy);
+ destroy_xdg_surface_v6(xdg_surface);
+}
+
+static void xdg_surface_handle_surface_commit(struct wl_listener *listener,
+ void *data) {
+ struct wlr_xdg_surface_v6 *surface =
+ wl_container_of(listener, surface, surface_commit);
+
+ if (wlr_surface_has_buffer(surface->surface) && !surface->configured) {
+ wl_resource_post_error(surface->resource,
+ ZXDG_SURFACE_V6_ERROR_UNCONFIGURED_BUFFER,
+ "xdg_surface has never been configured");
+ return;
+ }
+
+ if (surface->role == WLR_XDG_SURFACE_V6_ROLE_NONE) {
+ wl_resource_post_error(surface->resource,
+ ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED,
+ "xdg_surface must have a role");
+ return;
+ }
+}
+
+void handle_xdg_surface_v6_commit(struct wlr_surface *wlr_surface) {
+ struct wlr_xdg_surface_v6 *surface =
+ wlr_xdg_surface_v6_from_wlr_surface(wlr_surface);
+ if (surface == NULL) {
+ return;
+ }
+
+ if (surface->has_next_geometry) {
+ surface->has_next_geometry = false;
+ surface->geometry.x = surface->next_geometry.x;
+ surface->geometry.y = surface->next_geometry.y;
+ surface->geometry.width = surface->next_geometry.width;
+ surface->geometry.height = surface->next_geometry.height;
+ }
+
+ switch (surface->role) {
+ case WLR_XDG_SURFACE_V6_ROLE_NONE:
+ assert(false);
+ case WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL:
+ handle_xdg_surface_v6_toplevel_committed(surface);
+ break;
+ case WLR_XDG_SURFACE_V6_ROLE_POPUP:
+ handle_xdg_surface_v6_popup_committed(surface);
+ break;
+ }
+
+ if (!surface->added) {
+ surface->added = true;
+ wlr_signal_emit_safe(&surface->client->shell->events.new_surface,
+ surface);
+ }
+ if (surface->configured && wlr_surface_has_buffer(surface->surface) &&
+ !surface->mapped) {
+ surface->mapped = true;
+ wlr_signal_emit_safe(&surface->events.map, surface);
+ }
+}
+
+void handle_xdg_surface_v6_precommit(struct wlr_surface *wlr_surface) {
+ struct wlr_xdg_surface_v6 *surface =
+ wlr_xdg_surface_v6_from_wlr_surface(wlr_surface);
+ if (surface == NULL) {
+ return;
+ }
+
+ if (wlr_surface->pending.committed & WLR_SURFACE_STATE_BUFFER &&
+ wlr_surface->pending.buffer_resource == NULL) {
+ // This is a NULL commit
+ if (surface->configured && surface->mapped) {
+ unmap_xdg_surface_v6(surface);
+ }
+ }
+}
+
+static void xdg_surface_handle_resource_destroy(struct wl_resource *resource) {
+ struct wlr_xdg_surface_v6 *surface = xdg_surface_from_resource(resource);
+ if (surface != NULL) {
+ destroy_xdg_surface_v6(surface);
+ }
+}
+
+struct wlr_xdg_surface_v6 *create_xdg_surface_v6(
+ struct wlr_xdg_client_v6 *client, struct wlr_surface *surface,
+ uint32_t id) {
+ if (wlr_surface_has_buffer(surface)) {
+ wl_resource_post_error(client->resource,
+ ZXDG_SURFACE_V6_ERROR_UNCONFIGURED_BUFFER,
+ "xdg_surface must not have a buffer at creation");
+ return NULL;
+ }
+
+ struct wlr_xdg_surface_v6 *xdg_surface =
+ calloc(1, sizeof(struct wlr_xdg_surface_v6));
+ if (xdg_surface == NULL) {
+ wl_client_post_no_memory(client->client);
+ return NULL;
+ }
+
+ xdg_surface->client = client;
+ xdg_surface->role = WLR_XDG_SURFACE_V6_ROLE_NONE;
+ xdg_surface->surface = surface;
+ xdg_surface->resource = wl_resource_create(client->client,
+ &zxdg_surface_v6_interface, wl_resource_get_version(client->resource),
+ id);
+ if (xdg_surface->resource == NULL) {
+ free(xdg_surface);
+ wl_client_post_no_memory(client->client);
+ return NULL;
+ }
+ wl_resource_set_implementation(xdg_surface->resource,
+ &zxdg_surface_v6_implementation, xdg_surface,
+ xdg_surface_handle_resource_destroy);
+
+ wl_list_init(&xdg_surface->configure_list);
+ wl_list_init(&xdg_surface->popups);
+
+ wl_signal_init(&xdg_surface->events.destroy);
+ wl_signal_init(&xdg_surface->events.ping_timeout);
+ wl_signal_init(&xdg_surface->events.new_popup);
+ wl_signal_init(&xdg_surface->events.map);
+ wl_signal_init(&xdg_surface->events.unmap);
+
+ wl_signal_add(&xdg_surface->surface->events.destroy,
+ &xdg_surface->surface_destroy);
+ xdg_surface->surface_destroy.notify = xdg_surface_handle_surface_destroy;
+
+ wl_signal_add(&xdg_surface->surface->events.commit,
+ &xdg_surface->surface_commit);
+ xdg_surface->surface_commit.notify = xdg_surface_handle_surface_commit;
+
+ wlr_log(WLR_DEBUG, "new xdg_surface %p (res %p)", xdg_surface,
+ xdg_surface->resource);
+ wl_list_insert(&client->surfaces, &xdg_surface->link);
+
+ return xdg_surface;
+}
+
+static void xdg_popup_v6_get_position(struct wlr_xdg_popup_v6 *popup,
+ double *popup_sx, double *popup_sy) {
+ struct wlr_xdg_surface_v6 *parent = popup->parent;
+ struct wlr_box parent_geo;
+ wlr_xdg_surface_v6_get_geometry(parent, &parent_geo);
+ *popup_sx = parent_geo.x + popup->geometry.x -
+ popup->base->geometry.x;
+ *popup_sy = parent_geo.y + popup->geometry.y -
+ popup->base->geometry.y;
+}
+
+struct wlr_surface *wlr_xdg_surface_v6_surface_at(
+ struct wlr_xdg_surface_v6 *surface, double sx, double sy,
+ double *sub_x, double *sub_y) {
+ struct wlr_xdg_popup_v6 *popup_state;
+ wl_list_for_each(popup_state, &surface->popups, link) {
+ struct wlr_xdg_surface_v6 *popup = popup_state->base;
+
+ double popup_sx, popup_sy;
+ xdg_popup_v6_get_position(popup_state, &popup_sx, &popup_sy);
+
+ struct wlr_surface *sub = wlr_xdg_surface_v6_surface_at(popup,
+ sx - popup_sx,
+ sy - popup_sy,
+ sub_x, sub_y);
+ if (sub != NULL) {
+ return sub;
+ }
+ }
+
+ return wlr_surface_surface_at(surface->surface, sx, sy, sub_x, sub_y);
+}
+
+struct xdg_surface_v6_iterator_data {
+ wlr_surface_iterator_func_t user_iterator;
+ void *user_data;
+ int x, y;
+};
+
+static void xdg_surface_v6_iterator(struct wlr_surface *surface,
+ int sx, int sy, void *data) {
+ struct xdg_surface_v6_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_v6_for_each_surface(struct wlr_xdg_surface_v6 *surface,
+ int x, int y, wlr_surface_iterator_func_t iterator, void *user_data) {
+ struct xdg_surface_v6_iterator_data data = {
+ .user_iterator = iterator,
+ .user_data = user_data,
+ .x = x, .y = y,
+ };
+ wlr_surface_for_each_surface(surface->surface, xdg_surface_v6_iterator,
+ &data);
+
+ struct wlr_xdg_popup_v6 *popup_state;
+ wl_list_for_each(popup_state, &surface->popups, link) {
+ struct wlr_xdg_surface_v6 *popup = popup_state->base;
+ if (!popup->configured) {
+ continue;
+ }
+
+ double popup_sx, popup_sy;
+ xdg_popup_v6_get_position(popup_state, &popup_sx, &popup_sy);
+
+ xdg_surface_v6_for_each_surface(popup,
+ x + popup_sx,
+ y + popup_sy,
+ iterator, user_data);
+ }
+}
+
+static void xdg_surface_v6_for_each_popup(struct wlr_xdg_surface_v6 *surface,
+ int x, int y, wlr_surface_iterator_func_t iterator, void *user_data) {
+ struct wlr_xdg_popup_v6 *popup_state;
+ wl_list_for_each(popup_state, &surface->popups, link) {
+ struct wlr_xdg_surface_v6 *popup = popup_state->base;
+ if (!popup->configured) {
+ continue;
+ }
+
+ double popup_sx, popup_sy;
+ xdg_popup_v6_get_position(popup_state, &popup_sx, &popup_sy);
+ iterator(popup->surface, x + popup_sx, y + popup_sy, user_data);
+
+ xdg_surface_v6_for_each_popup(popup,
+ x + popup_sx,
+ y + popup_sy,
+ iterator, user_data);
+ }
+}
+
+void wlr_xdg_surface_v6_for_each_surface(struct wlr_xdg_surface_v6 *surface,
+ wlr_surface_iterator_func_t iterator, void *user_data) {
+ xdg_surface_v6_for_each_surface(surface, 0, 0, iterator, user_data);
+}
+
+void wlr_xdg_surface_v6_for_each_popup(struct wlr_xdg_surface_v6 *surface,
+ wlr_surface_iterator_func_t iterator, void *user_data) {
+ xdg_surface_v6_for_each_popup(surface, 0, 0, iterator, user_data);
+}
+
+void wlr_xdg_surface_v6_get_geometry(struct wlr_xdg_surface_v6 *surface, struct wlr_box *box) {
+ wlr_surface_get_extends(surface->surface, box);
+ /* The client never set the geometry */
+ if (!surface->geometry.width) {
+ return;
+ }
+
+ wlr_box_intersection(box, &surface->geometry, box);
+}
diff --git a/types/xdg_shell_v6/wlr_xdg_toplevel_v6.c b/types/xdg_shell_v6/wlr_xdg_toplevel_v6.c
new file mode 100644
index 00000000..297f49f5
--- /dev/null
+++ b/types/xdg_shell_v6/wlr_xdg_toplevel_v6.c
@@ -0,0 +1,506 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wlr/util/log.h>
+#include "types/wlr_xdg_shell_v6.h"
+#include "util/signal.h"
+
+static const struct zxdg_toplevel_v6_interface zxdg_toplevel_v6_implementation;
+
+static struct wlr_xdg_surface_v6 *xdg_surface_from_xdg_toplevel_resource(
+ struct wl_resource *resource) {
+ assert(wl_resource_instance_of(resource, &zxdg_toplevel_v6_interface,
+ &zxdg_toplevel_v6_implementation));
+ return wl_resource_get_user_data(resource);
+}
+
+void destroy_xdg_toplevel_v6(struct wlr_xdg_surface_v6 *surface) {
+ assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL);
+ unmap_xdg_surface_v6(surface);
+
+ wl_resource_set_user_data(surface->toplevel->resource, NULL);
+ free(surface->toplevel);
+ surface->toplevel = NULL;
+
+ surface->role = WLR_XDG_SURFACE_V6_ROLE_NONE;
+}
+
+static void xdg_toplevel_handle_destroy(struct wl_client *client,
+ struct wl_resource *resource) {
+ wl_resource_destroy(resource);
+}
+
+static void xdg_toplevel_handle_set_parent(struct wl_client *client,
+ struct wl_resource *resource, struct wl_resource *parent_resource) {
+ struct wlr_xdg_surface_v6 *surface =
+ xdg_surface_from_xdg_toplevel_resource(resource);
+
+ struct wlr_xdg_surface_v6 *parent = NULL;
+ if (parent_resource != NULL) {
+ parent = xdg_surface_from_xdg_toplevel_resource(parent_resource);
+ }
+
+ surface->toplevel->parent = parent;
+ wlr_signal_emit_safe(&surface->toplevel->events.set_parent, surface);
+}
+
+static void xdg_toplevel_handle_set_title(struct wl_client *client,
+ struct wl_resource *resource, const char *title) {
+ struct wlr_xdg_surface_v6 *surface =
+ xdg_surface_from_xdg_toplevel_resource(resource);
+
+ char *tmp = strdup(title);
+ if (tmp == NULL) {
+ return;
+ }
+
+ free(surface->toplevel->title);
+ surface->toplevel->title = tmp;
+ wlr_signal_emit_safe(&surface->toplevel->events.set_title, surface);
+}
+
+static void xdg_toplevel_handle_set_app_id(struct wl_client *client,
+ struct wl_resource *resource, const char *app_id) {
+ struct wlr_xdg_surface_v6 *surface =
+ xdg_surface_from_xdg_toplevel_resource(resource);
+
+ char *tmp = strdup(app_id);
+ if (tmp == NULL) {
+ return;
+ }
+
+ free(surface->toplevel->app_id);
+ surface->toplevel->app_id = tmp;
+ wlr_signal_emit_safe(&surface->toplevel->events.set_app_id, surface);
+}
+
+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_surface_v6 *surface =
+ xdg_surface_from_xdg_toplevel_resource(resource);
+ struct wlr_seat_client *seat =
+ wlr_seat_client_from_resource(seat_resource);
+
+ if (!surface->configured) {
+ wl_resource_post_error(surface->toplevel->resource,
+ ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED,
+ "surface has not been configured yet");
+ return;
+ }
+
+ if (!wlr_seat_validate_grab_serial(seat->seat, serial)) {
+ wlr_log(WLR_DEBUG, "invalid serial for grab");
+ return;
+ }
+
+ struct wlr_xdg_toplevel_v6_show_window_menu_event event = {
+ .surface = surface,
+ .seat = seat,
+ .serial = serial,
+ .x = x,
+ .y = y,
+ };
+
+ wlr_signal_emit_safe(&surface->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_surface_v6 *surface =
+ xdg_surface_from_xdg_toplevel_resource(resource);
+ struct wlr_seat_client *seat =
+ wlr_seat_client_from_resource(seat_resource);
+
+ if (!surface->configured) {
+ wl_resource_post_error(surface->toplevel->resource,
+ ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED,
+ "surface has not been configured yet");
+ return;
+ }
+
+ if (!wlr_seat_validate_grab_serial(seat->seat, serial)) {
+ wlr_log(WLR_DEBUG, "invalid serial for grab");
+ return;
+ }
+
+ struct wlr_xdg_toplevel_v6_move_event event = {
+ .surface = surface,
+ .seat = seat,
+ .serial = serial,
+ };
+
+ wlr_signal_emit_safe(&surface->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_surface_v6 *surface =
+ xdg_surface_from_xdg_toplevel_resource(resource);
+ struct wlr_seat_client *seat =
+ wlr_seat_client_from_resource(seat_resource);
+
+ if (!surface->configured) {
+ wl_resource_post_error(surface->toplevel->resource,
+ ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED,
+ "surface has not been configured yet");
+ return;
+ }
+
+ if (!wlr_seat_validate_grab_serial(seat->seat, serial)) {
+ wlr_log(WLR_DEBUG, "invalid serial for grab");
+ return;
+ }
+
+ struct wlr_xdg_toplevel_v6_resize_event event = {
+ .surface = surface,
+ .seat = seat,
+ .serial = serial,
+ .edges = edges,
+ };
+
+ wlr_signal_emit_safe(&surface->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_surface_v6 *surface =
+ xdg_surface_from_xdg_toplevel_resource(resource);
+ surface->toplevel->client_pending.max_width = width;
+ surface->toplevel->client_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_surface_v6 *surface =
+ xdg_surface_from_xdg_toplevel_resource(resource);
+ surface->toplevel->client_pending.min_width = width;
+ surface->toplevel->client_pending.min_height = height;
+}
+
+static void xdg_toplevel_handle_set_maximized(struct wl_client *client,
+ struct wl_resource *resource) {
+ struct wlr_xdg_surface_v6 *surface =
+ xdg_surface_from_xdg_toplevel_resource(resource);
+ surface->toplevel->client_pending.maximized = true;
+ wlr_signal_emit_safe(&surface->toplevel->events.request_maximize, surface);
+}
+
+static void xdg_toplevel_handle_unset_maximized(struct wl_client *client,
+ struct wl_resource *resource) {
+ struct wlr_xdg_surface_v6 *surface =
+ xdg_surface_from_xdg_toplevel_resource(resource);
+ surface->toplevel->client_pending.maximized = false;
+ wlr_signal_emit_safe(&surface->toplevel->events.request_maximize, surface);
+}
+
+static void xdg_toplevel_handle_set_fullscreen(struct wl_client *client,
+ struct wl_resource *resource, struct wl_resource *output_resource) {
+ struct wlr_xdg_surface_v6 *surface =
+ xdg_surface_from_xdg_toplevel_resource(resource);
+
+ struct wlr_output *output = NULL;
+ if (output_resource != NULL) {
+ output = wlr_output_from_resource(output_resource);
+ }
+
+ surface->toplevel->client_pending.fullscreen = true;
+
+ struct wlr_xdg_toplevel_v6_set_fullscreen_event event = {
+ .surface = surface,
+ .fullscreen = true,
+ .output = output,
+ };
+
+ wlr_signal_emit_safe(&surface->toplevel->events.request_fullscreen, &event);
+}
+
+static void xdg_toplevel_handle_unset_fullscreen(struct wl_client *client,
+ struct wl_resource *resource) {
+ struct wlr_xdg_surface_v6 *surface =
+ xdg_surface_from_xdg_toplevel_resource(resource);
+
+ surface->toplevel->client_pending.fullscreen = false;
+
+ struct wlr_xdg_toplevel_v6_set_fullscreen_event event = {
+ .surface = surface,
+ .fullscreen = false,
+ .output = NULL,
+ };
+
+ wlr_signal_emit_safe(&surface->toplevel->events.request_fullscreen, &event);
+}
+
+static void xdg_toplevel_handle_set_minimized(struct wl_client *client,
+ struct wl_resource *resource) {
+ struct wlr_xdg_surface_v6 *surface =
+ xdg_surface_from_xdg_toplevel_resource(resource);
+ wlr_signal_emit_safe(&surface->toplevel->events.request_minimize, surface);
+}
+
+static const struct zxdg_toplevel_v6_interface
+ zxdg_toplevel_v6_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,
+};
+
+void handle_xdg_toplevel_v6_ack_configure(struct wlr_xdg_surface_v6 *surface,
+ struct wlr_xdg_surface_v6_configure *configure) {
+ assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL);
+ assert(configure->toplevel_state != NULL);
+
+ surface->toplevel->current.maximized =
+ configure->toplevel_state->maximized;
+ surface->toplevel->current.fullscreen =
+ configure->toplevel_state->fullscreen;
+ surface->toplevel->current.resizing =
+ configure->toplevel_state->resizing;
+ surface->toplevel->current.activated =
+ configure->toplevel_state->activated;
+}
+
+bool compare_xdg_surface_v6_toplevel_state(struct wlr_xdg_toplevel_v6 *state) {
+ struct {
+ struct wlr_xdg_toplevel_v6_state state;
+ uint32_t width, height;
+ } configured;
+
+ // is pending state different from current state?
+ if (!state->base->configured) {
+ return false;
+ }
+
+ if (wl_list_empty(&state->base->configure_list)) {
+ // last configure is actually the current state, just use it
+ configured.state = state->current;
+ configured.width = state->base->surface->current.width;
+ configured.height = state->base->surface->current.height;
+ } else {
+ struct wlr_xdg_surface_v6_configure *configure =
+ wl_container_of(state->base->configure_list.prev, configure, link);
+ configured.state = *configure->toplevel_state;
+ configured.width = configure->toplevel_state->width;
+ configured.height = configure->toplevel_state->height;
+ }
+
+ if (state->server_pending.activated != configured.state.activated) {
+ return false;
+ }
+ if (state->server_pending.fullscreen != configured.state.fullscreen) {
+ return false;
+ }
+ if (state->server_pending.maximized != configured.state.maximized) {
+ return false;
+ }
+ if (state->server_pending.resizing != configured.state.resizing) {
+ return false;
+ }
+
+ if (state->server_pending.width == configured.width &&
+ state->server_pending.height == configured.height) {
+ return true;
+ }
+
+ if (state->server_pending.width == 0 && state->server_pending.height == 0) {
+ return true;
+ }
+
+ return false;
+}
+
+void send_xdg_toplevel_v6_configure(struct wlr_xdg_surface_v6 *surface,
+ struct wlr_xdg_surface_v6_configure *configure) {
+ assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL);
+
+ configure->toplevel_state = malloc(sizeof(*configure->toplevel_state));
+ if (configure->toplevel_state == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed");
+ wl_resource_post_no_memory(surface->toplevel->resource);
+ return;
+ }
+ *configure->toplevel_state = surface->toplevel->server_pending;
+
+ uint32_t *s;
+ struct wl_array states;
+ wl_array_init(&states);
+ if (surface->toplevel->server_pending.maximized) {
+ s = wl_array_add(&states, sizeof(uint32_t));
+ if (!s) {
+ wlr_log(WLR_ERROR,
+ "Could not allocate state for maximized xdg_toplevel");
+ goto error_out;
+ }
+ *s = ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED;
+ }
+ if (surface->toplevel->server_pending.fullscreen) {
+ s = wl_array_add(&states, sizeof(uint32_t));
+ if (!s) {
+ wlr_log(WLR_ERROR,
+ "Could not allocate state for fullscreen xdg_toplevel");
+ goto error_out;
+ }
+ *s = ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN;
+ }
+ if (surface->toplevel->server_pending.resizing) {
+ s = wl_array_add(&states, sizeof(uint32_t));
+ if (!s) {
+ wlr_log(WLR_ERROR,
+ "Could not allocate state for resizing xdg_toplevel");
+ goto error_out;
+ }
+ *s = ZXDG_TOPLEVEL_V6_STATE_RESIZING;
+ }
+ if (surface->toplevel->server_pending.activated) {
+ s = wl_array_add(&states, sizeof(uint32_t));
+ if (!s) {
+ wlr_log(WLR_ERROR,
+ "Could not allocate state for activated xdg_toplevel");
+ goto error_out;
+ }
+ *s = ZXDG_TOPLEVEL_V6_STATE_ACTIVATED;
+ }
+
+ uint32_t width = surface->toplevel->server_pending.width;
+ uint32_t height = surface->toplevel->server_pending.height;
+ zxdg_toplevel_v6_send_configure(surface->toplevel->resource, width,
+ height, &states);
+
+ wl_array_release(&states);
+ return;
+
+error_out:
+ wl_array_release(&states);
+ wl_resource_post_no_memory(surface->toplevel->resource);
+}
+
+void handle_xdg_surface_v6_toplevel_committed(struct wlr_xdg_surface_v6 *surface) {
+ assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL);
+
+ if (!surface->toplevel->added) {
+ // on the first commit, send a configure request to tell the client it
+ // is added
+ schedule_xdg_surface_v6_configure(surface);
+ surface->toplevel->added = true;
+ return;
+ }
+
+ // update state that doesn't need compositor approval
+ surface->toplevel->current.max_width =
+ surface->toplevel->client_pending.max_width;
+ surface->toplevel->current.min_width =
+ surface->toplevel->client_pending.min_width;
+ surface->toplevel->current.max_height =
+ surface->toplevel->client_pending.max_height;
+ surface->toplevel->current.min_height =
+ surface->toplevel->client_pending.min_height;
+}
+
+static void xdg_toplevel_handle_resource_destroy(struct wl_resource *resource) {
+ struct wlr_xdg_surface_v6 *surface =
+ xdg_surface_from_xdg_toplevel_resource(resource);
+ if (surface != NULL) {
+ destroy_xdg_toplevel_v6(surface);
+ }
+}
+
+const struct wlr_surface_role xdg_toplevel_v6_surface_role = {
+ .name = "xdg_toplevel_v6",
+ .commit = handle_xdg_surface_v6_commit,
+ .precommit = handle_xdg_surface_v6_precommit,
+};
+
+void create_xdg_toplevel_v6(struct wlr_xdg_surface_v6 *xdg_surface,
+ uint32_t id) {
+ if (!wlr_surface_set_role(xdg_surface->surface, &xdg_toplevel_v6_surface_role,
+ xdg_surface, xdg_surface->resource, ZXDG_SHELL_V6_ERROR_ROLE)) {
+ return;
+ }
+
+ xdg_surface->toplevel = calloc(1, sizeof(struct wlr_xdg_toplevel_v6));
+ if (xdg_surface->toplevel == NULL) {
+ wl_resource_post_no_memory(xdg_surface->resource);
+ return;
+ }
+ wl_signal_init(&xdg_surface->toplevel->events.request_maximize);
+ wl_signal_init(&xdg_surface->toplevel->events.request_fullscreen);
+ wl_signal_init(&xdg_surface->toplevel->events.request_minimize);
+ wl_signal_init(&xdg_surface->toplevel->events.request_move);
+ wl_signal_init(&xdg_surface->toplevel->events.request_resize);
+ wl_signal_init(&xdg_surface->toplevel->events.request_show_window_menu);
+ wl_signal_init(&xdg_surface->toplevel->events.set_parent);
+ wl_signal_init(&xdg_surface->toplevel->events.set_title);
+ wl_signal_init(&xdg_surface->toplevel->events.set_app_id);
+
+ xdg_surface->role = WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL;
+ xdg_surface->toplevel->base = xdg_surface;
+
+ struct wl_resource *toplevel_resource = wl_resource_create(
+ xdg_surface->client->client, &zxdg_toplevel_v6_interface,
+ wl_resource_get_version(xdg_surface->resource), id);
+ if (toplevel_resource == NULL) {
+ free(xdg_surface->toplevel);
+ wl_resource_post_no_memory(xdg_surface->resource);
+ return;
+ }
+ xdg_surface->toplevel->resource = toplevel_resource;
+ wl_resource_set_implementation(toplevel_resource,
+ &zxdg_toplevel_v6_implementation, xdg_surface,
+ xdg_toplevel_handle_resource_destroy);
+}
+
+uint32_t wlr_xdg_toplevel_v6_set_size(struct wlr_xdg_surface_v6 *surface,
+ uint32_t width, uint32_t height) {
+ assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL);
+ surface->toplevel->server_pending.width = width;
+ surface->toplevel->server_pending.height = height;
+
+ return schedule_xdg_surface_v6_configure(surface);
+}
+
+uint32_t wlr_xdg_toplevel_v6_set_activated(struct wlr_xdg_surface_v6 *surface,
+ bool activated) {
+ assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL);
+ surface->toplevel->server_pending.activated = activated;
+
+ return schedule_xdg_surface_v6_configure(surface);
+}
+
+uint32_t wlr_xdg_toplevel_v6_set_maximized(struct wlr_xdg_surface_v6 *surface,
+ bool maximized) {
+ assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL);
+ surface->toplevel->server_pending.maximized = maximized;
+
+ return schedule_xdg_surface_v6_configure(surface);
+}
+
+uint32_t wlr_xdg_toplevel_v6_set_fullscreen(struct wlr_xdg_surface_v6 *surface,
+ bool fullscreen) {
+ assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL);
+ surface->toplevel->server_pending.fullscreen = fullscreen;
+
+ return schedule_xdg_surface_v6_configure(surface);
+}
+
+uint32_t wlr_xdg_toplevel_v6_set_resizing(struct wlr_xdg_surface_v6 *surface,
+ bool resizing) {
+ assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL);
+ surface->toplevel->server_pending.resizing = resizing;
+
+ return schedule_xdg_surface_v6_configure(surface);
+}
diff --git a/util/array.c b/util/array.c
new file mode 100644
index 00000000..9ee39d33
--- /dev/null
+++ b/util/array.c
@@ -0,0 +1,21 @@
+#include <stdlib.h>
+#include <stdint.h>
+
+// https://www.geeksforgeeks.org/move-zeroes-end-array/
+size_t push_zeroes_to_end(uint32_t arr[], size_t n) {
+ size_t count = 0;
+
+ for (size_t i = 0; i < n; i++) {
+ if (arr[i] != 0) {
+ arr[count++] = arr[i];
+ }
+ }
+
+ size_t ret = count;
+
+ while (count < n) {
+ arr[count++] = 0;
+ }
+
+ return ret;
+}
diff --git a/util/log.c b/util/log.c
new file mode 100644
index 00000000..3ef5f484
--- /dev/null
+++ b/util/log.c
@@ -0,0 +1,91 @@
+#define _POSIX_C_SOURCE 199506L
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <wlr/util/log.h>
+
+static bool colored = true;
+static enum wlr_log_importance log_importance = WLR_ERROR;
+
+static const char *verbosity_colors[] = {
+ [WLR_SILENT] = "",
+ [WLR_ERROR ] = "\x1B[1;31m",
+ [WLR_INFO ] = "\x1B[1;34m",
+ [WLR_DEBUG ] = "\x1B[1;30m",
+};
+
+static void log_stderr(enum wlr_log_importance verbosity, const char *fmt,
+ va_list args) {
+ if (verbosity > log_importance) {
+ return;
+ }
+ // prefix the time to the log message
+ struct tm result;
+ time_t t = time(NULL);
+ struct tm *tm_info = localtime_r(&t, &result);
+ char buffer[26];
+
+ // generate time prefix
+ strftime(buffer, sizeof(buffer), "%F %T - ", tm_info);
+ fprintf(stderr, "%s", buffer);
+
+ unsigned c = (verbosity < WLR_LOG_IMPORTANCE_LAST) ? verbosity : WLR_LOG_IMPORTANCE_LAST - 1;
+
+ if (colored && isatty(STDERR_FILENO)) {
+ fprintf(stderr, "%s", verbosity_colors[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;
+
+void wlr_log_init(enum wlr_log_importance verbosity, wlr_log_func_t callback) {
+ if (verbosity < WLR_LOG_IMPORTANCE_LAST) {
+ log_importance = verbosity;
+ }
+ if (callback) {
+ log_callback = callback;
+ }
+}
+
+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);
+}
+
+// strips the path prefix from filepath
+// will try to strip WLR_SRC_DIR as well as a relative src dir
+// e.g. '/src/build/wlroots/backend/wayland/backend.c' and
+// '../backend/wayland/backend.c' will both be stripped to
+// 'backend/wayland/backend.c'
+const char *_wlr_strip_path(const char *filepath) {
+ static int srclen = sizeof(WLR_SRC_DIR);
+ if (strstr(filepath, WLR_SRC_DIR) == filepath) {
+ filepath += srclen;
+ } else if (*filepath == '.') {
+ while (*filepath == '.' || *filepath == '/') {
+ ++filepath;
+ }
+ }
+ return filepath;
+}
+
+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 00000000..dca3e9a4
--- /dev/null
+++ b/util/meson.build
@@ -0,0 +1,12 @@
+lib_wlr_util = static_library(
+ 'wlr_util',
+ files(
+ 'array.c',
+ 'log.c',
+ 'region.c',
+ 'shm.c',
+ 'signal.c',
+ ),
+ include_directories: wlr_inc,
+ dependencies: [wayland_server, pixman, rt],
+)
diff --git a/util/region.c b/util/region.c
new file mode 100644
index 00000000..61f9c7c7
--- /dev/null
+++ b/util/region.c
@@ -0,0 +1,250 @@
+#include <assert.h>
+#include <math.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <wlr/types/wlr_box.h>
+#include <wlr/util/region.h>
+
+void wlr_region_scale(pixman_region32_t *dst, pixman_region32_t *src,
+ float scale) {
+ if (scale == 1) {
+ pixman_region32_copy(dst, src);
+ return;
+ }
+
+ int nrects;
+ 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);
+ dst_rects[i].x2 = ceil(src_rects[i].x2 * scale);
+ dst_rects[i].y1 = floor(src_rects[i].y1 * scale);
+ dst_rects[i].y2 = ceil(src_rects[i].y2 * scale);
+ }
+
+ pixman_region32_fini(dst);
+ pixman_region32_init_rects(dst, dst_rects, nrects);
+ free(dst_rects);
+}
+
+void wlr_region_transform(pixman_region32_t *dst, 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;
+ 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 = 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_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 = 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_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 = 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;
+ 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 = 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;
+ }
+ }
+
+ pixman_region32_fini(dst);
+ pixman_region32_init_rects(dst, dst_rects, nrects);
+ free(dst_rects);
+}
+
+void wlr_region_expand(pixman_region32_t *dst, pixman_region32_t *src,
+ int distance) {
+ if (distance == 0) {
+ pixman_region32_copy(dst, src);
+ return;
+ }
+
+ int nrects;
+ 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, pixman_region32_t *src,
+ float rotation, int ox, int oy) {
+ if (rotation == 0) {
+ pixman_region32_copy(dst, src);
+ return;
+ }
+
+ int nrects;
+ 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(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, x1, y1, 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) || (!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(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/shm.c b/util/shm.c
new file mode 100644
index 00000000..f7c7303e
--- /dev/null
+++ b/util/shm.c
@@ -0,0 +1,55 @@
+#define _POSIX_C_SOURCE 200112L
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <unistd.h>
+#include <wlr/config.h>
+#include "util/shm.h"
+
+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;
+ }
+}
+
+int create_shm_file(void) {
+ int retries = 100;
+ do {
+ char name[] = "/wlroots-XXXXXX";
+ randname(name + strlen(name) - 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) {
+ shm_unlink(name);
+ return fd;
+ }
+ } while (retries > 0 && errno == EEXIST);
+
+ return -1;
+}
+
+int allocate_shm_file(size_t size) {
+ int fd = create_shm_file();
+ if (fd < 0) {
+ return -1;
+ }
+
+ int ret;
+ do {
+ ret = ftruncate(fd, size);
+ } while (ret < 0 && errno == EINTR);
+ if (ret < 0) {
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
diff --git a/util/signal.c b/util/signal.c
new file mode 100644
index 00000000..39618465
--- /dev/null
+++ b/util/signal.c
@@ -0,0 +1,34 @@
+#include "util/signal.h"
+
+static void handle_noop(struct wl_listener *listener, void *data) {
+ // Do nothing
+}
+
+void wlr_signal_emit_safe(struct wl_signal *signal, void *data) {
+ struct wl_listener cursor;
+ struct wl_listener end;
+
+ /* Add two special markers: one cursor and one end marker. This way, we know
+ * that we've already called listeners on the left of the cursor and that we
+ * don't want to call listeners on the right of the end marker. The 'it'
+ * function can remove any element it wants from the list without troubles.
+ * wl_list_for_each_safe tries to be safe but it fails: it works fine
+ * if the current item is removed, but not if the next one is. */
+ wl_list_insert(&signal->listener_list, &cursor.link);
+ cursor.notify = handle_noop;
+ wl_list_insert(signal->listener_list.prev, &end.link);
+ end.notify = handle_noop;
+
+ while (cursor.link.next != &end.link) {
+ struct wl_list *pos = cursor.link.next;
+ struct wl_listener *l = wl_container_of(pos, l, link);
+
+ wl_list_remove(&cursor.link);
+ wl_list_insert(pos, &cursor.link);
+
+ l->notify(l, data);
+ }
+
+ wl_list_remove(&cursor.link);
+ wl_list_remove(&end.link);
+}
diff --git a/wlroots.syms b/wlroots.syms
new file mode 100644
index 00000000..3176f874
--- /dev/null
+++ b/wlroots.syms
@@ -0,0 +1,10 @@
+{
+ global:
+ wlr_*;
+ _wlr_log;
+ _wlr_vlog;
+ _wlr_strip_path;
+ local:
+ wlr_signal_emit_safe;
+ *;
+};
diff --git a/xcursor/meson.build b/xcursor/meson.build
new file mode 100644
index 00000000..31040a92
--- /dev/null
+++ b/xcursor/meson.build
@@ -0,0 +1,9 @@
+lib_wlr_xcursor = static_library(
+ 'wlr_xcursor',
+ files(
+ 'wlr_xcursor.c',
+ 'xcursor.c',
+ ),
+ include_directories: wlr_inc,
+ dependencies: [egl] # header required via include/wlr/render.h
+)
diff --git a/xcursor/wlr_xcursor.c b/xcursor/wlr_xcursor.c
new file mode 100644
index 00000000..d651497b
--- /dev/null
+++ b/xcursor/wlr_xcursor.c
@@ -0,0 +1,350 @@
+/*
+ * 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.
+ */
+
+#define _POSIX_C_SOURCE 200809L
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wlr/util/log.h>
+#include <wlr/xcursor.h>
+#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(
+ struct cursor_metadata *metadata, struct wlr_xcursor_theme *theme) {
+ struct wlr_xcursor *cursor;
+ struct wlr_xcursor_image *image;
+ int size;
+
+ cursor = malloc(sizeof(*cursor));
+ if (!cursor) {
+ return NULL;
+ }
+
+ cursor->image_count = 1;
+ cursor->images = malloc(sizeof(*cursor->images));
+ if (!cursor->images) {
+ goto err_free_cursor;
+ }
+
+ cursor->name = strdup(metadata->name);
+ cursor->total_delay = 0;
+
+ image = malloc(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;
+
+ 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) {
+ uint32_t i;
+
+ free(theme->name);
+ theme->name = strdup("default");
+
+ theme->cursor_count = sizeof(cursor_metadata) / sizeof(cursor_metadata[0]);
+ theme->cursors = malloc(theme->cursor_count * sizeof(*theme->cursors));
+
+ if (theme->cursors == NULL) {
+ theme->cursor_count = 0;
+ return;
+ }
+
+ for (i = 0; i < theme->cursor_count; ++i) {
+ theme->cursors[i] =
+ xcursor_create_from_data(&cursor_metadata[i], theme);
+
+ if (theme->cursors[i] == NULL) {
+ break;
+ }
+ }
+ theme->cursor_count = i;
+}
+
+static struct wlr_xcursor *xcursor_create_from_xcursor_images(
+ XcursorImages *images, struct wlr_xcursor_theme *theme) {
+ struct wlr_xcursor *cursor;
+ struct wlr_xcursor_image *image;
+ int i, size;
+
+ cursor = malloc(sizeof(*cursor));
+ if (!cursor) {
+ return NULL;
+ }
+
+ cursor->images = malloc(images->nimage * sizeof(cursor->images[0]));
+ if (!cursor->images) {
+ free(cursor);
+ return NULL;
+ }
+
+ cursor->name = strdup(images->name);
+ cursor->total_delay = 0;
+
+ for (i = 0; i < images->nimage; i++) {
+ image = malloc(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 = 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 = i;
+
+ if (cursor->image_count == 0) {
+ free(cursor->name);
+ free(cursor->images);
+ free(cursor);
+ return NULL;
+ }
+
+ return cursor;
+}
+
+static void load_callback(XcursorImages *images, void *data) {
+ struct wlr_xcursor_theme *theme = data;
+ struct wlr_xcursor *cursor;
+
+ if (wlr_xcursor_theme_get_cursor(theme, images->name)) {
+ XcursorImagesDestroy(images);
+ return;
+ }
+
+ cursor = xcursor_create_from_xcursor_images(images, theme);
+
+ if (cursor) {
+ theme->cursor_count++;
+ theme->cursors =
+ realloc(theme->cursors,
+ theme->cursor_count * sizeof(theme->cursors[0]));
+
+ if (theme->cursors == NULL) {
+ theme->cursor_count--;
+ free(cursor);
+ } else {
+ theme->cursors[theme->cursor_count - 1] = cursor;
+ }
+ }
+
+ XcursorImagesDestroy(images);
+}
+
+struct wlr_xcursor_theme *wlr_xcursor_theme_load(const char *name, int size) {
+ struct wlr_xcursor_theme *theme;
+
+ theme = malloc(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', available cursors:",
+ theme->name);
+ for (size_t i = 0; i < theme->cursor_count; ++i) {
+ struct wlr_xcursor *c = theme->cursors[i];
+ struct wlr_xcursor_image *i = c->images[0];
+ wlr_log(WLR_DEBUG, "%s (%u images) %dx%d+%d,%d",
+ c->name, c->image_count,
+ i->width, i->height, i->hotspot_x, i->hotspot_y);
+ }
+
+ return theme;
+
+out_error_name:
+ free(theme);
+ return NULL;
+}
+
+void wlr_xcursor_theme_destroy(struct wlr_xcursor_theme *theme) {
+ unsigned int i;
+
+ for (i = 0; i < theme->cursor_count; i++) {
+ xcursor_destroy(theme->cursors[i]);
+ }
+
+ free(theme->name);
+ free(theme->cursors);
+ free(theme);
+}
+
+struct wlr_xcursor *wlr_xcursor_theme_get_cursor(struct wlr_xcursor_theme *theme,
+ const char *name) {
+ unsigned int i;
+
+ for (i = 0; i < theme->cursor_count; i++) {
+ if (strcmp(name, theme->cursors[i]->name) == 0) {
+ return theme->cursors[i];
+ }
+ }
+
+ return NULL;
+}
+
+static int xcursor_frame_and_duration(struct wlr_xcursor *cursor,
+ uint32_t time, uint32_t *duration) {
+ uint32_t t;
+ int i;
+
+ if (cursor->image_count == 1) {
+ if (duration) {
+ *duration = 0;
+ }
+ return 0;
+ }
+
+ i = 0;
+ 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 00000000..5b20fc56
--- /dev/null
+++ b/xcursor/xcursor.c
@@ -0,0 +1,983 @@
+/*
+ * 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.
+ */
+
+#define _DEFAULT_SOURCE
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "xcursor/xcursor.h"
+
+/*
+ * From libXcursor/include/X11/extensions/Xcursor.h
+ */
+
+#define XcursorTrue 1
+#define XcursorFalse 0
+
+/*
+ * 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 */
+
+/*
+ * Current Xcursor version number. Will be substituted by configure
+ * from the version in the libXcursor configure.ac file.
+ */
+
+#define XCURSOR_LIB_MAJOR 1
+#define XCURSOR_LIB_MINOR 1
+#define XCURSOR_LIB_REVISION 13
+#define XCURSOR_LIB_VERSION ((XCURSOR_LIB_MAJOR * 10000) + \
+ (XCURSOR_LIB_MINOR * 100) + \
+ (XCURSOR_LIB_REVISION))
+
+/*
+ * 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)
+
+typedef struct _XcursorFileToc {
+ XcursorUInt type; /* chunk type */
+ XcursorUInt subtype; /* subtype (size for images) */
+ XcursorUInt position; /* absolute position in file */
+} XcursorFileToc;
+
+typedef struct _XcursorFileHeader {
+ XcursorUInt magic; /* magic number */
+ XcursorUInt header; /* byte length of header */
+ XcursorUInt version; /* file version number */
+ XcursorUInt ntoc; /* number of toc entries */
+ XcursorFileToc *tocs; /* table of contents */
+} XcursorFileHeader;
+
+/*
+ * The rest of the file is a list of chunks, each tagged by type
+ * and version.
+ *
+ * Chunk:
+ * ChunkHeader
+ * <extra type-specific header fields>
+ * <type-specific data>
+ *
+ * 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)
+
+typedef struct _XcursorChunkHeader {
+ XcursorUInt header; /* bytes in chunk header */
+ XcursorUInt type; /* chunk type */
+ XcursorUInt subtype; /* chunk subtype (size for images) */
+ XcursorUInt version; /* version of this type */
+} XcursorChunkHeader;
+
+/*
+ * Here's a list of the known chunk types
+ */
+
+/*
+ * Comments consist of a 4-byte length field followed by
+ * UTF-8 encoded text
+ *
+ * Comment:
+ * ChunkHeader header chunk header
+ * CARD32 length bytes in text
+ * LISTofCARD8 text UTF-8 encoded text
+ */
+
+#define XCURSOR_COMMENT_TYPE 0xfffe0001
+#define XCURSOR_COMMENT_VERSION 1
+#define XCURSOR_COMMENT_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (1 *4))
+#define XCURSOR_COMMENT_COPYRIGHT 1
+#define XCURSOR_COMMENT_LICENSE 2
+#define XCURSOR_COMMENT_OTHER 3
+#define XCURSOR_COMMENT_MAX_LEN 0x100000
+
+typedef struct _XcursorComment {
+ XcursorUInt version;
+ XcursorUInt comment_type;
+ char *comment;
+} XcursorComment;
+
+/*
+ * 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 */
+
+typedef struct _XcursorFile XcursorFile;
+
+struct _XcursorFile {
+ void *closure;
+ int (*read) (XcursorFile *file, unsigned char *buf, int len);
+ int (*write) (XcursorFile *file, unsigned char *buf, int len);
+ int (*seek) (XcursorFile *file, long offset, int whence);
+};
+
+typedef struct _XcursorComments {
+ int ncomment; /* number of comments */
+ XcursorComment **comments; /* array of XcursorComment pointers */
+} XcursorComments;
+
+/*
+ * From libXcursor/src/file.c
+ */
+
+static XcursorImage *
+XcursorImageCreate (int width, int height)
+{
+ XcursorImage *image;
+
+ if (width < 0 || height < 0)
+ return NULL;
+ if (width > XCURSOR_IMAGE_MAX_SIZE || height > XCURSOR_IMAGE_MAX_SIZE)
+ return NULL;
+
+ image = malloc (sizeof (XcursorImage) +
+ width * height * sizeof (XcursorPixel));
+ if (!image)
+ return NULL;
+ image->version = XCURSOR_IMAGE_VERSION;
+ image->pixels = (XcursorPixel *) (image + 1);
+ image->size = width > height ? width : height;
+ image->width = width;
+ image->height = height;
+ image->delay = 0;
+ return image;
+}
+
+static void
+XcursorImageDestroy (XcursorImage *image)
+{
+ free (image);
+}
+
+static XcursorImages *
+XcursorImagesCreate (int size)
+{
+ XcursorImages *images;
+
+ images = malloc (sizeof (XcursorImages) +
+ size * sizeof (XcursorImage *));
+ if (!images)
+ return NULL;
+ images->nimage = 0;
+ images->images = (XcursorImage **) (images + 1);
+ images->name = NULL;
+ return images;
+}
+
+void
+XcursorImagesDestroy (XcursorImages *images)
+{
+ int n;
+
+ if (!images)
+ return;
+
+ for (n = 0; n < images->nimage; n++)
+ XcursorImageDestroy (images->images[n]);
+ if (images->name)
+ free (images->name);
+ free (images);
+}
+
+static void
+XcursorImagesSetName (XcursorImages *images, const char *name)
+{
+ char *new;
+
+ if (!images || !name)
+ return;
+
+ new = malloc (strlen (name) + 1);
+
+ if (!new)
+ return;
+
+ strcpy (new, name);
+ if (images->name)
+ free (images->name);
+ images->name = new;
+}
+
+static XcursorBool
+_XcursorReadUInt (XcursorFile *file, XcursorUInt *u)
+{
+ unsigned char bytes[4];
+
+ if (!file || !u)
+ return XcursorFalse;
+
+ if ((*file->read) (file, bytes, 4) != 4)
+ return XcursorFalse;
+ *u = ((bytes[0] << 0) |
+ (bytes[1] << 8) |
+ (bytes[2] << 16) |
+ (bytes[3] << 24));
+ return XcursorTrue;
+}
+
+static void
+_XcursorFileHeaderDestroy (XcursorFileHeader *fileHeader)
+{
+ free (fileHeader);
+}
+
+static XcursorFileHeader *
+_XcursorFileHeaderCreate (int ntoc)
+{
+ XcursorFileHeader *fileHeader;
+
+ if (ntoc > 0x10000)
+ return NULL;
+ fileHeader = malloc (sizeof (XcursorFileHeader) +
+ ntoc * sizeof (XcursorFileToc));
+ if (!fileHeader)
+ return NULL;
+ fileHeader->magic = XCURSOR_MAGIC;
+ fileHeader->header = XCURSOR_FILE_HEADER_LEN;
+ fileHeader->version = XCURSOR_FILE_VERSION;
+ fileHeader->ntoc = ntoc;
+ fileHeader->tocs = (XcursorFileToc *) (fileHeader + 1);
+ return fileHeader;
+}
+
+static XcursorFileHeader *
+_XcursorReadFileHeader (XcursorFile *file)
+{
+ XcursorFileHeader head, *fileHeader;
+ XcursorUInt skip;
+ unsigned int n;
+
+ if (!file)
+ return NULL;
+
+ if (!_XcursorReadUInt (file, &head.magic))
+ return NULL;
+ if (head.magic != XCURSOR_MAGIC)
+ return NULL;
+ if (!_XcursorReadUInt (file, &head.header))
+ return NULL;
+ if (!_XcursorReadUInt (file, &head.version))
+ return NULL;
+ if (!_XcursorReadUInt (file, &head.ntoc))
+ return NULL;
+ skip = head.header - XCURSOR_FILE_HEADER_LEN;
+ if (skip)
+ if ((*file->seek) (file, skip, SEEK_CUR) == EOF)
+ return NULL;
+ fileHeader = _XcursorFileHeaderCreate (head.ntoc);
+ if (!fileHeader)
+ return NULL;
+ fileHeader->magic = head.magic;
+ fileHeader->header = head.header;
+ fileHeader->version = head.version;
+ fileHeader->ntoc = head.ntoc;
+ for (n = 0; n < fileHeader->ntoc; n++)
+ {
+ if (!_XcursorReadUInt (file, &fileHeader->tocs[n].type))
+ break;
+ if (!_XcursorReadUInt (file, &fileHeader->tocs[n].subtype))
+ break;
+ if (!_XcursorReadUInt (file, &fileHeader->tocs[n].position))
+ break;
+ }
+ if (n != fileHeader->ntoc)
+ {
+ _XcursorFileHeaderDestroy (fileHeader);
+ return NULL;
+ }
+ return fileHeader;
+}
+
+static XcursorBool
+_XcursorSeekToToc (XcursorFile *file,
+ XcursorFileHeader *fileHeader,
+ int toc)
+{
+ if (!file || !fileHeader || \
+ (*file->seek) (file, fileHeader->tocs[toc].position, SEEK_SET) == EOF)
+ return XcursorFalse;
+ return XcursorTrue;
+}
+
+static XcursorBool
+_XcursorFileReadChunkHeader (XcursorFile *file,
+ XcursorFileHeader *fileHeader,
+ int toc,
+ XcursorChunkHeader *chunkHeader)
+{
+ if (!file || !fileHeader || !chunkHeader)
+ return XcursorFalse;
+ if (!_XcursorSeekToToc (file, fileHeader, toc))
+ return XcursorFalse;
+ if (!_XcursorReadUInt (file, &chunkHeader->header))
+ return XcursorFalse;
+ if (!_XcursorReadUInt (file, &chunkHeader->type))
+ return XcursorFalse;
+ if (!_XcursorReadUInt (file, &chunkHeader->subtype))
+ return XcursorFalse;
+ if (!_XcursorReadUInt (file, &chunkHeader->version))
+ return XcursorFalse;
+ /* sanity check */
+ if (chunkHeader->type != fileHeader->tocs[toc].type ||
+ chunkHeader->subtype != fileHeader->tocs[toc].subtype)
+ return XcursorFalse;
+ return XcursorTrue;
+}
+
+#define dist(a,b) ((a) > (b) ? (a) - (b) : (b) - (a))
+
+static XcursorDim
+_XcursorFindBestSize (XcursorFileHeader *fileHeader,
+ XcursorDim size,
+ int *nsizesp)
+{
+ unsigned int n;
+ int nsizes = 0;
+ XcursorDim bestSize = 0;
+ XcursorDim thisSize;
+
+ if (!fileHeader || !nsizesp)
+ return 0;
+
+ for (n = 0; n < fileHeader->ntoc; n++)
+ {
+ if (fileHeader->tocs[n].type != XCURSOR_IMAGE_TYPE)
+ continue;
+ thisSize = fileHeader->tocs[n].subtype;
+ if (!bestSize || dist (thisSize, size) < dist (bestSize, size))
+ {
+ bestSize = thisSize;
+ nsizes = 1;
+ }
+ else if (thisSize == bestSize)
+ nsizes++;
+ }
+ *nsizesp = nsizes;
+ return bestSize;
+}
+
+static int
+_XcursorFindImageToc (XcursorFileHeader *fileHeader,
+ XcursorDim size,
+ int count)
+{
+ unsigned int toc;
+ XcursorDim thisSize;
+
+ if (!fileHeader)
+ return 0;
+
+ for (toc = 0; toc < fileHeader->ntoc; toc++)
+ {
+ if (fileHeader->tocs[toc].type != XCURSOR_IMAGE_TYPE)
+ continue;
+ thisSize = fileHeader->tocs[toc].subtype;
+ if (thisSize != size)
+ continue;
+ if (!count)
+ break;
+ count--;
+ }
+ if (toc == fileHeader->ntoc)
+ return -1;
+ return toc;
+}
+
+static XcursorImage *
+_XcursorReadImage (XcursorFile *file,
+ XcursorFileHeader *fileHeader,
+ int toc)
+{
+ XcursorChunkHeader chunkHeader;
+ XcursorImage head;
+ XcursorImage *image;
+ int n;
+ XcursorPixel *p;
+
+ if (!file || !fileHeader)
+ return NULL;
+
+ if (!_XcursorFileReadChunkHeader (file, fileHeader, toc, &chunkHeader))
+ return NULL;
+ if (!_XcursorReadUInt (file, &head.width))
+ return NULL;
+ if (!_XcursorReadUInt (file, &head.height))
+ return NULL;
+ if (!_XcursorReadUInt (file, &head.xhot))
+ return NULL;
+ if (!_XcursorReadUInt (file, &head.yhot))
+ return NULL;
+ if (!_XcursorReadUInt (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 = XcursorImageCreate (head.width, head.height);
+ if (image == NULL)
+ return NULL;
+ if (chunkHeader.version < image->version)
+ image->version = chunkHeader.version;
+ image->size = chunkHeader.subtype;
+ image->xhot = head.xhot;
+ image->yhot = head.yhot;
+ image->delay = head.delay;
+ n = image->width * image->height;
+ p = image->pixels;
+ while (n--)
+ {
+ if (!_XcursorReadUInt (file, p))
+ {
+ XcursorImageDestroy (image);
+ return NULL;
+ }
+ p++;
+ }
+ return image;
+}
+
+static XcursorImages *
+XcursorXcFileLoadImages (XcursorFile *file, int size)
+{
+ XcursorFileHeader *fileHeader;
+ XcursorDim bestSize;
+ int nsize;
+ XcursorImages *images;
+ int n;
+ int toc;
+
+ if (!file || size < 0)
+ return NULL;
+ fileHeader = _XcursorReadFileHeader (file);
+ if (!fileHeader)
+ return NULL;
+ bestSize = _XcursorFindBestSize (fileHeader, (XcursorDim) size, &nsize);
+ if (!bestSize)
+ {
+ _XcursorFileHeaderDestroy (fileHeader);
+ return NULL;
+ }
+ images = XcursorImagesCreate (nsize);
+ if (!images)
+ {
+ _XcursorFileHeaderDestroy (fileHeader);
+ return NULL;
+ }
+ for (n = 0; n < nsize; n++)
+ {
+ toc = _XcursorFindImageToc (fileHeader, bestSize, n);
+ if (toc < 0)
+ break;
+ images->images[images->nimage] = _XcursorReadImage (file, fileHeader,
+ toc);
+ if (!images->images[images->nimage])
+ break;
+ images->nimage++;
+ }
+ _XcursorFileHeaderDestroy (fileHeader);
+ if (images->nimage != nsize)
+ {
+ XcursorImagesDestroy (images);
+ images = NULL;
+ }
+ return images;
+}
+
+static int
+_XcursorStdioFileRead (XcursorFile *file, unsigned char *buf, int len)
+{
+ FILE *f = file->closure;
+ return fread (buf, 1, len, f);
+}
+
+static int
+_XcursorStdioFileWrite (XcursorFile *file, unsigned char *buf, int len)
+{
+ FILE *f = file->closure;
+ return fwrite (buf, 1, len, f);
+}
+
+static int
+_XcursorStdioFileSeek (XcursorFile *file, long offset, int whence)
+{
+ FILE *f = file->closure;
+ return fseek (f, offset, whence);
+}
+
+static void
+_XcursorStdioFileInitialize (FILE *stdfile, XcursorFile *file)
+{
+ file->closure = stdfile;
+ file->read = _XcursorStdioFileRead;
+ file->write = _XcursorStdioFileWrite;
+ file->seek = _XcursorStdioFileSeek;
+}
+
+static XcursorImages *
+XcursorFileLoadImages (FILE *file, int size)
+{
+ XcursorFile f;
+
+ if (!file)
+ return NULL;
+
+ _XcursorStdioFileInitialize (file, &f);
+ return XcursorXcFileLoadImages (&f, size);
+}
+
+/*
+ * From libXcursor/src/library.c
+ */
+
+#ifndef ICONDIR
+#define ICONDIR "/usr/X11R6/lib/X11/icons"
+#endif
+
+#ifndef XCURSORPATH
+#define XCURSORPATH "~/.local/share/icons:~/.icons:/usr/share/icons:/usr/share/pixmaps:"ICONDIR
+#endif
+
+static const char *
+XcursorLibraryPath (void)
+{
+ static const char *path;
+
+ if (!path)
+ {
+ path = getenv ("XCURSOR_PATH");
+ if (!path)
+ path = XCURSORPATH;
+ }
+ return path;
+}
+
+static void
+_XcursorAddPathElt (char *path, const char *elt, int len)
+{
+ int pathlen = strlen (path);
+
+ /* append / if the path doesn't currently have one */
+ if (path[0] == '\0' || path[pathlen - 1] != '/')
+ {
+ strcat (path, "/");
+ pathlen++;
+ }
+ if (len == -1)
+ len = strlen (elt);
+ /* strip leading slashes */
+ while (len && elt[0] == '/')
+ {
+ elt++;
+ len--;
+ }
+ strncpy (path + pathlen, elt, len);
+ path[pathlen + len] = '\0';
+}
+
+static char *
+_XcursorBuildThemeDir (const char *dir, const char *theme)
+{
+ const char *colon;
+ const char *tcolon;
+ char *full;
+ char *home;
+ int dirlen;
+ int homelen;
+ int themelen;
+ int len;
+
+ 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 = NULL;
+ homelen = 0;
+ if (*dir == '~')
+ {
+ home = getenv ("HOME");
+ if (!home)
+ return NULL;
+ homelen = strlen (home);
+ dir++;
+ dirlen--;
+ }
+
+ /*
+ * add space for any needed directory separators, one per component,
+ * and one for the trailing null
+ */
+ len = 1 + homelen + 1 + dirlen + 1 + themelen + 1;
+
+ full = malloc (len);
+ if (!full)
+ return NULL;
+ full[0] = '\0';
+
+ if (home)
+ _XcursorAddPathElt (full, home, -1);
+ _XcursorAddPathElt (full, dir, dirlen);
+ _XcursorAddPathElt (full, theme, themelen);
+ return full;
+}
+
+static char *
+_XcursorBuildFullname (const char *dir, const char *subdir, const char *file)
+{
+ char *full;
+
+ if (!dir || !subdir || !file)
+ return NULL;
+
+ full = malloc (strlen (dir) + 1 + strlen (subdir) + 1 + strlen (file) + 1);
+ if (!full)
+ return NULL;
+ full[0] = '\0';
+ _XcursorAddPathElt (full, dir, -1);
+ _XcursorAddPathElt (full, subdir, -1);
+ _XcursorAddPathElt (full, file, -1);
+ return full;
+}
+
+static const char *
+_XcursorNextPath (const char *path)
+{
+ char *colon = strchr (path, ':');
+
+ if (!colon)
+ return NULL;
+ return colon + 1;
+}
+
+#define XcursorWhite(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
+#define XcursorSep(c) ((c) == ';' || (c) == ',')
+
+static char *
+_XcursorThemeInherits (const char *full)
+{
+ char line[8192];
+ char *result = NULL;
+ FILE *f;
+
+ if (!full)
+ return NULL;
+
+ f = fopen (full, "r");
+ if (f)
+ {
+ while (fgets (line, sizeof (line), f))
+ {
+ if (!strncmp (line, "Inherits", 8))
+ {
+ char *l = line + 8;
+ char *r;
+ while (*l == ' ') l++;
+ if (*l != '=') continue;
+ l++;
+ while (*l == ' ') l++;
+ result = malloc (strlen (l) + 1);
+ if (result)
+ {
+ r = result;
+ while (*l)
+ {
+ while (XcursorSep(*l) || XcursorWhite (*l)) l++;
+ if (!*l)
+ break;
+ if (r != result)
+ *r++ = ':';
+ while (*l && !XcursorWhite(*l) &&
+ !XcursorSep(*l))
+ *r++ = *l++;
+ }
+ *r++ = '\0';
+ }
+ break;
+ }
+ }
+ fclose (f);
+ }
+ return result;
+}
+
+static FILE *
+XcursorScanTheme (const char *theme, const char *name)
+{
+ FILE *f = NULL;
+ char *full;
+ char *dir;
+ const char *path;
+ char *inherits = NULL;
+ const char *i;
+
+ if (!theme || !name)
+ return NULL;
+
+ /*
+ * Scan this theme
+ */
+ for (path = XcursorLibraryPath ();
+ path && f == NULL;
+ path = _XcursorNextPath (path))
+ {
+ dir = _XcursorBuildThemeDir (path, theme);
+ if (dir)
+ {
+ full = _XcursorBuildFullname (dir, "cursors", name);
+ if (full)
+ {
+ f = fopen (full, "r");
+ free (full);
+ }
+ if (!f && !inherits)
+ {
+ full = _XcursorBuildFullname (dir, "", "index.theme");
+ if (full)
+ {
+ inherits = _XcursorThemeInherits (full);
+ free (full);
+ }
+ }
+ free (dir);
+ }
+ }
+ /*
+ * Recurse to scan inherited themes
+ */
+ for (i = inherits; i && f == NULL; i = _XcursorNextPath (i))
+ {
+ if (strcmp(i, theme) != 0)
+ f = XcursorScanTheme (i, name);
+ else
+ printf("Not calling XcursorScanTheme because of circular dependency: %s. %s", i, name);
+ }
+ if (inherits != NULL)
+ free (inherits);
+ return f;
+}
+
+XcursorImages *
+XcursorLibraryLoadImages (const char *file, const char *theme, int size)
+{
+ FILE *f = NULL;
+ XcursorImages *images = NULL;
+
+ if (!file)
+ return NULL;
+
+ if (theme)
+ f = XcursorScanTheme (theme, file);
+ if (!f)
+ f = XcursorScanTheme ("default", file);
+ if (f)
+ {
+ images = XcursorFileLoadImages (f, size);
+ if (images)
+ XcursorImagesSetName (images, file);
+ fclose (f);
+ }
+ return images;
+}
+
+static void
+load_all_cursors_from_dir(const char *path, int size,
+ void (*load_callback)(XcursorImages *, void *),
+ void *user_data)
+{
+ FILE *f;
+ DIR *dir = opendir(path);
+ struct dirent *ent;
+ char *full;
+ XcursorImages *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 = _XcursorBuildFullname(path, "", ent->d_name);
+ if (!full)
+ continue;
+
+ f = fopen(full, "r");
+ if (!f) {
+ free(full);
+ continue;
+ }
+
+ images = XcursorFileLoadImages(f, size);
+
+ if (images) {
+ XcursorImagesSetName(images, 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 XcursorImages 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 XcursorImages
+ * object which have the same name. The user is expected to destroy the
+ * XcursorImages objects passed to the callback with
+ * XcursorImagesDestroy().
+ *
+ * \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 XcursorImages
+ * 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)(XcursorImages *, void *),
+ void *user_data)
+{
+ char *full, *dir;
+ char *inherits = NULL;
+ const char *path, *i;
+
+ if (!theme)
+ theme = "default";
+
+ for (path = XcursorLibraryPath();
+ path;
+ path = _XcursorNextPath(path)) {
+ dir = _XcursorBuildThemeDir(path, theme);
+ if (!dir)
+ continue;
+
+ full = _XcursorBuildFullname(dir, "cursors", "");
+
+ if (full) {
+ load_all_cursors_from_dir(full, size, load_callback,
+ user_data);
+ free(full);
+ }
+
+ if (!inherits) {
+ full = _XcursorBuildFullname(dir, "", "index.theme");
+ if (full) {
+ inherits = _XcursorThemeInherits(full);
+ free(full);
+ }
+ }
+
+ free(dir);
+ }
+
+ for (i = inherits; i; i = _XcursorNextPath(i))
+ xcursor_load_theme(i, size, load_callback, user_data);
+
+ if (inherits)
+ free(inherits);
+}
diff --git a/xwayland/meson.build b/xwayland/meson.build
new file mode 100644
index 00000000..e5f9b9be
--- /dev/null
+++ b/xwayland/meson.build
@@ -0,0 +1,51 @@
+xwayland_libs = []
+xwayland_required = [
+ 'xcb',
+ 'xcb-composite',
+ 'xcb-render',
+ 'xcb-xfixes',
+]
+xwayland_optional = [
+ 'xcb-errors',
+ 'xcb-icccm',
+]
+
+foreach lib : xwayland_required
+ dep = dependency(lib, required: get_option('xwayland'))
+ if not dep.found()
+ subdir_done()
+ endif
+
+ xwayland_libs += dep
+endforeach
+
+foreach lib : xwayland_optional
+ dep = dependency(lib, required: get_option(lib))
+ if dep.found()
+ xwayland_libs += dep
+ conf_data.set10('WLR_HAS_' + lib.underscorify().to_upper(), true)
+ endif
+endforeach
+
+lib_wlr_xwayland = static_library(
+ 'wlr_xwayland',
+ files(
+ 'selection/dnd.c',
+ 'selection/incoming.c',
+ 'selection/outgoing.c',
+ 'selection/selection.c',
+ 'sockets.c',
+ 'xwayland.c',
+ 'xwm.c',
+ ),
+ include_directories: wlr_inc,
+ dependencies: [
+ wayland_server,
+ xwayland_libs,
+ xkbcommon,
+ pixman,
+ ],
+)
+
+wlr_parts += lib_wlr_xwayland
+conf_data.set10('WLR_HAS_XWAYLAND', true)
diff --git a/xwayland/selection/dnd.c b/xwayland/selection/dnd.c
new file mode 100644
index 00000000..ec5f16c7
--- /dev/null
+++ b/xwayland/selection/dnd.c
@@ -0,0 +1,339 @@
+#include <assert.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_gtk_primary_selection.h>
+#include <wlr/util/log.h>
+#include <xcb/xfixes.h>
+#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_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_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_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_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_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_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=%d 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=%d 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 00000000..3e97ca04
--- /dev/null
+++ b/xwayland/selection/incoming.c
@@ -0,0 +1,464 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_primary_selection.h>
+#include <wlr/util/log.h>
+#include <xcb/xfixes.h>
+#include "xwayland/selection.h"
+#include "xwayland/xwm.h"
+
+/**
+ * Write the X11 selection to a Wayland client.
+ */
+static int xwm_data_source_write(int fd, uint32_t mask, void *data) {
+ struct wlr_xwm_selection_transfer *transfer = data;
+ struct wlr_xwm *xwm = transfer->selection->xwm;
+
+ 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) {
+ xwm_selection_transfer_destroy_property_reply(transfer);
+ xwm_selection_transfer_remove_source(transfer);
+ xwm_selection_transfer_close_source_fd(transfer);
+ wlr_log(WLR_ERROR, "write error to target fd: %m");
+ return 1;
+ }
+
+ wlr_log(WLR_DEBUG, "wrote %zd (chunk size %zd) of %d bytes",
+ transfer->property_start + len,
+ len, xcb_get_property_value_length(transfer->property_reply));
+
+ transfer->property_start += len;
+ if (len == remainder) {
+ xwm_selection_transfer_destroy_property_reply(transfer);
+ xwm_selection_transfer_remove_source(transfer);
+
+ if (transfer->incr) {
+ wlr_log(WLR_DEBUG, "deleting property");
+ xcb_delete_property(xwm->xcb_conn, transfer->selection->window,
+ xwm->atoms[WL_SELECTION]);
+ xcb_flush(xwm->xcb_conn);
+ } else {
+ wlr_log(WLR_DEBUG, "transfer complete");
+ xwm_selection_transfer_close_source_fd(transfer);
+ }
+ }
+
+ return 1;
+}
+
+static void xwm_write_property(struct wlr_xwm_selection_transfer *transfer,
+ xcb_get_property_reply_t *reply) {
+ struct wlr_xwm *xwm = transfer->selection->xwm;
+
+ transfer->property_start = 0;
+ transfer->property_reply = reply;
+
+ xwm_data_source_write(transfer->source_fd, WL_EVENT_WRITABLE, transfer);
+
+ if (transfer->property_reply != NULL) {
+ struct wl_event_loop *loop =
+ wl_display_get_event_loop(xwm->xwayland->wl_display);
+ transfer->source = wl_event_loop_add_fd(loop,
+ transfer->source_fd, WL_EVENT_WRITABLE, xwm_data_source_write,
+ transfer);
+ }
+}
+
+void xwm_get_incr_chunk(struct wlr_xwm_selection_transfer *transfer) {
+ struct wlr_xwm *xwm = transfer->selection->xwm;
+ wlr_log(WLR_DEBUG, "xwm_get_incr_chunk");
+
+ xcb_get_property_cookie_t cookie = xcb_get_property(xwm->xcb_conn,
+ 0, // delete
+ transfer->selection->window,
+ xwm->atoms[WL_SELECTION],
+ XCB_GET_PROPERTY_TYPE_ANY,
+ 0, // offset
+ 0x1fffffff // length
+ );
+
+ xcb_get_property_reply_t *reply =
+ xcb_get_property_reply(xwm->xcb_conn, cookie, NULL);
+ if (reply == NULL) {
+ wlr_log(WLR_ERROR, "cannot get selection property");
+ return;
+ }
+ //dump_property(xwm, xwm->atoms[WL_SELECTION], reply);
+
+ if (xcb_get_property_value_length(reply) > 0) {
+ /* Reply's ownership is transferred to xwm, which is responsible
+ * for freeing it */
+ xwm_write_property(transfer, reply);
+ } else {
+ wlr_log(WLR_DEBUG, "transfer complete");
+ xwm_selection_transfer_close_source_fd(transfer);
+ free(reply);
+ }
+}
+
+static void xwm_selection_get_data(struct wlr_xwm_selection *selection) {
+ 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
+ 0x1fffffff // length
+ );
+
+ xcb_get_property_reply_t *reply =
+ xcb_get_property_reply(xwm->xcb_conn, cookie, NULL);
+ if (reply == NULL) {
+ wlr_log(WLR_ERROR, "Cannot get selection property");
+ return;
+ }
+
+ struct wlr_xwm_selection_transfer *transfer = &selection->incoming;
+ if (reply->type == xwm->atoms[INCR]) {
+ transfer->incr = true;
+ free(reply);
+ } else {
+ transfer->incr = false;
+ // reply's ownership is transferred to wm, which is responsible
+ // for freeing it
+ xwm_write_property(transfer, reply);
+ }
+}
+
+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;
+ struct wlr_xwm_selection_transfer *transfer = &selection->incoming;
+
+ 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");
+ return;
+ }
+
+ xcb_convert_selection(xwm->xcb_conn,
+ selection->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->source_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));
+ return (struct x11_data_source *)wlr_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_cancel(struct wlr_data_source *wlr_source) {
+ struct x11_data_source *source =
+ data_source_from_wlr_data_source(wlr_source);
+ wlr_data_source_finish(&source->base);
+ wl_array_release(&source->mime_types_atoms);
+ free(source);
+}
+
+static const struct wlr_data_source_impl data_source_impl = {
+ .send = data_source_send,
+ .cancel = data_source_cancel,
+};
+
+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 =
+ (struct x11_primary_selection_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 primary_selection_source_destroy(
+ struct wlr_primary_selection_source *wlr_source) {
+ struct x11_primary_selection_source *source =
+ (struct x11_primary_selection_source *)wlr_source;
+ 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(struct x11_data_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_set_selection(xwm->seat, &source->base,
+ wl_display_next_serial(xwm->xwayland->wl_display));
+ } else {
+ wlr_data_source_cancel(&source->base);
+ }
+ } else if (selection == &xwm->primary_selection) {
+ struct x11_primary_selection_source *source =
+ calloc(1, sizeof(struct x11_primary_selection_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);
+ } 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;
+ }
+
+ if (event->property == XCB_ATOM_NONE) {
+ wlr_log(WLR_ERROR, "convert selection failed");
+ } 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 {
+ xwm_selection_get_data(selection);
+ }
+}
+
+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_set_selection(xwm->seat, NULL,
+ wl_display_next_serial(xwm->xwayland->wl_display));
+ } else if (selection == &xwm->primary_selection) {
+ wlr_seat_set_primary_selection(xwm->seat, NULL);
+ } 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;
+ }
+
+ selection->owner = event->owner;
+
+ // 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.
+ if (event->owner == selection->window) {
+ selection->timestamp = event->timestamp;
+ return 1;
+ }
+
+ struct wlr_xwm_selection_transfer *transfer = &selection->incoming;
+ transfer->incr = false;
+ // 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 00000000..fd5021d5
--- /dev/null
+++ b/xwayland/selection/outgoing.c
@@ -0,0 +1,419 @@
+#include <assert.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_primary_selection.h>
+#include <wlr/util/log.h>
+#include <xcb/xfixes.h>
+#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=%d SelectionNotify(31) time=%d "
+ "requestor=%d selection=%d target=%d property=%d", 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);
+
+static void xwm_selection_transfer_destroy_outgoing(
+ struct wlr_xwm_selection_transfer *transfer) {
+ wl_list_remove(&transfer->outgoing_link);
+
+ // Start next queued transfer
+ struct wlr_xwm_selection_transfer *first = NULL;
+ if (!wl_list_empty(&transfer->selection->outgoing)) {
+ first = wl_container_of(transfer->selection->outgoing.prev, first,
+ outgoing_link);
+ xwm_selection_transfer_start_outgoing(first);
+ }
+
+ xwm_selection_transfer_remove_source(transfer);
+ xwm_selection_transfer_close_source_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(WLR_ERROR, "read error from data source: %m");
+ 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_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_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_source(transfer);
+ xwm_selection_transfer_close_source_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->source_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);
+ transfer->source = wl_event_loop_add_fd(loop, transfer->source_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 void 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");
+ xwm_selection_send_notify(selection->xwm, req, false);
+ return;
+ }
+
+ 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);
+ xwm_selection_send_notify(selection->xwm, req, false);
+ return;
+ }
+
+ struct wlr_xwm_selection_transfer *transfer =
+ calloc(1, sizeof(struct wlr_xwm_selection_transfer));
+ if (transfer == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed");
+ return;
+ }
+ transfer->selection = selection;
+ transfer->request = *req;
+ wl_array_init(&transfer->source_data);
+
+ int p[2];
+ if (pipe(p) == -1) {
+ wlr_log(WLR_ERROR, "pipe() failed: %m");
+ xwm_selection_send_notify(selection->xwm, req, false);
+ return;
+ }
+ 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->source_fd = p[0];
+
+ wlr_log(WLR_DEBUG, "Sending Wayland selection %u to Xwayland window with "
+ "MIME type %s, target %u", req->target, mime_type, req->target);
+ xwm_selection_source_send(selection, mime_type, p[1]);
+
+ wl_list_insert(&selection->outgoing, &transfer->outgoing_link);
+
+ // We can only handle one transfer at a time
+ if (wl_list_length(&selection->outgoing) == 1) {
+ xwm_selection_transfer_start_outgoing(transfer);
+ }
+}
+
+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");
+ return;
+ }
+
+ if (selection->window != req->owner) {
+ wlr_log(WLR_DEBUG, "received selection request with invalid owner");
+ return;
+ }
+
+ // No xwayland surface focused, deny access to clipboard
+ if (xwm->focus_surface == NULL && xwm->drag_focus == NULL) {
+ 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);
+ xwm_selection_send_notify(xwm, req, false);
+ return;
+ }
+
+ 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);
+ xwm_selection_send_notify(xwm, req, false);
+ return;
+ }
+ xwm_selection_send_data(selection, req, mime_type);
+ free(mime_type);
+ }
+}
diff --git a/xwayland/selection/selection.c b/xwayland/selection/selection.c
new file mode 100644
index 00000000..4d7732cb
--- /dev/null
+++ b/xwayland/selection/selection.c
@@ -0,0 +1,317 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_primary_selection.h>
+#include <wlr/util/log.h>
+#include <xcb/xfixes.h>
+#include "xwayland/selection.h"
+#include "xwayland/xwm.h"
+
+void xwm_selection_transfer_remove_source(
+ struct wlr_xwm_selection_transfer *transfer) {
+ if (transfer->source != NULL) {
+ wl_event_source_remove(transfer->source);
+ transfer->source = NULL;
+ }
+}
+
+void xwm_selection_transfer_close_source_fd(
+ struct wlr_xwm_selection_transfer *transfer) {
+ if (transfer->source_fd >= 0) {
+ close(transfer->source_fd);
+ transfer->source_fd = -1;
+ }
+}
+
+void xwm_selection_transfer_destroy_property_reply(
+ struct wlr_xwm_selection_transfer *transfer) {
+ free(transfer->property_reply);
+ transfer->property_reply = NULL;
+}
+
+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->window == selection->window) {
+ if (event->state == XCB_PROPERTY_NEW_VALUE &&
+ event->atom == xwm->atoms[WL_SELECTION] &&
+ selection->incoming.incr) {
+ xwm_get_incr_chunk(&selection->incoming);
+ }
+ return 1;
+ }
+
+ struct wlr_xwm_selection_transfer *outgoing;
+ wl_list_for_each(outgoing, &selection->outgoing, 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;
+}
+
+static void selection_init(struct wlr_xwm *xwm,
+ struct wlr_xwm_selection *selection, xcb_atom_t atom) {
+ selection->xwm = xwm;
+ selection->atom = atom;
+ selection->window = xwm->selection_window;
+ selection->incoming.selection = selection;
+ wl_list_init(&selection->outgoing);
+
+ 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_init(struct wlr_xwm *xwm) {
+ // Clipboard and primary selection
+ uint32_t selection_values[] = {
+ XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE
+ };
+ xwm->selection_window = xcb_generate_id(xwm->xcb_conn);
+ xcb_create_window(xwm->xcb_conn,
+ XCB_COPY_FROM_PARENT,
+ xwm->selection_window,
+ xwm->screen->root,
+ 0, 0,
+ 10, 10,
+ 0,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ xwm->screen->root_visual,
+ XCB_CW_EVENT_MASK, selection_values);
+
+ xcb_set_selection_owner(xwm->xcb_conn,
+ xwm->selection_window,
+ xwm->atoms[CLIPBOARD_MANAGER],
+ XCB_TIME_CURRENT_TIME);
+
+ selection_init(xwm, &xwm->clipboard_selection, xwm->atoms[CLIPBOARD]);
+ selection_init(xwm, &xwm->primary_selection, xwm->atoms[PRIMARY]);
+
+ // Drag'n'drop
+ uint32_t dnd_values[] = {
+ XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE
+ };
+ xwm->dnd_window = xcb_generate_id(xwm->xcb_conn);
+ xcb_create_window(xwm->xcb_conn,
+ XCB_COPY_FROM_PARENT,
+ xwm->dnd_window,
+ xwm->screen->root,
+ 0, 0,
+ 8192, 8192,
+ 0,
+ XCB_WINDOW_CLASS_INPUT_ONLY,
+ xwm->screen->root_visual,
+ XCB_CW_EVENT_MASK, dnd_values);
+
+ uint32_t version = XDND_VERSION;
+ xcb_change_property(xwm->xcb_conn,
+ XCB_PROP_MODE_REPLACE,
+ xwm->dnd_window,
+ xwm->atoms[DND_AWARE],
+ XCB_ATOM_ATOM,
+ 32, // format
+ 1, &version);
+
+ selection_init(xwm, &xwm->dnd_selection, xwm->atoms[DND_SELECTION]);
+}
+
+void xwm_selection_finish(struct wlr_xwm *xwm) {
+ if (!xwm) {
+ return;
+ }
+ if (xwm->selection_window) {
+ xcb_destroy_window(xwm->xcb_conn, xwm->selection_window);
+ }
+ if (xwm->dnd_window) {
+ xcb_destroy_window(xwm->xcb_conn, xwm->dnd_window);
+ }
+ 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);
+ }
+ wlr_xwayland_set_seat(xwm->xwayland, NULL);
+ }
+}
+
+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);
+ } else {
+ if (selection->owner == selection->window) {
+ xcb_set_selection_owner(selection->xwm->xcb_conn,
+ XCB_WINDOW_NONE,
+ selection->atom,
+ selection->timestamp);
+ }
+ }
+}
+
+static void seat_handle_selection(struct wl_listener *listener,
+ void *data) {
+ struct wlr_seat *seat = data;
+ struct wlr_xwm *xwm =
+ wl_container_of(listener, xwm, seat_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 seat_handle_primary_selection(struct wl_listener *listener,
+ void *data) {
+ struct wlr_seat *seat = data;
+ struct wlr_xwm *xwm =
+ wl_container_of(listener, xwm, seat_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_selection.link);
+ wl_list_remove(&xwm->seat_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.selection, &xwm->seat_selection);
+ xwm->seat_selection.notify = seat_handle_selection;
+ wl_signal_add(&seat->events.primary_selection, &xwm->seat_primary_selection);
+ xwm->seat_primary_selection.notify = seat_handle_primary_selection;
+ wl_signal_add(&seat->events.start_drag, &xwm->seat_start_drag);
+ xwm->seat_start_drag.notify = seat_handle_start_drag;
+
+ seat_handle_selection(&xwm->seat_selection, seat);
+ seat_handle_primary_selection(&xwm->seat_primary_selection, seat);
+}
diff --git a/xwayland/sockets.c b/xwayland/sockets.c
new file mode 100644
index 00000000..112a8bb0
--- /dev/null
+++ b/xwayland/sockets.c
@@ -0,0 +1,168 @@
+#define _POSIX_C_SOURCE 200809L
+#ifdef __FreeBSD__
+// for SOCK_CLOEXEC
+#define __BSD_VISIBLE 1
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <wlr/util/log.h>
+#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
+
+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(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ if (fd < 0) {
+ wlr_log_errno(WLR_DEBUG, "Failed to create socket %c%s",
+ addr->sun_path[0] ? addr->sun_path[0] : '@',
+ addr->sun_path + 1);
+ 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_DEBUG, "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_DEBUG, "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 open_sockets(int socks[2], int display) {
+ struct sockaddr_un addr = { .sun_family = AF_LOCAL };
+ size_t path_size;
+
+ mkdir(socket_dir, 0777);
+
+#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 00000000..73eb36e0
--- /dev/null
+++ b/xwayland/sockets.h
@@ -0,0 +1,7 @@
+#ifndef XWAYLAND_SOCKETS_H
+#define XWAYLAND_SOCKETS_H
+
+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 00000000..e6f15735
--- /dev/null
+++ b/xwayland/xwayland.c
@@ -0,0 +1,488 @@
+#define _POSIX_C_SOURCE 200112L
+#ifdef __FreeBSD__
+// for SOCK_CLOEXEC
+#define __BSD_VISIBLE 1
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/util/log.h>
+#include <wlr/xwayland.h>
+#include "sockets.h"
+#include "util/signal.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 safe_close(int fd) {
+ if (fd >= 0) {
+ close(fd);
+ }
+}
+
+static int unset_cloexec(int fd) {
+ if (fcntl(fd, F_SETFD, 0) != 0) {
+ wlr_log_errno(WLR_ERROR, "fcntl() failed on fd %d", fd);
+ return -1;
+ }
+ return 0;
+}
+
+static int fill_arg(char ***argv, const char *fmt, ...) {
+ int len;
+ char **cur_arg = *argv;
+ va_list args;
+ va_start(args, fmt);
+ len = vsnprintf(NULL, 0, fmt, args) + 1;
+ va_end(args);
+ while (*cur_arg) {
+ cur_arg++;
+ }
+ *cur_arg = malloc(len);
+ if (!*cur_arg) {
+ return -1;
+ }
+ *argv = cur_arg;
+ va_start(args, fmt);
+ len = vsnprintf(*cur_arg, len, fmt, args);
+ va_end(args);
+ return len;
+}
+
+_Noreturn
+static void exec_xwayland(struct wlr_xwayland *wlr_xwayland) {
+ if (unset_cloexec(wlr_xwayland->x_fd[0]) ||
+ unset_cloexec(wlr_xwayland->x_fd[1]) ||
+ unset_cloexec(wlr_xwayland->wm_fd[1]) ||
+ unset_cloexec(wlr_xwayland->wl_fd[1])) {
+ _exit(EXIT_FAILURE);
+ }
+
+ /* Make Xwayland signal us when it's ready */
+ signal(SIGUSR1, SIG_IGN);
+
+ char *argv[] = {
+ "Xwayland", NULL /* display, e.g. :1 */,
+ "-rootless", "-terminate",
+ "-listen", NULL /* x_fd[0] */,
+ "-listen", NULL /* x_fd[1] */,
+ "-wm", NULL /* wm_fd[1] */,
+ NULL };
+ char **cur_arg = argv;
+
+ if (fill_arg(&cur_arg, ":%d", wlr_xwayland->display) < 0 ||
+ fill_arg(&cur_arg, "%d", wlr_xwayland->x_fd[0]) < 0 ||
+ fill_arg(&cur_arg, "%d", wlr_xwayland->x_fd[1]) < 0 ||
+ fill_arg(&cur_arg, "%d", wlr_xwayland->wm_fd[1]) < 0) {
+ wlr_log_errno(WLR_ERROR, "alloc/print failure");
+ _exit(EXIT_FAILURE);
+ }
+
+ char wayland_socket_str[16];
+ snprintf(wayland_socket_str, sizeof(wayland_socket_str), "%d", wlr_xwayland->wl_fd[1]);
+ setenv("WAYLAND_SOCKET", wayland_socket_str, true);
+
+ wlr_log(WLR_INFO, "WAYLAND_SOCKET=%d Xwayland :%d -rootless -terminate -listen %d -listen %d -wm %d",
+ wlr_xwayland->wl_fd[1], wlr_xwayland->display, wlr_xwayland->x_fd[0],
+ wlr_xwayland->x_fd[1], wlr_xwayland->wm_fd[1]);
+
+ // 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, 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);
+ }
+
+ // This returns if and only if the call fails
+ execvp("Xwayland", argv);
+
+ wlr_log_errno(WLR_ERROR, "failed to exec Xwayland");
+ close(devnull);
+ _exit(EXIT_FAILURE);
+}
+
+static void xwayland_finish_server(struct wlr_xwayland *wlr_xwayland) {
+ if (!wlr_xwayland || wlr_xwayland->display == -1) {
+ return;
+ }
+
+ if (wlr_xwayland->x_fd_read_event[0]) {
+ wl_event_source_remove(wlr_xwayland->x_fd_read_event[0]);
+ wl_event_source_remove(wlr_xwayland->x_fd_read_event[1]);
+
+ wlr_xwayland->x_fd_read_event[0] = wlr_xwayland->x_fd_read_event[1] = NULL;
+ }
+
+ if (wlr_xwayland->cursor != NULL) {
+ free(wlr_xwayland->cursor);
+ }
+
+ xwm_destroy(wlr_xwayland->xwm);
+
+ if (wlr_xwayland->client) {
+ wl_list_remove(&wlr_xwayland->client_destroy.link);
+ wl_client_destroy(wlr_xwayland->client);
+ }
+ if (wlr_xwayland->sigusr1_source) {
+ wl_event_source_remove(wlr_xwayland->sigusr1_source);
+ }
+
+ safe_close(wlr_xwayland->wl_fd[0]);
+ safe_close(wlr_xwayland->wl_fd[1]);
+ safe_close(wlr_xwayland->wm_fd[0]);
+ safe_close(wlr_xwayland->wm_fd[1]);
+ memset(wlr_xwayland, 0, offsetof(struct wlr_xwayland, display));
+ wlr_xwayland->wl_fd[0] = wlr_xwayland->wl_fd[1] = -1;
+ wlr_xwayland->wm_fd[0] = wlr_xwayland->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 xwayland_finish_display(struct wlr_xwayland *wlr_xwayland) {
+ if (!wlr_xwayland || wlr_xwayland->display == -1) {
+ return;
+ }
+
+ safe_close(wlr_xwayland->x_fd[0]);
+ safe_close(wlr_xwayland->x_fd[1]);
+ wlr_xwayland->x_fd[0] = wlr_xwayland->x_fd[1] = -1;
+
+ wl_list_remove(&wlr_xwayland->display_destroy.link);
+
+ unlink_display_sockets(wlr_xwayland->display);
+ wlr_xwayland->display = -1;
+ unsetenv("DISPLAY");
+}
+
+static bool xwayland_start_display(struct wlr_xwayland *wlr_xwayland,
+ struct wl_display *wl_display);
+
+static bool xwayland_start_server(struct wlr_xwayland *wlr_xwayland);
+static bool xwayland_start_server_lazy(struct wlr_xwayland *wlr_xwayland);
+
+static void handle_client_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_xwayland *wlr_xwayland =
+ wl_container_of(listener, wlr_xwayland, client_destroy);
+
+ if (wlr_xwayland->sigusr1_source) {
+ // Xwayland failed to start, let the sigusr1 handler deal with it
+ return;
+ }
+
+ // Don't call client destroy: it's being destroyed already
+ wlr_xwayland->client = NULL;
+ wl_list_remove(&wlr_xwayland->client_destroy.link);
+
+ xwayland_finish_server(wlr_xwayland);
+
+ if (time(NULL) - wlr_xwayland->server_start > 5) {
+ if (wlr_xwayland->lazy) {
+ wlr_log(WLR_INFO, "Restarting Xwayland (lazy)");
+ xwayland_start_server_lazy(wlr_xwayland);
+ } else {
+ wlr_log(WLR_INFO, "Restarting Xwayland");
+ xwayland_start_server(wlr_xwayland);
+ }
+ }
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_xwayland *wlr_xwayland =
+ wl_container_of(listener, wlr_xwayland, display_destroy);
+
+ // Don't call client destroy: the display is being destroyed, it's too late
+ if (wlr_xwayland->client) {
+ wlr_xwayland->client = NULL;
+ wl_list_remove(&wlr_xwayland->client_destroy.link);
+ }
+
+ wlr_xwayland_destroy(wlr_xwayland);
+}
+
+static int xserver_handle_ready(int signal_number, void *data) {
+ struct wlr_xwayland *wlr_xwayland = data;
+
+ int stat_val = -1;
+ while (waitpid(wlr_xwayland->pid, &stat_val, 0) < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ wlr_log_errno(WLR_ERROR, "waitpid for Xwayland fork failed");
+ return 1;
+ }
+ if (stat_val) {
+ wlr_log(WLR_ERROR, "Xwayland startup failed, not setting up xwm");
+ return 1;
+ }
+ wlr_log(WLR_DEBUG, "Xserver is ready");
+
+ wlr_xwayland->xwm = xwm_create(wlr_xwayland);
+ if (!wlr_xwayland->xwm) {
+ xwayland_finish_server(wlr_xwayland);
+ return 1;
+ }
+
+ if (wlr_xwayland->seat) {
+ xwm_set_seat(wlr_xwayland->xwm, wlr_xwayland->seat);
+ }
+
+ wl_event_source_remove(wlr_xwayland->sigusr1_source);
+ wlr_xwayland->sigusr1_source = NULL;
+
+ if (wlr_xwayland->cursor != NULL) {
+ struct wlr_xwayland_cursor *cur = wlr_xwayland->cursor;
+ xwm_set_cursor(wlr_xwayland->xwm, cur->pixels, cur->stride, cur->width,
+ cur->height, cur->hotspot_x, cur->hotspot_y);
+ free(cur);
+ wlr_xwayland->cursor = NULL;
+ }
+
+
+ wlr_signal_emit_safe(&wlr_xwayland->events.ready, wlr_xwayland);
+ /* ready is a one-shot signal, fire and forget */
+ wl_signal_init(&wlr_xwayland->events.ready);
+
+ return 1; /* wayland event loop dispatcher's count */
+}
+
+static int xwayland_socket_connected(int fd, uint32_t mask, void* data){
+ struct wlr_xwayland *wlr_xwayland = data;
+
+ wl_event_source_remove(wlr_xwayland->x_fd_read_event[0]);
+ wl_event_source_remove(wlr_xwayland->x_fd_read_event[1]);
+
+ wlr_xwayland->x_fd_read_event[0] = wlr_xwayland->x_fd_read_event[1] = NULL;
+
+ xwayland_start_server(wlr_xwayland);
+
+ return 0;
+}
+
+static bool xwayland_start_display(struct wlr_xwayland *wlr_xwayland,
+ struct wl_display *wl_display) {
+
+ wlr_xwayland->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(wl_display, &wlr_xwayland->display_destroy);
+
+ wlr_xwayland->display = open_display_sockets(wlr_xwayland->x_fd);
+ if (wlr_xwayland->display < 0) {
+ xwayland_finish_display(wlr_xwayland);
+ return false;
+ }
+
+ char display_name[16];
+ snprintf(display_name, sizeof(display_name), ":%d", wlr_xwayland->display);
+ setenv("DISPLAY", display_name, true);
+
+ return true;
+}
+
+static bool xwayland_start_server(struct wlr_xwayland *wlr_xwayland) {
+
+ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wlr_xwayland->wl_fd) != 0 ||
+ socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wlr_xwayland->wm_fd) != 0) {
+ wlr_log_errno(WLR_ERROR, "failed to create socketpair");
+ xwayland_finish_server(wlr_xwayland);
+ return false;
+ }
+ wlr_xwayland->server_start = time(NULL);
+
+ if (!(wlr_xwayland->client = wl_client_create(wlr_xwayland->wl_display, wlr_xwayland->wl_fd[0]))) {
+ wlr_log_errno(WLR_ERROR, "wl_client_create failed");
+ xwayland_finish_server(wlr_xwayland);
+ return false;
+ }
+
+ wlr_xwayland->wl_fd[0] = -1; /* not ours anymore */
+
+ wlr_xwayland->client_destroy.notify = handle_client_destroy;
+ wl_client_add_destroy_listener(wlr_xwayland->client,
+ &wlr_xwayland->client_destroy);
+
+ struct wl_event_loop *loop = wl_display_get_event_loop(wlr_xwayland->wl_display);
+ wlr_xwayland->sigusr1_source = wl_event_loop_add_signal(loop, SIGUSR1,
+ xserver_handle_ready, wlr_xwayland);
+
+ if ((wlr_xwayland->pid = fork()) == 0) {
+ /* Double-fork, but we need to forward SIGUSR1 once Xserver(1)
+ * is ready, or error if there was one. */
+ pid_t pid, ppid;
+ sigset_t sigset;
+ int sig;
+ ppid = getppid();
+ sigemptyset(&sigset);
+ sigaddset(&sigset, SIGUSR1);
+ sigaddset(&sigset, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &sigset, NULL);
+ if ((pid = fork()) == 0) {
+ exec_xwayland(wlr_xwayland);
+ }
+ if (pid < 0) {
+ wlr_log_errno(WLR_ERROR, "second fork failed");
+ _exit(EXIT_FAILURE);
+ }
+ sigwait(&sigset, &sig);
+ kill(ppid, SIGUSR1);
+ wlr_log(WLR_DEBUG, "sent SIGUSR1 to process %d", ppid);
+ if (sig == SIGCHLD) {
+ waitpid(pid, NULL, 0);
+ _exit(EXIT_FAILURE);
+ }
+ _exit(EXIT_SUCCESS);
+ }
+ if (wlr_xwayland->pid < 0) {
+ wlr_log_errno(WLR_ERROR, "fork failed");
+ xwayland_finish_server(wlr_xwayland);
+ return false;
+ }
+
+ /* close child fds */
+ /* remain managing x sockets for lazy start */
+ close(wlr_xwayland->wl_fd[1]);
+ close(wlr_xwayland->wm_fd[1]);
+ wlr_xwayland->wl_fd[1] = wlr_xwayland->wm_fd[1] = -1;
+
+ return true;
+}
+
+static bool xwayland_start_server_lazy(struct wlr_xwayland *wlr_xwayland) {
+ struct wl_event_loop *loop = wl_display_get_event_loop(wlr_xwayland->wl_display);
+ wlr_xwayland->x_fd_read_event[0] =
+ wl_event_loop_add_fd(loop, wlr_xwayland->x_fd[0], WL_EVENT_READABLE,
+ xwayland_socket_connected, wlr_xwayland);
+ wlr_xwayland->x_fd_read_event[1] =
+ wl_event_loop_add_fd(loop, wlr_xwayland->x_fd[1], WL_EVENT_READABLE,
+ xwayland_socket_connected, wlr_xwayland);
+
+ return true;
+}
+
+void wlr_xwayland_destroy(struct wlr_xwayland *wlr_xwayland) {
+ if (!wlr_xwayland) {
+ return;
+ }
+
+ wlr_xwayland_set_seat(wlr_xwayland, NULL);
+ xwayland_finish_server(wlr_xwayland);
+ xwayland_finish_display(wlr_xwayland);
+ free(wlr_xwayland);
+}
+
+struct wlr_xwayland *wlr_xwayland_create(struct wl_display *wl_display,
+ struct wlr_compositor *compositor, bool lazy) {
+ struct wlr_xwayland *wlr_xwayland = calloc(1, sizeof(struct wlr_xwayland));
+ if (!wlr_xwayland) {
+ return NULL;
+ }
+
+ wlr_xwayland->wl_display = wl_display;
+ wlr_xwayland->compositor = compositor;
+ wlr_xwayland->lazy = lazy;
+
+ wlr_xwayland->x_fd[0] = wlr_xwayland->x_fd[1] = -1;
+ wlr_xwayland->wl_fd[0] = wlr_xwayland->wl_fd[1] = -1;
+ wlr_xwayland->wm_fd[0] = wlr_xwayland->wm_fd[1] = -1;
+
+ wl_signal_init(&wlr_xwayland->events.new_surface);
+ wl_signal_init(&wlr_xwayland->events.ready);
+
+ if (!xwayland_start_display(wlr_xwayland, wl_display)) {
+ goto error_alloc;
+ }
+
+ if (wlr_xwayland->lazy) {
+ if (!xwayland_start_server_lazy(wlr_xwayland)) {
+ goto error_display;
+ }
+ } else {
+ if (!xwayland_start_server(wlr_xwayland)) {
+ goto error_display;
+ }
+ }
+
+ return wlr_xwayland;
+
+error_display:
+ xwayland_finish_display(wlr_xwayland);
+
+error_alloc:
+ free(wlr_xwayland);
+ return NULL;
+}
+
+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) {
+ if (wlr_xwayland->xwm != NULL) {
+ xwm_set_cursor(wlr_xwayland->xwm, pixels, stride, width, height,
+ hotspot_x, hotspot_y);
+ return;
+ }
+
+ free(wlr_xwayland->cursor);
+
+ wlr_xwayland->cursor = calloc(1, sizeof(struct wlr_xwayland_cursor));
+ if (wlr_xwayland->cursor == NULL) {
+ return;
+ }
+ wlr_xwayland->cursor->pixels = pixels;
+ wlr_xwayland->cursor->stride = stride;
+ wlr_xwayland->cursor->width = width;
+ wlr_xwayland->cursor->height = height;
+ wlr_xwayland->cursor->hotspot_x = hotspot_x;
+ wlr_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 00000000..91fa7e3d
--- /dev/null
+++ b/xwayland/xwm.c
@@ -0,0 +1,1802 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/util/edges.h>
+#include <wlr/util/log.h>
+#include <wlr/xcursor.h>
+#include <wlr/xwayland.h>
+#include <xcb/composite.h>
+#include <xcb/render.h>
+#include <xcb/xfixes.h>
+#include "util/signal.h"
+#include "xwayland/xwm.h"
+
+const char *atom_map[ATOM_LAST] = {
+ "WL_SURFACE_ID",
+ "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_WINDOW_TYPE",
+ "WM_TAKE_FOCUS",
+ "WINDOW",
+ "_NET_ACTIVE_WINDOW",
+ "_NET_WM_MOVERESIZE",
+ "_NET_WM_NAME",
+ "_NET_SUPPORTING_WM_CHECK",
+ "_NET_WM_STATE_MODAL",
+ "_NET_WM_STATE_FULLSCREEN",
+ "_NET_WM_STATE_MAXIMIZED_VERT",
+ "_NET_WM_STATE_MAXIMIZED_HORZ",
+ "_NET_WM_PING",
+ "WM_STATE",
+ "CLIPBOARD",
+ "PRIMARY",
+ "_WL_SELECTION",
+ "TARGETS",
+ "CLIPBOARD_MANAGER",
+ "INCR",
+ "TEXT",
+ "TIMESTAMP",
+ "DELETE",
+ "_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",
+ "XdndSelection",
+ "XdndAware",
+ "XdndStatus",
+ "XdndPosition",
+ "XdndEnter",
+ "XdndLeave",
+ "XdndDrop",
+ "XdndFinished",
+ "XdndProxy",
+ "XdndTypeList",
+ "XdndActionMove",
+ "XdndActionCopy",
+ "XdndActionAsk",
+ "XdndActionPrivate",
+};
+
+static const struct wlr_surface_role xwayland_surface_role;
+
+bool wlr_surface_is_xwayland_surface(struct wlr_surface *surface) {
+ return surface->role == &xwayland_surface_role;
+}
+
+struct wlr_xwayland_surface *wlr_xwayland_surface_from_wlr_surface(
+ struct wlr_surface *surface) {
+ assert(wlr_surface_is_xwayland_surface(surface));
+ return (struct wlr_xwayland_surface *)surface->role_data;
+}
+
+// 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;
+
+ wlr_signal_emit_safe(&surface->events.ping_timeout, surface);
+ surface->pinging = false;
+ return 1;
+}
+
+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(struct wlr_xwayland_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);
+
+ 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_insert(&xwm->surfaces, &surface->link);
+ wl_list_init(&surface->children);
+ wl_list_init(&surface->parent_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_maximize);
+ wl_signal_init(&surface->events.request_fullscreen);
+ wl_signal_init(&surface->events.request_activate);
+ wl_signal_init(&surface->events.map);
+ wl_signal_init(&surface->events.unmap);
+ 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_pid);
+ 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_override_redirect);
+ 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;
+ }
+
+ wlr_signal_emit_safe(&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_send_focus_window(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface) {
+ 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_set_input_focus(xwm->xcb_conn, XCB_INPUT_FOCUS_POINTER_ROOT,
+ xsurface->window_id, XCB_CURRENT_TIME);
+ }
+
+ uint32_t values[1];
+ values[0] = XCB_STACK_MODE_ABOVE;
+ xcb_configure_window(xwm->xcb_conn, xsurface->window_id,
+ XCB_CONFIG_WINDOW_STACK_MODE, values);
+}
+
+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_send_focus_window(xwm, xsurface);
+
+ xwm->focus_surface = xsurface;
+
+ xcb_flush(xwm->xcb_conn);
+}
+
+static void xsurface_set_net_wm_state(struct wlr_xwayland_surface *xsurface) {
+ struct wlr_xwm *xwm = xsurface->xwm;
+ uint32_t property[3];
+ int i;
+
+ 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];
+ }
+
+ 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 xsurface_unmap(struct wlr_xwayland_surface *surface);
+
+static void xwayland_surface_destroy(
+ struct wlr_xwayland_surface *xsurface) {
+ xsurface_unmap(xsurface);
+
+ wlr_signal_emit_safe(&xsurface->events.destroy, xsurface);
+
+ 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;
+ }
+
+ if (xsurface->surface_id) {
+ wl_list_remove(&xsurface->unpaired_link);
+ }
+
+ if (xsurface->surface) {
+ wl_list_remove(&xsurface->surface_destroy.link);
+ xsurface->surface->role_data = NULL;
+ }
+
+ 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->hints);
+ free(xsurface->size_hints);
+ 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;
+ }
+
+ wlr_log(WLR_DEBUG, "XCB_ATOM_WM_CLASS: %s %s", surface->instance,
+ surface->class);
+ wlr_signal_emit_safe(&surface->events.set_class, surface);
+}
+
+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;
+ }
+
+ wlr_log(WLR_DEBUG, "XCB_ATOM_WM_WINDOW_ROLE: %s", xsurface->role);
+ wlr_signal_emit_safe(&xsurface->events.set_role, xsurface);
+}
+
+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;
+
+ wlr_log(WLR_DEBUG, "XCB_ATOM_WM_NAME: %s", xsurface->title);
+ wlr_signal_emit_safe(&xsurface->events.set_title, xsurface);
+}
+
+static void read_surface_parent(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface,
+ xcb_get_property_reply_t *reply) {
+ if (reply->type != XCB_ATOM_WINDOW) {
+ return;
+ }
+
+ xcb_window_t *xid = xcb_get_property_value(reply);
+ if (xid != NULL) {
+ xsurface->parent = lookup_surface(xwm, *xid);
+ } 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);
+ }
+
+ wlr_log(WLR_DEBUG, "XCB_ATOM_WM_TRANSIENT_FOR: %p", xsurface->parent);
+ wlr_signal_emit_safe(&xsurface->events.set_parent, xsurface);
+}
+
+static void read_surface_pid(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface,
+ xcb_get_property_reply_t *reply) {
+ if (reply->type != XCB_ATOM_CARDINAL) {
+ return;
+ }
+
+ pid_t *pid = xcb_get_property_value(reply);
+ xsurface->pid = *pid;
+ wlr_log(WLR_DEBUG, "NET_WM_PID %d", xsurface->pid);
+ wlr_signal_emit_safe(&xsurface->events.set_pid, xsurface);
+}
+
+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;
+
+ wlr_log(WLR_DEBUG, "NET_WM_WINDOW_TYPE (%zu)", atoms_len);
+ wlr_signal_emit_safe(&xsurface->events.set_window_type, xsurface);
+}
+
+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;
+
+ wlr_log(WLR_DEBUG, "WM_PROTOCOLS (%zu)", atoms_len);
+}
+
+#if WLR_HAS_XCB_ICCCM
+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;
+ }
+
+ xcb_icccm_wm_hints_t hints;
+ xcb_icccm_get_wm_hints_from_reply(&hints, reply);
+
+ free(xsurface->hints);
+ xsurface->hints = calloc(1, sizeof(struct wlr_xwayland_surface_hints));
+ if (xsurface->hints == NULL) {
+ return;
+ }
+ memcpy(xsurface->hints, &hints, sizeof(struct wlr_xwayland_surface_hints));
+ xsurface->hints_urgency = xcb_icccm_wm_hints_get_urgency(&hints);
+
+ 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;
+ }
+
+ wlr_log(WLR_DEBUG, "WM_HINTS (%d)", reply->value_len);
+ wlr_signal_emit_safe(&xsurface->events.set_hints, xsurface);
+}
+#else
+static void read_surface_hints(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface,
+ xcb_get_property_reply_t *reply) {
+ // Do nothing
+}
+#endif
+
+#if WLR_HAS_XCB_ICCCM
+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;
+ }
+
+ xcb_size_hints_t size_hints;
+ xcb_icccm_get_wm_size_hints_from_reply(&size_hints, reply);
+
+ free(xsurface->size_hints);
+ xsurface->size_hints =
+ calloc(1, sizeof(struct wlr_xwayland_surface_size_hints));
+ if (xsurface->size_hints == NULL) {
+ return;
+ }
+ memcpy(xsurface->size_hints, &size_hints,
+ sizeof(struct wlr_xwayland_surface_size_hints));
+
+ if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) == 0) {
+ xsurface->size_hints->min_width = -1;
+ xsurface->size_hints->min_height = -1;
+ }
+ if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) == 0) {
+ xsurface->size_hints->max_width = -1;
+ xsurface->size_hints->max_height = -1;
+ }
+
+ wlr_log(WLR_DEBUG, "WM_NORMAL_HINTS (%d)", reply->value_len);
+}
+#else
+static void read_surface_normal_hints(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface,
+ xcb_get_property_reply_t *reply) {
+ // Do nothing
+}
+#endif
+
+
+#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;
+ }
+ }
+ wlr_signal_emit_safe(&xsurface->events.set_decorations, xsurface);
+ }
+
+ wlr_log(WLR_DEBUG, "MOTIF_WM_HINTS (%d)", reply->value_len);
+}
+
+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;
+ }
+ }
+}
+
+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_cookie_t cookie = xcb_get_property(xwm->xcb_conn, 0,
+ xsurface->window_id, property, XCB_ATOM_ANY, 0, 2048);
+ xcb_get_property_reply_t *reply = xcb_get_property_reply(xwm->xcb_conn,
+ cookie, NULL);
+ if (reply == NULL) {
+ return;
+ }
+
+ 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]) {
+ read_surface_pid(xwm, xsurface, reply);
+ } 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[WM_WINDOW_ROLE]) {
+ read_surface_role(xwm, xsurface, reply);
+ } else {
+ char *prop_name = xwm_get_atom_name(xwm, property);
+ wlr_log(WLR_DEBUG, "unhandled X11 property %u (%s) for window %u",
+ property, prop_name, xsurface->window_id);
+ free(prop_name);
+ }
+
+ free(reply);
+}
+
+static void xwayland_surface_role_commit(struct wlr_surface *wlr_surface) {
+ assert(wlr_surface->role == &xwayland_surface_role);
+ struct wlr_xwayland_surface *surface = wlr_surface->role_data;
+ if (surface == NULL) {
+ return;
+ }
+
+ if (!surface->mapped && wlr_surface_has_buffer(surface->surface)) {
+ wlr_signal_emit_safe(&surface->events.map, surface);
+ surface->mapped = true;
+ }
+}
+
+static void xwayland_surface_role_precommit(struct wlr_surface *wlr_surface) {
+ assert(wlr_surface->role == &xwayland_surface_role);
+ struct wlr_xwayland_surface *surface = wlr_surface->role_data;
+ if (surface == NULL) {
+ return;
+ }
+
+ if (wlr_surface->pending.committed & WLR_SURFACE_STATE_BUFFER &&
+ wlr_surface->pending.buffer_resource == NULL) {
+ // This is a NULL commit
+ if (surface->mapped) {
+ wlr_signal_emit_safe(&surface->events.unmap, surface);
+ surface->mapped = false;
+ }
+ }
+}
+
+static const struct wlr_surface_role xwayland_surface_role = {
+ .name = "wlr_xwayland_surface",
+ .commit = xwayland_surface_role_commit,
+ .precommit = xwayland_surface_role_precommit,
+};
+
+static void handle_surface_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_xwayland_surface *surface =
+ wl_container_of(listener, surface, surface_destroy);
+ xsurface_unmap(surface);
+}
+
+static void xwm_map_shell_surface(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface, struct wlr_surface *surface) {
+ if (!wlr_surface_set_role(surface, &xwayland_surface_role, xsurface,
+ NULL, 0)) {
+ wlr_log(WLR_ERROR, "Failed to set xwayland surface role");
+ return;
+ }
+
+ xsurface->surface = surface;
+
+ // 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_WM_STATE],
+ xwm->atoms[NET_WM_WINDOW_TYPE],
+ xwm->atoms[NET_WM_NAME],
+ xwm->atoms[NET_WM_PID],
+ };
+ for (size_t i = 0; i < sizeof(props)/sizeof(xcb_atom_t); i++) {
+ read_surface_property(xwm, xsurface, props[i]);
+ }
+
+ xsurface->surface_destroy.notify = handle_surface_destroy;
+ wl_signal_add(&surface->events.destroy, &xsurface->surface_destroy);
+}
+
+static void xsurface_unmap(struct wlr_xwayland_surface *surface) {
+ if (surface->mapped) {
+ surface->mapped = false;
+ wlr_signal_emit_safe(&surface->events.unmap, surface);
+ }
+
+ if (surface->surface_id) {
+ // 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(&surface->unpaired_link);
+ surface->surface_id = 0;
+ }
+
+ if (surface->surface) {
+ wl_list_remove(&surface->surface_destroy.link);
+ surface->surface->role_data = NULL;
+ surface->surface = NULL;
+ }
+}
+
+static void xwm_handle_create_notify(struct wlr_xwm *xwm,
+ xcb_create_notify_event_t *ev) {
+ wlr_log(WLR_DEBUG, "XCB_CREATE_NOTIFY (%u)", ev->window);
+
+ if (ev->window == xwm->window ||
+ ev->window == xwm->selection_window ||
+ ev->window == xwm->dnd_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) {
+ wlr_log(WLR_DEBUG, "XCB_DESTROY_NOTIFY (%u)", ev->window);
+ struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window);
+ if (xsurface == NULL) {
+ return;
+ }
+ xwayland_surface_destroy(xsurface);
+}
+
+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,
+ };
+ wlr_log(WLR_DEBUG, "XCB_CONFIGURE_REQUEST (%u) [%ux%u+%d,%d]", ev->window,
+ wlr_event.width, wlr_event.height, wlr_event.x, wlr_event.y);
+
+ wlr_signal_emit_safe(&surface->events.request_configure, &wlr_event);
+}
+
+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;
+ }
+
+ xsurface->x = ev->x;
+ xsurface->y = ev->y;
+ xsurface->width = ev->width;
+ xsurface->height = ev->height;
+
+ if (xsurface->override_redirect != ev->override_redirect) {
+ xsurface->override_redirect = ev->override_redirect;
+ wlr_signal_emit_safe(&xsurface->events.set_override_redirect, xsurface);
+ }
+}
+
+#define ICCCM_WITHDRAWN_STATE 0
+#define ICCCM_NORMAL_STATE 1
+#define ICCCM_ICONIC_STATE 3
+
+static void xsurface_set_wm_state(struct wlr_xwayland_surface *xsurface,
+ int32_t state) {
+ struct wlr_xwm *xwm = xsurface->xwm;
+ uint32_t property[2];
+
+ property[0] = state;
+ property[1] = XCB_WINDOW_NONE;
+
+ xcb_change_property(xwm->xcb_conn,
+ XCB_PROP_MODE_REPLACE,
+ xsurface->window_id,
+ xwm->atoms[WM_STATE],
+ xwm->atoms[WM_STATE],
+ 32, // format
+ 2, property);
+}
+
+static void xwm_handle_map_request(struct wlr_xwm *xwm,
+ xcb_map_request_event_t *ev) {
+ wlr_log(WLR_DEBUG, "XCB_MAP_REQUEST (%u)", ev->window);
+ struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window);
+ if (!xsurface) {
+ return;
+ }
+
+ xsurface_set_wm_state(xsurface, ICCCM_NORMAL_STATE);
+ xsurface_set_net_wm_state(xsurface);
+ xcb_map_window(xwm->xcb_conn, ev->window);
+}
+
+static void xwm_handle_map_notify(struct wlr_xwm *xwm,
+ xcb_map_notify_event_t *ev) {
+ wlr_log(WLR_DEBUG, "XCB_MAP_NOTIFY (%u)", ev->window);
+ struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window);
+ if (!xsurface) {
+ return;
+ }
+
+ if (xsurface->override_redirect != ev->override_redirect) {
+ xsurface->override_redirect = ev->override_redirect;
+ wlr_signal_emit_safe(&xsurface->events.set_override_redirect, xsurface);
+ }
+}
+
+static void xwm_handle_unmap_notify(struct wlr_xwm *xwm,
+ xcb_unmap_notify_event_t *ev) {
+ wlr_log(WLR_DEBUG, "XCB_UNMAP_NOTIFY (%u)", ev->window);
+ struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window);
+ if (xsurface == NULL) {
+ return;
+ }
+
+ xsurface_unmap(xsurface);
+ xsurface_set_wm_state(xsurface, ICCCM_WITHDRAWN_STATE);
+}
+
+static void xwm_handle_property_notify(struct wlr_xwm *xwm,
+ xcb_property_notify_event_t *ev) {
+ wlr_log(WLR_DEBUG, "XCB_PROPERTY_NOTIFY (%u)", ev->window);
+ struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window);
+ if (xsurface == NULL) {
+ return;
+ }
+
+ read_surface_property(xwm, xsurface, ev->atom);
+}
+
+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->client, id);
+ if (resource) {
+ struct wlr_surface *surface = wlr_surface_from_resource(resource);
+ xsurface->surface_id = 0;
+ xwm_map_shell_surface(xwm, xsurface, surface);
+ } else {
+ xsurface->surface_id = id;
+ 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) {
+ enum wlr_edges edges = WLR_EDGE_NONE;
+
+ switch(net_wm_edges) {
+ case _NET_WM_MOVERESIZE_SIZE_TOPLEFT:
+ edges = WLR_EDGE_TOP | WLR_EDGE_LEFT;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_TOP:
+ edges = WLR_EDGE_TOP;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT:
+ edges = WLR_EDGE_TOP | WLR_EDGE_RIGHT;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_RIGHT:
+ edges = WLR_EDGE_RIGHT;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT:
+ edges = WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_BOTTOM:
+ edges = WLR_EDGE_BOTTOM;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT:
+ edges = WLR_EDGE_BOTTOM | WLR_EDGE_LEFT;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_LEFT:
+ edges = WLR_EDGE_LEFT;
+ break;
+ default:
+ break;
+ }
+
+ return edges;
+}
+
+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;
+ }
+
+ // TODO: we should probably add input or seat info to this but we would just
+ // be guessing
+ struct wlr_xwayland_resize_event resize_event;
+ struct wlr_xwayland_move_event move_event;
+
+ int detail = ev->data.data32[2];
+ switch (detail) {
+ case _NET_WM_MOVERESIZE_MOVE:
+ move_event.surface = xsurface;
+ wlr_signal_emit_safe(&xsurface->events.request_move, &move_event);
+ 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:
+ resize_event.surface = xsurface;
+ resize_event.edges = net_wm_edges_to_wlr(detail);
+ wlr_signal_emit_safe(&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 inline 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);
+
+ uint32_t action = client_message->data.data32[0];
+ for (size_t i = 0; i < 2; ++i) {
+ uint32_t property = client_message->data.data32[1 + i];
+
+ if (property == xwm->atoms[_NET_WM_STATE_MODAL] &&
+ update_state(action, &xsurface->modal)) {
+ xsurface_set_net_wm_state(xsurface);
+ } else if (property == xwm->atoms[_NET_WM_STATE_FULLSCREEN] &&
+ update_state(action, &xsurface->fullscreen)) {
+ xsurface_set_net_wm_state(xsurface);
+ } else if (property == xwm->atoms[_NET_WM_STATE_MAXIMIZED_VERT] &&
+ update_state(action, &xsurface->maximized_vert)) {
+ xsurface_set_net_wm_state(xsurface);
+ } else if (property == xwm->atoms[_NET_WM_STATE_MAXIMIZED_HORZ] &&
+ update_state(action, &xsurface->maximized_horz)) {
+ 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;
+ }
+
+ wlr_signal_emit_safe(&xsurface->events.request_fullscreen, xsurface);
+ }
+
+ if (maximized != xsurface_is_maximized(xsurface)) {
+ if (xsurface_is_maximized(xsurface)) {
+ xsurface->saved_width = xsurface->width;
+ xsurface->saved_height = xsurface->height;
+ }
+
+ wlr_signal_emit_safe(&xsurface->events.request_maximize, xsurface);
+ }
+}
+
+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 {
+ char *type_name = xwm_get_atom_name(xwm, type);
+ wlr_log(WLR_DEBUG, "unhandled WM_PROTOCOLS client message %u (%s)",
+ type, type_name);
+ 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;
+ }
+ wlr_signal_emit_safe(&surface->events.request_activate, surface);
+}
+
+static void xwm_handle_client_message(struct wlr_xwm *xwm,
+ xcb_client_message_event_t *ev) {
+ wlr_log(WLR_DEBUG, "XCB_CLIENT_MESSAGE (%u)", ev->window);
+
+ if (ev->type == xwm->atoms[WL_SURFACE_ID]) {
+ xwm_handle_surface_id_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 (!xwm_handle_selection_client_message(xwm, ev)) {
+ char *type_name = xwm_get_atom_name(xwm, ev->type);
+ wlr_log(WLR_DEBUG, "unhandled x11 client message %u (%s)", ev->type,
+ type_name);
+ free(type_name);
+ }
+}
+
+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;
+ }
+
+ // Do not let X clients change the focus behind the compositor's
+ // back. Reset the focus to the old one if it changed.
+ if (!xwm->focus_surface || ev->event != xwm->focus_surface->window_id) {
+ xwm_send_focus_window(xwm, xwm->focus_surface);
+ }
+}
+
+static void xwm_handle_xcb_error(struct wlr_xwm *xwm, xcb_value_error_t *ev) {
+#if WLR_HAS_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 WLR_HAS_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;
+
+ while ((event = xcb_poll_for_event(xwm->xcb_conn))) {
+ count++;
+
+ if (xwm->xwayland->user_event_handler &&
+ xwm->xwayland->user_event_handler(xwm, event)) {
+ break;
+ }
+
+ 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;
+ if (wl_resource_get_client(surface->resource) != xwm->xwayland->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) {
+ xwm_map_shell_surface(xwm, xsurface, surface);
+ xsurface->surface_id = 0;
+ wl_list_remove(&xsurface->unpaired_link);
+ 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);
+}
+
+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) {
+ 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);
+ 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);
+ 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 WLR_HAS_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, link) {
+ xwayland_surface_destroy(xsurface);
+ }
+ wl_list_remove(&xwm->compositor_new_surface.link);
+ wl_list_remove(&xwm->compositor_destroy.link);
+ xcb_disconnect(xwm->xcb_conn);
+
+ 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);
+
+ 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: %d.%d",
+ xfixes_reply->major_version, xfixes_reply->minor_version);
+
+ free(xfixes_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);
+
+ 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 *wlr_xwayland) {
+ struct wlr_xwm *xwm = calloc(1, sizeof(struct wlr_xwm));
+ if (xwm == NULL) {
+ return NULL;
+ }
+
+ xwm->xwayland = wlr_xwayland;
+ wl_list_init(&xwm->surfaces);
+ wl_list_init(&xwm->unpaired_surfaces);
+ xwm->ping_timeout = 10000;
+
+ xwm->xcb_conn = xcb_connect_to_fd(wlr_xwayland->wm_fd[0], NULL);
+
+ int rc = xcb_connection_has_error(xwm->xcb_conn);
+ if (rc) {
+ wlr_log(WLR_ERROR, "xcb connect failed: %d", rc);
+ close(wlr_xwayland->wm_fd[0]);
+ free(xwm);
+ return NULL;
+ }
+
+#if WLR_HAS_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(
+ wlr_xwayland->wl_display);
+ xwm->event_source =
+ wl_event_loop_add_fd(event_loop,
+ wlr_xwayland->wm_fd[0],
+ 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_MODAL],
+ xwm->atoms[_NET_WM_STATE_FULLSCREEN],
+ xwm->atoms[_NET_WM_STATE_MAXIMIZED_VERT],
+ xwm->atoms[_NET_WM_STATE_MAXIMIZED_HORZ],
+ };
+ 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);
+
+ xcb_flush(xwm->xcb_conn);
+
+ xwm_set_net_active_window(xwm, XCB_WINDOW_NONE);
+
+ xwm_selection_init(xwm);
+
+ xwm->compositor_new_surface.notify = handle_compositor_new_surface;
+ wl_signal_add(&wlr_xwayland->compositor->events.new_surface,
+ &xwm->compositor_new_surface);
+ xwm->compositor_destroy.notify = handle_compositor_destroy;
+ wl_signal_add(&wlr_xwayland->compositor->events.destroy,
+ &xwm->compositor_destroy);
+
+ xwm_create_wm_window(xwm);
+
+ xcb_flush(xwm->xcb_conn);
+
+ return xwm;
+}
+
+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 *surface) {
+ bool ret = true;
+ static 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_TOOLTIP,
+ NET_WM_WINDOW_TYPE_UTILITY,
+ };
+ for (size_t i = 0; i < sizeof(needles) / sizeof(needles[0]); ++i) {
+ if (xwm_atoms_contains(surface->xwm, surface->window_type,
+ surface->window_type_len, needles[i])) {
+ ret = false;
+ }
+ }
+
+ return ret;
+}