aboutsummaryrefslogtreecommitdiff
path: root/xwayland
diff options
context:
space:
mode:
Diffstat (limited to 'xwayland')
-rw-r--r--xwayland/meson.build51
-rw-r--r--xwayland/selection/dnd.c339
-rw-r--r--xwayland/selection/incoming.c464
-rw-r--r--xwayland/selection/outgoing.c419
-rw-r--r--xwayland/selection/selection.c317
-rw-r--r--xwayland/sockets.c168
-rw-r--r--xwayland/sockets.h7
-rw-r--r--xwayland/xwayland.c488
-rw-r--r--xwayland/xwm.c1802
9 files changed, 4055 insertions, 0 deletions
diff --git a/xwayland/meson.build b/xwayland/meson.build
new file mode 100644
index 00000000..e5f9b9be
--- /dev/null
+++ b/xwayland/meson.build
@@ -0,0 +1,51 @@
+xwayland_libs = []
+xwayland_required = [
+ 'xcb',
+ 'xcb-composite',
+ 'xcb-render',
+ 'xcb-xfixes',
+]
+xwayland_optional = [
+ 'xcb-errors',
+ 'xcb-icccm',
+]
+
+foreach lib : xwayland_required
+ dep = dependency(lib, required: get_option('xwayland'))
+ if not dep.found()
+ subdir_done()
+ endif
+
+ xwayland_libs += dep
+endforeach
+
+foreach lib : xwayland_optional
+ dep = dependency(lib, required: get_option(lib))
+ if dep.found()
+ xwayland_libs += dep
+ conf_data.set10('WLR_HAS_' + lib.underscorify().to_upper(), true)
+ endif
+endforeach
+
+lib_wlr_xwayland = static_library(
+ 'wlr_xwayland',
+ files(
+ 'selection/dnd.c',
+ 'selection/incoming.c',
+ 'selection/outgoing.c',
+ 'selection/selection.c',
+ 'sockets.c',
+ 'xwayland.c',
+ 'xwm.c',
+ ),
+ include_directories: wlr_inc,
+ dependencies: [
+ wayland_server,
+ xwayland_libs,
+ xkbcommon,
+ pixman,
+ ],
+)
+
+wlr_parts += lib_wlr_xwayland
+conf_data.set10('WLR_HAS_XWAYLAND', true)
diff --git a/xwayland/selection/dnd.c b/xwayland/selection/dnd.c
new file mode 100644
index 00000000..ec5f16c7
--- /dev/null
+++ b/xwayland/selection/dnd.c
@@ -0,0 +1,339 @@
+#include <assert.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_gtk_primary_selection.h>
+#include <wlr/util/log.h>
+#include <xcb/xfixes.h>
+#include "xwayland/xwm.h"
+#include "xwayland/selection.h"
+
+static xcb_atom_t data_device_manager_dnd_action_to_atom(
+ struct wlr_xwm *xwm, enum wl_data_device_manager_dnd_action action) {
+ if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) {
+ return xwm->atoms[DND_ACTION_COPY];
+ } else if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) {
+ return xwm->atoms[DND_ACTION_MOVE];
+ } else if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) {
+ return xwm->atoms[DND_ACTION_ASK];
+ }
+ return XCB_ATOM_NONE;
+}
+
+static enum wl_data_device_manager_dnd_action
+ data_device_manager_dnd_action_from_atom(struct wlr_xwm *xwm,
+ enum atom_name atom) {
+ if (atom == xwm->atoms[DND_ACTION_COPY] ||
+ atom == xwm->atoms[DND_ACTION_PRIVATE]) {
+ return WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
+ } else if (atom == xwm->atoms[DND_ACTION_MOVE]) {
+ return WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
+ } else if (atom == xwm->atoms[DND_ACTION_ASK]) {
+ return WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK;
+ }
+ return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
+}
+
+static void xwm_dnd_send_event(struct wlr_xwm *xwm, xcb_atom_t type,
+ xcb_client_message_data_t *data) {
+ struct wlr_xwayland_surface *dest = xwm->drag_focus;
+ assert(dest != NULL);
+
+ xcb_client_message_event_t event = {
+ .response_type = XCB_CLIENT_MESSAGE,
+ .format = 32,
+ .sequence = 0,
+ .window = dest->window_id,
+ .type = type,
+ .data = *data,
+ };
+
+ xcb_send_event(xwm->xcb_conn,
+ 0, // propagate
+ dest->window_id,
+ XCB_EVENT_MASK_NO_EVENT,
+ (const char *)&event);
+ xcb_flush(xwm->xcb_conn);
+}
+
+static void xwm_dnd_send_enter(struct wlr_xwm *xwm) {
+ struct wlr_drag *drag = xwm->drag;
+ assert(drag != NULL);
+ struct wl_array *mime_types = &drag->source->mime_types;
+
+ xcb_client_message_data_t data = { 0 };
+ data.data32[0] = xwm->dnd_window;
+ data.data32[1] = XDND_VERSION << 24;
+
+ // If we have 3 MIME types or less, we can send them directly in the
+ // DND_ENTER message
+ size_t n = mime_types->size / sizeof(char *);
+ if (n <= 3) {
+ size_t i = 0;
+ char **mime_type_ptr;
+ wl_array_for_each(mime_type_ptr, mime_types) {
+ char *mime_type = *mime_type_ptr;
+ data.data32[2+i] = xwm_mime_type_to_atom(xwm, mime_type);
+ ++i;
+ }
+ } else {
+ // Let the client know that targets are not contained in the message
+ // data and must be retrieved with the DND_TYPE_LIST property
+ data.data32[1] |= 1;
+
+ xcb_atom_t targets[n];
+ size_t i = 0;
+ char **mime_type_ptr;
+ wl_array_for_each(mime_type_ptr, mime_types) {
+ char *mime_type = *mime_type_ptr;
+ targets[i] = xwm_mime_type_to_atom(xwm, mime_type);
+ ++i;
+ }
+
+ xcb_change_property(xwm->xcb_conn,
+ XCB_PROP_MODE_REPLACE,
+ xwm->dnd_window,
+ xwm->atoms[DND_TYPE_LIST],
+ XCB_ATOM_ATOM,
+ 32, // format
+ n, targets);
+ }
+
+ xwm_dnd_send_event(xwm, xwm->atoms[DND_ENTER], &data);
+}
+
+static void xwm_dnd_send_position(struct wlr_xwm *xwm, uint32_t time, int16_t x,
+ int16_t y) {
+ struct wlr_drag *drag = xwm->drag;
+ assert(drag != NULL);
+
+ xcb_client_message_data_t data = { 0 };
+ data.data32[0] = xwm->dnd_window;
+ data.data32[2] = (x << 16) | y;
+ data.data32[3] = time;
+ data.data32[4] =
+ data_device_manager_dnd_action_to_atom(xwm, drag->source->actions);
+
+ xwm_dnd_send_event(xwm, xwm->atoms[DND_POSITION], &data);
+}
+
+static void xwm_dnd_send_drop(struct wlr_xwm *xwm, uint32_t time) {
+ struct wlr_drag *drag = xwm->drag;
+ assert(drag != NULL);
+ struct wlr_xwayland_surface *dest = xwm->drag_focus;
+ assert(dest != NULL);
+
+ xcb_client_message_data_t data = { 0 };
+ data.data32[0] = xwm->dnd_window;
+ data.data32[2] = time;
+
+ xwm_dnd_send_event(xwm, xwm->atoms[DND_DROP], &data);
+}
+
+static void xwm_dnd_send_leave(struct wlr_xwm *xwm) {
+ struct wlr_drag *drag = xwm->drag;
+ assert(drag != NULL);
+ struct wlr_xwayland_surface *dest = xwm->drag_focus;
+ assert(dest != NULL);
+
+ xcb_client_message_data_t data = { 0 };
+ data.data32[0] = xwm->dnd_window;
+
+ xwm_dnd_send_event(xwm, xwm->atoms[DND_LEAVE], &data);
+}
+
+/*static void xwm_dnd_send_finished(struct wlr_xwm *xwm) {
+ struct wlr_drag *drag = xwm->drag;
+ assert(drag != NULL);
+ struct wlr_xwayland_surface *dest = xwm->drag_focus;
+ assert(dest != NULL);
+
+ xcb_client_message_data_t data = { 0 };
+ data.data32[0] = xwm->dnd_window;
+ data.data32[1] = drag->source->accepted;
+
+ if (drag->source->accepted) {
+ data.data32[2] = data_device_manager_dnd_action_to_atom(xwm,
+ drag->source->current_dnd_action);
+ }
+
+ xwm_dnd_send_event(xwm, xwm->atoms[DND_FINISHED], &data);
+}*/
+
+int xwm_handle_selection_client_message(struct wlr_xwm *xwm,
+ xcb_client_message_event_t *ev) {
+ if (ev->type == xwm->atoms[DND_STATUS]) {
+ if (xwm->drag == NULL) {
+ wlr_log(WLR_DEBUG, "ignoring XdndStatus client message because "
+ "there's no drag");
+ return 1;
+ }
+
+ xcb_client_message_data_t *data = &ev->data;
+ xcb_window_t target_window = data->data32[0];
+ bool accepted = data->data32[1] & 1;
+ xcb_atom_t action_atom = data->data32[4];
+
+ if (xwm->drag_focus == NULL ||
+ target_window != xwm->drag_focus->window_id) {
+ wlr_log(WLR_DEBUG, "ignoring XdndStatus client message because "
+ "it doesn't match the current drag focus window ID");
+ return 1;
+ }
+
+ enum wl_data_device_manager_dnd_action action =
+ data_device_manager_dnd_action_from_atom(xwm, action_atom);
+
+ struct wlr_drag *drag = xwm->drag;
+ assert(drag != NULL);
+
+ drag->source->accepted = accepted;
+ wlr_data_source_dnd_action(drag->source, action);
+
+ wlr_log(WLR_DEBUG, "DND_STATUS window=%d accepted=%d action=%d",
+ target_window, accepted, action);
+ return 1;
+ } else if (ev->type == xwm->atoms[DND_FINISHED]) {
+ // This should only happen after the drag has ended, but before the drag
+ // source is destroyed
+ if (xwm->seat == NULL || xwm->seat->drag_source == NULL ||
+ xwm->drag != NULL) {
+ wlr_log(WLR_DEBUG, "ignoring XdndFinished client message because "
+ "there's no finished drag");
+ return 1;
+ }
+
+ struct wlr_data_source *source = xwm->seat->drag_source;
+
+ xcb_client_message_data_t *data = &ev->data;
+ xcb_window_t target_window = data->data32[0];
+ bool performed = data->data32[1] & 1;
+ xcb_atom_t action_atom = data->data32[2];
+
+ if (xwm->drag_focus == NULL ||
+ target_window != xwm->drag_focus->window_id) {
+ wlr_log(WLR_DEBUG, "ignoring XdndFinished client message because "
+ "it doesn't match the finished drag focus window ID");
+ return 1;
+ }
+
+ enum wl_data_device_manager_dnd_action action =
+ data_device_manager_dnd_action_from_atom(xwm, action_atom);
+
+ if (performed) {
+ wlr_data_source_dnd_finish(source);
+ }
+
+ wlr_log(WLR_DEBUG, "DND_FINISH window=%d performed=%d action=%d",
+ target_window, performed, action);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+static void seat_handle_drag_focus(struct wl_listener *listener, void *data) {
+ struct wlr_drag *drag = data;
+ struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_drag_focus);
+
+ struct wlr_xwayland_surface *focus = NULL;
+ if (drag->focus != NULL) {
+ // TODO: check for subsurfaces?
+ struct wlr_xwayland_surface *surface;
+ wl_list_for_each(surface, &xwm->surfaces, link) {
+ if (surface->surface == drag->focus) {
+ focus = surface;
+ break;
+ }
+ }
+ }
+
+ if (focus == xwm->drag_focus) {
+ return;
+ }
+
+ if (xwm->drag_focus != NULL) {
+ wlr_data_source_dnd_action(drag->source,
+ WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE);
+ xwm_dnd_send_leave(xwm);
+ }
+
+ xwm->drag_focus = focus;
+
+ if (xwm->drag_focus != NULL) {
+ xwm_dnd_send_enter(xwm);
+ }
+}
+
+static void seat_handle_drag_motion(struct wl_listener *listener, void *data) {
+ struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_drag_motion);
+ struct wlr_drag_motion_event *event = data;
+ struct wlr_xwayland_surface *surface = xwm->drag_focus;
+
+ if (surface == NULL) {
+ return; // No xwayland surface focused
+ }
+
+ xwm_dnd_send_position(xwm, event->time, surface->x + (int16_t)event->sx,
+ surface->y + (int16_t)event->sy);
+}
+
+static void seat_handle_drag_drop(struct wl_listener *listener, void *data) {
+ struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_drag_drop);
+ struct wlr_drag_drop_event *event = data;
+
+ if (xwm->drag_focus == NULL) {
+ return; // No xwayland surface focused
+ }
+
+ wlr_log(WLR_DEBUG, "Wayland drag dropped over an Xwayland window");
+ xwm_dnd_send_drop(xwm, event->time);
+}
+
+static void seat_handle_drag_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_drag_destroy);
+
+ // Don't reset drag focus yet because the target will read the drag source
+ // right after
+ if (xwm->drag_focus != NULL && !xwm->drag->source->accepted) {
+ wlr_log(WLR_DEBUG, "Wayland drag cancelled over an Xwayland window");
+ xwm_dnd_send_leave(xwm);
+ }
+
+ wl_list_remove(&xwm->seat_drag_focus.link);
+ wl_list_remove(&xwm->seat_drag_motion.link);
+ wl_list_remove(&xwm->seat_drag_drop.link);
+ wl_list_remove(&xwm->seat_drag_destroy.link);
+ xwm->drag = NULL;
+}
+
+static void seat_handle_drag_source_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_xwm *xwm =
+ wl_container_of(listener, xwm, seat_drag_source_destroy);
+
+ wl_list_remove(&xwm->seat_drag_source_destroy.link);
+ xwm->drag_focus = NULL;
+}
+
+void xwm_seat_handle_start_drag(struct wlr_xwm *xwm, struct wlr_drag *drag) {
+ xwm->drag = drag;
+ xwm->drag_focus = NULL;
+
+ if (drag != NULL) {
+ wl_signal_add(&drag->events.focus, &xwm->seat_drag_focus);
+ xwm->seat_drag_focus.notify = seat_handle_drag_focus;
+ wl_signal_add(&drag->events.motion, &xwm->seat_drag_motion);
+ xwm->seat_drag_motion.notify = seat_handle_drag_motion;
+ wl_signal_add(&drag->events.drop, &xwm->seat_drag_drop);
+ xwm->seat_drag_drop.notify = seat_handle_drag_drop;
+ wl_signal_add(&drag->events.destroy, &xwm->seat_drag_destroy);
+ xwm->seat_drag_destroy.notify = seat_handle_drag_destroy;
+
+ wl_signal_add(&drag->source->events.destroy,
+ &xwm->seat_drag_source_destroy);
+ xwm->seat_drag_source_destroy.notify = seat_handle_drag_source_destroy;
+ }
+}
diff --git a/xwayland/selection/incoming.c b/xwayland/selection/incoming.c
new file mode 100644
index 00000000..3e97ca04
--- /dev/null
+++ b/xwayland/selection/incoming.c
@@ -0,0 +1,464 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_primary_selection.h>
+#include <wlr/util/log.h>
+#include <xcb/xfixes.h>
+#include "xwayland/selection.h"
+#include "xwayland/xwm.h"
+
+/**
+ * Write the X11 selection to a Wayland client.
+ */
+static int xwm_data_source_write(int fd, uint32_t mask, void *data) {
+ struct wlr_xwm_selection_transfer *transfer = data;
+ struct wlr_xwm *xwm = transfer->selection->xwm;
+
+ char *property = xcb_get_property_value(transfer->property_reply);
+ int remainder = xcb_get_property_value_length(transfer->property_reply) -
+ transfer->property_start;
+
+ ssize_t len = write(fd, property + transfer->property_start, remainder);
+ if (len == -1) {
+ xwm_selection_transfer_destroy_property_reply(transfer);
+ xwm_selection_transfer_remove_source(transfer);
+ xwm_selection_transfer_close_source_fd(transfer);
+ wlr_log(WLR_ERROR, "write error to target fd: %m");
+ return 1;
+ }
+
+ wlr_log(WLR_DEBUG, "wrote %zd (chunk size %zd) of %d bytes",
+ transfer->property_start + len,
+ len, xcb_get_property_value_length(transfer->property_reply));
+
+ transfer->property_start += len;
+ if (len == remainder) {
+ xwm_selection_transfer_destroy_property_reply(transfer);
+ xwm_selection_transfer_remove_source(transfer);
+
+ if (transfer->incr) {
+ wlr_log(WLR_DEBUG, "deleting property");
+ xcb_delete_property(xwm->xcb_conn, transfer->selection->window,
+ xwm->atoms[WL_SELECTION]);
+ xcb_flush(xwm->xcb_conn);
+ } else {
+ wlr_log(WLR_DEBUG, "transfer complete");
+ xwm_selection_transfer_close_source_fd(transfer);
+ }
+ }
+
+ return 1;
+}
+
+static void xwm_write_property(struct wlr_xwm_selection_transfer *transfer,
+ xcb_get_property_reply_t *reply) {
+ struct wlr_xwm *xwm = transfer->selection->xwm;
+
+ transfer->property_start = 0;
+ transfer->property_reply = reply;
+
+ xwm_data_source_write(transfer->source_fd, WL_EVENT_WRITABLE, transfer);
+
+ if (transfer->property_reply != NULL) {
+ struct wl_event_loop *loop =
+ wl_display_get_event_loop(xwm->xwayland->wl_display);
+ transfer->source = wl_event_loop_add_fd(loop,
+ transfer->source_fd, WL_EVENT_WRITABLE, xwm_data_source_write,
+ transfer);
+ }
+}
+
+void xwm_get_incr_chunk(struct wlr_xwm_selection_transfer *transfer) {
+ struct wlr_xwm *xwm = transfer->selection->xwm;
+ wlr_log(WLR_DEBUG, "xwm_get_incr_chunk");
+
+ xcb_get_property_cookie_t cookie = xcb_get_property(xwm->xcb_conn,
+ 0, // delete
+ transfer->selection->window,
+ xwm->atoms[WL_SELECTION],
+ XCB_GET_PROPERTY_TYPE_ANY,
+ 0, // offset
+ 0x1fffffff // length
+ );
+
+ xcb_get_property_reply_t *reply =
+ xcb_get_property_reply(xwm->xcb_conn, cookie, NULL);
+ if (reply == NULL) {
+ wlr_log(WLR_ERROR, "cannot get selection property");
+ return;
+ }
+ //dump_property(xwm, xwm->atoms[WL_SELECTION], reply);
+
+ if (xcb_get_property_value_length(reply) > 0) {
+ /* Reply's ownership is transferred to xwm, which is responsible
+ * for freeing it */
+ xwm_write_property(transfer, reply);
+ } else {
+ wlr_log(WLR_DEBUG, "transfer complete");
+ xwm_selection_transfer_close_source_fd(transfer);
+ free(reply);
+ }
+}
+
+static void xwm_selection_get_data(struct wlr_xwm_selection *selection) {
+ struct wlr_xwm *xwm = selection->xwm;
+
+ xcb_get_property_cookie_t cookie = xcb_get_property(xwm->xcb_conn,
+ 1, // delete
+ selection->window,
+ xwm->atoms[WL_SELECTION],
+ XCB_GET_PROPERTY_TYPE_ANY,
+ 0, // offset
+ 0x1fffffff // length
+ );
+
+ xcb_get_property_reply_t *reply =
+ xcb_get_property_reply(xwm->xcb_conn, cookie, NULL);
+ if (reply == NULL) {
+ wlr_log(WLR_ERROR, "Cannot get selection property");
+ return;
+ }
+
+ struct wlr_xwm_selection_transfer *transfer = &selection->incoming;
+ if (reply->type == xwm->atoms[INCR]) {
+ transfer->incr = true;
+ free(reply);
+ } else {
+ transfer->incr = false;
+ // reply's ownership is transferred to wm, which is responsible
+ // for freeing it
+ xwm_write_property(transfer, reply);
+ }
+}
+
+static void source_send(struct wlr_xwm_selection *selection,
+ struct wl_array *mime_types, struct wl_array *mime_types_atoms,
+ const char *requested_mime_type, int fd) {
+ struct wlr_xwm *xwm = selection->xwm;
+ struct wlr_xwm_selection_transfer *transfer = &selection->incoming;
+
+ xcb_atom_t *atoms = mime_types_atoms->data;
+ bool found = false;
+ xcb_atom_t mime_type_atom;
+ char **mime_type_ptr;
+ size_t i = 0;
+ wl_array_for_each(mime_type_ptr, mime_types) {
+ char *mime_type = *mime_type_ptr;
+ if (strcmp(mime_type, requested_mime_type) == 0) {
+ found = true;
+ mime_type_atom = atoms[i];
+ break;
+ }
+ ++i;
+ }
+ if (!found) {
+ wlr_log(WLR_DEBUG, "Cannot send X11 selection to Wayland: "
+ "unsupported MIME type");
+ return;
+ }
+
+ xcb_convert_selection(xwm->xcb_conn,
+ selection->window,
+ selection->atom,
+ mime_type_atom,
+ xwm->atoms[WL_SELECTION],
+ XCB_TIME_CURRENT_TIME);
+
+ xcb_flush(xwm->xcb_conn);
+
+ fcntl(fd, F_SETFL, O_WRONLY | O_NONBLOCK);
+ transfer->source_fd = fd;
+}
+
+struct x11_data_source {
+ struct wlr_data_source base;
+ struct wlr_xwm_selection *selection;
+ struct wl_array mime_types_atoms;
+};
+
+static const struct wlr_data_source_impl data_source_impl;
+
+bool data_source_is_xwayland(
+ struct wlr_data_source *wlr_source) {
+ return wlr_source->impl == &data_source_impl;
+}
+
+static struct x11_data_source *data_source_from_wlr_data_source(
+ struct wlr_data_source *wlr_source) {
+ assert(data_source_is_xwayland(wlr_source));
+ return (struct x11_data_source *)wlr_source;
+}
+
+static void data_source_send(struct wlr_data_source *wlr_source,
+ const char *mime_type, int32_t fd) {
+ struct x11_data_source *source =
+ data_source_from_wlr_data_source(wlr_source);
+ struct wlr_xwm_selection *selection = source->selection;
+
+ source_send(selection, &wlr_source->mime_types, &source->mime_types_atoms,
+ mime_type, fd);
+}
+
+static void data_source_cancel(struct wlr_data_source *wlr_source) {
+ struct x11_data_source *source =
+ data_source_from_wlr_data_source(wlr_source);
+ wlr_data_source_finish(&source->base);
+ wl_array_release(&source->mime_types_atoms);
+ free(source);
+}
+
+static const struct wlr_data_source_impl data_source_impl = {
+ .send = data_source_send,
+ .cancel = data_source_cancel,
+};
+
+struct x11_primary_selection_source {
+ struct wlr_primary_selection_source base;
+ struct wlr_xwm_selection *selection;
+ struct wl_array mime_types_atoms;
+};
+
+static const struct wlr_primary_selection_source_impl
+ primary_selection_source_impl;
+
+bool primary_selection_source_is_xwayland(
+ struct wlr_primary_selection_source *wlr_source) {
+ return wlr_source->impl == &primary_selection_source_impl;
+}
+
+static void primary_selection_source_send(
+ struct wlr_primary_selection_source *wlr_source,
+ const char *mime_type, int fd) {
+ struct x11_primary_selection_source *source =
+ (struct x11_primary_selection_source *)wlr_source;
+ struct wlr_xwm_selection *selection = source->selection;
+
+ source_send(selection, &wlr_source->mime_types, &source->mime_types_atoms,
+ mime_type, fd);
+}
+
+static void primary_selection_source_destroy(
+ struct wlr_primary_selection_source *wlr_source) {
+ struct x11_primary_selection_source *source =
+ (struct x11_primary_selection_source *)wlr_source;
+ wl_array_release(&source->mime_types_atoms);
+ free(source);
+}
+
+static const struct wlr_primary_selection_source_impl
+ primary_selection_source_impl = {
+ .send = primary_selection_source_send,
+ .destroy = primary_selection_source_destroy,
+};
+
+static bool source_get_targets(struct wlr_xwm_selection *selection,
+ struct wl_array *mime_types, struct wl_array *mime_types_atoms) {
+ struct wlr_xwm *xwm = selection->xwm;
+
+ xcb_get_property_cookie_t cookie = xcb_get_property(xwm->xcb_conn,
+ 1, // delete
+ selection->window,
+ xwm->atoms[WL_SELECTION],
+ XCB_GET_PROPERTY_TYPE_ANY,
+ 0, // offset
+ 4096 // length
+ );
+
+ xcb_get_property_reply_t *reply =
+ xcb_get_property_reply(xwm->xcb_conn, cookie, NULL);
+ if (reply == NULL) {
+ return false;
+ }
+
+ if (reply->type != XCB_ATOM_ATOM) {
+ free(reply);
+ return false;
+ }
+
+ xcb_atom_t *value = xcb_get_property_value(reply);
+ for (uint32_t i = 0; i < reply->value_len; i++) {
+ char *mime_type = NULL;
+
+ if (value[i] == xwm->atoms[UTF8_STRING]) {
+ mime_type = strdup("text/plain;charset=utf-8");
+ } else if (value[i] == xwm->atoms[TEXT]) {
+ mime_type = strdup("text/plain");
+ } else if (value[i] != xwm->atoms[TARGETS] &&
+ value[i] != xwm->atoms[TIMESTAMP]) {
+ xcb_get_atom_name_cookie_t name_cookie =
+ xcb_get_atom_name(xwm->xcb_conn, value[i]);
+ xcb_get_atom_name_reply_t *name_reply =
+ xcb_get_atom_name_reply(xwm->xcb_conn, name_cookie, NULL);
+ if (name_reply == NULL) {
+ continue;
+ }
+ size_t len = xcb_get_atom_name_name_length(name_reply);
+ char *name = xcb_get_atom_name_name(name_reply); // not a C string
+ if (memchr(name, '/', len) != NULL) {
+ mime_type = malloc((len + 1) * sizeof(char));
+ if (mime_type == NULL) {
+ free(name_reply);
+ continue;
+ }
+ memcpy(mime_type, name, len);
+ mime_type[len] = '\0';
+ }
+ free(name_reply);
+ }
+
+ if (mime_type != NULL) {
+ char **mime_type_ptr =
+ wl_array_add(mime_types, sizeof(*mime_type_ptr));
+ if (mime_type_ptr == NULL) {
+ free(mime_type);
+ break;
+ }
+ *mime_type_ptr = mime_type;
+
+ xcb_atom_t *atom_ptr =
+ wl_array_add(mime_types_atoms, sizeof(*atom_ptr));
+ if (atom_ptr == NULL) {
+ break;
+ }
+ *atom_ptr = value[i];
+ }
+ }
+
+ free(reply);
+ return true;
+}
+
+static void xwm_selection_get_targets(struct wlr_xwm_selection *selection) {
+ // set the wayland selection to the X11 selection
+ struct wlr_xwm *xwm = selection->xwm;
+
+ if (selection == &xwm->clipboard_selection) {
+ struct x11_data_source *source =
+ calloc(1, sizeof(struct x11_data_source));
+ if (source == NULL) {
+ return;
+ }
+ wlr_data_source_init(&source->base, &data_source_impl);
+
+ source->selection = selection;
+ wl_array_init(&source->mime_types_atoms);
+
+ bool ok = source_get_targets(selection, &source->base.mime_types,
+ &source->mime_types_atoms);
+ if (ok) {
+ wlr_seat_set_selection(xwm->seat, &source->base,
+ wl_display_next_serial(xwm->xwayland->wl_display));
+ } else {
+ wlr_data_source_cancel(&source->base);
+ }
+ } else if (selection == &xwm->primary_selection) {
+ struct x11_primary_selection_source *source =
+ calloc(1, sizeof(struct x11_primary_selection_source));
+ if (source == NULL) {
+ return;
+ }
+ wlr_primary_selection_source_init(&source->base,
+ &primary_selection_source_impl);
+
+ source->selection = selection;
+ wl_array_init(&source->mime_types_atoms);
+
+ bool ok = source_get_targets(selection, &source->base.mime_types,
+ &source->mime_types_atoms);
+ if (ok) {
+ wlr_seat_set_primary_selection(xwm->seat, &source->base);
+ } else {
+ wlr_primary_selection_source_destroy(&source->base);
+ }
+ } else if (selection == &xwm->dnd_selection) {
+ // TODO
+ }
+}
+
+void xwm_handle_selection_notify(struct wlr_xwm *xwm,
+ xcb_selection_notify_event_t *event) {
+ wlr_log(WLR_DEBUG, "XCB_SELECTION_NOTIFY (selection=%u, property=%u, target=%u)",
+ event->selection, event->property,
+ event->target);
+
+ struct wlr_xwm_selection *selection =
+ xwm_get_selection(xwm, event->selection);
+ if (selection == NULL) {
+ return;
+ }
+
+ if (event->property == XCB_ATOM_NONE) {
+ wlr_log(WLR_ERROR, "convert selection failed");
+ } else if (event->target == xwm->atoms[TARGETS]) {
+ // No xwayland surface focused, deny access to clipboard
+ if (xwm->focus_surface == NULL) {
+ wlr_log(WLR_DEBUG, "denying write access to clipboard: "
+ "no xwayland surface focused");
+ return;
+ }
+
+ // This sets the Wayland clipboard (by calling wlr_seat_set_selection)
+ xwm_selection_get_targets(selection);
+ } else {
+ xwm_selection_get_data(selection);
+ }
+}
+
+int xwm_handle_xfixes_selection_notify(struct wlr_xwm *xwm,
+ xcb_xfixes_selection_notify_event_t *event) {
+ wlr_log(WLR_DEBUG, "XCB_XFIXES_SELECTION_NOTIFY (selection=%u, owner=%u)",
+ event->selection, event->owner);
+
+ struct wlr_xwm_selection *selection =
+ xwm_get_selection(xwm, event->selection);
+ if (selection == NULL) {
+ return 0;
+ }
+
+ if (event->owner == XCB_WINDOW_NONE) {
+ if (selection->owner != selection->window) {
+ // A real X client selection went away, not our
+ // proxy selection
+ if (selection == &xwm->clipboard_selection) {
+ wlr_seat_set_selection(xwm->seat, NULL,
+ wl_display_next_serial(xwm->xwayland->wl_display));
+ } else if (selection == &xwm->primary_selection) {
+ wlr_seat_set_primary_selection(xwm->seat, NULL);
+ } else if (selection == &xwm->dnd_selection) {
+ // TODO: DND
+ } else {
+ wlr_log(WLR_DEBUG, "X11 selection has been cleared, but cannot "
+ "clear Wayland selection");
+ }
+ }
+
+ selection->owner = XCB_WINDOW_NONE;
+ return 1;
+ }
+
+ selection->owner = event->owner;
+
+ // We have to use XCB_TIME_CURRENT_TIME when we claim the
+ // selection, so grab the actual timestamp here so we can
+ // answer TIMESTAMP conversion requests correctly.
+ if (event->owner == selection->window) {
+ selection->timestamp = event->timestamp;
+ return 1;
+ }
+
+ struct wlr_xwm_selection_transfer *transfer = &selection->incoming;
+ transfer->incr = false;
+ // doing this will give a selection notify where we actually handle the sync
+ xcb_convert_selection(xwm->xcb_conn, selection->window,
+ selection->atom,
+ xwm->atoms[TARGETS],
+ xwm->atoms[WL_SELECTION],
+ event->timestamp);
+ xcb_flush(xwm->xcb_conn);
+
+ return 1;
+}
diff --git a/xwayland/selection/outgoing.c b/xwayland/selection/outgoing.c
new file mode 100644
index 00000000..fd5021d5
--- /dev/null
+++ b/xwayland/selection/outgoing.c
@@ -0,0 +1,419 @@
+#include <assert.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_primary_selection.h>
+#include <wlr/util/log.h>
+#include <xcb/xfixes.h>
+#include "xwayland/selection.h"
+#include "xwayland/xwm.h"
+
+static void xwm_selection_send_notify(struct wlr_xwm *xwm,
+ xcb_selection_request_event_t *req, bool success) {
+ xcb_selection_notify_event_t selection_notify = {
+ .response_type = XCB_SELECTION_NOTIFY,
+ .sequence = 0,
+ .time = req->time,
+ .requestor = req->requestor,
+ .selection = req->selection,
+ .target = req->target,
+ .property = success ? req->property : XCB_ATOM_NONE,
+ };
+
+ wlr_log(WLR_DEBUG, "SendEvent destination=%d SelectionNotify(31) time=%d "
+ "requestor=%d selection=%d target=%d property=%d", req->requestor,
+ req->time, req->requestor, req->selection, req->target,
+ selection_notify.property);
+ xcb_send_event(xwm->xcb_conn,
+ 0, // propagate
+ req->requestor,
+ XCB_EVENT_MASK_NO_EVENT,
+ (const char *)&selection_notify);
+ xcb_flush(xwm->xcb_conn);
+}
+
+static int xwm_selection_flush_source_data(
+ struct wlr_xwm_selection_transfer *transfer) {
+ xcb_change_property(transfer->selection->xwm->xcb_conn,
+ XCB_PROP_MODE_REPLACE,
+ transfer->request.requestor,
+ transfer->request.property,
+ transfer->request.target,
+ 8, // format
+ transfer->source_data.size,
+ transfer->source_data.data);
+ xcb_flush(transfer->selection->xwm->xcb_conn);
+ transfer->property_set = true;
+ size_t length = transfer->source_data.size;
+ transfer->source_data.size = 0;
+ return length;
+}
+
+static void xwm_selection_transfer_start_outgoing(
+ struct wlr_xwm_selection_transfer *transfer);
+
+static void xwm_selection_transfer_destroy_outgoing(
+ struct wlr_xwm_selection_transfer *transfer) {
+ wl_list_remove(&transfer->outgoing_link);
+
+ // Start next queued transfer
+ struct wlr_xwm_selection_transfer *first = NULL;
+ if (!wl_list_empty(&transfer->selection->outgoing)) {
+ first = wl_container_of(transfer->selection->outgoing.prev, first,
+ outgoing_link);
+ xwm_selection_transfer_start_outgoing(first);
+ }
+
+ xwm_selection_transfer_remove_source(transfer);
+ xwm_selection_transfer_close_source_fd(transfer);
+ wl_array_release(&transfer->source_data);
+ free(transfer);
+}
+
+static int xwm_data_source_read(int fd, uint32_t mask, void *data) {
+ struct wlr_xwm_selection_transfer *transfer = data;
+ struct wlr_xwm *xwm = transfer->selection->xwm;
+
+ void *p;
+ size_t current = transfer->source_data.size;
+ if (transfer->source_data.size < INCR_CHUNK_SIZE) {
+ p = wl_array_add(&transfer->source_data, INCR_CHUNK_SIZE);
+ if (p == NULL) {
+ wlr_log(WLR_ERROR, "Could not allocate selection source_data");
+ goto error_out;
+ }
+ } else {
+ p = (char *)transfer->source_data.data + transfer->source_data.size;
+ }
+
+ size_t available = transfer->source_data.alloc - current;
+ ssize_t len = read(fd, p, available);
+ if (len == -1) {
+ wlr_log(WLR_ERROR, "read error from data source: %m");
+ goto error_out;
+ }
+
+ wlr_log(WLR_DEBUG, "read %zd bytes (available %zu, mask 0x%x)", len,
+ available, mask);
+
+ transfer->source_data.size = current + len;
+ if (transfer->source_data.size >= INCR_CHUNK_SIZE) {
+ if (!transfer->incr) {
+ wlr_log(WLR_DEBUG, "got %zu bytes, starting incr",
+ transfer->source_data.size);
+
+ size_t incr_chunk_size = INCR_CHUNK_SIZE;
+ xcb_change_property(xwm->xcb_conn,
+ XCB_PROP_MODE_REPLACE,
+ transfer->request.requestor,
+ transfer->request.property,
+ xwm->atoms[INCR],
+ 32, /* format */
+ 1, &incr_chunk_size);
+ transfer->incr = true;
+ transfer->property_set = true;
+ transfer->flush_property_on_delete = true;
+ xwm_selection_transfer_remove_source(transfer);
+ xwm_selection_send_notify(xwm, &transfer->request, true);
+ } else if (transfer->property_set) {
+ wlr_log(WLR_DEBUG, "got %zu bytes, waiting for property delete",
+ transfer->source_data.size);
+
+ transfer->flush_property_on_delete = true;
+ xwm_selection_transfer_remove_source(transfer);
+ } else {
+ wlr_log(WLR_DEBUG, "got %zu bytes, property deleted, setting new "
+ "property", transfer->source_data.size);
+ xwm_selection_flush_source_data(transfer);
+ }
+ } else if (len == 0 && !transfer->incr) {
+ wlr_log(WLR_DEBUG, "non-incr transfer complete");
+ xwm_selection_flush_source_data(transfer);
+ xwm_selection_send_notify(xwm, &transfer->request, true);
+ xwm_selection_transfer_destroy_outgoing(transfer);
+ } else if (len == 0 && transfer->incr) {
+ wlr_log(WLR_DEBUG, "incr transfer complete");
+
+ transfer->flush_property_on_delete = true;
+ if (transfer->property_set) {
+ wlr_log(WLR_DEBUG, "got %zu bytes, waiting for property delete",
+ transfer->source_data.size);
+ } else {
+ wlr_log(WLR_DEBUG, "got %zu bytes, property deleted, setting new "
+ "property", transfer->source_data.size);
+ xwm_selection_flush_source_data(transfer);
+ }
+ xwm_selection_transfer_remove_source(transfer);
+ xwm_selection_transfer_close_source_fd(transfer);
+ } else {
+ wlr_log(WLR_DEBUG, "nothing happened, buffered the bytes");
+ }
+
+ return 1;
+
+error_out:
+ xwm_selection_send_notify(xwm, &transfer->request, false);
+ xwm_selection_transfer_destroy_outgoing(transfer);
+ return 0;
+}
+
+void xwm_send_incr_chunk(struct wlr_xwm_selection_transfer *transfer) {
+ wlr_log(WLR_DEBUG, "property deleted");
+
+ transfer->property_set = false;
+ if (transfer->flush_property_on_delete) {
+ wlr_log(WLR_DEBUG, "setting new property, %zu bytes",
+ transfer->source_data.size);
+ transfer->flush_property_on_delete = false;
+ int length = xwm_selection_flush_source_data(transfer);
+
+ if (transfer->source_fd >= 0) {
+ xwm_selection_transfer_start_outgoing(transfer);
+ } else if (length > 0) {
+ /* Transfer is all done, but queue a flush for
+ * the delete of the last chunk so we can set
+ * the 0 sized property to signal the end of
+ * the transfer. */
+ transfer->flush_property_on_delete = true;
+ wl_array_release(&transfer->source_data);
+ wl_array_init(&transfer->source_data);
+ } else {
+ xwm_selection_transfer_destroy_outgoing(transfer);
+ }
+ }
+}
+
+static void xwm_selection_source_send(struct wlr_xwm_selection *selection,
+ const char *mime_type, int32_t fd) {
+ if (selection == &selection->xwm->clipboard_selection) {
+ struct wlr_data_source *source =
+ selection->xwm->seat->selection_source;
+ if (source != NULL) {
+ wlr_data_source_send(source, mime_type, fd);
+ return;
+ }
+ } else if (selection == &selection->xwm->primary_selection) {
+ struct wlr_primary_selection_source *source =
+ selection->xwm->seat->primary_selection_source;
+ if (source != NULL) {
+ wlr_primary_selection_source_send(source, mime_type, fd);
+ return;
+ }
+ } else if (selection == &selection->xwm->dnd_selection) {
+ struct wlr_data_source *source =
+ selection->xwm->seat->drag_source;
+ if (source != NULL) {
+ wlr_data_source_send(source, mime_type, fd);
+ return;
+ }
+ }
+
+ wlr_log(WLR_DEBUG, "not sending selection: no selection source available");
+}
+
+static void xwm_selection_transfer_start_outgoing(
+ struct wlr_xwm_selection_transfer *transfer) {
+ struct wlr_xwm *xwm = transfer->selection->xwm;
+ struct wl_event_loop *loop =
+ wl_display_get_event_loop(xwm->xwayland->wl_display);
+ transfer->source = wl_event_loop_add_fd(loop, transfer->source_fd,
+ WL_EVENT_READABLE, xwm_data_source_read, transfer);
+}
+
+static struct wl_array *xwm_selection_source_get_mime_types(
+ struct wlr_xwm_selection *selection) {
+ if (selection == &selection->xwm->clipboard_selection) {
+ struct wlr_data_source *source =
+ selection->xwm->seat->selection_source;
+ if (source != NULL) {
+ return &source->mime_types;
+ }
+ } else if (selection == &selection->xwm->primary_selection) {
+ struct wlr_primary_selection_source *source =
+ selection->xwm->seat->primary_selection_source;
+ if (source != NULL) {
+ return &source->mime_types;
+ }
+ } else if (selection == &selection->xwm->dnd_selection) {
+ struct wlr_data_source *source =
+ selection->xwm->seat->drag_source;
+ if (source != NULL) {
+ return &source->mime_types;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * Read the Wayland selection and send it to an Xwayland client.
+ */
+static void xwm_selection_send_data(struct wlr_xwm_selection *selection,
+ xcb_selection_request_event_t *req, const char *mime_type) {
+ // Check MIME type
+ struct wl_array *mime_types =
+ xwm_selection_source_get_mime_types(selection);
+ if (mime_types == NULL) {
+ wlr_log(WLR_ERROR, "not sending selection: no MIME type list available");
+ xwm_selection_send_notify(selection->xwm, req, false);
+ return;
+ }
+
+ bool found = false;
+ char **mime_type_ptr;
+ wl_array_for_each(mime_type_ptr, mime_types) {
+ char *t = *mime_type_ptr;
+ if (strcmp(t, mime_type) == 0) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ wlr_log(WLR_ERROR, "not sending selection: "
+ "requested an unsupported MIME type %s", mime_type);
+ xwm_selection_send_notify(selection->xwm, req, false);
+ return;
+ }
+
+ struct wlr_xwm_selection_transfer *transfer =
+ calloc(1, sizeof(struct wlr_xwm_selection_transfer));
+ if (transfer == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed");
+ return;
+ }
+ transfer->selection = selection;
+ transfer->request = *req;
+ wl_array_init(&transfer->source_data);
+
+ int p[2];
+ if (pipe(p) == -1) {
+ wlr_log(WLR_ERROR, "pipe() failed: %m");
+ xwm_selection_send_notify(selection->xwm, req, false);
+ return;
+ }
+ fcntl(p[0], F_SETFD, FD_CLOEXEC);
+ fcntl(p[0], F_SETFL, O_NONBLOCK);
+ fcntl(p[1], F_SETFD, FD_CLOEXEC);
+ fcntl(p[1], F_SETFL, O_NONBLOCK);
+
+ transfer->source_fd = p[0];
+
+ wlr_log(WLR_DEBUG, "Sending Wayland selection %u to Xwayland window with "
+ "MIME type %s, target %u", req->target, mime_type, req->target);
+ xwm_selection_source_send(selection, mime_type, p[1]);
+
+ wl_list_insert(&selection->outgoing, &transfer->outgoing_link);
+
+ // We can only handle one transfer at a time
+ if (wl_list_length(&selection->outgoing) == 1) {
+ xwm_selection_transfer_start_outgoing(transfer);
+ }
+}
+
+static void xwm_selection_send_targets(struct wlr_xwm_selection *selection,
+ xcb_selection_request_event_t *req) {
+ struct wlr_xwm *xwm = selection->xwm;
+
+ struct wl_array *mime_types =
+ xwm_selection_source_get_mime_types(selection);
+ if (mime_types == NULL) {
+ wlr_log(WLR_ERROR, "not sending selection targets: "
+ "no selection source available");
+ xwm_selection_send_notify(selection->xwm, req, false);
+ return;
+ }
+
+ size_t n = 2 + mime_types->size / sizeof(char *);
+ xcb_atom_t targets[n];
+ targets[0] = xwm->atoms[TIMESTAMP];
+ targets[1] = xwm->atoms[TARGETS];
+
+ size_t i = 0;
+ char **mime_type_ptr;
+ wl_array_for_each(mime_type_ptr, mime_types) {
+ char *mime_type = *mime_type_ptr;
+ targets[2+i] = xwm_mime_type_to_atom(xwm, mime_type);
+ ++i;
+ }
+
+ xcb_change_property(xwm->xcb_conn,
+ XCB_PROP_MODE_REPLACE,
+ req->requestor,
+ req->property,
+ XCB_ATOM_ATOM,
+ 32, // format
+ n, targets);
+
+ xwm_selection_send_notify(selection->xwm, req, true);
+}
+
+static void xwm_selection_send_timestamp(struct wlr_xwm_selection *selection,
+ xcb_selection_request_event_t *req) {
+ xcb_change_property(selection->xwm->xcb_conn,
+ XCB_PROP_MODE_REPLACE,
+ req->requestor,
+ req->property,
+ XCB_ATOM_INTEGER,
+ 32, // format
+ 1, &selection->timestamp);
+
+ xwm_selection_send_notify(selection->xwm, req, true);
+}
+
+void xwm_handle_selection_request(struct wlr_xwm *xwm,
+ xcb_selection_request_event_t *req) {
+ wlr_log(WLR_DEBUG, "XCB_SELECTION_REQUEST (time=%u owner=%u, requestor=%u "
+ "selection=%u, target=%u, property=%u)",
+ req->time, req->owner, req->requestor, req->selection, req->target,
+ req->property);
+
+ if (req->selection == xwm->atoms[CLIPBOARD_MANAGER]) {
+ // The wlroots clipboard should already have grabbed the first target,
+ // so just send selection notify now. This isn't synchronized with the
+ // clipboard finishing getting the data, so there's a race here.
+ xwm_selection_send_notify(xwm, req, true);
+ return;
+ }
+
+ struct wlr_xwm_selection *selection =
+ xwm_get_selection(xwm, req->selection);
+ if (selection == NULL) {
+ wlr_log(WLR_DEBUG, "received selection request for unknown selection");
+ return;
+ }
+
+ if (selection->window != req->owner) {
+ wlr_log(WLR_DEBUG, "received selection request with invalid owner");
+ return;
+ }
+
+ // No xwayland surface focused, deny access to clipboard
+ if (xwm->focus_surface == NULL && xwm->drag_focus == NULL) {
+ char *selection_name = xwm_get_atom_name(xwm, selection->atom);
+ wlr_log(WLR_DEBUG, "denying read access to selection %u (%s): "
+ "no xwayland surface focused", selection->atom, selection_name);
+ free(selection_name);
+ xwm_selection_send_notify(xwm, req, false);
+ return;
+ }
+
+ if (req->target == xwm->atoms[TARGETS]) {
+ xwm_selection_send_targets(selection, req);
+ } else if (req->target == xwm->atoms[TIMESTAMP]) {
+ xwm_selection_send_timestamp(selection, req);
+ } else if (req->target == xwm->atoms[DELETE]) {
+ xwm_selection_send_notify(selection->xwm, req, true);
+ } else {
+ // Send data
+ char *mime_type = xwm_mime_type_from_atom(xwm, req->target);
+ if (mime_type == NULL) {
+ wlr_log(WLR_ERROR, "ignoring selection request: unknown atom %u",
+ req->target);
+ xwm_selection_send_notify(xwm, req, false);
+ return;
+ }
+ xwm_selection_send_data(selection, req, mime_type);
+ free(mime_type);
+ }
+}
diff --git a/xwayland/selection/selection.c b/xwayland/selection/selection.c
new file mode 100644
index 00000000..4d7732cb
--- /dev/null
+++ b/xwayland/selection/selection.c
@@ -0,0 +1,317 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_primary_selection.h>
+#include <wlr/util/log.h>
+#include <xcb/xfixes.h>
+#include "xwayland/selection.h"
+#include "xwayland/xwm.h"
+
+void xwm_selection_transfer_remove_source(
+ struct wlr_xwm_selection_transfer *transfer) {
+ if (transfer->source != NULL) {
+ wl_event_source_remove(transfer->source);
+ transfer->source = NULL;
+ }
+}
+
+void xwm_selection_transfer_close_source_fd(
+ struct wlr_xwm_selection_transfer *transfer) {
+ if (transfer->source_fd >= 0) {
+ close(transfer->source_fd);
+ transfer->source_fd = -1;
+ }
+}
+
+void xwm_selection_transfer_destroy_property_reply(
+ struct wlr_xwm_selection_transfer *transfer) {
+ free(transfer->property_reply);
+ transfer->property_reply = NULL;
+}
+
+xcb_atom_t xwm_mime_type_to_atom(struct wlr_xwm *xwm, char *mime_type) {
+ if (strcmp(mime_type, "text/plain;charset=utf-8") == 0) {
+ return xwm->atoms[UTF8_STRING];
+ } else if (strcmp(mime_type, "text/plain") == 0) {
+ return xwm->atoms[TEXT];
+ }
+
+ xcb_intern_atom_cookie_t cookie =
+ xcb_intern_atom(xwm->xcb_conn, 0, strlen(mime_type), mime_type);
+ xcb_intern_atom_reply_t *reply =
+ xcb_intern_atom_reply(xwm->xcb_conn, cookie, NULL);
+ if (reply == NULL) {
+ return XCB_ATOM_NONE;
+ }
+ xcb_atom_t atom = reply->atom;
+ free(reply);
+ return atom;
+}
+
+char *xwm_mime_type_from_atom(struct wlr_xwm *xwm, xcb_atom_t atom) {
+ if (atom == xwm->atoms[UTF8_STRING]) {
+ return strdup("text/plain;charset=utf-8");
+ } else if (atom == xwm->atoms[TEXT]) {
+ return strdup("text/plain");
+ } else {
+ return xwm_get_atom_name(xwm, atom);
+ }
+}
+
+struct wlr_xwm_selection *xwm_get_selection(struct wlr_xwm *xwm,
+ xcb_atom_t selection_atom) {
+ if (selection_atom == xwm->atoms[CLIPBOARD]) {
+ return &xwm->clipboard_selection;
+ } else if (selection_atom == xwm->atoms[PRIMARY]) {
+ return &xwm->primary_selection;
+ } else if (selection_atom == xwm->atoms[DND_SELECTION]) {
+ return &xwm->dnd_selection;
+ } else {
+ return NULL;
+ }
+}
+
+static int xwm_handle_selection_property_notify(struct wlr_xwm *xwm,
+ xcb_property_notify_event_t *event) {
+ struct wlr_xwm_selection *selections[] = {
+ &xwm->clipboard_selection,
+ &xwm->primary_selection,
+ &xwm->dnd_selection,
+ };
+
+ for (size_t i = 0; i < sizeof(selections)/sizeof(selections[0]); ++i) {
+ struct wlr_xwm_selection *selection = selections[i];
+
+ if (event->window == selection->window) {
+ if (event->state == XCB_PROPERTY_NEW_VALUE &&
+ event->atom == xwm->atoms[WL_SELECTION] &&
+ selection->incoming.incr) {
+ xwm_get_incr_chunk(&selection->incoming);
+ }
+ return 1;
+ }
+
+ struct wlr_xwm_selection_transfer *outgoing;
+ wl_list_for_each(outgoing, &selection->outgoing, outgoing_link) {
+ if (event->window == outgoing->request.requestor) {
+ if (event->state == XCB_PROPERTY_DELETE &&
+ event->atom == outgoing->request.property &&
+ outgoing->incr) {
+ xwm_send_incr_chunk(outgoing);
+ }
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int xwm_handle_selection_event(struct wlr_xwm *xwm,
+ xcb_generic_event_t *event) {
+ if (xwm->seat == NULL) {
+ wlr_log(WLR_DEBUG, "not handling selection events: "
+ "no seat assigned to xwayland");
+ return 0;
+ }
+
+ switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) {
+ case XCB_SELECTION_NOTIFY:
+ xwm_handle_selection_notify(xwm, (xcb_selection_notify_event_t *)event);
+ return 1;
+ case XCB_PROPERTY_NOTIFY:
+ return xwm_handle_selection_property_notify(xwm,
+ (xcb_property_notify_event_t *)event);
+ case XCB_SELECTION_REQUEST:
+ xwm_handle_selection_request(xwm,
+ (xcb_selection_request_event_t *)event);
+ return 1;
+ }
+
+ switch (event->response_type - xwm->xfixes->first_event) {
+ case XCB_XFIXES_SELECTION_NOTIFY:
+ // an X11 window has copied something to the clipboard
+ return xwm_handle_xfixes_selection_notify(xwm,
+ (xcb_xfixes_selection_notify_event_t *)event);
+ }
+
+ return 0;
+}
+
+static void selection_init(struct wlr_xwm *xwm,
+ struct wlr_xwm_selection *selection, xcb_atom_t atom) {
+ selection->xwm = xwm;
+ selection->atom = atom;
+ selection->window = xwm->selection_window;
+ selection->incoming.selection = selection;
+ wl_list_init(&selection->outgoing);
+
+ uint32_t mask =
+ XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER |
+ XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY |
+ XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE;
+ xcb_xfixes_select_selection_input(xwm->xcb_conn, selection->window,
+ selection->atom, mask);
+}
+
+void xwm_selection_init(struct wlr_xwm *xwm) {
+ // Clipboard and primary selection
+ uint32_t selection_values[] = {
+ XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE
+ };
+ xwm->selection_window = xcb_generate_id(xwm->xcb_conn);
+ xcb_create_window(xwm->xcb_conn,
+ XCB_COPY_FROM_PARENT,
+ xwm->selection_window,
+ xwm->screen->root,
+ 0, 0,
+ 10, 10,
+ 0,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ xwm->screen->root_visual,
+ XCB_CW_EVENT_MASK, selection_values);
+
+ xcb_set_selection_owner(xwm->xcb_conn,
+ xwm->selection_window,
+ xwm->atoms[CLIPBOARD_MANAGER],
+ XCB_TIME_CURRENT_TIME);
+
+ selection_init(xwm, &xwm->clipboard_selection, xwm->atoms[CLIPBOARD]);
+ selection_init(xwm, &xwm->primary_selection, xwm->atoms[PRIMARY]);
+
+ // Drag'n'drop
+ uint32_t dnd_values[] = {
+ XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE
+ };
+ xwm->dnd_window = xcb_generate_id(xwm->xcb_conn);
+ xcb_create_window(xwm->xcb_conn,
+ XCB_COPY_FROM_PARENT,
+ xwm->dnd_window,
+ xwm->screen->root,
+ 0, 0,
+ 8192, 8192,
+ 0,
+ XCB_WINDOW_CLASS_INPUT_ONLY,
+ xwm->screen->root_visual,
+ XCB_CW_EVENT_MASK, dnd_values);
+
+ uint32_t version = XDND_VERSION;
+ xcb_change_property(xwm->xcb_conn,
+ XCB_PROP_MODE_REPLACE,
+ xwm->dnd_window,
+ xwm->atoms[DND_AWARE],
+ XCB_ATOM_ATOM,
+ 32, // format
+ 1, &version);
+
+ selection_init(xwm, &xwm->dnd_selection, xwm->atoms[DND_SELECTION]);
+}
+
+void xwm_selection_finish(struct wlr_xwm *xwm) {
+ if (!xwm) {
+ return;
+ }
+ if (xwm->selection_window) {
+ xcb_destroy_window(xwm->xcb_conn, xwm->selection_window);
+ }
+ if (xwm->dnd_window) {
+ xcb_destroy_window(xwm->xcb_conn, xwm->dnd_window);
+ }
+ if (xwm->seat) {
+ if (xwm->seat->selection_source &&
+ data_source_is_xwayland(
+ xwm->seat->selection_source)) {
+ wlr_seat_set_selection(xwm->seat, NULL,
+ wl_display_next_serial(xwm->xwayland->wl_display));
+ }
+ if (xwm->seat->primary_selection_source &&
+ primary_selection_source_is_xwayland(
+ xwm->seat->primary_selection_source)) {
+ wlr_seat_set_primary_selection(xwm->seat, NULL);
+ }
+ wlr_xwayland_set_seat(xwm->xwayland, NULL);
+ }
+}
+
+static void xwm_selection_set_owner(struct wlr_xwm_selection *selection,
+ bool set) {
+ if (set) {
+ xcb_set_selection_owner(selection->xwm->xcb_conn,
+ selection->window,
+ selection->atom,
+ XCB_TIME_CURRENT_TIME);
+ } else {
+ if (selection->owner == selection->window) {
+ xcb_set_selection_owner(selection->xwm->xcb_conn,
+ XCB_WINDOW_NONE,
+ selection->atom,
+ selection->timestamp);
+ }
+ }
+}
+
+static void seat_handle_selection(struct wl_listener *listener,
+ void *data) {
+ struct wlr_seat *seat = data;
+ struct wlr_xwm *xwm =
+ wl_container_of(listener, xwm, seat_selection);
+ struct wlr_data_source *source = seat->selection_source;
+
+ if (source != NULL && data_source_is_xwayland(source)) {
+ return;
+ }
+
+ xwm_selection_set_owner(&xwm->clipboard_selection, source != NULL);
+}
+
+static void seat_handle_primary_selection(struct wl_listener *listener,
+ void *data) {
+ struct wlr_seat *seat = data;
+ struct wlr_xwm *xwm =
+ wl_container_of(listener, xwm, seat_primary_selection);
+ struct wlr_primary_selection_source *source =
+ seat->primary_selection_source;
+
+ if (source != NULL && primary_selection_source_is_xwayland(source)) {
+ return;
+ }
+
+ xwm_selection_set_owner(&xwm->primary_selection, source != NULL);
+}
+
+static void seat_handle_start_drag(struct wl_listener *listener, void *data) {
+ struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_start_drag);
+ struct wlr_drag *drag = data;
+
+ xwm_selection_set_owner(&xwm->dnd_selection, drag != NULL);
+ xwm_seat_handle_start_drag(xwm, drag);
+}
+
+void xwm_set_seat(struct wlr_xwm *xwm, struct wlr_seat *seat) {
+ if (xwm->seat != NULL) {
+ wl_list_remove(&xwm->seat_selection.link);
+ wl_list_remove(&xwm->seat_primary_selection.link);
+ wl_list_remove(&xwm->seat_start_drag.link);
+ xwm->seat = NULL;
+ }
+
+ if (seat == NULL) {
+ return;
+ }
+
+ xwm->seat = seat;
+
+ wl_signal_add(&seat->events.selection, &xwm->seat_selection);
+ xwm->seat_selection.notify = seat_handle_selection;
+ wl_signal_add(&seat->events.primary_selection, &xwm->seat_primary_selection);
+ xwm->seat_primary_selection.notify = seat_handle_primary_selection;
+ wl_signal_add(&seat->events.start_drag, &xwm->seat_start_drag);
+ xwm->seat_start_drag.notify = seat_handle_start_drag;
+
+ seat_handle_selection(&xwm->seat_selection, seat);
+ seat_handle_primary_selection(&xwm->seat_primary_selection, seat);
+}
diff --git a/xwayland/sockets.c b/xwayland/sockets.c
new file mode 100644
index 00000000..112a8bb0
--- /dev/null
+++ b/xwayland/sockets.c
@@ -0,0 +1,168 @@
+#define _POSIX_C_SOURCE 200809L
+#ifdef __FreeBSD__
+// for SOCK_CLOEXEC
+#define __BSD_VISIBLE 1
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <wlr/util/log.h>
+#include "sockets.h"
+
+static const char *lock_fmt = "/tmp/.X%d-lock";
+static const char *socket_dir = "/tmp/.X11-unix";
+static const char *socket_fmt = "/tmp/.X11-unix/X%d";
+#ifndef __linux__
+static const char *socket_fmt2 = "/tmp/.X11-unix/X%d_";
+#endif
+
+static int open_socket(struct sockaddr_un *addr, size_t path_size) {
+ int fd, rc;
+ socklen_t size = offsetof(struct sockaddr_un, sun_path) + path_size + 1;
+
+ fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ if (fd < 0) {
+ wlr_log_errno(WLR_DEBUG, "Failed to create socket %c%s",
+ addr->sun_path[0] ? addr->sun_path[0] : '@',
+ addr->sun_path + 1);
+ return -1;
+ }
+
+ if (addr->sun_path[0]) {
+ unlink(addr->sun_path);
+ }
+ if (bind(fd, (struct sockaddr*)addr, size) < 0) {
+ rc = errno;
+ wlr_log_errno(WLR_DEBUG, "Failed to bind socket %c%s",
+ addr->sun_path[0] ? addr->sun_path[0] : '@',
+ addr->sun_path + 1);
+ goto cleanup;
+ }
+ if (listen(fd, 1) < 0) {
+ rc = errno;
+ wlr_log_errno(WLR_DEBUG, "Failed to listen to socket %c%s",
+ addr->sun_path[0] ? addr->sun_path[0] : '@',
+ addr->sun_path + 1);
+ goto cleanup;
+ }
+
+ return fd;
+
+cleanup:
+ close(fd);
+ if (addr->sun_path[0]) {
+ unlink(addr->sun_path);
+ }
+ errno = rc;
+ return -1;
+}
+
+static bool open_sockets(int socks[2], int display) {
+ struct sockaddr_un addr = { .sun_family = AF_LOCAL };
+ size_t path_size;
+
+ mkdir(socket_dir, 0777);
+
+#ifdef __linux__
+ addr.sun_path[0] = 0;
+ path_size = snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, socket_fmt, display);
+#else
+ path_size = snprintf(addr.sun_path, sizeof(addr.sun_path), socket_fmt2, display);
+#endif
+ socks[0] = open_socket(&addr, path_size);
+ if (socks[0] < 0) {
+ return false;
+ }
+
+ path_size = snprintf(addr.sun_path, sizeof(addr.sun_path), socket_fmt, display);
+ socks[1] = open_socket(&addr, path_size);
+ if (socks[1] < 0) {
+ close(socks[0]);
+ socks[0] = -1;
+ return false;
+ }
+
+ return true;
+}
+
+void unlink_display_sockets(int display) {
+ char sun_path[64];
+
+ snprintf(sun_path, sizeof(sun_path), socket_fmt, display);
+ unlink(sun_path);
+
+#ifndef __linux__
+ snprintf(sun_path, sizeof(sun_path), socket_fmt2, display);
+ unlink(sun_path);
+#endif
+
+ snprintf(sun_path, sizeof(sun_path), lock_fmt, display);
+ unlink(sun_path);
+}
+
+int open_display_sockets(int socks[2]) {
+ int lock_fd, display;
+ char lock_name[64];
+
+ for (display = 0; display <= 32; display++) {
+ snprintf(lock_name, sizeof(lock_name), lock_fmt, display);
+ if ((lock_fd = open(lock_name, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0444)) >= 0) {
+ if (!open_sockets(socks, display)) {
+ unlink(lock_name);
+ close(lock_fd);
+ continue;
+ }
+ char pid[12];
+ snprintf(pid, sizeof(pid), "%10d", getpid());
+ if (write(lock_fd, pid, sizeof(pid) - 1) != sizeof(pid) - 1) {
+ unlink(lock_name);
+ close(lock_fd);
+ continue;
+ }
+ close(lock_fd);
+ break;
+ }
+
+ if ((lock_fd = open(lock_name, O_RDONLY | O_CLOEXEC)) < 0) {
+ continue;
+ }
+
+ char pid[12] = { 0 }, *end_pid;
+ ssize_t bytes = read(lock_fd, pid, sizeof(pid) - 1);
+ close(lock_fd);
+
+ if (bytes != sizeof(pid) - 1) {
+ continue;
+ }
+ long int read_pid;
+ read_pid = strtol(pid, &end_pid, 10);
+ if (read_pid < 0 || read_pid > INT32_MAX || end_pid != pid + sizeof(pid) - 2) {
+ continue;
+ }
+ errno = 0;
+ if (kill((pid_t)read_pid, 0) != 0 && errno == ESRCH) {
+ if (unlink(lock_name) != 0) {
+ continue;
+ }
+ // retry
+ display--;
+ continue;
+ }
+ }
+
+ if (display > 32) {
+ wlr_log(WLR_ERROR, "No display available in the first 33");
+ return -1;
+ }
+
+ return display;
+}
diff --git a/xwayland/sockets.h b/xwayland/sockets.h
new file mode 100644
index 00000000..73eb36e0
--- /dev/null
+++ b/xwayland/sockets.h
@@ -0,0 +1,7 @@
+#ifndef XWAYLAND_SOCKETS_H
+#define XWAYLAND_SOCKETS_H
+
+void unlink_display_sockets(int display);
+int open_display_sockets(int socks[2]);
+
+#endif
diff --git a/xwayland/xwayland.c b/xwayland/xwayland.c
new file mode 100644
index 00000000..e6f15735
--- /dev/null
+++ b/xwayland/xwayland.c
@@ -0,0 +1,488 @@
+#define _POSIX_C_SOURCE 200112L
+#ifdef __FreeBSD__
+// for SOCK_CLOEXEC
+#define __BSD_VISIBLE 1
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/util/log.h>
+#include <wlr/xwayland.h>
+#include "sockets.h"
+#include "util/signal.h"
+#include "xwayland/xwm.h"
+
+struct wlr_xwayland_cursor {
+ uint8_t *pixels;
+ uint32_t stride;
+ uint32_t width;
+ uint32_t height;
+ int32_t hotspot_x;
+ int32_t hotspot_y;
+};
+
+static void safe_close(int fd) {
+ if (fd >= 0) {
+ close(fd);
+ }
+}
+
+static int unset_cloexec(int fd) {
+ if (fcntl(fd, F_SETFD, 0) != 0) {
+ wlr_log_errno(WLR_ERROR, "fcntl() failed on fd %d", fd);
+ return -1;
+ }
+ return 0;
+}
+
+static int fill_arg(char ***argv, const char *fmt, ...) {
+ int len;
+ char **cur_arg = *argv;
+ va_list args;
+ va_start(args, fmt);
+ len = vsnprintf(NULL, 0, fmt, args) + 1;
+ va_end(args);
+ while (*cur_arg) {
+ cur_arg++;
+ }
+ *cur_arg = malloc(len);
+ if (!*cur_arg) {
+ return -1;
+ }
+ *argv = cur_arg;
+ va_start(args, fmt);
+ len = vsnprintf(*cur_arg, len, fmt, args);
+ va_end(args);
+ return len;
+}
+
+_Noreturn
+static void exec_xwayland(struct wlr_xwayland *wlr_xwayland) {
+ if (unset_cloexec(wlr_xwayland->x_fd[0]) ||
+ unset_cloexec(wlr_xwayland->x_fd[1]) ||
+ unset_cloexec(wlr_xwayland->wm_fd[1]) ||
+ unset_cloexec(wlr_xwayland->wl_fd[1])) {
+ _exit(EXIT_FAILURE);
+ }
+
+ /* Make Xwayland signal us when it's ready */
+ signal(SIGUSR1, SIG_IGN);
+
+ char *argv[] = {
+ "Xwayland", NULL /* display, e.g. :1 */,
+ "-rootless", "-terminate",
+ "-listen", NULL /* x_fd[0] */,
+ "-listen", NULL /* x_fd[1] */,
+ "-wm", NULL /* wm_fd[1] */,
+ NULL };
+ char **cur_arg = argv;
+
+ if (fill_arg(&cur_arg, ":%d", wlr_xwayland->display) < 0 ||
+ fill_arg(&cur_arg, "%d", wlr_xwayland->x_fd[0]) < 0 ||
+ fill_arg(&cur_arg, "%d", wlr_xwayland->x_fd[1]) < 0 ||
+ fill_arg(&cur_arg, "%d", wlr_xwayland->wm_fd[1]) < 0) {
+ wlr_log_errno(WLR_ERROR, "alloc/print failure");
+ _exit(EXIT_FAILURE);
+ }
+
+ char wayland_socket_str[16];
+ snprintf(wayland_socket_str, sizeof(wayland_socket_str), "%d", wlr_xwayland->wl_fd[1]);
+ setenv("WAYLAND_SOCKET", wayland_socket_str, true);
+
+ wlr_log(WLR_INFO, "WAYLAND_SOCKET=%d Xwayland :%d -rootless -terminate -listen %d -listen %d -wm %d",
+ wlr_xwayland->wl_fd[1], wlr_xwayland->display, wlr_xwayland->x_fd[0],
+ wlr_xwayland->x_fd[1], wlr_xwayland->wm_fd[1]);
+
+ // Closes stdout/stderr depending on log verbosity
+ enum wlr_log_importance verbosity = wlr_log_get_verbosity();
+ int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666);
+ if (devnull < 0) {
+ wlr_log_errno(WLR_ERROR, "XWayland: failed to open /dev/null");
+ _exit(EXIT_FAILURE);
+ }
+ if (verbosity < WLR_INFO) {
+ dup2(devnull, STDOUT_FILENO);
+ }
+ if (verbosity < WLR_ERROR) {
+ dup2(devnull, STDERR_FILENO);
+ }
+
+ // This returns if and only if the call fails
+ execvp("Xwayland", argv);
+
+ wlr_log_errno(WLR_ERROR, "failed to exec Xwayland");
+ close(devnull);
+ _exit(EXIT_FAILURE);
+}
+
+static void xwayland_finish_server(struct wlr_xwayland *wlr_xwayland) {
+ if (!wlr_xwayland || wlr_xwayland->display == -1) {
+ return;
+ }
+
+ if (wlr_xwayland->x_fd_read_event[0]) {
+ wl_event_source_remove(wlr_xwayland->x_fd_read_event[0]);
+ wl_event_source_remove(wlr_xwayland->x_fd_read_event[1]);
+
+ wlr_xwayland->x_fd_read_event[0] = wlr_xwayland->x_fd_read_event[1] = NULL;
+ }
+
+ if (wlr_xwayland->cursor != NULL) {
+ free(wlr_xwayland->cursor);
+ }
+
+ xwm_destroy(wlr_xwayland->xwm);
+
+ if (wlr_xwayland->client) {
+ wl_list_remove(&wlr_xwayland->client_destroy.link);
+ wl_client_destroy(wlr_xwayland->client);
+ }
+ if (wlr_xwayland->sigusr1_source) {
+ wl_event_source_remove(wlr_xwayland->sigusr1_source);
+ }
+
+ safe_close(wlr_xwayland->wl_fd[0]);
+ safe_close(wlr_xwayland->wl_fd[1]);
+ safe_close(wlr_xwayland->wm_fd[0]);
+ safe_close(wlr_xwayland->wm_fd[1]);
+ memset(wlr_xwayland, 0, offsetof(struct wlr_xwayland, display));
+ wlr_xwayland->wl_fd[0] = wlr_xwayland->wl_fd[1] = -1;
+ wlr_xwayland->wm_fd[0] = wlr_xwayland->wm_fd[1] = -1;
+
+ /* We do not kill the Xwayland process, it dies to broken pipe
+ * after we close our side of the wm/wl fds. This is more reliable
+ * than trying to kill something that might no longer be Xwayland.
+ */
+}
+
+static void xwayland_finish_display(struct wlr_xwayland *wlr_xwayland) {
+ if (!wlr_xwayland || wlr_xwayland->display == -1) {
+ return;
+ }
+
+ safe_close(wlr_xwayland->x_fd[0]);
+ safe_close(wlr_xwayland->x_fd[1]);
+ wlr_xwayland->x_fd[0] = wlr_xwayland->x_fd[1] = -1;
+
+ wl_list_remove(&wlr_xwayland->display_destroy.link);
+
+ unlink_display_sockets(wlr_xwayland->display);
+ wlr_xwayland->display = -1;
+ unsetenv("DISPLAY");
+}
+
+static bool xwayland_start_display(struct wlr_xwayland *wlr_xwayland,
+ struct wl_display *wl_display);
+
+static bool xwayland_start_server(struct wlr_xwayland *wlr_xwayland);
+static bool xwayland_start_server_lazy(struct wlr_xwayland *wlr_xwayland);
+
+static void handle_client_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_xwayland *wlr_xwayland =
+ wl_container_of(listener, wlr_xwayland, client_destroy);
+
+ if (wlr_xwayland->sigusr1_source) {
+ // Xwayland failed to start, let the sigusr1 handler deal with it
+ return;
+ }
+
+ // Don't call client destroy: it's being destroyed already
+ wlr_xwayland->client = NULL;
+ wl_list_remove(&wlr_xwayland->client_destroy.link);
+
+ xwayland_finish_server(wlr_xwayland);
+
+ if (time(NULL) - wlr_xwayland->server_start > 5) {
+ if (wlr_xwayland->lazy) {
+ wlr_log(WLR_INFO, "Restarting Xwayland (lazy)");
+ xwayland_start_server_lazy(wlr_xwayland);
+ } else {
+ wlr_log(WLR_INFO, "Restarting Xwayland");
+ xwayland_start_server(wlr_xwayland);
+ }
+ }
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_xwayland *wlr_xwayland =
+ wl_container_of(listener, wlr_xwayland, display_destroy);
+
+ // Don't call client destroy: the display is being destroyed, it's too late
+ if (wlr_xwayland->client) {
+ wlr_xwayland->client = NULL;
+ wl_list_remove(&wlr_xwayland->client_destroy.link);
+ }
+
+ wlr_xwayland_destroy(wlr_xwayland);
+}
+
+static int xserver_handle_ready(int signal_number, void *data) {
+ struct wlr_xwayland *wlr_xwayland = data;
+
+ int stat_val = -1;
+ while (waitpid(wlr_xwayland->pid, &stat_val, 0) < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ wlr_log_errno(WLR_ERROR, "waitpid for Xwayland fork failed");
+ return 1;
+ }
+ if (stat_val) {
+ wlr_log(WLR_ERROR, "Xwayland startup failed, not setting up xwm");
+ return 1;
+ }
+ wlr_log(WLR_DEBUG, "Xserver is ready");
+
+ wlr_xwayland->xwm = xwm_create(wlr_xwayland);
+ if (!wlr_xwayland->xwm) {
+ xwayland_finish_server(wlr_xwayland);
+ return 1;
+ }
+
+ if (wlr_xwayland->seat) {
+ xwm_set_seat(wlr_xwayland->xwm, wlr_xwayland->seat);
+ }
+
+ wl_event_source_remove(wlr_xwayland->sigusr1_source);
+ wlr_xwayland->sigusr1_source = NULL;
+
+ if (wlr_xwayland->cursor != NULL) {
+ struct wlr_xwayland_cursor *cur = wlr_xwayland->cursor;
+ xwm_set_cursor(wlr_xwayland->xwm, cur->pixels, cur->stride, cur->width,
+ cur->height, cur->hotspot_x, cur->hotspot_y);
+ free(cur);
+ wlr_xwayland->cursor = NULL;
+ }
+
+
+ wlr_signal_emit_safe(&wlr_xwayland->events.ready, wlr_xwayland);
+ /* ready is a one-shot signal, fire and forget */
+ wl_signal_init(&wlr_xwayland->events.ready);
+
+ return 1; /* wayland event loop dispatcher's count */
+}
+
+static int xwayland_socket_connected(int fd, uint32_t mask, void* data){
+ struct wlr_xwayland *wlr_xwayland = data;
+
+ wl_event_source_remove(wlr_xwayland->x_fd_read_event[0]);
+ wl_event_source_remove(wlr_xwayland->x_fd_read_event[1]);
+
+ wlr_xwayland->x_fd_read_event[0] = wlr_xwayland->x_fd_read_event[1] = NULL;
+
+ xwayland_start_server(wlr_xwayland);
+
+ return 0;
+}
+
+static bool xwayland_start_display(struct wlr_xwayland *wlr_xwayland,
+ struct wl_display *wl_display) {
+
+ wlr_xwayland->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(wl_display, &wlr_xwayland->display_destroy);
+
+ wlr_xwayland->display = open_display_sockets(wlr_xwayland->x_fd);
+ if (wlr_xwayland->display < 0) {
+ xwayland_finish_display(wlr_xwayland);
+ return false;
+ }
+
+ char display_name[16];
+ snprintf(display_name, sizeof(display_name), ":%d", wlr_xwayland->display);
+ setenv("DISPLAY", display_name, true);
+
+ return true;
+}
+
+static bool xwayland_start_server(struct wlr_xwayland *wlr_xwayland) {
+
+ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wlr_xwayland->wl_fd) != 0 ||
+ socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wlr_xwayland->wm_fd) != 0) {
+ wlr_log_errno(WLR_ERROR, "failed to create socketpair");
+ xwayland_finish_server(wlr_xwayland);
+ return false;
+ }
+ wlr_xwayland->server_start = time(NULL);
+
+ if (!(wlr_xwayland->client = wl_client_create(wlr_xwayland->wl_display, wlr_xwayland->wl_fd[0]))) {
+ wlr_log_errno(WLR_ERROR, "wl_client_create failed");
+ xwayland_finish_server(wlr_xwayland);
+ return false;
+ }
+
+ wlr_xwayland->wl_fd[0] = -1; /* not ours anymore */
+
+ wlr_xwayland->client_destroy.notify = handle_client_destroy;
+ wl_client_add_destroy_listener(wlr_xwayland->client,
+ &wlr_xwayland->client_destroy);
+
+ struct wl_event_loop *loop = wl_display_get_event_loop(wlr_xwayland->wl_display);
+ wlr_xwayland->sigusr1_source = wl_event_loop_add_signal(loop, SIGUSR1,
+ xserver_handle_ready, wlr_xwayland);
+
+ if ((wlr_xwayland->pid = fork()) == 0) {
+ /* Double-fork, but we need to forward SIGUSR1 once Xserver(1)
+ * is ready, or error if there was one. */
+ pid_t pid, ppid;
+ sigset_t sigset;
+ int sig;
+ ppid = getppid();
+ sigemptyset(&sigset);
+ sigaddset(&sigset, SIGUSR1);
+ sigaddset(&sigset, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &sigset, NULL);
+ if ((pid = fork()) == 0) {
+ exec_xwayland(wlr_xwayland);
+ }
+ if (pid < 0) {
+ wlr_log_errno(WLR_ERROR, "second fork failed");
+ _exit(EXIT_FAILURE);
+ }
+ sigwait(&sigset, &sig);
+ kill(ppid, SIGUSR1);
+ wlr_log(WLR_DEBUG, "sent SIGUSR1 to process %d", ppid);
+ if (sig == SIGCHLD) {
+ waitpid(pid, NULL, 0);
+ _exit(EXIT_FAILURE);
+ }
+ _exit(EXIT_SUCCESS);
+ }
+ if (wlr_xwayland->pid < 0) {
+ wlr_log_errno(WLR_ERROR, "fork failed");
+ xwayland_finish_server(wlr_xwayland);
+ return false;
+ }
+
+ /* close child fds */
+ /* remain managing x sockets for lazy start */
+ close(wlr_xwayland->wl_fd[1]);
+ close(wlr_xwayland->wm_fd[1]);
+ wlr_xwayland->wl_fd[1] = wlr_xwayland->wm_fd[1] = -1;
+
+ return true;
+}
+
+static bool xwayland_start_server_lazy(struct wlr_xwayland *wlr_xwayland) {
+ struct wl_event_loop *loop = wl_display_get_event_loop(wlr_xwayland->wl_display);
+ wlr_xwayland->x_fd_read_event[0] =
+ wl_event_loop_add_fd(loop, wlr_xwayland->x_fd[0], WL_EVENT_READABLE,
+ xwayland_socket_connected, wlr_xwayland);
+ wlr_xwayland->x_fd_read_event[1] =
+ wl_event_loop_add_fd(loop, wlr_xwayland->x_fd[1], WL_EVENT_READABLE,
+ xwayland_socket_connected, wlr_xwayland);
+
+ return true;
+}
+
+void wlr_xwayland_destroy(struct wlr_xwayland *wlr_xwayland) {
+ if (!wlr_xwayland) {
+ return;
+ }
+
+ wlr_xwayland_set_seat(wlr_xwayland, NULL);
+ xwayland_finish_server(wlr_xwayland);
+ xwayland_finish_display(wlr_xwayland);
+ free(wlr_xwayland);
+}
+
+struct wlr_xwayland *wlr_xwayland_create(struct wl_display *wl_display,
+ struct wlr_compositor *compositor, bool lazy) {
+ struct wlr_xwayland *wlr_xwayland = calloc(1, sizeof(struct wlr_xwayland));
+ if (!wlr_xwayland) {
+ return NULL;
+ }
+
+ wlr_xwayland->wl_display = wl_display;
+ wlr_xwayland->compositor = compositor;
+ wlr_xwayland->lazy = lazy;
+
+ wlr_xwayland->x_fd[0] = wlr_xwayland->x_fd[1] = -1;
+ wlr_xwayland->wl_fd[0] = wlr_xwayland->wl_fd[1] = -1;
+ wlr_xwayland->wm_fd[0] = wlr_xwayland->wm_fd[1] = -1;
+
+ wl_signal_init(&wlr_xwayland->events.new_surface);
+ wl_signal_init(&wlr_xwayland->events.ready);
+
+ if (!xwayland_start_display(wlr_xwayland, wl_display)) {
+ goto error_alloc;
+ }
+
+ if (wlr_xwayland->lazy) {
+ if (!xwayland_start_server_lazy(wlr_xwayland)) {
+ goto error_display;
+ }
+ } else {
+ if (!xwayland_start_server(wlr_xwayland)) {
+ goto error_display;
+ }
+ }
+
+ return wlr_xwayland;
+
+error_display:
+ xwayland_finish_display(wlr_xwayland);
+
+error_alloc:
+ free(wlr_xwayland);
+ return NULL;
+}
+
+void wlr_xwayland_set_cursor(struct wlr_xwayland *wlr_xwayland,
+ uint8_t *pixels, uint32_t stride, uint32_t width, uint32_t height,
+ int32_t hotspot_x, int32_t hotspot_y) {
+ if (wlr_xwayland->xwm != NULL) {
+ xwm_set_cursor(wlr_xwayland->xwm, pixels, stride, width, height,
+ hotspot_x, hotspot_y);
+ return;
+ }
+
+ free(wlr_xwayland->cursor);
+
+ wlr_xwayland->cursor = calloc(1, sizeof(struct wlr_xwayland_cursor));
+ if (wlr_xwayland->cursor == NULL) {
+ return;
+ }
+ wlr_xwayland->cursor->pixels = pixels;
+ wlr_xwayland->cursor->stride = stride;
+ wlr_xwayland->cursor->width = width;
+ wlr_xwayland->cursor->height = height;
+ wlr_xwayland->cursor->hotspot_x = hotspot_x;
+ wlr_xwayland->cursor->hotspot_y = hotspot_y;
+}
+
+static void xwayland_handle_seat_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_xwayland *xwayland =
+ wl_container_of(listener, xwayland, seat_destroy);
+
+ wlr_xwayland_set_seat(xwayland, NULL);
+}
+
+void wlr_xwayland_set_seat(struct wlr_xwayland *xwayland,
+ struct wlr_seat *seat) {
+ if (xwayland->seat) {
+ wl_list_remove(&xwayland->seat_destroy.link);
+ }
+
+ xwayland->seat = seat;
+
+ if (xwayland->xwm) {
+ xwm_set_seat(xwayland->xwm, seat);
+ }
+
+ if (seat == NULL) {
+ return;
+ }
+
+ xwayland->seat_destroy.notify = xwayland_handle_seat_destroy;
+ wl_signal_add(&seat->events.destroy, &xwayland->seat_destroy);
+}
diff --git a/xwayland/xwm.c b/xwayland/xwm.c
new file mode 100644
index 00000000..91fa7e3d
--- /dev/null
+++ b/xwayland/xwm.c
@@ -0,0 +1,1802 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <wlr/config.h>
+#include <wlr/types/wlr_surface.h>
+#include <wlr/util/edges.h>
+#include <wlr/util/log.h>
+#include <wlr/xcursor.h>
+#include <wlr/xwayland.h>
+#include <xcb/composite.h>
+#include <xcb/render.h>
+#include <xcb/xfixes.h>
+#include "util/signal.h"
+#include "xwayland/xwm.h"
+
+const char *atom_map[ATOM_LAST] = {
+ "WL_SURFACE_ID",
+ "WM_DELETE_WINDOW",
+ "WM_PROTOCOLS",
+ "WM_HINTS",
+ "WM_NORMAL_HINTS",
+ "WM_SIZE_HINTS",
+ "WM_WINDOW_ROLE",
+ "_MOTIF_WM_HINTS",
+ "UTF8_STRING",
+ "WM_S0",
+ "_NET_SUPPORTED",
+ "_NET_WM_CM_S0",
+ "_NET_WM_PID",
+ "_NET_WM_NAME",
+ "_NET_WM_STATE",
+ "_NET_WM_WINDOW_TYPE",
+ "WM_TAKE_FOCUS",
+ "WINDOW",
+ "_NET_ACTIVE_WINDOW",
+ "_NET_WM_MOVERESIZE",
+ "_NET_WM_NAME",
+ "_NET_SUPPORTING_WM_CHECK",
+ "_NET_WM_STATE_MODAL",
+ "_NET_WM_STATE_FULLSCREEN",
+ "_NET_WM_STATE_MAXIMIZED_VERT",
+ "_NET_WM_STATE_MAXIMIZED_HORZ",
+ "_NET_WM_PING",
+ "WM_STATE",
+ "CLIPBOARD",
+ "PRIMARY",
+ "_WL_SELECTION",
+ "TARGETS",
+ "CLIPBOARD_MANAGER",
+ "INCR",
+ "TEXT",
+ "TIMESTAMP",
+ "DELETE",
+ "_NET_WM_WINDOW_TYPE_NORMAL",
+ "_NET_WM_WINDOW_TYPE_UTILITY",
+ "_NET_WM_WINDOW_TYPE_TOOLTIP",
+ "_NET_WM_WINDOW_TYPE_DND",
+ "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU",
+ "_NET_WM_WINDOW_TYPE_POPUP_MENU",
+ "_NET_WM_WINDOW_TYPE_COMBO",
+ "_NET_WM_WINDOW_TYPE_MENU",
+ "_NET_WM_WINDOW_TYPE_NOTIFICATION",
+ "_NET_WM_WINDOW_TYPE_SPLASH",
+ "XdndSelection",
+ "XdndAware",
+ "XdndStatus",
+ "XdndPosition",
+ "XdndEnter",
+ "XdndLeave",
+ "XdndDrop",
+ "XdndFinished",
+ "XdndProxy",
+ "XdndTypeList",
+ "XdndActionMove",
+ "XdndActionCopy",
+ "XdndActionAsk",
+ "XdndActionPrivate",
+};
+
+static const struct wlr_surface_role xwayland_surface_role;
+
+bool wlr_surface_is_xwayland_surface(struct wlr_surface *surface) {
+ return surface->role == &xwayland_surface_role;
+}
+
+struct wlr_xwayland_surface *wlr_xwayland_surface_from_wlr_surface(
+ struct wlr_surface *surface) {
+ assert(wlr_surface_is_xwayland_surface(surface));
+ return (struct wlr_xwayland_surface *)surface->role_data;
+}
+
+// TODO: replace this with hash table?
+static struct wlr_xwayland_surface *lookup_surface(struct wlr_xwm *xwm,
+ xcb_window_t window_id) {
+ struct wlr_xwayland_surface *surface;
+ wl_list_for_each(surface, &xwm->surfaces, link) {
+ if (surface->window_id == window_id) {
+ return surface;
+ }
+ }
+ return NULL;
+}
+
+static int xwayland_surface_handle_ping_timeout(void *data) {
+ struct wlr_xwayland_surface *surface = data;
+
+ wlr_signal_emit_safe(&surface->events.ping_timeout, surface);
+ surface->pinging = false;
+ return 1;
+}
+
+static struct wlr_xwayland_surface *xwayland_surface_create(
+ struct wlr_xwm *xwm, xcb_window_t window_id, int16_t x, int16_t y,
+ uint16_t width, uint16_t height, bool override_redirect) {
+ struct wlr_xwayland_surface *surface =
+ calloc(1, sizeof(struct wlr_xwayland_surface));
+ if (!surface) {
+ wlr_log(WLR_ERROR, "Could not allocate wlr xwayland surface");
+ return NULL;
+ }
+
+ xcb_get_geometry_cookie_t geometry_cookie =
+ xcb_get_geometry(xwm->xcb_conn, window_id);
+
+ uint32_t values[1];
+ values[0] =
+ XCB_EVENT_MASK_FOCUS_CHANGE |
+ XCB_EVENT_MASK_PROPERTY_CHANGE;
+ xcb_change_window_attributes(xwm->xcb_conn, window_id,
+ XCB_CW_EVENT_MASK, values);
+
+ surface->xwm = xwm;
+ surface->window_id = window_id;
+ surface->x = x;
+ surface->y = y;
+ surface->width = width;
+ surface->height = height;
+ surface->override_redirect = override_redirect;
+ wl_list_insert(&xwm->surfaces, &surface->link);
+ wl_list_init(&surface->children);
+ wl_list_init(&surface->parent_link);
+ wl_signal_init(&surface->events.destroy);
+ wl_signal_init(&surface->events.request_configure);
+ wl_signal_init(&surface->events.request_move);
+ wl_signal_init(&surface->events.request_resize);
+ wl_signal_init(&surface->events.request_maximize);
+ wl_signal_init(&surface->events.request_fullscreen);
+ wl_signal_init(&surface->events.request_activate);
+ wl_signal_init(&surface->events.map);
+ wl_signal_init(&surface->events.unmap);
+ wl_signal_init(&surface->events.set_class);
+ wl_signal_init(&surface->events.set_role);
+ wl_signal_init(&surface->events.set_title);
+ wl_signal_init(&surface->events.set_parent);
+ wl_signal_init(&surface->events.set_pid);
+ wl_signal_init(&surface->events.set_window_type);
+ wl_signal_init(&surface->events.set_hints);
+ wl_signal_init(&surface->events.set_decorations);
+ wl_signal_init(&surface->events.set_override_redirect);
+ wl_signal_init(&surface->events.ping_timeout);
+
+ xcb_get_geometry_reply_t *geometry_reply =
+ xcb_get_geometry_reply(xwm->xcb_conn, geometry_cookie, NULL);
+ if (geometry_reply != NULL) {
+ surface->has_alpha = geometry_reply->depth == 32;
+ }
+ free(geometry_reply);
+
+ struct wl_display *display = xwm->xwayland->wl_display;
+ struct wl_event_loop *loop = wl_display_get_event_loop(display);
+ surface->ping_timer = wl_event_loop_add_timer(loop,
+ xwayland_surface_handle_ping_timeout, surface);
+ if (surface->ping_timer == NULL) {
+ free(surface);
+ wlr_log(WLR_ERROR, "Could not add timer to event loop");
+ return NULL;
+ }
+
+ wlr_signal_emit_safe(&xwm->xwayland->events.new_surface, surface);
+
+ return surface;
+}
+
+static void xwm_set_net_active_window(struct wlr_xwm *xwm,
+ xcb_window_t window) {
+ xcb_change_property(xwm->xcb_conn, XCB_PROP_MODE_REPLACE,
+ xwm->screen->root, xwm->atoms[_NET_ACTIVE_WINDOW],
+ xwm->atoms[WINDOW], 32, 1, &window);
+}
+
+static void xwm_send_wm_message(struct wlr_xwayland_surface *surface,
+ xcb_client_message_data_t *data, uint32_t event_mask) {
+ struct wlr_xwm *xwm = surface->xwm;
+
+ xcb_client_message_event_t event = {
+ .response_type = XCB_CLIENT_MESSAGE,
+ .format = 32,
+ .sequence = 0,
+ .window = surface->window_id,
+ .type = xwm->atoms[WM_PROTOCOLS],
+ .data = *data,
+ };
+
+ xcb_send_event(xwm->xcb_conn,
+ 0, // propagate
+ surface->window_id,
+ event_mask,
+ (const char *)&event);
+ xcb_flush(xwm->xcb_conn);
+}
+
+static void xwm_send_focus_window(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface) {
+ if (!xsurface) {
+ xcb_set_input_focus_checked(xwm->xcb_conn,
+ XCB_INPUT_FOCUS_POINTER_ROOT,
+ XCB_NONE, XCB_CURRENT_TIME);
+ return;
+ }
+
+ if (xsurface->override_redirect) {
+ return;
+ }
+
+ xcb_client_message_data_t message_data = { 0 };
+ message_data.data32[0] = xwm->atoms[WM_TAKE_FOCUS];
+ message_data.data32[1] = XCB_TIME_CURRENT_TIME;
+
+ if (xsurface->hints && !xsurface->hints->input) {
+ // if the surface doesn't allow the focus request, we will send him
+ // only the take focus event. It will get the focus by itself.
+ xwm_send_wm_message(xsurface, &message_data, XCB_EVENT_MASK_NO_EVENT);
+ } else {
+ xwm_send_wm_message(xsurface, &message_data, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT);
+
+ xcb_set_input_focus(xwm->xcb_conn, XCB_INPUT_FOCUS_POINTER_ROOT,
+ xsurface->window_id, XCB_CURRENT_TIME);
+ }
+
+ uint32_t values[1];
+ values[0] = XCB_STACK_MODE_ABOVE;
+ xcb_configure_window(xwm->xcb_conn, xsurface->window_id,
+ XCB_CONFIG_WINDOW_STACK_MODE, values);
+}
+
+static void xwm_surface_activate(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface) {
+ if (xwm->focus_surface == xsurface ||
+ (xsurface && xsurface->override_redirect)) {
+ return;
+ }
+
+ if (xsurface) {
+ xwm_set_net_active_window(xwm, xsurface->window_id);
+ } else {
+ xwm_set_net_active_window(xwm, XCB_WINDOW_NONE);
+ }
+
+ xwm_send_focus_window(xwm, xsurface);
+
+ xwm->focus_surface = xsurface;
+
+ xcb_flush(xwm->xcb_conn);
+}
+
+static void xsurface_set_net_wm_state(struct wlr_xwayland_surface *xsurface) {
+ struct wlr_xwm *xwm = xsurface->xwm;
+ uint32_t property[3];
+ int i;
+
+ i = 0;
+ if (xsurface->modal) {
+ property[i++] = xwm->atoms[_NET_WM_STATE_MODAL];
+ }
+ if (xsurface->fullscreen) {
+ property[i++] = xwm->atoms[_NET_WM_STATE_FULLSCREEN];
+ }
+ if (xsurface->maximized_vert) {
+ property[i++] = xwm->atoms[_NET_WM_STATE_MAXIMIZED_VERT];
+ }
+ if (xsurface->maximized_horz) {
+ property[i++] = xwm->atoms[_NET_WM_STATE_MAXIMIZED_HORZ];
+ }
+
+ xcb_change_property(xwm->xcb_conn,
+ XCB_PROP_MODE_REPLACE,
+ xsurface->window_id,
+ xwm->atoms[NET_WM_STATE],
+ XCB_ATOM_ATOM,
+ 32, // format
+ i, property);
+}
+
+static void xsurface_unmap(struct wlr_xwayland_surface *surface);
+
+static void xwayland_surface_destroy(
+ struct wlr_xwayland_surface *xsurface) {
+ xsurface_unmap(xsurface);
+
+ wlr_signal_emit_safe(&xsurface->events.destroy, xsurface);
+
+ if (xsurface == xsurface->xwm->focus_surface) {
+ xwm_surface_activate(xsurface->xwm, NULL);
+ }
+
+ wl_list_remove(&xsurface->link);
+ wl_list_remove(&xsurface->parent_link);
+
+ struct wlr_xwayland_surface *child, *next;
+ wl_list_for_each_safe(child, next, &xsurface->children, parent_link) {
+ wl_list_remove(&child->parent_link);
+ wl_list_init(&child->parent_link);
+ child->parent = NULL;
+ }
+
+ if (xsurface->surface_id) {
+ wl_list_remove(&xsurface->unpaired_link);
+ }
+
+ if (xsurface->surface) {
+ wl_list_remove(&xsurface->surface_destroy.link);
+ xsurface->surface->role_data = NULL;
+ }
+
+ wl_event_source_remove(xsurface->ping_timer);
+
+ free(xsurface->title);
+ free(xsurface->class);
+ free(xsurface->instance);
+ free(xsurface->role);
+ free(xsurface->window_type);
+ free(xsurface->protocols);
+ free(xsurface->hints);
+ free(xsurface->size_hints);
+ free(xsurface);
+}
+
+static void read_surface_class(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *surface, xcb_get_property_reply_t *reply) {
+ if (reply->type != XCB_ATOM_STRING &&
+ reply->type != xwm->atoms[UTF8_STRING]) {
+ return;
+ }
+
+ size_t len = xcb_get_property_value_length(reply);
+ char *class = xcb_get_property_value(reply);
+
+ // Unpack two sequentially stored strings: instance, class
+ size_t instance_len = strnlen(class, len);
+ free(surface->instance);
+ if (len > 0 && instance_len < len) {
+ surface->instance = strndup(class, instance_len);
+ class += instance_len + 1;
+ } else {
+ surface->instance = NULL;
+ }
+ free(surface->class);
+ if (len > 0) {
+ surface->class = strndup(class, len);
+ } else {
+ surface->class = NULL;
+ }
+
+ wlr_log(WLR_DEBUG, "XCB_ATOM_WM_CLASS: %s %s", surface->instance,
+ surface->class);
+ wlr_signal_emit_safe(&surface->events.set_class, surface);
+}
+
+static void read_surface_role(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface,
+ xcb_get_property_reply_t *reply) {
+ if (reply->type != XCB_ATOM_STRING &&
+ reply->type != xwm->atoms[UTF8_STRING]) {
+ return;
+ }
+
+ size_t len = xcb_get_property_value_length(reply);
+ char *role = xcb_get_property_value(reply);
+
+ free(xsurface->role);
+ if (len > 0) {
+ xsurface->role = strndup(role, len);
+ } else {
+ xsurface->role = NULL;
+ }
+
+ wlr_log(WLR_DEBUG, "XCB_ATOM_WM_WINDOW_ROLE: %s", xsurface->role);
+ wlr_signal_emit_safe(&xsurface->events.set_role, xsurface);
+}
+
+static void read_surface_title(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface,
+ xcb_get_property_reply_t *reply) {
+ if (reply->type != XCB_ATOM_STRING &&
+ reply->type != xwm->atoms[UTF8_STRING]) {
+ return;
+ }
+
+ bool is_utf8 = reply->type == xwm->atoms[UTF8_STRING];
+ if (!is_utf8 && xsurface->has_utf8_title) {
+ return;
+ }
+
+ size_t len = xcb_get_property_value_length(reply);
+ char *title = xcb_get_property_value(reply);
+
+ free(xsurface->title);
+ if (len > 0) {
+ xsurface->title = strndup(title, len);
+ } else {
+ xsurface->title = NULL;
+ }
+ xsurface->has_utf8_title = is_utf8;
+
+ wlr_log(WLR_DEBUG, "XCB_ATOM_WM_NAME: %s", xsurface->title);
+ wlr_signal_emit_safe(&xsurface->events.set_title, xsurface);
+}
+
+static void read_surface_parent(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface,
+ xcb_get_property_reply_t *reply) {
+ if (reply->type != XCB_ATOM_WINDOW) {
+ return;
+ }
+
+ xcb_window_t *xid = xcb_get_property_value(reply);
+ if (xid != NULL) {
+ xsurface->parent = lookup_surface(xwm, *xid);
+ } else {
+ xsurface->parent = NULL;
+ }
+
+ wl_list_remove(&xsurface->parent_link);
+ if (xsurface->parent != NULL) {
+ wl_list_insert(&xsurface->parent->children, &xsurface->parent_link);
+ } else {
+ wl_list_init(&xsurface->parent_link);
+ }
+
+ wlr_log(WLR_DEBUG, "XCB_ATOM_WM_TRANSIENT_FOR: %p", xsurface->parent);
+ wlr_signal_emit_safe(&xsurface->events.set_parent, xsurface);
+}
+
+static void read_surface_pid(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface,
+ xcb_get_property_reply_t *reply) {
+ if (reply->type != XCB_ATOM_CARDINAL) {
+ return;
+ }
+
+ pid_t *pid = xcb_get_property_value(reply);
+ xsurface->pid = *pid;
+ wlr_log(WLR_DEBUG, "NET_WM_PID %d", xsurface->pid);
+ wlr_signal_emit_safe(&xsurface->events.set_pid, xsurface);
+}
+
+static void read_surface_window_type(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface,
+ xcb_get_property_reply_t *reply) {
+ if (reply->type != XCB_ATOM_ATOM) {
+ return;
+ }
+
+ xcb_atom_t *atoms = xcb_get_property_value(reply);
+ size_t atoms_len = reply->value_len;
+ size_t atoms_size = sizeof(xcb_atom_t) * atoms_len;
+
+ free(xsurface->window_type);
+ xsurface->window_type = malloc(atoms_size);
+ if (xsurface->window_type == NULL) {
+ return;
+ }
+ memcpy(xsurface->window_type, atoms, atoms_size);
+ xsurface->window_type_len = atoms_len;
+
+ wlr_log(WLR_DEBUG, "NET_WM_WINDOW_TYPE (%zu)", atoms_len);
+ wlr_signal_emit_safe(&xsurface->events.set_window_type, xsurface);
+}
+
+static void read_surface_protocols(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface,
+ xcb_get_property_reply_t *reply) {
+ if (reply->type != XCB_ATOM_ATOM) {
+ return;
+ }
+
+ xcb_atom_t *atoms = xcb_get_property_value(reply);
+ size_t atoms_len = reply->value_len;
+ size_t atoms_size = sizeof(xcb_atom_t) * atoms_len;
+
+ free(xsurface->protocols);
+ xsurface->protocols = malloc(atoms_size);
+ if (xsurface->protocols == NULL) {
+ return;
+ }
+ memcpy(xsurface->protocols, atoms, atoms_size);
+ xsurface->protocols_len = atoms_len;
+
+ wlr_log(WLR_DEBUG, "WM_PROTOCOLS (%zu)", atoms_len);
+}
+
+#if WLR_HAS_XCB_ICCCM
+static void read_surface_hints(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface,
+ xcb_get_property_reply_t *reply) {
+ // According to the docs, reply->type == xwm->atoms[WM_HINTS]
+ // In practice, reply->type == XCB_ATOM_ATOM
+ if (reply->value_len == 0) {
+ return;
+ }
+
+ xcb_icccm_wm_hints_t hints;
+ xcb_icccm_get_wm_hints_from_reply(&hints, reply);
+
+ free(xsurface->hints);
+ xsurface->hints = calloc(1, sizeof(struct wlr_xwayland_surface_hints));
+ if (xsurface->hints == NULL) {
+ return;
+ }
+ memcpy(xsurface->hints, &hints, sizeof(struct wlr_xwayland_surface_hints));
+ xsurface->hints_urgency = xcb_icccm_wm_hints_get_urgency(&hints);
+
+ if (!(xsurface->hints->flags & XCB_ICCCM_WM_HINT_INPUT)) {
+ // The client didn't specify whether it wants input.
+ // Assume it does.
+ xsurface->hints->input = true;
+ }
+
+ wlr_log(WLR_DEBUG, "WM_HINTS (%d)", reply->value_len);
+ wlr_signal_emit_safe(&xsurface->events.set_hints, xsurface);
+}
+#else
+static void read_surface_hints(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface,
+ xcb_get_property_reply_t *reply) {
+ // Do nothing
+}
+#endif
+
+#if WLR_HAS_XCB_ICCCM
+static void read_surface_normal_hints(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface,
+ xcb_get_property_reply_t *reply) {
+ if (reply->type != xwm->atoms[WM_SIZE_HINTS] || reply->value_len == 0) {
+ return;
+ }
+
+ xcb_size_hints_t size_hints;
+ xcb_icccm_get_wm_size_hints_from_reply(&size_hints, reply);
+
+ free(xsurface->size_hints);
+ xsurface->size_hints =
+ calloc(1, sizeof(struct wlr_xwayland_surface_size_hints));
+ if (xsurface->size_hints == NULL) {
+ return;
+ }
+ memcpy(xsurface->size_hints, &size_hints,
+ sizeof(struct wlr_xwayland_surface_size_hints));
+
+ if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) == 0) {
+ xsurface->size_hints->min_width = -1;
+ xsurface->size_hints->min_height = -1;
+ }
+ if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) == 0) {
+ xsurface->size_hints->max_width = -1;
+ xsurface->size_hints->max_height = -1;
+ }
+
+ wlr_log(WLR_DEBUG, "WM_NORMAL_HINTS (%d)", reply->value_len);
+}
+#else
+static void read_surface_normal_hints(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface,
+ xcb_get_property_reply_t *reply) {
+ // Do nothing
+}
+#endif
+
+
+#define MWM_HINTS_FLAGS_FIELD 0
+#define MWM_HINTS_DECORATIONS_FIELD 2
+
+#define MWM_HINTS_DECORATIONS (1 << 1)
+
+#define MWM_DECOR_ALL (1 << 0)
+#define MWM_DECOR_BORDER (1 << 1)
+#define MWM_DECOR_TITLE (1 << 3)
+
+static void read_surface_motif_hints(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface,
+ xcb_get_property_reply_t *reply) {
+ if (reply->value_len < 5) {
+ return;
+ }
+
+ uint32_t *motif_hints = xcb_get_property_value(reply);
+ if (motif_hints[MWM_HINTS_FLAGS_FIELD] & MWM_HINTS_DECORATIONS) {
+ xsurface->decorations = WLR_XWAYLAND_SURFACE_DECORATIONS_ALL;
+ uint32_t decorations = motif_hints[MWM_HINTS_DECORATIONS_FIELD];
+ if ((decorations & MWM_DECOR_ALL) == 0) {
+ if ((decorations & MWM_DECOR_BORDER) == 0) {
+ xsurface->decorations |=
+ WLR_XWAYLAND_SURFACE_DECORATIONS_NO_BORDER;
+ }
+ if ((decorations & MWM_DECOR_TITLE) == 0) {
+ xsurface->decorations |=
+ WLR_XWAYLAND_SURFACE_DECORATIONS_NO_TITLE;
+ }
+ }
+ wlr_signal_emit_safe(&xsurface->events.set_decorations, xsurface);
+ }
+
+ wlr_log(WLR_DEBUG, "MOTIF_WM_HINTS (%d)", reply->value_len);
+}
+
+static void read_surface_net_wm_state(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface,
+ xcb_get_property_reply_t *reply) {
+ xsurface->fullscreen = 0;
+ xcb_atom_t *atom = xcb_get_property_value(reply);
+ for (uint32_t i = 0; i < reply->value_len; i++) {
+ if (atom[i] == xwm->atoms[_NET_WM_STATE_MODAL]) {
+ xsurface->modal = true;
+ } else if (atom[i] == xwm->atoms[_NET_WM_STATE_FULLSCREEN]) {
+ xsurface->fullscreen = true;
+ } else if (atom[i] == xwm->atoms[_NET_WM_STATE_MAXIMIZED_VERT]) {
+ xsurface->maximized_vert = true;
+ } else if (atom[i] == xwm->atoms[_NET_WM_STATE_MAXIMIZED_HORZ]) {
+ xsurface->maximized_horz = true;
+ }
+ }
+}
+
+char *xwm_get_atom_name(struct wlr_xwm *xwm, xcb_atom_t atom) {
+ xcb_get_atom_name_cookie_t name_cookie =
+ xcb_get_atom_name(xwm->xcb_conn, atom);
+ xcb_get_atom_name_reply_t *name_reply =
+ xcb_get_atom_name_reply(xwm->xcb_conn, name_cookie, NULL);
+ if (name_reply == NULL) {
+ return NULL;
+ }
+ size_t len = xcb_get_atom_name_name_length(name_reply);
+ char *buf = xcb_get_atom_name_name(name_reply); // not a C string
+ char *name = strndup(buf, len);
+ free(name_reply);
+ return name;
+}
+
+static void read_surface_property(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface, xcb_atom_t property) {
+ xcb_get_property_cookie_t cookie = xcb_get_property(xwm->xcb_conn, 0,
+ xsurface->window_id, property, XCB_ATOM_ANY, 0, 2048);
+ xcb_get_property_reply_t *reply = xcb_get_property_reply(xwm->xcb_conn,
+ cookie, NULL);
+ if (reply == NULL) {
+ return;
+ }
+
+ if (property == XCB_ATOM_WM_CLASS) {
+ read_surface_class(xwm, xsurface, reply);
+ } else if (property == XCB_ATOM_WM_NAME ||
+ property == xwm->atoms[NET_WM_NAME]) {
+ read_surface_title(xwm, xsurface, reply);
+ } else if (property == XCB_ATOM_WM_TRANSIENT_FOR) {
+ read_surface_parent(xwm, xsurface, reply);
+ } else if (property == xwm->atoms[NET_WM_PID]) {
+ read_surface_pid(xwm, xsurface, reply);
+ } else if (property == xwm->atoms[NET_WM_WINDOW_TYPE]) {
+ read_surface_window_type(xwm, xsurface, reply);
+ } else if (property == xwm->atoms[WM_PROTOCOLS]) {
+ read_surface_protocols(xwm, xsurface, reply);
+ } else if (property == xwm->atoms[NET_WM_STATE]) {
+ read_surface_net_wm_state(xwm, xsurface, reply);
+ } else if (property == xwm->atoms[WM_HINTS]) {
+ read_surface_hints(xwm, xsurface, reply);
+ } else if (property == xwm->atoms[WM_NORMAL_HINTS]) {
+ read_surface_normal_hints(xwm, xsurface, reply);
+ } else if (property == xwm->atoms[MOTIF_WM_HINTS]) {
+ read_surface_motif_hints(xwm, xsurface, reply);
+ } else if (property == xwm->atoms[WM_WINDOW_ROLE]) {
+ read_surface_role(xwm, xsurface, reply);
+ } else {
+ char *prop_name = xwm_get_atom_name(xwm, property);
+ wlr_log(WLR_DEBUG, "unhandled X11 property %u (%s) for window %u",
+ property, prop_name, xsurface->window_id);
+ free(prop_name);
+ }
+
+ free(reply);
+}
+
+static void xwayland_surface_role_commit(struct wlr_surface *wlr_surface) {
+ assert(wlr_surface->role == &xwayland_surface_role);
+ struct wlr_xwayland_surface *surface = wlr_surface->role_data;
+ if (surface == NULL) {
+ return;
+ }
+
+ if (!surface->mapped && wlr_surface_has_buffer(surface->surface)) {
+ wlr_signal_emit_safe(&surface->events.map, surface);
+ surface->mapped = true;
+ }
+}
+
+static void xwayland_surface_role_precommit(struct wlr_surface *wlr_surface) {
+ assert(wlr_surface->role == &xwayland_surface_role);
+ struct wlr_xwayland_surface *surface = wlr_surface->role_data;
+ if (surface == NULL) {
+ return;
+ }
+
+ if (wlr_surface->pending.committed & WLR_SURFACE_STATE_BUFFER &&
+ wlr_surface->pending.buffer_resource == NULL) {
+ // This is a NULL commit
+ if (surface->mapped) {
+ wlr_signal_emit_safe(&surface->events.unmap, surface);
+ surface->mapped = false;
+ }
+ }
+}
+
+static const struct wlr_surface_role xwayland_surface_role = {
+ .name = "wlr_xwayland_surface",
+ .commit = xwayland_surface_role_commit,
+ .precommit = xwayland_surface_role_precommit,
+};
+
+static void handle_surface_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_xwayland_surface *surface =
+ wl_container_of(listener, surface, surface_destroy);
+ xsurface_unmap(surface);
+}
+
+static void xwm_map_shell_surface(struct wlr_xwm *xwm,
+ struct wlr_xwayland_surface *xsurface, struct wlr_surface *surface) {
+ if (!wlr_surface_set_role(surface, &xwayland_surface_role, xsurface,
+ NULL, 0)) {
+ wlr_log(WLR_ERROR, "Failed to set xwayland surface role");
+ return;
+ }
+
+ xsurface->surface = surface;
+
+ // read all surface properties
+ const xcb_atom_t props[] = {
+ XCB_ATOM_WM_CLASS,
+ XCB_ATOM_WM_NAME,
+ XCB_ATOM_WM_TRANSIENT_FOR,
+ xwm->atoms[WM_PROTOCOLS],
+ xwm->atoms[WM_HINTS],
+ xwm->atoms[WM_NORMAL_HINTS],
+ xwm->atoms[MOTIF_WM_HINTS],
+ xwm->atoms[NET_WM_STATE],
+ xwm->atoms[NET_WM_WINDOW_TYPE],
+ xwm->atoms[NET_WM_NAME],
+ xwm->atoms[NET_WM_PID],
+ };
+ for (size_t i = 0; i < sizeof(props)/sizeof(xcb_atom_t); i++) {
+ read_surface_property(xwm, xsurface, props[i]);
+ }
+
+ xsurface->surface_destroy.notify = handle_surface_destroy;
+ wl_signal_add(&surface->events.destroy, &xsurface->surface_destroy);
+}
+
+static void xsurface_unmap(struct wlr_xwayland_surface *surface) {
+ if (surface->mapped) {
+ surface->mapped = false;
+ wlr_signal_emit_safe(&surface->events.unmap, surface);
+ }
+
+ if (surface->surface_id) {
+ // Make sure we're not on the unpaired surface list or we
+ // could be assigned a surface during surface creation that
+ // was mapped before this unmap request.
+ wl_list_remove(&surface->unpaired_link);
+ surface->surface_id = 0;
+ }
+
+ if (surface->surface) {
+ wl_list_remove(&surface->surface_destroy.link);
+ surface->surface->role_data = NULL;
+ surface->surface = NULL;
+ }
+}
+
+static void xwm_handle_create_notify(struct wlr_xwm *xwm,
+ xcb_create_notify_event_t *ev) {
+ wlr_log(WLR_DEBUG, "XCB_CREATE_NOTIFY (%u)", ev->window);
+
+ if (ev->window == xwm->window ||
+ ev->window == xwm->selection_window ||
+ ev->window == xwm->dnd_window) {
+ return;
+ }
+
+ xwayland_surface_create(xwm, ev->window, ev->x, ev->y,
+ ev->width, ev->height, ev->override_redirect);
+}
+
+static void xwm_handle_destroy_notify(struct wlr_xwm *xwm,
+ xcb_destroy_notify_event_t *ev) {
+ wlr_log(WLR_DEBUG, "XCB_DESTROY_NOTIFY (%u)", ev->window);
+ struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window);
+ if (xsurface == NULL) {
+ return;
+ }
+ xwayland_surface_destroy(xsurface);
+}
+
+static void xwm_handle_configure_request(struct wlr_xwm *xwm,
+ xcb_configure_request_event_t *ev) {
+ struct wlr_xwayland_surface *surface = lookup_surface(xwm, ev->window);
+ if (surface == NULL) {
+ return;
+ }
+
+ // TODO: handle ev->{parent,sibling}?
+
+ uint16_t mask = ev->value_mask;
+ uint16_t geo_mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
+ XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
+ if ((mask & geo_mask) == 0) {
+ return;
+ }
+
+ struct wlr_xwayland_surface_configure_event wlr_event = {
+ .surface = surface,
+ .x = mask & XCB_CONFIG_WINDOW_X ? ev->x : surface->x,
+ .y = mask & XCB_CONFIG_WINDOW_Y ? ev->y : surface->y,
+ .width = mask & XCB_CONFIG_WINDOW_WIDTH ? ev->width : surface->width,
+ .height = mask & XCB_CONFIG_WINDOW_HEIGHT ? ev->height : surface->height,
+ };
+ wlr_log(WLR_DEBUG, "XCB_CONFIGURE_REQUEST (%u) [%ux%u+%d,%d]", ev->window,
+ wlr_event.width, wlr_event.height, wlr_event.x, wlr_event.y);
+
+ wlr_signal_emit_safe(&surface->events.request_configure, &wlr_event);
+}
+
+static void xwm_handle_configure_notify(struct wlr_xwm *xwm,
+ xcb_configure_notify_event_t *ev) {
+ struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window);
+ if (!xsurface) {
+ return;
+ }
+
+ xsurface->x = ev->x;
+ xsurface->y = ev->y;
+ xsurface->width = ev->width;
+ xsurface->height = ev->height;
+
+ if (xsurface->override_redirect != ev->override_redirect) {
+ xsurface->override_redirect = ev->override_redirect;
+ wlr_signal_emit_safe(&xsurface->events.set_override_redirect, xsurface);
+ }
+}
+
+#define ICCCM_WITHDRAWN_STATE 0
+#define ICCCM_NORMAL_STATE 1
+#define ICCCM_ICONIC_STATE 3
+
+static void xsurface_set_wm_state(struct wlr_xwayland_surface *xsurface,
+ int32_t state) {
+ struct wlr_xwm *xwm = xsurface->xwm;
+ uint32_t property[2];
+
+ property[0] = state;
+ property[1] = XCB_WINDOW_NONE;
+
+ xcb_change_property(xwm->xcb_conn,
+ XCB_PROP_MODE_REPLACE,
+ xsurface->window_id,
+ xwm->atoms[WM_STATE],
+ xwm->atoms[WM_STATE],
+ 32, // format
+ 2, property);
+}
+
+static void xwm_handle_map_request(struct wlr_xwm *xwm,
+ xcb_map_request_event_t *ev) {
+ wlr_log(WLR_DEBUG, "XCB_MAP_REQUEST (%u)", ev->window);
+ struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window);
+ if (!xsurface) {
+ return;
+ }
+
+ xsurface_set_wm_state(xsurface, ICCCM_NORMAL_STATE);
+ xsurface_set_net_wm_state(xsurface);
+ xcb_map_window(xwm->xcb_conn, ev->window);
+}
+
+static void xwm_handle_map_notify(struct wlr_xwm *xwm,
+ xcb_map_notify_event_t *ev) {
+ wlr_log(WLR_DEBUG, "XCB_MAP_NOTIFY (%u)", ev->window);
+ struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window);
+ if (!xsurface) {
+ return;
+ }
+
+ if (xsurface->override_redirect != ev->override_redirect) {
+ xsurface->override_redirect = ev->override_redirect;
+ wlr_signal_emit_safe(&xsurface->events.set_override_redirect, xsurface);
+ }
+}
+
+static void xwm_handle_unmap_notify(struct wlr_xwm *xwm,
+ xcb_unmap_notify_event_t *ev) {
+ wlr_log(WLR_DEBUG, "XCB_UNMAP_NOTIFY (%u)", ev->window);
+ struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window);
+ if (xsurface == NULL) {
+ return;
+ }
+
+ xsurface_unmap(xsurface);
+ xsurface_set_wm_state(xsurface, ICCCM_WITHDRAWN_STATE);
+}
+
+static void xwm_handle_property_notify(struct wlr_xwm *xwm,
+ xcb_property_notify_event_t *ev) {
+ wlr_log(WLR_DEBUG, "XCB_PROPERTY_NOTIFY (%u)", ev->window);
+ struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window);
+ if (xsurface == NULL) {
+ return;
+ }
+
+ read_surface_property(xwm, xsurface, ev->atom);
+}
+
+static void xwm_handle_surface_id_message(struct wlr_xwm *xwm,
+ xcb_client_message_event_t *ev) {
+ struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window);
+ if (xsurface == NULL) {
+ wlr_log(WLR_DEBUG,
+ "client message WL_SURFACE_ID but no new window %u ?",
+ ev->window);
+ return;
+ }
+ /* Check if we got notified after wayland surface create event */
+ uint32_t id = ev->data.data32[0];
+ struct wl_resource *resource =
+ wl_client_get_object(xwm->xwayland->client, id);
+ if (resource) {
+ struct wlr_surface *surface = wlr_surface_from_resource(resource);
+ xsurface->surface_id = 0;
+ xwm_map_shell_surface(xwm, xsurface, surface);
+ } else {
+ xsurface->surface_id = id;
+ wl_list_insert(&xwm->unpaired_surfaces, &xsurface->unpaired_link);
+ }
+}
+
+#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0
+#define _NET_WM_MOVERESIZE_SIZE_TOP 1
+#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2
+#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6
+#define _NET_WM_MOVERESIZE_SIZE_LEFT 7
+#define _NET_WM_MOVERESIZE_MOVE 8 // movement only
+#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 // size via keyboard
+#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 // move via keyboard
+#define _NET_WM_MOVERESIZE_CANCEL 11 // cancel operation
+
+static enum wlr_edges net_wm_edges_to_wlr(uint32_t net_wm_edges) {
+ enum wlr_edges edges = WLR_EDGE_NONE;
+
+ switch(net_wm_edges) {
+ case _NET_WM_MOVERESIZE_SIZE_TOPLEFT:
+ edges = WLR_EDGE_TOP | WLR_EDGE_LEFT;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_TOP:
+ edges = WLR_EDGE_TOP;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT:
+ edges = WLR_EDGE_TOP | WLR_EDGE_RIGHT;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_RIGHT:
+ edges = WLR_EDGE_RIGHT;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT:
+ edges = WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_BOTTOM:
+ edges = WLR_EDGE_BOTTOM;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT:
+ edges = WLR_EDGE_BOTTOM | WLR_EDGE_LEFT;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_LEFT:
+ edges = WLR_EDGE_LEFT;
+ break;
+ default:
+ break;
+ }
+
+ return edges;
+}
+
+static void xwm_handle_net_wm_moveresize_message(struct wlr_xwm *xwm,
+ xcb_client_message_event_t *ev) {
+ struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window);
+ if (!xsurface) {
+ return;
+ }
+
+ // TODO: we should probably add input or seat info to this but we would just
+ // be guessing
+ struct wlr_xwayland_resize_event resize_event;
+ struct wlr_xwayland_move_event move_event;
+
+ int detail = ev->data.data32[2];
+ switch (detail) {
+ case _NET_WM_MOVERESIZE_MOVE:
+ move_event.surface = xsurface;
+ wlr_signal_emit_safe(&xsurface->events.request_move, &move_event);
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_TOPLEFT:
+ case _NET_WM_MOVERESIZE_SIZE_TOP:
+ case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT:
+ case _NET_WM_MOVERESIZE_SIZE_RIGHT:
+ case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT:
+ case _NET_WM_MOVERESIZE_SIZE_BOTTOM:
+ case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT:
+ case _NET_WM_MOVERESIZE_SIZE_LEFT:
+ resize_event.surface = xsurface;
+ resize_event.edges = net_wm_edges_to_wlr(detail);
+ wlr_signal_emit_safe(&xsurface->events.request_resize, &resize_event);
+ break;
+ case _NET_WM_MOVERESIZE_CANCEL:
+ // handled by the compositor
+ break;
+ }
+}
+
+#define _NET_WM_STATE_REMOVE 0
+#define _NET_WM_STATE_ADD 1
+#define _NET_WM_STATE_TOGGLE 2
+
+static bool update_state(int action, bool *state) {
+ int new_state, changed;
+
+ switch (action) {
+ case _NET_WM_STATE_REMOVE:
+ new_state = false;
+ break;
+ case _NET_WM_STATE_ADD:
+ new_state = true;
+ break;
+ case _NET_WM_STATE_TOGGLE:
+ new_state = !*state;
+ break;
+ default:
+ return false;
+ }
+
+ changed = (*state != new_state);
+ *state = new_state;
+
+ return changed;
+}
+
+static inline bool xsurface_is_maximized(
+ struct wlr_xwayland_surface *xsurface) {
+ return xsurface->maximized_horz && xsurface->maximized_vert;
+}
+
+static void xwm_handle_net_wm_state_message(struct wlr_xwm *xwm,
+ xcb_client_message_event_t *client_message) {
+ struct wlr_xwayland_surface *xsurface =
+ lookup_surface(xwm, client_message->window);
+ if (!xsurface) {
+ return;
+ }
+ if (client_message->format != 32) {
+ return;
+ }
+
+ bool fullscreen = xsurface->fullscreen;
+ bool maximized = xsurface_is_maximized(xsurface);
+
+ uint32_t action = client_message->data.data32[0];
+ for (size_t i = 0; i < 2; ++i) {
+ uint32_t property = client_message->data.data32[1 + i];
+
+ if (property == xwm->atoms[_NET_WM_STATE_MODAL] &&
+ update_state(action, &xsurface->modal)) {
+ xsurface_set_net_wm_state(xsurface);
+ } else if (property == xwm->atoms[_NET_WM_STATE_FULLSCREEN] &&
+ update_state(action, &xsurface->fullscreen)) {
+ xsurface_set_net_wm_state(xsurface);
+ } else if (property == xwm->atoms[_NET_WM_STATE_MAXIMIZED_VERT] &&
+ update_state(action, &xsurface->maximized_vert)) {
+ xsurface_set_net_wm_state(xsurface);
+ } else if (property == xwm->atoms[_NET_WM_STATE_MAXIMIZED_HORZ] &&
+ update_state(action, &xsurface->maximized_horz)) {
+ xsurface_set_net_wm_state(xsurface);
+ }
+ }
+ // client_message->data.data32[3] is the source indication
+ // all other values are set to 0
+
+ if (fullscreen != xsurface->fullscreen) {
+ if (xsurface->fullscreen) {
+ xsurface->saved_width = xsurface->width;
+ xsurface->saved_height = xsurface->height;
+ }
+
+ wlr_signal_emit_safe(&xsurface->events.request_fullscreen, xsurface);
+ }
+
+ if (maximized != xsurface_is_maximized(xsurface)) {
+ if (xsurface_is_maximized(xsurface)) {
+ xsurface->saved_width = xsurface->width;
+ xsurface->saved_height = xsurface->height;
+ }
+
+ wlr_signal_emit_safe(&xsurface->events.request_maximize, xsurface);
+ }
+}
+
+static void xwm_handle_wm_protocols_message(struct wlr_xwm *xwm,
+ xcb_client_message_event_t *ev) {
+ xcb_atom_t type = ev->data.data32[0];
+
+ if (type == xwm->atoms[_NET_WM_PING]) {
+ xcb_window_t window_id = ev->data.data32[2];
+
+ struct wlr_xwayland_surface *surface = lookup_surface(xwm, window_id);
+ if (surface == NULL) {
+ return;
+ }
+
+ if (!surface->pinging) {
+ return;
+ }
+
+ wl_event_source_timer_update(surface->ping_timer, 0);
+ surface->pinging = false;
+ } else {
+ char *type_name = xwm_get_atom_name(xwm, type);
+ wlr_log(WLR_DEBUG, "unhandled WM_PROTOCOLS client message %u (%s)",
+ type, type_name);
+ free(type_name);
+ }
+}
+
+static void xwm_handle_net_active_window_message(struct wlr_xwm *xwm,
+ xcb_client_message_event_t *ev) {
+ struct wlr_xwayland_surface *surface = lookup_surface(xwm, ev->window);
+ if (surface == NULL) {
+ return;
+ }
+ wlr_signal_emit_safe(&surface->events.request_activate, surface);
+}
+
+static void xwm_handle_client_message(struct wlr_xwm *xwm,
+ xcb_client_message_event_t *ev) {
+ wlr_log(WLR_DEBUG, "XCB_CLIENT_MESSAGE (%u)", ev->window);
+
+ if (ev->type == xwm->atoms[WL_SURFACE_ID]) {
+ xwm_handle_surface_id_message(xwm, ev);
+ } else if (ev->type == xwm->atoms[NET_WM_STATE]) {
+ xwm_handle_net_wm_state_message(xwm, ev);
+ } else if (ev->type == xwm->atoms[_NET_WM_MOVERESIZE]) {
+ xwm_handle_net_wm_moveresize_message(xwm, ev);
+ } else if (ev->type == xwm->atoms[WM_PROTOCOLS]) {
+ xwm_handle_wm_protocols_message(xwm, ev);
+ } else if (ev->type == xwm->atoms[_NET_ACTIVE_WINDOW]) {
+ xwm_handle_net_active_window_message(xwm, ev);
+ } else if (!xwm_handle_selection_client_message(xwm, ev)) {
+ char *type_name = xwm_get_atom_name(xwm, ev->type);
+ wlr_log(WLR_DEBUG, "unhandled x11 client message %u (%s)", ev->type,
+ type_name);
+ free(type_name);
+ }
+}
+
+static void xwm_handle_focus_in(struct wlr_xwm *xwm,
+ xcb_focus_in_event_t *ev) {
+ // Do not interfere with grabs
+ if (ev->mode == XCB_NOTIFY_MODE_GRAB ||
+ ev->mode == XCB_NOTIFY_MODE_UNGRAB) {
+ return;
+ }
+
+ // Do not let X clients change the focus behind the compositor's
+ // back. Reset the focus to the old one if it changed.
+ if (!xwm->focus_surface || ev->event != xwm->focus_surface->window_id) {
+ xwm_send_focus_window(xwm, xwm->focus_surface);
+ }
+}
+
+static void xwm_handle_xcb_error(struct wlr_xwm *xwm, xcb_value_error_t *ev) {
+#if WLR_HAS_XCB_ERRORS
+ const char *major_name =
+ xcb_errors_get_name_for_major_code(xwm->errors_context,
+ ev->major_opcode);
+ if (!major_name) {
+ wlr_log(WLR_DEBUG, "xcb error happened, but could not get major name");
+ goto log_raw;
+ }
+
+ const char *minor_name =
+ xcb_errors_get_name_for_minor_code(xwm->errors_context,
+ ev->major_opcode, ev->minor_opcode);
+
+ const char *extension;
+ const char *error_name =
+ xcb_errors_get_name_for_error(xwm->errors_context,
+ ev->error_code, &extension);
+ if (!error_name) {
+ wlr_log(WLR_DEBUG, "xcb error happened, but could not get error name");
+ goto log_raw;
+ }
+
+ wlr_log(WLR_ERROR, "xcb error: op %s (%s), code %s (%s), sequence %"PRIu16", value %"PRIu32,
+ major_name, minor_name ? minor_name : "no minor",
+ error_name, extension ? extension : "no extension",
+ ev->sequence, ev->bad_value);
+
+ return;
+log_raw:
+#endif
+ wlr_log(WLR_ERROR,
+ "xcb error: op %"PRIu8":%"PRIu16", code %"PRIu8", sequence %"PRIu16", value %"PRIu32,
+ ev->major_opcode, ev->minor_opcode, ev->error_code,
+ ev->sequence, ev->bad_value);
+
+}
+
+static void xwm_handle_unhandled_event(struct wlr_xwm *xwm, xcb_generic_event_t *ev) {
+#if WLR_HAS_XCB_ERRORS
+ const char *extension;
+ const char *event_name =
+ xcb_errors_get_name_for_xcb_event(xwm->errors_context,
+ ev, &extension);
+ if (!event_name) {
+ wlr_log(WLR_DEBUG, "no name for unhandled event: %u",
+ ev->response_type);
+ return;
+ }
+
+ wlr_log(WLR_DEBUG, "unhandled X11 event: %s (%u)", event_name, ev->response_type);
+#else
+ wlr_log(WLR_DEBUG, "unhandled X11 event: %u", ev->response_type);
+#endif
+}
+
+static int x11_event_handler(int fd, uint32_t mask, void *data) {
+ int count = 0;
+ xcb_generic_event_t *event;
+ struct wlr_xwm *xwm = data;
+
+ while ((event = xcb_poll_for_event(xwm->xcb_conn))) {
+ count++;
+
+ if (xwm->xwayland->user_event_handler &&
+ xwm->xwayland->user_event_handler(xwm, event)) {
+ break;
+ }
+
+ if (xwm_handle_selection_event(xwm, event)) {
+ free(event);
+ continue;
+ }
+
+ switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) {
+ case XCB_CREATE_NOTIFY:
+ xwm_handle_create_notify(xwm, (xcb_create_notify_event_t *)event);
+ break;
+ case XCB_DESTROY_NOTIFY:
+ xwm_handle_destroy_notify(xwm, (xcb_destroy_notify_event_t *)event);
+ break;
+ case XCB_CONFIGURE_REQUEST:
+ xwm_handle_configure_request(xwm,
+ (xcb_configure_request_event_t *)event);
+ break;
+ case XCB_CONFIGURE_NOTIFY:
+ xwm_handle_configure_notify(xwm,
+ (xcb_configure_notify_event_t *)event);
+ break;
+ case XCB_MAP_REQUEST:
+ xwm_handle_map_request(xwm, (xcb_map_request_event_t *)event);
+ break;
+ case XCB_MAP_NOTIFY:
+ xwm_handle_map_notify(xwm, (xcb_map_notify_event_t *)event);
+ break;
+ case XCB_UNMAP_NOTIFY:
+ xwm_handle_unmap_notify(xwm, (xcb_unmap_notify_event_t *)event);
+ break;
+ case XCB_PROPERTY_NOTIFY:
+ xwm_handle_property_notify(xwm,
+ (xcb_property_notify_event_t *)event);
+ break;
+ case XCB_CLIENT_MESSAGE:
+ xwm_handle_client_message(xwm, (xcb_client_message_event_t *)event);
+ break;
+ case XCB_FOCUS_IN:
+ xwm_handle_focus_in(xwm, (xcb_focus_in_event_t *)event);
+ break;
+ case 0:
+ xwm_handle_xcb_error(xwm, (xcb_value_error_t *)event);
+ break;
+ default:
+ xwm_handle_unhandled_event(xwm, event);
+ break;
+ }
+ free(event);
+ }
+
+ if (count) {
+ xcb_flush(xwm->xcb_conn);
+ }
+
+ return count;
+}
+
+static void handle_compositor_new_surface(struct wl_listener *listener,
+ void *data) {
+ struct wlr_xwm *xwm =
+ wl_container_of(listener, xwm, compositor_new_surface);
+ struct wlr_surface *surface = data;
+ if (wl_resource_get_client(surface->resource) != xwm->xwayland->client) {
+ return;
+ }
+
+ wlr_log(WLR_DEBUG, "New xwayland surface: %p", surface);
+
+ uint32_t surface_id = wl_resource_get_id(surface->resource);
+ struct wlr_xwayland_surface *xsurface;
+ wl_list_for_each(xsurface, &xwm->unpaired_surfaces, unpaired_link) {
+ if (xsurface->surface_id == surface_id) {
+ xwm_map_shell_surface(xwm, xsurface, surface);
+ xsurface->surface_id = 0;
+ wl_list_remove(&xsurface->unpaired_link);
+ xcb_flush(xwm->xcb_conn);
+ return;
+ }
+ }
+}
+
+static void handle_compositor_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_xwm *xwm =
+ wl_container_of(listener, xwm, compositor_destroy);
+ wl_list_remove(&xwm->compositor_new_surface.link);
+ wl_list_remove(&xwm->compositor_destroy.link);
+ wl_list_init(&xwm->compositor_new_surface.link);
+ wl_list_init(&xwm->compositor_destroy.link);
+}
+
+void wlr_xwayland_surface_activate(struct wlr_xwayland_surface *xsurface,
+ bool activated) {
+ struct wlr_xwayland_surface *focused = xsurface->xwm->focus_surface;
+ if (activated) {
+ xwm_surface_activate(xsurface->xwm, xsurface);
+ } else if (focused == xsurface) {
+ xwm_surface_activate(xsurface->xwm, NULL);
+ }
+}
+
+void wlr_xwayland_surface_configure(struct wlr_xwayland_surface *xsurface,
+ int16_t x, int16_t y, uint16_t width, uint16_t height) {
+ xsurface->x = x;
+ xsurface->y = y;
+ xsurface->width = width;
+ xsurface->height = height;
+
+ struct wlr_xwm *xwm = xsurface->xwm;
+ uint32_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
+ XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
+ XCB_CONFIG_WINDOW_BORDER_WIDTH;
+ uint32_t values[] = {x, y, width, height, 0};
+ xcb_configure_window(xwm->xcb_conn, xsurface->window_id, mask, values);
+ xcb_flush(xwm->xcb_conn);
+}
+
+void wlr_xwayland_surface_close(struct wlr_xwayland_surface *xsurface) {
+ struct wlr_xwm *xwm = xsurface->xwm;
+
+ bool supports_delete = false;
+ for (size_t i = 0; i < xsurface->protocols_len; i++) {
+ if (xsurface->protocols[i] == xwm->atoms[WM_DELETE_WINDOW]) {
+ supports_delete = true;
+ break;
+ }
+ }
+
+ if (supports_delete) {
+ xcb_client_message_data_t message_data = {0};
+ message_data.data32[0] = xwm->atoms[WM_DELETE_WINDOW];
+ message_data.data32[1] = XCB_CURRENT_TIME;
+ xwm_send_wm_message(xsurface, &message_data, XCB_EVENT_MASK_NO_EVENT);
+ } else {
+ xcb_kill_client(xwm->xcb_conn, xsurface->window_id);
+ xcb_flush(xwm->xcb_conn);
+ }
+}
+
+void xwm_destroy(struct wlr_xwm *xwm) {
+ if (!xwm) {
+ return;
+ }
+ xwm_selection_finish(xwm);
+ if (xwm->cursor) {
+ xcb_free_cursor(xwm->xcb_conn, xwm->cursor);
+ }
+ if (xwm->colormap) {
+ xcb_free_colormap(xwm->xcb_conn, xwm->colormap);
+ }
+ if (xwm->window) {
+ xcb_destroy_window(xwm->xcb_conn, xwm->window);
+ }
+ if (xwm->event_source) {
+ wl_event_source_remove(xwm->event_source);
+ }
+#if WLR_HAS_XCB_ERRORS
+ if (xwm->errors_context) {
+ xcb_errors_context_free(xwm->errors_context);
+ }
+#endif
+ struct wlr_xwayland_surface *xsurface, *tmp;
+ wl_list_for_each_safe(xsurface, tmp, &xwm->surfaces, link) {
+ xwayland_surface_destroy(xsurface);
+ }
+ wl_list_for_each_safe(xsurface, tmp, &xwm->unpaired_surfaces, link) {
+ xwayland_surface_destroy(xsurface);
+ }
+ wl_list_remove(&xwm->compositor_new_surface.link);
+ wl_list_remove(&xwm->compositor_destroy.link);
+ xcb_disconnect(xwm->xcb_conn);
+
+ free(xwm);
+}
+
+static void xwm_get_resources(struct wlr_xwm *xwm) {
+ xcb_prefetch_extension_data(xwm->xcb_conn, &xcb_xfixes_id);
+ xcb_prefetch_extension_data(xwm->xcb_conn, &xcb_composite_id);
+
+ size_t i;
+ xcb_intern_atom_cookie_t cookies[ATOM_LAST];
+
+ for (i = 0; i < ATOM_LAST; i++) {
+ cookies[i] =
+ xcb_intern_atom(xwm->xcb_conn, 0, strlen(atom_map[i]), atom_map[i]);
+ }
+ for (i = 0; i < ATOM_LAST; i++) {
+ xcb_generic_error_t *error;
+ xcb_intern_atom_reply_t *reply =
+ xcb_intern_atom_reply(xwm->xcb_conn, cookies[i], &error);
+ if (reply && !error) {
+ xwm->atoms[i] = reply->atom;
+ }
+ free(reply);
+
+ if (error) {
+ wlr_log(WLR_ERROR, "could not resolve atom %s, x11 error code %d",
+ atom_map[i], error->error_code);
+ free(error);
+ return;
+ }
+ }
+
+ xwm->xfixes = xcb_get_extension_data(xwm->xcb_conn, &xcb_xfixes_id);
+
+ if (!xwm->xfixes || !xwm->xfixes->present) {
+ wlr_log(WLR_DEBUG, "xfixes not available");
+ }
+
+ xcb_xfixes_query_version_cookie_t xfixes_cookie;
+ xcb_xfixes_query_version_reply_t *xfixes_reply;
+ xfixes_cookie =
+ xcb_xfixes_query_version(xwm->xcb_conn, XCB_XFIXES_MAJOR_VERSION,
+ XCB_XFIXES_MINOR_VERSION);
+ xfixes_reply =
+ xcb_xfixes_query_version_reply(xwm->xcb_conn, xfixes_cookie, NULL);
+
+ wlr_log(WLR_DEBUG, "xfixes version: %d.%d",
+ xfixes_reply->major_version, xfixes_reply->minor_version);
+
+ free(xfixes_reply);
+}
+
+static void xwm_create_wm_window(struct wlr_xwm *xwm) {
+ static const char name[] = "wlroots wm";
+
+ xwm->window = xcb_generate_id(xwm->xcb_conn);
+
+ xcb_create_window(xwm->xcb_conn,
+ XCB_COPY_FROM_PARENT,
+ xwm->window,
+ xwm->screen->root,
+ 0, 0,
+ 10, 10,
+ 0,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ xwm->screen->root_visual,
+ 0, NULL);
+
+ xcb_change_property(xwm->xcb_conn,
+ XCB_PROP_MODE_REPLACE,
+ xwm->window,
+ xwm->atoms[_NET_WM_NAME],
+ xwm->atoms[UTF8_STRING],
+ 8, // format
+ strlen(name), name);
+
+ xcb_change_property(xwm->xcb_conn,
+ XCB_PROP_MODE_REPLACE,
+ xwm->screen->root,
+ xwm->atoms[_NET_SUPPORTING_WM_CHECK],
+ XCB_ATOM_WINDOW,
+ 32, // format
+ 1, &xwm->window);
+
+ xcb_change_property(xwm->xcb_conn,
+ XCB_PROP_MODE_REPLACE,
+ xwm->window,
+ xwm->atoms[_NET_SUPPORTING_WM_CHECK],
+ XCB_ATOM_WINDOW,
+ 32, // format
+ 1, &xwm->window);
+
+ xcb_set_selection_owner(xwm->xcb_conn,
+ xwm->window,
+ xwm->atoms[WM_S0],
+ XCB_CURRENT_TIME);
+
+ xcb_set_selection_owner(xwm->xcb_conn,
+ xwm->window,
+ xwm->atoms[NET_WM_CM_S0],
+ XCB_CURRENT_TIME);
+}
+
+// TODO use me to support 32 bit color somehow
+static void xwm_get_visual_and_colormap(struct wlr_xwm *xwm) {
+ xcb_depth_iterator_t d_iter;
+ xcb_visualtype_iterator_t vt_iter;
+ xcb_visualtype_t *visualtype;
+
+ d_iter = xcb_screen_allowed_depths_iterator(xwm->screen);
+ visualtype = NULL;
+ while (d_iter.rem > 0) {
+ if (d_iter.data->depth == 32) {
+ vt_iter = xcb_depth_visuals_iterator(d_iter.data);
+ visualtype = vt_iter.data;
+ break;
+ }
+
+ xcb_depth_next(&d_iter);
+ }
+
+ if (visualtype == NULL) {
+ wlr_log(WLR_DEBUG, "No 32 bit visualtype\n");
+ return;
+ }
+
+ xwm->visual_id = visualtype->visual_id;
+ xwm->colormap = xcb_generate_id(xwm->xcb_conn);
+ xcb_create_colormap(xwm->xcb_conn,
+ XCB_COLORMAP_ALLOC_NONE,
+ xwm->colormap,
+ xwm->screen->root,
+ xwm->visual_id);
+}
+
+static void xwm_get_render_format(struct wlr_xwm *xwm) {
+ xcb_render_query_pict_formats_cookie_t cookie =
+ xcb_render_query_pict_formats(xwm->xcb_conn);
+ xcb_render_query_pict_formats_reply_t *reply =
+ xcb_render_query_pict_formats_reply(xwm->xcb_conn, cookie, NULL);
+ if (!reply) {
+ wlr_log(WLR_ERROR, "Did not get any reply from xcb_render_query_pict_formats");
+ return;
+ }
+ xcb_render_pictforminfo_iterator_t iter =
+ xcb_render_query_pict_formats_formats_iterator(reply);
+ xcb_render_pictforminfo_t *format = NULL;
+ while (iter.rem > 0) {
+ if (iter.data->depth == 32) {
+ format = iter.data;
+ break;
+ }
+
+ xcb_render_pictforminfo_next(&iter);
+ }
+
+ if (format == NULL) {
+ wlr_log(WLR_DEBUG, "No 32 bit render format");
+ free(reply);
+ return;
+ }
+
+ xwm->render_format_id = format->id;
+ free(reply);
+}
+
+void xwm_set_cursor(struct wlr_xwm *xwm, const uint8_t *pixels, uint32_t stride,
+ uint32_t width, uint32_t height, int32_t hotspot_x, int32_t hotspot_y) {
+ if (!xwm->render_format_id) {
+ wlr_log(WLR_ERROR, "Cannot set xwm cursor: no render format available");
+ return;
+ }
+ if (xwm->cursor) {
+ xcb_free_cursor(xwm->xcb_conn, xwm->cursor);
+ }
+
+ int depth = 32;
+
+ xcb_pixmap_t pix = xcb_generate_id(xwm->xcb_conn);
+ xcb_create_pixmap(xwm->xcb_conn, depth, pix, xwm->screen->root, width,
+ height);
+
+ xcb_render_picture_t pic = xcb_generate_id(xwm->xcb_conn);
+ xcb_render_create_picture(xwm->xcb_conn, pic, pix, xwm->render_format_id,
+ 0, 0);
+
+ xcb_gcontext_t gc = xcb_generate_id(xwm->xcb_conn);
+ xcb_create_gc(xwm->xcb_conn, gc, pix, 0, NULL);
+
+ xcb_put_image(xwm->xcb_conn, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc,
+ width, height, 0, 0, 0, depth, stride * height * sizeof(uint8_t),
+ pixels);
+ xcb_free_gc(xwm->xcb_conn, gc);
+
+ xwm->cursor = xcb_generate_id(xwm->xcb_conn);
+ xcb_render_create_cursor(xwm->xcb_conn, xwm->cursor, pic, hotspot_x,
+ hotspot_y);
+ xcb_free_pixmap(xwm->xcb_conn, pix);
+
+ uint32_t values[] = {xwm->cursor};
+ xcb_change_window_attributes(xwm->xcb_conn, xwm->screen->root,
+ XCB_CW_CURSOR, values);
+ xcb_flush(xwm->xcb_conn);
+}
+
+struct wlr_xwm *xwm_create(struct wlr_xwayland *wlr_xwayland) {
+ struct wlr_xwm *xwm = calloc(1, sizeof(struct wlr_xwm));
+ if (xwm == NULL) {
+ return NULL;
+ }
+
+ xwm->xwayland = wlr_xwayland;
+ wl_list_init(&xwm->surfaces);
+ wl_list_init(&xwm->unpaired_surfaces);
+ xwm->ping_timeout = 10000;
+
+ xwm->xcb_conn = xcb_connect_to_fd(wlr_xwayland->wm_fd[0], NULL);
+
+ int rc = xcb_connection_has_error(xwm->xcb_conn);
+ if (rc) {
+ wlr_log(WLR_ERROR, "xcb connect failed: %d", rc);
+ close(wlr_xwayland->wm_fd[0]);
+ free(xwm);
+ return NULL;
+ }
+
+#if WLR_HAS_XCB_ERRORS
+ if (xcb_errors_context_new(xwm->xcb_conn, &xwm->errors_context)) {
+ wlr_log(WLR_ERROR, "Could not allocate error context");
+ xwm_destroy(xwm);
+ return NULL;
+ }
+#endif
+ xcb_screen_iterator_t screen_iterator =
+ xcb_setup_roots_iterator(xcb_get_setup(xwm->xcb_conn));
+ xwm->screen = screen_iterator.data;
+
+ struct wl_event_loop *event_loop = wl_display_get_event_loop(
+ wlr_xwayland->wl_display);
+ xwm->event_source =
+ wl_event_loop_add_fd(event_loop,
+ wlr_xwayland->wm_fd[0],
+ WL_EVENT_READABLE,
+ x11_event_handler,
+ xwm);
+ wl_event_source_check(xwm->event_source);
+
+ xwm_get_resources(xwm);
+ xwm_get_visual_and_colormap(xwm);
+ xwm_get_render_format(xwm);
+
+ uint32_t values[] = {
+ XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
+ XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
+ XCB_EVENT_MASK_PROPERTY_CHANGE,
+ };
+ xcb_change_window_attributes(xwm->xcb_conn,
+ xwm->screen->root,
+ XCB_CW_EVENT_MASK,
+ values);
+
+ xcb_composite_redirect_subwindows(xwm->xcb_conn,
+ xwm->screen->root,
+ XCB_COMPOSITE_REDIRECT_MANUAL);
+
+ xcb_atom_t supported[] = {
+ xwm->atoms[NET_WM_STATE],
+ xwm->atoms[_NET_ACTIVE_WINDOW],
+ xwm->atoms[_NET_WM_MOVERESIZE],
+ xwm->atoms[_NET_WM_STATE_MODAL],
+ xwm->atoms[_NET_WM_STATE_FULLSCREEN],
+ xwm->atoms[_NET_WM_STATE_MAXIMIZED_VERT],
+ xwm->atoms[_NET_WM_STATE_MAXIMIZED_HORZ],
+ };
+ xcb_change_property(xwm->xcb_conn,
+ XCB_PROP_MODE_REPLACE,
+ xwm->screen->root,
+ xwm->atoms[NET_SUPPORTED],
+ XCB_ATOM_ATOM,
+ 32,
+ sizeof(supported)/sizeof(*supported),
+ supported);
+
+ xcb_flush(xwm->xcb_conn);
+
+ xwm_set_net_active_window(xwm, XCB_WINDOW_NONE);
+
+ xwm_selection_init(xwm);
+
+ xwm->compositor_new_surface.notify = handle_compositor_new_surface;
+ wl_signal_add(&wlr_xwayland->compositor->events.new_surface,
+ &xwm->compositor_new_surface);
+ xwm->compositor_destroy.notify = handle_compositor_destroy;
+ wl_signal_add(&wlr_xwayland->compositor->events.destroy,
+ &xwm->compositor_destroy);
+
+ xwm_create_wm_window(xwm);
+
+ xcb_flush(xwm->xcb_conn);
+
+ return xwm;
+}
+
+void wlr_xwayland_surface_set_maximized(struct wlr_xwayland_surface *surface,
+ bool maximized) {
+ surface->maximized_horz = maximized;
+ surface->maximized_vert = maximized;
+ xsurface_set_net_wm_state(surface);
+ xcb_flush(surface->xwm->xcb_conn);
+}
+
+void wlr_xwayland_surface_set_fullscreen(struct wlr_xwayland_surface *surface,
+ bool fullscreen) {
+ surface->fullscreen = fullscreen;
+ xsurface_set_net_wm_state(surface);
+ xcb_flush(surface->xwm->xcb_conn);
+}
+
+bool xwm_atoms_contains(struct wlr_xwm *xwm, xcb_atom_t *atoms,
+ size_t num_atoms, enum atom_name needle) {
+ xcb_atom_t atom = xwm->atoms[needle];
+
+ for (size_t i = 0; i < num_atoms; ++i) {
+ if (atom == atoms[i]) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void wlr_xwayland_surface_ping(struct wlr_xwayland_surface *surface) {
+ xcb_client_message_data_t data = { 0 };
+ data.data32[0] = surface->xwm->atoms[_NET_WM_PING];
+ data.data32[1] = XCB_CURRENT_TIME;
+ data.data32[2] = surface->window_id;
+
+ xwm_send_wm_message(surface, &data, XCB_EVENT_MASK_NO_EVENT);
+
+ wl_event_source_timer_update(surface->ping_timer,
+ surface->xwm->ping_timeout);
+ surface->pinging = true;
+}
+
+bool wlr_xwayland_or_surface_wants_focus(
+ const struct wlr_xwayland_surface *surface) {
+ bool ret = true;
+ static enum atom_name needles[] = {
+ NET_WM_WINDOW_TYPE_COMBO,
+ NET_WM_WINDOW_TYPE_DND,
+ NET_WM_WINDOW_TYPE_DROPDOWN_MENU,
+ NET_WM_WINDOW_TYPE_MENU,
+ NET_WM_WINDOW_TYPE_NOTIFICATION,
+ NET_WM_WINDOW_TYPE_POPUP_MENU,
+ NET_WM_WINDOW_TYPE_SPLASH,
+ NET_WM_WINDOW_TYPE_TOOLTIP,
+ NET_WM_WINDOW_TYPE_UTILITY,
+ };
+ for (size_t i = 0; i < sizeof(needles) / sizeof(needles[0]); ++i) {
+ if (xwm_atoms_contains(surface->xwm, surface->window_type,
+ surface->window_type_len, needles[i])) {
+ ret = false;
+ }
+ }
+
+ return ret;
+}