// Copyright 2017 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "sommelier.h" // NOLINT(build/include_directory) #include "sommelier-tracing.h" // NOLINT(build/include_directory) #include "sommelier-transform.h" // NOLINT(build/include_directory) #include "sommelier-xshape.h" // NOLINT(build/include_directory) #include #include #include #include #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" // NOLINT(build/include_directory) #include "drm-server-protocol.h" // NOLINT(build/include_directory) #ifdef GAMEPAD_SUPPORT #include "gaming-input-unstable-v2-client-protocol.h" // NOLINT(build/include_directory) #endif #include "keyboard-extension-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) #include "linux-dmabuf-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) #include "linux-explicit-synchronization-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) #include "pointer-constraints-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) #include "relative-pointer-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) #include "text-input-extension-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) #include "text-input-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) #include "viewporter-client-protocol.h" // NOLINT(build/include_directory) #include "xdg-output-unstable-v1-client-protocol.h" // NOLINT(build/include_directory) #include "xdg-shell-client-protocol.h" // NOLINT(build/include_directory) // 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 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; }; #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 MIN_AURA_SHELL_VERSION 6 #define MAX_AURA_SHELL_VERSION 38 int sl_open_wayland_socket(const char* socket_name, struct sockaddr_un* addr, int* lock_fd, int* sock_fd); int sl_shm_format_for_drm_format(uint32_t drm_format) { switch (drm_format) { case WL_DRM_FORMAT_NV12: return WL_SHM_FORMAT_NV12; case WL_DRM_FORMAT_RGB565: return WL_SHM_FORMAT_RGB565; case WL_DRM_FORMAT_ARGB8888: return WL_SHM_FORMAT_ARGB8888; case WL_DRM_FORMAT_ABGR8888: return WL_SHM_FORMAT_ABGR8888; case WL_DRM_FORMAT_XRGB8888: return WL_SHM_FORMAT_XRGB8888; case WL_DRM_FORMAT_XBGR8888: return WL_SHM_FORMAT_XBGR8888; } assert(0); return 0; } uint32_t sl_drm_format_for_shm_format(int format) { switch (format) { case WL_SHM_FORMAT_NV12: return WL_DRM_FORMAT_NV12; case WL_SHM_FORMAT_RGB565: return WL_DRM_FORMAT_RGB565; case WL_SHM_FORMAT_ARGB8888: return WL_DRM_FORMAT_ARGB8888; case WL_SHM_FORMAT_ABGR8888: return WL_DRM_FORMAT_ABGR8888; case WL_SHM_FORMAT_XRGB8888: return WL_DRM_FORMAT_XRGB8888; case WL_SHM_FORMAT_XBGR8888: return WL_DRM_FORMAT_XBGR8888; } assert(0); return 0; } const char* net_wm_state_to_string(int i) { switch (i) { case NET_WM_STATE_REMOVE: return "NET_WM_STATE_REMOVE"; case NET_WM_STATE_ADD: return "NET_WM_STATE_ADD"; case NET_WM_STATE_TOGGLE: return "NET_WM_STATE_TOGGLE"; } return ""; } struct sl_sync_point* sl_sync_point_create(int fd) { TRACE_EVENT("sync", "sl_sync_point_create"); struct sl_sync_point* sync_point = new sl_sync_point(); sync_point->fd = fd; sync_point->sync = NULL; return sync_point; } void sl_sync_point_destroy(struct sl_sync_point* sync_point) { TRACE_EVENT("sync", "sl_sync_point_destroy"); close(sync_point->fd); delete sync_point; } static void sl_internal_xdg_shell_ping(void* data, struct xdg_wm_base* xdg_shell, uint32_t serial) { TRACE_EVENT("shell", "sl_internal_xdg_shell_ping"); xdg_wm_base_pong(xdg_shell, serial); } static const struct xdg_wm_base_listener sl_internal_xdg_shell_listener = { sl_internal_xdg_shell_ping}; static void sl_adjust_window_size_for_screen_size(struct sl_window* window) { TRACE_EVENT("surface", "sl_adjust_window_size_for_screen_size", "id", window->id); struct sl_context* ctx = window->ctx; // Clamp size to screen. window->width = MIN(window->width, ctx->screen->width_in_pixels); window->height = MIN(window->height, ctx->screen->height_in_pixels); } static void sl_adjust_window_position_for_screen_size( struct sl_window* window) { struct sl_context* ctx = window->ctx; // Center horizontally/vertically. window->x = ctx->screen->width_in_pixels / 2 - window->width / 2; window->y = ctx->screen->height_in_pixels / 2 - window->height / 2; } static void sl_set_input_focus(struct sl_context* ctx, struct sl_window* window) { if (window) { xcb_client_message_event_t event; event.response_type = XCB_CLIENT_MESSAGE; event.format = 32; event.window = window->id; event.type = ctx->atoms[ATOM_WM_PROTOCOLS].value; event.data.data32[0] = ctx->atoms[ATOM_WM_TAKE_FOCUS].value; event.data.data32[1] = XCB_CURRENT_TIME; if (!window->managed) return; if (window->focus_model_take_focus) { xcb_send_event(ctx->connection, 0, window->id, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast(&event)); } xcb_set_input_focus(ctx->connection, XCB_INPUT_FOCUS_NONE, window->id, XCB_CURRENT_TIME); } else { xcb_set_input_focus(ctx->connection, XCB_INPUT_FOCUS_NONE, XCB_NONE, XCB_CURRENT_TIME); } } void sl_restack_windows(struct sl_context* ctx, uint32_t focus_resource_id) { struct sl_window* sibling; uint32_t values[1]; wl_list_for_each(sibling, &ctx->windows, link) { if (!sibling->managed) continue; // Move focus window to the top and all other windows to the bottom. values[0] = sibling->host_surface_id == focus_resource_id ? XCB_STACK_MODE_ABOVE : XCB_STACK_MODE_BELOW; xcb_configure_window(ctx->connection, sibling->frame_id, XCB_CONFIG_WINDOW_STACK_MODE, values); } } void sl_roundtrip(struct sl_context* ctx) { TRACE_EVENT("other", "sl_roundtrip", "id", ctx->application_id != nullptr ? ctx->application_id : ""); free(xcb_get_input_focus_reply(ctx->connection, xcb_get_input_focus(ctx->connection), NULL)); } static void sl_window_set_wm_state(struct sl_window* window, int state) { TRACE_EVENT("surface", "sl_window_set_wm_state", "id", window->id); struct sl_context* ctx = window->ctx; uint32_t values[2]; values[0] = state; values[1] = XCB_WINDOW_NONE; xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, window->id, ctx->atoms[ATOM_WM_STATE].value, ctx->atoms[ATOM_WM_STATE].value, 32, 2, values); } static void sl_host_buffer_destroy(struct wl_client* client, struct wl_resource* resource) { TRACE_EVENT("surface", "sl_host_buffer_destroy"); wl_resource_destroy(resource); } static const struct wl_buffer_interface sl_buffer_implementation = { sl_host_buffer_destroy}; static void sl_buffer_release(void* data, struct wl_buffer* buffer) { struct sl_host_buffer* host = static_cast(wl_buffer_get_user_data(buffer)); auto resource_id = host->resource ? wl_resource_get_id(host->resource) : -1; TRACE_EVENT("surface", "sl_buffer_release", "resource_id", resource_id); if (host->ctx->timing != NULL) { host->ctx->timing->UpdateLastRelease(resource_id); } wl_buffer_send_release(host->resource); } static const struct wl_buffer_listener sl_buffer_listener = {sl_buffer_release}; static void sl_destroy_host_buffer(struct wl_resource* resource) { TRACE_EVENT("surface", "sl_destroy_host_buffer", "resource_id", resource ? wl_resource_get_id(resource) : -1); struct sl_host_buffer* host = static_cast(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); delete host; } struct sl_host_buffer* sl_create_host_buffer(struct sl_context* ctx, struct wl_client* client, uint32_t id, struct wl_buffer* proxy, int32_t width, int32_t height, bool is_drm) { TRACE_EVENT("surface", "sl_create_host_buffer", "id", id); struct sl_host_buffer* host_buffer = new sl_host_buffer(); host_buffer->ctx = ctx; 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_add_listener(host_buffer->proxy, &sl_buffer_listener, host_buffer); } host_buffer->sync_point = NULL; host_buffer->is_drm = is_drm; return host_buffer; } // NOLINT(whitespace/indent) static void sl_internal_data_offer_destroy(struct sl_data_offer* host) { TRACE_EVENT("other", "sl_internal_data_offer_destroy"); wl_data_offer_destroy(host->internal); wl_array_release(&host->atoms); wl_array_release(&host->cookies); delete host; } static void sl_set_selection(struct sl_context* ctx, struct sl_data_offer* data_offer) { TRACE_EVENT("other", "sl_set_selection"); if (ctx->selection_data_offer) { sl_internal_data_offer_destroy(ctx->selection_data_offer); ctx->selection_data_offer = NULL; } if (ctx->clipboard_manager) { if (!data_offer) { if (ctx->selection_owner == ctx->selection_window) xcb_set_selection_owner(ctx->connection, XCB_ATOM_NONE, ctx->atoms[ATOM_CLIPBOARD].value, ctx->selection_timestamp); return; } int atoms = data_offer->cookies.size / sizeof(xcb_intern_atom_cookie_t); wl_array_add(&data_offer->atoms, sizeof(xcb_atom_t) * (atoms + 2)); (reinterpret_cast(data_offer->atoms.data))[0] = ctx->atoms[ATOM_TARGETS].value; (reinterpret_cast(data_offer->atoms.data))[1] = ctx->atoms[ATOM_TIMESTAMP].value; for (int i = 0; i < atoms; i++) { xcb_intern_atom_cookie_t cookie = (reinterpret_cast( data_offer->cookies.data))[i]; xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(ctx->connection, cookie, NULL); if (reply) { (reinterpret_cast(data_offer->atoms.data))[i + 2] = reply->atom; free(reply); } } xcb_set_selection_owner(ctx->connection, ctx->selection_window, ctx->atoms[ATOM_CLIPBOARD].value, XCB_CURRENT_TIME); } ctx->selection_data_offer = data_offer; } static void sl_internal_data_offer_offer(void* data, struct wl_data_offer* data_offer, const char* type) { TRACE_EVENT("other", "sl_internal_data_offer_offer"); struct sl_data_offer* host = static_cast(data); xcb_intern_atom_cookie_t* cookie = static_cast( wl_array_add(&host->cookies, sizeof(xcb_intern_atom_cookie_t))); *cookie = xcb_intern_atom(host->ctx->connection, 0, strlen(type), type); } static void sl_internal_data_offer_source_actions( void* data, struct wl_data_offer* data_offer, uint32_t source_actions) { TRACE_EVENT("other", "sl_internal_data_offer_source_actions"); } static void sl_internal_data_offer_action(void* data, struct wl_data_offer* data_offer, uint32_t dnd_action) { TRACE_EVENT("other", "sl_internal_data_offer_action"); } static const struct wl_data_offer_listener sl_internal_data_offer_listener = { sl_internal_data_offer_offer, sl_internal_data_offer_source_actions, sl_internal_data_offer_action}; static void sl_internal_data_device_data_offer( void* data, struct wl_data_device* data_device, struct wl_data_offer* data_offer) { struct sl_context* ctx = (struct sl_context*)data; struct sl_data_offer* host_data_offer = new sl_data_offer(); host_data_offer->ctx = ctx; host_data_offer->internal = data_offer; wl_array_init(&host_data_offer->atoms); wl_array_init(&host_data_offer->cookies); wl_data_offer_add_listener(host_data_offer->internal, &sl_internal_data_offer_listener, host_data_offer); } static void sl_internal_data_device_enter(void* data, struct wl_data_device* data_device, uint32_t serial, struct wl_surface* surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer* data_offer) {} static void sl_internal_data_device_leave(void* data, struct wl_data_device* data_device) {} static void sl_internal_data_device_motion(void* data, struct wl_data_device* data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y) {} static void sl_internal_data_device_drop(void* data, struct wl_data_device* data_device) {} static void sl_internal_data_device_selection( void* data, struct wl_data_device* data_device, struct wl_data_offer* data_offer) { struct sl_context* ctx = (struct sl_context*)data; struct sl_data_offer* host_data_offer = data_offer ? static_cast(wl_data_offer_get_user_data(data_offer)) : NULL; sl_set_selection(ctx, host_data_offer); } static const struct wl_data_device_listener sl_internal_data_device_listener = { sl_internal_data_device_data_offer, sl_internal_data_device_enter, sl_internal_data_device_leave, sl_internal_data_device_motion, sl_internal_data_device_drop, sl_internal_data_device_selection}; void sl_host_seat_added(struct sl_host_seat* host) { struct sl_context* ctx = host->seat->ctx; if (ctx->default_seat) return; ctx->default_seat = host; // Get data device for selections. if (ctx->data_device_manager && ctx->data_device_manager->internal) { ctx->selection_data_device = wl_data_device_manager_get_data_device( ctx->data_device_manager->internal, host->proxy); wl_data_device_add_listener(ctx->selection_data_device, &sl_internal_data_device_listener, ctx); } #ifdef GAMEPAD_SUPPORT sl_gaming_seat_add_listener(ctx); #endif } void sl_host_seat_removed(struct sl_host_seat* host) { TRACE_EVENT("other", "sl_host_seat_removed"); if (host->seat->ctx->default_seat == host) host->seat->ctx->default_seat = NULL; } bool sl_client_supports_interface(const sl_context* ctx, const wl_client* client, const wl_interface* interface) { if (ctx->client == client) { return true; } // Clients created for IME support only receive the minimal set of required // interfaces. const char* name = interface->name; return strcmp(name, "wl_seat") == 0 || strcmp(name, "zwp_text_input_manager_v1") == 0 || strcmp(name, "zcr_text_input_extension_v1") == 0 || strcmp(name, "zcr_text_input_x11_v1") == 0; } static void sl_global_destroy(struct sl_global* global) { TRACE_EVENT("other", "sl_global_destroy"); struct sl_host_registry* registry; wl_list_for_each(registry, &global->ctx->registries, link) { if (sl_client_supports_interface(global->ctx, wl_resource_get_client(registry->resource), global->interface)) { wl_resource_post_event(registry->resource, WL_REGISTRY_GLOBAL_REMOVE, global->name); } } wl_list_remove(&global->link); free(global); } // Called on each "wl_registry::global" event from the host compositor, // giving Sommelier an opportunity to bind to the new global object // (so we can receive events or invoke requests on it), and/or forward the // wl_registry::global event on to our clients. void sl_registry_handler(void* data, struct wl_registry* registry, uint32_t id, const char* interface, uint32_t version) { struct sl_context* ctx = (struct sl_context*)data; TRACE_EVENT("other", "sl_registry_handler", "id", id); if (strcmp(interface, "wl_compositor") == 0) { sl_compositor_init_context(ctx, registry, id, version); } else if (strcmp(interface, "wl_subcompositor") == 0) { struct sl_subcompositor* subcompositor = static_cast(malloc(sizeof(struct sl_subcompositor))); assert(subcompositor); subcompositor->ctx = ctx; subcompositor->id = id; assert(!ctx->subcompositor); ctx->subcompositor = subcompositor; subcompositor->host_global = sl_subcompositor_global_create(ctx); } else if (strcmp(interface, "wl_shm") == 0) { struct sl_shm* shm = static_cast(malloc(sizeof(struct sl_shm))); assert(shm); shm->ctx = ctx; shm->id = id; shm->internal = static_cast( wl_registry_bind(registry, id, &wl_shm_interface, 1)); assert(!ctx->shm); ctx->shm = shm; shm->host_global = sl_shm_global_create(ctx); } else if (strcmp(interface, "wl_shell") == 0) { struct sl_shell* shell = static_cast(malloc(sizeof(struct sl_shell))); assert(shell); shell->ctx = ctx; shell->id = id; assert(!ctx->shell); ctx->shell = shell; shell->host_global = sl_shell_global_create(ctx); } else if (strcmp(interface, "wl_output") == 0) { struct sl_output* output = static_cast(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); output->host_output = NULL; } else if (strcmp(interface, "wl_seat") == 0) { struct sl_seat* seat = static_cast(malloc(sizeof(struct sl_seat))); assert(seat); seat->ctx = ctx; seat->id = id; seat->version = MIN(5, version); seat->last_serial = 0; seat->host_global = sl_seat_global_create(seat); wl_list_insert(&ctx->seats, &seat->link); } else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0) { struct sl_relative_pointer_manager* relative_pointer = static_cast( malloc(sizeof(struct sl_relative_pointer_manager))); assert(relative_pointer); relative_pointer->ctx = ctx; relative_pointer->id = id; assert(!ctx->relative_pointer_manager); ctx->relative_pointer_manager = relative_pointer; relative_pointer->host_global = sl_relative_pointer_manager_global_create(ctx); } else if (strcmp(interface, "zwp_pointer_constraints_v1") == 0) { struct sl_pointer_constraints* pointer_constraints = static_cast( malloc(sizeof(struct sl_pointer_constraints))); assert(pointer_constraints); pointer_constraints->ctx = ctx; pointer_constraints->id = id; assert(!ctx->pointer_constraints); ctx->pointer_constraints = pointer_constraints; pointer_constraints->host_global = sl_pointer_constraints_global_create(ctx); } else if (strcmp(interface, "wl_data_device_manager") == 0) { struct sl_data_device_manager* data_device_manager = static_cast( malloc(sizeof(struct sl_data_device_manager))); assert(data_device_manager); data_device_manager->ctx = ctx; data_device_manager->id = id; data_device_manager->version = MIN(3, version); data_device_manager->internal = NULL; data_device_manager->host_global = NULL; assert(!ctx->data_device_manager); ctx->data_device_manager = data_device_manager; if (ctx->xwayland) { data_device_manager->internal = static_cast( wl_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, "xdg_wm_base") == 0) { struct sl_xdg_shell* xdg_shell = static_cast(malloc(sizeof(struct sl_xdg_shell))); assert(xdg_shell); xdg_shell->ctx = ctx; xdg_shell->id = id; xdg_shell->internal = NULL; xdg_shell->host_global = NULL; assert(!ctx->xdg_shell); ctx->xdg_shell = xdg_shell; if (ctx->xwayland) { xdg_shell->internal = static_cast( wl_registry_bind(registry, id, &xdg_wm_base_interface, 1)); xdg_wm_base_add_listener(xdg_shell->internal, &sl_internal_xdg_shell_listener, NULL); } else { xdg_shell->host_global = sl_xdg_shell_global_create(ctx); } } else if (strcmp(interface, "zaura_shell") == 0) { if (version >= MIN_AURA_SHELL_VERSION) { struct sl_aura_shell* aura_shell = static_cast(malloc(sizeof(struct sl_aura_shell))); assert(aura_shell); aura_shell->ctx = ctx; aura_shell->id = id; aura_shell->version = MIN(MAX_AURA_SHELL_VERSION, version); aura_shell->host_gtk_shell_global = NULL; aura_shell->internal = static_cast(wl_registry_bind( registry, id, &zaura_shell_interface, aura_shell->version)); assert(!ctx->aura_shell); ctx->aura_shell = aura_shell; aura_shell->host_gtk_shell_global = sl_gtk_shell_global_create(ctx); } } else if (strcmp(interface, "wp_viewporter") == 0) { struct sl_viewporter* viewporter = static_cast(malloc(sizeof(struct sl_viewporter))); assert(viewporter); viewporter->ctx = ctx; viewporter->id = id; viewporter->host_viewporter_global = NULL; viewporter->internal = static_cast( wl_registry_bind(registry, id, &wp_viewporter_interface, 1)); assert(!ctx->viewporter); ctx->viewporter = viewporter; viewporter->host_viewporter_global = sl_viewporter_global_create(ctx); // Allow non-integer scale. ctx->scale = MIN(MAX_SCALE, MAX(MIN_SCALE, ctx->desired_scale)); } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { struct sl_linux_dmabuf* linux_dmabuf = static_cast(malloc(sizeof(struct sl_linux_dmabuf))); assert(linux_dmabuf); linux_dmabuf->ctx = ctx; linux_dmabuf->id = id; linux_dmabuf->version = MIN(2, version); linux_dmabuf->internal = static_cast(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, "zwp_linux_explicit_synchronization_v1") == 0) { struct sl_linux_explicit_synchronization* linux_explicit_synchronization = static_cast( malloc(sizeof(struct sl_linux_explicit_synchronization))); assert(linux_explicit_synchronization); linux_explicit_synchronization->ctx = ctx; linux_explicit_synchronization->id = id; linux_explicit_synchronization->internal = static_cast(wl_registry_bind( registry, id, &zwp_linux_explicit_synchronization_v1_interface, 1)); assert(!ctx->linux_explicit_synchronization); ctx->linux_explicit_synchronization = linux_explicit_synchronization; } else if (strcmp(interface, "zcr_keyboard_extension_v1") == 0) { struct sl_keyboard_extension* keyboard_extension = static_cast( malloc(sizeof(struct sl_keyboard_extension))); assert(keyboard_extension); keyboard_extension->ctx = ctx; keyboard_extension->id = id; keyboard_extension->internal = static_cast(wl_registry_bind( registry, id, &zcr_keyboard_extension_v1_interface, 1)); assert(!ctx->keyboard_extension); ctx->keyboard_extension = keyboard_extension; } else if (strcmp(interface, "zwp_text_input_manager_v1") == 0) { struct sl_text_input_manager* text_input_manager = static_cast( malloc(sizeof(struct sl_text_input_manager))); assert(text_input_manager); text_input_manager->ctx = ctx; text_input_manager->id = id; text_input_manager->host_global = sl_text_input_manager_global_create(ctx); text_input_manager->host_x11_global = sl_text_input_x11_global_create(ctx); assert(!ctx->text_input_manager); ctx->text_input_manager = text_input_manager; } else if (strcmp(interface, "zcr_text_input_extension_v1") == 0) { struct sl_text_input_extension* text_input_extension = static_cast( malloc(sizeof(struct sl_text_input_extension))); assert(text_input_extension); text_input_extension->ctx = ctx; text_input_extension->id = id; text_input_extension->host_global = sl_text_input_extension_global_create(ctx); assert(!ctx->text_input_extension); ctx->text_input_extension = text_input_extension; #ifdef GAMEPAD_SUPPORT } else if (strcmp(interface, "zcr_gaming_input_v2") == 0) { struct sl_gaming_input_manager* gaming_input_manager = static_cast( malloc(sizeof(struct sl_gaming_input_manager))); assert(gaming_input_manager); gaming_input_manager->ctx = ctx; gaming_input_manager->id = id; gaming_input_manager->internal = static_cast( wl_registry_bind(registry, id, &zcr_gaming_input_v2_interface, 2)); assert(!ctx->gaming_input_manager); ctx->gaming_input_manager = gaming_input_manager; #endif } else if (strcmp(interface, "zxdg_output_manager_v1") == 0 && ctx->use_direct_scale) { // This protocol cannot be bound unconditionally as doing so // causes issues under Crostini. For this reason we will only // bind it when we need to do so (direct mode enabled). struct sl_xdg_output_manager* output_manager = static_cast( malloc(sizeof(struct sl_xdg_output_manager))); assert(output_manager); output_manager->ctx = ctx; output_manager->id = id; output_manager->internal = static_cast(wl_registry_bind( registry, id, &zxdg_output_manager_v1_interface, MIN(3, version))); ctx->xdg_output_manager = output_manager; } } static void sl_registry_remover(void* data, struct wl_registry* registry, uint32_t id) { TRACE_EVENT("other", "sl_registry_remover"); struct sl_context* ctx = (struct sl_context*)data; struct sl_output* output; struct sl_seat* seat; if (ctx->compositor && ctx->compositor->id == id) { sl_global_destroy(ctx->compositor->host_global); wl_compositor_destroy(ctx->compositor->internal); free(ctx->compositor); ctx->compositor = NULL; return; } if (ctx->subcompositor && ctx->subcompositor->id == id) { sl_global_destroy(ctx->subcompositor->host_global); wl_shm_destroy(ctx->shm->internal); free(ctx->subcompositor); ctx->subcompositor = NULL; return; } if (ctx->shm && ctx->shm->id == id) { sl_global_destroy(ctx->shm->host_global); free(ctx->shm); ctx->shm = NULL; return; } if (ctx->shell && ctx->shell->id == id) { sl_global_destroy(ctx->shell->host_global); free(ctx->shell); ctx->shell = NULL; return; } if (ctx->data_device_manager && ctx->data_device_manager->id == id) { if (ctx->data_device_manager->host_global) sl_global_destroy(ctx->data_device_manager->host_global); if (ctx->data_device_manager->internal) wl_data_device_manager_destroy(ctx->data_device_manager->internal); free(ctx->data_device_manager); ctx->data_device_manager = NULL; return; } if (ctx->xdg_shell && ctx->xdg_shell->id == id) { if (ctx->xdg_shell->host_global) sl_global_destroy(ctx->xdg_shell->host_global); if (ctx->xdg_shell->internal) xdg_wm_base_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->linux_explicit_synchronization && ctx->linux_explicit_synchronization->id == id) { zwp_linux_explicit_synchronization_v1_destroy( ctx->linux_explicit_synchronization->internal); free(ctx->linux_explicit_synchronization); ctx->linux_explicit_synchronization = 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->text_input_extension && ctx->text_input_extension->id == id) { sl_global_destroy(ctx->text_input_extension->host_global); free(ctx->text_input_extension); ctx->text_input_extension = NULL; return; } #ifdef GAMEPAD_SUPPORT if (ctx->gaming_input_manager && ctx->gaming_input_manager->id == id) { zcr_gaming_input_v2_destroy(ctx->gaming_input_manager->internal); free(ctx->gaming_input_manager); ctx->gaming_input_manager = NULL; return; } #endif if (ctx->relative_pointer_manager && ctx->relative_pointer_manager->id == id) { sl_global_destroy(ctx->relative_pointer_manager->host_global); free(ctx->relative_pointer_manager); ctx->relative_pointer_manager = NULL; return; } if (ctx->pointer_constraints && ctx->pointer_constraints->id == id) { sl_global_destroy(ctx->pointer_constraints->host_global); free(ctx->pointer_constraints); ctx->pointer_constraints = NULL; return; } wl_list_for_each(output, &ctx->outputs, link) { if (output->id == id) { sl_global_destroy(output->host_global); if (output->host_output) wl_resource_destroy(output->host_output->resource); 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); } const struct wl_registry_listener sl_registry_listener = {sl_registry_handler, sl_registry_remover}; static int sl_handle_event(int fd, uint32_t mask, void* data) { TRACE_EVENT("other", "sl_handle_event"); struct sl_context* ctx = (struct sl_context*)data; int count = 0; if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { wl_client_flush(ctx->client); exit(EXIT_SUCCESS); } if (mask & WL_EVENT_READABLE) count = wl_display_dispatch(ctx->display); if (mask & WL_EVENT_WRITABLE) wl_display_flush(ctx->display); if (mask == 0) { count = wl_display_dispatch_pending(ctx->display); wl_display_flush(ctx->display); } return count; } void sl_create_window(struct sl_context* ctx, xcb_window_t id, int x, int y, int width, int height, int border_width) { TRACE_EVENT("surface", "sl_create_window"); sl_window* window = new sl_window(ctx, id, x, y, width, height, border_width); uint32_t values[1]; 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); // Also enable shape events for this window to come in if the xshape // flag has been enabled if (ctx->enable_xshape) xcb_shape_select_input(ctx->connection, id, 1); } static void sl_destroy_window(struct sl_window* window) { TRACE_EVENT("surface", "sl_destroy_window"); if (window->frame_id != XCB_WINDOW_NONE) xcb_destroy_window(window->ctx->connection, window->frame_id); if (window->xdg_popup) xdg_popup_destroy(window->xdg_popup); if (window->xdg_toplevel) xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_surface) xdg_surface_destroy(window->xdg_surface); if (window->aura_surface) zaura_surface_destroy(window->aura_surface); delete 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; } 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; } 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); } 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); } 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 const char* sl_decode_wm_class(struct sl_window* window, xcb_get_property_reply_t* reply) { // WM_CLASS property contains two consecutive null-terminated strings. // These specify the Instance and Class names. If a global app ID is // not set then use Class name for app ID. const char* value = static_cast(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); return window->clazz; } return nullptr; } static void sl_set_application_id_from_atom(struct sl_context* ctx, struct sl_window* window, xcb_get_property_reply_t* reply) { if (reply->type == XCB_ATOM_CARDINAL) { uint32_t value = *static_cast(xcb_get_property_value(reply)); window->app_id_property = std::to_string(value); } } void sl_handle_map_request(struct sl_context* ctx, xcb_map_request_event_t* event) { TRACE_EVENT("shm", "sl_handle_map_request", [&](perfetto::EventContext p) { perfetto_annotate_window(ctx, p, "window", event->window); }); 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_NET_WM_NAME, ctx->atoms[ATOM_NET_WM_NAME].value}, {PROPERTY_WM_CLASS, XCB_ATOM_WM_CLASS}, {PROPERTY_WM_TRANSIENT_FOR, XCB_ATOM_WM_TRANSIENT_FOR}, {PROPERTY_WM_NORMAL_HINTS, XCB_ATOM_WM_NORMAL_HINTS}, {PROPERTY_WM_CLIENT_LEADER, ctx->atoms[ATOM_WM_CLIENT_LEADER].value}, {PROPERTY_WM_PROTOCOLS, ctx->atoms[ATOM_WM_PROTOCOLS].value}, {PROPERTY_MOTIF_WM_HINTS, ctx->atoms[ATOM_MOTIF_WM_HINTS].value}, {PROPERTY_NET_STARTUP_ID, ctx->atoms[ATOM_NET_STARTUP_ID].value}, {PROPERTY_NET_WM_STATE, ctx->atoms[ATOM_NET_WM_STATE].value}, {PROPERTY_GTK_THEME_VARIANT, ctx->atoms[ATOM_GTK_THEME_VARIANT].value}, {PROPERTY_XWAYLAND_RANDR_EMU_MONITOR_RECTS, ctx->atoms[ATOM_XWAYLAND_RANDR_EMU_MONITOR_RECTS].value}, {PROPERTY_SPECIFIED_FOR_APP_ID, ctx->application_id_property_atom}, }; 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}; bool maximize_h = false, maximize_v = false, fullscreen = false; uint32_t values[5]; if (!window) return; if (sl_is_our_window(ctx, event->window)) return; window->managed = 1; if (window->frame_id == XCB_WINDOW_NONE) geometry_cookie = xcb_get_geometry(ctx->connection, window->id); for (unsigned i = 0; i < ARRAY_SIZE(properties); ++i) { property_cookies[i] = xcb_get_property(ctx->connection, 0, window->id, properties[i].atom, XCB_ATOM_ANY, 0, 2048); } if (window->frame_id == XCB_WINDOW_NONE) { xcb_get_geometry_reply_t* geometry_reply = xcb_get_geometry_reply(ctx->connection, geometry_cookie, NULL); if (geometry_reply) { window->x = geometry_reply->x; window->y = geometry_reply->y; window->width = geometry_reply->width; window->height = geometry_reply->height; window->depth = geometry_reply->depth; free(geometry_reply); } } free(window->name); window->name = NULL; free(window->clazz); window->clazz = NULL; free(window->startup_id); window->startup_id = NULL; window->transient_for = XCB_WINDOW_NONE; window->client_leader = XCB_WINDOW_NONE; window->decorated = 1; window->size_flags = 0; window->dark_frame = 0; for (unsigned i = 0; i < ARRAY_SIZE(properties); ++i) { xcb_get_property_reply_t* reply = xcb_get_property_reply(ctx->connection, property_cookies[i], NULL); if (!reply) continue; if (reply->type == XCB_ATOM_NONE) { free(reply); continue; } const char* value = nullptr; int value_int = std::numeric_limits::max(); xcb_atom_t* reply_atoms = nullptr; switch (properties[i].type) { case PROPERTY_WM_NAME: // WM_NAME is only used for the window name if _NET_WM_NAME is not // present or not yet processed, thus window->name is null. if (!window->has_net_wm_name) { window->name = strndup(static_cast(xcb_get_property_value(reply)), xcb_get_property_value_length(reply)); value = window->name; } break; case PROPERTY_NET_WM_NAME: // _NET_WM_NAME is the preferred window name property. Remove any // previously set window name. if (window->name) { free(window->name); window->name = nullptr; } window->has_net_wm_name = true; window->name = strndup(static_cast(xcb_get_property_value(reply)), xcb_get_property_value_length(reply)); value = window->name; break; case PROPERTY_WM_CLASS: value = sl_decode_wm_class(window, reply); if (!value) value = ""; break; case PROPERTY_WM_TRANSIENT_FOR: if (xcb_get_property_value_length(reply) >= 4) { window->transient_for = *(reinterpret_cast(xcb_get_property_value(reply))); value_int = window->transient_for; } break; case PROPERTY_WM_NORMAL_HINTS: if (xcb_get_property_value_length(reply) >= static_cast(sizeof(size_hints))) memcpy(&size_hints, xcb_get_property_value(reply), sizeof(size_hints)); break; case PROPERTY_WM_CLIENT_LEADER: if (xcb_get_property_value_length(reply) >= 4) { window->client_leader = *(reinterpret_cast(xcb_get_property_value(reply))); value_int = window->client_leader; } break; case PROPERTY_WM_PROTOCOLS: reply_atoms = static_cast(xcb_get_property_value(reply)); for (unsigned j = 0; j < xcb_get_property_value_length(reply) / sizeof(xcb_atom_t); ++j) { if (reply_atoms[j] == ctx->atoms[ATOM_WM_TAKE_FOCUS].value) { window->focus_model_take_focus = 1; value = "ATOM_WM_TAKE_FOCUS"; } } break; case PROPERTY_MOTIF_WM_HINTS: if (xcb_get_property_value_length(reply) >= static_cast(sizeof(mwm_hints))) memcpy(&mwm_hints, xcb_get_property_value(reply), sizeof(mwm_hints)); break; case PROPERTY_NET_STARTUP_ID: window->startup_id = strndup(static_cast(xcb_get_property_value(reply)), xcb_get_property_value_length(reply)); value = window->startup_id; break; case PROPERTY_NET_WM_STATE: reply_atoms = static_cast(xcb_get_property_value(reply)); for (unsigned j = 0; j < xcb_get_property_value_length(reply) / sizeof(xcb_atom_t); ++j) { if (reply_atoms[j] == ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value) { maximize_h = true; } else if (reply_atoms[j] == ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value) { maximize_v = true; } else if (reply_atoms[j] == ctx->atoms[ATOM_NET_WM_STATE_FULLSCREEN].value) { fullscreen = 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; window->fullscreen = fullscreen; if (window->maximized) { if (window->fullscreen) { value = "_NET_WM_STATE_FULLSCREEN, _NET_WM_STATE_MAXIMIZED_VERT && " "HORZ"; } else { value = "_NET_WM_STATE_MAXIMIZED_VERT && HORZ"; } } else if (window->fullscreen) { value = "_NET_WM_STATE_FULLSCREEN"; } break; case PROPERTY_GTK_THEME_VARIANT: if (xcb_get_property_value_length(reply) >= 4) window->dark_frame = !strcmp( static_cast(xcb_get_property_value(reply)), "dark"); break; case PROPERTY_SPECIFIED_FOR_APP_ID: sl_set_application_id_from_atom(ctx, window, reply); value = window->app_id_property.c_str(); break; default: break; } TRACE_EVENT("x11wm", "XCB_MAP_REQUEST: X property", [&](perfetto::EventContext p) { perfetto_annotate_atom(ctx, p, "name", properties[i].atom); if (value_int != std::numeric_limits::max()) { auto* dbg = p.event()->add_debug_annotations(); dbg->set_name("int value"); dbg->set_int_value(value_int); } if (value) { auto* dbg = p.event()->add_debug_annotations(); dbg->set_name("str value"); dbg->set_string_value(value, strlen(value)); } switch (properties[i].type) { case PROPERTY_WM_NORMAL_HINTS: perfetto_annotate_size_hints(p, size_hints); break; case PROPERTY_XWAYLAND_RANDR_EMU_MONITOR_RECTS: perfetto_annotate_cardinal_list(p, "value", reply); break; } }); free(reply); } if (mwm_hints.flags & MWM_HINTS_DECORATIONS) { if (mwm_hints.decorations & MWM_DECOR_ALL) window->decorated = ~mwm_hints.decorations & MWM_DECOR_TITLE; else window->decorated = mwm_hints.decorations & MWM_DECOR_TITLE; } // Allow user/program controlled position for transients. if (window->transient_for) window->size_flags |= size_hints.flags & (US_POSITION | P_POSITION); // If startup ID is not set, then try the client leader window. if (!window->startup_id && window->client_leader) { xcb_get_property_reply_t* reply = xcb_get_property_reply( ctx->connection, xcb_get_property(ctx->connection, 0, window->client_leader, ctx->atoms[ATOM_NET_STARTUP_ID].value, XCB_ATOM_ANY, 0, 2048), NULL); if (reply) { if (reply->type != XCB_ATOM_NONE) { window->startup_id = strndup(static_cast(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) {} 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; } 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); if (!window) return; int width = window->width; int height = window->height; uint32_t values[7]; if (sl_is_our_window(ctx, event->window)) return; if (!window->managed) { int i = 0; if (event->value_mask & XCB_CONFIG_WINDOW_X) values[i++] = event->x; if (event->value_mask & XCB_CONFIG_WINDOW_Y) values[i++] = event->y; if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH) values[i++] = event->width; if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT) values[i++] = event->height; if (event->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) values[i++] = event->border_width; if (event->value_mask & XCB_CONFIG_WINDOW_SIBLING) values[i++] = event->sibling; if (event->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) values[i++] = event->stack_mode; xcb_configure_window(ctx->connection, window->id, event->value_mask, values); return; } // Ack configure events as satisfying request removes the guarantee // that matching contents will arrive. if (window->xdg_toplevel) { if (window->pending_config.serial) { xdg_surface_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) { xdg_surface_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 XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; case NET_WM_MOVERESIZE_SIZE_TOP: return XDG_TOPLEVEL_RESIZE_EDGE_TOP; case NET_WM_MOVERESIZE_SIZE_TOPRIGHT: return XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; case NET_WM_MOVERESIZE_SIZE_RIGHT: return XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; case NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; case NET_WM_MOVERESIZE_SIZE_BOTTOM: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; case NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; case NET_WM_MOVERESIZE_SIZE_LEFT: return XDG_TOPLEVEL_RESIZE_EDGE_LEFT; default: return XDG_TOPLEVEL_RESIZE_EDGE_NONE; } } static void sl_request_attention(struct sl_context* ctx, struct sl_window* window, bool is_strong_request) { if (!window->aura_surface || ctx->aura_shell->version < ZAURA_SURFACE_DRAW_ATTENTION_SINCE_VERSION) return; if (is_strong_request) { zaura_surface_activate(window->aura_surface); } else { zaura_surface_draw_attention(window->aura_surface); } } void sl_handle_client_message(struct sl_context* ctx, xcb_client_message_event_t* event) { TRACE_EVENT("x11wm", "XCB_CLIENT_MESSAGE", [&](perfetto::EventContext p) { perfetto_annotate_atom(ctx, p, "event->type", event->type); perfetto_annotate_window(ctx, p, "event->window", event->window); }); if (event->type == ctx->atoms[ATOM_WL_SURFACE_ID].value) { struct sl_window *window, *unpaired_window = NULL; wl_list_for_each(window, &ctx->unpaired_windows, link) { if (sl_is_window(window, event->window)) { unpaired_window = window; break; } } if (unpaired_window) { unpaired_window->host_surface_id = event->data.data32[0]; sl_window_update(unpaired_window); } } else if (event->type == ctx->atoms[ATOM_NET_ACTIVE_WINDOW].value) { struct sl_window* window = sl_lookup_window(ctx, event->window); if (window) sl_request_attention(ctx, window, /*is_strong_request=*/true); } else if (event->type == ctx->atoms[ATOM_NET_WM_MOVERESIZE].value) { struct sl_window* window = sl_lookup_window(ctx, event->window); if (window && window->xdg_toplevel) { struct sl_host_seat* seat = window->ctx->default_seat; if (!seat) return; if (event->data.data32[2] == NET_WM_MOVERESIZE_MOVE) { xdg_toplevel_move(window->xdg_toplevel, seat->proxy, seat->seat->last_serial); } else { uint32_t edge = sl_resize_edge(event->data.data32[2]); if (edge == XDG_TOPLEVEL_RESIZE_EDGE_NONE) return; xdg_toplevel_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) { int changed[ATOM_LAST + 1]; uint32_t action = event->data.data32[0]; unsigned i; for (i = 0; i < ARRAY_SIZE(ctx->atoms); ++i) { changed[i] = event->data.data32[1] == ctx->atoms[i].value || event->data.data32[2] == ctx->atoms[i].value; } if (changed[ATOM_NET_WM_STATE_FULLSCREEN]) { TRACE_EVENT("x11wm", "XCB_CLIENT_MESSAGE: ATOM_NET_WM_STATE_FULLSCREEN", "action", net_wm_state_to_string(action), "window->name", window->name); if (action == NET_WM_STATE_ADD) { window->fullscreen = 1; if (window->xdg_toplevel && !window->iconified) { xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); } else { window->pending_fullscreen_change = true; } } else if (action == NET_WM_STATE_REMOVE) { window->fullscreen = 0; if (window->xdg_toplevel && !window->iconified) { xdg_toplevel_unset_fullscreen(window->xdg_toplevel); } else { window->pending_fullscreen_change = true; } } } if (changed[ATOM_NET_WM_STATE_MAXIMIZED_VERT] && changed[ATOM_NET_WM_STATE_MAXIMIZED_HORZ]) { TRACE_EVENT( "x11wm", "XCB_CLIENT_MESSAGE: ATOM_NET_WM_STATE_MAXIMIZED_VERT && HORZ", "action", net_wm_state_to_string(action), "window->name", window->name); if (action == NET_WM_STATE_ADD) { window->maximized = 1; if (window->xdg_toplevel && !window->iconified) { xdg_toplevel_set_maximized(window->xdg_toplevel); } else { window->pending_maximized_change = true; } } else if (action == NET_WM_STATE_REMOVE) { window->maximized = 0; if (window->xdg_toplevel && !window->iconified) { xdg_toplevel_unset_maximized(window->xdg_toplevel); } else { window->pending_maximized_change = true; } } } } } 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); TRACE_EVENT("x11wm", "XCB_CLIENT_MESSAGE: WM_STATE_ICONIC (minimize)", "window->name", window ? window->name : ""); if (window && window->xdg_toplevel) { xdg_toplevel_set_minimized(window->xdg_toplevel); #ifdef BLACK_SCREEN_FIX // Workaround for some borealis apps showing a black screen after losing // focus from fullscreen. // When a window is iconified, it should be unmapped. To return it back // to a visible state, it should be remapped. However sommelier does not // do this. Therefore we are sending a synthetic unmap then map notify // so that the app is rendered again. xcb_unmap_notify_event_t unmap_event = {.response_type = XCB_UNMAP_NOTIFY, .pad0 = 0, .event = window->id, .window = window->id, .from_configure = 0}; xcb_send_event(ctx->connection, 0, window->id, XCB_EVENT_MASK_STRUCTURE_NOTIFY, reinterpret_cast(&unmap_event)); sl_send_configure_notify(window); sl_window_set_wm_state(window, WM_STATE_ICONIC); sl_send_configure_notify(window); xcb_map_notify_event_t map_event = {.response_type = XCB_MAP_NOTIFY, .pad0 = 0, .event = window->id, .window = window->id, .override_redirect = 0}; xcb_send_event(ctx->connection, 0, window->id, XCB_EVENT_MASK_STRUCTURE_NOTIFY, reinterpret_cast(&map_event)); sl_send_configure_notify(window); sl_window_set_wm_state(window, WM_STATE_NORMAL); sl_send_configure_notify(window); sl_set_input_focus(ctx, nullptr); xcb_flush(ctx->connection); // When we are iconified we want to suppress any calls that deiconify // the window as it should in theory be unmapped. window->iconified = 1; #endif } } } 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) xdg_toplevel_set_parent(window->xdg_toplevel, parent->xdg_toplevel); } if (window) { if (window->xdg_toplevel && window->pending_fullscreen_change) { if (window->fullscreen) { xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); } else { xdg_toplevel_unset_fullscreen(window->xdg_toplevel); } window->pending_fullscreen_change = false; } if (window->xdg_toplevel && window->pending_maximized_change) { if (window->maximized) { xdg_toplevel_set_maximized(window->xdg_toplevel); } else { xdg_toplevel_unset_maximized(window->xdg_toplevel); } window->pending_maximized_change = false; } window->iconified = 0; } } static void sl_handle_focus_out(struct sl_context* ctx, xcb_focus_out_event_t* event) {} int sl_begin_data_source_send(struct sl_context* ctx, int fd, xcb_intern_atom_cookie_t cookie, struct sl_data_source* data_source) { xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(ctx->connection, cookie, NULL); if (!reply) { close(fd); return 0; } int flags, rv; xcb_convert_selection(ctx->connection, ctx->selection_window, ctx->atoms[ATOM_CLIPBOARD].value, reply->atom, ctx->atoms[ATOM_WL_SELECTION].value, XCB_CURRENT_TIME); flags = fcntl(fd, F_GETFL, 0); rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK); errno_assert(!rv); ctx->selection_data_source_send_fd = fd; free(reply); return 1; } void sl_process_data_source_send_pending_list(struct sl_context* ctx) { while (!wl_list_empty(&ctx->selection_data_source_send_pending)) { struct wl_list* next = ctx->selection_data_source_send_pending.next; struct sl_data_source_send_request* request; request = wl_container_of(next, request, link); wl_list_remove(next); int rv = sl_begin_data_source_send(ctx, request->fd, request->cookie, request->data_source); free(request); if (rv) break; } } static int sl_handle_selection_fd_writable(int fd, uint32_t mask, void* data) { struct sl_context* ctx = static_cast(data); int bytes, bytes_left; uint8_t* value = static_cast( 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) { ctx->selection_send_event_source.reset(); } 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.reset(wl_event_loop_add_fd( wl_display_get_event_loop(ctx->host_display), ctx->selection_data_source_send_fd, WL_EVENT_WRITABLE, sl_handle_selection_fd_writable, ctx)); } static void sl_send_selection_notify(struct sl_context* ctx, xcb_atom_t property) { TRACE_EVENT("other", "sl_send_selection_notify"); xcb_selection_notify_event_t event = { .response_type = XCB_SELECTION_NOTIFY, .pad0 = 0, .sequence = 0, .time = ctx->selection_request.time, .requestor = ctx->selection_request.requestor, .selection = ctx->selection_request.selection, .target = ctx->selection_request.target, .property = property}; xcb_send_event(ctx->connection, 0, ctx->selection_request.requestor, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast(&event)); } static void sl_send_selection_data(struct sl_context* ctx) { assert(!ctx->selection_data_ack_pending); xcb_change_property( ctx->connection, XCB_PROP_MODE_REPLACE, ctx->selection_request.requestor, ctx->selection_request.property, ctx->selection_data_type, /*format=*/8, ctx->selection_data.size, ctx->selection_data.data); ctx->selection_data_ack_pending = 1; ctx->selection_data.size = 0; } static const uint32_t sl_incr_chunk_size = 64 * 1024; static int sl_handle_selection_fd_readable(int fd, uint32_t mask, void* data) { struct sl_context* ctx = static_cast(data); // When a selection starts, the wl_array in |ctx->selection_data| is // initialized with a size of zero. Since we now need to actually write into // it, allocate |sl_incr_chunk_size| bytes to store the selection data in. We // need to buffer this much to decide between a one-shot transfer and an // incremental transfer, as this decision must be made before the first // response is sent. if (ctx->selection_data.alloc == 0) { // wl_array_add is ostensibly failable, but the only failure case comes from // calling malloc, and if that fails we should just die anyway. errno_assert( (size_t)wl_array_add(&ctx->selection_data, sl_incr_chunk_size)); // wl_array_add increments |size| as well as |alloc|, but we don't actually // want that yet. Instead we will set |size| later based on the results of // the read call. ctx->selection_data.size -= sl_incr_chunk_size; } int offset = ctx->selection_data.size; void* p = reinterpret_cast(ctx->selection_data.data) + offset; int bytes_left = ctx->selection_data.alloc - offset; int 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; } } ctx->selection_event_source.reset(); return 1; } void sl_handle_property_notify(struct sl_context* ctx, xcb_property_notify_event_t* event) { TRACE_EVENT("x11wm", "XCB_PROPERTY_NOTIFY", [&](perfetto::EventContext p) { perfetto_annotate_atom(ctx, p, "event->atom", event->atom); perfetto_annotate_xcb_property_state(p, "event->state", event->state); perfetto_annotate_window(ctx, p, "event->window", event->window); }); if (event->atom == XCB_ATOM_WM_NAME || event->atom == ctx->atoms[ATOM_NET_WM_NAME].value) { struct sl_window* window = sl_lookup_window(ctx, event->window); if (!window) return; bool atom_is_net_wm_name = event->atom == ctx->atoms[ATOM_NET_WM_NAME].value; // If _NET_WM_NAME is set, ignore changes to WM_NAME. if (!atom_is_net_wm_name && window->has_net_wm_name) return; if (window->name) { free(window->name); window->name = NULL; window->has_net_wm_name = false; } xcb_atom_t atom = XCB_ATOM_NONE; if (event->state != XCB_PROPERTY_DELETE) atom = event->atom; else if (atom_is_net_wm_name) // If _NET_WM_NAME is deleted, fall back to WM_NAME if it is set. atom = XCB_ATOM_WM_NAME; if (atom != XCB_ATOM_NONE) { xcb_get_property_reply_t* reply = xcb_get_property_reply( ctx->connection, xcb_get_property(ctx->connection, 0, window->id, atom, XCB_ATOM_ANY, 0, 2048), NULL); if (reply) { window->name = strndup(static_cast(xcb_get_property_value(reply)), xcb_get_property_value_length(reply)); free(reply); if (atom == ctx->atoms[ATOM_NET_WM_NAME].value) { window->has_net_wm_name = true; } } } if (!window->xdg_toplevel) return; if (window->name) { xdg_toplevel_set_title(window->xdg_toplevel, window->name); } else { xdg_toplevel_set_title(window->xdg_toplevel, ""); } } else if (event->atom == XCB_ATOM_WM_CLASS) { struct sl_window* window = sl_lookup_window(ctx, event->window); if (!window || event->state == XCB_PROPERTY_DELETE) return; xcb_get_property_cookie_t cookie = xcb_get_property(ctx->connection, 0, window->id, XCB_ATOM_WM_CLASS, XCB_ATOM_ANY, 0, 2048); xcb_get_property_reply_t* reply = xcb_get_property_reply(ctx->connection, cookie, NULL); if (reply) { sl_decode_wm_class(window, reply); free(reply); } sl_update_application_id(ctx, window); } else if (event->atom == ctx->application_id_property_atom) { struct sl_window* window = sl_lookup_window(ctx, event->window); if (!window || event->state == XCB_PROPERTY_DELETE) return; // TODO(cpelling): Support other atom types (e.g. strings) if/when a use // case arises. The current use case is for cardinals (uint32) but this // is easy enough to extend later. xcb_get_property_cookie_t cookie = xcb_get_property( ctx->connection, 0, window->id, ctx->application_id_property_atom, XCB_ATOM_CARDINAL, 0, 1); xcb_get_property_reply_t* reply = xcb_get_property_reply(ctx->connection, cookie, NULL); if (reply) { sl_set_application_id_from_atom(ctx, window, reply); sl_update_application_id(ctx, window); free(reply); } } 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); } TRACE_EVENT("x11wm", "XCB_PROPERTY_NOTIFY: XCB_ATOM_WM_NORMAL_HINTS", [&](perfetto::EventContext p) { perfetto_annotate_size_hints(p, size_hints); }); 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) { int32_t minw = window->min_width; int32_t minh = window->min_height; sl_transform_guest_to_host(window->ctx, window->paired_surface, &minw, &minh); xdg_toplevel_set_min_size(window->xdg_toplevel, minw, minh); } else { xdg_toplevel_set_min_size(window->xdg_toplevel, 0, 0); } if (window->size_flags & P_MAX_SIZE) { int32_t maxw = window->max_width; int32_t maxh = window->max_height; sl_transform_guest_to_host(window->ctx, window->paired_surface, &maxw, &maxh); xdg_toplevel_set_max_size(window->xdg_toplevel, maxw, maxh); } else { xdg_toplevel_set_max_size(window->xdg_toplevel, 0, 0); } } else if (event->atom == XCB_ATOM_WM_HINTS) { struct sl_window* window = sl_lookup_window(ctx, event->window); if (!window) return; if (event->state == XCB_PROPERTY_DELETE) return; struct sl_wm_hints wm_hints = {0}; xcb_get_property_reply_t* reply = xcb_get_property_reply( ctx->connection, xcb_get_property(ctx->connection, 0, window->id, XCB_ATOM_WM_HINTS, XCB_ATOM_ANY, 0, sizeof(wm_hints)), NULL); if (!reply) return; memcpy(&wm_hints, xcb_get_property_value(reply), sizeof(wm_hints)); free(reply); if (wm_hints.flags & WM_HINTS_FLAG_URGENCY) { sl_request_attention(ctx, window, /*is_strong_request=*/false); } } else if (event->atom == ctx->atoms[ATOM_MOTIF_WM_HINTS].value) { struct sl_window* window = sl_lookup_window(ctx, event->window); if (!window) return; // Managed windows are decorated by default. window->decorated = window->managed; if (event->state != XCB_PROPERTY_DELETE) { struct sl_mwm_hints mwm_hints = {0}; xcb_get_property_reply_t* reply = xcb_get_property_reply( ctx->connection, xcb_get_property(ctx->connection, 0, window->id, ctx->atoms[ATOM_MOTIF_WM_HINTS].value, XCB_ATOM_ANY, 0, sizeof(mwm_hints)), NULL); if (reply) { if (xcb_get_property_value_length(reply) >= static_cast(sizeof(mwm_hints))) { memcpy(&mwm_hints, xcb_get_property_value(reply), sizeof(mwm_hints)); } 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; } } } if (!window->aura_surface) return; zaura_surface_set_frame(window->aura_surface, window->decorated ? ZAURA_SURFACE_FRAME_TYPE_NORMAL : window->depth == 32 ? ZAURA_SURFACE_FRAME_TYPE_NONE : ZAURA_SURFACE_FRAME_TYPE_SHADOW); } else if (event->atom == ctx->atoms[ATOM_GTK_THEME_VARIANT].value) { struct sl_window* window; uint32_t frame_color; window = sl_lookup_window(ctx, event->window); if (!window) return; window->dark_frame = 0; if (event->state != XCB_PROPERTY_DELETE) { xcb_get_property_reply_t* reply = xcb_get_property_reply( ctx->connection, xcb_get_property(ctx->connection, 0, window->id, ctx->atoms[ATOM_GTK_THEME_VARIANT].value, XCB_ATOM_ANY, 0, 2048), NULL); if (reply) { if (xcb_get_property_value_length(reply) >= 4) window->dark_frame = !strcmp( static_cast(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_XWAYLAND_RANDR_EMU_MONITOR_RECTS].value) { TRACE_EVENT("x11wm", "XCB_PROPERTY_NOTIFY: _XWAYLAND_RANDR_EMU_MONITOR_RECTS", [&](perfetto::EventContext p) { xcb_get_property_cookie_t cookie = xcb_get_property( ctx->connection, 0, event->window, ctx->atoms[ATOM_XWAYLAND_RANDR_EMU_MONITOR_RECTS].value, XCB_ATOM_ANY, 0, 2048); perfetto_annotate_window(ctx, p, "window", event->window); xcb_get_property_reply_t* reply = xcb_get_property_reply(ctx->connection, cookie, NULL); perfetto_annotate_cardinal_list(p, "value", reply); free(reply); }); } 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.reset(wl_event_loop_add_fd( wl_display_get_event_loop(ctx->host_display), ctx->selection_data_offer_receive_fd, WL_EVENT_READABLE, sl_handle_selection_fd_readable, ctx)); } return; } sl_send_selection_data(ctx); // Release data if transfer is complete. if (!data_size) { ctx->selection_request.requestor = XCB_NONE; wl_array_release(&ctx->selection_data); } } } } static void sl_internal_data_source_target(void* data, struct wl_data_source* data_source, const char* mime_type) {} static void sl_internal_data_source_send(void* data, struct wl_data_source* data_source, const char* mime_type, int32_t fd) { TRACE_EVENT("other", "sl_internal_data_source_send"); struct sl_data_source* host = static_cast(data); struct sl_context* ctx = host->ctx; xcb_intern_atom_cookie_t cookie = xcb_intern_atom(ctx->connection, false, strlen(mime_type), mime_type); if (ctx->selection_data_source_send_fd < 0) { sl_begin_data_source_send(ctx, fd, cookie, host); } else { struct sl_data_source_send_request* request = static_cast( malloc(sizeof(struct sl_data_source_send_request))); request->fd = fd; request->cookie = cookie; request->data_source = host; wl_list_insert(&ctx->selection_data_source_send_pending, &request->link); } } static void sl_internal_data_source_cancelled( void* data, struct wl_data_source* data_source) { TRACE_EVENT("other", "sl_internal_data_source_cancelled"); struct sl_data_source* host = static_cast(data); if (host->ctx->selection_data_source == host) host->ctx->selection_data_source = NULL; wl_data_source_destroy(data_source); } static const struct wl_data_source_listener sl_internal_data_source_listener = { sl_internal_data_source_target, sl_internal_data_source_send, sl_internal_data_source_cancelled}; char* sl_copy_atom_name(xcb_get_atom_name_reply_t* reply) { // The string produced by xcb_get_atom_name_name isn't null terminated, so we // have to copy |name_len| bytes into a new buffer and add the null character // ourselves. char* name_start = xcb_get_atom_name_name(reply); int name_len = xcb_get_atom_name_name_length(reply); char* name = static_cast(malloc(name_len + 1)); memcpy(name, name_start, name_len); name[name_len] = '\0'; return name; } static void sl_get_selection_targets(struct sl_context* ctx) { TRACE_EVENT("other", "sl_get_selection_targets"); struct sl_data_source* data_source = NULL; xcb_get_property_reply_t* reply; xcb_atom_t* value; uint32_t i; reply = xcb_get_property_reply( ctx->connection, xcb_get_property(ctx->connection, 1, ctx->selection_window, ctx->atoms[ATOM_WL_SELECTION].value, XCB_GET_PROPERTY_TYPE_ANY, 0, DEFAULT_BUFFER_SIZE), NULL); if (!reply) return; if (reply->type != XCB_ATOM_ATOM) { free(reply); return; } if (ctx->data_device_manager) { data_source = static_cast(malloc(sizeof(*data_source))); assert(data_source); data_source->ctx = ctx; data_source->internal = wl_data_device_manager_create_data_source( ctx->data_device_manager->internal); wl_data_source_add_listener(data_source->internal, &sl_internal_data_source_listener, data_source); value = static_cast(xcb_get_property_value(reply)); // We need to convert all of the offered target types from X11 atoms to // strings (i.e. getting the names of the atoms). Each conversion requires a // round trip to the X server, but none of the requests depend on each // other. Therefore, we can speed things up by sending out all the requests // as a batch with xcb_get_atom_name, and then read all the replies as a // batch with xcb_get_atom_name_reply. xcb_get_atom_name_cookie_t* atom_name_cookies = static_cast( malloc(sizeof(xcb_get_atom_name_cookie_t) * reply->value_len)); for (i = 0; i < reply->value_len; i++) { atom_name_cookies[i] = xcb_get_atom_name(ctx->connection, value[i]); } for (i = 0; i < reply->value_len; i++) { xcb_get_atom_name_reply_t* atom_name_reply = xcb_get_atom_name_reply(ctx->connection, atom_name_cookies[i], NULL); if (atom_name_reply) { char* name = sl_copy_atom_name(atom_name_reply); wl_data_source_offer(data_source->internal, name); free(atom_name_reply); free(name); } } free(atom_name_cookies); if (ctx->selection_data_device && ctx->default_seat) { wl_data_device_set_selection(ctx->selection_data_device, data_source->internal, ctx->default_seat->seat->last_serial); } if (ctx->selection_data_source) { wl_data_source_destroy(ctx->selection_data_source->internal); free(ctx->selection_data_source); } ctx->selection_data_source = data_source; } free(reply); } static void sl_get_selection_data(struct sl_context* ctx) { TRACE_EVENT("other", "sl_get_selection_data"); xcb_get_property_reply_t* reply = xcb_get_property_reply( ctx->connection, xcb_get_property(ctx->connection, 1, ctx->selection_window, ctx->atoms[ATOM_WL_SELECTION].value, XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff), NULL); if (!reply) return; if (reply->type == ctx->atoms[ATOM_INCR].value) { ctx->selection_incremental_transfer = 1; free(reply); } else { ctx->selection_incremental_transfer = 0; sl_write_selection_property(ctx, reply); } } static void sl_handle_selection_notify(struct sl_context* ctx, xcb_selection_notify_event_t* event) { if (event->property == XCB_ATOM_NONE) return; if (event->target == ctx->atoms[ATOM_TARGETS].value) sl_get_selection_targets(ctx); else sl_get_selection_data(ctx); } static void sl_send_targets(struct sl_context* ctx) { xcb_change_property( ctx->connection, XCB_PROP_MODE_REPLACE, ctx->selection_request.requestor, ctx->selection_request.property, XCB_ATOM_ATOM, 32, ctx->selection_data_offer->atoms.size / sizeof(xcb_atom_t), ctx->selection_data_offer->atoms.data); sl_send_selection_notify(ctx, ctx->selection_request.property); } static void sl_send_timestamp(struct sl_context* ctx) { xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, ctx->selection_request.requestor, ctx->selection_request.property, XCB_ATOM_INTEGER, 32, 1, &ctx->selection_timestamp); sl_send_selection_notify(ctx, ctx->selection_request.property); } static void sl_send_data(struct sl_context* ctx, xcb_atom_t data_type) { TRACE_EVENT("other", "sl_send_data"); int rv, fd_to_receive, fd_to_wayland; if (!ctx->selection_data_offer) { sl_send_selection_notify(ctx, XCB_ATOM_NONE); return; } if (ctx->selection_event_source) { fprintf(stderr, "error: selection transfer already pending\n"); sl_send_selection_notify(ctx, XCB_ATOM_NONE); return; } ctx->selection_data_type = data_type; // We will need the name of this atom later to tell the wayland server what // type of data to send us, so start the request now. xcb_get_atom_name_cookie_t atom_name_cookie = xcb_get_atom_name(ctx->connection, data_type); wl_array_init(&ctx->selection_data); ctx->selection_data_ack_pending = 0; if (ctx->channel == NULL) { // Running in noop mode, without virtualization. int p[2]; rv = pipe2(p, O_CLOEXEC | O_NONBLOCK); errno_assert(!rv); fd_to_receive = p[0]; fd_to_wayland = p[1]; } else { int pipe_fd; rv = ctx->channel->create_pipe(pipe_fd); if (rv) { fprintf(stderr, "error: failed to create virtwl pipe: %s\n", strerror(-rv)); sl_send_selection_notify(ctx, XCB_ATOM_NONE); return; } fd_to_receive = pipe_fd; fd_to_wayland = pipe_fd; } 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.reset(wl_event_loop_add_fd( wl_display_get_event_loop(ctx->host_display), ctx->selection_data_offer_receive_fd, WL_EVENT_READABLE, sl_handle_selection_fd_readable, ctx)); } else { // If getting the atom name failed, notify the requestor that there won't be // any data, and close our end of the pipe. close(fd_to_receive); sl_send_selection_notify(ctx, XCB_ATOM_NONE); } // Close the wayland end of the pipe, now that it's either been sent or not // going to be sent. The VIRTWL driver uses the same fd for both ends of the // pipe, so don't close the fd if both ends are the same. if (fd_to_receive != fd_to_wayland) close(fd_to_wayland); } static void sl_handle_selection_request(struct sl_context* ctx, xcb_selection_request_event_t* event) { ctx->selection_request = *event; ctx->selection_incremental_transfer = 0; if (event->selection == ctx->atoms[ATOM_CLIPBOARD_MANAGER].value) { sl_send_selection_notify(ctx, ctx->selection_request.property); return; } if (event->target == ctx->atoms[ATOM_TARGETS].value) { sl_send_targets(ctx); } else if (event->target == ctx->atoms[ATOM_TIMESTAMP].value) { sl_send_timestamp(ctx); } else { int success = 0; xcb_atom_t* atom; sl_array_for_each(atom, &ctx->selection_data_offer->atoms) { if (event->target == *atom) { success = 1; sl_send_data(ctx, *atom); break; } } if (!success) { sl_send_selection_notify(ctx, XCB_ATOM_NONE); } } } static void sl_handle_xfixes_selection_notify( struct sl_context* ctx, xcb_xfixes_selection_notify_event_t* event) { if (event->selection != ctx->atoms[ATOM_CLIPBOARD].value) return; if (event->owner == XCB_WINDOW_NONE) { // If client selection is gone. Set NULL selection for each seat. if (ctx->selection_owner != ctx->selection_window) { if (ctx->selection_data_device && ctx->default_seat) { wl_data_device_set_selection(ctx->selection_data_device, NULL, ctx->default_seat->seat->last_serial); } } ctx->selection_owner = XCB_WINDOW_NONE; return; } ctx->selection_owner = event->owner; // Save timestamp if it's our selection. if (event->owner == ctx->selection_window) { ctx->selection_timestamp = event->timestamp; return; } ctx->selection_incremental_transfer = 0; xcb_convert_selection(ctx->connection, ctx->selection_window, ctx->atoms[ATOM_CLIPBOARD].value, ctx->atoms[ATOM_TARGETS].value, ctx->atoms[ATOM_WL_SELECTION].value, event->timestamp); } static int sl_handle_x_connection_event(int fd, uint32_t mask, void* data) { TRACE_EVENT("other", "sl_handle_x_connection_event"); struct sl_context* ctx = (struct sl_context*)data; xcb_generic_event_t* event; uint32_t count = 0; if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { fprintf(stderr, "Got error or hangup (mask %d) on X connection, exiting\n", mask); exit(EXIT_SUCCESS); } while ((event = xcb_poll_for_event(ctx->connection))) { switch (event->response_type & ~SEND_EVENT_MASK) { case XCB_CREATE_NOTIFY: sl_handle_create_notify( ctx, reinterpret_cast(event)); break; case XCB_DESTROY_NOTIFY: sl_handle_destroy_notify( ctx, reinterpret_cast(event)); break; case XCB_REPARENT_NOTIFY: sl_handle_reparent_notify( ctx, reinterpret_cast(event)); break; case XCB_MAP_REQUEST: sl_handle_map_request( ctx, reinterpret_cast(event)); break; case XCB_MAP_NOTIFY: sl_handle_map_notify(ctx, reinterpret_cast(event)); break; case XCB_UNMAP_NOTIFY: sl_handle_unmap_notify( ctx, reinterpret_cast(event)); break; case XCB_CONFIGURE_REQUEST: sl_handle_configure_request( ctx, reinterpret_cast(event)); break; case XCB_CONFIGURE_NOTIFY: sl_handle_configure_notify( ctx, reinterpret_cast(event)); break; case XCB_CLIENT_MESSAGE: sl_handle_client_message( ctx, reinterpret_cast(event)); break; case XCB_FOCUS_IN: sl_handle_focus_in(ctx, reinterpret_cast(event)); break; case XCB_FOCUS_OUT: sl_handle_focus_out(ctx, reinterpret_cast(event)); break; case XCB_PROPERTY_NOTIFY: sl_handle_property_notify( ctx, reinterpret_cast(event)); break; case XCB_SELECTION_NOTIFY: sl_handle_selection_notify( ctx, reinterpret_cast(event)); break; case XCB_SELECTION_REQUEST: sl_handle_selection_request( ctx, reinterpret_cast(event)); break; } switch (event->response_type - ctx->xfixes_extension->first_event) { case XCB_XFIXES_SELECTION_NOTIFY: sl_handle_xfixes_selection_notify( ctx, reinterpret_cast(event)); break; } // Xshape specific events extend the normal event numbers // The first event id is retrieved when querying for xshape // extension information. This can be used to determine // if the event that is received is Xshape specific. if (ctx->enable_xshape) { uint8_t xshape_event_id = event->response_type - ctx->xshape_extension->first_event; switch (xshape_event_id) { case XCB_SHAPE_NOTIFY: sl_handle_shape_notify( ctx, reinterpret_cast(event)); break; } } free(event); ++count; } if ((mask & ~WL_EVENT_WRITABLE) == 0) xcb_flush(ctx->connection); return count; } static void sl_set_supported(struct sl_context* ctx) { const xcb_atom_t supported_atoms[] = { ctx->atoms[ATOM_NET_ACTIVE_WINDOW].value, ctx->atoms[ATOM_NET_WM_MOVERESIZE].value, ctx->atoms[ATOM_NET_WM_NAME].value, ctx->atoms[ATOM_NET_WM_STATE].value, ctx->atoms[ATOM_NET_WM_STATE_FULLSCREEN].value, ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value, ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value, ctx->atoms[ATOM_NET_WM_STATE_FOCUSED].value, // TODO(hollingum): STATE_MODAL and CLIENT_LIST, based on what wlroots // has. }; xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, ctx->screen->root, ctx->atoms[ATOM_NET_SUPPORTED].value, XCB_ATOM_ATOM, 32, sizeof(supported_atoms) / sizeof(xcb_atom_t), supported_atoms); } // The window manager is responsible for setting the default cursor on the root // window, which will be used by applications that do not try to set their own // cursor. If we do not do this, the cursor will be invisible, see b/204724669 // for details. static void sl_initialize_cursor(struct sl_context* ctx) { xcb_generic_error_t* err; xcb_void_cookie_t cookie; // Load the "cursor" font, which is a built-in set of fonts, documented at // https://tronche.com/gui/x/xlib/appendix/b/. xcb_font_t font_id = xcb_generate_id(ctx->connection); cookie = xcb_open_font_checked(ctx->connection, font_id, strlen("cursor"), "cursor"); err = xcb_request_check(ctx->connection, cookie); assert(!err); // Create the standard "left ptr" cursor. The below call is a bit technical, // but it means: // - font_id appears twice because we use a mask (background) whose font is // the same as the source. // - the mask is the source + 1, because the inbuilt cursor font has // even-numbered foreground glyphs and odd-numbered backgrounds. // - The numbers are 16-bit RGB for the foreground and background, so this // has a black foreground and a white background. constexpr int left_ptr_cursor_id = 68; xcb_cursor_t cursor_id = xcb_generate_id(ctx->connection); cookie = xcb_create_glyph_cursor_checked( ctx->connection, cursor_id, font_id, font_id, left_ptr_cursor_id, left_ptr_cursor_id + 1, 0, 0, 0, 65535, 65535, 65535); err = xcb_request_check(ctx->connection, cookie); assert(!err); // Set the cursor for the root window. cookie = xcb_change_window_attributes_checked( ctx->connection, ctx->screen->root, XCB_CW_CURSOR, &cursor_id); err = xcb_request_check(ctx->connection, cookie); assert(!err); // Free the cursor and the fonts, since we no-longer need them. xcb_free_cursor(ctx->connection, cursor_id); cookie = xcb_close_font_checked(ctx->connection, font_id); err = xcb_request_check(ctx->connection, cookie); assert(!err); } static void sl_connect(struct sl_context* ctx) { TRACE_EVENT("other", "sl_connect"); const char wm_name[] = "Sommelier"; const xcb_setup_t* setup; xcb_screen_iterator_t screen_iterator; uint32_t values[1]; xcb_void_cookie_t change_attributes_cookie, redirect_subwindows_cookie; xcb_generic_error_t* error; xcb_intern_atom_reply_t* atom_reply; xcb_depth_iterator_t depth_iterator; xcb_xfixes_query_version_reply_t* xfixes_query_version_reply; xcb_shape_query_version_reply_t* xshape_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); // Send requests to fetch/create ("intern") all the atoms we'll need later. 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); } xcb_intern_atom_cookie_t app_id_atom_cookie; if (ctx->application_id_property_name) { app_id_atom_cookie = xcb_intern_atom( ctx->connection, 0, strlen(ctx->application_id_property_name), ctx->application_id_property_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.reset(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); if (ctx->enable_xshape) { xcb_prefetch_extension_data(ctx->connection, &xcb_shape_id); ctx->xshape_extension = xcb_get_extension_data(ctx->connection, &xcb_shape_id); xshape_query_version_reply = xcb_shape_query_version_reply( ctx->connection, xcb_shape_query_version(ctx->connection), NULL); assert(xshape_query_version_reply); assert(xshape_query_version_reply->major_version >= XCB_SHAPE_MAJOR_VERSION); assert(xshape_query_version_reply->minor_version >= XCB_SHAPE_MINOR_VERSION); free(xshape_query_version_reply); xshape_query_version_reply = NULL; } 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); // Wait on results for all the atom intern requests we sent above. 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); } if (ctx->application_id_property_name) { atom_reply = xcb_intern_atom_reply(ctx->connection, app_id_atom_cookie, &error); assert(!error); ctx->application_id_property_atom = atom_reply->atom; free(atom_reply); } depth_iterator = xcb_screen_allowed_depths_iterator(ctx->screen); while (depth_iterator.rem > 0) { int depth = depth_iterator.data->depth; if (depth == ctx->screen->root_depth) { ctx->visual_ids[depth] = ctx->screen->root_visual; ctx->colormaps[depth] = ctx->screen->default_colormap; } else { xcb_visualtype_iterator_t visualtype_iterator = xcb_depth_visuals_iterator(depth_iterator.data); ctx->visual_ids[depth] = visualtype_iterator.data->visual_id; ctx->colormaps[depth] = xcb_generate_id(ctx->connection); xcb_create_colormap(ctx->connection, XCB_COLORMAP_ALLOC_NONE, ctx->colormaps[depth], ctx->screen->root, ctx->visual_ids[depth]); } xcb_depth_next(&depth_iterator); } assert(ctx->visual_ids[ctx->screen->root_depth]); if (ctx->clipboard_manager) { values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE; ctx->selection_window = xcb_generate_id(ctx->connection); xcb_create_window(ctx->connection, XCB_COPY_FROM_PARENT, ctx->selection_window, ctx->screen->root, 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, ctx->screen->root_visual, XCB_CW_EVENT_MASK, values); xcb_set_selection_owner(ctx->connection, ctx->selection_window, ctx->atoms[ATOM_CLIPBOARD_MANAGER].value, XCB_CURRENT_TIME); xcb_xfixes_select_selection_input( ctx->connection, ctx->selection_window, ctx->atoms[ATOM_CLIPBOARD].value, XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE); sl_set_selection(ctx, NULL); } xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, ctx->window, ctx->atoms[ATOM_NET_SUPPORTING_WM_CHECK].value, XCB_ATOM_WINDOW, 32, 1, &ctx->window); xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, ctx->window, ctx->atoms[ATOM_NET_WM_NAME].value, ctx->atoms[ATOM_UTF8_STRING].value, 8, strlen(wm_name), wm_name); xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, ctx->screen->root, ctx->atoms[ATOM_NET_SUPPORTING_WM_CHECK].value, XCB_ATOM_WINDOW, 32, 1, &ctx->window); sl_set_supported(ctx); xcb_set_selection_owner(ctx->connection, ctx->window, ctx->atoms[ATOM_WM_S0].value, XCB_CURRENT_TIME); xcb_set_input_focus(ctx->connection, XCB_INPUT_FOCUS_NONE, XCB_NONE, XCB_CURRENT_TIME); xcb_flush(ctx->connection); sl_initialize_cursor(ctx); } static void sl_sd_notify(const char* state) { const char* socket_name; struct msghdr msghdr; struct iovec iovec; struct sockaddr_un addr; int fd; int rv; socket_name = getenv("NOTIFY_SOCKET"); assert(socket_name); fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); errno_assert(fd >= 0); memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path)); memset(&iovec, 0, sizeof(iovec)); // iovec is just going to be used to send data as part of a const msghdr. iovec.iov_base = const_cast(state); iovec.iov_len = strlen(state); memset(&msghdr, 0, sizeof(msghdr)); msghdr.msg_name = &addr; msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(socket_name); msghdr.msg_iov = &iovec; msghdr.msg_iovlen = 1; rv = sendmsg(fd, &msghdr, MSG_NOSIGNAL); errno_assert(rv != -1); } static int sl_handle_sigchld(int signal_number, void* data) { struct sl_context* ctx = (struct sl_context*)data; int status; pid_t pid; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { if (pid == ctx->child_pid) { ctx->child_pid = -1; if (WIFEXITED(status) && WEXITSTATUS(status)) { fprintf(stderr, "Child exited with status: %d\n", WEXITSTATUS(status)); } if (ctx->exit_with_child) { if (ctx->xwayland_pid >= 0) kill(ctx->xwayland_pid, SIGTERM); } else { // Notify systemd that we are ready to accept connections now that // child process has finished running and all environment is ready. if (ctx->sd_notify) sl_sd_notify(ctx->sd_notify); } } else if (pid == ctx->xwayland_pid) { ctx->xwayland_pid = -1; if (WIFEXITED(status) && WEXITSTATUS(status)) { fprintf(stderr, "Xwayland exited with status: %d\n", WEXITSTATUS(status)); exit(WEXITSTATUS(status)); } } } return 1; } static int sl_handle_sigusr1(int signal_number, void* data) { struct sl_context* ctx = (struct sl_context*)data; fprintf(stderr, "dumping trace %s\n", ctx->trace_filename); dump_trace(ctx->trace_filename); if (ctx->timing != NULL) { ctx->timing->OutputLog(); } 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 = ctx->desired_scale; if (!ctx->use_direct_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 multiplied 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); } // Extra connections are made to XWayland-hosting instances for IME support. static void sl_extra_client_created_notify(struct wl_listener* listener, void* data) { sl_context* ctx = wl_container_of(listener, ctx, extra_client_created_listener); wl_client* client = static_cast(data); sl_set_display_implementation(ctx, client); } static int sl_handle_display_ready_event(int fd, uint32_t mask, void* data) { TRACE_EVENT("surface", "sl_handle_display_ready_event"); struct sl_context* ctx = (struct sl_context*)data; char display_name[9]; int bytes_read = 0; pid_t pid; if (!(mask & WL_EVENT_READABLE)) { fprintf(stderr, "Got error or hangup on display ready connection" " (mask %d), exiting\n", mask); exit(EXIT_SUCCESS); } display_name[0] = ':'; do { int bytes_left = sizeof(display_name) - bytes_read - 1; int bytes; if (!bytes_left) break; bytes = read(fd, &display_name[bytes_read + 1], bytes_left); if (!bytes) break; bytes_read += bytes; } while (display_name[bytes_read] != '\n'); display_name[bytes_read] = '\0'; setenv("DISPLAY", display_name, 1); sl_connect(ctx); ctx->display_ready_event_source.reset(); 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); // Open wayland socket so cros_im can provide IME support to X11 apps. // This depends on the display number, which is only available once Xwayland // initialization is complete (now), so we can't create this earlier to use // it for the wayland connection with Xwayland. char* socket_name = sl_xasprintf("DISPLAY-%s-wl", display_name); struct sockaddr_un addr; int lock_fd; int sock_fd; int rv = sl_open_wayland_socket(socket_name, &addr, &lock_fd, &sock_fd); if (rv >= 0) { wl_display_add_socket_fd(ctx->host_display, sock_fd); ctx->extra_client_created_listener.notify = sl_extra_client_created_notify; wl_display_add_client_created_listener(ctx->host_display, &ctx->extra_client_created_listener); } else { fprintf(stderr, "warning: unable to open wayland socket for cros_im.\n"); } free(socket_name); putenv(sl_xasprintf("XCURSOR_SIZE=%d", static_cast(XCURSOR_SIZE_BASE * ctx->scale + 0.5))); pid = fork(); errno_assert(pid >= 0); if (pid == 0) { // Set WAYLAND_DISPLAY to a value that is guaranteed to not point to a // valid wayland compositor socket name. Unsetting WAYLAND_DISPLAY is // insufficient as some clients may attempt to connect to wayland-0 as a // default fallback. setenv("WAYLAND_DISPLAY", ".", 1); 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); } // Break |str| into a sequence of zero or more nonempty arguments. No more // than |argc| arguments will be added to |argv|. Returns the total number of // argments found in |str|. static int sl_parse_cmd_prefix(char* str, int argc, char const** argv) { char* s = str; int n = 0; int delim = 0; do { // Look for ending delimiter if |delim| is set. if (delim) { if (*s == delim) { delim = 0; *s = '\0'; } ++s; } else { // Skip forward to first non-space character. while (*s == ' ' && *s != '\0') ++s; // Check for quote delimiter. if (*s == '"') { delim = '"'; ++s; } else { delim = ' '; } // Add string to arguments if there's room. if (n < argc) argv[n] = s; ++n; } } while (*s != '\0'); return n; } static void sl_print_usage() { printf( "usage: sommelier [options] [program] [args...]\n\n" "options:\n" " -h, --help\t\t\tPrint this help\n" " -X\t\t\t\tEnable X11 forwarding\n" " --parent\t\t\tRun as parent and spawn child processes\n" " --socket=SOCKET\t\tName of socket to listen on\n" " --display=DISPLAY\t\tWayland display to connect to\n" " --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" " --windowed_accelerators=ACCELERATORS\tList of keyboard accelerators\n" "\tonly while windowed\n" " --application-id=ID\t\tForced application ID for all X11 windows\n" " --vm-identifier=NAME\t\tName of the VM, used to identify X11 " "windows.\n" "\t\t\t\tIgnored if --application-id is set.\n" " --application-id-x11-property=PROPERTY\n" "\tA cardinal window property used to identify X11 windows, as follows:\n" "\t org.chromium..xprop.\n" "\tIgnored if --application-id is set.\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" " --enable-xshape\t\tEnable X11 XShape extension support\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" " --no-support-damage-buffer\t" "Disable wl_surface::damage_buffer support.\n" " --force-drm-device=DEVICE\tDRM device to use\n" " --glamor\t\t\tUse glamor to accelerate X11 clients\n" " --timing-filename=PATH\tPath to timing output log\n" " --direct-scale\t\tEnable direct scaling mode\n" #ifdef PERFETTO_TRACING " --trace-filename=PATH\t\tPath to Perfetto trace filename\n" " --trace-system\t\tPerfetto trace to system daemon\n" #endif " --fullscreen-mode=MODE\tDefault fullscreen behavior (immersive," " plain)\n" " --noop-driver\t\tPass through to existing Wayland server" " without virtualization\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; } // 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). // Sommelier will exit with EXIT_FAILURE if this returns false. // TODO(b/237946069) Confirm accelerator handling works with i18n. static bool sl_parse_accelerators(struct wl_list* accelerator_list, const char* accelerators) { 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 false; } } else { const char* end = strchrnul(accelerators, ','); char* name = strndup(accelerators, end - accelerators); struct sl_accelerator* accelerator = static_cast(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 false; } wl_list_insert(accelerator_list, &accelerator->link); modifiers = 0; accelerators = end; free(name); } } } return true; } // Takes the lock and then opens the socket for wayland clients to connect to. // Returns a negative value on error. |addr|, |lock_fd| and |sock_fd| will be // populated on success. int sl_open_wayland_socket(const char* socket_name, struct sockaddr_un* addr, int* lock_fd, int* sock_fd) { int rv; const char* runtime_dir = getenv("XDG_RUNTIME_DIR"); if (!runtime_dir) { fprintf(stderr, "error: XDG_RUNTIME_DIR not set in the environment\n"); return -1; } addr->sun_family = AF_LOCAL; snprintf(addr->sun_path, sizeof(addr->sun_path), "%s/%s", runtime_dir, socket_name); char* lock_addr = sl_xasprintf("%s%s", addr->sun_path, LOCK_SUFFIX); *lock_fd = open(lock_addr, O_CREAT | O_CLOEXEC, (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)); errno_assert(*lock_fd >= 0); rv = flock(*lock_fd, LOCK_EX | LOCK_NB); if (rv < 0) { fprintf(stderr, "error: unable to lock %s, is another compositor running?\n", lock_addr); return rv; } free(lock_addr); struct stat sock_stat; rv = stat(addr->sun_path, &sock_stat); if (rv >= 0) { if (sock_stat.st_mode & (S_IWUSR | S_IWGRP)) unlink(addr->sun_path); } else { errno_assert(errno == ENOENT); } *sock_fd = socket(PF_LOCAL, SOCK_STREAM, 0); errno_assert(*sock_fd >= 0); rv = bind(*sock_fd, (struct sockaddr*)addr, offsetof(struct sockaddr_un, sun_path) + strlen(addr->sun_path)); errno_assert(rv >= 0); rv = listen(*sock_fd, 128); errno_assert(rv >= 0); return 0; } int sl_run_parent(int argc, char** argv, sl_context* ctx, const char* socket_name, const char* peer_cmd_prefix) { struct sockaddr_un addr; int lock_fd; int sock_fd; int rv; rv = sl_open_wayland_socket(socket_name, &addr, &lock_fd, &sock_fd); if (rv < 0) return EXIT_FAILURE; // 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]) { int pid = fork(); errno_assert(pid != -1); if (pid == 0) { setenv("WAYLAND_DISPLAY", socket_name, 1); sl_execvp(ctx->runprog[0], ctx->runprog, -1); _exit(EXIT_FAILURE); } while (waitpid(-1, NULL, WNOHANG) != pid) continue; } if (ctx->sd_notify) sl_sd_notify(ctx->sd_notify); struct sigaction sa; sa.sa_handler = sl_sigchld_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; rv = sigaction(SIGCHLD, &sa, NULL); errno_assert(rv >= 0); do { struct ucred ucred; socklen_t length = sizeof(addr); int 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); int pid = fork(); errno_assert(pid != -1); if (pid == 0) { char* client_fd_str; char* peer_pid_str; char* peer_cmd_prefix_str; char const* args[64]; int i = 0; close(sock_fd); close(lock_fd); 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 (int j = 1; j < argc; ++j) { char* arg = argv[j]; if (strstr(arg, "--display") == arg || strstr(arg, "--scale") == arg || strstr(arg, "--direct-scale") == arg || strstr(arg, "--accelerators") == arg || strstr(arg, "--windowed-accelerators") == arg || strstr(arg, "--drm-device") == arg || strstr(arg, "--support-damage-buffer") == arg || strstr(arg, "--vm-identififer") == arg) { args[i++] = arg; } } args[i++] = NULL; execvp(args[0], const_cast(args)); _exit(EXIT_FAILURE); } close(client_fd); } while (1); // Control should never reach here. assert(false); } void sl_spawn_xwayland(sl_context* ctx, wl_event_loop* event_loop, int wayland_socket_fd, const char* xwayland_cmd_prefix, const char* xwayland_path, int xdisplay, const char* xauth_path, const char* xfont_path, const char* xwayland_gl_driver_path, const char* glamor) { int ds[2]; int rv; // Xwayland display ready socket. rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ds); errno_assert(!rv); ctx->display_ready_event_source.reset( wl_event_loop_add_fd(event_loop, ds[0], WL_EVENT_READABLE, sl_handle_display_ready_event, ctx)); int wm[2]; // X connection to Xwayland. rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wm); errno_assert(!rv); ctx->wm_fd = wm[0]; int pid = fork(); errno_assert(pid != -1); if (pid == 0) { char const* args[64]; int i = 0; if (xwayland_cmd_prefix) { char* 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); int fd = dup(ds[1]); char* display_fd_str = sl_xasprintf("%d", fd); fd = dup(wm[1]); char* wm_fd_str = sl_xasprintf("%d", fd); if (xdisplay > 0) { args[i++] = sl_xasprintf(":%d", xdisplay); } args[i++] = "-nolisten"; args[i++] = "tcp"; args[i++] = "-rootless"; // Use software rendering unless we have a DRM device and glamor is // enabled. if (!ctx->drm_device || !glamor || !strcmp(glamor, "0")) args[i++] = "-shm"; args[i++] = "-displayfd"; args[i++] = display_fd_str; args[i++] = "-wm"; args[i++] = wm_fd_str; if (xauth_path) { args[i++] = "-auth"; args[i++] = sl_xasprintf("%s", xauth_path); } if (xfont_path) { args[i++] = "-fp"; args[i++] = sl_xasprintf("%s", xfont_path); } args[i++] = NULL; // If a path is explicitly specified via command line or environment // use that instead of the compiled in default. In either case, only // set the environment variable if the value specified is non-empty. if (xwayland_gl_driver_path) { if (*xwayland_gl_driver_path) { setenv("LIBGL_DRIVERS_PATH", xwayland_gl_driver_path, 1); } } else if (XWAYLAND_GL_DRIVER_PATH && *XWAYLAND_GL_DRIVER_PATH) { setenv("LIBGL_DRIVERS_PATH", XWAYLAND_GL_DRIVER_PATH, 1); } sl_execvp(args[0], const_cast(args), wayland_socket_fd); _exit(EXIT_FAILURE); } close(wm[1]); ctx->xwayland_pid = pid; } int real_main(int argc, char** argv) { struct sl_context ctx; sl_context_init_default(&ctx); 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* support_damage_buffer = getenv("SOMMELIER_SUPPORT_DAMAGE_BUFFER"); const char* force_drm_device = NULL; const char* glamor = getenv("SOMMELIER_GLAMOR"); const char* fullscreen_mode = getenv("SOMMELIER_FULLSCREEN_MODE"); 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* windowed_accelerators = getenv("SOMMELIER_WINDOWED_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* vm_id = getenv("SOMMELIER_VM_IDENTIFIER"); const char* socket_name = "wayland-0"; bool noop_driver = false; struct wl_event_loop* event_loop; struct wl_listener client_destroy_listener = {}; client_destroy_listener.notify = sl_client_destroy_notify; int sv[2]; pid_t pid; int xdisplay = -1; int parent = 0; int client_fd = -1; int i; // Ignore SIGUSR1 (used for trace dumping) in all child processes. signal(SIGUSR1, SIG_IGN); for (i = 1; i < argc; ++i) { const char* arg = argv[i]; if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0 || strcmp(arg, "-?") == 0) { sl_print_usage(); return EXIT_SUCCESS; } if (strcmp(arg, "--version") == 0 || strcmp(arg, "-v") == 0) { printf("Version: %s\n", SOMMELIER_VERSION); return EXIT_SUCCESS; } if (strstr(arg, "--parent") == arg) { parent = 1; } else if (strstr(arg, "--socket") == arg) { socket_name = sl_arg_value(arg); } else if (strstr(arg, "--display") == arg) { display = 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, "--direct-scale") == arg) { ctx.use_direct_scale = true; } 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, "--windowed-accelerators") == arg) { windowed_accelerators = sl_arg_value(arg); } else if (strstr(arg, "--vm-identifier") == arg) { vm_id = sl_arg_value(arg); } else if (strstr(arg, "--application-id-x11-property") == arg) { // NB: Must be parsed before --application-id. ctx.application_id_property_name = 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, "--force-drm-device") == arg) { force_drm_device = sl_arg_value(arg); } else if (strstr(arg, "--no-support-damage-buffer") == arg) { support_damage_buffer = "0"; } else if (strstr(arg, "--glamor") == arg) { glamor = "1"; } else if (strstr(arg, "--fullscreen-mode") == arg) { fullscreen_mode = sl_arg_value(arg); } else if (strstr(arg, "--x-auth") == arg) { xauth_path = sl_arg_value(arg); } else if (strstr(arg, "--x-font-path") == arg) { xfont_path = sl_arg_value(arg); } else if (strstr(arg, "--timing-filename") == arg) { ctx.timing = new Timing(sl_arg_value(arg)); } else if (strstr(arg, "--explicit-fence") == arg) { ctx.use_explicit_fence = true; } else if (strstr(arg, "--enable-xshape") == arg) { ctx.enable_xshape = true; } else if (strstr(arg, "--virtgpu-channel") == arg) { ctx.use_virtgpu_channel = true; } else if (strstr(arg, "--noop-driver") == arg) { noop_driver = true; #ifdef PERFETTO_TRACING } else if (strstr(arg, "--trace-filename") == arg) { ctx.trace_filename = sl_arg_value(arg); } else if (strstr(arg, "--trace-system") == arg) { ctx.trace_system = true; #endif } else if (arg[0] == '-') { if (strcmp(arg, "--") == 0) { ctx.runprog = &argv[i + 1]; break; } // Don't exit on unknown options so we can have forward compatibility // with new flags introduced. fprintf(stderr, "Option `%s' is unknown, ignoring.\n", arg); } else { ctx.runprog = &argv[i]; break; } } if (vm_id && strlen(vm_id)) { // Some GuestOS instances of sommelier will be started with vm_id set to a // container_token from /dev/.container_token. // The vm_id may be empty if the token or file does not yet exist. // Keep the vm_id as default (termina) if this occurs. ctx.vm_id = vm_id; } if (ctx.application_id && ctx.vm_id) { fprintf(stderr, "warning: --application-id overrides --vm-identifier\n"); } if (ctx.application_id && ctx.application_id_property_name) { fprintf(stderr, "warning: --application-id overrides" " --application-id-x11-property\n"); } if (parent) { return sl_run_parent(argc, argv, &ctx, socket_name, peer_cmd_prefix); } 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. // In direct scale mode, take the scale value as is. if (ctx.use_direct_scale) { ctx.scale = ctx.desired_scale; } else { 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); } ctx.support_damage_buffer = support_damage_buffer == nullptr || strcmp(support_damage_buffer, "1") == 0; if (fullscreen_mode) { if (strcmp(fullscreen_mode, "immersive") == 0) { ctx.fullscreen_mode = ZAURA_SURFACE_FULLSCREEN_MODE_IMMERSIVE; } else if (strcmp(fullscreen_mode, "plain") == 0) { ctx.fullscreen_mode = ZAURA_SURFACE_FULLSCREEN_MODE_PLAIN; } else { fprintf(stderr, "error: unrecognised --fullscreen-mode\n"); sl_print_usage(); return EXIT_FAILURE; } } // Handle broken pipes without signals that kill the entire process. signal(SIGPIPE, SIG_IGN); ctx.host_display = wl_display_create(); assert(ctx.host_display); if (noop_driver) { ctx.channel = NULL; } else if (ctx.use_virtgpu_channel) { ctx.channel = new VirtGpuChannel(); } else { ctx.channel = new VirtWaylandChannel(); } event_loop = wl_display_get_event_loop(ctx.host_display); if (!sl_context_init_wayland_channel(&ctx, event_loop, display)) { return EXIT_FAILURE; } int drm_fd = -1; char* drm_device = NULL; if (force_drm_device != NULL) { // Use DRM device specified on the command line. drm_fd = open(force_drm_device, O_RDWR | O_CLOEXEC); if (drm_fd == -1) { fprintf(stderr, "error: could not open %s (%s)\n", force_drm_device, strerror(errno)); return EXIT_FAILURE; } drm_device = strdup(force_drm_device); } else { // Enumerate render nodes to find the virtio_gpu device. drm_fd = open_virtgpu(&drm_device); } if (drm_fd >= 0) { 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; } wl_array_init(&ctx.dpi); if (dpi) { char* str = strdup(dpi); char* token = strtok(str, ","); // NOLINT(runtime/threadsafe_fn) int* p; while (token) { p = static_cast(wl_array_add(&ctx.dpi, sizeof *p)); assert(p); *p = MAX(MIN_DPI, MIN(atoi(token), MAX_DPI)); token = strtok(NULL, ","); // NOLINT(runtime/threadsafe_fn) } free(str); } // The success of this depends on xkb-data being installed. ctx.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!ctx.xkb_context) { fprintf(stderr, "error: xkb_context_new failed. xkb-data missing?\n"); return EXIT_FAILURE; } if (ctx.virtwl_display_fd != -1) { ctx.display = wl_display_connect_to_fd(ctx.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; } if (!sl_parse_accelerators(&ctx.accelerators, accelerators)) return EXIT_FAILURE; if (!sl_parse_accelerators(&ctx.windowed_accelerators, windowed_accelerators)) return EXIT_FAILURE; ctx.display_event_source.reset( 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); if (ctx.runprog || ctx.xwayland) { // Wayland connection from client. int rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv); errno_assert(!rv); client_fd = sv[0]; } 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, ctx.client); if (ctx.runprog || ctx.xwayland) { ctx.sigchld_event_source.reset( wl_event_loop_add_signal(event_loop, SIGCHLD, sl_handle_sigchld, &ctx)); if (ctx.xwayland) { sl_spawn_xwayland(&ctx, event_loop, sv[1], xwayland_cmd_prefix, xwayland_path, xdisplay, xauth_path, xfont_path, xwayland_gl_driver_path, glamor); } else { pid = fork(); errno_assert(pid != -1); if (pid == 0) { // Unset DISPLAY to prevent X clients from connecting to an existing X // server when X forwarding is not enabled. unsetenv("DISPLAY"); sl_execvp(ctx.runprog[0], ctx.runprog, sv[1]); _exit(EXIT_FAILURE); } ctx.child_pid = pid; } close(sv[1]); } // Attempt to enable tracing. This could be called earlier but would rather // spawn all children first. const bool tracing_needed = ctx.trace_filename || ctx.trace_system; if (tracing_needed) { initialize_tracing(ctx.trace_filename, ctx.trace_system); enable_tracing(!ctx.trace_system); } // Trigger trace and timing log dumps when USR1 signals are received if (tracing_needed || ctx.timing) { ctx.sigusr1_event_source.reset( wl_event_loop_add_signal(event_loop, SIGUSR1, sl_handle_sigusr1, &ctx)); } // Initialize timing log values. if (ctx.timing) { ctx.timing->RecordStartTime(); } 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; if (wl_event_loop_dispatch(event_loop, -1) == -1) { // Ignore EINTR or sommelier will exit when attached by strace or gdb. if (errno != EINTR) return EXIT_FAILURE; } } while (1); return EXIT_SUCCESS; } // NOLINT(readability/fn_size)