summaryrefslogtreecommitdiffstats
path: root/source/wayland.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/wayland.c')
-rw-r--r--source/wayland.c1022
1 files changed, 1022 insertions, 0 deletions
diff --git a/source/wayland.c b/source/wayland.c
new file mode 100644
index 00000000..ca68c9f6
--- /dev/null
+++ b/source/wayland.c
@@ -0,0 +1,1022 @@
+/**
+ * MIT/X11 License
+ * Modified (c) 2017 Quentin “Sardem FF7” Glidic
+ *
+ * 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.
+ */
+
+#define G_LOG_DOMAIN "Wayland"
+#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <locale.h>
+#include <linux/input-event-codes.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <cairo.h>
+#include <wayland-client.h>
+#include <wayland-cursor.h>
+#include <libgwater-wayland.h>
+#include <xkbcommon/xkbcommon.h>
+#include <xkbcommon/xkbcommon-compose.h>
+
+#include "xkb.h"
+#include "view.h"
+
+#include "unstable/launcher-menu/launcher-menu-unstable-v1-client-protocol.h"
+#include "wayland.h"
+#include "display.h"
+
+typedef struct _display_buffer_pool wayland_buffer_pool;
+typedef struct {
+ wayland_stuff *context;
+ uint32_t global_name;
+ struct wl_output *output;
+ int32_t scale;
+} wayland_output;
+
+typedef struct {
+ wayland_buffer_pool *pool;
+ struct wl_buffer *buffer;
+ uint8_t *data;
+ gboolean released;
+} wayland_buffer;
+
+struct _display_buffer_pool {
+ wayland_stuff *context;
+ uint8_t *data;
+ size_t size;
+ int32_t width;
+ int32_t height;
+ gboolean to_free;
+ wayland_buffer *buffers;
+};
+
+static wayland_stuff wayland_;
+wayland_stuff *wayland = &wayland_;
+static const cairo_user_data_key_t wayland_cairo_surface_user_data;
+
+static void
+wayland_buffer_cleanup(wayland_buffer_pool *self)
+{
+ if ( ! self->to_free )
+ return;
+
+ size_t i, count = 0;
+ for ( i = 0 ; i < wayland->buffer_count ; ++i )
+ {
+ if ( ( self->buffers[i].released ) && ( self->buffers[i].buffer != NULL ) )
+ {
+ wl_buffer_destroy(self->buffers[i].buffer);
+ self->buffers[i].buffer = NULL;
+ }
+ if ( self->buffers[i].buffer == NULL )
+ ++count;
+ }
+
+ if ( count < wayland->buffer_count )
+ return;
+
+ munmap(self->data, self->size);
+ g_free(self);
+}
+
+static void
+wayland_buffer_release(void *data, struct wl_buffer *buffer)
+{
+ wayland_buffer_pool *self = data;
+
+ size_t i;
+ for ( i = 0 ; i < wayland->buffer_count ; ++i )
+ {
+ if ( self->buffers[i].buffer == buffer )
+ self->buffers[i].released = TRUE;
+ }
+
+ wayland_buffer_cleanup(self);
+}
+
+static const struct wl_buffer_listener wayland_buffer_listener = {
+ wayland_buffer_release
+};
+
+wayland_buffer_pool *
+display_buffer_pool_new(gint width, gint height)
+{
+ struct wl_shm_pool *wl_pool;
+ struct wl_buffer *buffer;
+ int fd;
+ uint8_t *data;
+ width *= wayland->scale;
+ height *= wayland->scale;
+ int32_t stride;
+ size_t size;
+ size_t pool_size;
+
+ stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
+ size = stride * height;
+ pool_size = size * wayland->buffer_count;
+
+ gchar filename[PATH_MAX];
+ g_snprintf(filename, PATH_MAX, "%s/rofi-wayland-surface", g_get_user_runtime_dir());
+ fd = g_open(filename, O_CREAT | O_RDWR | O_CLOEXEC, 0);
+ g_unlink(filename);
+ if ( fd < 0 )
+ {
+ g_warning("creating a buffer file for %zu B failed: %s", pool_size, g_strerror(errno));
+ return NULL;
+ }
+ if ( ftruncate(fd, pool_size) < 0 )
+ {
+ g_close(fd, NULL);
+ return NULL;
+ }
+
+ data = mmap(NULL, pool_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if ( data == MAP_FAILED )
+ {
+ g_warning("mmap of size %zu failed: %s", pool_size, g_strerror(errno));
+ close(fd);
+ return NULL;
+ }
+
+ wayland_buffer_pool *pool;
+ pool = g_new0(wayland_buffer_pool, 1);
+
+ pool->width = width;
+ pool->height = height;
+
+ pool->buffers = g_new0(wayland_buffer, wayland->buffer_count);
+
+ wl_pool = wl_shm_create_pool(wayland->shm, fd, pool_size);
+ size_t i;
+ for ( i = 0 ; i < wayland->buffer_count ; ++i )
+ {
+ pool->buffers[i].pool = pool;
+ pool->buffers[i].buffer = wl_shm_pool_create_buffer(wl_pool, size * i, width, height, stride, WL_SHM_FORMAT_ARGB8888);
+ pool->buffers[i].data = data + size * i;
+ pool->buffers[i].released = TRUE;
+ wl_buffer_add_listener(pool->buffers[i].buffer, &wayland_buffer_listener, pool);
+ }
+ wl_shm_pool_destroy(wl_pool);
+ close(fd);
+
+ return pool;
+}
+
+void
+display_buffer_pool_free(wayland_buffer_pool *self)
+{
+ self->to_free = TRUE;
+ wayland_buffer_cleanup(self);
+}
+
+static void
+wayland_surface_protocol_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output)
+{
+ wayland_output *output;
+
+ output = g_hash_table_lookup(wayland->outputs, wl_output);
+ if ( output == NULL )
+ return;
+ if ( ( output->scale < 1 ) || ( output->scale > 3 ) )
+ return;
+
+ ++wayland->scales[output->scale - 1];
+ if ( wayland->scale < output->scale )
+ {
+ wayland->scale = output->scale;
+
+ rofi_view_set_size(rofi_view_get_active(), -1, -1);
+ }
+}
+
+static void
+wayland_surface_protocol_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output)
+{
+ wayland_output *output;
+
+ output = g_hash_table_lookup(wayland->outputs, wl_output);
+ if ( output == NULL )
+ return;
+ if ( ( output->scale < 1 ) || ( output->scale > 3 ) )
+ return;
+
+ if ( ( --wayland->scales[output->scale - 1] < 1 ) && ( wayland->scale == output->scale ) )
+ {
+ int32_t i;
+ for ( i = 0 ; i < 3 ; ++i )
+ {
+ if ( wayland->scales[i] > 0 )
+ wayland->scale = i + 1;
+ }
+ rofi_view_set_size(rofi_view_get_active(), -1, -1);
+ }
+
+}
+
+static const struct wl_surface_listener wayland_surface_interface = {
+ .enter = wayland_surface_protocol_enter,
+ .leave = wayland_surface_protocol_leave,
+};
+
+static void wayland_frame_callback(void *data, struct wl_callback *callback, uint32_t time);
+
+static const struct wl_callback_listener wayland_frame_wl_callback_listener = {
+ .done = wayland_frame_callback,
+};
+
+cairo_surface_t *
+display_buffer_pool_get_next_buffer(wayland_buffer_pool *pool)
+{
+ wayland_buffer *buffer = NULL;
+ size_t i;
+ for ( i = 0 ; ( buffer == NULL ) && ( i < wayland->buffer_count ) ; ++i )
+ {
+ buffer = pool->buffers + i;
+ if ( ! buffer->released )
+ buffer = NULL;
+ }
+ if ( buffer == NULL )
+ return NULL;
+
+ cairo_surface_t *surface;
+
+ surface = cairo_image_surface_create_for_data(buffer->data, CAIRO_FORMAT_ARGB32, pool->width, pool->height, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pool->width));
+ cairo_surface_set_user_data(surface, &wayland_cairo_surface_user_data, buffer, NULL);
+ return surface;
+}
+
+void
+display_surface_commit(cairo_surface_t *surface)
+{
+ if ( surface == NULL )
+ return;
+
+ wayland_buffer *buffer = cairo_surface_get_user_data(surface, &wayland_cairo_surface_user_data);
+ wayland_buffer_pool *pool = buffer->pool;
+
+ cairo_surface_destroy(surface);
+
+ wl_surface_damage(wayland->surface, 0, 0, pool->width, pool->height);
+ wl_surface_attach(wayland->surface, buffer->buffer, 0, 0);
+ if ( wl_surface_get_version(wayland->surface) >= WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION )
+ wl_surface_set_buffer_scale(wayland->surface, wayland->scale);
+ buffer->released = FALSE;
+
+ wl_surface_commit(wayland->surface);
+}
+
+static void
+wayland_frame_callback(void *data, struct wl_callback *callback, uint32_t timestamp)
+{
+ if ( wayland->frame_cb != NULL )
+ {
+ wl_callback_destroy(wayland->frame_cb);
+ rofi_view_frame_callback();
+ }
+ wayland->frame_cb = wl_surface_frame(wayland->surface);
+ wl_callback_add_listener(wayland->frame_cb, &wayland_frame_wl_callback_listener, wayland);
+}
+
+static void
+wayland_dismiss(void *data, struct zww_launcher_menu_v1 *launcher_menu)
+{
+ g_main_loop_quit ( wayland->main_loop );
+}
+
+static const struct zww_launcher_menu_v1_listener wayland_launcher_menu_listener = {
+ .dismiss = wayland_dismiss,
+};
+
+
+static void
+wayland_keyboard_keymap(void *data, struct wl_keyboard *keyboard, enum wl_keyboard_keymap_format format, int32_t fd, uint32_t size)
+{
+ wayland_seat *self = data;
+
+ if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
+ close(fd);
+ return;
+ }
+
+ char *str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+ if (str == MAP_FAILED) {
+ close(fd);
+ return;
+ }
+
+ self->xkb.keymap = xkb_keymap_new_from_string ( self->xkb.context, str, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS );
+ if ( self->xkb.keymap == NULL ) {
+ fprintf ( stderr, "Failed to get Keymap for current keyboard device.\n" );
+ return;
+ }
+ self->xkb.state = xkb_state_new ( self->xkb.keymap );
+ if ( self->xkb.state == NULL ) {
+ fprintf ( stderr, "Failed to get state object for current keyboard device.\n" );
+ return;
+ }
+
+ xkb_common_init(&self->xkb);
+}
+
+static void
+wayland_keyboard_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys)
+{
+ wayland_seat *self = data;
+
+ wayland->last_seat = self;
+ self->serial = serial;
+
+ uint32_t *key, *kend;
+ for ( key = keys->data, kend = key + keys->size ; key < kend ; ++key ) {
+ xkb_keysym_t keysym = xkb_state_key_get_one_sym ( self->xkb.state, *key + 8 );
+ widget_modifier_mask modstate = xkb_get_modmask ( &self->xkb, keysym );
+ abe_find_action ( modstate, keysym );
+ }
+}
+
+static void
+wayland_keyboard_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface)
+{
+ wayland_seat *self = data;
+ abe_reset_release ();
+}
+
+static void
+wayland_keyboard_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, enum wl_keyboard_key_state state)
+{
+ wayland_seat *self = data;
+ widget_modifier_mask modmask;
+ xkb_keysym_t keysym;
+ char *text;
+ int len = 0;
+
+ wayland->last_seat = self;
+ self->serial = serial;
+
+ keysym = xkb_handle_key(&self->xkb, key + 8, &text, &len);
+ modmask = xkb_get_modmask(&self->xkb, keysym);
+
+ if ( state == WL_KEYBOARD_KEY_STATE_RELEASED ) {
+ if ( modmask != 0 )
+ return;
+ abe_trigger_release ();
+ }
+ else {
+ rofi_view_handle_keypress ( modmask, keysym, text, len );
+ }
+
+ rofi_view_maybe_update();
+}
+
+static void
+wayland_keyboard_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group)
+{
+ wayland_seat *self = data;
+
+ xkb_state_update_mask ( self->xkb.state, mods_depressed, mods_latched, mods_locked, 0, 0, group );
+}
+
+static void
+wayland_keyboard_repeat_info(void *data, struct wl_keyboard *keyboard, int32_t rate, int32_t delay)
+{
+ wayland_seat *self = data;
+}
+
+static const struct wl_keyboard_listener wayland_keyboard_listener = {
+ .keymap = wayland_keyboard_keymap,
+ .enter = wayland_keyboard_enter,
+ .leave = wayland_keyboard_leave,
+ .key = wayland_keyboard_key,
+ .modifiers = wayland_keyboard_modifiers,
+ .repeat_info = wayland_keyboard_repeat_info,
+};
+
+
+static void
+wayland_cursor_set_image(int i)
+{
+ struct wl_buffer *buffer;
+ struct wl_cursor_image *image;
+ image = wayland->cursor.cursor->images[i];
+
+ wayland->cursor.image = image;
+ buffer = wl_cursor_image_get_buffer(wayland->cursor.image);
+ wl_surface_attach(wayland->cursor.surface, buffer, 0, 0);
+ wl_surface_damage(wayland->cursor.surface, 0, 0, wayland->cursor.image->width, wayland->cursor.image->height);
+ wl_surface_commit(wayland->cursor.surface);
+}
+
+static void wayland_cursor_frame_callback(void *data, struct wl_callback *callback, uint32_t time);
+
+static const struct wl_callback_listener wayland_cursor_frame_wl_callback_listener = {
+ .done = wayland_cursor_frame_callback,
+};
+
+static void
+wayland_cursor_frame_callback(void *data, struct wl_callback *callback, uint32_t time)
+{
+ int i;
+
+ if ( wayland->cursor.frame_cb != NULL )
+ wl_callback_destroy(wayland->cursor.frame_cb);
+ wayland->cursor.frame_cb = wl_surface_frame(wayland->cursor.surface);
+ wl_callback_add_listener(wayland->cursor.frame_cb, &wayland_cursor_frame_wl_callback_listener, wayland);
+
+ i = wl_cursor_frame(wayland->cursor.cursor, time);
+ wayland_cursor_set_image(i);
+}
+
+static void
+wayland_pointer_send_events(wayland_seat *self)
+{
+ widget_modifier_mask modmask = xkb_get_modmask(&self->xkb, XKB_KEY_NoSymbol);
+
+ if ( self->motion.x > -1 || self->motion.y > -1 )
+ {
+ rofi_view_handle_mouse_motion(&self->motion);
+ self->motion.x = -1;
+ self->motion.y = -1;
+ }
+
+ if ( self->button.button > 0 )
+ {
+ rofi_view_handle_mouse_button(&self->button);
+ self->button.button = 0;
+ }
+
+ if ( self->wheel.vertical != 0 )
+ {
+ rofi_mouse_wheel_direction direction = self->wheel.vertical < 0 ? ROFI_MOUSE_WHEEL_UP : ROFI_MOUSE_WHEEL_DOWN;
+ guint val = ABS(self->wheel.vertical);
+ do
+ rofi_view_mouse_navigation(direction);
+ while ( --val > 0 );
+ self->wheel.vertical = 0;
+ }
+
+ if ( self->wheel.horizontal != 0 )
+ {
+ rofi_mouse_wheel_direction direction = self->wheel.horizontal < 0 ? ROFI_MOUSE_WHEEL_LEFT : ROFI_MOUSE_WHEEL_RIGHT;
+ guint val = ABS(self->wheel.horizontal);
+ do
+ rofi_view_mouse_navigation(direction);
+ while ( --val > 0 );
+ self->wheel.horizontal = 0;
+ }
+
+ rofi_view_maybe_update();
+}
+
+static void
+wayland_pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y)
+{
+ wayland_seat *self = data;
+
+ if ( wayland->cursor.surface == NULL )
+ return;
+
+ if ( wayland->cursor.cursor->image_count < 2 )
+ wayland_cursor_set_image(0);
+ else
+ wayland_cursor_frame_callback(wayland, wayland->cursor.frame_cb, 0);
+
+ wl_pointer_set_cursor(self->pointer, serial, wayland->cursor.surface, wayland->cursor.image->hotspot_x, wayland->cursor.image->hotspot_y);
+}
+
+static void
+wayland_pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface)
+{
+ wayland_seat *self = data;
+
+ if ( wayland->cursor.frame_cb != NULL )
+ wl_callback_destroy(wayland->cursor.frame_cb);
+}
+
+static void
+wayland_pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y)
+{
+ wayland_seat *self = data;
+
+ self->button.x = wl_fixed_to_int(x);
+ self->button.y = wl_fixed_to_int(y);
+ self->motion.x = wl_fixed_to_int(x);
+ self->motion.y = wl_fixed_to_int(y);
+ self->motion.time = time;
+
+ if ( wl_pointer_get_version(self->pointer) >= WL_POINTER_FRAME_SINCE_VERSION )
+ return;
+
+ wayland_pointer_send_events(self);
+}
+
+static void
+wayland_pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, enum wl_pointer_button_state state)
+{
+ wayland_seat *self = data;
+
+ wayland->last_seat = self;
+ self->serial = serial;
+
+ self->button.time = time;
+ self->button.pressed = (state == WL_POINTER_BUTTON_STATE_PRESSED);
+ switch ( button )
+ {
+ case BTN_LEFT:
+ self->button.button = WIDGET_BUTTON_LEFT;
+ break;
+ case BTN_RIGHT:
+ self->button.button = WIDGET_BUTTON_RIGHT;
+ break;
+ case BTN_MIDDLE:
+ self->button.button = WIDGET_BUTTON_MIDDLE;
+ break;
+ }
+
+ if ( wl_pointer_get_version(self->pointer) >= WL_POINTER_FRAME_SINCE_VERSION )
+ return;
+
+ wayland_pointer_send_events(self);
+}
+
+static void
+wayland_pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time, enum wl_pointer_axis axis, wl_fixed_t value)
+{
+ wayland_seat *self = data;
+
+ if ( wl_pointer_get_version(self->pointer) >= WL_POINTER_FRAME_SINCE_VERSION )
+ return;
+
+ switch ( axis )
+ {
+ case WL_POINTER_AXIS_VERTICAL_SCROLL:
+ self->wheel.vertical += (gint)(wl_fixed_to_double(value) / 10.);
+ break;
+ case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
+ self->wheel.horizontal += (gint)(wl_fixed_to_double(value) / 10.);
+ break;
+ }
+
+ wayland_pointer_send_events(self);
+}
+
+static void
+wayland_pointer_frame(void *data, struct wl_pointer *pointer)
+{
+ wayland_seat *self = data;
+ wayland_pointer_send_events(self);
+}
+
+static void
+wayland_pointer_axis_source(void *data, struct wl_pointer *pointer, enum wl_pointer_axis_source axis_source)
+{
+}
+
+static void
+wayland_pointer_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, enum wl_pointer_axis axis)
+{
+}
+
+static void
+wayland_pointer_axis_discrete(void *data, struct wl_pointer *pointer, enum wl_pointer_axis axis, int32_t discrete)
+{
+ wayland_seat *self = data;
+
+ switch ( axis )
+ {
+ case WL_POINTER_AXIS_VERTICAL_SCROLL:
+ self->wheel.vertical += discrete;
+ break;
+ case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
+ self->wheel.horizontal += discrete;
+ break;
+ }
+}
+
+static const struct wl_pointer_listener wayland_pointer_listener = {
+ .enter = wayland_pointer_enter,
+ .leave = wayland_pointer_leave,
+ .motion = wayland_pointer_motion,
+ .button = wayland_pointer_button,
+ .axis = wayland_pointer_axis,
+ .frame = wayland_pointer_frame,
+ .axis_source = wayland_pointer_axis_source,
+ .axis_stop = wayland_pointer_axis_stop,
+ .axis_discrete = wayland_pointer_axis_discrete,
+};
+
+static void
+wayland_keyboard_release(wayland_seat *self)
+{
+ if ( self->keyboard == NULL )
+ return;
+
+ if ( self->xkb.compose.state != NULL ) {
+ xkb_compose_state_unref ( self->xkb.compose.state );
+ self->xkb.compose.state = NULL;
+ }
+ if ( self->xkb.compose.table != NULL ) {
+ xkb_compose_table_unref ( self->xkb.compose.table );
+ self->xkb.compose.table = NULL;
+ }
+ if ( self->xkb.state != NULL ) {
+ xkb_state_unref ( self->xkb.state );
+ self->xkb.state = NULL;
+ }
+ if ( self->xkb.keymap != NULL ) {
+ xkb_keymap_unref ( self->xkb.keymap );
+ self->xkb.keymap = NULL;
+ }
+ if ( self->xkb.context != NULL ) {
+ xkb_context_unref ( self->xkb.context );
+ self->xkb.context = NULL;
+ }
+
+ if ( wl_keyboard_get_version(self->keyboard) >= WL_KEYBOARD_RELEASE_SINCE_VERSION )
+ wl_keyboard_release(self->keyboard);
+ else
+ wl_keyboard_destroy(self->keyboard);
+
+ self->keyboard = NULL;
+}
+
+
+static void
+wayland_pointer_release(wayland_seat *self)
+{
+ if ( self->pointer == NULL )
+ return;
+
+ if ( wl_pointer_get_version(self->pointer) >= WL_POINTER_RELEASE_SINCE_VERSION )
+ wl_pointer_release(self->pointer);
+ else
+ wl_pointer_destroy(self->pointer);
+
+ self->pointer = NULL;
+}
+
+static void
+wayland_seat_release(wayland_seat *self)
+{
+ wayland_keyboard_release(self);
+ wayland_pointer_release(self);
+
+ if ( wl_seat_get_version(self->seat) >= WL_SEAT_RELEASE_SINCE_VERSION )
+ wl_seat_release(self->seat);
+ else
+ wl_seat_destroy(self->seat);
+
+ g_hash_table_remove(wayland->seats, self->seat);
+
+ g_free(self);
+}
+
+static void
+wayland_seat_capabilities(void *data, struct wl_seat *seat, uint32_t capabilities)
+{
+ wayland_seat *self = data;
+
+ if ( ( capabilities & WL_SEAT_CAPABILITY_KEYBOARD ) && ( self->keyboard == NULL ) )
+ {
+ self->keyboard = wl_seat_get_keyboard(self->seat);
+ wl_keyboard_add_listener(self->keyboard, &wayland_keyboard_listener, self);
+ self->xkb.context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ }
+ else if ( ( ! ( capabilities & WL_SEAT_CAPABILITY_POINTER ) ) && ( self->keyboard != NULL ) )
+ wayland_keyboard_release(self);
+
+ if ( ( capabilities & WL_SEAT_CAPABILITY_POINTER ) && ( self->pointer == NULL ) )
+ {
+ self->pointer = wl_seat_get_pointer(self->seat);
+ wl_pointer_add_listener(self->pointer, &wayland_pointer_listener, self);
+ }
+ else if ( ( ! ( capabilities & WL_SEAT_CAPABILITY_POINTER ) ) && ( self->pointer != NULL ) )
+ wayland_pointer_release(self);
+}
+
+static void
+wayland_seat_name(void *data, struct wl_seat *seat, const char *name)
+{
+ wayland_seat *self = data;
+
+ if ( self->name != NULL )
+ g_hash_table_remove(wayland->seats_by_name, self->name);
+ self->name = g_strdup(name);
+ g_hash_table_insert(wayland->seats_by_name, self->name, self);
+}
+
+static const struct wl_seat_listener wayland_seat_listener = {
+ .capabilities = wayland_seat_capabilities,
+ .name = wayland_seat_name,
+};
+
+static void
+wayland_output_release(wayland_output *self)
+{
+ if ( wl_output_get_version(self->output) >= WL_OUTPUT_RELEASE_SINCE_VERSION )
+ wl_output_release(self->output);
+ else
+ wl_output_destroy(self->output);
+
+ g_hash_table_remove(wayland->outputs, self->output);
+
+ g_free(self);
+}
+
+static void
+wayland_output_done(void *data, struct wl_output *output)
+{
+}
+
+static void
+wayland_output_geometry(void *data, struct wl_output *output, int32_t x, int32_t y, int32_t width, int32_t height, int32_t subpixel, const char *make, const char *model, int32_t transform)
+{
+}
+
+static void
+wayland_output_mode(void *data, struct wl_output *output, enum wl_output_mode flags, int32_t width, int32_t height, int32_t refresh)
+{
+}
+
+static void
+wayland_output_scale(void *data, struct wl_output *output, int32_t scale)
+{
+ wayland_output *self = data;
+
+ self->scale = scale;
+}
+
+static const struct wl_output_listener wayland_output_listener = {
+ .geometry = wayland_output_geometry,
+ .mode = wayland_output_mode,
+ .scale = wayland_output_scale,
+ .done = wayland_output_done,
+};
+
+static const char * const wayland_cursor_names[] = {
+ "left_ptr",
+ "default",
+ "top_left_arrow",
+ "left-arrow",
+ NULL
+};
+
+static void
+wayland_registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version)
+{
+ if ( g_strcmp0(interface, "wl_compositor") == 0 )
+ {
+ wayland->global_names[WAYLAND_GLOBAL_COMPOSITOR] = name;
+ wayland->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, MIN(version, WL_COMPOSITOR_INTERFACE_VERSION));
+ }
+ else if ( g_strcmp0(interface, "zww_launcher_menu_v1") == 0 )
+ {
+ wayland->global_names[WAYLAND_GLOBAL_LAUNCHER_MENU] = name;
+ wayland->launcher_menu = wl_registry_bind(registry, name, &zww_launcher_menu_v1_interface, WW_LAUNCHER_MENU_INTERFACE_VERSION);
+ zww_launcher_menu_v1_add_listener(wayland->launcher_menu, &wayland_launcher_menu_listener, wayland);
+ }
+ else if ( g_strcmp0(interface, "zww_window_switcher_v1") == 0 )
+ {
+ wayland->global_names[WAYLAND_GLOBAL_WINDOW_SWITCHER] = name;
+ }
+ else if ( g_strcmp0(interface, "wl_shm") == 0 )
+ {
+ wayland->global_names[WAYLAND_GLOBAL_SHM] = name;
+ wayland->shm = wl_registry_bind(registry, name, &wl_shm_interface, MIN(version, WL_SHM_INTERFACE_VERSION));
+ }
+ else if ( g_strcmp0(interface, "wl_seat") == 0 )
+ {
+ wayland_seat *seat = g_new0(wayland_seat, 1);
+ seat->context = wayland;
+ seat->global_name = name;
+ seat->seat = wl_registry_bind(registry, name, &wl_seat_interface, MIN(version, WL_SEAT_INTERFACE_VERSION));
+
+ g_hash_table_insert(wayland->seats, seat->seat, seat);
+
+ wl_seat_add_listener(seat->seat, &wayland_seat_listener, seat);
+ }
+ else if ( g_strcmp0(interface, "wl_output") == 0 )
+ {
+ wayland_output *output = g_new0(wayland_output, 1);
+ output->context = wayland;
+ output->global_name = name;
+ output->output = wl_registry_bind(registry, name, &wl_output_interface, MIN(version, WL_OUTPUT_INTERFACE_VERSION));
+ output->scale = 1;
+
+ g_hash_table_insert(wayland->outputs, output->output, output);
+
+ wl_output_add_listener(output->output, &wayland_output_listener, output);
+ }
+
+ if ( ( wayland->cursor.theme == NULL ) && ( wayland->compositor != NULL ) && ( wayland->shm != NULL ) )
+ {
+ wayland->cursor.theme = wl_cursor_theme_load(wayland->cursor.theme_name, 32, wayland->shm);
+ if ( wayland->cursor.theme != NULL )
+ {
+ const char * const *cname = (const char * const *) wayland->cursor.name;
+ for ( cname = ( cname != NULL ) ? cname : wayland_cursor_names ; ( wayland->cursor.cursor == NULL ) && ( *cname != NULL ) ; ++cname )
+ wayland->cursor.cursor = wl_cursor_theme_get_cursor(wayland->cursor.theme, *cname);
+ if ( wayland->cursor.cursor == NULL )
+ {
+ wl_cursor_theme_destroy(wayland->cursor.theme);
+ wayland->cursor.theme = NULL;
+ }
+ else
+ wayland->cursor.surface = wl_compositor_create_surface(wayland->compositor);
+ }
+ }
+}
+
+static void
+wayland_registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
+{
+ wayland_global_name i;
+ for ( i = 0 ; i < _WAYLAND_GLOBAL_SIZE ; ++i )
+ {
+ if ( wayland->global_names[i] != name )
+ continue;
+ wayland->global_names[i] = 0;
+
+ switch ( i )
+ {
+ case WAYLAND_GLOBAL_COMPOSITOR:
+ wl_compositor_destroy(wayland->compositor);
+ wayland->compositor = NULL;
+ break;
+ case WAYLAND_GLOBAL_LAUNCHER_MENU:
+ if ( wayland->launcher_menu != NULL )
+ zww_launcher_menu_v1_destroy(wayland->launcher_menu);
+ wayland->launcher_menu = NULL;
+ break;
+ case WAYLAND_GLOBAL_WINDOW_SWITCHER:
+ zww_window_switcher_v1_destroy(wayland->window_switcher);
+ wayland->window_switcher = NULL;
+ break;
+ case WAYLAND_GLOBAL_SHM:
+ wl_shm_destroy(wayland->shm);
+ wayland->shm = NULL;
+ break;
+ case _WAYLAND_GLOBAL_SIZE:
+ g_assert_not_reached();
+ }
+ return;
+ }
+ if ( ( wayland->cursor.theme != NULL ) && ( ( wayland->compositor == NULL ) || ( wayland->shm == NULL ) ) )
+ {
+ if ( wayland->cursor.frame_cb != NULL )
+ wl_callback_destroy(wayland->cursor.frame_cb);
+ wayland->cursor.frame_cb = NULL;
+
+ wl_surface_destroy(wayland->cursor.surface);
+ wl_cursor_theme_destroy(wayland->cursor.theme);
+ wayland->cursor.surface = NULL;
+ wayland->cursor.image = NULL;
+ wayland->cursor.cursor = NULL;
+ wayland->cursor.theme = NULL;
+ }
+
+ GHashTableIter iter;
+
+ wayland_seat *seat;
+ g_hash_table_iter_init(&iter, wayland->seats);
+ while ( g_hash_table_iter_next(&iter, NULL, (gpointer *) &seat) )
+ {
+ if ( seat->global_name != name )
+ continue;
+
+ g_hash_table_iter_remove(&iter);
+ wayland_seat_release(seat);
+ return;
+ }
+
+ wayland_output *output;
+ g_hash_table_iter_init(&iter, wayland->outputs);
+ while ( g_hash_table_iter_next(&iter, NULL, (gpointer *) &output) )
+ {
+ if ( output->global_name != name )
+ continue;
+
+ g_hash_table_iter_remove(&iter);
+ wayland_output_release(output);
+ return;
+ }
+}
+
+static const struct wl_registry_listener wayland_registry_listener = {
+ .global = wayland_registry_handle_global,
+ .global_remove = wayland_registry_handle_global_remove,
+};
+
+static gboolean
+wayland_error(gpointer user_data)
+{
+ g_main_loop_quit(wayland->main_loop);
+ return G_SOURCE_REMOVE;
+}
+
+gboolean
+display_init(GMainLoop *main_loop, const gchar *display)
+{
+ wayland->main_loop = main_loop;
+
+ wayland->main_loop_source = g_water_wayland_source_new ( NULL, display );
+ if ( wayland->main_loop_source == NULL ) {
+ g_warning("Could not connect to the Wayland compositor");
+ return FALSE;
+ }
+
+ g_water_wayland_source_set_error_callback(wayland->main_loop_source, wayland_error, NULL, NULL);
+
+ wayland->buffer_count = 3;
+ wayland->scale = 1;
+
+ wayland->outputs = g_hash_table_new(g_direct_hash, g_direct_equal);
+ wayland->seats = g_hash_table_new(g_direct_hash, g_direct_equal);
+ wayland->seats_by_name = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+ wayland->display = g_water_wayland_source_get_display ( wayland->main_loop_source );
+ wayland->registry = wl_display_get_registry ( wayland->display );
+ wl_registry_add_listener ( wayland->registry, &wayland_registry_listener, NULL );
+ wl_display_roundtrip ( wayland->display );
+
+ if ( wayland->launcher_menu == NULL ) {
+ g_warning("Wayland compositor missing 'ww_launcher_menu' interface");
+ return FALSE;
+ }
+
+ wayland->surface = wl_compositor_create_surface(wayland->compositor);
+
+ const gchar *serial_str = g_getenv("ROFI_SERIAL");
+ const gchar *seat_name = g_getenv("ROFI_SEAT");
+ if ( ( serial_str != NULL ) && ( seat_name != NULL ) )
+ {
+ wl_display_roundtrip ( wayland->display );
+
+ wayland_seat *seat = g_hash_table_lookup(wayland->seats_by_name, seat_name);
+ if ( seat == NULL ) {
+ g_warning("Could not find seat named %s (${ROFI_SEAT})", seat_name);
+ return FALSE;
+ }
+
+ guint64 serial = g_ascii_strtoull(serial_str, NULL, 10);
+ const gchar *geometry = g_getenv("ROFI_GEOMETRY");
+ if ( geometry != NULL )
+ {
+ int x, y, width, height;
+ if ( sscanf(geometry, "%dx%d@%d,%d", &width, &height, &x, &y) != 4 ) {
+ g_warning("Malformed ${ROFI_GEOMETRY}, must be '<width>x<height>@<x>,<y>', was '%s'", geometry);
+ return FALSE;
+ }
+ zww_launcher_menu_v1_show_at_surface(wayland->launcher_menu, wayland->surface, seat->seat, serial, x, y, width, height);
+ }
+ else
+ zww_launcher_menu_v1_show_at_pointer(wayland->launcher_menu, wayland->surface, seat->seat, serial);
+ }
+ else
+ zww_launcher_menu_v1_show(wayland->launcher_menu, wayland->surface);
+
+ wl_surface_add_listener(wayland->surface, &wayland_surface_interface, wayland);
+ wayland_frame_callback(wayland, wayland->frame_cb, 0);
+
+ return TRUE;
+}
+
+void
+display_cleanup(void)
+{
+ if ( wayland->main_loop_source == NULL )
+ return;
+
+ if ( wayland->surface != NULL )
+ wl_surface_destroy(wayland->surface);
+
+ g_hash_table_unref( wayland->seats_by_name);
+ g_hash_table_unref( wayland->seats);
+ g_hash_table_unref( wayland->outputs);
+ wl_registry_destroy ( wayland->registry );
+ g_water_wayland_source_free ( wayland->main_loop_source );
+}