/** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "keyb.h" #include "rofi-types.h" #include "settings.h" #include "view.h" #include "display-internal.h" #include "display.h" #include "wayland-internal.h" #ifdef HAVE_WAYLAND_CURSOR_SHAPE #include "cursor-shape-v1-protocol.h" #endif #include "primary-selection-unstable-v1-protocol.h" #include "wlr-layer-shell-unstable-v1-protocol.h" #include "keyboard-shortcuts-inhibit-unstable-v1-protocol.h" #define wayland_output_get_dpi(output, scale, dimension) \ ((output)->current.physical_##dimension > 0 && (scale) > 0 \ ? round((double)(output)->current.dimension * 25.4 / (scale) / \ (output)->current.physical_##dimension) \ : 0) typedef struct _display_buffer_pool wayland_buffer_pool; typedef struct { wayland_stuff *context; uint32_t global_name; struct wl_output *output; gchar *name; struct { int32_t x; int32_t y; int32_t width; int32_t height; int32_t physical_width; /* mm */ int32_t physical_height; /* mm */ int32_t scale; int32_t transform; } current, pending; } 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 gboolean wayland_display_late_setup(void); 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); if (stride < 0) { g_warning("cairo stride width calculation failure"); return NULL; } size = (size_t)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, 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 (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { g_close(fd, NULL); 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) { if (self == NULL) { return; } 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 (config.dpi == 0 || config.dpi == 1) { // DPI auto-detect requested. config.dpi = wayland_output_get_dpi(output, output->current.scale, height); g_debug("Auto-detected DPI: %d", config.dpi); } wl_surface_set_buffer_scale(wl_surface, output->current.scale); if (wayland->scale != output->current.scale) { wayland->scale = output->current.scale; // create new buffers with the correct scaled size rofi_view_pool_refresh(); RofiViewState *state = rofi_view_get_active(); if (state != NULL) { rofi_view_set_size(state, -1, -1); } } } static void wayland_surface_protocol_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) {} 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 || wayland->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); // FIXME: hidpi 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); wayland->frame_cb = NULL; rofi_view_frame_callback(); } if (wayland->surface != NULL) { wayland->frame_cb = wl_surface_frame(wayland->surface); wl_callback_add_listener(wayland->frame_cb, &wayland_frame_wl_callback_listener, wayland); } } static void wayland_keyboard_keymap(void *data, struct wl_keyboard *keyboard, enum wl_keyboard_keymap_format format, int32_t fd, uint32_t size) { if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { close(fd); return; } char *str = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (str == MAP_FAILED) { close(fd); return; } struct xkb_keymap *keymap = xkb_keymap_new_from_string( nk_bindings_seat_get_context(wayland->bindings_seat), str, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); if (keymap == NULL) { fprintf(stderr, "Failed to get Keymap for current keyboard device.\n"); return; } struct xkb_state *state = xkb_state_new(keymap); if (state == NULL) { fprintf(stderr, "Failed to get state object for current keyboard device.\n"); return; } nk_bindings_seat_update_keymap(wayland->bindings_seat, keymap, state); } 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 / sizeof(*key); key < kend; ++key) { nk_bindings_seat_handle_key(wayland->bindings_seat, NULL, *key + 8, NK_BINDINGS_KEY_STATE_PRESSED); } } static void wayland_keyboard_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { wayland_seat *self = data; // TODO? } static gboolean wayland_key_repeat(void *data) { wayland_seat *self = data; if (self->repeat.key == 0) { self->repeat.source = NULL; return G_SOURCE_REMOVE; } char *text = nk_bindings_seat_handle_key(wayland->bindings_seat, NULL, self->repeat.key, NK_BINDINGS_KEY_STATE_PRESS); RofiViewState *state = rofi_view_get_active(); if (state == NULL) { return G_SOURCE_REMOVE; } if (text != NULL) { rofi_view_handle_text(state, text); } rofi_view_maybe_update(state); return G_SOURCE_CONTINUE; } static gboolean wayland_key_repeat_delay(void *data) { wayland_seat *self = data; if (self->repeat.key == 0) { return FALSE; } char *text = nk_bindings_seat_handle_key(wayland->bindings_seat, NULL, self->repeat.key, NK_BINDINGS_KEY_STATE_PRESS); RofiViewState *state = rofi_view_get_active(); if (state == NULL) { return G_SOURCE_REMOVE; } if (text != NULL) { rofi_view_handle_text(state, text); } guint repeat_wait_ms = 30; if (self->repeat.rate != 0) { repeat_wait_ms = 1000 / self->repeat.rate; } guint source_id = g_timeout_add(repeat_wait_ms, wayland_key_repeat, data); self->repeat.source = g_main_context_find_source_by_id(NULL, source_id); rofi_view_maybe_update(state); return G_SOURCE_REMOVE; } 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 kstate) { RofiViewState *state = rofi_view_get_active(); wayland_seat *self = data; wayland->last_seat = self; self->serial = serial; xkb_keycode_t keycode = key + 8; if (kstate == WL_KEYBOARD_KEY_STATE_RELEASED) { if (keycode == self->repeat.key) { self->repeat.key = 0; if (self->repeat.source != NULL) { g_source_destroy(self->repeat.source); self->repeat.source = NULL; } } nk_bindings_seat_handle_key(wayland->bindings_seat, NULL, keycode, NK_BINDINGS_KEY_STATE_RELEASE); } else if (kstate == WL_KEYBOARD_KEY_STATE_PRESSED) { gchar *text = nk_bindings_seat_handle_key( wayland->bindings_seat, NULL, keycode, NK_BINDINGS_KEY_STATE_PRESS); if (self->repeat.source != NULL) { g_source_destroy(self->repeat.source); self->repeat.source = NULL; } if (state != NULL) { if (text != NULL) { rofi_view_handle_text(state, text); } self->repeat.key = keycode; guint source_id = g_timeout_add(self->repeat.delay, wayland_key_repeat_delay, data); self->repeat.source = g_main_context_find_source_by_id(NULL, source_id); } } if (state != NULL) { rofi_view_maybe_update(state); } } 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; nk_bindings_seat_update_mask(wayland->bindings_seat, NULL, mods_depressed, mods_latched, mods_locked, 0, 0, group); RofiViewState *state = rofi_view_get_active(); if (state != NULL) { rofi_view_maybe_update(state); } } static void wayland_keyboard_repeat_info(void *data, struct wl_keyboard *keyboard, int32_t rate, int32_t delay) { wayland_seat *self = data; self->repeat.key = 0; self->repeat.rate = rate; self->repeat.delay = delay; if (self->repeat.source != NULL) { g_source_destroy(self->repeat.source); self->repeat.source = NULL; } } 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 gboolean wayland_cursor_reload_theme(guint scale); 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_set_buffer_scale(wayland->cursor.surface, wayland->scale); 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) { RofiViewState *state = rofi_view_get_active(); if (state == NULL) { return; } if (self->motion.x > -1 || self->motion.y > -1) { rofi_view_handle_mouse_motion(state, self->motion.x, self->motion.y, config.hover_select); self->motion.x = -1; self->motion.y = -1; } NkBindingsMouseButton button = -1; switch (self->button.button) { case BTN_LEFT: button = NK_BINDINGS_MOUSE_BUTTON_PRIMARY; break; case BTN_RIGHT: button = NK_BINDINGS_MOUSE_BUTTON_SECONDARY; break; case BTN_MIDDLE: button = NK_BINDINGS_MOUSE_BUTTON_MIDDLE; break; } if (self->button.button > 0) { if (self->button.pressed) { rofi_view_handle_mouse_motion(state, self->button.x, self->button.y, FALSE); nk_bindings_seat_handle_button(wayland->bindings_seat, NULL, button, NK_BINDINGS_BUTTON_STATE_PRESS, self->button.time); } else { nk_bindings_seat_handle_button(wayland->bindings_seat, NULL, button, NK_BINDINGS_BUTTON_STATE_RELEASE, self->button.time); } self->button.button = 0; } if (self->axis_source == WL_POINTER_AXIS_SOURCE_FINGER || self->axis_source == WL_POINTER_AXIS_SOURCE_CONTINUOUS) { self->wheel.vertical += 20 * self->wheel_continuous.vertical; self->wheel.horizontal += 20 * self->wheel_continuous.horizontal; } if (abs(self->wheel.vertical) >= 120) { gint v120 = self->wheel.vertical; nk_bindings_seat_handle_scroll(wayland->bindings_seat, NULL, NK_BINDINGS_SCROLL_AXIS_VERTICAL, v120 / 120); if (v120 > 0) { self->wheel.vertical = v120 % 120; } else { self->wheel.vertical = -((-v120) % 120); } } if (abs(self->wheel.horizontal) >= 120) { gint v120 = self->wheel.horizontal; nk_bindings_seat_handle_scroll(wayland->bindings_seat, NULL, NK_BINDINGS_SCROLL_AXIS_HORIZONTAL, v120 / 120); if (v120 > 0) { self->wheel.horizontal = v120 % 120; } else { self->wheel.horizontal = -((-v120) % 120); } } self->axis_source = 0; self->wheel_continuous.vertical = 0; self->wheel_continuous.horizontal = 0; rofi_view_maybe_update(state); } static struct wl_cursor * rofi_cursor_type_to_wl_cursor(struct wl_cursor_theme *theme, RofiCursorType type) { static const char *const default_names[] = { "default", "left_ptr", "top_left_arrow", "left-arrow", NULL}; static const char *const pointer_names[] = {"pointer", "hand1", NULL}; static const char *const text_names[] = {"text", "xterm", NULL}; const char *const *name; struct wl_cursor *cursor = NULL; switch (type) { case ROFI_CURSOR_POINTER: name = pointer_names; break; case ROFI_CURSOR_TEXT: name = text_names; break; default: name = default_names; break; } for (; cursor == NULL && *name != NULL; ++name) { cursor = wl_cursor_theme_get_cursor(theme, *name); } return cursor; } #ifdef HAVE_WAYLAND_CURSOR_SHAPE static enum wp_cursor_shape_device_v1_shape rofi_cursor_type_to_wp_cursor_shape(RofiCursorType type) { switch (type) { case ROFI_CURSOR_POINTER: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER; case ROFI_CURSOR_TEXT: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT; default: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; } } #endif static void wayland_cursor_update_for_seat(wayland_seat *seat) { #ifdef HAVE_WAYLAND_CURSOR_SHAPE if (seat->cursor_shape_device != NULL) { wp_cursor_shape_device_v1_set_shape( seat->cursor_shape_device, seat->pointer_serial, rofi_cursor_type_to_wp_cursor_shape(wayland->cursor.type)); return; } else if (wayland->cursor.theme == NULL) { // cursor-shape-v1 is available, but the seat haven't seen a pointer yet return; } #endif if (wayland->cursor.surface == NULL) { wayland->cursor.surface = wl_compositor_create_surface(wayland->compositor); } 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( seat->pointer, seat->pointer_serial, wayland->cursor.surface, wayland->cursor.image->hotspot_x / wayland->cursor.scale, wayland->cursor.image->hotspot_y / wayland->cursor.scale); } 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; self->pointer_serial = serial; #ifdef HAVE_WAYLAND_CURSOR_SHAPE if (wayland->cursor_shape_manager != NULL) { if (self->cursor_shape_device == NULL) { self->cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer( wayland->cursor_shape_manager, pointer); } } else #endif if (!wayland_cursor_reload_theme(wayland->scale)) { return; } wayland_cursor_update_for_seat(self); } void wayland_display_set_cursor_type(RofiCursorType type) { wayland_seat *seat; GHashTableIter iter; struct wl_cursor *cursor; if (wayland->cursor.type == type) { return; } wayland->cursor.type = type; #ifdef HAVE_WAYLAND_CURSOR_SHAPE if (wayland->cursor_shape_manager == NULL) #endif { if (wayland->cursor.theme == NULL) { return; } cursor = rofi_cursor_type_to_wl_cursor(wayland->cursor.theme, type); if (cursor == NULL) { g_info("Failed to load cursor type %d", type); return; } wayland->cursor.cursor = cursor; } g_hash_table_iter_init(&iter, wayland->seats); while (g_hash_table_iter_next(&iter, NULL, (gpointer *)&seat)) { if (seat->pointer != NULL) { wayland_cursor_update_for_seat(seat); } } } 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); wayland->cursor.frame_cb = NULL; } } 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; } 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); self->button.button = button; } 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; switch (axis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: self->wheel_continuous.vertical += wl_fixed_to_double(value); break; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: self->wheel_continuous.horizontal += wl_fixed_to_double(value); break; } } 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) { wayland_seat *self = data; self->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; // values are multiplied by 120 for compatibility with the // new high-resolution events switch (axis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: self->wheel.vertical += discrete * 120; break; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: self->wheel.horizontal += discrete * 120; break; } } #ifdef WL_POINTER_AXIS_VALUE120_SINCE_VERSION static void wayland_pointer_axis120(void *data, struct wl_pointer *wl_pointer, enum wl_pointer_axis axis, int32_t value120) { wayland_seat *self = data; switch (axis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: self->wheel.vertical += value120; break; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: self->wheel.horizontal += value120; break; } } #endif 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, #ifdef WL_POINTER_AXIS_VALUE120_SINCE_VERSION .axis_value120 = wayland_pointer_axis120, #endif .axis_discrete = wayland_pointer_axis_discrete, }; static void wayland_keyboard_release(wayland_seat *self) { if (self->keyboard == NULL) { return; } wl_keyboard_release(self->keyboard); self->repeat.key = 0; if (self->repeat.source != NULL) { g_source_destroy(self->repeat.source); self->repeat.source = NULL; } self->keyboard = NULL; } #define CLIPBOARD_READ_INCREMENT 1024 struct clipboard_read_info { char *buffer; size_t size; int fd; ClipboardCb callback; void *user_data; }; static gboolean clipboard_read_glib_callback(GIOChannel *channel, GIOCondition condition, gpointer opaque) { struct clipboard_read_info *info = opaque; gsize read; GIOStatus status = g_io_channel_read_chars(channel, info->buffer + info->size, CLIPBOARD_READ_INCREMENT, &read, NULL); switch (status) { case G_IO_STATUS_AGAIN: return TRUE; case G_IO_STATUS_NORMAL: { info->size += read; info->buffer = g_realloc(info->buffer, info->size + CLIPBOARD_READ_INCREMENT); if (!info->buffer) { g_io_channel_shutdown(channel, FALSE, NULL); g_io_channel_unref(channel); close(info->fd); g_free(info); return FALSE; } return TRUE; } default: info->buffer[info->size] = '\0'; if (status == G_IO_STATUS_EOF) { info->callback(info->buffer, info->user_data); } else { // G_IO_STATUS_ERROR g_warning("Could not read data from clipboard"); g_free(info->buffer); } g_io_channel_shutdown(channel, FALSE, NULL); g_io_channel_unref(channel); close(info->fd); g_free(info); return FALSE; } } static gboolean clipboard_read_data(int fd, ClipboardCb callback, void *user_data) { GIOChannel *channel = g_io_channel_unix_new(fd); struct clipboard_read_info *info = g_malloc(sizeof *info); if (info == NULL) { g_io_channel_unref(channel); close(fd); return FALSE; } info->fd = fd; info->size = 0; info->callback = callback; info->user_data = user_data; info->buffer = g_malloc(CLIPBOARD_READ_INCREMENT); if (info->buffer == NULL) { g_io_channel_unref(channel); close(info->fd); g_free(info); return FALSE; } g_io_add_watch(channel, G_IO_IN | G_IO_HUP | G_IO_ERR, clipboard_read_glib_callback, info); return TRUE; } static void data_offer_handle_offer(void *data, struct wl_data_offer *offer, const char *mime_type) {} static void data_offer_handle_source_actions( void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions) {} static void data_offer_handle_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) {} static const struct wl_data_offer_listener data_offer_listener = { .offer = data_offer_handle_offer, .source_actions = data_offer_handle_source_actions, .action = data_offer_handle_action, }; static void data_device_handle_data_offer(void *data, struct wl_data_device *data_device, struct wl_data_offer *offer) { wl_data_offer_add_listener(offer, &data_offer_listener, NULL); } static void data_device_handle_enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) { } static void data_device_handle_leave(void *data, struct wl_data_device *wl_data_device) { } static void data_device_handle_motion(void *data, struct wl_data_device *wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y) { } static void data_device_handle_drop(void *data, struct wl_data_device *wl_data_device) { } static void clipboard_handle_selection(enum clipboard_type cb_type, void *offer) { clipboard_data *clipboard = &wayland->clipboards[cb_type]; if (clipboard->offer != NULL) { if (cb_type == CLIPBOARD_DEFAULT) { wl_data_offer_destroy(clipboard->offer); } else { zwp_primary_selection_offer_v1_destroy(clipboard->offer); } } clipboard->offer = offer; } static void data_device_handle_selection(void *data, struct wl_data_device *data_device, struct wl_data_offer *offer) { clipboard_handle_selection(CLIPBOARD_DEFAULT, offer); } static const struct wl_data_device_listener data_device_listener = { .data_offer = data_device_handle_data_offer, .enter = data_device_handle_enter, .leave = data_device_handle_leave, .motion = data_device_handle_motion, .drop = data_device_handle_drop, .selection = data_device_handle_selection, }; static void primary_selection_handle_offer(void *data, struct zwp_primary_selection_offer_v1 *offer, const char *mime_type) {} static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = { .offer = primary_selection_handle_offer, }; static void primary_selection_device_handle_data_offer( void *data, struct zwp_primary_selection_device_v1 *data_device, struct zwp_primary_selection_offer_v1 *offer) { zwp_primary_selection_offer_v1_add_listener( offer, &primary_selection_offer_listener, NULL); } static void primary_selection_device_handle_selection( void *data, struct zwp_primary_selection_device_v1 *data_device, struct zwp_primary_selection_offer_v1 *offer) { clipboard_handle_selection(CLIPBOARD_PRIMARY, offer); } static const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = { .data_offer = primary_selection_device_handle_data_offer, .selection = primary_selection_device_handle_selection, }; static void wayland_pointer_release(wayland_seat *self) { if (self->pointer == NULL) { return; } #ifdef HAVE_WAYLAND_CURSOR_SHAPE if (self->cursor_shape_device != NULL) { wp_cursor_shape_device_v1_destroy(self->cursor_shape_device); self->cursor_shape_device = NULL; } #endif wl_pointer_release(self->pointer); self->pointer = NULL; } static void wayland_seat_release(wayland_seat *self) { wayland_keyboard_release(self); wayland_pointer_release(self); wl_seat_release(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); } 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); } if (wayland->data_device_manager != NULL) { self->data_device = wl_data_device_manager_get_data_device( wayland->data_device_manager, seat); wl_data_device_add_listener(self->data_device, &data_device_listener, NULL); } if (wayland->primary_selection_device_manager != NULL) { self->primary_selection_device = zwp_primary_selection_device_manager_v1_get_device( wayland->primary_selection_device_manager, seat); zwp_primary_selection_device_v1_add_listener( self->primary_selection_device, &primary_selection_device_listener, NULL); } } 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) { g_debug("Output release: %s", self->name); 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->name); g_free(self); } static wayland_output *wayland_output_by_name(const char *name) { #ifdef WL_OUTPUT_NAME_SINCE_VERSION GHashTableIter iter; wayland_output *output; g_debug("Monitor lookup by name : %s", name); g_hash_table_iter_init(&iter, wayland->outputs); while (g_hash_table_iter_next(&iter, NULL, (gpointer *)&output)) { if (g_strcmp0(output->name, name) == 0) { return output; } } #endif g_debug("Monitor lookup by name failed: %s", name); return NULL; } static void wayland_output_done(void *data, struct wl_output *output) { wayland_output *self = data; self->current = self->pending; g_debug("Output %s: %" PRIi32 "x%" PRIi32 " (%" PRIi32 "x%" PRIi32 "mm)" " position %" PRIi32 "x%" PRIi32 " scale %" PRIi32 " transform %" PRIi32, self->name ? self->name : "Unknown", self->current.width, self->current.height, self->current.physical_width, self->current.physical_height, self->current.x, self->current.y, self->current.scale, self->current.transform); } 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) { wayland_output *self = data; self->pending.x = x; self->pending.y = y; self->pending.physical_width = width; self->pending.physical_height = height; self->pending.transform = 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) { wayland_output *self = data; if (flags & WL_OUTPUT_MODE_CURRENT) { self->pending.width = width; self->pending.height = height; } } static void wayland_output_scale(void *data, struct wl_output *output, int32_t scale) { wayland_output *self = data; self->pending.scale = scale; } #ifdef WL_OUTPUT_NAME_SINCE_VERSION static void wayland_output_name(void *data, struct wl_output *output, const char *name) { wayland_output *self = data; g_free(self->name); self->name = g_strdup(name); } #endif #ifdef WL_OUTPUT_DESCRIPTION_SINCE_VERSION static void wayland_output_description(void *data, struct wl_output *output, const char *name) {} #endif 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, #ifdef WL_OUTPUT_NAME_SINCE_VERSION .name = wayland_output_name, #endif #ifdef WL_OUTPUT_DESCRIPTION_SINCE_VERSION .description = wayland_output_description, #endif }; static void wayland_registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { g_debug("wayland registry: interface %s", interface); if (g_strcmp0(interface, wl_compositor_interface.name) == 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, zwlr_layer_shell_v1_interface.name) == 0) { wayland->global_names[WAYLAND_GLOBAL_LAYER_SHELL] = name; wayland->layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, MIN(version, WL_LAYER_SHELL_INTERFACE_VERSION)); } else if (g_strcmp0(interface, zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name) == 0) { wayland->global_names[WAYLAND_GLOBAL_KEYBOARD_SHORTCUTS_INHIBITOR] = name; wayland->kb_shortcuts_inhibit_manager = wl_registry_bind(registry, name, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, MIN(version, WL_KEYBOARD_SHORTCUTS_INHIBITOR_INTERFACE_VERSION)); } else if (g_strcmp0(interface, wl_shm_interface.name) == 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_interface.name) == 0) { if (version < WL_SEAT_INTERFACE_MIN_VERSION) { g_error("Minimum version of wayland seat interface is %u, got %u", WL_SEAT_INTERFACE_MIN_VERSION, version); return; } version = MIN(version, WL_SEAT_INTERFACE_MAX_VERSION); 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, 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_interface.name) == 0) { if (version < WL_OUTPUT_INTERFACE_MIN_VERSION) { g_error("Minimum version of wayland output interface is %u, got %u", WL_OUTPUT_INTERFACE_MIN_VERSION, version); return; } version = MIN(version, WL_OUTPUT_INTERFACE_MAX_VERSION); 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, version); output->pending.scale = 1; output->current = output->pending; g_hash_table_insert(wayland->outputs, output->output, output); wl_output_add_listener(output->output, &wayland_output_listener, output); } else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) { wayland->data_device_manager = wl_registry_bind(registry, name, &wl_data_device_manager_interface, 3); } else if (strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name) == 0) { wayland->primary_selection_device_manager = wl_registry_bind( registry, name, &zwp_primary_selection_device_manager_v1_interface, 1); } #ifdef HAVE_WAYLAND_CURSOR_SHAPE else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { wayland->global_names[WAYLAND_GLOBAL_CURSOR_SHAPE] = name; wayland->cursor_shape_manager = wl_registry_bind( registry, name, &wp_cursor_shape_manager_v1_interface, 1); } #endif } 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_CURSOR_SHAPE: #ifdef HAVE_WAYLAND_CURSOR_SHAPE wp_cursor_shape_manager_v1_destroy(wayland->cursor_shape_manager); wayland->cursor_shape_manager = NULL; #endif break; case WAYLAND_GLOBAL_LAYER_SHELL: zwlr_layer_shell_v1_destroy(wayland->layer_shell); wayland->layer_shell = NULL; break; case WAYLAND_GLOBAL_KEYBOARD_SHORTCUTS_INHIBITOR: zwp_keyboard_shortcuts_inhibit_manager_v1_destroy(wayland->kb_shortcuts_inhibit_manager); wayland->kb_shortcuts_inhibit_manager = 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; wayland->cursor.scale = 0; } 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 void wayland_layer_shell_surface_configure( void *data, struct zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t width, uint32_t height) { wayland->layer_width = width; wayland->layer_height = height; zwlr_layer_surface_v1_ack_configure(surface, serial); } static void wayland_surface_destroy(void) { if (wayland->wlr_surface != NULL) { zwlr_layer_surface_v1_destroy(wayland->wlr_surface); wayland->wlr_surface = NULL; } if (wayland->surface != NULL) { wl_surface_destroy(wayland->surface); wayland->surface = NULL; } } static void wayland_layer_shell_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface) { g_debug("Layer shell surface closed"); wayland_surface_destroy(); // In this case, we recreate the layer shell surface the best we can and // re-initialize everything: // recreate layer shell wayland_display_late_setup(); // create new buffers with the correct scaled size rofi_view_pool_refresh(); RofiViewState *state = rofi_view_get_active(); if (state != NULL) { rofi_view_set_size(state, -1, -1); } } static const struct zwlr_layer_surface_v1_listener wayland_layer_shell_surface_listener = { .configure = wayland_layer_shell_surface_configure, .closed = wayland_layer_shell_surface_closed, }; static gboolean wayland_error(gpointer user_data) { g_main_loop_quit(wayland->main_loop); return G_SOURCE_REMOVE; } static gboolean wayland_cursor_reload_theme(guint scale) { if (wayland->cursor.theme != NULL) { if (wayland->cursor.scale == scale) return TRUE; wl_cursor_theme_destroy(wayland->cursor.theme); wayland->cursor.theme = NULL; wayland->cursor.cursor = NULL; } guint64 cursor_size = 24; char *env_cursor_size = (char *)g_getenv("XCURSOR_SIZE"); if (env_cursor_size && strlen(env_cursor_size) > 0) { guint64 size = g_ascii_strtoull(env_cursor_size, NULL, 10); if (0 < size && size < G_MAXUINT64) { cursor_size = size; } } cursor_size *= scale; wayland->cursor.theme = wl_cursor_theme_load(wayland->cursor.theme_name, cursor_size, wayland->shm); if (wayland->cursor.theme != NULL) { wayland->cursor.cursor = rofi_cursor_type_to_wl_cursor( wayland->cursor.theme, wayland->cursor.type); if (wayland->cursor.cursor == NULL) { wl_cursor_theme_destroy(wayland->cursor.theme); wayland->cursor.theme = NULL; return FALSE; } else { wayland->cursor.scale = scale; return TRUE; } } return FALSE; } static gboolean wayland_display_setup(GMainLoop *main_loop, NkBindings *bindings) { wayland->main_loop = main_loop; char *display = (char *)g_getenv("WAYLAND_DISPLAY"); 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->cursor.type = ROFI_CURSOR_DEFAULT; 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->compositor == NULL || wayland->shm == NULL || g_hash_table_size(wayland->outputs) == 0 || g_hash_table_size(wayland->seats) == 0) { g_error("Could not connect to wayland compositor"); return FALSE; } if (wayland->layer_shell == NULL) { g_error("Rofi on wayland requires support for the layer shell protocol"); return FALSE; } wayland->bindings_seat = nk_bindings_seat_new(bindings, XKB_CONTEXT_NO_FLAGS); // Wait for output information wl_display_roundtrip(wayland->display); return TRUE; } static gboolean wayland_display_late_setup(void) { wayland_output *output = wayland_output_by_name(config.monitor); wayland->surface = wl_compositor_create_surface(wayland->compositor); wayland->wlr_surface = zwlr_layer_shell_v1_get_layer_surface( wayland->layer_shell, wayland->surface, (output != NULL ? output->output : NULL), ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "rofi"); // Set size zero and anchor on all corners to get the usable screen size // see https://github.com/swaywm/wlroots/pull/2422 zwlr_layer_surface_v1_set_anchor(wayland->wlr_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); zwlr_layer_surface_v1_set_size(wayland->wlr_surface, 0, 0); zwlr_layer_surface_v1_set_keyboard_interactivity(wayland->wlr_surface, 1); zwlr_layer_surface_v1_add_listener( wayland->wlr_surface, &wayland_layer_shell_surface_listener, NULL); if (config.global_kb && wayland->kb_shortcuts_inhibit_manager) { g_debug("inhibit shortcuts from compositor"); GHashTableIter iter; wayland_seat *seat; g_hash_table_iter_init(&iter, wayland->seats); while (g_hash_table_iter_next(&iter, NULL, (gpointer *)&seat)) { // we don't need to keep track of these, they will get inactive when the // surface is destroyed zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts(wayland->kb_shortcuts_inhibit_manager, wayland->surface, seat->seat); } } wl_surface_add_listener(wayland->surface, &wayland_surface_interface, wayland); wl_surface_commit(wayland->surface); wl_display_roundtrip(wayland->display); wayland_frame_callback(wayland, wayland->frame_cb, 0); return TRUE; } gboolean display_get_surface_dimensions(int *width, int *height) { if (wayland->layer_width != 0) { if (width != NULL) { *width = wayland->layer_width; } if (height != NULL) { *height = wayland->layer_height; } return TRUE; } return FALSE; } void display_set_surface_dimensions(int width, int height, int x_margin, int y_margin, int loc) { wayland->layer_width = width; wayland->layer_height = height; zwlr_layer_surface_v1_set_size(wayland->wlr_surface, width, height); uint32_t wlr_anchor = 0; switch (loc) { case WL_NORTH_WEST: wlr_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; break; case WL_NORTH: wlr_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; break; case WL_NORTH_EAST: wlr_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; break; case WL_EAST: wlr_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; break; case WL_SOUTH_EAST: wlr_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; break; case WL_SOUTH: wlr_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; break; case WL_SOUTH_WEST: wlr_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; break; case WL_WEST: wlr_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; break; case WL_CENTER: default: break; } if (height == 0) { wlr_anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; } if (width == 0) { wlr_anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; } zwlr_layer_surface_v1_set_anchor(wayland->wlr_surface, wlr_anchor); // NOTE: Setting margin for edges we are not anchored to has no effect, so we // can safely set contradictory margins (e.g. top vs bottom) - at most one of // the margins on a given axis will have effect. // This also means that margin has no effect if the window is centered. :( zwlr_layer_surface_v1_set_margin(wayland->wlr_surface, y_margin, -x_margin, -y_margin, x_margin); } static void wayland_display_early_cleanup(void) { if (wayland->main_loop_source == NULL) { return; } wayland_surface_destroy(); wl_display_flush(wayland->display); } static void wayland_display_cleanup(void) { if (wayland->main_loop_source == NULL) { return; } nk_bindings_seat_free(wayland->bindings_seat); 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); wl_display_flush(wayland->display); g_water_wayland_source_free(wayland->main_loop_source); } static void wayland_display_dump_monitor_layout(void) { int is_term = isatty(fileno(stdout)); GHashTableIter iter; wayland_output *output; g_hash_table_iter_init(&iter, wayland->outputs); printf("Monitor layout:\n"); while (g_hash_table_iter_next(&iter, NULL, (gpointer *)&output)) { printf("%s ID%s: %" PRIu32, is_term ? color_bold : "", is_term ? color_reset : "", output->global_name); printf("\n"); printf("%s name%s: %s\n", is_term ? color_bold : "", is_term ? color_reset : "", output->name); printf("%s scale%s: %" PRIi32 "\n", is_term ? color_bold : "", is_term ? color_reset : "", output->current.scale); printf("%s position%s: %" PRIi32 ",%" PRIi32 "\n", is_term ? color_bold : "", is_term ? color_reset : "", output->current.x, output->current.y); printf("%s size%s: %" PRIi32 ",%" PRIi32 "\n", is_term ? color_bold : "", is_term ? color_reset : "", output->current.width, output->current.height); if (output->current.physical_width > 0 && output->current.physical_height > 0) { printf("%s size%s: %" PRIi32 "mm,%" PRIi32 "mm dpi: %.0f,%.0f\n", is_term ? color_bold : "", is_term ? color_reset : "", output->current.physical_width, output->current.physical_height, wayland_output_get_dpi(output, output->current.scale, width), wayland_output_get_dpi(output, output->current.scale, height)); } printf("\n"); } } static void wayland_display_startup_notification(RofiHelperExecuteContext *context, GSpawnChildSetupFunc *child_setup, gpointer *user_data) {} static int wayland_display_monitor_active(workarea *mon) { // TODO: do something? return FALSE; } static void wayland_display_set_input_focus(guint w) {} static void wayland_display_revert_input_focus(void) {} static const struct _view_proxy *wayland_display_view_proxy(void) { return wayland_view_proxy; } static guint wayland_display_scale(void) { return wayland->scale; } static void wayland_get_clipboard_data(int cb_type, ClipboardCb callback, void *user_data) { clipboard_data *clipboard = &wayland->clipboards[cb_type]; if (clipboard->offer == NULL) { return; } int fds[2]; if (pipe(fds) < 0) { return; } if (cb_type == CLIPBOARD_DEFAULT) { wl_data_offer_receive(clipboard->offer, "text/plain", fds[1]); } else { zwp_primary_selection_offer_v1_receive(clipboard->offer, "text/plain", fds[1]); } close(fds[1]); clipboard_read_data(fds[0], callback, user_data); } static void wayland_set_fullscreen_mode(void) { if (!wayland->wlr_surface) { return; } zwlr_layer_surface_v1_set_exclusive_zone(wayland->wlr_surface, -1); zwlr_layer_surface_v1_set_size(wayland->wlr_surface, 0, 0); wl_surface_commit(wayland->surface); wl_display_roundtrip(wayland->display); rofi_view_pool_refresh(); } static display_proxy display_ = { .setup = wayland_display_setup, .late_setup = wayland_display_late_setup, .early_cleanup = wayland_display_early_cleanup, .cleanup = wayland_display_cleanup, .dump_monitor_layout = wayland_display_dump_monitor_layout, .startup_notification = wayland_display_startup_notification, .monitor_active = wayland_display_monitor_active, .set_input_focus = wayland_display_set_input_focus, .revert_input_focus = wayland_display_revert_input_focus, .scale = wayland_display_scale, .get_clipboard_data = wayland_get_clipboard_data, .set_fullscreen_mode = wayland_set_fullscreen_mode, .view = wayland_display_view_proxy, }; display_proxy *const wayland_proxy = &display_;