diff --git a/include/wlr/xwayland.h b/include/wlr/xwayland.h new file mode 100644 index 00000000..ef9cc887 --- /dev/null +++ b/include/wlr/xwayland.h @@ -0,0 +1,17 @@ +#ifndef _WLR_XWAYLAND_H +#define _WLR_XWAYLAND_H + +struct wlr_xwayland { + pid_t pid; + int display; + int x_fd[2], wl_fd[2], wm_fd[2]; + struct wl_client *client; + struct wl_display *wl_display; + time_t server_start; +}; + +void wlr_xwayland_finish(struct wlr_xwayland *wlr_xwayland); +bool wlr_xwayland_init(struct wlr_xwayland *wlr_xwayland, + struct wl_display *wl_display); + +#endif diff --git a/include/xwayland/internals.h b/include/xwayland/internals.h new file mode 100644 index 00000000..b91f7930 --- /dev/null +++ b/include/xwayland/internals.h @@ -0,0 +1,6 @@ +#ifndef XWAYLAND_INTERNALS_H +#define XWAYLAND_INTERNALS_H + +void unlink_sockets(int display); +int open_display_sockets(int socks[2]); +#endif diff --git a/meson.build b/meson.build index 4c449f53..b3706dbc 100644 --- a/meson.build +++ b/meson.build @@ -50,6 +50,7 @@ subdir('render') subdir('types') subdir('util') subdir('xcursor') +subdir('xwayland') wlr_deps = [ wayland_server, @@ -77,6 +78,7 @@ lib_wlr = library('wlroots', files('dummy.c'), lib_wlr_types, lib_wlr_util, lib_wlr_xcursor, + lib_wlr_xwayland, ], dependencies: wlr_deps, include_directories: wlr_inc) diff --git a/xwayland/meson.build b/xwayland/meson.build new file mode 100644 index 00000000..d43ace9b --- /dev/null +++ b/xwayland/meson.build @@ -0,0 +1,5 @@ +lib_wlr_xwayland = static_library('wlr_xwayland', files( + 'sockets.c', + 'xwayland.c', + ), + include_directories: wlr_inc) diff --git a/xwayland/sockets.c b/xwayland/sockets.c new file mode 100644 index 00000000..868d6ada --- /dev/null +++ b/xwayland/sockets.c @@ -0,0 +1,144 @@ +#define _XOPEN_SOURCE 700 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wlr/util/log.h" +#include "xwayland/internals.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_ERROR, "Failed to create socket %s", addr->sun_path); + 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_ERROR, "Failed to bind socket %s", addr->sun_path); + goto cleanup; + } + if (listen(fd, 1) < 0) { + rc = errno; + wlr_log_errno(L_ERROR, "Failed to listen to socket %s", addr->sun_path); + 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_sockets(int display) { + char sun_path[64]; + + snprintf(sun_path, sizeof(sun_path), socket_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)) { + 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/xwayland.c b/xwayland/xwayland.c new file mode 100644 index 00000000..49173ace --- /dev/null +++ b/xwayland/xwayland.c @@ -0,0 +1,170 @@ +#define _XOPEN_SOURCE 700 +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wlr/util/log.h" +#include "wlr/xwayland.h" +#include "xwayland/internals.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 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); + } + + char *argv[11] = { 0 }; + argv[0] = "Xwayland"; + if (asprintf(&argv[1], ":%d", wlr_xwayland->display) < 0) { + wlr_log_errno(L_ERROR, "asprintf failed"); + exit(EXIT_FAILURE); + } + argv[2] = "-rootless"; + argv[3] = "-terminate"; + argv[4] = "-listen"; + if (asprintf(&argv[5], "%d", wlr_xwayland->x_fd[0]) < 0) { + wlr_log_errno(L_ERROR, "asprintf failed"); + exit(EXIT_FAILURE); + } + argv[6] = "-listen"; + if (asprintf(&argv[7], "%d", wlr_xwayland->x_fd[1]) < 0) { + wlr_log_errno(L_ERROR, "asprintf failed"); + exit(EXIT_FAILURE); + } + argv[8] = "-wm"; + if (asprintf(&argv[9], "%d", wlr_xwayland->wm_fd[1]) < 0) { + wlr_log_errno(L_ERROR, "asprintf failed"); + 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); + } + + char *envp[3] = { 0 }; + if (asprintf(&envp[0], "XDG_RUNTIME_DIR=%s", xdg_runtime) < 0 || + asprintf(&envp[1], "WAYLAND_SOCKET=%d", wlr_xwayland->wl_fd[1]) < 0) { + wlr_log_errno(L_ERROR, "asprintf failed"); + exit(EXIT_FAILURE); + } + + wlr_log(L_INFO, "Xwayland :%d -rootless -terminate -listen %d -listen %d -wm %d", + wlr_xwayland->display, wlr_xwayland->x_fd[0], wlr_xwayland->x_fd[1], + wlr_xwayland->wm_fd[1]); + + execvpe("Xwayland", argv, envp); +} + +static void xwayland_destroy_event(struct wl_listener *listener, void *data) { + struct wl_client *client = data; + struct wlr_xwayland *wlr_xwayland = wl_container_of(client, wlr_xwayland, client); + + /* don't call client destroy */ + wlr_xwayland->client = NULL; + wlr_xwayland_finish(wlr_xwayland); + + if (wlr_xwayland->server_start - time(NULL) > 5) { + wlr_xwayland_init(wlr_xwayland, wlr_xwayland->wl_display); + } +} + +static struct wl_listener xwayland_destroy_listener = { + .notify = xwayland_destroy_event, +}; + +void wlr_xwayland_finish(struct wlr_xwayland *wlr_xwayland) { + + if (wlr_xwayland->client) { + wl_list_remove(&xwayland_destroy_listener.link); + wl_client_destroy(wlr_xwayland->client); + } + + 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_sockets(wlr_xwayland->display); + unsetenv("DISPLAY"); + /* kill Xwayland process? */ +} + +bool wlr_xwayland_init(struct wlr_xwayland *wlr_xwayland, + struct wl_display *wl_display) { + wlr_xwayland->wl_display = wl_display; + 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; + + 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; + + char display_name[16]; + snprintf(display_name, sizeof(display_name), ":%d", wlr_xwayland->display); + setenv("DISPLAY", display_name, true); + 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); + } + + wl_client_add_destroy_listener(wlr_xwayland->client, &xwayland_destroy_listener); + + return true; +}