aboutsummaryrefslogtreecommitdiff
path: root/xwayland
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2017-08-24 07:06:18 -0500
committerGitHub <noreply@github.com>2017-08-24 07:06:18 -0500
commitf10da8291b3f0d51b55262bd623511c8b8349a9e (patch)
treefebfff4ace5017dee7d6796eb1d1ac9f53604f1d /xwayland
parenta138657598efafbd3cfd7c77cb32ba294d0b841c (diff)
parentb29c7d01b19fc8c6bda70af016bc536cc72fe4f9 (diff)
Merge pull request #119 from martinetd/xwayland
Xwayland
Diffstat (limited to 'xwayland')
-rw-r--r--xwayland/meson.build7
-rw-r--r--xwayland/sockets.c153
-rw-r--r--xwayland/sockets.h7
-rw-r--r--xwayland/xwayland.c250
-rw-r--r--xwayland/xwm.c360
-rw-r--r--xwayland/xwm.h84
6 files changed, 861 insertions, 0 deletions
diff --git a/xwayland/meson.build b/xwayland/meson.build
new file mode 100644
index 00000000..dbad27bb
--- /dev/null
+++ b/xwayland/meson.build
@@ -0,0 +1,7 @@
+lib_wlr_xwayland = static_library('wlr_xwayland', files(
+ 'sockets.c',
+ 'xwayland.c',
+ 'xwm.c',
+ ),
+ include_directories: wlr_inc,
+ dependencies: [wayland_server, xcb, xcb_composite, pixman])
diff --git a/xwayland/sockets.c b/xwayland/sockets.c
new file mode 100644
index 00000000..48bcc822
--- /dev/null
+++ b/xwayland/sockets.c
@@ -0,0 +1,153 @@
+#define _XOPEN_SOURCE 700
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.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";
+
+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(L_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(L_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(L_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);
+
+ // TODO: non-linux apparently want another format
+ addr.sun_path[0] = 0;
+ path_size = snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, socket_fmt, display);
+ 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);
+
+ 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(L_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..211e2a04
--- /dev/null
+++ b/xwayland/xwayland.c
@@ -0,0 +1,250 @@
+#define _XOPEN_SOURCE 700
+#define _DEFAULT_SOURCE
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <wayland-server.h>
+#include "wlr/util/log.h"
+#include "wlr/xwayland.h"
+#include "sockets.h"
+#include "xwm.h"
+
+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(L_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;
+}
+
+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(L_ERROR, "alloc/print failure");
+ exit(EXIT_FAILURE);
+ }
+
+ const char *xdg_runtime = getenv("XDG_RUNTIME_DIR");
+ if (!xdg_runtime) {
+ wlr_log(L_ERROR, "XDG_RUNTIME_DIR is not set");
+ exit(EXIT_FAILURE);
+ }
+
+ if (clearenv()) {
+ wlr_log_errno(L_ERROR, "clearenv failed");
+ exit(EXIT_FAILURE);
+ }
+ setenv("XDG_RUNTIME_DIR", xdg_runtime, true);
+ 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(L_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]);
+
+ // TODO: close stdout/err depending on log level
+
+ execvp("Xwayland", argv);
+}
+
+static bool wlr_xwayland_init(struct wlr_xwayland *wlr_xwayland,
+ struct wl_display *wl_display, struct wlr_compositor *compositor);
+static void wlr_xwayland_finish(struct wlr_xwayland *wlr_xwayland);
+
+static void xwayland_destroy_event(struct wl_listener *listener, void *data) {
+ struct wlr_xwayland *wlr_xwayland = wl_container_of(listener, wlr_xwayland, destroy_listener);
+
+ /* don't call client destroy */
+ wlr_xwayland->client = NULL;
+ wl_list_remove(&wlr_xwayland->destroy_listener.link);
+ wlr_xwayland_finish(wlr_xwayland);
+
+ if (time(NULL) - wlr_xwayland->server_start > 5) {
+ wlr_xwayland_init(wlr_xwayland, wlr_xwayland->wl_display,
+ wlr_xwayland->compositor);
+ }
+}
+
+static void wlr_xwayland_finish(struct wlr_xwayland *wlr_xwayland) {
+ if (!wlr_xwayland || wlr_xwayland->display == -1) {
+ return;
+ }
+ if (wlr_xwayland->client) {
+ wl_list_remove(&wlr_xwayland->destroy_listener.link);
+ wl_client_destroy(wlr_xwayland->client);
+ }
+ if (wlr_xwayland->sigusr1_source) {
+ wl_event_source_remove(wlr_xwayland->sigusr1_source);
+ }
+
+ xwm_destroy(wlr_xwayland->xwm);
+
+ safe_close(wlr_xwayland->x_fd[0]);
+ safe_close(wlr_xwayland->x_fd[1]);
+ 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]);
+
+ unlink_display_sockets(wlr_xwayland->display);
+ wlr_xwayland->display = -1;
+ unsetenv("DISPLAY");
+ /* 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.
+ */
+ // TODO: figure how to wait for dying process though. Probably handle SIGCHILD
+}
+
+static int xserver_handle_ready(int signal_number, void *data) {
+ struct wlr_xwayland *wlr_xwayland = data;
+
+ wlr_log(L_DEBUG, "Xserver is ready");
+
+ wlr_xwayland->xwm = xwm_create(wlr_xwayland);
+ if (!wlr_xwayland->xwm) {
+ wlr_xwayland_finish(wlr_xwayland);
+ return 1;
+ }
+
+ wl_event_source_remove(wlr_xwayland->sigusr1_source);
+ wlr_xwayland->sigusr1_source = NULL;
+
+ char display_name[16];
+ snprintf(display_name, sizeof(display_name), ":%d", wlr_xwayland->display);
+ setenv("DISPLAY", display_name, true);
+
+ return 1; /* wayland event loop dispatcher's count */
+}
+
+static bool wlr_xwayland_init(struct wlr_xwayland *wlr_xwayland,
+ struct wl_display *wl_display, struct wlr_compositor *compositor) {
+ memset(wlr_xwayland, 0, sizeof(struct wlr_xwayland));
+ wlr_xwayland->wl_display = wl_display;
+ wlr_xwayland->compositor = compositor;
+ 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_list_init(&wlr_xwayland->displayable_windows);
+
+ wlr_xwayland->display = open_display_sockets(wlr_xwayland->x_fd);
+ if (wlr_xwayland->display < 0) {
+ wlr_xwayland_finish(wlr_xwayland);
+ return false;
+ }
+ 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(L_ERROR, "failed to create socketpair");
+ wlr_xwayland_finish(wlr_xwayland);
+ return false;
+ }
+
+ if ((wlr_xwayland->pid = fork()) == 0) {
+ exec_xwayland(wlr_xwayland);
+ wlr_log_errno(L_ERROR, "execvpe failed");
+ exit(EXIT_FAILURE);
+ }
+
+ if (wlr_xwayland->pid < 0) {
+ wlr_log_errno(L_ERROR, "fork failed");
+ wlr_xwayland_finish(wlr_xwayland);
+ return false;
+ }
+
+ /* close child fds */
+ close(wlr_xwayland->x_fd[0]);
+ close(wlr_xwayland->x_fd[1]);
+ close(wlr_xwayland->wl_fd[1]);
+ close(wlr_xwayland->wm_fd[1]);
+ wlr_xwayland->x_fd[0] = wlr_xwayland->x_fd[1] = -1;
+ wlr_xwayland->wl_fd[1] = wlr_xwayland->wm_fd[1] = -1;
+
+ wlr_xwayland->server_start = time(NULL);
+
+ if (!(wlr_xwayland->client = wl_client_create(wl_display, wlr_xwayland->wl_fd[0]))) {
+ wlr_log_errno(L_ERROR, "wl_client_create failed");
+ wlr_xwayland_finish(wlr_xwayland);
+ return false;
+ }
+ wlr_xwayland->wl_fd[0] = -1; /* not ours anymore */
+
+ wlr_xwayland->destroy_listener.notify = xwayland_destroy_event;
+ wl_client_add_destroy_listener(wlr_xwayland->client, &wlr_xwayland->destroy_listener);
+
+ struct wl_event_loop *loop = wl_display_get_event_loop(wl_display);
+ wlr_xwayland->sigusr1_source = wl_event_loop_add_signal(loop, SIGUSR1, xserver_handle_ready, wlr_xwayland);
+
+ return true;
+}
+
+void wlr_xwayland_destroy(struct wlr_xwayland *wlr_xwayland) {
+ wlr_xwayland_finish(wlr_xwayland);
+ free(wlr_xwayland);
+}
+
+struct wlr_xwayland *wlr_xwayland_create(struct wl_display *wl_display,
+ struct wlr_compositor *compositor) {
+ struct wlr_xwayland *wlr_xwayland = calloc(1, sizeof(struct wlr_xwayland));
+ if (wlr_xwayland_init(wlr_xwayland, wl_display, compositor)) {
+ return wlr_xwayland;
+ }
+ free(wlr_xwayland);
+ return NULL;
+}
diff --git a/xwayland/xwm.c b/xwayland/xwm.c
new file mode 100644
index 00000000..3fa6cb98
--- /dev/null
+++ b/xwayland/xwm.c
@@ -0,0 +1,360 @@
+#include <stdlib.h>
+#include <xcb/composite.h>
+#include "wlr/util/log.h"
+#include "wlr/types/wlr_surface.h"
+#include "wlr/xwayland.h"
+#include "xwm.h"
+
+/* General helpers */
+// TODO: replace this with hash table?
+static struct wlr_x11_window *lookup_window(struct wl_list *list, xcb_window_t window_id) {
+ struct wlr_x11_window *window;
+ wl_list_for_each(window, list, link) {
+ if (window->window_id == window_id) {
+ return window;
+ }
+ }
+ return NULL;
+}
+static struct wlr_x11_window *lookup_window_any(struct wlr_xwm *xwm, xcb_window_t window_id) {
+ struct wlr_x11_window *window;
+ if ((window = lookup_window(&xwm->xwayland->displayable_windows, window_id)) ||
+ (window = lookup_window(&xwm->unpaired_windows, window_id)) ||
+ (window = lookup_window(&xwm->new_windows, window_id))) {
+ return window;
+ }
+ return NULL;
+}
+
+static struct wlr_x11_window *wlr_x11_window_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_x11_window *window;
+
+ window = calloc(1, sizeof(struct wlr_x11_window));
+ if (!window) {
+ wlr_log(L_ERROR, "Could not allocate wlr x11 window");
+ return NULL;
+ }
+ window->window_id = window_id;
+ window->x = x;
+ window->y = y;
+ window->width = width;
+ window->height = height;
+ window->override_redirect = override_redirect;
+ wl_list_insert(&xwm->new_windows, &window->link);
+ return window;
+}
+
+static void wlr_x11_window_destroy(struct wlr_x11_window *window) {
+ wl_list_remove(&window->link);
+ free(window);
+}
+
+/* xcb helpers */
+#define XCB_CALL(xwm, x) xcb_call(xwm, __PRETTY_FUNCTION__, __LINE__, x)
+static bool xcb_call(struct wlr_xwm *xwm, const char *func, uint32_t line,
+ xcb_void_cookie_t cookie) {
+ xcb_generic_error_t *error;
+ if (!(error = xcb_request_check(xwm->xcb_conn, cookie))) {
+ return true;
+ }
+
+ wlr_log(L_ERROR, "xcb call failed in %s:%u, x11 error code %d",
+ func, line, error->error_code);
+ free(error);
+ return false;
+}
+
+static void map_shell_surface(struct wlr_xwm *xwm, struct wlr_x11_window *window,
+ struct wlr_surface *surface) {
+ // get xcb geometry for depth = alpha channel
+ window->surface = surface->resource;
+
+ wl_list_remove(&window->link);
+ wl_list_insert(&xwm->xwayland->displayable_windows, &window->link);
+}
+
+/* xcb event handlers */
+static void handle_create_notify(struct wlr_xwm *xwm, xcb_create_notify_event_t *ev) {
+ wlr_log(L_DEBUG, "XCB_CREATE_NOTIFY (%u)", ev->window);
+ wlr_x11_window_create(xwm, ev->window, ev->x, ev->y,
+ ev->width, ev->height, ev->override_redirect);
+}
+
+static void handle_destroy_notify(struct wlr_xwm *xwm, xcb_destroy_notify_event_t *ev) {
+ struct wlr_x11_window *window;
+ wlr_log(L_DEBUG, "XCB_DESTROY_NOTIFY (%u)", ev->window);
+ if (!(window = lookup_window_any(xwm, ev->window))) {
+ return;
+ }
+ wlr_x11_window_destroy(window);
+}
+
+static void handle_configure_request(struct wlr_xwm *xwm, xcb_configure_request_event_t *ev) {
+ struct wlr_x11_window *window;
+ wlr_log(L_DEBUG, "XCB_CONFIGURE_REQUEST (%u) [%ux%u+%d,%d]", ev->window,
+ ev->width, ev->height, ev->x, ev->y);
+ if (!(window = lookup_window_any(xwm, ev->window))) {
+ return;
+ }
+
+ window->x = ev->x;
+ window->y = ev->y;
+ window->width = ev->width;
+ window->height = ev->height;
+ // handle parent/sibling?
+
+ uint32_t values[] = { ev->x, ev->y, ev->width, ev->height, 0 };
+ uint32_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
+ XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
+ XCB_CONFIG_WINDOW_BORDER_WIDTH;
+ xcb_configure_window(xwm->xcb_conn, ev->window, mask, values);
+}
+
+static void handle_map_request(struct wlr_xwm *xwm, xcb_map_request_event_t *ev) {
+ wlr_log(L_DEBUG, "XCB_MAP_REQUEST (%u)", ev->window);
+ XCB_CALL(xwm, xcb_change_window_attributes_checked(xwm->xcb_conn,
+ ev->window, XCB_CW_EVENT_MASK,
+ &(uint32_t){XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE}));
+ XCB_CALL(xwm, xcb_map_window_checked(xwm->xcb_conn, ev->window));
+}
+
+static void handle_map_notify(struct wlr_xwm *xwm, xcb_map_notify_event_t *ev) {
+ struct wlr_x11_window *window;
+ wlr_log(L_DEBUG, "XCB_MAP_NOTIFY (%u)", ev->window);
+ if ((window = lookup_window_any(xwm, ev->window))) {
+ window->override_redirect = ev->override_redirect;
+ } else {
+ wlr_x11_window_create(xwm, ev->window, 0, 0, 1, 1, ev->override_redirect);
+ }
+}
+
+static void handle_unmap_notify(struct wlr_xwm *xwm, xcb_unmap_notify_event_t *ev) {
+ struct wlr_x11_window *window;
+ wlr_log(L_DEBUG, "XCB_UNMAP_NOTIFY (%u)", ev->window);
+ if (!(window = lookup_window_any(xwm, ev->window))) {
+ return;
+ }
+ // remove pointer to surface only?
+ wlr_x11_window_destroy(window);
+}
+
+static void handle_property_notify(struct wlr_xwm *xwm, xcb_property_notify_event_t *ev) {
+ wlr_log(L_DEBUG, "XCB_PROPERTY_NOTIFY (%u)", ev->window);
+ // TODO lookup window & get properties
+}
+
+static void handle_client_message(struct wlr_xwm *xwm, xcb_client_message_event_t *ev) {
+ wlr_log(L_DEBUG, "XCB_CLIENT_MESSAGE (%u)", ev->window);
+
+ if (ev->type == xwm->atoms[WL_SURFACE_ID]) {
+ struct wlr_x11_window *window;
+ struct wl_resource *resource;
+ window = lookup_window(&xwm->new_windows, ev->window);
+ if (!window) {
+ wlr_log(L_DEBUG, "client message WL_SURFACE_ID but no new window %u ?",
+ ev->window);
+ return;
+ }
+ window->surface_id = ev->data.data32[0];
+ /* Check if we got notified after wayland surface create event */
+ resource = wl_client_get_object(xwm->xwayland->client, window->surface_id);
+ if (resource) {
+ map_shell_surface(xwm, window, wl_resource_get_user_data(resource));
+ } else {
+ wl_list_remove(&window->link);
+ wl_list_insert(&xwm->unpaired_windows, &window->link);
+ }
+ }
+ wlr_log(L_DEBUG, "unhandled client message %u", ev->type);
+}
+
+/* This is in xcb/xcb_event.h, but pulling xcb-util just for a constant
+ * others redefine anyway is meh
+ */
+#define XCB_EVENT_RESPONSE_TYPE_MASK (0x7f)
+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++;
+ switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) {
+ case XCB_CREATE_NOTIFY:
+ handle_create_notify(xwm, (xcb_create_notify_event_t *)event);
+ break;
+ case XCB_DESTROY_NOTIFY:
+ handle_destroy_notify(xwm, (xcb_destroy_notify_event_t *)event);
+ break;
+ case XCB_CONFIGURE_REQUEST:
+ handle_configure_request(xwm, (xcb_configure_request_event_t *)event);
+ break;
+ case XCB_MAP_REQUEST:
+ handle_map_request(xwm, (xcb_map_request_event_t *)event);
+ break;
+ case XCB_MAP_NOTIFY:
+ handle_map_notify(xwm, (xcb_map_notify_event_t *)event);
+ break;
+ case XCB_UNMAP_NOTIFY:
+ handle_unmap_notify(xwm, (xcb_unmap_notify_event_t *)event);
+ break;
+ case XCB_PROPERTY_NOTIFY:
+ handle_property_notify(xwm, (xcb_property_notify_event_t *)event);
+ break;
+ case XCB_CLIENT_MESSAGE:
+ handle_client_message(xwm, (xcb_client_message_event_t *)event);
+ break;
+ default:
+ wlr_log(L_DEBUG, "X11 event: %d",
+ event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK);
+ break;
+ }
+ }
+
+ xcb_flush(xwm->xcb_conn);
+ return count;
+}
+
+static void create_surface_handler(struct wl_listener *listener, void *data) {
+ struct wlr_surface *surface = data;
+ struct wlr_xwm *xwm = wl_container_of(listener, xwm, surface_create_listener);
+ struct wlr_x11_window *window;
+ uint32_t surface_id;
+
+ if (wl_resource_get_client(surface->resource) != xwm->xwayland->client) {
+ return;
+ }
+
+ wlr_log(L_DEBUG, "New x11 surface: %p", surface);
+
+ surface_id = wl_resource_get_id(surface->resource);
+ wl_list_for_each(window, &xwm->unpaired_windows, link) {
+ if (window->surface_id == surface_id) {
+ map_shell_surface(xwm, window, surface);
+ xcb_flush(xwm->xcb_conn);
+ return;
+ }
+ }
+}
+
+static void xcb_get_resources(struct wlr_xwm *xwm) {
+ 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_intern_atom_reply_t *reply;
+ xcb_generic_error_t *error;
+
+ reply = xcb_intern_atom_reply(xwm->xcb_conn, cookies[i], &error);
+
+ if (reply && !error) {
+ xwm->atoms[i] = reply->atom;
+ }
+ if (reply) {
+ free(reply);
+ }
+ if (error) {
+ wlr_log(L_ERROR, "could not resolve atom %s, x11 error code %d",
+ atom_map[i], error->error_code);
+ free(error);
+ return;
+ }
+ }
+}
+
+static void xcb_init_wm(struct wlr_xwm *xwm) {
+ xcb_screen_iterator_t screen_iterator;
+ screen_iterator = xcb_setup_roots_iterator(xcb_get_setup(xwm->xcb_conn));
+ xwm->screen = screen_iterator.data;
+
+ xwm->window = xcb_generate_id(xwm->xcb_conn);
+
+ uint32_t values[] = {
+ XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
+ XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
+ XCB_EVENT_MASK_PROPERTY_CHANGE,
+ /* xwm->cursor, */
+ };
+ XCB_CALL(xwm, xcb_change_window_attributes_checked(xwm->xcb_conn, xwm->screen->root,
+ XCB_CW_EVENT_MASK /* | XCB_CW_CURSOR */, values));
+ XCB_CALL(xwm, xcb_composite_redirect_subwindows_checked(xwm->xcb_conn,
+ xwm->screen->root, XCB_COMPOSITE_REDIRECT_MANUAL));
+
+ XCB_CALL(xwm, xcb_create_window_checked(xwm->xcb_conn, XCB_COPY_FROM_PARENT,
+ xwm->window, xwm->screen->root, 0, 0, 1, 1, 0,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT, xwm->screen->root_visual,
+ XCB_CW_EVENT_MASK, (uint32_t[]){XCB_EVENT_MASK_PROPERTY_CHANGE}));
+ xcb_atom_t supported[] = {
+ xwm->atoms[NET_WM_STATE],
+ };
+ XCB_CALL(xwm, xcb_change_property_checked(xwm->xcb_conn, XCB_PROP_MODE_REPLACE,
+ xwm->screen->root, xwm->atoms[NET_SUPPORTED], XCB_ATOM_ATOM,
+ 32, sizeof(supported)/sizeof(*supported), supported));
+
+ XCB_CALL(xwm, xcb_set_selection_owner_checked(xwm->xcb_conn, xwm->window,
+ xwm->atoms[WM_S0], XCB_CURRENT_TIME));
+ XCB_CALL(xwm, xcb_set_selection_owner_checked(xwm->xcb_conn, xwm->window,
+ xwm->atoms[NET_WM_S0], XCB_CURRENT_TIME));
+ xcb_flush(xwm->xcb_conn);
+}
+
+void xwm_destroy(struct wlr_xwm *xwm) {
+ if (!xwm) {
+ return;
+ }
+ if (xwm->event_source) {
+ wl_event_source_remove(xwm->event_source);
+ }
+ struct wlr_x11_window *window, *tmp;
+ wl_list_for_each_safe(window, tmp, &xwm->xwayland->displayable_windows, link) {
+ wlr_x11_window_destroy(window);
+ }
+ wl_list_for_each_safe(window, tmp, &xwm->new_windows, link) {
+ wlr_x11_window_destroy(window);
+ }
+ wl_list_for_each_safe(window, tmp, &xwm->unpaired_windows, link) {
+ wlr_x11_window_destroy(window);
+ }
+ wl_list_remove(&xwm->surface_create_listener.link);
+ xcb_disconnect(xwm->xcb_conn);
+
+ free(xwm);
+}
+
+struct wlr_xwm *xwm_create(struct wlr_xwayland *wlr_xwayland) {
+ struct wlr_xwm *xwm = calloc(1, sizeof(struct wlr_xwm));
+ int rc;
+
+ xwm->xwayland = wlr_xwayland;
+ wl_list_init(&xwm->new_windows);
+ wl_list_init(&xwm->unpaired_windows);
+
+ xwm->xcb_conn = xcb_connect_to_fd(wlr_xwayland->wm_fd[0], NULL);
+ if ((rc = xcb_connection_has_error(xwm->xcb_conn))) {
+ wlr_log(L_ERROR, "xcb connect failed: %d", rc);
+ free(xwm);
+ return NULL;
+ }
+
+ 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);
+ // probably not needed
+ // wl_event_source_check(xwm->event_source);
+
+ // TODO more xcb init
+ // xcb_prefetch_extension_data(xwm->xcb_conn, &xcb_composite_id);
+ xcb_get_resources(xwm);
+ xcb_init_wm(xwm);
+
+ xwm->surface_create_listener.notify = create_surface_handler;
+ wl_signal_add(&wlr_xwayland->compositor->events.create_surface,
+ &xwm->surface_create_listener);
+
+ return xwm;
+}
diff --git a/xwayland/xwm.h b/xwayland/xwm.h
new file mode 100644
index 00000000..235145b9
--- /dev/null
+++ b/xwayland/xwm.h
@@ -0,0 +1,84 @@
+#ifndef XWAYLAND_INTERNALS_H
+#define XWAYLAND_INTERNALS_H
+#include <wayland-server-core.h>
+#include <wlr/xwayland.h>
+
+/* wlc's atom list:
+ WL_SURFACE_ID,
+ WM_DELETE_WINDOW,
+ WM_TAKE_FOCUS,
+ WM_PROTOCOLS,
+ WM_NORMAL_HINTS,
+ MOTIF_WM_HINTS,
+ TEXT,
+ UTF8_STRING,
+ CLIPBOARD,
+ CLIPBOARD_MANAGER,
+ TARGETS,
+ PRIMARY,
+ WM_S0,
+ STRING,
+ WLC_SELECTION,
+ NET_WM_S0,
+ NET_WM_PID,
+ NET_WM_NAME,
+ NET_WM_STATE,
+ NET_WM_STATE_FULLSCREEN,
+ NET_WM_STATE_MODAL,
+ NET_WM_STATE_ABOVE,
+ NET_SUPPORTED,
+ NET_SUPPORTING_WM_CHECK,
+ NET_WM_WINDOW_TYPE,
+ NET_WM_WINDOW_TYPE_DESKTOP,
+ NET_WM_WINDOW_TYPE_DOCK,
+ NET_WM_WINDOW_TYPE_TOOLBAR,
+ NET_WM_WINDOW_TYPE_MENU,
+ NET_WM_WINDOW_TYPE_UTILITY,
+ NET_WM_WINDOW_TYPE_SPLASH,
+ NET_WM_WINDOW_TYPE_DIALOG,
+ NET_WM_WINDOW_TYPE_DROPDOWN_MENU,
+ NET_WM_WINDOW_TYPE_POPUP_MENU,
+ NET_WM_WINDOW_TYPE_TOOLTIP,
+ NET_WM_WINDOW_TYPE_NOTIFICATION,
+ NET_WM_WINDOW_TYPE_COMBO,
+ NET_WM_WINDOW_TYPE_DND,
+ NET_WM_WINDOW_TYPE_NORMAL,
+ */
+
+enum atom_name {
+ WL_SURFACE_ID,
+ WM_PROTOCOLS,
+ WM_S0,
+ NET_SUPPORTED,
+ NET_WM_S0,
+ NET_WM_STATE,
+ ATOM_LAST
+};
+
+static const char * const atom_map[ATOM_LAST] = {
+ "WL_SURFACE_ID",
+ "WM_PROTOCOLS",
+ "WM_S0",
+ "_NET_SUPPORTED",
+ "_NET_WM_S0",
+ "_NET_WM_STATE",
+};
+
+struct wlr_xwm {
+ struct wlr_xwayland *xwayland;
+ struct wl_event_source *event_source;
+ struct wl_listener surface_create_listener;
+
+ xcb_atom_t atoms[ATOM_LAST];
+ xcb_connection_t *xcb_conn;
+ xcb_screen_t *screen;
+ xcb_window_t window;
+
+ struct wl_list new_windows;
+ struct wl_list unpaired_windows;
+};
+
+void xwm_destroy(struct wlr_xwm *xwm);
+struct wlr_xwm *xwm_create(struct wlr_xwayland *wlr_xwayland);
+
+#endif