424 lines
13 KiB
C++
424 lines
13 KiB
C++
// Copyright 2021 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-ctx.h" // NOLINT(build/include_directory)
|
|
|
|
#include <assert.h>
|
|
#include <cerrno>
|
|
#include <string>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
#include <wayland-util.h>
|
|
|
|
#include "aura-shell-client-protocol.h" // NOLINT(build/include_directory)
|
|
#include "sommelier.h" // NOLINT(build/include_directory)
|
|
#include "sommelier-tracing.h" // NOLINT(build/include_directory)
|
|
|
|
// TODO(b/173147612): Use container_token rather than this name.
|
|
#define DEFAULT_VM_NAME "termina"
|
|
|
|
// Returns the string mapped to the given ATOM_ enum value.
|
|
//
|
|
// Note this is NOT the atom value sent via the X protocol, despite both being
|
|
// ints. Use |sl_context::atoms| to map between X protocol atoms and ATOM_ enum
|
|
// values: If `atoms[i].value = j`, i is the ATOM_ enum value and j is the
|
|
// X protocol atom.
|
|
//
|
|
// If the given value is out of range of the ATOM_ enum, returns NULL.
|
|
const char* sl_context_atom_name(int atom_enum) {
|
|
switch (atom_enum) {
|
|
case ATOM_WM_S0:
|
|
return "WM_S0";
|
|
case ATOM_WM_PROTOCOLS:
|
|
return "WM_PROTOCOLS";
|
|
case ATOM_WM_STATE:
|
|
return "WM_STATE";
|
|
case ATOM_WM_CHANGE_STATE:
|
|
return "WM_CHANGE_STATE";
|
|
case ATOM_WM_DELETE_WINDOW:
|
|
return "WM_DELETE_WINDOW";
|
|
case ATOM_WM_TAKE_FOCUS:
|
|
return "WM_TAKE_FOCUS";
|
|
case ATOM_WM_CLIENT_LEADER:
|
|
return "WM_CLIENT_LEADER";
|
|
case ATOM_WL_SURFACE_ID:
|
|
return "WL_SURFACE_ID";
|
|
case ATOM_UTF8_STRING:
|
|
return "UTF8_STRING";
|
|
case ATOM_MOTIF_WM_HINTS:
|
|
return "_MOTIF_WM_HINTS";
|
|
case ATOM_NET_ACTIVE_WINDOW:
|
|
return "_NET_ACTIVE_WINDOW";
|
|
case ATOM_NET_FRAME_EXTENTS:
|
|
return "_NET_FRAME_EXTENTS";
|
|
case ATOM_NET_STARTUP_ID:
|
|
return "_NET_STARTUP_ID";
|
|
case ATOM_NET_SUPPORTED:
|
|
return "_NET_SUPPORTED";
|
|
case ATOM_NET_SUPPORTING_WM_CHECK:
|
|
return "_NET_SUPPORTING_WM_CHECK";
|
|
case ATOM_NET_WM_NAME:
|
|
return "_NET_WM_NAME";
|
|
case ATOM_NET_WM_MOVERESIZE:
|
|
return "_NET_WM_MOVERESIZE";
|
|
case ATOM_NET_WM_STATE:
|
|
return "_NET_WM_STATE";
|
|
case ATOM_NET_WM_STATE_FULLSCREEN:
|
|
return "_NET_WM_STATE_FULLSCREEN";
|
|
case ATOM_NET_WM_STATE_MAXIMIZED_VERT:
|
|
return "_NET_WM_STATE_MAXIMIZED_VERT";
|
|
case ATOM_NET_WM_STATE_MAXIMIZED_HORZ:
|
|
return "_NET_WM_STATE_MAXIMIZED_HORZ";
|
|
case ATOM_NET_WM_STATE_FOCUSED:
|
|
return "_NET_WM_STATE_FOCUSED";
|
|
case ATOM_CLIPBOARD:
|
|
return "CLIPBOARD";
|
|
case ATOM_CLIPBOARD_MANAGER:
|
|
return "CLIPBOARD_MANAGER";
|
|
case ATOM_TARGETS:
|
|
return "TARGETS";
|
|
case ATOM_TIMESTAMP:
|
|
return "TIMESTAMP";
|
|
case ATOM_TEXT:
|
|
return "TEXT";
|
|
case ATOM_INCR:
|
|
return "INCR";
|
|
case ATOM_WL_SELECTION:
|
|
return "_WL_SELECTION";
|
|
case ATOM_GTK_THEME_VARIANT:
|
|
return "_GTK_THEME_VARIANT";
|
|
case ATOM_XWAYLAND_RANDR_EMU_MONITOR_RECTS:
|
|
return "_XWAYLAND_RANDR_EMU_MONITOR_RECTS";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void sl_context_init_default(struct sl_context* ctx) {
|
|
*ctx = {0};
|
|
ctx->runprog = NULL;
|
|
ctx->display = NULL;
|
|
ctx->host_display = NULL;
|
|
ctx->client = NULL;
|
|
ctx->compositor = NULL;
|
|
ctx->subcompositor = NULL;
|
|
ctx->shm = NULL;
|
|
ctx->shell = NULL;
|
|
ctx->data_device_manager = NULL;
|
|
ctx->xdg_shell = NULL;
|
|
ctx->aura_shell = NULL;
|
|
ctx->viewporter = NULL;
|
|
ctx->linux_dmabuf = NULL;
|
|
ctx->keyboard_extension = NULL;
|
|
ctx->text_input_manager = NULL;
|
|
ctx->text_input_extension = NULL;
|
|
ctx->xdg_output_manager = NULL;
|
|
#ifdef GAMEPAD_SUPPORT
|
|
ctx->gaming_input_manager = NULL;
|
|
#endif
|
|
ctx->display_event_source = NULL;
|
|
ctx->display_ready_event_source = NULL;
|
|
ctx->sigchld_event_source = NULL;
|
|
ctx->sigusr1_event_source = NULL;
|
|
ctx->wm_fd = -1;
|
|
ctx->wayland_channel_fd = -1;
|
|
ctx->virtwl_socket_fd = -1;
|
|
ctx->virtwl_display_fd = -1;
|
|
ctx->wayland_channel_event_source = NULL;
|
|
ctx->virtwl_socket_event_source = NULL;
|
|
ctx->vm_id = DEFAULT_VM_NAME;
|
|
ctx->drm_device = NULL;
|
|
ctx->gbm = NULL;
|
|
ctx->xwayland = 0;
|
|
ctx->xwayland_pid = -1;
|
|
ctx->child_pid = -1;
|
|
ctx->peer_pid = -1;
|
|
ctx->xkb_context = NULL;
|
|
ctx->next_global_id = 1;
|
|
ctx->connection = NULL;
|
|
ctx->connection_event_source = NULL;
|
|
ctx->xfixes_extension = NULL;
|
|
ctx->screen = NULL;
|
|
ctx->window = 0;
|
|
ctx->host_focus_window = NULL;
|
|
ctx->needs_set_input_focus = 0;
|
|
ctx->desired_scale = 1.0;
|
|
ctx->scale = 1.0;
|
|
ctx->virt_scale_x = 1.0;
|
|
ctx->virt_scale_y = 1.0;
|
|
ctx->xdg_scale_x = 1.0;
|
|
ctx->xdg_scale_y = 1.0;
|
|
ctx->application_id = NULL;
|
|
ctx->application_id_property_name = NULL;
|
|
ctx->exit_with_child = 1;
|
|
ctx->sd_notify = NULL;
|
|
ctx->clipboard_manager = 0;
|
|
ctx->frame_color = 0xffffffff;
|
|
ctx->dark_frame_color = 0xff000000;
|
|
ctx->support_damage_buffer = true;
|
|
ctx->fullscreen_mode = ZAURA_SURFACE_FULLSCREEN_MODE_IMMERSIVE;
|
|
ctx->default_seat = NULL;
|
|
ctx->selection_window = XCB_WINDOW_NONE;
|
|
ctx->selection_owner = XCB_WINDOW_NONE;
|
|
ctx->selection_incremental_transfer = 0;
|
|
ctx->selection_request.requestor = XCB_NONE;
|
|
ctx->selection_request.property = XCB_ATOM_NONE;
|
|
ctx->selection_timestamp = XCB_CURRENT_TIME;
|
|
ctx->selection_data_device = NULL;
|
|
ctx->selection_data_offer = NULL;
|
|
ctx->selection_data_source = NULL;
|
|
ctx->selection_data_source_send_fd = -1;
|
|
ctx->selection_send_event_source = NULL;
|
|
ctx->selection_property_reply = NULL;
|
|
ctx->selection_property_offset = 0;
|
|
ctx->selection_event_source = NULL;
|
|
ctx->selection_data_offer_receive_fd = -1;
|
|
ctx->selection_data_ack_pending = 0;
|
|
for (unsigned i = 0; i < ARRAY_SIZE(ctx->atoms); i++) {
|
|
const char* name = sl_context_atom_name(i);
|
|
assert(name != NULL);
|
|
ctx->atoms[i].name = name;
|
|
}
|
|
ctx->timing = NULL;
|
|
ctx->trace_filename = NULL;
|
|
ctx->enable_xshape = false;
|
|
ctx->trace_system = false;
|
|
ctx->use_direct_scale = false;
|
|
|
|
wl_list_init(&ctx->accelerators);
|
|
wl_list_init(&ctx->windowed_accelerators);
|
|
wl_list_init(&ctx->registries);
|
|
wl_list_init(&ctx->globals);
|
|
wl_list_init(&ctx->outputs);
|
|
wl_list_init(&ctx->seats);
|
|
wl_list_init(&ctx->windows);
|
|
wl_list_init(&ctx->unpaired_windows);
|
|
wl_list_init(&ctx->host_outputs);
|
|
wl_list_init(&ctx->selection_data_source_send_pending);
|
|
#ifdef GAMEPAD_SUPPORT
|
|
wl_list_init(&ctx->gamepads);
|
|
#endif
|
|
}
|
|
|
|
static int sl_handle_clipboard_event(int fd, uint32_t mask, void* data) {
|
|
int rv;
|
|
struct sl_context* ctx = (struct sl_context*)data;
|
|
bool readable = false;
|
|
bool hang_up = false;
|
|
|
|
if (mask & WL_EVENT_READABLE)
|
|
readable = true;
|
|
if (mask & WL_EVENT_HANGUP)
|
|
hang_up = true;
|
|
|
|
rv = ctx->channel->handle_pipe(fd, readable, hang_up);
|
|
if (rv) {
|
|
fprintf(stderr, "reading pipe failed with %s\n", strerror(rv));
|
|
return 0;
|
|
}
|
|
|
|
if (hang_up) {
|
|
ctx->clipboard_event_source.reset();
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int sl_handle_wayland_channel_event(int fd, uint32_t mask, void* data) {
|
|
TRACE_EVENT("surface", "sl_handle_wayland_channel_event");
|
|
struct sl_context* ctx = (struct sl_context*)data;
|
|
struct WaylandSendReceive receive = {0};
|
|
int pipe_read_fd = -1;
|
|
enum WaylandChannelEvent event_type = WaylandChannelEvent::None;
|
|
|
|
char fd_buffer[CMSG_LEN(sizeof(int) * WAYLAND_MAX_FDs)];
|
|
struct msghdr msg = {0};
|
|
struct iovec buffer_iov;
|
|
ssize_t bytes;
|
|
int rv;
|
|
|
|
if (!(mask & WL_EVENT_READABLE)) {
|
|
fprintf(stderr,
|
|
"Got error or hangup on virtwl ctx fd"
|
|
" (mask %d), exiting\n",
|
|
mask);
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
receive.channel_fd = fd;
|
|
rv = ctx->channel->handle_channel_event(event_type, receive, pipe_read_fd);
|
|
if (rv) {
|
|
close(ctx->virtwl_socket_fd);
|
|
ctx->virtwl_socket_fd = -1;
|
|
return 0;
|
|
}
|
|
|
|
if (event_type == WaylandChannelEvent::ReceiveAndProxy) {
|
|
struct wl_event_loop* event_loop =
|
|
wl_display_get_event_loop(ctx->host_display);
|
|
|
|
ctx->clipboard_event_source.reset(
|
|
wl_event_loop_add_fd(event_loop, pipe_read_fd, WL_EVENT_READABLE,
|
|
sl_handle_clipboard_event, ctx));
|
|
} else if (event_type != WaylandChannelEvent::Receive) {
|
|
return 1;
|
|
}
|
|
|
|
buffer_iov.iov_base = receive.data;
|
|
buffer_iov.iov_len = receive.data_size;
|
|
|
|
msg.msg_iov = &buffer_iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = fd_buffer;
|
|
|
|
if (receive.num_fds) {
|
|
struct cmsghdr* cmsg;
|
|
|
|
// Need to set msg_controllen so CMSG_FIRSTHDR will return the first
|
|
// cmsghdr. We copy every fd we just received from the ioctl into this
|
|
// cmsghdr.
|
|
msg.msg_controllen = sizeof(fd_buffer);
|
|
cmsg = CMSG_FIRSTHDR(&msg);
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_RIGHTS;
|
|
cmsg->cmsg_len = CMSG_LEN(receive.num_fds * sizeof(int));
|
|
memcpy(CMSG_DATA(cmsg), receive.fds, receive.num_fds * sizeof(int));
|
|
msg.msg_controllen = cmsg->cmsg_len;
|
|
}
|
|
|
|
bytes = sendmsg(ctx->virtwl_socket_fd, &msg, MSG_NOSIGNAL);
|
|
errno_assert(bytes == static_cast<ssize_t>(receive.data_size));
|
|
|
|
while (receive.num_fds--)
|
|
close(receive.fds[receive.num_fds]);
|
|
|
|
if (receive.data)
|
|
free(receive.data);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int sl_handle_virtwl_socket_event(int fd, uint32_t mask, void* data) {
|
|
TRACE_EVENT("surface", "sl_handle_virtwl_socket_event");
|
|
struct sl_context* ctx = (struct sl_context*)data;
|
|
struct WaylandSendReceive send = {0};
|
|
char fd_buffer[CMSG_LEN(sizeof(int) * WAYLAND_MAX_FDs)];
|
|
uint8_t data_buffer[DEFAULT_BUFFER_SIZE];
|
|
|
|
struct iovec buffer_iov;
|
|
struct msghdr msg = {0};
|
|
struct cmsghdr* cmsg;
|
|
ssize_t bytes;
|
|
int rv;
|
|
|
|
if (!(mask & WL_EVENT_READABLE)) {
|
|
fprintf(stderr,
|
|
"Got error or hangup on virtwl socket"
|
|
" (mask %d), exiting\n",
|
|
mask);
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
buffer_iov.iov_base = data_buffer;
|
|
buffer_iov.iov_len = ctx->channel->max_send_size();
|
|
|
|
msg.msg_iov = &buffer_iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = fd_buffer;
|
|
msg.msg_controllen = sizeof(fd_buffer);
|
|
|
|
bytes = recvmsg(ctx->virtwl_socket_fd, &msg, 0);
|
|
errno_assert(bytes > 0);
|
|
|
|
// If there were any FDs recv'd by recvmsg, there will be some data in the
|
|
// msg_control buffer. To get the FDs out we iterate all cmsghdr's within and
|
|
// unpack the FDs if the cmsghdr type is SCM_RIGHTS.
|
|
for (cmsg = msg.msg_controllen != 0 ? CMSG_FIRSTHDR(&msg) : NULL; cmsg;
|
|
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
|
|
size_t cmsg_fd_count;
|
|
|
|
if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS)
|
|
continue;
|
|
|
|
cmsg_fd_count = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
|
|
|
|
// fd_count will never exceed WAYLAND_MAX_FDs because the
|
|
// control message buffer only allocates enough space for that many FDs.
|
|
memcpy(&send.fds[send.num_fds], CMSG_DATA(cmsg),
|
|
cmsg_fd_count * sizeof(int));
|
|
send.num_fds += cmsg_fd_count;
|
|
}
|
|
|
|
send.channel_fd = ctx->wayland_channel_fd;
|
|
send.data = data_buffer;
|
|
send.data_size = bytes;
|
|
|
|
rv = ctx->channel->send(send);
|
|
errno_assert(!rv);
|
|
|
|
while (send.num_fds--)
|
|
close(send.fds[send.num_fds]);
|
|
|
|
return 1;
|
|
}
|
|
|
|
bool sl_context_init_wayland_channel(struct sl_context* ctx,
|
|
struct wl_event_loop* event_loop,
|
|
bool display) {
|
|
if (ctx->channel == NULL) {
|
|
// Running in noop mode, without virtualization.
|
|
return true;
|
|
}
|
|
int rv = ctx->channel->init();
|
|
if (rv) {
|
|
fprintf(stderr, "error: could not initialize wayland channel: %s\n",
|
|
strerror(-rv));
|
|
return false;
|
|
}
|
|
if (!display) {
|
|
// We use a wayland virtual context unless display was explicitly specified.
|
|
// WARNING: It's critical that we never call wl_display_roundtrip
|
|
// as we're not spawning a new thread to handle forwarding. Calling
|
|
// wl_display_roundtrip will cause a deadlock.
|
|
int vws[2];
|
|
|
|
// Connection to virtwl channel.
|
|
rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, vws);
|
|
errno_assert(!rv);
|
|
|
|
ctx->virtwl_socket_fd = vws[0];
|
|
ctx->virtwl_display_fd = vws[1];
|
|
|
|
rv = ctx->channel->create_context(ctx->wayland_channel_fd);
|
|
if (rv) {
|
|
fprintf(stderr, "error: failed to create virtwl context: %s\n",
|
|
strerror(-rv));
|
|
return false;
|
|
}
|
|
|
|
ctx->virtwl_socket_event_source.reset(wl_event_loop_add_fd(
|
|
event_loop, ctx->virtwl_socket_fd, WL_EVENT_READABLE,
|
|
sl_handle_virtwl_socket_event, ctx));
|
|
ctx->wayland_channel_event_source.reset(wl_event_loop_add_fd(
|
|
event_loop, ctx->wayland_channel_fd, WL_EVENT_READABLE,
|
|
sl_handle_wayland_channel_event, ctx));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
sl_window* sl_context_lookup_window_for_surface(struct sl_context* ctx,
|
|
wl_resource* resource) {
|
|
sl_window* surface_window = NULL;
|
|
sl_window* window;
|
|
|
|
wl_list_for_each(window, &ctx->windows, link) {
|
|
if (window->host_surface_id == wl_resource_get_id(resource)) {
|
|
surface_window = window;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return surface_window;
|
|
}
|