aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/dmabuf-capture.c2
-rw-r--r--examples/input-method.c402
-rw-r--r--examples/layer-shell.c6
-rw-r--r--examples/meson.build20
-rw-r--r--examples/pointer-constraints.c260
-rw-r--r--examples/text-input.c394
6 files changed, 1083 insertions, 1 deletions
diff --git a/examples/dmabuf-capture.c b/examples/dmabuf-capture.c
index 2c510b5a..abdb146a 100644
--- a/examples/dmabuf-capture.c
+++ b/examples/dmabuf-capture.c
@@ -12,7 +12,7 @@
#include <unistd.h>
#include <pthread.h>
#include <stdbool.h>
-#include <libdrm/drm_fourcc.h>
+#include <drm_fourcc.h>
#include "wlr-export-dmabuf-unstable-v1-client-protocol.h"
struct wayland_output {
diff --git a/examples/input-method.c b/examples/input-method.c
new file mode 100644
index 00000000..3e485d1c
--- /dev/null
+++ b/examples/input-method.c
@@ -0,0 +1,402 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <errno.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 <wlr/render/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
index f56825c3..114b98f6 100644
--- a/examples/layer-shell.c
+++ b/examples/layer-shell.c
@@ -596,10 +596,16 @@ int main(int argc, char **argv) {
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];
diff --git a/examples/meson.build b/examples/meson.build
index b5ad6c98..369c7049 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -6,6 +6,13 @@ 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()
@@ -70,12 +77,17 @@ examples = {
'src': 'gamma-control.c',
'dep': [wayland_client, wayland_cursor, wlr_protos, wlroots],
},
+ '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,
@@ -90,6 +102,14 @@ examples = {
'src': 'toplevel-decoration.c',
'dep': [wayland_client, wlr_protos, wlroots],
},
+ 'input-method': {
+ 'src': 'input-method.c',
+ 'dep': [wayland_client, wlr_protos, wlroots] + libepoll,
+ },
+ 'text-input': {
+ 'src': 'text-input.c',
+ 'dep': [wayland_cursor, wayland_client, wlr_protos, wlroots],
+ },
}
foreach name, info : examples
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/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;
+}