// 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" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "aura-shell-client-protocol.h" #include "drm-server-protocol.h" #include "keyboard-extension-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" #include "pointer-constraints-unstable-v1-client-protocol.h" #include "relative-pointer-unstable-v1-client-protocol.h" #include "text-input-unstable-v1-client-protocol.h" #include "viewporter-client-protocol.h" #include "xdg-shell-unstable-v6-client-protocol.h" // 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_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; }; #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 #define APPLICATION_ID_FORMAT_PREFIX "org.chromium.termina" #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 // 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) { struct sl_mmap* map; map = malloc(sizeof(*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); assert(map->addr != MAP_FAILED); return map; } struct sl_mmap* sl_mmap_ref(struct sl_mmap* map) { map->refcount++; return map; } void sl_mmap_unref(struct sl_mmap* map) { if (map->refcount-- == 1) { munmap(map->addr, map->size + map->offset[0]); close(map->fd); free(map); } } struct sl_sync_point* sl_sync_point_create(int fd) { struct sl_sync_point* sync_point; sync_point = malloc(sizeof(*sync_point)); sync_point->fd = fd; sync_point->sync = NULL; return sync_point; } void sl_sync_point_destroy(struct sl_sync_point* sync_point) { 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) { 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 = { .response_type = XCB_CONFIGURE_NOTIFY, .event = window->id, .window = window->id, .above_sibling = XCB_WINDOW_NONE, .x = window->x, .y = window->y, .width = window->width, .height = window->height, .border_width = window->border_width, .override_redirect = 0, .pad0 = 0, .pad1 = 0, }; xcb_send_event(window->ctx->connection, 0, window->id, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&event); } static void sl_adjust_window_size_for_screen_size(struct sl_window* window) { 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) { 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 = { .response_type = XCB_CLIENT_MESSAGE, .format = 32, .window = window->id, .type = ctx->atoms[ATOM_WM_PROTOCOLS].value, .data.data32 = { ctx->atoms[ATOM_WM_TAKE_FOCUS].value, XCB_CURRENT_TIME, }, }; if (!window->managed) return; xcb_send_event(ctx->connection, 0, window->id, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (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) { 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) { int width = window->width + window->border_width * 2; int 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) { struct sl_window* 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 = 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) { struct sl_window* 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; wl_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) { struct sl_window* window = zxdg_toplevel_v6_get_user_data(xdg_toplevel); xcb_client_message_event_t event = { .response_type = XCB_CLIENT_MESSAGE, .format = 32, .window = window->id, .type = window->ctx->atoms[ATOM_WM_PROTOCOLS].value, .data.data32 = { window->ctx->atoms[ATOM_WM_DELETE_WINDOW].value, 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) { 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_window_update(struct sl_window* window) { 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 = 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 = 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); if (ctx->application_id) { zaura_surface_set_application_id(window->aura_surface, ctx->application_id); } else { // 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, window->clazz); } else if (window->client_leader != XCB_WINDOW_NONE) { application_id_str = sl_xasprintf( WM_CLIENT_LEADER_APPLICATION_ID_FORMAT, window->client_leader); } else { application_id_str = sl_xasprintf(XID_APPLICATION_ID_FORMAT, window->id); } zaura_surface_set_application_id(window->aura_surface, application_id_str); free(application_id_str); } } } // 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) { 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 = wl_buffer_get_user_data(buffer); 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) { struct sl_host_buffer* host = 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) { struct sl_host_buffer* host_buffer; 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) { 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) { 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)); ((xcb_atom_t*)data_offer->atoms.data)[0] = ctx->atoms[ATOM_TARGETS].value; ((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 = ((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) { ((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) { struct sl_data_offer* host = data; xcb_intern_atom_cookie_t* cookie = 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) {} static void sl_internal_data_offer_action(void* data, struct wl_data_offer* data_offer, uint32_t dnd_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; host_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 ? 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); } } void sl_host_seat_removed(struct sl_host_seat* host) { 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) { struct sl_host_registry* registry; struct sl_global* global; assert(version > 0); assert(version <= interface->version); 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) { 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; if (strcmp(interface, "wl_compositor") == 0) { struct sl_compositor* compositor = malloc(sizeof(struct sl_compositor)); assert(compositor); compositor->ctx = ctx; compositor->id = id; assert(version >= 3); compositor->version = 3; compositor->internal = 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 = 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 = malloc(sizeof(struct sl_shm)); assert(shm); shm->ctx = ctx; shm->id = id; shm->internal = 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 = 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 = 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 = 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 = malloc(sizeof(struct sl_relative_pointer_manager)); assert(relative_pointer); relative_pointer->ctx = ctx; relative_pointer->id = id; relative_pointer->internal = 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 = malloc(sizeof(struct sl_pointer_constraints)); assert(pointer_constraints); pointer_constraints->ctx = ctx; pointer_constraints->id = id; pointer_constraints->internal = 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 = 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 = 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 = 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 = 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 = malloc(sizeof(struct sl_aura_shell)); assert(aura_shell); aura_shell->ctx = ctx; aura_shell->id = id; aura_shell->version = MIN(MIN_AURA_SHELL_VERSION, version); aura_shell->host_gtk_shell_global = NULL; aura_shell->internal = 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 = malloc(sizeof(struct sl_viewporter)); assert(viewporter); viewporter->ctx = ctx; viewporter->id = id; viewporter->host_viewporter_global = NULL; viewporter->internal = 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 = 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 = 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 = malloc(sizeof(struct sl_keyboard_extension)); assert(keyboard_extension); keyboard_extension->ctx = ctx; keyboard_extension->id = id; keyboard_extension->internal = 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 = 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 = 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; } } static void sl_registry_remover(void* data, struct wl_registry* registry, uint32_t id) { 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; } 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) { 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) { struct sl_window* 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->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) { 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_handle_map_request(struct sl_context* ctx, xcb_map_request_event_t* event) { 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_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]; int i; if (!window) return; assert(!sl_is_our_window(ctx, event->window)); window->managed = 1; if (window->frame_id == XCB_WINDOW_NONE) geometry_cookie = xcb_get_geometry(ctx->connection, window->id); for (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 (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(xcb_get_property_value(reply), xcb_get_property_value_length(reply)); break; case PROPERTY_WM_CLASS: { // 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 = 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); } } break; case PROPERTY_WM_TRANSIENT_FOR: if (xcb_get_property_value_length(reply) >= 4) window->transient_for = *((uint32_t*)xcb_get_property_value(reply)); break; case PROPERTY_WM_NORMAL_HINTS: if (xcb_get_property_value_length(reply) >= 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 = *((uint32_t*)xcb_get_property_value(reply)); break; case PROPERTY_MOTIF_WM_HINTS: if (xcb_get_property_value_length(reply) >= sizeof(mwm_hints)) memcpy(&mwm_hints, xcb_get_property_value(reply), sizeof(mwm_hints)); break; case PROPERTY_NET_STARTUP_ID: window->startup_id = strndup(xcb_get_property_value(reply), xcb_get_property_value_length(reply)); break; case PROPERTY_NET_WM_STATE: reply_atoms = xcb_get_property_value(reply); for (i = 0; i < xcb_get_property_value_length(reply) / sizeof(xcb_atom_t); ++i) { if (reply_atoms[i] == ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value) { maximize_h = true; } else if (reply_atoms[i] == 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(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(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]; assert(!sl_is_our_window(ctx, event->window)); 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_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_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]; int 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); assert(!rv); UNUSED(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 = data; uint8_t* value; int bytes, bytes_left; value = 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) { xcb_selection_notify_event_t event = { .response_type = XCB_SELECTION_NOTIFY, .sequence = 0, .time = ctx->selection_request.time, .requestor = ctx->selection_request.requestor, .selection = ctx->selection_request.selection, .target = ctx->selection_request.target, .property = property, .pad0 = 0}; xcb_send_event(ctx->connection, 0, ctx->selection_request.requestor, XCB_EVENT_MASK_NO_EVENT, (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 = 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 = (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) { 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(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_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 == 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(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) { struct sl_data_source* host = 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 = 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) { struct sl_data_source* host = 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 = 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) { 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 = 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 = 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 = 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) { 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) { 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, .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); 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; wl_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) { 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)) return 0; while ((event = xcb_poll_for_event(ctx->connection))) { switch (event->response_type & ~SEND_EVENT_MASK) { case XCB_CREATE_NOTIFY: sl_handle_create_notify(ctx, (xcb_create_notify_event_t*)event); break; case XCB_DESTROY_NOTIFY: sl_handle_destroy_notify(ctx, (xcb_destroy_notify_event_t*)event); break; case XCB_REPARENT_NOTIFY: sl_handle_reparent_notify(ctx, (xcb_reparent_notify_event_t*)event); break; case XCB_MAP_REQUEST: sl_handle_map_request(ctx, (xcb_map_request_event_t*)event); break; case XCB_MAP_NOTIFY: sl_handle_map_notify(ctx, (xcb_map_notify_event_t*)event); break; case XCB_UNMAP_NOTIFY: sl_handle_unmap_notify(ctx, (xcb_unmap_notify_event_t*)event); break; case XCB_CONFIGURE_REQUEST: sl_handle_configure_request(ctx, (xcb_configure_request_event_t*)event); break; case XCB_CONFIGURE_NOTIFY: sl_handle_configure_notify(ctx, (xcb_configure_notify_event_t*)event); break; case XCB_CLIENT_MESSAGE: sl_handle_client_message(ctx, (xcb_client_message_event_t*)event); break; case XCB_FOCUS_IN: sl_handle_focus_in(ctx, (xcb_focus_in_event_t*)event); break; case XCB_FOCUS_OUT: sl_handle_focus_out(ctx, (xcb_focus_out_event_t*)event); break; case XCB_PROPERTY_NOTIFY: sl_handle_property_notify(ctx, (xcb_property_notify_event_t*)event); break; case XCB_SELECTION_NOTIFY: sl_handle_selection_notify(ctx, (xcb_selection_notify_event_t*)event); break; case XCB_SELECTION_REQUEST: sl_handle_selection_request(ctx, (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, (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_connect(struct sl_context* ctx) { 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); 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); 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.iov_base = (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); assert(rv != -1); UNUSED(rv); } 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 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) { struct sl_context* ctx = (struct sl_context*)data; char display_name[9]; int bytes_read = 0; pid_t pid; if (!(mask & WL_EVENT_READABLE)) return 0; 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", (int)(XCURSOR_SIZE_BASE * ctx->scale + 0.5))); pid = fork(); 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) { 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; 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); assert(bytes == ioctl_recv->len); UNUSED(bytes); 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) { 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; 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); 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); assert(!rv); UNUSED(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** 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" " --master\t\t\tRun as master and spawn child processes\n" " --socket=SOCKET\t\tName of socket to listen on\n" " --display=DISPLAY\t\tWayland display to connect to\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"); } 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 = { .runprog = NULL, .display = NULL, .host_display = NULL, .client = NULL, .compositor = NULL, .subcompositor = NULL, .shm = NULL, .shell = NULL, .data_device_manager = NULL, .xdg_shell = NULL, .aura_shell = NULL, .viewporter = NULL, .linux_dmabuf = NULL, .keyboard_extension = NULL, .text_input_manager = NULL, .display_event_source = NULL, .display_ready_event_source = NULL, .sigchld_event_source = NULL, .shm_driver = SHM_DRIVER_NOOP, .data_driver = DATA_DRIVER_NOOP, .wm_fd = -1, .virtwl_fd = -1, .virtwl_ctx_fd = -1, .virtwl_socket_fd = -1, .virtwl_ctx_event_source = NULL, .virtwl_socket_event_source = NULL, .drm_device = NULL, .gbm = NULL, .xwayland = 0, .xwayland_pid = -1, .child_pid = -1, .peer_pid = -1, .xkb_context = NULL, .next_global_id = 1, .connection = NULL, .connection_event_source = NULL, .xfixes_extension = NULL, .screen = NULL, .window = 0, .host_focus_window = NULL, .needs_set_input_focus = 0, .desired_scale = 1.0, .scale = 1.0, .application_id = NULL, .exit_with_child = 1, .sd_notify = NULL, .clipboard_manager = 0, .frame_color = 0xffffffff, .dark_frame_color = 0xff000000, .default_seat = NULL, .selection_window = XCB_WINDOW_NONE, .selection_owner = XCB_WINDOW_NONE, .selection_incremental_transfer = 0, .selection_request = {.requestor = XCB_NONE, .property = XCB_ATOM_NONE}, .selection_timestamp = XCB_CURRENT_TIME, .selection_data_device = NULL, .selection_data_offer = NULL, .selection_data_source = NULL, .selection_data_source_send_fd = -1, .selection_send_event_source = NULL, .selection_property_reply = NULL, .selection_property_offset = 0, .selection_event_source = NULL, .selection_data_offer_receive_fd = -1, .selection_data_ack_pending = 0, .atoms = { [ATOM_WM_S0] = {"WM_S0"}, [ATOM_WM_PROTOCOLS] = {"WM_PROTOCOLS"}, [ATOM_WM_STATE] = {"WM_STATE"}, [ATOM_WM_CHANGE_STATE] = {"WM_CHANGE_STATE"}, [ATOM_WM_DELETE_WINDOW] = {"WM_DELETE_WINDOW"}, [ATOM_WM_TAKE_FOCUS] = {"WM_TAKE_FOCUS"}, [ATOM_WM_CLIENT_LEADER] = {"WM_CLIENT_LEADER"}, [ATOM_WL_SURFACE_ID] = {"WL_SURFACE_ID"}, [ATOM_UTF8_STRING] = {"UTF8_STRING"}, [ATOM_MOTIF_WM_HINTS] = {"_MOTIF_WM_HINTS"}, [ATOM_NET_FRAME_EXTENTS] = {"_NET_FRAME_EXTENTS"}, [ATOM_NET_STARTUP_ID] = {"_NET_STARTUP_ID"}, [ATOM_NET_SUPPORTING_WM_CHECK] = {"_NET_SUPPORTING_WM_CHECK"}, [ATOM_NET_WM_NAME] = {"_NET_WM_NAME"}, [ATOM_NET_WM_MOVERESIZE] = {"_NET_WM_MOVERESIZE"}, [ATOM_NET_WM_STATE] = {"_NET_WM_STATE"}, [ATOM_NET_WM_STATE_FULLSCREEN] = {"_NET_WM_STATE_FULLSCREEN"}, [ATOM_NET_WM_STATE_MAXIMIZED_VERT] = {"_NET_WM_STATE_MAXIMIZED_VERT"}, [ATOM_NET_WM_STATE_MAXIMIZED_HORZ] = {"_NET_WM_STATE_MAXIMIZED_HORZ"}, [ATOM_CLIPBOARD] = {"CLIPBOARD"}, [ATOM_CLIPBOARD_MANAGER] = {"CLIPBOARD_MANAGER"}, [ATOM_TARGETS] = {"TARGETS"}, [ATOM_TIMESTAMP] = {"TIMESTAMP"}, [ATOM_TEXT] = {"TEXT"}, [ATOM_INCR] = {"INCR"}, [ATOM_WL_SELECTION] = {"_WL_SELECTION"}, [ATOM_GTK_THEME_VARIANT] = {"_GTK_THEME_VARIANT"}, }, .visual_ids = {0}, .colormaps = {0}}; 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* 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 = {.notify = sl_client_destroy_notify}; int sv[2]; pid_t pid; int virtwl_display_fd = -1; int xdisplay = -1; int master = 0; int client_fd = -1; int rv; int i; 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, "--master") == arg) { master = 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, "--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, "--x-auth") == arg) { xauth_path = sl_arg_value(arg); } else if (strstr(arg, "--x-font-path") == arg) { xfont_path = sl_arg_value(arg); } 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 (master) { 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)); 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 { assert(errno == ENOENT); } sock_fd = socket(PF_LOCAL, SOCK_STREAM, 0); assert(sock_fd >= 0); rv = bind(sock_fd, (struct sockaddr*)&addr, offsetof(struct sockaddr_un, sun_path) + strlen(addr.sun_path)); assert(rv >= 0); rv = listen(sock_fd, 128); 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(); 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); 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(); assert(pid != -1); if (pid == 0) { char* client_fd_str; char* peer_pid_str; //char* peer_cmd_prefix_str; char* 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], 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); } // 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, .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); 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, .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; } // Use well known values for DPI by default with Xwayland. if (!dpi && ctx.xwayland) dpi = "72,96,160,240,320,480"; wl_array_init(&ctx.dpi); if (dpi) { char* str = strdup(dpi); char* token = strtok(str, ","); int* p; while (token) { p = wl_array_add(&ctx.dpi, sizeof *p); assert(p); *p = MAX(MIN_DPI, MIN(atoi(token), MAX_DPI)); token = strtok(NULL, ","); } free(str); } if (ctx.runprog || ctx.xwayland) { // Wayland connection from client. rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv); assert(!rv); client_fd = sv[0]; } // The success of this depends on xkb-data being installed. ctx.xkb_context = xkb_context_new(0); 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); // 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. ) 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, "", 9) == 0) { modifiers |= CONTROL_MASK; accelerators += 9; } else if (strncmp(accelerators, "", 5) == 0) { modifiers |= ALT_MASK; accelerators += 5; } else if (strncmp(accelerators, "", 7) == 0) { modifiers |= SHIFT_MASK; accelerators += 7; } else { fprintf(stderr, "error: invalid modifier\n"); return EXIT_FAILURE; } } else { struct sl_accelerator* accelerator; const char* end = strchrnul(accelerators, ','); char* name = strndup(accelerators, end - accelerators); 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); 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); assert(!rv); ctx.wm_fd = wm[0]; pid = fork(); assert(pid != -1); if (pid == 0) { char* display_fd_str; char* wm_fd_str; char* xwayland_cmd_prefix_str; char* 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], args, sv[1]); _exit(EXIT_FAILURE); } close(wm[1]); ctx.xwayland_pid = pid; } else { pid = fork(); 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]); } 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; }