summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHoe Hao Cheng <haochengho12907@gmail.com>2023-03-25 21:36:09 +0800
committerErik Faye-Lund <erik.faye-lund@collabora.com>2023-04-04 19:56:58 +0000
commit609db1ba91cf5f338d3ba87cf0a002e4c787ca8b (patch)
tree44c6b68366fed5ef0b82b227988f7480ec1bccf1
parent4bbacec1719fab574d116d1b3218851304ced592 (diff)
downloadmesa-demos-609db1ba91cf5f338d3ba87cf0a002e4c787ca8b.tar.gz
eglut/wsi: introduce Wayland WSI
This is copy-pasted from eglut_wayland.c, the only change that has been made is adding a wayland_wsi_interface() function, renaming the _eglut*() functions, and making them static. Reviewed-by: Erik Faye-Lund <erik.faye-lund@collabora.com>
-rw-r--r--src/egl/eglut/wsi/wayland.c579
1 files changed, 579 insertions, 0 deletions
diff --git a/src/egl/eglut/wsi/wayland.c b/src/egl/eglut/wsi/wayland.c
new file mode 100644
index 00000000..c00fa13e
--- /dev/null
+++ b/src/egl/eglut/wsi/wayland.c
@@ -0,0 +1,579 @@
+#include <wayland-client.h>
+#include <wayland-egl.h>
+
+#include <assert.h>
+#include <poll.h>
+#include <errno.h>
+#include <string.h>
+#include <stdbool.h>
+#include <sys/mman.h>
+#include <sys/timerfd.h>
+#include <unistd.h>
+#include <xkbcommon/xkbcommon.h>
+
+#include "eglutint.h"
+#include "wsi.h"
+#include <libdecor.h>
+
+struct display {
+ struct wl_display *display;
+ struct wl_seat *wl_seat;
+ struct wl_compositor *compositor;
+ uint32_t mask;
+
+ struct {
+ struct wl_keyboard *keyboard;
+ struct xkb_state *xkb_state;
+ struct xkb_context *xkb_context;
+ struct xkb_keymap *xkb_keymap;
+
+ int key_repeat_fd;
+ xkb_keycode_t repeat_keycode;
+
+ int32_t rate, delay;
+ } seat;
+};
+
+struct window {
+ struct wl_surface *surface;
+ struct libdecor *decor_context;
+ struct libdecor_frame *frame;
+ bool open;
+ bool opaque;
+ bool configured;
+ int floating_width;
+ int floating_height;
+};
+
+static struct display display = {0};
+static struct window window = {0};
+
+static void
+keymap_callback(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t format, int32_t fd, uint32_t size)
+{
+ struct display *d = data;
+ assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1);
+
+ char *map_shm = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+ assert(map_shm != MAP_FAILED);
+
+ struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_string(
+ d->seat.xkb_context, map_shm, XKB_KEYMAP_FORMAT_TEXT_V1,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+ munmap(map_shm, size);
+ close(fd);
+
+ struct xkb_state *xkb_state = xkb_state_new(xkb_keymap);
+ xkb_keymap_unref(d->seat.xkb_keymap);
+ xkb_state_unref(d->seat.xkb_state);
+ d->seat.xkb_keymap = xkb_keymap;
+ d->seat.xkb_state = xkb_state;
+}
+
+
+static int
+lookup_keysym(xkb_keysym_t sym)
+{
+ switch (sym) {
+
+ /* function keys */
+ case XKB_KEY_F1: return EGLUT_KEY_F1;
+ case XKB_KEY_F2: return EGLUT_KEY_F2;
+ case XKB_KEY_F3: return EGLUT_KEY_F3;
+ case XKB_KEY_F4: return EGLUT_KEY_F4;
+ case XKB_KEY_F5: return EGLUT_KEY_F5;
+ case XKB_KEY_F6: return EGLUT_KEY_F6;
+ case XKB_KEY_F7: return EGLUT_KEY_F7;
+ case XKB_KEY_F8: return EGLUT_KEY_F8;
+ case XKB_KEY_F9: return EGLUT_KEY_F9;
+ case XKB_KEY_F10: return EGLUT_KEY_F10;
+ case XKB_KEY_F11: return EGLUT_KEY_F11;
+ case XKB_KEY_F12: return EGLUT_KEY_F12;
+
+ /* directional keys */
+
+ case XKB_KEY_Left:
+ case XKB_KEY_KP_Left:
+ return EGLUT_KEY_LEFT;
+
+ case XKB_KEY_Up:
+ case XKB_KEY_KP_Up:
+ return EGLUT_KEY_UP;
+
+ case XKB_KEY_Right:
+ case XKB_KEY_KP_Right:
+ return EGLUT_KEY_RIGHT;
+
+ case XKB_KEY_Down:
+ case XKB_KEY_KP_Down:
+ return EGLUT_KEY_DOWN;
+
+ default:
+ return -1;
+ }
+}
+
+static void
+emit_keypress(struct display *d, xkb_keycode_t keycode)
+{
+ struct eglut_window *win = _eglut->current;
+
+ xkb_keysym_t sym = xkb_state_key_get_one_sym(d->seat.xkb_state, keycode);
+ int special = lookup_keysym(sym);
+ if (special >= 0 && win->special_cb)
+ win->special_cb(special);
+ else {
+ uint32_t utf32 = xkb_keysym_to_utf32(sym);
+ if (utf32 && utf32 < 128)
+ win->keyboard_cb(utf32);
+ }
+}
+
+static void
+handle_key(struct display *d, xkb_keycode_t keycode,
+ enum wl_keyboard_key_state state)
+{
+ struct itimerspec timer = {0};
+ if (d->seat.rate != 0 &&
+ xkb_keymap_key_repeats(d->seat.xkb_keymap, keycode) &&
+ state == WL_KEYBOARD_KEY_STATE_PRESSED) {
+ d->seat.repeat_keycode = keycode;
+ if (d->seat.rate > 1)
+ timer.it_interval.tv_nsec = 1000000000 / d->seat.rate;
+ else
+ timer.it_interval.tv_sec = 1;
+
+ timer.it_value.tv_sec = d->seat.delay / 1000;
+ timer.it_value.tv_nsec = ( d->seat.delay % 1000) * 1000000;
+ }
+ timerfd_settime(d->seat.key_repeat_fd, 0, &timer, NULL);
+
+ if (state == WL_KEYBOARD_KEY_STATE_PRESSED)
+ emit_keypress(d, keycode);
+}
+
+static void
+enter_callback(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, struct wl_surface *surface,
+ struct wl_array *keys)
+{
+ struct display *d = data;
+ uint32_t *key;
+ wl_array_for_each(key, keys)
+ handle_key(d, *key + 8, WL_KEYBOARD_KEY_STATE_PRESSED);
+}
+
+static void
+leave_callback(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, struct wl_surface *surface)
+{
+ struct display *d = data;
+ struct itimerspec timer = {0};
+ timerfd_settime(d->seat.key_repeat_fd, 0, &timer, NULL);
+}
+
+static void
+key_callback(void *data, struct wl_keyboard *keyboard,
+ uint32_t serial, uint32_t time, uint32_t key,
+ uint32_t state)
+{
+ struct display *d = data;
+ handle_key(d, key + 8, state);
+}
+
+static void
+modifiers_callback(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 display *d = data;
+ xkb_state_update_mask(d->seat.xkb_state, mods_depressed, mods_latched,
+ mods_locked, 0, 0, group);
+}
+
+static void
+repeat_info_callback(void *data, struct wl_keyboard *wl_keyboard,
+ int32_t rate, int32_t delay)
+{
+ struct display *d = data;
+ d->seat.rate = rate;
+ d->seat.delay = delay;
+}
+
+static const struct wl_keyboard_listener keyboard_listener = {
+ keymap_callback,
+ enter_callback,
+ leave_callback,
+ key_callback,
+ modifiers_callback,
+ repeat_info_callback,
+};
+
+static void
+seat_capabilities(void *data, struct wl_seat *seat,
+ enum wl_seat_capability caps)
+{
+ struct display *d = data;
+ if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
+ d->seat.keyboard = wl_seat_get_keyboard(seat);
+ wl_keyboard_add_listener(d->seat.keyboard, &keyboard_listener, data);
+ d->seat.key_repeat_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
+ } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD)) {
+ wl_keyboard_destroy(d->seat.keyboard);
+ d->seat.keyboard = NULL;
+ }
+}
+
+static void
+seat_name(void* data, struct wl_seat* wl_seat,
+ const char* name)
+{
+}
+
+static const struct wl_seat_listener seat_listener = {
+ seat_capabilities,
+ seat_name,
+};
+
+static void
+registry_handle_global(void *data, struct wl_registry *registry, uint32_t id,
+ const char *interface, uint32_t version)
+{
+ struct display *d = data;
+
+ if (strcmp(interface, wl_compositor_interface.name) == 0) {
+ d->compositor =
+ wl_registry_bind(registry, id, &wl_compositor_interface, 1);
+ } else if (strcmp(interface, wl_seat_interface.name) == 0) {
+ d->wl_seat =
+ wl_registry_bind(registry, id, &wl_seat_interface, 4);
+ wl_seat_add_listener(d->wl_seat, &seat_listener, d);
+ }
+}
+
+static void
+registry_handle_global_remove(void *data, struct wl_registry *registry,
+ uint32_t name)
+{
+}
+
+static const struct wl_registry_listener registry_listener = {
+ registry_handle_global,
+ registry_handle_global_remove
+};
+
+static void
+sync_callback(void *data, struct wl_callback *callback, uint32_t serial)
+{
+ int *done = data;
+
+ *done = 1;
+ wl_callback_destroy(callback);
+}
+
+static const struct wl_callback_listener sync_listener = {
+ sync_callback
+};
+
+static int
+wayland_roundtrip(struct wl_display *display)
+{
+ struct wl_callback *callback;
+ int done = 0, ret = 0;
+
+ callback = wl_display_sync(display);
+ wl_callback_add_listener(callback, &sync_listener, &done);
+ while (ret != -1 && !done)
+ ret = wl_display_dispatch(display);
+
+ if (!done)
+ wl_callback_destroy(callback);
+
+ return ret;
+}
+
+static void
+init_display(void)
+{
+ struct wl_registry *registry;
+
+ _eglut->native_dpy = display.display = wl_display_connect(NULL);
+
+ if (!_eglut->native_dpy)
+ _eglutFatal("failed to initialize native display");
+
+ display.seat.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+
+ registry = wl_display_get_registry(_eglut->native_dpy);
+ wl_registry_add_listener(registry, &registry_listener, &display);
+ wayland_roundtrip(_eglut->native_dpy);
+ wl_registry_destroy(registry);
+
+ _eglut->surface_type = EGL_WINDOW_BIT;
+ _eglut->redisplay = 1;
+}
+
+static void
+fini_display(void)
+{
+ wl_seat_destroy(display.wl_seat);
+ xkb_context_unref(display.seat.xkb_context);
+ wl_compositor_destroy(display.compositor);
+ wl_display_flush(_eglut->native_dpy);
+ wl_display_disconnect(_eglut->native_dpy);
+ _eglut->native_dpy = NULL;
+}
+
+static void
+libdecor_error(struct libdecor *context,
+ enum libdecor_error error,
+ const char *message)
+{
+ printf("EGLUT: libdecor error %d due to %s\n", error, message);
+}
+
+static struct libdecor_interface libdecor_interface = {
+ .error = libdecor_error,
+};
+
+static void
+frame_configure(struct libdecor_frame *frame,
+ struct libdecor_configuration *configuration,
+ void *user_data)
+{
+ struct eglut_window *win = user_data;
+ struct libdecor_state *state;
+ int width, height;
+
+ if (!libdecor_configuration_get_content_size(configuration, frame,
+ &width, &height)) {
+ width = window.floating_width;
+ height = window.floating_height;
+ }
+
+ win->native.width = width;
+ win->native.height = height;
+
+ wl_egl_window_resize(win->native.u.window,
+ win->native.width,
+ win->native.height,
+ 0,
+ 0);
+
+ if (win->reshape_cb)
+ win->reshape_cb(win->native.width, win->native.height);
+
+ state = libdecor_state_new(width, height);
+ libdecor_frame_commit(frame, state, configuration);
+ libdecor_state_free(state);
+
+ /* store floating dimensions */
+ if (libdecor_frame_is_floating(frame)) {
+ window.floating_width = width;
+ window.floating_height = height;
+ }
+
+ window.configured = true;
+}
+
+static void
+frame_close(struct libdecor_frame *frame, void *user_data)
+{
+ struct eglut_window *win = user_data;
+ eglutDestroyWindow(win->index);
+
+ // FIXME: eglut does not terminate when all windows are closed.
+ // eglut_x11 dies due to "X connection to $DISPLAY broken".
+ // Since wl_display works fine with all windows closed, terminate ourselves.
+ eglTerminate(_eglut->dpy);
+ fini_display();
+
+ window.open = false;
+}
+
+static void
+frame_commit(struct libdecor_frame *frame, void *user_data)
+{
+ struct eglut_window *window = user_data;
+
+ eglSwapBuffers(_eglut->dpy, window->surface);
+}
+
+
+static struct libdecor_frame_interface frame_interface = {
+ .configure = frame_configure,
+ .close = frame_close,
+ .commit = frame_commit,
+};
+
+static void
+init_window(struct eglut_window *win, const char *title,
+ int x, int y, int w, int h)
+{
+ struct wl_egl_window *native;
+
+ window.surface = wl_compositor_create_surface(display.compositor);
+
+ EGLint alpha_size;
+ if (!eglGetConfigAttrib(_eglut->dpy,
+ win->config, EGL_ALPHA_SIZE, &alpha_size))
+ _eglutFatal("failed to get alpha size");
+ window.opaque = !alpha_size;
+
+ native = wl_egl_window_create(window.surface, w, h);
+
+ win->native.u.window = native;
+ win->native.width = w;
+ win->native.height = h;
+
+ window.decor_context = libdecor_new(display.display,
+ &libdecor_interface);
+ window.frame = libdecor_decorate(window.decor_context,
+ window.surface,
+ &frame_interface,
+ win);
+ window.floating_width = w;
+ window.floating_height = h;
+ libdecor_frame_set_app_id(window.frame, title);
+ libdecor_frame_set_title(window.frame, title);
+ libdecor_frame_map(window.frame);
+}
+
+static void
+fini_window(struct eglut_window *win)
+{
+ wl_egl_window_destroy(win->native.u.window);
+
+ if (window.decor_context)
+ libdecor_unref(window.decor_context);
+}
+
+static void
+draw(struct window *window)
+{
+ struct eglut_window *win = _eglut->current;
+
+ /* Our client doesn't want to push another frame; go back to sleep. */
+ if (!_eglut->redisplay)
+ return;
+ _eglut->redisplay = 0;
+
+ if (win->display_cb)
+ win->display_cb();
+
+ if (window->opaque) {
+ struct wl_region *region =
+ wl_compositor_create_region(display.compositor);
+ wl_region_add(region, 0, 0, win->native.width, win->native.height);
+ wl_surface_set_opaque_region(window->surface, region);
+ wl_region_destroy(region);
+ }
+
+ eglSwapBuffers(_eglut->dpy, win->surface);
+}
+
+static void
+event_loop(void)
+{
+ int ret;
+
+ while (!window.configured) {
+ if (libdecor_dispatch(window.decor_context, 0) < 0)
+ return;
+ }
+
+ struct pollfd pollfds[] = {
+ {
+ .fd = wl_display_get_fd(display.display),
+ .events = POLLIN,
+ },
+ {
+ .fd = libdecor_get_fd(window.decor_context),
+ .events = POLLIN,
+ }, {
+ .fd = display.seat.key_repeat_fd,
+ .events = POLLIN,
+ },
+ };
+
+ while (_eglut->native_dpy != NULL) {
+ /* If we need to flush but can't, don't do anything at all which could
+ * push further events into the socket. */
+ if (!(pollfds[0].events & POLLOUT)) {
+ wl_display_dispatch_pending(display.display);
+
+ if (_eglut->idle_cb)
+ _eglut->idle_cb();
+
+ /* Client wants to redraw, but we have no frame event to trigger the
+ * redraw; kickstart it by redrawing immediately. */
+ if (_eglut->redisplay)
+ draw(&window);
+ }
+
+ ret = wl_display_flush(display.display);
+ if (ret < 0 && errno != EAGAIN)
+ break; /* fatal error; socket is broken */
+ else if (ret < 0 && errno == EAGAIN)
+ pollfds[0].events |= POLLOUT; /* need to wait until we can flush */
+ else
+ pollfds[0].events &= ~POLLOUT; /* successfully flushed */
+
+ unsigned poll_count = 2 + (display.seat.rate > 0);
+ if (poll(pollfds, poll_count, -1) == -1)
+ break;
+
+ if ((pollfds[0].revents | pollfds[1].revents) &
+ (POLLERR | POLLHUP | POLLNVAL))
+ break;
+
+ if (pollfds[0].events & POLLOUT) {
+ if (!(pollfds[0].revents & POLLOUT))
+ continue; /* block until we can flush */
+ pollfds[0].events &= ~POLLOUT;
+ }
+
+ if (pollfds[0].revents & POLLIN) {
+ ret = wl_display_dispatch(display.display);
+ if (ret == -1)
+ break;
+ }
+
+ ret = wl_display_flush(display.display);
+ if (ret < 0 && errno != EAGAIN)
+ break; /* fatal error; socket is broken */
+ else if (ret < 0 && errno == EAGAIN)
+ pollfds[0].events |= POLLOUT; /* need to wait until we can flush */
+ else
+ pollfds[0].events &= ~POLLOUT; /* successfully flushed */
+
+ if (pollfds[1].revents & POLLIN) {
+ if (window.open && libdecor_dispatch(window.decor_context, 0) < 0) {
+ ret = 1;
+ break;
+ }
+ }
+
+ if (pollfds[2].revents & POLLIN) {
+ uint64_t repeats;
+ if (read(display.seat.key_repeat_fd, &repeats, sizeof(repeats)) == 8) {
+ for (uint64_t i = 0; i < repeats; i++)
+ emit_keypress(&display, display.seat.repeat_keycode);
+ }
+ }
+ }
+}
+
+struct eglut_wsi_interface
+wayland_wsi_interface(void)
+{
+ return (struct eglut_wsi_interface) {
+ .init_display = init_display,
+ .fini_display = fini_display,
+ .init_window = init_window,
+ .fini_window = fini_window,
+ .event_loop = event_loop,
+ };
+}