| // Copyright 2017 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "sommelier.h" // NOLINT(build/include_directory) |
| #include "sommelier-tracing.h" // NOLINT(build/include_directory) |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <gbm.h> |
| #include <libgen.h> |
| #include <linux/virtwl.h> |
| #include <math.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/file.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <sys/un.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| #include <wayland-client.h> |
| #include <xcb/composite.h> |
| #include <xcb/xfixes.h> |
| #include <xcb/xproto.h> |
| |
| #include "aura-shell-client-protocol.h" // NOLINT(build/include_directory) |
| #include "drm-server-protocol.h" // NOLINT(build/include_directory) |
| #ifdef GAMEPAD_SUPPORT |
| #include "gaming-input-unstable-v2-client-protocol.h" // NOLINT(build/include_directory) |
| #endif |
| #include "keyboard-extension-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) |
| #include "linux-dmabuf-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) |
| #include "pointer-constraints-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) |
| #include "relative-pointer-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) |
| #include "text-input-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) |
| #include "viewporter-client-protocol.h" // NOLINT(build/include_directory) |
| #include "xdg-shell-unstable-v6-client-protocol.h" // NOLINT(build/include_directory) |
| |
| #define errno_assert(rv) \ |
| { \ |
| int macro_private_assert_value = (rv); \ |
| if (!macro_private_assert_value) { \ |
| fprintf(stderr, "Unexpected error: %s\n", strerror(errno)); \ |
| assert(false); \ |
| } \ |
| } |
| |
| // Check that required macro definitions exist. |
| #ifndef XWAYLAND_PATH |
| #error XWAYLAND_PATH must be defined |
| #endif |
| #ifndef XWAYLAND_GL_DRIVER_PATH |
| #error XWAYLAND_GL_DRIVER_PATH must be defined |
| #endif |
| #ifndef XWAYLAND_SHM_DRIVER |
| #error XWAYLAND_SHM_DRIVER must be defined |
| #endif |
| #ifndef SHM_DRIVER |
| #error SHM_DRIVER must be defined |
| #endif |
| #ifndef VIRTWL_DEVICE |
| #error VIRTWL_DEVICE must be defined |
| #endif |
| #ifndef PEER_CMD_PREFIX |
| #error PEER_CMD_PREFIX must be defined |
| #endif |
| #ifndef FRAME_COLOR |
| #error FRAME_COLOR must be defined |
| #endif |
| #ifndef DARK_FRAME_COLOR |
| #error DARK_FRAME_COLOR must be defined |
| #endif |
| |
| struct sl_data_source { |
| struct sl_context* ctx; |
| struct wl_data_source* internal; |
| }; |
| |
| enum { |
| PROPERTY_WM_NAME, |
| PROPERTY_WM_CLASS, |
| PROPERTY_WM_TRANSIENT_FOR, |
| PROPERTY_WM_NORMAL_HINTS, |
| PROPERTY_WM_CLIENT_LEADER, |
| PROPERTY_WM_PROTOCOLS, |
| PROPERTY_MOTIF_WM_HINTS, |
| PROPERTY_NET_STARTUP_ID, |
| PROPERTY_NET_WM_STATE, |
| PROPERTY_GTK_THEME_VARIANT, |
| }; |
| |
| #define US_POSITION (1L << 0) |
| #define US_SIZE (1L << 1) |
| #define P_POSITION (1L << 2) |
| #define P_SIZE (1L << 3) |
| #define P_MIN_SIZE (1L << 4) |
| #define P_MAX_SIZE (1L << 5) |
| #define P_RESIZE_INC (1L << 6) |
| #define P_ASPECT (1L << 7) |
| #define P_BASE_SIZE (1L << 8) |
| #define P_WIN_GRAVITY (1L << 9) |
| |
| struct sl_wm_size_hints { |
| uint32_t flags; |
| int32_t x, y; |
| int32_t width, height; |
| int32_t min_width, min_height; |
| int32_t max_width, max_height; |
| int32_t width_inc, height_inc; |
| struct { |
| int32_t x; |
| int32_t y; |
| } min_aspect, max_aspect; |
| int32_t base_width, base_height; |
| int32_t win_gravity; |
| }; |
| |
| // WM_HINTS is defined at: https://tronche.com/gui/x/icccm/sec-4.html |
| |
| #define WM_HINTS_FLAG_INPUT (1L << 0) |
| #define WM_HINTS_FLAG_STATE (1L << 1) |
| #define WM_HINTS_FLAG_ICON_PIXMAP (1L << 2) |
| #define WM_HINTS_FLAG_ICON_WINDOW (1L << 3) |
| #define WM_HINTS_FLAG_ICON_POSITION (1L << 4) |
| #define WM_HINTS_FLAG_ICON_MASK (1L << 5) |
| #define WM_HINTS_FLAG_WINDOW_GROUP (1L << 6) |
| #define WM_HINTS_FLAG_MESSAGE (1L << 7) |
| #define WM_HINTS_FLAG_URGENCY (1L << 8) |
| |
| struct sl_wm_hints { |
| uint32_t flags; |
| uint32_t input; |
| uint32_t initiali_state; |
| xcb_pixmap_t icon_pixmap; |
| xcb_window_t icon_window; |
| int32_t icon_x; |
| int32_t icon_y; |
| xcb_pixmap_t icon_mask; |
| }; |
| |
| #define MWM_HINTS_FUNCTIONS (1L << 0) |
| #define MWM_HINTS_DECORATIONS (1L << 1) |
| #define MWM_HINTS_INPUT_MODE (1L << 2) |
| #define MWM_HINTS_STATUS (1L << 3) |
| |
| #define MWM_DECOR_ALL (1L << 0) |
| #define MWM_DECOR_BORDER (1L << 1) |
| #define MWM_DECOR_RESIZEH (1L << 2) |
| #define MWM_DECOR_TITLE (1L << 3) |
| #define MWM_DECOR_MENU (1L << 4) |
| #define MWM_DECOR_MINIMIZE (1L << 5) |
| #define MWM_DECOR_MAXIMIZE (1L << 6) |
| |
| struct sl_mwm_hints { |
| uint32_t flags; |
| uint32_t functions; |
| uint32_t decorations; |
| int32_t input_mode; |
| uint32_t status; |
| }; |
| |
| #define NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 |
| #define NET_WM_MOVERESIZE_SIZE_TOP 1 |
| #define NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 |
| #define NET_WM_MOVERESIZE_SIZE_RIGHT 3 |
| #define NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 |
| #define NET_WM_MOVERESIZE_SIZE_BOTTOM 5 |
| #define NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 |
| #define NET_WM_MOVERESIZE_SIZE_LEFT 7 |
| #define NET_WM_MOVERESIZE_MOVE 8 |
| |
| #define NET_WM_STATE_REMOVE 0 |
| #define NET_WM_STATE_ADD 1 |
| #define NET_WM_STATE_TOGGLE 2 |
| |
| #define WM_STATE_WITHDRAWN 0 |
| #define WM_STATE_NORMAL 1 |
| #define WM_STATE_ICONIC 3 |
| |
| #define SEND_EVENT_MASK 0x80 |
| |
| #define MIN_SCALE 0.1 |
| #define MAX_SCALE 10.0 |
| |
| #define MIN_DPI 72 |
| #define MAX_DPI 9600 |
| |
| #define XCURSOR_SIZE_BASE 24 |
| |
| #ifndef UNIX_PATH_MAX |
| #define UNIX_PATH_MAX 108 |
| #endif |
| |
| #define LOCK_SUFFIX ".lock" |
| #define LOCK_SUFFIXLEN 5 |
| |
| // TODO(b/173147612): Use container_token rather than this name. |
| #define DEFAULT_VM_NAME "termina" |
| |
| #define APPLICATION_ID_FORMAT_PREFIX "org.chromium.%s" |
| #define XID_APPLICATION_ID_FORMAT APPLICATION_ID_FORMAT_PREFIX ".xid.%d" |
| #define WM_CLIENT_LEADER_APPLICATION_ID_FORMAT \ |
| APPLICATION_ID_FORMAT_PREFIX ".wmclientleader.%d" |
| #define WM_CLASS_APPLICATION_ID_FORMAT \ |
| APPLICATION_ID_FORMAT_PREFIX ".wmclass.%s" |
| |
| #define MIN_AURA_SHELL_VERSION 6 |
| #define MAX_AURA_SHELL_VERSION 10 |
| |
| // Performs an asprintf operation and checks the result for validity and calls |
| // abort() if there's a failure. Returns a newly allocated string rather than |
| // taking a double pointer argument like asprintf. |
| __attribute__((__format__(__printf__, 1, 0))) static char* sl_xasprintf( |
| const char* fmt, ...) { |
| char* str; |
| va_list args; |
| va_start(args, fmt); |
| int rv = vasprintf(&str, fmt, args); |
| assert(rv >= 0); |
| UNUSED(rv); |
| va_end(args); |
| return str; |
| } |
| |
| struct sl_mmap* sl_mmap_create(int fd, |
| size_t size, |
| size_t bpp, |
| size_t num_planes, |
| size_t offset0, |
| size_t stride0, |
| size_t offset1, |
| size_t stride1, |
| size_t y_ss0, |
| size_t y_ss1) { |
| TRACE_EVENT("shm", "sl_mmap_create"); |
| struct sl_mmap* map = static_cast<sl_mmap*>(malloc(sizeof(*map))); |
| assert(map); |
| map->refcount = 1; |
| map->fd = fd; |
| map->size = size; |
| map->num_planes = num_planes; |
| map->bpp = bpp; |
| map->offset[0] = offset0; |
| map->stride[0] = stride0; |
| map->offset[1] = offset1; |
| map->stride[1] = stride1; |
| map->y_ss[0] = y_ss0; |
| map->y_ss[1] = y_ss1; |
| map->begin_write = NULL; |
| map->end_write = NULL; |
| map->buffer_resource = NULL; |
| map->addr = |
| mmap(NULL, size + offset0, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); |
| errno_assert(map->addr != MAP_FAILED); |
| |
| return map; |
| } |
| |
| struct sl_mmap* sl_mmap_ref(struct sl_mmap* map) { |
| TRACE_EVENT("shm", "sl_mmap_ref"); |
| map->refcount++; |
| return map; |
| } |
| |
| void sl_mmap_unref(struct sl_mmap* map) { |
| TRACE_EVENT("shm", "sl_mmap_unref"); |
| if (map->refcount-- == 1) { |
| munmap(map->addr, map->size + map->offset[0]); |
| if (map->fd != -1) |
| close(map->fd); |
| free(map); |
| } |
| } |
| |
| struct sl_sync_point* sl_sync_point_create(int fd) { |
| TRACE_EVENT("sync", "sl_sync_point_create"); |
| struct sl_sync_point* sync_point = |
| static_cast<sl_sync_point*>(malloc(sizeof(*sync_point))); |
| assert(sync_point); |
| sync_point->fd = fd; |
| sync_point->sync = NULL; |
| |
| return sync_point; |
| } |
| |
| void sl_sync_point_destroy(struct sl_sync_point* sync_point) { |
| TRACE_EVENT("sync", "sl_sync_point_destroy"); |
| close(sync_point->fd); |
| free(sync_point); |
| } |
| |
| static void sl_internal_xdg_shell_ping(void* data, |
| struct zxdg_shell_v6* xdg_shell, |
| uint32_t serial) { |
| TRACE_EVENT("shell", "sl_internal_xdg_shell_ping"); |
| zxdg_shell_v6_pong(xdg_shell, serial); |
| } |
| |
| static const struct zxdg_shell_v6_listener sl_internal_xdg_shell_listener = { |
| sl_internal_xdg_shell_ping}; |
| |
| static void sl_send_configure_notify(struct sl_window* window) { |
| xcb_configure_notify_event_t event = {}; |
| event.response_type = XCB_CONFIGURE_NOTIFY; |
| event.pad0 = 0; |
| event.event = window->id; |
| event.window = window->id; |
| event.above_sibling = XCB_WINDOW_NONE; |
| event.x = static_cast<int16_t>(window->x); |
| event.y = static_cast<int16_t>(window->y); |
| event.width = static_cast<uint16_t>(window->width); |
| event.height = static_cast<uint16_t>(window->height); |
| event.border_width = static_cast<uint16_t>(window->border_width); |
| event.override_redirect = 0; |
| event.pad1 = 0; |
| |
| xcb_send_event(window->ctx->connection, 0, window->id, |
| XCB_EVENT_MASK_STRUCTURE_NOTIFY, |
| reinterpret_cast<char*>(&event)); |
| } |
| |
| static void sl_adjust_window_size_for_screen_size(struct sl_window* window) { |
| TRACE_EVENT("surface", "sl_adjust_window_size_for_screen_size", "id", |
| window->id); |
| struct sl_context* ctx = window->ctx; |
| |
| // Clamp size to screen. |
| window->width = MIN(window->width, ctx->screen->width_in_pixels); |
| window->height = MIN(window->height, ctx->screen->height_in_pixels); |
| } |
| |
| static void sl_adjust_window_position_for_screen_size( |
| struct sl_window* window) { |
| struct sl_context* ctx = window->ctx; |
| |
| // Center horizontally/vertically. |
| window->x = ctx->screen->width_in_pixels / 2 - window->width / 2; |
| window->y = ctx->screen->height_in_pixels / 2 - window->height / 2; |
| } |
| |
| static void sl_configure_window(struct sl_window* window) { |
| TRACE_EVENT("surface", "sl_configure_window", "id", window->id); |
| assert(!window->pending_config.serial); |
| |
| if (window->next_config.mask) { |
| int values[5]; |
| int x = window->x; |
| int y = window->y; |
| int i = 0; |
| |
| xcb_configure_window(window->ctx->connection, window->frame_id, |
| window->next_config.mask, window->next_config.values); |
| |
| if (window->next_config.mask & XCB_CONFIG_WINDOW_X) |
| x = window->next_config.values[i++]; |
| if (window->next_config.mask & XCB_CONFIG_WINDOW_Y) |
| y = window->next_config.values[i++]; |
| if (window->next_config.mask & XCB_CONFIG_WINDOW_WIDTH) |
| window->width = window->next_config.values[i++]; |
| if (window->next_config.mask & XCB_CONFIG_WINDOW_HEIGHT) |
| window->height = window->next_config.values[i++]; |
| if (window->next_config.mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) |
| window->border_width = window->next_config.values[i++]; |
| |
| // Set x/y to origin in case window gravity is not northwest as expected. |
| assert(window->managed); |
| values[0] = 0; |
| values[1] = 0; |
| values[2] = window->width; |
| values[3] = window->height; |
| values[4] = window->border_width; |
| xcb_configure_window( |
| window->ctx->connection, window->id, |
| XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | |
| XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH, |
| values); |
| |
| if (x != window->x || y != window->y) { |
| window->x = x; |
| window->y = y; |
| sl_send_configure_notify(window); |
| } |
| } |
| |
| if (window->managed) { |
| xcb_change_property(window->ctx->connection, XCB_PROP_MODE_REPLACE, |
| window->id, window->ctx->atoms[ATOM_NET_WM_STATE].value, |
| XCB_ATOM_ATOM, 32, window->next_config.states_length, |
| window->next_config.states); |
| } |
| |
| window->pending_config = window->next_config; |
| window->next_config.serial = 0; |
| window->next_config.mask = 0; |
| window->next_config.states_length = 0; |
| } |
| |
| static void sl_set_input_focus(struct sl_context* ctx, |
| struct sl_window* window) { |
| if (window) { |
| xcb_client_message_event_t event; |
| event.response_type = XCB_CLIENT_MESSAGE; |
| event.format = 32; |
| event.window = window->id; |
| event.type = ctx->atoms[ATOM_WM_PROTOCOLS].value; |
| event.data.data32[0] = ctx->atoms[ATOM_WM_TAKE_FOCUS].value; |
| event.data.data32[1] = XCB_CURRENT_TIME; |
| |
| if (!window->managed) |
| return; |
| |
| if (window->focus_model_take_focus) { |
| xcb_send_event(ctx->connection, 0, window->id, XCB_EVENT_MASK_NO_EVENT, |
| reinterpret_cast<char*>(&event)); |
| } |
| |
| xcb_set_input_focus(ctx->connection, XCB_INPUT_FOCUS_NONE, window->id, |
| XCB_CURRENT_TIME); |
| } else { |
| xcb_set_input_focus(ctx->connection, XCB_INPUT_FOCUS_NONE, XCB_NONE, |
| XCB_CURRENT_TIME); |
| } |
| } |
| |
| void sl_restack_windows(struct sl_context* ctx, uint32_t focus_resource_id) { |
| struct sl_window* sibling; |
| uint32_t values[1]; |
| |
| wl_list_for_each(sibling, &ctx->windows, link) { |
| if (!sibling->managed) |
| continue; |
| |
| // Move focus window to the top and all other windows to the bottom. |
| values[0] = sibling->host_surface_id == focus_resource_id |
| ? XCB_STACK_MODE_ABOVE |
| : XCB_STACK_MODE_BELOW; |
| xcb_configure_window(ctx->connection, sibling->frame_id, |
| XCB_CONFIG_WINDOW_STACK_MODE, values); |
| } |
| } |
| |
| void sl_roundtrip(struct sl_context* ctx) { |
| TRACE_EVENT("other", "sl_roundtrip", "id", ctx->application_id); |
| free(xcb_get_input_focus_reply(ctx->connection, |
| xcb_get_input_focus(ctx->connection), NULL)); |
| } |
| |
| int sl_process_pending_configure_acks(struct sl_window* window, |
| struct sl_host_surface* host_surface) { |
| if (!window->pending_config.serial) |
| return 0; |
| |
| if (window->managed && host_surface) { |
| uint32_t width = window->width + window->border_width * 2; |
| uint32_t height = window->height + window->border_width * 2; |
| // Early out if we expect contents to match window size at some point in |
| // the future. |
| if (width != host_surface->contents_width || |
| height != host_surface->contents_height) { |
| return 0; |
| } |
| } |
| |
| if (window->xdg_surface) { |
| zxdg_surface_v6_ack_configure(window->xdg_surface, |
| window->pending_config.serial); |
| } |
| window->pending_config.serial = 0; |
| |
| if (window->next_config.serial) |
| sl_configure_window(window); |
| |
| return 1; |
| } |
| |
| static void sl_internal_xdg_surface_configure( |
| void* data, struct zxdg_surface_v6* xdg_surface, uint32_t serial) { |
| TRACE_EVENT("surface", "sl_internal_xdg_surface_configure"); |
| struct sl_window* window = |
| static_cast<sl_window*>(zxdg_surface_v6_get_user_data(xdg_surface)); |
| |
| window->next_config.serial = serial; |
| if (!window->pending_config.serial) { |
| struct wl_resource* host_resource; |
| struct sl_host_surface* host_surface = NULL; |
| |
| host_resource = |
| wl_client_get_object(window->ctx->client, window->host_surface_id); |
| if (host_resource) |
| host_surface = static_cast<sl_host_surface*>( |
| wl_resource_get_user_data(host_resource)); |
| |
| sl_configure_window(window); |
| |
| if (sl_process_pending_configure_acks(window, host_surface)) { |
| if (host_surface) |
| wl_surface_commit(host_surface->proxy); |
| } |
| } |
| } |
| |
| static const struct zxdg_surface_v6_listener sl_internal_xdg_surface_listener = |
| {sl_internal_xdg_surface_configure}; |
| |
| static void sl_internal_xdg_toplevel_configure( |
| void* data, |
| struct zxdg_toplevel_v6* xdg_toplevel, |
| int32_t width, |
| int32_t height, |
| struct wl_array* states) { |
| TRACE_EVENT("other", "sl_internal_xdg_toplevel_configure"); |
| struct sl_window* window = |
| static_cast<sl_window*>(zxdg_toplevel_v6_get_user_data(xdg_toplevel)); |
| int activated = 0; |
| uint32_t* state; |
| int i = 0; |
| |
| if (!window->managed) |
| return; |
| |
| if (width && height) { |
| int32_t width_in_pixels = width * window->ctx->scale; |
| int32_t height_in_pixels = height * window->ctx->scale; |
| int i = 0; |
| |
| window->next_config.mask = XCB_CONFIG_WINDOW_WIDTH | |
| XCB_CONFIG_WINDOW_HEIGHT | |
| XCB_CONFIG_WINDOW_BORDER_WIDTH; |
| if (!(window->size_flags & (US_POSITION | P_POSITION))) { |
| window->next_config.mask |= XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; |
| window->next_config.values[i++] = |
| window->ctx->screen->width_in_pixels / 2 - width_in_pixels / 2; |
| window->next_config.values[i++] = |
| window->ctx->screen->height_in_pixels / 2 - height_in_pixels / 2; |
| } |
| window->next_config.values[i++] = width_in_pixels; |
| window->next_config.values[i++] = height_in_pixels; |
| window->next_config.values[i++] = 0; |
| } |
| |
| window->allow_resize = 1; |
| sl_array_for_each(state, states) { |
| if (*state == ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN) { |
| window->allow_resize = 0; |
| window->next_config.states[i++] = |
| window->ctx->atoms[ATOM_NET_WM_STATE_FULLSCREEN].value; |
| } |
| if (*state == ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED) { |
| window->allow_resize = 0; |
| window->next_config.states[i++] = |
| window->ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value; |
| window->next_config.states[i++] = |
| window->ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value; |
| } |
| if (*state == ZXDG_TOPLEVEL_V6_STATE_ACTIVATED) |
| activated = 1; |
| if (*state == ZXDG_TOPLEVEL_V6_STATE_RESIZING) |
| window->allow_resize = 0; |
| } |
| |
| if (activated != window->activated) { |
| if (activated != (window->ctx->host_focus_window == window)) { |
| window->ctx->host_focus_window = activated ? window : NULL; |
| window->ctx->needs_set_input_focus = 1; |
| } |
| window->activated = activated; |
| } |
| |
| window->next_config.states_length = i; |
| } |
| |
| static void sl_internal_xdg_toplevel_close( |
| void* data, struct zxdg_toplevel_v6* xdg_toplevel) { |
| TRACE_EVENT("other", "sl_internal_xdg_toplevel_close"); |
| struct sl_window* window = |
| static_cast<sl_window*>(zxdg_toplevel_v6_get_user_data(xdg_toplevel)); |
| xcb_client_message_event_t event = {}; |
| event.response_type = XCB_CLIENT_MESSAGE; |
| event.format = 32; |
| event.window = window->id; |
| event.type = window->ctx->atoms[ATOM_WM_PROTOCOLS].value; |
| event.data.data32[0] = window->ctx->atoms[ATOM_WM_DELETE_WINDOW].value; |
| event.data.data32[1] = XCB_CURRENT_TIME; |
| |
| xcb_send_event(window->ctx->connection, 0, window->id, |
| XCB_EVENT_MASK_NO_EVENT, (const char*)&event); |
| } |
| |
| static const struct zxdg_toplevel_v6_listener |
| sl_internal_xdg_toplevel_listener = {sl_internal_xdg_toplevel_configure, |
| sl_internal_xdg_toplevel_close}; |
| |
| static void sl_internal_xdg_popup_configure(void* data, |
| struct zxdg_popup_v6* xdg_popup, |
| int32_t x, |
| int32_t y, |
| int32_t width, |
| int32_t height) {} |
| |
| static void sl_internal_xdg_popup_done(void* data, |
| struct zxdg_popup_v6* zxdg_popup_v6) {} |
| |
| static const struct zxdg_popup_v6_listener sl_internal_xdg_popup_listener = { |
| sl_internal_xdg_popup_configure, sl_internal_xdg_popup_done}; |
| |
| static void sl_window_set_wm_state(struct sl_window* window, int state) { |
| TRACE_EVENT("surface", "sl_window_set_wm_state", "id", window->id); |
| struct sl_context* ctx = window->ctx; |
| uint32_t values[2]; |
| |
| values[0] = state; |
| values[1] = XCB_WINDOW_NONE; |
| |
| xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, window->id, |
| ctx->atoms[ATOM_WM_STATE].value, |
| ctx->atoms[ATOM_WM_STATE].value, 32, 2, values); |
| } |
| |
| void sl_update_application_id(struct sl_context* ctx, |
| struct sl_window* window) { |
| TRACE_EVENT("other", "sl_update_application_id"); |
| if (!window->aura_surface) |
| return; |
| if (ctx->application_id) { |
| zaura_surface_set_application_id(window->aura_surface, ctx->application_id); |
| return; |
| } |
| // Don't set application id for X11 override redirect. This prevents |
| // aura shell from thinking that these are regular application windows |
| // that should appear in application lists. |
| if (!ctx->xwayland || window->managed) { |
| char* application_id_str; |
| if (window->clazz) { |
| application_id_str = sl_xasprintf(WM_CLASS_APPLICATION_ID_FORMAT, |
| ctx->vm_id, window->clazz); |
| } else if (window->client_leader != XCB_WINDOW_NONE) { |
| application_id_str = sl_xasprintf(WM_CLIENT_LEADER_APPLICATION_ID_FORMAT, |
| ctx->vm_id, window->client_leader); |
| } else { |
| application_id_str = |
| sl_xasprintf(XID_APPLICATION_ID_FORMAT, ctx->vm_id, window->id); |
| } |
| |
| zaura_surface_set_application_id(window->aura_surface, application_id_str); |
| free(application_id_str); |
| } |
| } |
| |
| void sl_window_update(struct sl_window* window) { |
| TRACE_EVENT("surface", "sl_window_update", "id", window->id); |
| struct wl_resource* host_resource = NULL; |
| struct sl_host_surface* host_surface; |
| struct sl_context* ctx = window->ctx; |
| struct sl_window* parent = NULL; |
| |
| if (window->host_surface_id) { |
| host_resource = wl_client_get_object(ctx->client, window->host_surface_id); |
| if (host_resource && window->unpaired) { |
| wl_list_remove(&window->link); |
| wl_list_insert(&ctx->windows, &window->link); |
| window->unpaired = 0; |
| } |
| } else if (!window->unpaired) { |
| wl_list_remove(&window->link); |
| wl_list_insert(&ctx->unpaired_windows, &window->link); |
| window->unpaired = 1; |
| } |
| |
| if (!host_resource) { |
| if (window->aura_surface) { |
| zaura_surface_destroy(window->aura_surface); |
| window->aura_surface = NULL; |
| } |
| if (window->xdg_toplevel) { |
| zxdg_toplevel_v6_destroy(window->xdg_toplevel); |
| window->xdg_toplevel = NULL; |
| } |
| if (window->xdg_popup) { |
| zxdg_popup_v6_destroy(window->xdg_popup); |
| window->xdg_popup = NULL; |
| } |
| if (window->xdg_surface) { |
| zxdg_surface_v6_destroy(window->xdg_surface); |
| window->xdg_surface = NULL; |
| } |
| window->realized = 0; |
| return; |
| } |
| |
| host_surface = |
| static_cast<sl_host_surface*>(wl_resource_get_user_data(host_resource)); |
| assert(host_surface); |
| assert(!host_surface->has_role); |
| |
| assert(ctx->xdg_shell); |
| assert(ctx->xdg_shell->internal); |
| |
| if (window->managed) { |
| if (window->transient_for != XCB_WINDOW_NONE) { |
| struct sl_window* sibling; |
| |
| wl_list_for_each(sibling, &ctx->windows, link) { |
| if (sibling->id == window->transient_for) { |
| if (sibling->xdg_toplevel) |
| parent = sibling; |
| break; |
| } |
| } |
| } |
| } |
| |
| // If we have a transient parent, but could not find it in the list of |
| // realized windows, then pick the window that had the last event for the |
| // parent. We update this again when we gain focus, so if we picked the wrong |
| // one it can get corrected at that point (but it's also possible the parent |
| // will never be realized, which is why selecting one here is important). |
| if (!window->managed || |
| (!parent && window->transient_for != XCB_WINDOW_NONE)) { |
| struct sl_window* sibling; |
| uint32_t parent_last_event_serial = 0; |
| |
| wl_list_for_each(sibling, &ctx->windows, link) { |
| struct wl_resource* sibling_host_resource; |
| struct sl_host_surface* sibling_host_surface; |
| |
| if (!sibling->realized) |
| continue; |
| |
| sibling_host_resource = |
| wl_client_get_object(ctx->client, sibling->host_surface_id); |
| if (!sibling_host_resource) |
| continue; |
| |
| // Any parent will do but prefer last event window. |
| sibling_host_surface = static_cast<sl_host_surface*>( |
| wl_resource_get_user_data(sibling_host_resource)); |
| if (parent_last_event_serial > sibling_host_surface->last_event_serial) |
| continue; |
| |
| // Do not use ourselves as the parent. |
| if (sibling->host_surface_id == window->host_surface_id) |
| continue; |
| |
| parent = sibling; |
| parent_last_event_serial = sibling_host_surface->last_event_serial; |
| } |
| } |
| |
| if (!window->depth) { |
| xcb_get_geometry_reply_t* geometry_reply = xcb_get_geometry_reply( |
| ctx->connection, xcb_get_geometry(ctx->connection, window->id), NULL); |
| if (geometry_reply) { |
| window->depth = geometry_reply->depth; |
| free(geometry_reply); |
| } |
| } |
| |
| if (!window->xdg_surface) { |
| window->xdg_surface = zxdg_shell_v6_get_xdg_surface( |
| ctx->xdg_shell->internal, host_surface->proxy); |
| zxdg_surface_v6_set_user_data(window->xdg_surface, window); |
| zxdg_surface_v6_add_listener(window->xdg_surface, |
| &sl_internal_xdg_surface_listener, window); |
| } |
| |
| if (ctx->aura_shell) { |
| uint32_t frame_color; |
| |
| if (!window->aura_surface) { |
| window->aura_surface = zaura_shell_get_aura_surface( |
| ctx->aura_shell->internal, host_surface->proxy); |
| } |
| |
| zaura_surface_set_frame(window->aura_surface, |
| window->decorated |
| ? ZAURA_SURFACE_FRAME_TYPE_NORMAL |
| : window->depth == 32 |
| ? ZAURA_SURFACE_FRAME_TYPE_NONE |
| : ZAURA_SURFACE_FRAME_TYPE_SHADOW); |
| |
| frame_color = window->dark_frame ? ctx->dark_frame_color : ctx->frame_color; |
| zaura_surface_set_frame_colors(window->aura_surface, frame_color, |
| frame_color); |
| zaura_surface_set_startup_id(window->aura_surface, window->startup_id); |
| sl_update_application_id(ctx, window); |
| |
| if (ctx->aura_shell->version >= |
| ZAURA_SURFACE_SET_FULLSCREEN_MODE_SINCE_VERSION) { |
| zaura_surface_set_fullscreen_mode(window->aura_surface, |
| ctx->fullscreen_mode); |
| } |
| } |
| |
| // Always use top-level surface for X11 windows as we can't control when the |
| // window is closed. |
| if (ctx->xwayland || !parent) { |
| if (!window->xdg_toplevel) { |
| window->xdg_toplevel = zxdg_surface_v6_get_toplevel(window->xdg_surface); |
| zxdg_toplevel_v6_set_user_data(window->xdg_toplevel, window); |
| zxdg_toplevel_v6_add_listener(window->xdg_toplevel, |
| &sl_internal_xdg_toplevel_listener, window); |
| } |
| if (parent) |
| zxdg_toplevel_v6_set_parent(window->xdg_toplevel, parent->xdg_toplevel); |
| if (window->name) |
| zxdg_toplevel_v6_set_title(window->xdg_toplevel, window->name); |
| if (window->size_flags & P_MIN_SIZE) { |
| zxdg_toplevel_v6_set_min_size(window->xdg_toplevel, |
| window->min_width / ctx->scale, |
| window->min_height / ctx->scale); |
| } |
| if (window->size_flags & P_MAX_SIZE) { |
| zxdg_toplevel_v6_set_max_size(window->xdg_toplevel, |
| window->max_width / ctx->scale, |
| window->max_height / ctx->scale); |
| } |
| if (window->maximized) { |
| zxdg_toplevel_v6_set_maximized(window->xdg_toplevel); |
| } |
| } else if (!window->xdg_popup) { |
| struct zxdg_positioner_v6* positioner; |
| |
| positioner = zxdg_shell_v6_create_positioner(ctx->xdg_shell->internal); |
| assert(positioner); |
| zxdg_positioner_v6_set_anchor( |
| positioner, |
| ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_LEFT); |
| zxdg_positioner_v6_set_gravity( |
| positioner, |
| ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT); |
| zxdg_positioner_v6_set_anchor_rect( |
| positioner, (window->x - parent->x) / ctx->scale, |
| (window->y - parent->y) / ctx->scale, 1, 1); |
| |
| window->xdg_popup = zxdg_surface_v6_get_popup( |
| window->xdg_surface, parent->xdg_surface, positioner); |
| zxdg_popup_v6_set_user_data(window->xdg_popup, window); |
| zxdg_popup_v6_add_listener(window->xdg_popup, |
| &sl_internal_xdg_popup_listener, window); |
| |
| zxdg_positioner_v6_destroy(positioner); |
| } |
| |
| if ((window->size_flags & (US_POSITION | P_POSITION)) && parent && |
| ctx->aura_shell) { |
| zaura_surface_set_parent(window->aura_surface, parent->aura_surface, |
| (window->x - parent->x) / ctx->scale, |
| (window->y - parent->y) / ctx->scale); |
| } |
| |
| wl_surface_commit(host_surface->proxy); |
| if (host_surface->contents_width && host_surface->contents_height) |
| window->realized = 1; |
| } |
| |
| static void sl_host_buffer_destroy(struct wl_client* client, |
| struct wl_resource* resource) { |
| TRACE_EVENT("surface", "sl_host_buffer_destroy"); |
| wl_resource_destroy(resource); |
| } |
| |
| static const struct wl_buffer_interface sl_buffer_implementation = { |
| sl_host_buffer_destroy}; |
| |
| static void sl_buffer_release(void* data, struct wl_buffer* buffer) { |
| struct sl_host_buffer* host = |
| static_cast<sl_host_buffer*>(wl_buffer_get_user_data(buffer)); |
| |
| TRACE_EVENT("surface", "sl_buffer_release", "resource_id", |
| wl_resource_get_id(host->resource)); |
| |
| wl_buffer_send_release(host->resource); |
| } |
| |
| static const struct wl_buffer_listener sl_buffer_listener = {sl_buffer_release}; |
| |
| static void sl_destroy_host_buffer(struct wl_resource* resource) { |
| TRACE_EVENT("surface", "sl_destroy_host_buffer", "resource_id", |
| wl_resource_get_id(resource)); |
| struct sl_host_buffer* host = |
| static_cast<sl_host_buffer*>(wl_resource_get_user_data(resource)); |
| |
| if (host->proxy) |
| wl_buffer_destroy(host->proxy); |
| if (host->shm_mmap) { |
| host->shm_mmap->buffer_resource = NULL; |
| sl_mmap_unref(host->shm_mmap); |
| } |
| if (host->sync_point) { |
| sl_sync_point_destroy(host->sync_point); |
| } |
| wl_resource_set_user_data(resource, NULL); |
| free(host); |
| } |
| |
| struct sl_host_buffer* sl_create_host_buffer(struct wl_client* client, |
| uint32_t id, |
| struct wl_buffer* proxy, |
| int32_t width, |
| int32_t height) { |
| TRACE_EVENT("surface", "sl_create_host_buffer", "id", id); |
| struct sl_host_buffer* host_buffer = |
| static_cast<sl_host_buffer*>(malloc(sizeof(*host_buffer))); |
| assert(host_buffer); |
| |
| host_buffer->width = width; |
| host_buffer->height = height; |
| host_buffer->resource = |
| wl_resource_create(client, &wl_buffer_interface, 1, id); |
| wl_resource_set_implementation(host_buffer->resource, |
| &sl_buffer_implementation, host_buffer, |
| sl_destroy_host_buffer); |
| host_buffer->shm_mmap = NULL; |
| host_buffer->shm_format = 0; |
| host_buffer->proxy = proxy; |
| if (host_buffer->proxy) { |
| wl_buffer_set_user_data(host_buffer->proxy, host_buffer); |
| wl_buffer_add_listener(host_buffer->proxy, &sl_buffer_listener, |
| host_buffer); |
| } |
| host_buffer->sync_point = NULL; |
| |
| return host_buffer; |
| } |
| |
| static void sl_internal_data_offer_destroy(struct sl_data_offer* host) { |
| TRACE_EVENT("other", "sl_internal_data_offer_destroy"); |
| wl_data_offer_destroy(host->internal); |
| wl_array_release(&host->atoms); |
| wl_array_release(&host->cookies); |
| free(host); |
| } |
| |
| static void sl_set_selection(struct sl_context* ctx, |
| struct sl_data_offer* data_offer) { |
| TRACE_EVENT("other", "sl_set_selection"); |
| if (ctx->selection_data_offer) { |
| sl_internal_data_offer_destroy(ctx->selection_data_offer); |
| ctx->selection_data_offer = NULL; |
| } |
| |
| if (ctx->clipboard_manager) { |
| if (!data_offer) { |
| if (ctx->selection_owner == ctx->selection_window) |
| xcb_set_selection_owner(ctx->connection, XCB_ATOM_NONE, |
| ctx->atoms[ATOM_CLIPBOARD].value, |
| ctx->selection_timestamp); |
| return; |
| } |
| |
| int atoms = data_offer->cookies.size / sizeof(xcb_intern_atom_cookie_t); |
| wl_array_add(&data_offer->atoms, sizeof(xcb_atom_t) * (atoms + 2)); |
| (reinterpret_cast<xcb_atom_t*>(data_offer->atoms.data))[0] = |
| ctx->atoms[ATOM_TARGETS].value; |
| (reinterpret_cast<xcb_atom_t*>(data_offer->atoms.data))[1] = |
| ctx->atoms[ATOM_TIMESTAMP].value; |
| for (int i = 0; i < atoms; i++) { |
| xcb_intern_atom_cookie_t cookie = |
| (reinterpret_cast<xcb_intern_atom_cookie_t*>( |
| data_offer->cookies.data))[i]; |
| xcb_intern_atom_reply_t* reply = |
| xcb_intern_atom_reply(ctx->connection, cookie, NULL); |
| if (reply) { |
| (reinterpret_cast<xcb_atom_t*>(data_offer->atoms.data))[i + 2] = |
| reply->atom; |
| free(reply); |
| } |
| } |
| |
| xcb_set_selection_owner(ctx->connection, ctx->selection_window, |
| ctx->atoms[ATOM_CLIPBOARD].value, XCB_CURRENT_TIME); |
| } |
| |
| ctx->selection_data_offer = data_offer; |
| } |
| |
| static void sl_internal_data_offer_offer(void* data, |
| struct wl_data_offer* data_offer, |
| const char* type) { |
| TRACE_EVENT("other", "sl_internal_data_offer_offer"); |
| struct sl_data_offer* host = static_cast<sl_data_offer*>(data); |
| xcb_intern_atom_cookie_t* cookie = static_cast<xcb_intern_atom_cookie_t*>( |
| wl_array_add(&host->cookies, sizeof(xcb_intern_atom_cookie_t))); |
| *cookie = xcb_intern_atom(host->ctx->connection, 0, strlen(type), type); |
| } |
| |
| static void sl_internal_data_offer_source_actions( |
| void* data, struct wl_data_offer* data_offer, uint32_t source_actions) { |
| TRACE_EVENT("other", "sl_internal_data_offer_source_actions"); |
| } |
| |
| static void sl_internal_data_offer_action(void* data, |
| struct wl_data_offer* data_offer, |
| uint32_t dnd_action) { |
| TRACE_EVENT("other", "sl_internal_data_offer_action"); |
| } |
| |
| static const struct wl_data_offer_listener sl_internal_data_offer_listener = { |
| sl_internal_data_offer_offer, sl_internal_data_offer_source_actions, |
| sl_internal_data_offer_action}; |
| |
| static void sl_internal_data_device_data_offer( |
| void* data, |
| struct wl_data_device* data_device, |
| struct wl_data_offer* data_offer) { |
| struct sl_context* ctx = (struct sl_context*)data; |
| struct sl_data_offer* host_data_offer = |
| static_cast<sl_data_offer*>(malloc(sizeof(*host_data_offer))); |
| assert(host_data_offer); |
| |
| host_data_offer->ctx = ctx; |
| host_data_offer->internal = data_offer; |
| wl_array_init(&host_data_offer->atoms); |
| wl_array_init(&host_data_offer->cookies); |
| |
| wl_data_offer_add_listener(host_data_offer->internal, |
| &sl_internal_data_offer_listener, host_data_offer); |
| } |
| |
| static void sl_internal_data_device_enter(void* data, |
| struct wl_data_device* data_device, |
| uint32_t serial, |
| struct wl_surface* surface, |
| wl_fixed_t x, |
| wl_fixed_t y, |
| struct wl_data_offer* data_offer) {} |
| |
| static void sl_internal_data_device_leave(void* data, |
| struct wl_data_device* data_device) {} |
| |
| static void sl_internal_data_device_motion(void* data, |
| struct wl_data_device* data_device, |
| uint32_t time, |
| wl_fixed_t x, |
| wl_fixed_t y) {} |
| |
| static void sl_internal_data_device_drop(void* data, |
| struct wl_data_device* data_device) {} |
| |
| static void sl_internal_data_device_selection( |
| void* data, |
| struct wl_data_device* data_device, |
| struct wl_data_offer* data_offer) { |
| struct sl_context* ctx = (struct sl_context*)data; |
| struct sl_data_offer* host_data_offer = |
| data_offer |
| ? static_cast<sl_data_offer*>(wl_data_offer_get_user_data(data_offer)) |
| : NULL; |
| |
| sl_set_selection(ctx, host_data_offer); |
| } |
| |
| static const struct wl_data_device_listener sl_internal_data_device_listener = { |
| sl_internal_data_device_data_offer, sl_internal_data_device_enter, |
| sl_internal_data_device_leave, sl_internal_data_device_motion, |
| sl_internal_data_device_drop, sl_internal_data_device_selection}; |
| |
| void sl_host_seat_added(struct sl_host_seat* host) { |
| struct sl_context* ctx = host->seat->ctx; |
| |
| if (ctx->default_seat) |
| return; |
| |
| ctx->default_seat = host; |
| |
| // Get data device for selections. |
| if (ctx->data_device_manager && ctx->data_device_manager->internal) { |
| ctx->selection_data_device = wl_data_device_manager_get_data_device( |
| ctx->data_device_manager->internal, host->proxy); |
| wl_data_device_add_listener(ctx->selection_data_device, |
| &sl_internal_data_device_listener, ctx); |
| } |
| |
| #ifdef GAMEPAD_SUPPORT |
| sl_gaming_seat_add_listener(ctx); |
| #endif |
| } |
| |
| void sl_host_seat_removed(struct sl_host_seat* host) { |
| TRACE_EVENT("other", "sl_host_seat_removed"); |
| if (host->seat->ctx->default_seat == host) |
| host->seat->ctx->default_seat = NULL; |
| } |
| |
| struct sl_global* sl_global_create(struct sl_context* ctx, |
| const struct wl_interface* interface, |
| int version, |
| void* data, |
| wl_global_bind_func_t bind) { |
| TRACE_EVENT("other", "sl_global_create"); |
| struct sl_host_registry* registry; |
| |
| assert(version > 0); |
| assert(version <= interface->version); |
| |
| struct sl_global* global = static_cast<sl_global*>(malloc(sizeof *global)); |
| assert(global); |
| |
| global->ctx = ctx; |
| global->name = ctx->next_global_id++; |
| global->interface = interface; |
| global->version = version; |
| global->data = data; |
| global->bind = bind; |
| wl_list_insert(ctx->globals.prev, &global->link); |
| |
| wl_list_for_each(registry, &ctx->registries, link) { |
| wl_resource_post_event(registry->resource, WL_REGISTRY_GLOBAL, global->name, |
| global->interface->name, global->version); |
| } |
| |
| return global; |
| } |
| |
| static void sl_global_destroy(struct sl_global* global) { |
| TRACE_EVENT("other", "sl_global_destroy"); |
| struct sl_host_registry* registry; |
| |
| wl_list_for_each(registry, &global->ctx->registries, link) |
| wl_resource_post_event(registry->resource, WL_REGISTRY_GLOBAL_REMOVE, |
| global->name); |
| wl_list_remove(&global->link); |
| free(global); |
| } |
| |
| static void sl_registry_handler(void* data, |
| struct wl_registry* registry, |
| uint32_t id, |
| const char* interface, |
| uint32_t version) { |
| struct sl_context* ctx = (struct sl_context*)data; |
| |
| TRACE_EVENT("other", "sl_registry_handler", "id", id); |
| if (strcmp(interface, "wl_compositor") == 0) { |
| struct sl_compositor* compositor = |
| static_cast<sl_compositor*>(malloc(sizeof(struct sl_compositor))); |
| assert(compositor); |
| compositor->ctx = ctx; |
| compositor->id = id; |
| assert(version >= 3); |
| compositor->version = 3; |
| compositor->internal = static_cast<wl_compositor*>(wl_registry_bind( |
| registry, id, &wl_compositor_interface, compositor->version)); |
| assert(!ctx->compositor); |
| ctx->compositor = compositor; |
| compositor->host_global = sl_compositor_global_create(ctx); |
| } else if (strcmp(interface, "wl_subcompositor") == 0) { |
| struct sl_subcompositor* subcompositor = |
| static_cast<sl_subcompositor*>(malloc(sizeof(struct sl_subcompositor))); |
| assert(subcompositor); |
| subcompositor->ctx = ctx; |
| subcompositor->id = id; |
| assert(!ctx->subcompositor); |
| ctx->subcompositor = subcompositor; |
| subcompositor->host_global = sl_subcompositor_global_create(ctx); |
| } else if (strcmp(interface, "wl_shm") == 0) { |
| struct sl_shm* shm = static_cast<sl_shm*>(malloc(sizeof(struct sl_shm))); |
| assert(shm); |
| shm->ctx = ctx; |
| shm->id = id; |
| shm->internal = static_cast<wl_shm*>( |
| wl_registry_bind(registry, id, &wl_shm_interface, 1)); |
| assert(!ctx->shm); |
| ctx->shm = shm; |
| shm->host_global = sl_shm_global_create(ctx); |
| } else if (strcmp(interface, "wl_shell") == 0) { |
| struct sl_shell* shell = |
| static_cast<sl_shell*>(malloc(sizeof(struct sl_shell))); |
| assert(shell); |
| shell->ctx = ctx; |
| shell->id = id; |
| assert(!ctx->shell); |
| ctx->shell = shell; |
| shell->host_global = sl_shell_global_create(ctx); |
| } else if (strcmp(interface, "wl_output") == 0) { |
| struct sl_output* output = |
| static_cast<sl_output*>(malloc(sizeof(struct sl_output))); |
| assert(output); |
| output->ctx = ctx; |
| output->id = id; |
| output->version = MIN(3, version); |
| output->host_global = sl_output_global_create(output); |
| wl_list_insert(&ctx->outputs, &output->link); |
| } else if (strcmp(interface, "wl_seat") == 0) { |
| struct sl_seat* seat = |
| static_cast<sl_seat*>(malloc(sizeof(struct sl_seat))); |
| assert(seat); |
| seat->ctx = ctx; |
| seat->id = id; |
| seat->version = MIN(5, version); |
| seat->last_serial = 0; |
| seat->host_global = sl_seat_global_create(seat); |
| wl_list_insert(&ctx->seats, &seat->link); |
| } else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0) { |
| struct sl_relative_pointer_manager* relative_pointer = |
| static_cast<sl_relative_pointer_manager*>( |
| malloc(sizeof(struct sl_relative_pointer_manager))); |
| assert(relative_pointer); |
| relative_pointer->ctx = ctx; |
| relative_pointer->id = id; |
| relative_pointer->internal = |
| static_cast<zwp_relative_pointer_manager_v1*>(wl_registry_bind( |
| registry, id, &zwp_relative_pointer_manager_v1_interface, 1)); |
| assert(!ctx->relative_pointer_manager); |
| ctx->relative_pointer_manager = relative_pointer; |
| relative_pointer->host_global = |
| sl_relative_pointer_manager_global_create(ctx); |
| } else if (strcmp(interface, "zwp_pointer_constraints_v1") == 0) { |
| struct sl_pointer_constraints* pointer_constraints = |
| static_cast<sl_pointer_constraints*>( |
| malloc(sizeof(struct sl_pointer_constraints))); |
| assert(pointer_constraints); |
| pointer_constraints->ctx = ctx; |
| pointer_constraints->id = id; |
| pointer_constraints->internal = |
| static_cast<zwp_pointer_constraints_v1*>(wl_registry_bind( |
| registry, id, &zwp_pointer_constraints_v1_interface, 1)); |
| assert(!ctx->pointer_constraints); |
| ctx->pointer_constraints = pointer_constraints; |
| pointer_constraints->host_global = |
| sl_pointer_constraints_global_create(ctx); |
| } else if (strcmp(interface, "wl_data_device_manager") == 0) { |
| struct sl_data_device_manager* data_device_manager = |
| static_cast<sl_data_device_manager*>( |
| malloc(sizeof(struct sl_data_device_manager))); |
| assert(data_device_manager); |
| data_device_manager->ctx = ctx; |
| data_device_manager->id = id; |
| data_device_manager->version = MIN(3, version); |
| data_device_manager->internal = NULL; |
| data_device_manager->host_global = NULL; |
| assert(!ctx->data_device_manager); |
| ctx->data_device_manager = data_device_manager; |
| if (ctx->xwayland) { |
| data_device_manager->internal = static_cast<wl_data_device_manager*>( |
| wl_registry_bind(registry, id, &wl_data_device_manager_interface, |
| data_device_manager->version)); |
| } else { |
| data_device_manager->host_global = |
| sl_data_device_manager_global_create(ctx); |
| } |
| } else if (strcmp(interface, "zxdg_shell_v6") == 0) { |
| struct sl_xdg_shell* xdg_shell = |
| static_cast<sl_xdg_shell*>(malloc(sizeof(struct sl_xdg_shell))); |
| assert(xdg_shell); |
| xdg_shell->ctx = ctx; |
| xdg_shell->id = id; |
| xdg_shell->internal = NULL; |
| xdg_shell->host_global = NULL; |
| assert(!ctx->xdg_shell); |
| ctx->xdg_shell = xdg_shell; |
| if (ctx->xwayland) { |
| xdg_shell->internal = static_cast<zxdg_shell_v6*>( |
| wl_registry_bind(registry, id, &zxdg_shell_v6_interface, 1)); |
| zxdg_shell_v6_add_listener(xdg_shell->internal, |
| &sl_internal_xdg_shell_listener, NULL); |
| } else { |
| xdg_shell->host_global = sl_xdg_shell_global_create(ctx); |
| } |
| } else if (strcmp(interface, "zaura_shell") == 0) { |
| if (version >= MIN_AURA_SHELL_VERSION) { |
| struct sl_aura_shell* aura_shell = |
| static_cast<sl_aura_shell*>(malloc(sizeof(struct sl_aura_shell))); |
| assert(aura_shell); |
| aura_shell->ctx = ctx; |
| aura_shell->id = id; |
| aura_shell->version = MIN(MAX_AURA_SHELL_VERSION, version); |
| aura_shell->host_gtk_shell_global = NULL; |
| aura_shell->internal = static_cast<zaura_shell*>(wl_registry_bind( |
| registry, id, &zaura_shell_interface, aura_shell->version)); |
| assert(!ctx->aura_shell); |
| ctx->aura_shell = aura_shell; |
| aura_shell->host_gtk_shell_global = sl_gtk_shell_global_create(ctx); |
| } |
| } else if (strcmp(interface, "wp_viewporter") == 0) { |
| struct sl_viewporter* viewporter = |
| static_cast<sl_viewporter*>(malloc(sizeof(struct sl_viewporter))); |
| assert(viewporter); |
| viewporter->ctx = ctx; |
| viewporter->id = id; |
| viewporter->host_viewporter_global = NULL; |
| viewporter->internal = static_cast<wp_viewporter*>( |
| wl_registry_bind(registry, id, &wp_viewporter_interface, 1)); |
| assert(!ctx->viewporter); |
| ctx->viewporter = viewporter; |
| viewporter->host_viewporter_global = sl_viewporter_global_create(ctx); |
| // Allow non-integer scale. |
| ctx->scale = MIN(MAX_SCALE, MAX(MIN_SCALE, ctx->desired_scale)); |
| } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { |
| struct sl_linux_dmabuf* linux_dmabuf = |
| static_cast<sl_linux_dmabuf*>(malloc(sizeof(struct sl_linux_dmabuf))); |
| assert(linux_dmabuf); |
| linux_dmabuf->ctx = ctx; |
| linux_dmabuf->id = id; |
| linux_dmabuf->version = MIN(2, version); |
| linux_dmabuf->internal = static_cast<zwp_linux_dmabuf_v1*>(wl_registry_bind( |
| registry, id, &zwp_linux_dmabuf_v1_interface, linux_dmabuf->version)); |
| assert(!ctx->linux_dmabuf); |
| ctx->linux_dmabuf = linux_dmabuf; |
| linux_dmabuf->host_drm_global = sl_drm_global_create(ctx); |
| } else if (strcmp(interface, "zcr_keyboard_extension_v1") == 0) { |
| struct sl_keyboard_extension* keyboard_extension = |
| static_cast<sl_keyboard_extension*>( |
| malloc(sizeof(struct sl_keyboard_extension))); |
| assert(keyboard_extension); |
| keyboard_extension->ctx = ctx; |
| keyboard_extension->id = id; |
| keyboard_extension->internal = |
| static_cast<zcr_keyboard_extension_v1*>(wl_registry_bind( |
| registry, id, &zcr_keyboard_extension_v1_interface, 1)); |
| assert(!ctx->keyboard_extension); |
| ctx->keyboard_extension = keyboard_extension; |
| } else if (strcmp(interface, "zwp_text_input_manager_v1") == 0) { |
| struct sl_text_input_manager* text_input_manager = |
| static_cast<sl_text_input_manager*>( |
| malloc(sizeof(struct sl_text_input_manager))); |
| assert(text_input_manager); |
| text_input_manager->ctx = ctx; |
| text_input_manager->id = id; |
| text_input_manager->internal = |
| static_cast<zwp_text_input_manager_v1*>(wl_registry_bind( |
| registry, id, &zwp_text_input_manager_v1_interface, 1)); |
| text_input_manager->host_global = sl_text_input_manager_global_create(ctx); |
| assert(!ctx->text_input_manager); |
| ctx->text_input_manager = text_input_manager; |
| #ifdef GAMEPAD_SUPPORT |
| } else if (strcmp(interface, "zcr_gaming_input_v2") == 0) { |
| struct sl_gaming_input_manager* gaming_input_manager = |
| static_cast<sl_gaming_input_manager*>( |
| malloc(sizeof(struct sl_gaming_input_manager))); |
| assert(gaming_input_manager); |
| gaming_input_manager->ctx = ctx; |
| gaming_input_manager->id = id; |
| gaming_input_manager->internal = static_cast<zcr_gaming_input_v2*>( |
| wl_registry_bind(registry, id, &zcr_gaming_input_v2_interface, 2)); |
| assert(!ctx->gaming_input_manager); |
| ctx->gaming_input_manager = gaming_input_manager; |
| #endif |
| } |
| } |
| |
| static void sl_registry_remover(void* data, |
| struct wl_registry* registry, |
| uint32_t id) { |
| TRACE_EVENT("other", "sl_registry_remover"); |
| struct sl_context* ctx = (struct sl_context*)data; |
| struct sl_output* output; |
| struct sl_seat* seat; |
| |
| if (ctx->compositor && ctx->compositor->id == id) { |
| sl_global_destroy(ctx->compositor->host_global); |
| wl_compositor_destroy(ctx->compositor->internal); |
| free(ctx->compositor); |
| ctx->compositor = NULL; |
| return; |
| } |
| if (ctx->subcompositor && ctx->subcompositor->id == id) { |
| sl_global_destroy(ctx->subcompositor->host_global); |
| wl_shm_destroy(ctx->shm->internal); |
| free(ctx->subcompositor); |
| ctx->subcompositor = NULL; |
| return; |
| } |
| if (ctx->shm && ctx->shm->id == id) { |
| sl_global_destroy(ctx->shm->host_global); |
| free(ctx->shm); |
| ctx->shm = NULL; |
| return; |
| } |
| if (ctx->shell && ctx->shell->id == id) { |
| sl_global_destroy(ctx->shell->host_global); |
| free(ctx->shell); |
| ctx->shell = NULL; |
| return; |
| } |
| if (ctx->data_device_manager && ctx->data_device_manager->id == id) { |
| if (ctx->data_device_manager->host_global) |
| sl_global_destroy(ctx->data_device_manager->host_global); |
| if (ctx->data_device_manager->internal) |
| wl_data_device_manager_destroy(ctx->data_device_manager->internal); |
| free(ctx->data_device_manager); |
| ctx->data_device_manager = NULL; |
| return; |
| } |
| if (ctx->xdg_shell && ctx->xdg_shell->id == id) { |
| if (ctx->xdg_shell->host_global) |
| sl_global_destroy(ctx->xdg_shell->host_global); |
| if (ctx->xdg_shell->internal) |
| zxdg_shell_v6_destroy(ctx->xdg_shell->internal); |
| free(ctx->xdg_shell); |
| ctx->xdg_shell = NULL; |
| return; |
| } |
| if (ctx->aura_shell && ctx->aura_shell->id == id) { |
| if (ctx->aura_shell->host_gtk_shell_global) |
| sl_global_destroy(ctx->aura_shell->host_gtk_shell_global); |
| zaura_shell_destroy(ctx->aura_shell->internal); |
| free(ctx->aura_shell); |
| ctx->aura_shell = NULL; |
| return; |
| } |
| if (ctx->viewporter && ctx->viewporter->id == id) { |
| if (ctx->viewporter->host_viewporter_global) |
| sl_global_destroy(ctx->viewporter->host_viewporter_global); |
| wp_viewporter_destroy(ctx->viewporter->internal); |
| free(ctx->viewporter); |
| ctx->viewporter = NULL; |
| return; |
| } |
| if (ctx->linux_dmabuf && ctx->linux_dmabuf->id == id) { |
| if (ctx->linux_dmabuf->host_drm_global) |
| sl_global_destroy(ctx->linux_dmabuf->host_drm_global); |
| zwp_linux_dmabuf_v1_destroy(ctx->linux_dmabuf->internal); |
| free(ctx->linux_dmabuf); |
| ctx->linux_dmabuf = NULL; |
| return; |
| } |
| if (ctx->keyboard_extension && ctx->keyboard_extension->id == id) { |
| zcr_keyboard_extension_v1_destroy(ctx->keyboard_extension->internal); |
| free(ctx->keyboard_extension); |
| ctx->keyboard_extension = NULL; |
| return; |
| } |
| if (ctx->text_input_manager && ctx->text_input_manager->id == id) { |
| sl_global_destroy(ctx->text_input_manager->host_global); |
| free(ctx->text_input_manager); |
| ctx->text_input_manager = NULL; |
| return; |
| } |
| #ifdef GAMEPAD_SUPPORT |
| if (ctx->gaming_input_manager && ctx->gaming_input_manager->id == id) { |
| zcr_gaming_input_v2_destroy(ctx->gaming_input_manager->internal); |
| free(ctx->gaming_input_manager); |
| ctx->gaming_input_manager = NULL; |
| return; |
| } |
| #endif |
| if (ctx->relative_pointer_manager && |
| ctx->relative_pointer_manager->id == id) { |
| sl_global_destroy(ctx->relative_pointer_manager->host_global); |
| free(ctx->relative_pointer_manager); |
| ctx->relative_pointer_manager = NULL; |
| return; |
| } |
| if (ctx->pointer_constraints && ctx->pointer_constraints->id == id) { |
| sl_global_destroy(ctx->pointer_constraints->host_global); |
| free(ctx->pointer_constraints); |
| ctx->pointer_constraints = NULL; |
| return; |
| } |
| wl_list_for_each(output, &ctx->outputs, link) { |
| if (output->id == id) { |
| sl_global_destroy(output->host_global); |
| wl_list_remove(&output->link); |
| free(output); |
| return; |
| } |
| } |
| wl_list_for_each(seat, &ctx->seats, link) { |
| if (seat->id == id) { |
| sl_global_destroy(seat->host_global); |
| wl_list_remove(&seat->link); |
| free(seat); |
| return; |
| } |
| } |
| |
| // Not reached. |
| assert(0); |
| } |
| |
| static const struct wl_registry_listener sl_registry_listener = { |
| sl_registry_handler, sl_registry_remover}; |
| |
| static int sl_handle_event(int fd, uint32_t mask, void* data) { |
| TRACE_EVENT("other", "sl_handle_event"); |
| struct sl_context* ctx = (struct sl_context*)data; |
| int count = 0; |
| |
| if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { |
| wl_client_flush(ctx->client); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (mask & WL_EVENT_READABLE) |
| count = wl_display_dispatch(ctx->display); |
| if (mask & WL_EVENT_WRITABLE) |
| wl_display_flush(ctx->display); |
| |
| if (mask == 0) { |
| count = wl_display_dispatch_pending(ctx->display); |
| wl_display_flush(ctx->display); |
| } |
| |
| return count; |
| } |
| |
| static void sl_create_window(struct sl_context* ctx, |
| xcb_window_t id, |
| int x, |
| int y, |
| int width, |
| int height, |
| int border_width) { |
| TRACE_EVENT("surface", "sl_create_window"); |
| struct sl_window* window = |
| static_cast<sl_window*>(malloc(sizeof(struct sl_window))); |
| uint32_t values[1]; |
| assert(window); |
| window->ctx = ctx; |
| window->id = id; |
| window->frame_id = XCB_WINDOW_NONE; |
| window->host_surface_id = 0; |
| window->unpaired = 1; |
| window->x = x; |
| window->y = y; |
| window->width = width; |
| window->height = height; |
| window->border_width = border_width; |
| window->depth = 0; |
| window->managed = 0; |
| window->realized = 0; |
| window->activated = 0; |
| window->maximized = 0; |
| window->allow_resize = 1; |
| window->transient_for = XCB_WINDOW_NONE; |
| window->client_leader = XCB_WINDOW_NONE; |
| window->decorated = 0; |
| window->name = NULL; |
| window->clazz = NULL; |
| window->startup_id = NULL; |
| window->dark_frame = 0; |
| window->size_flags = P_POSITION; |
| window->focus_model_take_focus = 0; |
| window->min_width = 0; |
| window->min_height = 0; |
| window->max_width = 0; |
| window->max_height = 0; |
| window->xdg_surface = NULL; |
| window->xdg_toplevel = NULL; |
| window->xdg_popup = NULL; |
| window->aura_surface = NULL; |
| window->next_config.serial = 0; |
| window->next_config.mask = 0; |
| window->next_config.states_length = 0; |
| window->pending_config.serial = 0; |
| window->pending_config.mask = 0; |
| window->pending_config.states_length = 0; |
| wl_list_insert(&ctx->unpaired_windows, &window->link); |
| values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_FOCUS_CHANGE; |
| xcb_change_window_attributes(ctx->connection, window->id, XCB_CW_EVENT_MASK, |
| values); |
| } |
| |
| static void sl_destroy_window(struct sl_window* window) { |
| TRACE_EVENT("surface", "sl_destroy_window"); |
| if (window->frame_id != XCB_WINDOW_NONE) |
| xcb_destroy_window(window->ctx->connection, window->frame_id); |
| |
| if (window->ctx->host_focus_window == window) { |
| window->ctx->host_focus_window = NULL; |
| window->ctx->needs_set_input_focus = 1; |
| } |
| |
| if (window->xdg_popup) |
| zxdg_popup_v6_destroy(window->xdg_popup); |
| if (window->xdg_toplevel) |
| zxdg_toplevel_v6_destroy(window->xdg_toplevel); |
| if (window->xdg_surface) |
| zxdg_surface_v6_destroy(window->xdg_surface); |
| if (window->aura_surface) |
| zaura_surface_destroy(window->aura_surface); |
| |
| if (window->name) |
| free(window->name); |
| if (window->clazz) |
| free(window->clazz); |
| if (window->startup_id) |
| free(window->startup_id); |
| |
| wl_list_remove(&window->link); |
| free(window); |
| } |
| |
| static int sl_is_window(struct sl_window* window, xcb_window_t id) { |
| if (window->id == id) |
| return 1; |
| |
| if (window->frame_id != XCB_WINDOW_NONE) { |
| if (window->frame_id == id) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static struct sl_window* sl_lookup_window(struct sl_context* ctx, |
| xcb_window_t id) { |
| struct sl_window* window; |
| |
| wl_list_for_each(window, &ctx->windows, link) { |
| if (sl_is_window(window, id)) |
| return window; |
| } |
| wl_list_for_each(window, &ctx->unpaired_windows, link) { |
| if (sl_is_window(window, id)) |
| return window; |
| } |
| return NULL; |
| } |
| |
| static int sl_is_our_window(struct sl_context* ctx, xcb_window_t id) { |
| const xcb_setup_t* setup = xcb_get_setup(ctx->connection); |
| |
| return (id & ~setup->resource_id_mask) == setup->resource_id_base; |
| } |
| |
| static void sl_handle_create_notify(struct sl_context* ctx, |
| xcb_create_notify_event_t* event) { |
| if (sl_is_our_window(ctx, event->window)) |
| return; |
| |
| sl_create_window(ctx, event->window, event->x, event->y, event->width, |
| event->height, event->border_width); |
| } |
| |
| static void sl_handle_destroy_notify(struct sl_context* ctx, |
| xcb_destroy_notify_event_t* event) { |
| struct sl_window* window; |
| |
| if (sl_is_our_window(ctx, event->window)) |
| return; |
| |
| window = sl_lookup_window(ctx, event->window); |
| if (!window) |
| return; |
| |
| sl_destroy_window(window); |
| } |
| |
| static void sl_handle_reparent_notify(struct sl_context* ctx, |
| xcb_reparent_notify_event_t* event) { |
| struct sl_window* window; |
| |
| if (event->parent == ctx->screen->root) { |
| int width = 1; |
| int height = 1; |
| int border_width = 0; |
| |
| // Return early if window is already tracked. This happens when we |
| // reparent an unampped window back to the root window. |
| window = sl_lookup_window(ctx, event->window); |
| if (window) |
| return; |
| |
| xcb_get_geometry_reply_t* geometry_reply = xcb_get_geometry_reply( |
| ctx->connection, xcb_get_geometry(ctx->connection, event->window), |
| NULL); |
| |
| if (geometry_reply) { |
| width = geometry_reply->width; |
| height = geometry_reply->height; |
| border_width = geometry_reply->border_width; |
| free(geometry_reply); |
| } |
| sl_create_window(ctx, event->window, event->x, event->y, width, height, |
| border_width); |
| return; |
| } |
| |
| if (sl_is_our_window(ctx, event->parent)) |
| return; |
| |
| window = sl_lookup_window(ctx, event->window); |
| if (!window) |
| return; |
| |
| sl_destroy_window(window); |
| } |
| |
| static void sl_decode_wm_class(struct sl_window* window, |
| xcb_get_property_reply_t* reply) { |
| // WM_CLASS property contains two consecutive null-terminated strings. |
| // These specify the Instance and Class names. If a global app ID is |
| // not set then use Class name for app ID. |
| const char* value = static_cast<char*>(xcb_get_property_value(reply)); |
| int value_length = xcb_get_property_value_length(reply); |
| int instance_length = strnlen(value, value_length); |
| if (value_length > instance_length) { |
| window->clazz = strndup(value + instance_length + 1, |
| value_length - instance_length - 1); |
| } |
| } |
| |
| static void sl_handle_map_request(struct sl_context* ctx, |
| xcb_map_request_event_t* event) { |
| TRACE_EVENT("shm", "sl_handle_map_request"); |
| struct sl_window* window = sl_lookup_window(ctx, event->window); |
| struct { |
| int type; |
| xcb_atom_t atom; |
| } properties[] = { |
| {PROPERTY_WM_NAME, XCB_ATOM_WM_NAME}, |
| {PROPERTY_WM_CLASS, XCB_ATOM_WM_CLASS}, |
| {PROPERTY_WM_TRANSIENT_FOR, XCB_ATOM_WM_TRANSIENT_FOR}, |
| {PROPERTY_WM_NORMAL_HINTS, XCB_ATOM_WM_NORMAL_HINTS}, |
| {PROPERTY_WM_CLIENT_LEADER, ctx->atoms[ATOM_WM_CLIENT_LEADER].value}, |
| {PROPERTY_WM_PROTOCOLS, ctx->atoms[ATOM_WM_PROTOCOLS].value}, |
| {PROPERTY_MOTIF_WM_HINTS, ctx->atoms[ATOM_MOTIF_WM_HINTS].value}, |
| {PROPERTY_NET_STARTUP_ID, ctx->atoms[ATOM_NET_STARTUP_ID].value}, |
| {PROPERTY_NET_WM_STATE, ctx->atoms[ATOM_NET_WM_STATE].value}, |
| {PROPERTY_GTK_THEME_VARIANT, ctx->atoms[ATOM_GTK_THEME_VARIANT].value}, |
| }; |
| xcb_get_geometry_cookie_t geometry_cookie; |
| xcb_get_property_cookie_t property_cookies[ARRAY_SIZE(properties)]; |
| struct sl_wm_size_hints size_hints = {0}; |
| struct sl_mwm_hints mwm_hints = {0}; |
| xcb_atom_t* reply_atoms; |
| bool maximize_h = false, maximize_v = false; |
| uint32_t values[5]; |
| |
| if (!window) |
| return; |
| |
| if (sl_is_our_window(ctx, event->window)) |
| return; |
| |
| window->managed = 1; |
| if (window->frame_id == XCB_WINDOW_NONE) |
| geometry_cookie = xcb_get_geometry(ctx->connection, window->id); |
| |
| for (unsigned i = 0; i < ARRAY_SIZE(properties); ++i) { |
| property_cookies[i] = |
| xcb_get_property(ctx->connection, 0, window->id, properties[i].atom, |
| XCB_ATOM_ANY, 0, 2048); |
| } |
| |
| if (window->frame_id == XCB_WINDOW_NONE) { |
| xcb_get_geometry_reply_t* geometry_reply = |
| xcb_get_geometry_reply(ctx->connection, geometry_cookie, NULL); |
| if (geometry_reply) { |
| window->x = geometry_reply->x; |
| window->y = geometry_reply->y; |
| window->width = geometry_reply->width; |
| window->height = geometry_reply->height; |
| window->depth = geometry_reply->depth; |
| free(geometry_reply); |
| } |
| } |
| |
| free(window->name); |
| window->name = NULL; |
| free(window->clazz); |
| window->clazz = NULL; |
| free(window->startup_id); |
| window->startup_id = NULL; |
| window->transient_for = XCB_WINDOW_NONE; |
| window->client_leader = XCB_WINDOW_NONE; |
| window->decorated = 1; |
| window->size_flags = 0; |
| window->dark_frame = 0; |
| |
| for (unsigned i = 0; i < ARRAY_SIZE(properties); ++i) { |
| xcb_get_property_reply_t* reply = |
| xcb_get_property_reply(ctx->connection, property_cookies[i], NULL); |
| |
| if (!reply) |
| continue; |
| |
| if (reply->type == XCB_ATOM_NONE) { |
| free(reply); |
| continue; |
| } |
| |
| switch (properties[i].type) { |
| case PROPERTY_WM_NAME: |
| window->name = |
| strndup(static_cast<char*>(xcb_get_property_value(reply)), |
| xcb_get_property_value_length(reply)); |
| break; |
| case PROPERTY_WM_CLASS: |
| sl_decode_wm_class(window, reply); |
| break; |
| case PROPERTY_WM_TRANSIENT_FOR: |
| if (xcb_get_property_value_length(reply) >= 4) |
| window->transient_for = |
| *(reinterpret_cast<uint32_t*>(xcb_get_property_value(reply))); |
| break; |
| case PROPERTY_WM_NORMAL_HINTS: |
| if (xcb_get_property_value_length(reply) >= |
| static_cast<int>(sizeof(size_hints))) |
| memcpy(&size_hints, xcb_get_property_value(reply), |
| sizeof(size_hints)); |
| break; |
| case PROPERTY_WM_CLIENT_LEADER: |
| if (xcb_get_property_value_length(reply) >= 4) |
| window->client_leader = |
| *(reinterpret_cast<uint32_t*>(xcb_get_property_value(reply))); |
| break; |
| case PROPERTY_WM_PROTOCOLS: |
| reply_atoms = static_cast<xcb_atom_t*>(xcb_get_property_value(reply)); |
| for (unsigned j = 0; |
| j < xcb_get_property_value_length(reply) / sizeof(xcb_atom_t); |
| ++j) { |
| if (reply_atoms[j] == ctx->atoms[ATOM_WM_TAKE_FOCUS].value) |
| window->focus_model_take_focus = 1; |
| } |
| break; |
| case PROPERTY_MOTIF_WM_HINTS: |
| if (xcb_get_property_value_length(reply) >= |
| static_cast<int>(sizeof(mwm_hints))) |
| memcpy(&mwm_hints, xcb_get_property_value(reply), sizeof(mwm_hints)); |
| break; |
| case PROPERTY_NET_STARTUP_ID: |
| window->startup_id = |
| strndup(static_cast<char*>(xcb_get_property_value(reply)), |
| xcb_get_property_value_length(reply)); |
| break; |
| case PROPERTY_NET_WM_STATE: |
| reply_atoms = static_cast<xcb_atom_t*>(xcb_get_property_value(reply)); |
| for (unsigned j = 0; |
| j < xcb_get_property_value_length(reply) / sizeof(xcb_atom_t); |
| ++j) { |
| if (reply_atoms[j] == |
| ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value) { |
| maximize_h = true; |
| } else if (reply_atoms[j] == |
| ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value) { |
| maximize_v = true; |
| } |
| } |
| // Neither wayland not CrOS support 1D maximizing, so sommelier will |
| // only consider a window maximized if both dimensions are. This |
| // behaviour is consistent with sl_handle_client_message(). |
| window->maximized = maximize_h && maximize_v; |
| break; |
| case PROPERTY_GTK_THEME_VARIANT: |
| if (xcb_get_property_value_length(reply) >= 4) |
| window->dark_frame = !strcmp( |
| static_cast<char*>(xcb_get_property_value(reply)), "dark"); |
| break; |
| default: |
| break; |
| } |
| free(reply); |
| } |
| |
| if (mwm_hints.flags & MWM_HINTS_DECORATIONS) { |
| if (mwm_hints.decorations & MWM_DECOR_ALL) |
| window->decorated = ~mwm_hints.decorations & MWM_DECOR_TITLE; |
| else |
| window->decorated = mwm_hints.decorations & MWM_DECOR_TITLE; |
| } |
| |
| // Allow user/program controlled position for transients. |
| if (window->transient_for) |
| window->size_flags |= size_hints.flags & (US_POSITION | P_POSITION); |
| |
| // If startup ID is not set, then try the client leader window. |
| if (!window->startup_id && window->client_leader) { |
| xcb_get_property_reply_t* reply = xcb_get_property_reply( |
| ctx->connection, |
| xcb_get_property(ctx->connection, 0, window->client_leader, |
| ctx->atoms[ATOM_NET_STARTUP_ID].value, XCB_ATOM_ANY, 0, |
| 2048), |
| NULL); |
| if (reply) { |
| if (reply->type != XCB_ATOM_NONE) { |
| window->startup_id = |
| strndup(static_cast<char*>(xcb_get_property_value(reply)), |
| xcb_get_property_value_length(reply)); |
| } |
| free(reply); |
| } |
| } |
| |
| window->size_flags |= size_hints.flags & (P_MIN_SIZE | P_MAX_SIZE); |
| if (window->size_flags & P_MIN_SIZE) { |
| window->min_width = size_hints.min_width; |
| window->min_height = size_hints.min_height; |
| } |
| if (window->size_flags & P_MAX_SIZE) { |
| window->max_width = size_hints.max_width; |
| window->max_height = size_hints.max_height; |
| } |
| |
| window->border_width = 0; |
| sl_adjust_window_size_for_screen_size(window); |
| if (!(window->size_flags & (US_POSITION | P_POSITION))) |
| sl_adjust_window_position_for_screen_size(window); |
| |
| values[0] = window->width; |
| values[1] = window->height; |
| values[2] = 0; |
| xcb_configure_window(ctx->connection, window->id, |
| XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | |
| XCB_CONFIG_WINDOW_BORDER_WIDTH, |
| values); |
| // This needs to match the frame extents of the X11 frame window used |
| // for reparenting or applications tend to be confused. The actual window |
| // frame size used by the host compositor can be different. |
| values[0] = 0; |
| values[1] = 0; |
| values[2] = 0; |
| values[3] = 0; |
| xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, window->id, |
| ctx->atoms[ATOM_NET_FRAME_EXTENTS].value, |
| XCB_ATOM_CARDINAL, 32, 4, values); |
| |
| // Remove weird gravities. |
| values[0] = XCB_GRAVITY_NORTH_WEST; |
| xcb_change_window_attributes(ctx->connection, window->id, XCB_CW_WIN_GRAVITY, |
| values); |
| |
| if (window->frame_id == XCB_WINDOW_NONE) { |
| int depth = window->depth ? window->depth : ctx->screen->root_depth; |
| |
| values[0] = ctx->screen->black_pixel; |
| values[1] = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | |
| XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; |
| values[2] = ctx->colormaps[depth]; |
| |
| window->frame_id = xcb_generate_id(ctx->connection); |
| xcb_create_window( |
| ctx->connection, depth, window->frame_id, ctx->screen->root, window->x, |
| window->y, window->width, window->height, 0, |
| XCB_WINDOW_CLASS_INPUT_OUTPUT, ctx->visual_ids[depth], |
| XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP, values); |
| values[0] = XCB_STACK_MODE_BELOW; |
| xcb_configure_window(ctx->connection, window->frame_id, |
| XCB_CONFIG_WINDOW_STACK_MODE, values); |
| xcb_reparent_window(ctx->connection, window->id, window->frame_id, 0, 0); |
| } else { |
| values[0] = window->x; |
| values[1] = window->y; |
| values[2] = window->width; |
| values[3] = window->height; |
| values[4] = XCB_STACK_MODE_BELOW; |
| xcb_configure_window( |
| ctx->connection, window->frame_id, |
| XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | |
| XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_STACK_MODE, |
| values); |
| } |
| |
| sl_window_set_wm_state(window, WM_STATE_NORMAL); |
| sl_send_configure_notify(window); |
| |
| xcb_map_window(ctx->connection, window->id); |
| xcb_map_window(ctx->connection, window->frame_id); |
| } |
| |
| static void sl_handle_map_notify(struct sl_context* ctx, |
| xcb_map_notify_event_t* event) {} |
| |
| static void sl_handle_unmap_notify(struct sl_context* ctx, |
| xcb_unmap_notify_event_t* event) { |
| struct sl_window* window; |
| |
| if (sl_is_our_window(ctx, event->window)) |
| return; |
| |
| if (event->response_type & SEND_EVENT_MASK) |
| return; |
| |
| window = sl_lookup_window(ctx, event->window); |
| if (!window) |
| return; |
| |
| if (ctx->host_focus_window == window) { |
| ctx->host_focus_window = NULL; |
| ctx->needs_set_input_focus = 1; |
| } |
| |
| if (window->host_surface_id) { |
| window->host_surface_id = 0; |
| sl_window_update(window); |
| } |
| |
| sl_window_set_wm_state(window, WM_STATE_WITHDRAWN); |
| |
| // Reparent window and destroy frame if it exists. |
| if (window->frame_id != XCB_WINDOW_NONE) { |
| xcb_reparent_window(ctx->connection, window->id, ctx->screen->root, |
| window->x, window->y); |
| xcb_destroy_window(ctx->connection, window->frame_id); |
| window->frame_id = XCB_WINDOW_NONE; |
| } |
| |
| // Reset properties to unmanaged state in case the window transitions to |
| // an override-redirect window. |
| window->managed = 0; |
| window->decorated = 0; |
| window->size_flags = P_POSITION; |
| } |
| |
| static void sl_handle_configure_request(struct sl_context* ctx, |
| xcb_configure_request_event_t* event) { |
| struct sl_window* window = sl_lookup_window(ctx, event->window); |
| int width = window->width; |
| int height = window->height; |
| uint32_t values[7]; |
| |
| if (sl_is_our_window(ctx, event->window)) |
| return; |
| |
| if (!window->managed) { |
| int i = 0; |
| |
| if (event->value_mask & XCB_CONFIG_WINDOW_X) |
| values[i++] = event->x; |
| if (event->value_mask & XCB_CONFIG_WINDOW_Y) |
| values[i++] = event->y; |
| if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH) |
| values[i++] = event->width; |
| if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT) |
| values[i++] = event->height; |
| if (event->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) |
| values[i++] = event->border_width; |
| if (event->value_mask & XCB_CONFIG_WINDOW_SIBLING) |
| values[i++] = event->sibling; |
| if (event->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) |
| values[i++] = event->stack_mode; |
| |
| xcb_configure_window(ctx->connection, window->id, event->value_mask, |
| values); |
| return; |
| } |
| |
| // Ack configure events as satisfying request removes the guarantee |
| // that matching contents will arrive. |
| if (window->xdg_toplevel) { |
| if (window->pending_config.serial) { |
| zxdg_surface_v6_ack_configure(window->xdg_surface, |
| window->pending_config.serial); |
| window->pending_config.serial = 0; |
| window->pending_config.mask = 0; |
| window->pending_config.states_length = 0; |
| } |
| if (window->next_config.serial) { |
| zxdg_surface_v6_ack_configure(window->xdg_surface, |
| window->next_config.serial); |
| window->next_config.serial = 0; |
| window->next_config.mask = 0; |
| window->next_config.states_length = 0; |
| } |
| } |
| |
| if (event->value_mask & XCB_CONFIG_WINDOW_X) |
| window->x = event->x; |
| if (event->value_mask & XCB_CONFIG_WINDOW_Y) |
| window->y = event->y; |
| |
| if (window->allow_resize) { |
| if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH) |
| window->width = event->width; |
| if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT) |
| window->height = event->height; |
| } |
| |
| sl_adjust_window_size_for_screen_size(window); |
| if (window->size_flags & (US_POSITION | P_POSITION)) |
| sl_window_update(window); |
| else |
| sl_adjust_window_position_for_screen_size(window); |
| |
| values[0] = window->x; |
| values[1] = window->y; |
| values[2] = window->width; |
| values[3] = window->height; |
| values[4] = 0; |
| xcb_configure_window(ctx->connection, window->frame_id, |
| XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | |
| XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, |
| values); |
| |
| // We need to send a synthetic configure notify if: |
| // - Not changing the size, location, border width. |
| // - Moving the window without resizing it or changing its border width. |
| if (width != window->width || height != window->height || |
| window->border_width) { |
| xcb_configure_window(ctx->connection, window->id, |
| XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | |
| XCB_CONFIG_WINDOW_BORDER_WIDTH, |
| &values[2]); |
| window->border_width = 0; |
| } else { |
| sl_send_configure_notify(window); |
| } |
| } |
| |
| static void sl_handle_configure_notify(struct sl_context* ctx, |
| xcb_configure_notify_event_t* event) { |
| struct sl_window* window; |
| |
| if (sl_is_our_window(ctx, event->window)) |
| return; |
| |
| if (event->window == ctx->screen->root) { |
| xcb_get_geometry_reply_t* geometry_reply = xcb_get_geometry_reply( |
| ctx->connection, xcb_get_geometry(ctx->connection, event->window), |
| NULL); |
| int width = ctx->screen->width_in_pixels; |
| int height = ctx->screen->height_in_pixels; |
| |
| if (geometry_reply) { |
| width = geometry_reply->width; |
| height = geometry_reply->height; |
| free(geometry_reply); |
| } |
| |
| if (width == ctx->screen->width_in_pixels || |
| height == ctx->screen->height_in_pixels) { |
| return; |
| } |
| |
| ctx->screen->width_in_pixels = width; |
| ctx->screen->height_in_pixels = height; |
| |
| // Re-center managed windows. |
| wl_list_for_each(window, &ctx->windows, link) { |
| int x, y; |
| |
| if (window->size_flags & (US_POSITION | P_POSITION)) |
| continue; |
| |
| x = window->x; |
| y = window->y; |
| sl_adjust_window_position_for_screen_size(window); |
| if (window->x != x || window->y != y) { |
| uint32_t values[2]; |
| |
| values[0] = window->x; |
| values[1] = window->y; |
| xcb_configure_window(ctx->connection, window->frame_id, |
| XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values); |
| sl_send_configure_notify(window); |
| } |
| } |
| return; |
| } |
| |
| window = sl_lookup_window(ctx, event->window); |
| if (!window) |
| return; |
| |
| if (window->managed) |
| return; |
| |
| window->width = event->width; |
| window->height = event->height; |
| window->border_width = event->border_width; |
| if (event->x != window->x || event->y != window->y) { |
| window->x = event->x; |
| window->y = event->y; |
| sl_window_update(window); |
| } |
| } |
| |
| static uint32_t sl_resize_edge(int net_wm_moveresize_size) { |
| switch (net_wm_moveresize_size) { |
| case NET_WM_MOVERESIZE_SIZE_TOPLEFT: |
| return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_LEFT; |
| case NET_WM_MOVERESIZE_SIZE_TOP: |
| return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP; |
| case NET_WM_MOVERESIZE_SIZE_TOPRIGHT: |
| return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_RIGHT; |
| case NET_WM_MOVERESIZE_SIZE_RIGHT: |
| return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_RIGHT; |
| case NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT: |
| return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_RIGHT; |
| case NET_WM_MOVERESIZE_SIZE_BOTTOM: |
| return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM; |
| case NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT: |
| return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_LEFT; |
| case NET_WM_MOVERESIZE_SIZE_LEFT: |
| return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_LEFT; |
| default: |
| return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_NONE; |
| } |
| } |
| |
| static void sl_request_attention(struct sl_context* ctx, |
| struct sl_window* window, |
| bool is_strong_request) { |
| if (!window->aura_surface || |
| ctx->aura_shell->version < ZAURA_SURFACE_DRAW_ATTENTION_SINCE_VERSION) |
| return; |
| if (is_strong_request) { |
| zaura_surface_activate(window->aura_surface); |
| } else { |
| zaura_surface_draw_attention(window->aura_surface); |
| } |
| } |
| |
| static void sl_handle_client_message(struct sl_context* ctx, |
| xcb_client_message_event_t* event) { |
| if (event->type == ctx->atoms[ATOM_WL_SURFACE_ID].value) { |
| struct sl_window *window, *unpaired_window = NULL; |
| |
| wl_list_for_each(window, &ctx->unpaired_windows, link) { |
| if (sl_is_window(window, event->window)) { |
| unpaired_window = window; |
| break; |
| } |
| } |
| |
| if (unpaired_window) { |
| unpaired_window->host_surface_id = event->data.data32[0]; |
| sl_window_update(unpaired_window); |
| } |
| } else if (event->type == ctx->atoms[ATOM_NET_ACTIVE_WINDOW].value) { |
| struct sl_window* window = sl_lookup_window(ctx, event->window); |
| if (window) |
| sl_request_attention(ctx, window, /*is_strong_request=*/true); |
| } else if (event->type == ctx->atoms[ATOM_NET_WM_MOVERESIZE].value) { |
| struct sl_window* window = sl_lookup_window(ctx, event->window); |
| |
| if (window && window->xdg_toplevel) { |
| struct sl_host_seat* seat = window->ctx->default_seat; |
| |
| if (!seat) |
| return; |
| |
| if (event->data.data32[2] == NET_WM_MOVERESIZE_MOVE) { |
| zxdg_toplevel_v6_move(window->xdg_toplevel, seat->proxy, |
| seat->seat->last_serial); |
| } else { |
| uint32_t edge = sl_resize_edge(event->data.data32[2]); |
| |
| if (edge == ZXDG_TOPLEVEL_V6_RESIZE_EDGE_NONE) |
| return; |
| |
| zxdg_toplevel_v6_resize(window->xdg_toplevel, seat->proxy, |
| seat->seat->last_serial, edge); |
| } |
| } |
| } else if (event->type == ctx->atoms[ATOM_NET_WM_STATE].value) { |
| struct sl_window* window = sl_lookup_window(ctx, event->window); |
| |
| if (window && window->xdg_toplevel) { |
| int changed[ATOM_LAST + 1]; |
| uint32_t action = event->data.data32[0]; |
| unsigned i; |
| |
| for (i = 0; i < ARRAY_SIZE(ctx->atoms); ++i) { |
| changed[i] = event->data.data32[1] == ctx->atoms[i].value || |
| event->data.data32[2] == ctx->atoms[i].value; |
| } |
| |
| if (changed[ATOM_NET_WM_STATE_FULLSCREEN]) { |
| if (action == NET_WM_STATE_ADD) |
| zxdg_toplevel_v6_set_fullscreen(window->xdg_toplevel, NULL); |
| else if (action == NET_WM_STATE_REMOVE) |
| zxdg_toplevel_v6_unset_fullscreen(window->xdg_toplevel); |
| } |
| |
| if (changed[ATOM_NET_WM_STATE_MAXIMIZED_VERT] && |
| changed[ATOM_NET_WM_STATE_MAXIMIZED_HORZ]) { |
| if (action == NET_WM_STATE_ADD) |
| zxdg_toplevel_v6_set_maximized(window->xdg_toplevel); |
| else if (action == NET_WM_STATE_REMOVE) |
| zxdg_toplevel_v6_unset_maximized(window->xdg_toplevel); |
| } |
| } |
| } else if (event->type == ctx->atoms[ATOM_WM_CHANGE_STATE].value && |
| event->data.data32[0] == WM_STATE_ICONIC) { |
| struct sl_window* window = sl_lookup_window(ctx, event->window); |
| if (window && window->xdg_toplevel) { |
| zxdg_toplevel_v6_set_minimized(window->xdg_toplevel); |
| } |
| } |
| } |
| |
| static void sl_handle_focus_in(struct sl_context* ctx, |
| xcb_focus_in_event_t* event) { |
| struct sl_window* window = sl_lookup_window(ctx, event->event); |
| if (window && window->transient_for != XCB_WINDOW_NONE) { |
| // Set our parent now as it might not have been set properly when the |
| // window was realized. |
| struct sl_window* parent = sl_lookup_window(ctx, window->transient_for); |
| if (parent && parent->xdg_toplevel && window->xdg_toplevel) |
| zxdg_toplevel_v6_set_parent(window->xdg_toplevel, parent->xdg_toplevel); |
| } |
| } |
| |
| static void sl_handle_focus_out(struct sl_context* ctx, |
| xcb_focus_out_event_t* event) {} |
| |
| int sl_begin_data_source_send(struct sl_context* ctx, |
| int fd, |
| xcb_intern_atom_cookie_t cookie, |
| struct sl_data_source* data_source) { |
| xcb_intern_atom_reply_t* reply = |
| xcb_intern_atom_reply(ctx->connection, cookie, NULL); |
| |
| if (!reply) { |
| close(fd); |
| return 0; |
| } |
| |
| int flags, rv; |
| |
| xcb_convert_selection(ctx->connection, ctx->selection_window, |
| ctx->atoms[ATOM_CLIPBOARD].value, reply->atom, |
| ctx->atoms[ATOM_WL_SELECTION].value, XCB_CURRENT_TIME); |
| |
| flags = fcntl(fd, F_GETFL, 0); |
| rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK); |
| errno_assert(!rv); |
| |
| ctx->selection_data_source_send_fd = fd; |
| free(reply); |
| return 1; |
| } |
| |
| void sl_process_data_source_send_pending_list(struct sl_context* ctx) { |
| while (!wl_list_empty(&ctx->selection_data_source_send_pending)) { |
| struct wl_list* next = ctx->selection_data_source_send_pending.next; |
| struct sl_data_source_send_request* request; |
| request = wl_container_of(next, request, link); |
| wl_list_remove(next); |
| |
| int rv = sl_begin_data_source_send(ctx, request->fd, request->cookie, |
| request->data_source); |
| free(request); |
| if (rv) |
| break; |
| } |
| } |
| |
| static int sl_handle_selection_fd_writable(int fd, uint32_t mask, void* data) { |
| struct sl_context* ctx = static_cast<sl_context*>(data); |
| int bytes, bytes_left; |
| |
| uint8_t* value = static_cast<uint8_t*>( |
| xcb_get_property_value(ctx->selection_property_reply)); |
| bytes_left = xcb_get_property_value_length(ctx->selection_property_reply) - |
| ctx->selection_property_offset; |
| |
| bytes = write(fd, value + ctx->selection_property_offset, bytes_left); |
| if (bytes == -1) { |
| fprintf(stderr, "write error to target fd: %m\n"); |
| close(fd); |
| fd = -1; |
| } else if (bytes == bytes_left) { |
| if (ctx->selection_incremental_transfer) { |
| xcb_delete_property(ctx->connection, ctx->selection_window, |
| ctx->atoms[ATOM_WL_SELECTION].value); |
| } else { |
| close(fd); |
| fd = -1; |
| } |
| } else { |
| ctx->selection_property_offset += bytes; |
| return 1; |
| } |
| |
| free(ctx->selection_property_reply); |
| ctx->selection_property_reply = NULL; |
| if (ctx->selection_send_event_source) { |
| wl_event_source_remove(ctx->selection_send_event_source); |
| ctx->selection_send_event_source = NULL; |
| } |
| if (fd < 0) { |
| ctx->selection_data_source_send_fd = -1; |
| sl_process_data_source_send_pending_list(ctx); |
| } |
| return 1; |
| } |
| |
| static void sl_write_selection_property(struct sl_context* ctx, |
| xcb_get_property_reply_t* reply) { |
| ctx->selection_property_offset = 0; |
| ctx->selection_property_reply = reply; |
| sl_handle_selection_fd_writable(ctx->selection_data_source_send_fd, |
| WL_EVENT_WRITABLE, ctx); |
| |
| if (!ctx->selection_property_reply) |
| return; |
| |
| assert(!ctx->selection_send_event_source); |
| ctx->selection_send_event_source = wl_event_loop_add_fd( |
| wl_display_get_event_loop(ctx->host_display), |
| ctx->selection_data_source_send_fd, WL_EVENT_WRITABLE, |
| sl_handle_selection_fd_writable, ctx); |
| } |
| |
| static void sl_send_selection_notify(struct sl_context* ctx, |
| xcb_atom_t property) { |
| TRACE_EVENT("other", "sl_send_selection_notify"); |
| xcb_selection_notify_event_t event = { |
| .response_type = XCB_SELECTION_NOTIFY, |
| .pad0 = 0, |
| .sequence = 0, |
| .time = ctx->selection_request.time, |
| .requestor = ctx->selection_request.requestor, |
| .selection = ctx->selection_request.selection, |
| .target = ctx->selection_request.target, |
| .property = property}; |
| |
| xcb_send_event(ctx->connection, 0, ctx->selection_request.requestor, |
| XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<char*>(&event)); |
| } |
| |
| static void sl_send_selection_data(struct sl_context* ctx) { |
| assert(!ctx->selection_data_ack_pending); |
| xcb_change_property( |
| ctx->connection, XCB_PROP_MODE_REPLACE, ctx->selection_request.requestor, |
| ctx->selection_request.property, ctx->selection_data_type, |
| /*format=*/8, ctx->selection_data.size, ctx->selection_data.data); |
| ctx->selection_data_ack_pending = 1; |
| ctx->selection_data.size = 0; |
| } |
| |
| static const uint32_t sl_incr_chunk_size = 64 * 1024; |
| |
| static int sl_handle_selection_fd_readable(int fd, uint32_t mask, void* data) { |
| struct sl_context* ctx = static_cast<sl_context*>(data); |
| int bytes, offset, bytes_left; |
| void* p; |
| |
| offset = ctx->selection_data.size; |
| if (ctx->selection_data.size < sl_incr_chunk_size) |
| p = wl_array_add(&ctx->selection_data, sl_incr_chunk_size); |
| else |
| p = reinterpret_cast<char*>(ctx->selection_data.data) + |
| ctx->selection_data.size; |
| bytes_left = ctx->selection_data.alloc - offset; |
| |
| bytes = read(fd, p, bytes_left); |
| if (bytes == -1) { |
| fprintf(stderr, "read error from data source: %m\n"); |
| sl_send_selection_notify(ctx, XCB_ATOM_NONE); |
| ctx->selection_data_offer_receive_fd = -1; |
| close(fd); |
| } else { |
| ctx->selection_data.size = offset + bytes; |
| if (ctx->selection_data.size >= sl_incr_chunk_size) { |
| if (!ctx->selection_incremental_transfer) { |
| ctx->selection_incremental_transfer = 1; |
| xcb_change_property( |
| ctx->connection, XCB_PROP_MODE_REPLACE, |
| ctx->selection_request.requestor, ctx->selection_request.property, |
| ctx->atoms[ATOM_INCR].value, 32, 1, &sl_incr_chunk_size); |
| ctx->selection_data_ack_pending = 1; |
| sl_send_selection_notify(ctx, ctx->selection_request.property); |
| } else if (!ctx->selection_data_ack_pending) { |
| sl_send_selection_data(ctx); |
| } |
| } else if (bytes == 0) { |
| if (!ctx->selection_data_ack_pending) |
| sl_send_selection_data(ctx); |
| if (!ctx->selection_incremental_transfer) { |
| sl_send_selection_notify(ctx, ctx->selection_request.property); |
| ctx->selection_request.requestor = XCB_NONE; |
| wl_array_release(&ctx->selection_data); |
| } |
| xcb_flush(ctx->connection); |
| ctx->selection_data_offer_receive_fd = -1; |
| close(fd); |
| } else { |
| ctx->selection_data.size = offset + bytes; |
| return 1; |
| } |
| } |
| |
| wl_event_source_remove(ctx->selection_event_source); |
| ctx->selection_event_source = NULL; |
| return 1; |
| } |
| |
| static void sl_handle_property_notify(struct sl_context* ctx, |
| xcb_property_notify_event_t* event) { |
| TRACE_EVENT("other", "sl_handle_property_notify"); |
| if (event->atom == XCB_ATOM_WM_NAME) { |
| struct sl_window* window = sl_lookup_window(ctx, event->window); |
| if (!window) |
| return; |
| |
| if (window->name) { |
| free(window->name); |
| window->name = NULL; |
| } |
| |
| if (event->state != XCB_PROPERTY_DELETE) { |
| xcb_get_property_reply_t* reply = xcb_get_property_reply( |
| ctx->connection, |
| xcb_get_property(ctx->connection, 0, window->id, XCB_ATOM_WM_NAME, |
| XCB_ATOM_ANY, 0, 2048), |
| NULL); |
| if (reply) { |
| window->name = |
| strndup(static_cast<char*>(xcb_get_property_value(reply)), |
| xcb_get_property_value_length(reply)); |
| free(reply); |
| } |
| } |
| |
| if (!window->xdg_toplevel) |
| return; |
| |
| if (window->name) { |
| zxdg_toplevel_v6_set_title(window->xdg_toplevel, window->name); |
| } else { |
| zxdg_toplevel_v6_set_title(window->xdg_toplevel, ""); |
| } |
| } else if (event->atom == XCB_ATOM_WM_CLASS) { |
| struct sl_window* window = sl_lookup_window(ctx, event->window); |
| if (!window || event->state == XCB_PROPERTY_DELETE) |
| return; |
| |
| xcb_get_property_cookie_t cookie = |
| xcb_get_property(ctx->connection, 0, window->id, XCB_ATOM_WM_CLASS, |
| XCB_ATOM_ANY, 0, 2048); |
| xcb_get_property_reply_t* reply = |
| xcb_get_property_reply(ctx->connection, cookie, NULL); |
| if (reply) { |
| sl_decode_wm_class(window, reply); |
| free(reply); |
| } |
| sl_update_application_id(ctx, window); |
| } else if (event->atom == XCB_ATOM_WM_NORMAL_HINTS) { |
| struct sl_window* window = sl_lookup_window(ctx, event->window); |
| if (!window) |
| return; |
| |
| window->size_flags &= ~(P_MIN_SIZE | P_MAX_SIZE); |
| |
| if (event->state != XCB_PROPERTY_DELETE) { |
| struct sl_wm_size_hints size_hints = {0}; |
| xcb_get_property_reply_t* reply = xcb_get_property_reply( |
| ctx->connection, |
| xcb_get_property(ctx->connection, 0, window->id, |
| XCB_ATOM_WM_NORMAL_HINTS, XCB_ATOM_ANY, 0, |
| sizeof(size_hints)), |
| NULL); |
| if (reply) { |
| memcpy(&size_hints, xcb_get_property_value(reply), sizeof(size_hints)); |
| free(reply); |
| } |
| |
| window->size_flags |= size_hints.flags & (P_MIN_SIZE | P_MAX_SIZE); |
| if (window->size_flags & P_MIN_SIZE) { |
| window->min_width = size_hints.min_width; |
| window->min_height = size_hints.min_height; |
| } |
| if (window->size_flags & P_MAX_SIZE) { |
| window->max_width = size_hints.max_width; |
| window->max_height = size_hints.max_height; |
| } |
| } |
| |
| if (!window->xdg_toplevel) |
| return; |
| |
| if (window->size_flags & P_MIN_SIZE) { |
| zxdg_toplevel_v6_set_min_size(window->xdg_toplevel, |
| window->min_width / ctx->scale, |
| window->min_height / ctx->scale); |
| } else { |
| zxdg_toplevel_v6_set_min_size(window->xdg_toplevel, 0, 0); |
| } |
| |
| if (window->size_flags & P_MAX_SIZE) { |
| zxdg_toplevel_v6_set_max_size(window->xdg_toplevel, |
| window->max_width / ctx->scale, |
| window->max_height / ctx->scale); |
| } else { |
| zxdg_toplevel_v6_set_max_size(window->xdg_toplevel, 0, 0); |
| } |
| } else if (event->atom == XCB_ATOM_WM_HINTS) { |
| struct sl_window* window = sl_lookup_window(ctx, event->window); |
| if (!window) |
| return; |
| |
| if (event->state == XCB_PROPERTY_DELETE) |
| return; |
| struct sl_wm_hints wm_hints = {0}; |
| xcb_get_property_reply_t* reply = xcb_get_property_reply( |
| ctx->connection, |
| xcb_get_property(ctx->connection, 0, window->id, XCB_ATOM_WM_HINTS, |
| XCB_ATOM_ANY, 0, sizeof(wm_hints)), |
| NULL); |
| |
| if (!reply) |
| return; |
| memcpy(&wm_hints, xcb_get_property_value(reply), sizeof(wm_hints)); |
| free(reply); |
| |
| if (wm_hints.flags & WM_HINTS_FLAG_URGENCY) { |
| sl_request_attention(ctx, window, /*is_strong_request=*/false); |
| } |
| } else if (event->atom == ctx->atoms[ATOM_MOTIF_WM_HINTS].value) { |
| struct sl_window* window = sl_lookup_window(ctx, event->window); |
| if (!window) |
| return; |
| |
| // Managed windows are decorated by default. |
| window->decorated = window->managed; |
| |
| if (event->state != XCB_PROPERTY_DELETE) { |
| struct sl_mwm_hints mwm_hints = {0}; |
| xcb_get_property_reply_t* reply = xcb_get_property_reply( |
| ctx->connection, |
| xcb_get_property(ctx->connection, 0, window->id, |
| ctx->atoms[ATOM_MOTIF_WM_HINTS].value, XCB_ATOM_ANY, |
| 0, sizeof(mwm_hints)), |
| NULL); |
| if (reply) { |
| if (mwm_hints.flags & MWM_HINTS_DECORATIONS) { |
| if (mwm_hints.decorations & MWM_DECOR_ALL) |
| window->decorated = ~mwm_hints.decorations & MWM_DECOR_TITLE; |
| else |
| window->decorated = mwm_hints.decorations & MWM_DECOR_TITLE; |
| } |
| } |
| } |
| |
| if (!window->aura_surface) |
| return; |
| |
| zaura_surface_set_frame(window->aura_surface, |
| window->decorated |
| ? ZAURA_SURFACE_FRAME_TYPE_NORMAL |
| : window->depth == 32 |
| ? ZAURA_SURFACE_FRAME_TYPE_NONE |
| : ZAURA_SURFACE_FRAME_TYPE_SHADOW); |
| } else if (event->atom == ctx->atoms[ATOM_GTK_THEME_VARIANT].value) { |
| struct sl_window* window; |
| uint32_t frame_color; |
| |
| window = sl_lookup_window(ctx, event->window); |
| if (!window) |
| return; |
| |
| window->dark_frame = 0; |
| |
| if (event->state != XCB_PROPERTY_DELETE) { |
| xcb_get_property_reply_t* reply = xcb_get_property_reply( |
| ctx->connection, |
| xcb_get_property(ctx->connection, 0, window->id, |
| ctx->atoms[ATOM_GTK_THEME_VARIANT].value, |
| XCB_ATOM_ANY, 0, 2048), |
| NULL); |
| if (reply) { |
| if (xcb_get_property_value_length(reply) >= 4) |
| window->dark_frame = !strcmp( |
| static_cast<char*>(xcb_get_property_value(reply)), "dark"); |
| free(reply); |
| } |
| } |
| |
| if (!window->aura_surface) |
| return; |
| |
| frame_color = window->dark_frame ? ctx->dark_frame_color : ctx->frame_color; |
| zaura_surface_set_frame_colors(window->aura_surface, frame_color, |
| frame_color); |
| } else if (event->atom == ctx->atoms[ATOM_WL_SELECTION].value) { |
| if (event->window == ctx->selection_window && |
| event->state == XCB_PROPERTY_NEW_VALUE && |
| ctx->selection_incremental_transfer) { |
| xcb_get_property_reply_t* reply = xcb_get_property_reply( |
| ctx->connection, |
| xcb_get_property(ctx->connection, 0, ctx->selection_window, |
| ctx->atoms[ATOM_WL_SELECTION].value, |
| XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff), |
| NULL); |
| |
| if (!reply) |
| return; |
| |
| if (xcb_get_property_value_length(reply) > 0) { |
| sl_write_selection_property(ctx, reply); |
| } else { |
| assert(!ctx->selection_send_event_source); |
| close(ctx->selection_data_source_send_fd); |
| ctx->selection_data_source_send_fd = -1; |
| free(reply); |
| |
| sl_process_data_source_send_pending_list(ctx); |
| } |
| } |
| } else if (event->atom == ctx->selection_request.property) { |
| if (event->window == ctx->selection_request.requestor && |
| event->state == XCB_PROPERTY_DELETE && |
| ctx->selection_incremental_transfer) { |
| int data_size = ctx->selection_data.size; |
| |
| ctx->selection_data_ack_pending = 0; |
| |
| // Handle the case when there's more data to be received. |
| if (ctx->selection_data_offer_receive_fd >= 0) { |
| // Avoid sending empty data until transfer is complete. |
| if (data_size) |
| sl_send_selection_data(ctx); |
| |
| if (!ctx->selection_event_source) { |
| ctx->selection_event_source = wl_event_loop_add_fd( |
| wl_display_get_event_loop(ctx->host_display), |
| ctx->selection_data_offer_receive_fd, WL_EVENT_READABLE, |
| sl_handle_selection_fd_readable, ctx); |
| } |
| return; |
| } |
| |
| sl_send_selection_data(ctx); |
| |
| // Release data if transfer is complete. |
| if (!data_size) { |
| ctx->selection_request.requestor = XCB_NONE; |
| wl_array_release(&ctx->selection_data); |
| } |
| } |
| } |
| } |
| |
| static void sl_internal_data_source_target(void* data, |
| struct wl_data_source* data_source, |
| const char* mime_type) {} |
| |
| static void sl_internal_data_source_send(void* data, |
| struct wl_data_source* data_source, |
| const char* mime_type, |
| int32_t fd) { |
| TRACE_EVENT("other", "sl_internal_data_source_send"); |
| struct sl_data_source* host = static_cast<sl_data_source*>(data); |
| struct sl_context* ctx = host->ctx; |
| |
| xcb_intern_atom_cookie_t cookie = |
| xcb_intern_atom(ctx->connection, false, strlen(mime_type), mime_type); |
| |
| if (ctx->selection_data_source_send_fd < 0) { |
| sl_begin_data_source_send(ctx, fd, cookie, host); |
| } else { |
| struct sl_data_source_send_request* request = |
| static_cast<sl_data_source_send_request*>( |
| malloc(sizeof(struct sl_data_source_send_request))); |
| |
| request->fd = fd; |
| request->cookie = cookie; |
| request->data_source = host; |
| wl_list_insert(&ctx->selection_data_source_send_pending, &request->link); |
| } |
| } |
| |
| static void sl_internal_data_source_cancelled( |
| void* data, struct wl_data_source* data_source) { |
| TRACE_EVENT("other", "sl_internal_data_source_cancelled"); |
| struct sl_data_source* host = static_cast<sl_data_source*>(data); |
| |
| if (host->ctx->selection_data_source == host) |
| host->ctx->selection_data_source = NULL; |
| |
| wl_data_source_destroy(data_source); |
| } |
| |
| static const struct wl_data_source_listener sl_internal_data_source_listener = { |
| sl_internal_data_source_target, sl_internal_data_source_send, |
| sl_internal_data_source_cancelled}; |
| |
| char* sl_copy_atom_name(xcb_get_atom_name_reply_t* reply) { |
| // The string produced by xcb_get_atom_name_name isn't null terminated, so we |
| // have to copy |name_len| bytes into a new buffer and add the null character |
| // ourselves. |
| char* name_start = xcb_get_atom_name_name(reply); |
| int name_len = xcb_get_atom_name_name_length(reply); |
| char* name = static_cast<char*>(malloc(name_len + 1)); |
| memcpy(name, name_start, name_len); |
| name[name_len] = '\0'; |
| return name; |
| } |
| |
| static void sl_get_selection_targets(struct sl_context* ctx) { |
| TRACE_EVENT("other", "sl_get_selection_targets"); |
| struct sl_data_source* data_source = NULL; |
| xcb_get_property_reply_t* reply; |
| xcb_atom_t* value; |
| uint32_t i; |
| |
| reply = xcb_get_property_reply( |
| ctx->connection, |
| xcb_get_property(ctx->connection, 1, ctx->selection_window, |
| ctx->atoms[ATOM_WL_SELECTION].value, |
| XCB_GET_PROPERTY_TYPE_ANY, 0, 4096), |
| NULL); |
| if (!reply) |
| return; |
| |
| if (reply->type != XCB_ATOM_ATOM) { |
| free(reply); |
| return; |
| } |
| |
| if (ctx->data_device_manager) { |
| data_source = static_cast<sl_data_source*>(malloc(sizeof(*data_source))); |
| assert(data_source); |
| |
| data_source->ctx = ctx; |
| data_source->internal = wl_data_device_manager_create_data_source( |
| ctx->data_device_manager->internal); |
| wl_data_source_add_listener(data_source->internal, |
| &sl_internal_data_source_listener, data_source); |
| |
| value = static_cast<xcb_atom_t*>(xcb_get_property_value(reply)); |
| |
| // We need to convert all of the offered target types from X11 atoms to |
| // strings (i.e. getting the names of the atoms). Each conversion requires a |
| // round trip to the X server, but none of the requests depend on each |
| // other. Therefore, we can speed things up by sending out all the requests |
| // as a batch with xcb_get_atom_name, and then read all the replies as a |
| // batch with xcb_get_atom_name_reply. |
| xcb_get_atom_name_cookie_t* atom_name_cookies = |
| static_cast<xcb_get_atom_name_cookie_t*>( |
| malloc(sizeof(xcb_get_atom_name_cookie_t) * reply->value_len)); |
| for (i = 0; i < reply->value_len; i++) { |
| atom_name_cookies[i] = xcb_get_atom_name(ctx->connection, value[i]); |
| } |
| for (i = 0; i < reply->value_len; i++) { |
| xcb_get_atom_name_reply_t* atom_name_reply = |
| xcb_get_atom_name_reply(ctx->connection, atom_name_cookies[i], NULL); |
| if (atom_name_reply) { |
| char* name = sl_copy_atom_name(atom_name_reply); |
| wl_data_source_offer(data_source->internal, name); |
| free(atom_name_reply); |
| free(name); |
| } |
| } |
| free(atom_name_cookies); |
| |
| if (ctx->selection_data_device && ctx->default_seat) { |
| wl_data_device_set_selection(ctx->selection_data_device, |
| data_source->internal, |
| ctx->default_seat->seat->last_serial); |
| } |
| |
| if (ctx->selection_data_source) { |
| wl_data_source_destroy(ctx->selection_data_source->internal); |
| free(ctx->selection_data_source); |
| } |
| ctx->selection_data_source = data_source; |
| } |
| |
| free(reply); |
| } |
| |
| static void sl_get_selection_data(struct sl_context* ctx) { |
| TRACE_EVENT("other", "sl_get_selection_data"); |
| xcb_get_property_reply_t* reply = xcb_get_property_reply( |
| ctx->connection, |
| xcb_get_property(ctx->connection, 1, ctx->selection_window, |
| ctx->atoms[ATOM_WL_SELECTION].value, |
| XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff), |
| NULL); |
| if (!reply) |
| return; |
| |
| if (reply->type == ctx->atoms[ATOM_INCR].value) { |
| ctx->selection_incremental_transfer = 1; |
| free(reply); |
| } else { |
| ctx->selection_incremental_transfer = 0; |
| sl_write_selection_property(ctx, reply); |
| } |
| } |
| |
| static void sl_handle_selection_notify(struct sl_context* ctx, |
| xcb_selection_notify_event_t* event) { |
| if (event->property == XCB_ATOM_NONE) |
| return; |
| |
| if (event->target == ctx->atoms[ATOM_TARGETS].value) |
| sl_get_selection_targets(ctx); |
| else |
| sl_get_selection_data(ctx); |
| } |
| |
| static void sl_send_targets(struct sl_context* ctx) { |
| xcb_change_property( |
| ctx->connection, XCB_PROP_MODE_REPLACE, ctx->selection_request.requestor, |
| ctx->selection_request.property, XCB_ATOM_ATOM, 32, |
| ctx->selection_data_offer->atoms.size / sizeof(xcb_atom_t), |
| ctx->selection_data_offer->atoms.data); |
| |
| sl_send_selection_notify(ctx, ctx->selection_request.property); |
| } |
| |
| static void sl_send_timestamp(struct sl_context* ctx) { |
| xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, |
| ctx->selection_request.requestor, |
| ctx->selection_request.property, XCB_ATOM_INTEGER, 32, 1, |
| &ctx->selection_timestamp); |
| |
| sl_send_selection_notify(ctx, ctx->selection_request.property); |
| } |
| |
| static void sl_send_data(struct sl_context* ctx, xcb_atom_t data_type) { |
| TRACE_EVENT("other", "sl_send_data"); |
| int rv, fd_to_receive, fd_to_wayland; |
| |
| if (!ctx->selection_data_offer) { |
| sl_send_selection_notify(ctx, XCB_ATOM_NONE); |
| return; |
| } |
| |
| if (ctx->selection_event_source) { |
| fprintf(stderr, "error: selection transfer already pending\n"); |
| sl_send_selection_notify(ctx, XCB_ATOM_NONE); |
| return; |
| } |
| |
| ctx->selection_data_type = data_type; |
| |
| // We will need the name of this atom later to tell the wayland server what |
| // type of data to send us, so start the request now. |
| xcb_get_atom_name_cookie_t atom_name_cookie = |
| xcb_get_atom_name(ctx->connection, data_type); |
| |
| wl_array_init(&ctx->selection_data); |
| ctx->selection_data_ack_pending = 0; |
| |
| switch (ctx->data_driver) { |
| case DATA_DRIVER_VIRTWL: { |
| struct virtwl_ioctl_new new_pipe = { |
| .type = VIRTWL_IOCTL_NEW_PIPE_READ, |
| .fd = -1, |
| .flags = 0, |
| }; |
| new_pipe.size = 0; |
| |
| rv = ioctl(ctx->virtwl_fd, VIRTWL_IOCTL_NEW, &new_pipe); |
| if (rv) { |
| fprintf(stderr, "error: failed to create virtwl pipe: %s\n", |
| strerror(errno)); |
| sl_send_selection_notify(ctx, XCB_ATOM_NONE); |
| return; |
| } |
| |
| fd_to_receive = new_pipe.fd; |
| fd_to_wayland = new_pipe.fd; |
| } break; |
| case DATA_DRIVER_NOOP: { |
| int p[2]; |
| |
| rv = pipe2(p, O_CLOEXEC | O_NONBLOCK); |
| errno_assert(!rv); |
| |
| fd_to_receive = p[0]; |
| fd_to_wayland = p[1]; |
| } break; |
| } |
| |
| xcb_get_atom_name_reply_t* atom_name_reply = |
| xcb_get_atom_name_reply(ctx->connection, atom_name_cookie, NULL); |
| if (atom_name_reply) { |
| // If we got the atom name, then send the request to wayland and add our end |
| // of the pipe to the wayland event loop. |
| ctx->selection_data_offer_receive_fd = fd_to_receive; |
| char* name = sl_copy_atom_name(atom_name_reply); |
| wl_data_offer_receive(ctx->selection_data_offer->internal, name, |
| fd_to_wayland); |
| free(atom_name_reply); |
| free(name); |
| |
| ctx->selection_event_source = wl_event_loop_add_fd( |
| wl_display_get_event_loop(ctx->host_display), |
| ctx->selection_data_offer_receive_fd, WL_EVENT_READABLE, |
| sl_handle_selection_fd_readable, ctx); |
| } else { |
| // If getting the atom name failed, notify the requestor that there won't be |
| // any data, and close our end of the pipe. |
| close(fd_to_receive); |
| sl_send_selection_notify(ctx, XCB_ATOM_NONE); |
| } |
| |
| // Close the wayland end of the pipe, now that it's either been sent or not |
| // going to be sent. The VIRTWL driver uses the same fd for both ends of the |
| // pipe, so don't close the fd if both ends are the same. |
| if (fd_to_receive != fd_to_wayland) |
| close(fd_to_wayland); |
| } |
| |
| static void sl_handle_selection_request(struct sl_context* ctx, |
| xcb_selection_request_event_t* event) { |
| ctx->selection_request = *event; |
| ctx->selection_incremental_transfer = 0; |
| |
| if (event->selection == ctx->atoms[ATOM_CLIPBOARD_MANAGER].value) { |
| sl_send_selection_notify(ctx, ctx->selection_request.property); |
| return; |
| } |
| |
| if (event->target == ctx->atoms[ATOM_TARGETS].value) { |
| sl_send_targets(ctx); |
| } else if (event->target == ctx->atoms[ATOM_TIMESTAMP].value) { |
| sl_send_timestamp(ctx); |
| } else { |
| int success = 0; |
| xcb_atom_t* atom; |
| sl_array_for_each(atom, &ctx->selection_data_offer->atoms) { |
| if (event->target == *atom) { |
| success = 1; |
| sl_send_data(ctx, *atom); |
| break; |
| } |
| } |
| if (!success) { |
| sl_send_selection_notify(ctx, XCB_ATOM_NONE); |
| } |
| } |
| } |
| |
| static void sl_handle_xfixes_selection_notify( |
| struct sl_context* ctx, xcb_xfixes_selection_notify_event_t* event) { |
| if (event->selection != ctx->atoms[ATOM_CLIPBOARD].value) |
| return; |
| |
| if (event->owner == XCB_WINDOW_NONE) { |
| // If client selection is gone. Set NULL selection for each seat. |
| if (ctx->selection_owner != ctx->selection_window) { |
| if (ctx->selection_data_device && ctx->default_seat) { |
| wl_data_device_set_selection(ctx->selection_data_device, NULL, |
| ctx->default_seat->seat->last_serial); |
| } |
| } |
| ctx->selection_owner = XCB_WINDOW_NONE; |
| return; |
| } |
| |
| ctx->selection_owner = event->owner; |
| |
| // Save timestamp if it's our selection. |
| if (event->owner == ctx->selection_window) { |
| ctx->selection_timestamp = event->timestamp; |
| return; |
| } |
| |
| ctx->selection_incremental_transfer = 0; |
| xcb_convert_selection(ctx->connection, ctx->selection_window, |
| ctx->atoms[ATOM_CLIPBOARD].value, |
| ctx->atoms[ATOM_TARGETS].value, |
| ctx->atoms[ATOM_WL_SELECTION].value, event->timestamp); |
| } |
| |
| static int sl_handle_x_connection_event(int fd, uint32_t mask, void* data) { |
| TRACE_EVENT("other", "sl_handle_x_connection_event"); |
| struct sl_context* ctx = (struct sl_context*)data; |
| xcb_generic_event_t* event; |
| uint32_t count = 0; |
| |
| if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { |
| fprintf(stderr, "Got error or hangup (mask %d) on X connection, exiting\n", |
| mask); |
| exit(EXIT_SUCCESS); |
| } |
| |
| while ((event = xcb_poll_for_event(ctx->connection))) { |
| switch (event->response_type & ~SEND_EVENT_MASK) { |
| case XCB_CREATE_NOTIFY: |
| sl_handle_create_notify( |
| ctx, reinterpret_cast<xcb_create_notify_event_t*>(event)); |
| break; |
| case XCB_DESTROY_NOTIFY: |
| sl_handle_destroy_notify( |
| ctx, reinterpret_cast<xcb_destroy_notify_event_t*>(event)); |
| break; |
| case XCB_REPARENT_NOTIFY: |
| sl_handle_reparent_notify( |
| ctx, reinterpret_cast<xcb_reparent_notify_event_t*>(event)); |
| break; |
| case XCB_MAP_REQUEST: |
| sl_handle_map_request( |
| ctx, reinterpret_cast<xcb_map_request_event_t*>(event)); |
| break; |
| case XCB_MAP_NOTIFY: |
| sl_handle_map_notify(ctx, |
| reinterpret_cast<xcb_map_notify_event_t*>(event)); |
| break; |
| case XCB_UNMAP_NOTIFY: |
| sl_handle_unmap_notify( |
| ctx, reinterpret_cast<xcb_unmap_notify_event_t*>(event)); |
| break; |
| case XCB_CONFIGURE_REQUEST: |
| sl_handle_configure_request( |
| ctx, reinterpret_cast<xcb_configure_request_event_t*>(event)); |
| break; |
| case XCB_CONFIGURE_NOTIFY: |
| sl_handle_configure_notify( |
| ctx, reinterpret_cast<xcb_configure_notify_event_t*>(event)); |
| break; |
| case XCB_CLIENT_MESSAGE: |
| sl_handle_client_message( |
| ctx, reinterpret_cast<xcb_client_message_event_t*>(event)); |
| break; |
| case XCB_FOCUS_IN: |
| sl_handle_focus_in(ctx, reinterpret_cast<xcb_focus_in_event_t*>(event)); |
| break; |
| case XCB_FOCUS_OUT: |
| sl_handle_focus_out(ctx, |
| reinterpret_cast<xcb_focus_out_event_t*>(event)); |
| break; |
| case XCB_PROPERTY_NOTIFY: |
| sl_handle_property_notify( |
| ctx, reinterpret_cast<xcb_property_notify_event_t*>(event)); |
| break; |
| case XCB_SELECTION_NOTIFY: |
| sl_handle_selection_notify( |
| ctx, reinterpret_cast<xcb_selection_notify_event_t*>(event)); |
| break; |
| case XCB_SELECTION_REQUEST: |
| sl_handle_selection_request( |
| ctx, reinterpret_cast<xcb_selection_request_event_t*>(event)); |
| break; |
| } |
| |
| switch (event->response_type - ctx->xfixes_extension->first_event) { |
| case XCB_XFIXES_SELECTION_NOTIFY: |
| sl_handle_xfixes_selection_notify( |
| ctx, reinterpret_cast<xcb_xfixes_selection_notify_event_t*>(event)); |
| break; |
| } |
| |
| free(event); |
| ++count; |
| } |
| |
| if ((mask & ~WL_EVENT_WRITABLE) == 0) |
| xcb_flush(ctx->connection); |
| |
| return count; |
| } |
| |
| static void sl_set_supported(struct sl_context* ctx) { |
| const xcb_atom_t supported_atoms[] = { |
| ctx->atoms[ATOM_NET_ACTIVE_WINDOW].value, |
| ctx->atoms[ATOM_NET_WM_MOVERESIZE].value, |
| ctx->atoms[ATOM_NET_WM_NAME].value, |
| ctx->atoms[ATOM_NET_WM_STATE].value, |
| ctx->atoms[ATOM_NET_WM_STATE_FULLSCREEN].value, |
| ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value, |
| ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value, |
| // TODO(hollingum): STATE_MODAL and CLIENT_LIST, based on what wlroots |
| // has. |
| }; |
| |
| xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, ctx->screen->root, |
| ctx->atoms[ATOM_NET_SUPPORTED].value, XCB_ATOM_ATOM, 32, |
| sizeof(supported_atoms) / sizeof(xcb_atom_t), |
| supported_atoms); |
| } |
| |
| static void sl_connect(struct sl_context* ctx) { |
| TRACE_EVENT("other", "sl_connect"); |
| const char wm_name[] = "Sommelier"; |
| const xcb_setup_t* setup; |
| xcb_screen_iterator_t screen_iterator; |
| uint32_t values[1]; |
| xcb_void_cookie_t change_attributes_cookie, redirect_subwindows_cookie; |
| xcb_generic_error_t* error; |
| xcb_intern_atom_reply_t* atom_reply; |
| xcb_depth_iterator_t depth_iterator; |
| xcb_xfixes_query_version_reply_t* xfixes_query_version_reply; |
| const xcb_query_extension_reply_t* composite_extension; |
| unsigned i; |
| |
| ctx->connection = xcb_connect_to_fd(ctx->wm_fd, NULL); |
| assert(!xcb_connection_has_error(ctx->connection)); |
| |
| xcb_prefetch_extension_data(ctx->connection, &xcb_xfixes_id); |
| xcb_prefetch_extension_data(ctx->connection, &xcb_composite_id); |
| |
| for (i = 0; i < ARRAY_SIZE(ctx->atoms); ++i) { |
| const char* name = ctx->atoms[i].name; |
| ctx->atoms[i].cookie = |
| xcb_intern_atom(ctx->connection, 0, strlen(name), name); |
| } |
| |
| setup = xcb_get_setup(ctx->connection); |
| screen_iterator = xcb_setup_roots_iterator(setup); |
| ctx->screen = screen_iterator.data; |
| |
| // Select for substructure redirect. |
| values[0] = XCB_EVENT_MASK_STRUCTURE_NOTIFY | |
| XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | |
| XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; |
| change_attributes_cookie = xcb_change_window_attributes( |
| ctx->connection, ctx->screen->root, XCB_CW_EVENT_MASK, values); |
| |
| ctx->connection_event_source = wl_event_loop_add_fd( |
| wl_display_get_event_loop(ctx->host_display), |
| xcb_get_file_descriptor(ctx->connection), WL_EVENT_READABLE, |
| &sl_handle_x_connection_event, ctx); |
| |
| ctx->xfixes_extension = |
| xcb_get_extension_data(ctx->connection, &xcb_xfixes_id); |
| assert(ctx->xfixes_extension->present); |
| |
| xfixes_query_version_reply = xcb_xfixes_query_version_reply( |
| ctx->connection, |
| xcb_xfixes_query_version(ctx->connection, XCB_XFIXES_MAJOR_VERSION, |
| XCB_XFIXES_MINOR_VERSION), |
| NULL); |
| assert(xfixes_query_version_reply); |
| assert(xfixes_query_version_reply->major_version >= 5); |
| free(xfixes_query_version_reply); |
| |
| composite_extension = |
| xcb_get_extension_data(ctx->connection, &xcb_composite_id); |
| assert(composite_extension->present); |
| UNUSED(composite_extension); |
| |
| redirect_subwindows_cookie = xcb_composite_redirect_subwindows_checked( |
| ctx->connection, ctx->screen->root, XCB_COMPOSITE_REDIRECT_MANUAL); |
| |
| // Another window manager should not be running. |
| error = xcb_request_check(ctx->connection, change_attributes_cookie); |
| assert(!error); |
| |
| // Redirecting subwindows of root for compositing should have succeeded. |
| error = xcb_request_check(ctx->connection, redirect_subwindows_cookie); |
| assert(!error); |
| |
| ctx->window = xcb_generate_id(ctx->connection); |
| xcb_create_window(ctx->connection, 0, ctx->window, ctx->screen->root, 0, 0, 1, |
| 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_COPY_FROM_PARENT, 0, |
| NULL); |
| |
| for (i = 0; i < ARRAY_SIZE(ctx->atoms); ++i) { |
| atom_reply = |
| xcb_intern_atom_reply(ctx->connection, ctx->atoms[i].cookie, &error); |
| assert(!error); |
| ctx->atoms[i].value = atom_reply->atom; |
| free(atom_reply); |
| } |
| |
| depth_iterator = xcb_screen_allowed_depths_iterator(ctx->screen); |
| while (depth_iterator.rem > 0) { |
| int depth = depth_iterator.data->depth; |
| if (depth == ctx->screen->root_depth) { |
| ctx->visual_ids[depth] = ctx->screen->root_visual; |
| ctx->colormaps[depth] = ctx->screen->default_colormap; |
| } else { |
| xcb_visualtype_iterator_t visualtype_iterator = |
| xcb_depth_visuals_iterator(depth_iterator.data); |
| |
| ctx->visual_ids[depth] = visualtype_iterator.data->visual_id; |
| ctx->colormaps[depth] = xcb_generate_id(ctx->connection); |
| xcb_create_colormap(ctx->connection, XCB_COLORMAP_ALLOC_NONE, |
| ctx->colormaps[depth], ctx->screen->root, |
| ctx->visual_ids[depth]); |
| } |
| xcb_depth_next(&depth_iterator); |
| } |
| assert(ctx->visual_ids[ctx->screen->root_depth]); |
| |
| if (ctx->clipboard_manager) { |
| values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE; |
| ctx->selection_window = xcb_generate_id(ctx->connection); |
| xcb_create_window(ctx->connection, XCB_COPY_FROM_PARENT, |
| ctx->selection_window, ctx->screen->root, 0, 0, 1, 1, 0, |
| XCB_WINDOW_CLASS_INPUT_OUTPUT, ctx->screen->root_visual, |
| XCB_CW_EVENT_MASK, values); |
| xcb_set_selection_owner(ctx->connection, ctx->selection_window, |
| ctx->atoms[ATOM_CLIPBOARD_MANAGER].value, |
| XCB_CURRENT_TIME); |
| xcb_xfixes_select_selection_input( |
| ctx->connection, ctx->selection_window, |
| ctx->atoms[ATOM_CLIPBOARD].value, |
| XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | |
| XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | |
| XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE); |
| sl_set_selection(ctx, NULL); |
| } |
| |
| xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, ctx->window, |
| ctx->atoms[ATOM_NET_SUPPORTING_WM_CHECK].value, |
| XCB_ATOM_WINDOW, 32, 1, &ctx->window); |
| xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, ctx->window, |
| ctx->atoms[ATOM_NET_WM_NAME].value, |
| ctx->atoms[ATOM_UTF8_STRING].value, 8, strlen(wm_name), |
| wm_name); |
| xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, ctx->screen->root, |
| ctx->atoms[ATOM_NET_SUPPORTING_WM_CHECK].value, |
| XCB_ATOM_WINDOW, 32, 1, &ctx->window); |
| sl_set_supported(ctx); |
| xcb_set_selection_owner(ctx->connection, ctx->window, |
| ctx->atoms[ATOM_WM_S0].value, XCB_CURRENT_TIME); |
| |
| xcb_set_input_focus(ctx->connection, XCB_INPUT_FOCUS_NONE, XCB_NONE, |
| XCB_CURRENT_TIME); |
| xcb_flush(ctx->connection); |
| } |
| |
| static void sl_sd_notify(const char* state) { |
| const char* socket_name; |
| struct msghdr msghdr; |
| struct iovec iovec; |
| struct sockaddr_un addr; |
| int fd; |
| int rv; |
| |
| socket_name = getenv("NOTIFY_SOCKET"); |
| assert(socket_name); |
| |
| fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); |
| errno_assert(fd >= 0); |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.sun_family = AF_UNIX; |
| strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path)); |
| |
| memset(&iovec, 0, sizeof(iovec)); |
| // iovec is just going to be used to send data as part of a const msghdr. |
| iovec.iov_base = const_cast<char*>(state); |
| iovec.iov_len = strlen(state); |
| |
| memset(&msghdr, 0, sizeof(msghdr)); |
| msghdr.msg_name = &addr; |
| msghdr.msg_namelen = |
| offsetof(struct sockaddr_un, sun_path) + strlen(socket_name); |
| msghdr.msg_iov = &iovec; |
| msghdr.msg_iovlen = 1; |
| |
| rv = sendmsg(fd, &msghdr, MSG_NOSIGNAL); |
| errno_assert(rv != -1); |
| } |
| |
| static int sl_handle_sigchld(int signal_number, void* data) { |
| struct sl_context* ctx = (struct sl_context*)data; |
| int status; |
| pid_t pid; |
| |
| while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { |
| if (pid == ctx->child_pid) { |
| ctx->child_pid = -1; |
| if (WIFEXITED(status) && WEXITSTATUS(status)) { |
| fprintf(stderr, "Child exited with status: %d\n", WEXITSTATUS(status)); |
| } |
| if (ctx->exit_with_child) { |
| if (ctx->xwayland_pid >= 0) |
| kill(ctx->xwayland_pid, SIGTERM); |
| } else { |
| // Notify systemd that we are ready to accept connections now that |
| // child process has finished running and all environment is ready. |
| if (ctx->sd_notify) |
| sl_sd_notify(ctx->sd_notify); |
| } |
| } else if (pid == ctx->xwayland_pid) { |
| ctx->xwayland_pid = -1; |
| if (WIFEXITED(status) && WEXITSTATUS(status)) { |
| fprintf(stderr, "Xwayland exited with status: %d\n", |
| WEXITSTATUS(status)); |
| exit(WEXITSTATUS(status)); |
| } |
| } |
| } |
| |
| return 1; |
| } |
| |
| static int sl_handle_sigusr1(int signal_number, void* data) { |
| struct sl_context* ctx = (struct sl_context*)data; |
| fprintf(stderr, "dumping trace %s\n", ctx->trace_filename); |
| dump_trace(ctx->trace_filename); |
| return 1; |
| } |
| |
| static void sl_execvp(const char* file, |
| char* const argv[], |
| int wayland_socked_fd) { |
| if (wayland_socked_fd >= 0) { |
| int fd; |
| fd = dup(wayland_socked_fd); |
| putenv(sl_xasprintf("WAYLAND_SOCKET=%d", fd)); |
| } |
| |
| setenv("SOMMELIER_VERSION", SOMMELIER_VERSION, 1); |
| |
| execvp(file, argv); |
| perror(file); |
| } |
| |
| static void sl_calculate_scale_for_xwayland(struct sl_context* ctx) { |
| struct sl_host_output* output; |
| double default_scale_factor = 1.0; |
| double scale; |
| |
| // Find internal output and determine preferred scale factor. |
| wl_list_for_each(output, &ctx->host_outputs, link) { |
| if (output->internal) { |
| double preferred_scale = |
| sl_output_aura_scale_factor_to_double(output->preferred_scale); |
| |
| if (ctx->aura_shell) { |
| double device_scale_factor = |
| sl_output_aura_scale_factor_to_double(output->device_scale_factor); |
| |
| default_scale_factor = device_scale_factor * preferred_scale; |
| } |
| break; |
| } |
| } |
| |
| // We use the default scale factor multipled by desired scale set by the |
| // user. This gives us HiDPI support by default but the user can still |
| // adjust it if higher or lower density is preferred. |
| scale = ctx->desired_scale * default_scale_factor; |
| |
| // Round to integer scale if wp_viewporter interface is not present. |
| if (!ctx->viewporter) |
| scale = round(scale); |
| |
| // Clamp and set scale. |
| ctx->scale = MIN(MAX_SCALE, MAX(MIN_SCALE, scale)); |
| |
| // Scale affects output state. Send updated output state to xwayland. |
| wl_list_for_each(output, &ctx->host_outputs, link) |
| sl_output_send_host_output_state(output); |
| } |
| |
| static int sl_handle_display_ready_event(int fd, uint32_t mask, void* data) { |
| TRACE_EVENT("surface", "sl_handle_display_ready_event"); |
| struct sl_context* ctx = (struct sl_context*)data; |
| char display_name[9]; |
| int bytes_read = 0; |
| pid_t pid; |
| |
| if (!(mask & WL_EVENT_READABLE)) { |
| fprintf(stderr, |
| "Got error or hangup on display ready connection" |
| " (mask %d), exiting\n", |
| mask); |
| exit(EXIT_SUCCESS); |
| } |
| |
| display_name[0] = ':'; |
| do { |
| int bytes_left = sizeof(display_name) - bytes_read - 1; |
| int bytes; |
| |
| if (!bytes_left) |
| break; |
| |
| bytes = read(fd, &display_name[bytes_read + 1], bytes_left); |
| if (!bytes) |
| break; |
| |
| bytes_read += bytes; |
| } while (display_name[bytes_read] != '\n'); |
| |
| display_name[bytes_read] = '\0'; |
| setenv("DISPLAY", display_name, 1); |
| |
| sl_connect(ctx); |
| |
| wl_event_source_remove(ctx->display_ready_event_source); |
| ctx->display_ready_event_source = NULL; |
| close(fd); |
| |
| // Calculate scale now that the default scale factor is known. This also |
| // happens to workaround an issue in Xwayland where an output update is |
| // needed for DPI to be set correctly. |
| sl_calculate_scale_for_xwayland(ctx); |
| wl_display_flush_clients(ctx->host_display); |
| |
| putenv(sl_xasprintf("XCURSOR_SIZE=%d", |
| static_cast<int>(XCURSOR_SIZE_BASE * ctx->scale + 0.5))); |
| |
| pid = fork(); |
| errno_assert(pid >= 0); |
| if (pid == 0) { |
| sl_execvp(ctx->runprog[0], ctx->runprog, -1); |
| _exit(EXIT_FAILURE); |
| } |
| |
| ctx->child_pid = pid; |
| |
| return 1; |
| } |
| |
| static void sl_sigchld_handler(int signal) { |
| while (waitpid(-1, NULL, WNOHANG) > 0) |
| continue; |
| } |
| |
| static void sl_client_destroy_notify(struct wl_listener* listener, void* data) { |
| exit(0); |
| } |
| |
| static int sl_handle_virtwl_ctx_event(int fd, uint32_t mask, void* data) { |
| TRACE_EVENT("surface", "sl_handle_virtwl_ctx_event"); |
| struct sl_context* ctx = (struct sl_context*)data; |
| uint8_t ioctl_buffer[4096]; |
| struct virtwl_ioctl_txn* ioctl_recv = (struct virtwl_ioctl_txn*)ioctl_buffer; |
| void* recv_data = ioctl_buffer + sizeof(struct virtwl_ioctl_txn); |
| size_t max_recv_size = sizeof(ioctl_buffer) - sizeof(struct virtwl_ioctl_txn); |
| char fd_buffer[CMSG_LEN(sizeof(int) * VIRTWL_SEND_MAX_ALLOCS)]; |
| struct msghdr msg = {0}; |
| struct iovec buffer_iov; |
| ssize_t bytes; |
| int fd_count; |
| int rv; |
| |
| if (!(mask & WL_EVENT_READABLE)) { |
| fprintf(stderr, |
| "Got error or hangup on virtwl ctx fd" |
| " (mask %d), exiting\n", |
| mask); |
| exit(EXIT_SUCCESS); |
| } |
| |
| ioctl_recv->len = max_recv_size; |
| rv = ioctl(fd, VIRTWL_IOCTL_RECV, ioctl_recv); |
| if (rv) { |
| close(ctx->virtwl_socket_fd); |
| ctx->virtwl_socket_fd = -1; |
| return 0; |
| } |
| |
| buffer_iov.iov_base = recv_data; |
| buffer_iov.iov_len = ioctl_recv->len; |
| |
| msg.msg_iov = &buffer_iov; |
| msg.msg_iovlen = 1; |
| msg.msg_control = fd_buffer; |
| |
| // Count how many FDs the kernel gave us. |
| for (fd_count = 0; fd_count < VIRTWL_SEND_MAX_ALLOCS; fd_count++) { |
| if (ioctl_recv->fds[fd_count] < 0) |
| break; |
| } |
| if (fd_count) { |
| struct cmsghdr* cmsg; |
| |
| // Need to set msg_controllen so CMSG_FIRSTHDR will return the first |
| // cmsghdr. We copy every fd we just received from the ioctl into this |
| // cmsghdr. |
| msg.msg_controllen = sizeof(fd_buffer); |
| cmsg = CMSG_FIRSTHDR(&msg); |
| cmsg->cmsg_level = SOL_SOCKET; |
| cmsg->cmsg_type = SCM_RIGHTS; |
| cmsg->cmsg_len = CMSG_LEN(fd_count * sizeof(int)); |
| memcpy(CMSG_DATA(cmsg), ioctl_recv->fds, fd_count * sizeof(int)); |
| msg.msg_controllen = cmsg->cmsg_len; |
| } |
| |
| bytes = sendmsg(ctx->virtwl_socket_fd, &msg, MSG_NOSIGNAL); |
| errno_assert(bytes == ioctl_recv->len); |
| |
| while (fd_count--) |
| close(ioctl_recv->fds[fd_count]); |
| |
| return 1; |
| } |
| |
| static int sl_handle_virtwl_socket_event(int fd, uint32_t mask, void* data) { |
| TRACE_EVENT("surface", "sl_handle_virtwl_socket_event"); |
| struct sl_context* ctx = (struct sl_context*)data; |
| uint8_t ioctl_buffer[4096]; |
| struct virtwl_ioctl_txn* ioctl_send = (struct virtwl_ioctl_txn*)ioctl_buffer; |
| void* send_data = ioctl_buffer + sizeof(struct virtwl_ioctl_txn); |
| size_t max_send_size = sizeof(ioctl_buffer) - sizeof(struct virtwl_ioctl_txn); |
| char fd_buffer[CMSG_LEN(sizeof(int) * VIRTWL_SEND_MAX_ALLOCS)]; |
| struct iovec buffer_iov; |
| struct msghdr msg = {0}; |
| struct cmsghdr* cmsg; |
| ssize_t bytes; |
| int fd_count = 0; |
| int rv; |
| int i; |
| |
| if (!(mask & WL_EVENT_READABLE)) { |
| fprintf(stderr, |
| "Got error or hangup on virtwl socket" |
| " (mask %d), exiting\n", |
| mask); |
| exit(EXIT_SUCCESS); |
| } |
| |
| buffer_iov.iov_base = send_data; |
| buffer_iov.iov_len = max_send_size; |
| |
| msg.msg_iov = &buffer_iov; |
| msg.msg_iovlen = 1; |
| msg.msg_control = fd_buffer; |
| msg.msg_controllen = sizeof(fd_buffer); |
| |
| bytes = recvmsg(ctx->virtwl_socket_fd, &msg, 0); |
| errno_assert(bytes > 0); |
| |
| // If there were any FDs recv'd by recvmsg, there will be some data in the |
| // msg_control buffer. To get the FDs out we iterate all cmsghdr's within and |
| // unpack the FDs if the cmsghdr type is SCM_RIGHTS. |
| for (cmsg = msg.msg_controllen != 0 ? CMSG_FIRSTHDR(&msg) : NULL; cmsg; |
| cmsg = CMSG_NXTHDR(&msg, cmsg)) { |
| size_t cmsg_fd_count; |
| |
| if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) |
| continue; |
| |
| cmsg_fd_count = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); |
| |
| // fd_count will never exceed VIRTWL_SEND_MAX_ALLOCS because the |
| // control message buffer only allocates enough space for that many FDs. |
| memcpy(&ioctl_send->fds[fd_count], CMSG_DATA(cmsg), |
| cmsg_fd_count * sizeof(int)); |
| fd_count += cmsg_fd_count; |
| } |
| |
| for (i = fd_count; i < VIRTWL_SEND_MAX_ALLOCS; ++i) |
| ioctl_send->fds[i] = -1; |
| |
| // The FDs and data were extracted from the recvmsg call into the ioctl_send |
| // structure which we now pass along to the kernel. |
| ioctl_send->len = bytes; |
| rv = ioctl(ctx->virtwl_ctx_fd, VIRTWL_IOCTL_SEND, ioctl_send); |
| errno_assert(!rv); |
| |
| while (fd_count--) |
| close(ioctl_send->fds[fd_count]); |
| |
| return 1; |
| } |
| |
| // Break |str| into a sequence of zero or more nonempty arguments. No more |
| // than |argc| arguments will be added to |argv|. Returns the total number of |
| // argments found in |str|. |
| static int sl_parse_cmd_prefix(char* str, int argc, char const** argv) { |
| char* s = str; |
| int n = 0; |
| int delim = 0; |
| |
| do { |
| // Look for ending delimiter if |delim| is set. |
| if (delim) { |
| if (*s == delim) { |
| delim = 0; |
| *s = '\0'; |
| } |
| ++s; |
| } else { |
| // Skip forward to first non-space character. |
| while (*s == ' ' && *s != '\0') |
| ++s; |
| |
| // Check for quote delimiter. |
| if (*s == '"') { |
| delim = '"'; |
| ++s; |
| } else { |
| delim = ' '; |
| } |
| |
| // Add string to arguments if there's room. |
| if (n < argc) |
| argv[n] = s; |
| |
| ++n; |
| } |
| } while (*s != '\0'); |
| |
| return n; |
| } |
| |
| static void sl_print_usage() { |
| printf( |
| "usage: sommelier [options] [program] [args...]\n\n" |
| "options:\n" |
| " -h, --help\t\t\tPrint this help\n" |
| " -X\t\t\t\tEnable X11 forwarding\n" |
| " --parent\t\t\tRun as parent and spawn child processes\n" |
| " --socket=SOCKET\t\tName of socket to listen on\n" |
| " --display=DISPLAY\t\tWayland display to connect to\n" |
| " --vm-identifier=NAME\t\tName of the VM, used to identify X11 windows\n" |
| " --shm-driver=DRIVER\t\tSHM driver to use (noop, dmabuf, virtwl)\n" |
| " --data-driver=DRIVER\t\tData driver to use (noop, virtwl)\n" |
| " --scale=SCALE\t\t\tScale factor for contents\n" |
| " --dpi=[DPI[,DPI...]]\t\tDPI buckets\n" |
| " --peer-cmd-prefix=PREFIX\tPeer process command line prefix\n" |
| " --accelerators=ACCELERATORS\tList of keyboard accelerators\n" |
| " --application-id=ID\t\tForced application ID for X11 clients\n" |
| " --x-display=DISPLAY\t\tX11 display to listen on\n" |
| " --xwayland-path=PATH\t\tPath to Xwayland executable\n" |
| " --xwayland-gl-driver-path=PATH\tPath to GL drivers for Xwayland\n" |
| " --xwayland-cmd-prefix=PREFIX\tXwayland command line prefix\n" |
| " --no-exit-with-child\t\tKeep process alive after child exists\n" |
| " --no-clipboard-manager\tDisable X11 clipboard manager\n" |
| " --frame-color=COLOR\t\tWindow frame color for X11 clients\n" |
| " --virtwl-device=DEVICE\tVirtWL device to use\n" |
| " --drm-device=DEVICE\t\tDRM device to use\n" |
| " --glamor\t\t\tUse glamor to accelerate X11 clients\n" |
| #ifdef PERFETTO_TRACING |
| " --trace-filename=PATH\t\tPath to Perfetto trace filename\n" |
| " --trace-system\t\tPerfetto trace to system daemon\n" |
| #endif |
| " --fullscreen-mode=MODE\tDefault fullscreen behavior (immersive," |
| " plain)\n"); |
| } |
| |
| static const char* sl_arg_value(const char* arg) { |
| const char* s = strchr(arg, '='); |
| if (!s) { |
| sl_print_usage(); |
| exit(EXIT_FAILURE); |
| } |
| return s + 1; |
| } |
| |
| int main(int argc, char** argv) { |
| struct sl_context ctx = {0}; |
| ctx.runprog = NULL; |
| ctx.display = NULL; |
| ctx.host_display = NULL; |
| ctx.client = NULL; |
| ctx.compositor = NULL; |
| ctx.subcompositor = NULL; |
| ctx.shm = NULL; |
| ctx.shell = NULL; |
| ctx.data_device_manager = NULL; |
| ctx.xdg_shell = NULL; |
| ctx.aura_shell = NULL; |
| ctx.viewporter = NULL; |
| ctx.linux_dmabuf = NULL; |
| ctx.keyboard_extension = NULL; |
| ctx.text_input_manager = NULL; |
| #ifdef GAMEPAD_SUPPORT |
| ctx.gaming_input_manager = NULL; |
| #endif |
| ctx.display_event_source = NULL; |
| ctx.display_ready_event_source = NULL; |
| ctx.sigchld_event_source = NULL; |
| ctx.sigusr1_event_source = NULL; |
| ctx.shm_driver = SHM_DRIVER_NOOP; |
| ctx.data_driver = DATA_DRIVER_NOOP; |
| ctx.wm_fd = -1; |
| ctx.virtwl_fd = -1; |
| ctx.virtwl_ctx_fd = -1; |
| ctx.virtwl_socket_fd = -1; |
| ctx.virtwl_ctx_event_source = NULL; |
| ctx.virtwl_socket_event_source = NULL; |
| ctx.vm_id = DEFAULT_VM_NAME; |
| ctx.drm_device = NULL; |
| ctx.gbm = NULL; |
| ctx.xwayland = 0; |
| ctx.xwayland_pid = -1; |
| ctx.child_pid = -1; |
| ctx.peer_pid = -1; |
| ctx.xkb_context = NULL; |
| ctx.next_global_id = 1; |
| ctx.connection = NULL; |
| ctx.connection_event_source = NULL; |
| ctx.xfixes_extension = NULL; |
| ctx.screen = NULL; |
| ctx.window = 0; |
| ctx.host_focus_window = NULL; |
| ctx.needs_set_input_focus = 0; |
| ctx.desired_scale = 1.0; |
| ctx.scale = 1.0; |
| ctx.application_id = NULL; |
| ctx.exit_with_child = 1; |
| ctx.sd_notify = NULL; |
| ctx.clipboard_manager = 0; |
| ctx.frame_color = 0xffffffff; |
| ctx.dark_frame_color = 0xff000000; |
| ctx.fullscreen_mode = ZAURA_SURFACE_FULLSCREEN_MODE_IMMERSIVE; |
| ctx.default_seat = NULL; |
| ctx.selection_window = XCB_WINDOW_NONE; |
| ctx.selection_owner = XCB_WINDOW_NONE; |
| ctx.selection_incremental_transfer = 0; |
| ctx.selection_request.requestor = XCB_NONE; |
| ctx.selection_request.property = XCB_ATOM_NONE; |
| ctx.selection_timestamp = XCB_CURRENT_TIME; |
| ctx.selection_data_device = NULL; |
| ctx.selection_data_offer = NULL; |
| ctx.selection_data_source = NULL; |
| ctx.selection_data_source_send_fd = -1; |
| ctx.selection_send_event_source = NULL; |
| ctx.selection_property_reply = NULL; |
| ctx.selection_property_offset = 0; |
| ctx.selection_event_source = NULL; |
| ctx.selection_data_offer_receive_fd = -1; |
| ctx.selection_data_ack_pending = 0; |
| ctx.atoms[ATOM_WM_S0] = {"WM_S0"}; |
| ctx.atoms[ATOM_WM_PROTOCOLS] = {"WM_PROTOCOLS"}; |
| ctx.atoms[ATOM_WM_STATE] = {"WM_STATE"}; |
| ctx.atoms[ATOM_WM_CHANGE_STATE] = {"WM_CHANGE_STATE"}; |
| ctx.atoms[ATOM_WM_DELETE_WINDOW] = {"WM_DELETE_WINDOW"}; |
| ctx.atoms[ATOM_WM_TAKE_FOCUS] = {"WM_TAKE_FOCUS"}; |
| ctx.atoms[ATOM_WM_CLIENT_LEADER] = {"WM_CLIENT_LEADER"}; |
| ctx.atoms[ATOM_WL_SURFACE_ID] = {"WL_SURFACE_ID"}; |
| ctx.atoms[ATOM_UTF8_STRING] = {"UTF8_STRING"}; |
| ctx.atoms[ATOM_MOTIF_WM_HINTS] = {"_MOTIF_WM_HINTS"}; |
| ctx.atoms[ATOM_NET_ACTIVE_WINDOW] = {"_NET_ACTIVE_WINDOW"}; |
| ctx.atoms[ATOM_NET_FRAME_EXTENTS] = {"_NET_FRAME_EXTENTS"}; |
| ctx.atoms[ATOM_NET_STARTUP_ID] = {"_NET_STARTUP_ID"}; |
| ctx.atoms[ATOM_NET_SUPPORTED] = {"_NET_SUPPORTED"}; |
| ctx.atoms[ATOM_NET_SUPPORTING_WM_CHECK] = {"_NET_SUPPORTING_WM_CHECK"}; |
| ctx.atoms[ATOM_NET_WM_NAME] = {"_NET_WM_NAME"}; |
| ctx.atoms[ATOM_NET_WM_MOVERESIZE] = {"_NET_WM_MOVERESIZE"}; |
| ctx.atoms[ATOM_NET_WM_STATE] = {"_NET_WM_STATE"}; |
| ctx.atoms[ATOM_NET_WM_STATE_FULLSCREEN] = {"_NET_WM_STATE_FULLSCREEN"}; |
| ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT] = { |
| "_NET_WM_STATE_MAXIMIZED_VERT"}; |
| ctx.atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ] = { |
| "_NET_WM_STATE_MAXIMIZED_HORZ"}; |
| ctx.atoms[ATOM_CLIPBOARD] = {"CLIPBOARD"}; |
| ctx.atoms[ATOM_CLIPBOARD_MANAGER] = {"CLIPBOARD_MANAGER"}; |
| ctx.atoms[ATOM_TARGETS] = {"TARGETS"}; |
| ctx.atoms[ATOM_TIMESTAMP] = {"TIMESTAMP"}; |
| ctx.atoms[ATOM_TEXT] = {"TEXT"}; |
| ctx.atoms[ATOM_INCR] = {"INCR"}; |
| ctx.atoms[ATOM_WL_SELECTION] = {"_WL_SELECTION"}; |
| ctx.atoms[ATOM_GTK_THEME_VARIANT] = {"_GTK_THEME_VARIANT"}; |
| ctx.trace_filename = NULL; |
| ctx.trace_system = false; |
| const char* display = getenv("SOMMELIER_DISPLAY"); |
| const char* scale = getenv("SOMMELIER_SCALE"); |
| const char* dpi = getenv("SOMMELIER_DPI"); |
| const char* clipboard_manager = getenv("SOMMELIER_CLIPBOARD_MANAGER"); |
| const char* frame_color = getenv("SOMMELIER_FRAME_COLOR"); |
| const char* dark_frame_color = getenv("SOMMELIER_DARK_FRAME_COLOR"); |
| const char* virtwl_device = getenv("SOMMELIER_VIRTWL_DEVICE"); |
| const char* drm_device = getenv("SOMMELIER_DRM_DEVICE"); |
| const char* glamor = getenv("SOMMELIER_GLAMOR"); |
| const char* fullscreen_mode = getenv("SOMMELIER_FULLSCREEN_MODE"); |
| const char* shm_driver = getenv("SOMMELIER_SHM_DRIVER"); |
| const char* data_driver = getenv("SOMMELIER_DATA_DRIVER"); |
| const char* peer_cmd_prefix = getenv("SOMMELIER_PEER_CMD_PREFIX"); |
| const char* xwayland_cmd_prefix = getenv("SOMMELIER_XWAYLAND_CMD_PREFIX"); |
| const char* accelerators = getenv("SOMMELIER_ACCELERATORS"); |
| const char* xwayland_path = getenv("SOMMELIER_XWAYLAND_PATH"); |
| const char* xwayland_gl_driver_path = |
| getenv("SOMMELIER_XWAYLAND_GL_DRIVER_PATH"); |
| const char* xauth_path = getenv("SOMMELIER_XAUTH_PATH"); |
| const char* xfont_path = getenv("SOMMELIER_XFONT_PATH"); |
| const char* socket_name = "wayland-0"; |
| const char* runtime_dir; |
| struct wl_event_loop* event_loop; |
| struct wl_listener client_destroy_listener = {}; |
| client_destroy_listener.notify = sl_client_destroy_notify; |
| int sv[2]; |
| pid_t pid; |
| int virtwl_display_fd = -1; |
| int xdisplay = -1; |
| int parent = 0; |
| int client_fd = -1; |
| int rv; |
| int i; |
| |
| // Ignore SIGUSR1 (used for trace dumping) in all child processes. |
| signal(SIGUSR1, SIG_IGN); |
| |
| for (i = 1; i < argc; ++i) { |
| const char* arg = argv[i]; |
| if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0 || |
| strcmp(arg, "-?") == 0) { |
| sl_print_usage(); |
| return EXIT_SUCCESS; |
| } |
| if (strcmp(arg, "--version") == 0 || strcmp(arg, "-v") == 0) { |
| printf("Version: %s\n", SOMMELIER_VERSION); |
| return EXIT_SUCCESS; |
| } |
| if (strstr(arg, "--parent") == arg) { |
| parent = 1; |
| } else if (strstr(arg, "--master") == arg) { |
| // TODO(b/172846445): deprecate the --master option. |
| parent = 1; |
| } else if (strstr(arg, "--socket") == arg) { |
| socket_name = sl_arg_value(arg); |
| } else if (strstr(arg, "--display") == arg) { |
| display = sl_arg_value(arg); |
| } else if (strstr(arg, "--vm-identifier") == arg) { |
| ctx.vm_id = sl_arg_value(arg); |
| } else if (strstr(arg, "--shm-driver") == arg) { |
| shm_driver = sl_arg_value(arg); |
| } else if (strstr(arg, "--data-driver") == arg) { |
| data_driver = sl_arg_value(arg); |
| } else if (strstr(arg, "--peer-pid") == arg) { |
| ctx.peer_pid = atoi(sl_arg_value(arg)); |
| } else if (strstr(arg, "--peer-cmd-prefix") == arg) { |
| peer_cmd_prefix = sl_arg_value(arg); |
| } else if (strstr(arg, "--xwayland-cmd-prefix") == arg) { |
| xwayland_cmd_prefix = sl_arg_value(arg); |
| } else if (strstr(arg, "--client-fd") == arg) { |
| client_fd = atoi(sl_arg_value(arg)); |
| } else if (strstr(arg, "--scale") == arg) { |
| scale = sl_arg_value(arg); |
| } else if (strstr(arg, "--dpi") == arg) { |
| dpi = sl_arg_value(arg); |
| } else if (strstr(arg, "--accelerators") == arg) { |
| accelerators = sl_arg_value(arg); |
| } else if (strstr(arg, "--application-id") == arg) { |
| ctx.application_id = sl_arg_value(arg); |
| } else if (strstr(arg, "-X") == arg) { |
| ctx.xwayland = 1; |
| } else if (strstr(arg, "--x-display") == arg) { |
| xdisplay = atoi(sl_arg_value(arg)); |
| // Automatically enable X forwarding if X display is specified. |
| ctx.xwayland = 1; |
| } else if (strstr(arg, "--xwayland-path") == arg) { |
| xwayland_path = sl_arg_value(arg); |
| } else if (strstr(arg, "--xwayland-gl-driver-path") == arg) { |
| xwayland_gl_driver_path = sl_arg_value(arg); |
| } else if (strstr(arg, "--no-exit-with-child") == arg) { |
| ctx.exit_with_child = 0; |
| } else if (strstr(arg, "--sd-notify") == arg) { |
| ctx.sd_notify = sl_arg_value(arg); |
| } else if (strstr(arg, "--no-clipboard-manager") == arg) { |
| clipboard_manager = "0"; |
| } else if (strstr(arg, "--frame-color") == arg) { |
| frame_color = sl_arg_value(arg); |
| } else if (strstr(arg, "--dark-frame-color") == arg) { |
| dark_frame_color = sl_arg_value(arg); |
| } else if (strstr(arg, "--virtwl-device") == arg) { |
| virtwl_device = sl_arg_value(arg); |
| } else if (strstr(arg, "--drm-device") == arg) { |
| drm_device = sl_arg_value(arg); |
| } else if (strstr(arg, "--glamor") == arg) { |
| glamor = "1"; |
| } else if (strstr(arg, "--fullscreen-mode") == arg) { |
| fullscreen_mode = sl_arg_value(arg); |
| } else if (strstr(arg, "--x-auth") == arg) { |
| xauth_path = sl_arg_value(arg); |
| } else if (strstr(arg, "--x-font-path") == arg) { |
| xfont_path = sl_arg_value(arg); |
| #ifdef PERFETTO_TRACING |
| } else if (strstr(arg, "--trace-filename") == arg) { |
| ctx.trace_filename = sl_arg_value(arg); |
| } else if (strstr(arg, "--trace-system") == arg) { |
| ctx.trace_system = true; |
| #endif |
| } else if (arg[0] == '-') { |
| if (strcmp(arg, "--") == 0) { |
| ctx.runprog = &argv[i + 1]; |
| break; |
| } |
| // Don't exit on unknown options so we can have forward compatibility |
| // with new flags introduced. |
| fprintf(stderr, "Option `%s' is unknown, ignoring.\n", arg); |
| } else { |
| ctx.runprog = &argv[i]; |
| break; |
| } |
| } |
| |
| runtime_dir = getenv("XDG_RUNTIME_DIR"); |
| if (!runtime_dir) { |
| fprintf(stderr, "error: XDG_RUNTIME_DIR not set in the environment\n"); |
| return EXIT_FAILURE; |
| } |
| |
| if (parent) { |
| char* lock_addr; |
| struct sockaddr_un addr; |
| struct sigaction sa; |
| struct stat sock_stat; |
| int lock_fd; |
| int sock_fd; |
| |
| addr.sun_family = AF_LOCAL; |
| snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", runtime_dir, |
| socket_name); |
| |
| lock_addr = sl_xasprintf("%s%s", addr.sun_path, LOCK_SUFFIX); |
| |
| lock_fd = open(lock_addr, O_CREAT | O_CLOEXEC, |
| (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)); |
| errno_assert(lock_fd >= 0); |
| |
| rv = flock(lock_fd, LOCK_EX | LOCK_NB); |
| if (rv < 0) { |
| fprintf(stderr, |
| "error: unable to lock %s, is another compositor running?\n", |
| lock_addr); |
| return EXIT_FAILURE; |
| } |
| free(lock_addr); |
| |
| rv = stat(addr.sun_path, &sock_stat); |
| if (rv >= 0) { |
| if (sock_stat.st_mode & (S_IWUSR | S_IWGRP)) |
| unlink(addr.sun_path); |
| } else { |
| errno_assert(errno == ENOENT); |
| } |
| |
| sock_fd = socket(PF_LOCAL, SOCK_STREAM, 0); |
| errno_assert(sock_fd >= 0); |
| |
| rv = bind(sock_fd, (struct sockaddr*)&addr, |
| offsetof(struct sockaddr_un, sun_path) + strlen(addr.sun_path)); |
| errno_assert(rv >= 0); |
| |
| rv = listen(sock_fd, 128); |
| errno_assert(rv >= 0); |
| |
| // Spawn optional child process before we notify systemd that we're ready |
| // to accept connections. WAYLAND_DISPLAY will be set but any attempt to |
| // connect to this socket at this time will fail. |
| if (ctx.runprog && ctx.runprog[0]) { |
| pid = fork(); |
| errno_assert(pid != -1); |
| if (pid == 0) { |
| setenv("WAYLAND_DISPLAY", socket_name, 1); |
| sl_execvp(ctx.runprog[0], ctx.runprog, -1); |
| _exit(EXIT_FAILURE); |
| } |
| while (waitpid(-1, NULL, WNOHANG) != pid) |
| continue; |
| } |
| |
| if (ctx.sd_notify) |
| sl_sd_notify(ctx.sd_notify); |
| |
| sa.sa_handler = sl_sigchld_handler; |
| sigemptyset(&sa.sa_mask); |
| sa.sa_flags = SA_RESTART; |
| rv = sigaction(SIGCHLD, &sa, NULL); |
| errno_assert(rv >= 0); |
| |
| do { |
| struct ucred ucred; |
| socklen_t length = sizeof(addr); |
| |
| client_fd = accept(sock_fd, (struct sockaddr*)&addr, &length); |
| if (client_fd < 0) { |
| fprintf(stderr, "error: failed to accept: %m\n"); |
| continue; |
| } |
| |
| ucred.pid = -1; |
| length = sizeof(ucred); |
| rv = getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &ucred, &length); |
| |
| pid = fork(); |
| errno_assert(pid != -1); |
| if (pid == 0) { |
| char* client_fd_str; |
| char* peer_pid_str; |
| char* peer_cmd_prefix_str; |
| char const* args[64]; |
| int i = 0, j; |
| |
| close(sock_fd); |
| close(lock_fd); |
| |
| if (!peer_cmd_prefix) |
| peer_cmd_prefix = PEER_CMD_PREFIX; |
| |
| if (peer_cmd_prefix) { |
| peer_cmd_prefix_str = sl_xasprintf("%s", peer_cmd_prefix); |
| |
| i = sl_parse_cmd_prefix(peer_cmd_prefix_str, 32, args); |
| if (i > 32) { |
| fprintf(stderr, "error: too many arguments in cmd prefix: %d\n", i); |
| i = 0; |
| } |
| } |
| |
| args[i++] = argv[0]; |
| peer_pid_str = sl_xasprintf("--peer-pid=%d", ucred.pid); |
| args[i++] = peer_pid_str; |
| client_fd_str = sl_xasprintf("--client-fd=%d", client_fd); |
| args[i++] = client_fd_str; |
| |
| // forward some flags. |
| for (j = 1; j < argc; ++j) { |
| char* arg = argv[j]; |
| if (strstr(arg, "--display") == arg || |
| strstr(arg, "--scale") == arg || |
| strstr(arg, "--accelerators") == arg || |
| strstr(arg, "--virtwl-device") == arg || |
| strstr(arg, "--drm-device") == arg || |
| strstr(arg, "--shm-driver") == arg || |
| strstr(arg, "--data-driver") == arg) { |
| args[i++] = arg; |
| } |
| } |
| |
| args[i++] = NULL; |
| |
| execvp(args[0], const_cast<char* const*>(args)); |
| _exit(EXIT_FAILURE); |
| } |
| close(client_fd); |
| } while (1); |
| |
| // Control should never reach here. |
| assert(false); |
| } |
| |
| if (client_fd == -1) { |
| if (!ctx.runprog || !ctx.runprog[0]) { |
| sl_print_usage(); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| if (ctx.xwayland) { |
| assert(client_fd == -1); |
| |
| ctx.clipboard_manager = 1; |
| if (clipboard_manager) |
| ctx.clipboard_manager = !!strcmp(clipboard_manager, "0"); |
| } |
| |
| if (scale) { |
| ctx.desired_scale = atof(scale); |
| // Round to integer scale until we detect wp_viewporter support. |
| ctx.scale = MIN(MAX_SCALE, MAX(MIN_SCALE, round(ctx.desired_scale))); |
| } |
| |
| if (!frame_color) |
| frame_color = FRAME_COLOR; |
| |
| if (frame_color) { |
| int r, g, b; |
| if (sscanf(frame_color, "#%02x%02x%02x", &r, &g, &b) == 3) |
| ctx.frame_color = 0xff000000 | (r << 16) | (g << 8) | (b << 0); |
| } |
| |
| if (!dark_frame_color) |
| dark_frame_color = DARK_FRAME_COLOR; |
| |
| if (dark_frame_color) { |
| int r, g, b; |
| if (sscanf(dark_frame_color, "#%02x%02x%02x", &r, &g, &b) == 3) |
| ctx.dark_frame_color = 0xff000000 | (r << 16) | (g << 8) | (b << 0); |
| } |
| |
| if (fullscreen_mode) { |
| if (strcmp(fullscreen_mode, "immersive") == 0) { |
| ctx.fullscreen_mode = ZAURA_SURFACE_FULLSCREEN_MODE_IMMERSIVE; |
| } else if (strcmp(fullscreen_mode, "plain") == 0) { |
| ctx.fullscreen_mode = ZAURA_SURFACE_FULLSCREEN_MODE_PLAIN; |
| } else { |
| fprintf(stderr, "error: unrecognised --fullscreen-mode\n"); |
| sl_print_usage(); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| // Handle broken pipes without signals that kill the entire process. |
| signal(SIGPIPE, SIG_IGN); |
| |
| ctx.host_display = wl_display_create(); |
| assert(ctx.host_display); |
| |
| event_loop = wl_display_get_event_loop(ctx.host_display); |
| |
| if (!virtwl_device) |
| virtwl_device = VIRTWL_DEVICE; |
| |
| if (virtwl_device) { |
| struct virtwl_ioctl_new new_ctx = { |
| .type = VIRTWL_IOCTL_NEW_CTX, |
| .fd = -1, |
| .flags = 0, |
| }; |
| new_ctx.size = 0; |
| |
| ctx.virtwl_fd = open(virtwl_device, O_RDWR); |
| if (ctx.virtwl_fd == -1) { |
| fprintf(stderr, "error: could not open %s (%s)\n", virtwl_device, |
| strerror(errno)); |
| return EXIT_FAILURE; |
| } |
| |
| // We use a virtwl context unless display was explicitly specified. |
| // WARNING: It's critical that we never call wl_display_roundtrip |
| // as we're not spawning a new thread to handle forwarding. Calling |
| // wl_display_roundtrip will cause a deadlock. |
| if (!display) { |
| int vws[2]; |
| |
| // Connection to virtwl channel. |
| rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, vws); |
| errno_assert(!rv); |
| |
| ctx.virtwl_socket_fd = vws[0]; |
| virtwl_display_fd = vws[1]; |
| |
| rv = ioctl(ctx.virtwl_fd, VIRTWL_IOCTL_NEW, &new_ctx); |
| if (rv) { |
| fprintf(stderr, "error: failed to create virtwl context: %s\n", |
| strerror(errno)); |
| return EXIT_FAILURE; |
| } |
| |
| ctx.virtwl_ctx_fd = new_ctx.fd; |
| |
| ctx.virtwl_socket_event_source = wl_event_loop_add_fd( |
| event_loop, ctx.virtwl_socket_fd, WL_EVENT_READABLE, |
| sl_handle_virtwl_socket_event, &ctx); |
| ctx.virtwl_ctx_event_source = |
| wl_event_loop_add_fd(event_loop, ctx.virtwl_ctx_fd, WL_EVENT_READABLE, |
| sl_handle_virtwl_ctx_event, &ctx); |
| } |
| } |
| |
| if (drm_device) { |
| int drm_fd = open(drm_device, O_RDWR | O_CLOEXEC); |
| if (drm_fd == -1) { |
| fprintf(stderr, "error: could not open %s (%s)\n", drm_device, |
| strerror(errno)); |
| return EXIT_FAILURE; |
| } |
| |
| ctx.gbm = gbm_create_device(drm_fd); |
| if (!ctx.gbm) { |
| fprintf(stderr, "error: couldn't get display device\n"); |
| return EXIT_FAILURE; |
| } |
| |
| ctx.drm_device = drm_device; |
| } |
| |
| if (!shm_driver) |
| shm_driver = ctx.xwayland ? XWAYLAND_SHM_DRIVER : SHM_DRIVER; |
| |
| if (shm_driver) { |
| if (strcmp(shm_driver, "dmabuf") == 0) { |
| if (!ctx.drm_device) { |
| fprintf(stderr, "error: need drm device for dmabuf driver\n"); |
| return EXIT_FAILURE; |
| } |
| ctx.shm_driver = SHM_DRIVER_DMABUF; |
| } else if (strcmp(shm_driver, "virtwl") == 0 || |
| strcmp(shm_driver, "virtwl-dmabuf") == 0) { |
| if (ctx.virtwl_fd == -1) { |
| fprintf(stderr, "error: need device for virtwl driver\n"); |
| return EXIT_FAILURE; |
| } |
| ctx.shm_driver = strcmp(shm_driver, "virtwl") ? SHM_DRIVER_VIRTWL_DMABUF |
| : SHM_DRIVER_VIRTWL; |
| // Check for compatibility with virtwl-dmabuf. |
| if (ctx.shm_driver == SHM_DRIVER_VIRTWL_DMABUF) { |
| struct virtwl_ioctl_new new_dmabuf = { |
| .type = VIRTWL_IOCTL_NEW_DMABUF, |
| .fd = -1, |
| .flags = 0, |
| }; |
| new_dmabuf.dmabuf = { |
| .width = 0, |
| .height = 0, |
| .format = 0, |
| }; |
| if (ioctl(ctx.virtwl_fd, VIRTWL_IOCTL_NEW, &new_dmabuf) == -1 && |
| errno == ENOTTY) { |
| fprintf(stderr, |
| "warning: virtwl-dmabuf driver not supported by host, using " |
| "virtwl instead\n"); |
| ctx.shm_driver = SHM_DRIVER_VIRTWL; |
| } else if (new_dmabuf.fd >= 0) { |
| // Close the returned dmabuf fd in case the invalid dmabuf metadata |
| // given above actually manages to return an fd successfully. |
| close(new_dmabuf.fd); |
| } |
| } |
| } |
| } else if (ctx.drm_device) { |
| ctx.shm_driver = SHM_DRIVER_DMABUF; |
| } else if (ctx.virtwl_fd != -1) { |
| ctx.shm_driver = SHM_DRIVER_VIRTWL_DMABUF; |
| } |
| |
| if (data_driver) { |
| if (strcmp(data_driver, "virtwl") == 0) { |
| if (ctx.virtwl_fd == -1) { |
| fprintf(stderr, "error: need device for virtwl driver\n"); |
| return EXIT_FAILURE; |
| } |
| ctx.data_driver = DATA_DRIVER_VIRTWL; |
| } |
| } else if (ctx.virtwl_fd != -1) { |
| ctx.data_driver = DATA_DRIVER_VIRTWL; |
| } |
| |
| wl_array_init(&ctx.dpi); |
| if (dpi) { |
| char* str = strdup(dpi); |
| char* token = strtok(str, ","); // NOLINT(runtime/threadsafe_fn) |
| int* p; |
| |
| while (token) { |
| p = static_cast<int*>(wl_array_add(&ctx.dpi, sizeof *p)); |
| assert(p); |
| *p = MAX(MIN_DPI, MIN(atoi(token), MAX_DPI)); |
| token = strtok(NULL, ","); // NOLINT(runtime/threadsafe_fn) |
| } |
| free(str); |
| } |
| |
| if (ctx.runprog || ctx.xwayland) { |
| // Wayland connection from client. |
| rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv); |
| errno_assert(!rv); |
| |
| client_fd = sv[0]; |
| } |
| |
| // The success of this depends on xkb-data being installed. |
| ctx.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); |
| if (!ctx.xkb_context) { |
| fprintf(stderr, "error: xkb_context_new failed. xkb-data missing?\n"); |
| return EXIT_FAILURE; |
| } |
| |
| if (virtwl_display_fd != -1) { |
| ctx.display = wl_display_connect_to_fd(virtwl_display_fd); |
| } else { |
| if (display == NULL) |
| display = getenv("WAYLAND_DISPLAY"); |
| if (display == NULL) |
| display = "wayland-0"; |
| |
| ctx.display = wl_display_connect(display); |
| } |
| |
| if (!ctx.display) { |
| fprintf(stderr, "error: failed to connect to %s\n", display); |
| return EXIT_FAILURE; |
| } |
| |
| wl_list_init(&ctx.accelerators); |
| wl_list_init(&ctx.registries); |
| wl_list_init(&ctx.globals); |
| wl_list_init(&ctx.outputs); |
| wl_list_init(&ctx.seats); |
| wl_list_init(&ctx.windows); |
| wl_list_init(&ctx.unpaired_windows); |
| wl_list_init(&ctx.host_outputs); |
| wl_list_init(&ctx.selection_data_source_send_pending); |
| #ifdef GAMEPAD_SUPPORT |
| wl_list_init(&ctx.gamepads); |
| #endif |
| |
| // Parse the list of accelerators that should be reserved by the |
| // compositor. Format is "|MODIFIERS|KEYSYM", where MODIFIERS is a |
| // list of modifier names (E.g. <Control><Alt>) and KEYSYM is an |
| // XKB key symbol name (E.g Delete). |
| if (accelerators) { |
| uint32_t modifiers = 0; |
| |
| while (*accelerators) { |
| if (*accelerators == ',') { |
| accelerators++; |
| } else if (*accelerators == '<') { |
| if (strncmp(accelerators, "<Control>", 9) == 0) { |
| modifiers |= CONTROL_MASK; |
| accelerators += 9; |
| } else if (strncmp(accelerators, "<Alt>", 5) == 0) { |
| modifiers |= ALT_MASK; |
| accelerators += 5; |
| } else if (strncmp(accelerators, "<Shift>", 7) == 0) { |
| modifiers |= SHIFT_MASK; |
| accelerators += 7; |
| } else { |
| fprintf(stderr, "error: invalid modifier\n"); |
| return EXIT_FAILURE; |
| } |
| } else { |
| const char* end = strchrnul(accelerators, ','); |
| char* name = strndup(accelerators, end - accelerators); |
| |
| struct sl_accelerator* accelerator = |
| static_cast<sl_accelerator*>(malloc(sizeof(*accelerator))); |
| accelerator->modifiers = modifiers; |
| accelerator->symbol = |
| xkb_keysym_from_name(name, XKB_KEYSYM_CASE_INSENSITIVE); |
| if (accelerator->symbol == XKB_KEY_NoSymbol) { |
| fprintf(stderr, "error: invalid key symbol\n"); |
| return EXIT_FAILURE; |
| } |
| |
| wl_list_insert(&ctx.accelerators, &accelerator->link); |
| |
| modifiers = 0; |
| accelerators = end; |
| free(name); |
| } |
| } |
| } |
| |
| ctx.display_event_source = |
| wl_event_loop_add_fd(event_loop, wl_display_get_fd(ctx.display), |
| WL_EVENT_READABLE, sl_handle_event, &ctx); |
| |
| wl_registry_add_listener(wl_display_get_registry(ctx.display), |
| &sl_registry_listener, &ctx); |
| |
| ctx.client = wl_client_create(ctx.host_display, client_fd); |
| |
| // Replace the core display implementation. This is needed in order to |
| // implement sync handler properly. |
| sl_set_display_implementation(&ctx); |
| |
| if (ctx.runprog || ctx.xwayland) { |
| ctx.sigchld_event_source = |
| wl_event_loop_add_signal(event_loop, SIGCHLD, sl_handle_sigchld, &ctx); |
| |
| // Unset DISPLAY to prevent X clients from connecting to an existing X |
| // server when X forwarding is not enabled. |
| unsetenv("DISPLAY"); |
| // Set WAYLAND_DISPLAY to value that is guaranteed to not point to a |
| // valid wayland compositor socket name. Resetting WAYLAND_DISPLAY is |
| // insufficient as clients will attempt to connect to wayland-0 if |
| // it's not set. |
| setenv("WAYLAND_DISPLAY", ".", 1); |
| |
| if (ctx.xwayland) { |
| int ds[2], wm[2]; |
| |
| // Xwayland display ready socket. |
| rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ds); |
| errno_assert(!rv); |
| |
| ctx.display_ready_event_source = |
| wl_event_loop_add_fd(event_loop, ds[0], WL_EVENT_READABLE, |
| sl_handle_display_ready_event, &ctx); |
| |
| // X connection to Xwayland. |
| rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wm); |
| errno_assert(!rv); |
| |
| ctx.wm_fd = wm[0]; |
| |
| pid = fork(); |
| errno_assert(pid != -1); |
| if (pid == 0) { |
| char* display_fd_str; |
| char* wm_fd_str; |
| char* xwayland_cmd_prefix_str; |
| char const* args[64]; |
| int i = 0; |
| int fd; |
| |
| if (xwayland_cmd_prefix) { |
| xwayland_cmd_prefix_str = sl_xasprintf("%s", xwayland_cmd_prefix); |
| |
| i = sl_parse_cmd_prefix(xwayland_cmd_prefix_str, 32, args); |
| if (i > 32) { |
| fprintf(stderr, "error: too many arguments in cmd prefix: %d\n", i); |
| i = 0; |
| } |
| } |
| |
| args[i++] = sl_xasprintf("%s", xwayland_path ?: XWAYLAND_PATH); |
| |
| fd = dup(ds[1]); |
| display_fd_str = sl_xasprintf("%d", fd); |
| fd = dup(wm[1]); |
| wm_fd_str = sl_xasprintf("%d", fd); |
| |
| if (xdisplay > 0) { |
| args[i++] = sl_xasprintf(":%d", xdisplay); |
| } |
| args[i++] = "-nolisten"; |
| args[i++] = "tcp"; |
| args[i++] = "-rootless"; |
| // Use software rendering unless we have a DRM device and glamor is |
| // enabled. |
| if (!ctx.drm_device || !glamor || !strcmp(glamor, "0")) |
| args[i++] = "-shm"; |
| args[i++] = "-displayfd"; |
| args[i++] = display_fd_str; |
| args[i++] = "-wm"; |
| args[i++] = wm_fd_str; |
| if (xauth_path) { |
| args[i++] = "-auth"; |
| args[i++] = sl_xasprintf("%s", xauth_path); |
| } |
| if (xfont_path) { |
| args[i++] = "-fp"; |
| args[i++] = sl_xasprintf("%s", xfont_path); |
| } |
| args[i++] = NULL; |
| |
| // If a path is explicitly specified via command line or environment |
| // use that instead of the compiled in default. In either case, only |
| // set the environment variable if the value specified is non-empty. |
| if (xwayland_gl_driver_path) { |
| if (*xwayland_gl_driver_path) { |
| setenv("LIBGL_DRIVERS_PATH", xwayland_gl_driver_path, 1); |
| } |
| } else if (XWAYLAND_GL_DRIVER_PATH && *XWAYLAND_GL_DRIVER_PATH) { |
| setenv("LIBGL_DRIVERS_PATH", XWAYLAND_GL_DRIVER_PATH, 1); |
| } |
| |
| sl_execvp(args[0], const_cast<char* const*>(args), sv[1]); |
| _exit(EXIT_FAILURE); |
| } |
| close(wm[1]); |
| ctx.xwayland_pid = pid; |
| } else { |
| pid = fork(); |
| errno_assert(pid != -1); |
| if (pid == 0) { |
| sl_execvp(ctx.runprog[0], ctx.runprog, sv[1]); |
| _exit(EXIT_FAILURE); |
| } |
| ctx.child_pid = pid; |
| } |
| close(sv[1]); |
| } |
| |
| // Attempt to enable tracing. This could be called earlier but would rather |
| // spawn all children first. |
| if (ctx.trace_filename || ctx.trace_system) { |
| initialize_tracing(ctx.trace_filename, ctx.trace_system); |
| enable_tracing(!ctx.trace_system); |
| ctx.sigusr1_event_source = |
| wl_event_loop_add_signal(event_loop, SIGUSR1, sl_handle_sigusr1, &ctx); |
| } |
| |
| wl_client_add_destroy_listener(ctx.client, &client_destroy_listener); |
| |
| do { |
| wl_display_flush_clients(ctx.host_display); |
| if (ctx.connection) { |
| if (ctx.needs_set_input_focus) { |
| sl_set_input_focus(&ctx, ctx.host_focus_window); |
| ctx.needs_set_input_focus = 0; |
| } |
| xcb_flush(ctx.connection); |
| } |
| if (wl_display_flush(ctx.display) < 0) |
| return EXIT_FAILURE; |
| } while (wl_event_loop_dispatch(event_loop, -1) != -1); |
| |
| return EXIT_SUCCESS; |
| } // NOLINT(readability/fn_size) |