diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 974346aee..7ec060139 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,7 +19,7 @@ variables: FDO_UPSTREAM_REPO: xorg/xserver FDO_DISTRIBUTION_VERSION: bullseye-slim FDO_DISTRIBUTION_EXEC: 'env FDO_CI_CONCURRENT=${FDO_CI_CONCURRENT} bash .gitlab-ci/debian-install.sh' - FDO_DISTRIBUTION_TAG: "2023-06-18-xorgproto-2023.2" + FDO_DISTRIBUTION_TAG: "2023-06-20-libei.0" include: - project: 'freedesktop/ci-templates' diff --git a/.gitlab-ci/debian-install.sh b/.gitlab-ci/debian-install.sh index b9a35e66e..622067520 100644 --- a/.gitlab-ci/debian-install.sh +++ b/.gitlab-ci/debian-install.sh @@ -99,6 +99,8 @@ apt-get install -y \ mingw-w64-tools \ nettle-dev \ pkg-config \ + python3-attr \ + python3-jinja2 \ python3-mako \ python3-numpy \ python3-six \ @@ -161,6 +163,14 @@ ninja -C _build -j${FDO_CI_CONCURRENT:-4} install cd .. rm -rf libdecor +# Install libei for Xwayland +git clone https://gitlab.freedesktop.org/libinput/libei.git --depth 1 --branch=1.0.0 +cd libei +meson setup _build -Dtests=disabled -Ddocumentation=[] -Dliboeffis=enabled +ninja -C _build -j${FDO_CI_CONCURRENT:-4} install +cd .. +rm -rf libei + git clone https://gitlab.freedesktop.org/mesa/piglit.git cd piglit git checkout 265896c86f90cb72e8f218ba6a3617fca8b9a1e3 diff --git a/hw/xwayland/meson.build b/hw/xwayland/meson.build index 8b3d7595a..f2038ab7c 100644 --- a/hw/xwayland/meson.build +++ b/hw/xwayland/meson.build @@ -92,6 +92,15 @@ srcs += code.process(shortcuts_inhibit_xml) srcs += code.process(xwayland_shell_xml) srcs += code.process(tearing_xml) +if build_ei + xwayland_dep += libei_dep + srcs += [ 'xwayland-xtest.c', 'xwayland-xtest.h' ] + + if build_ei_portal + xwayland_dep += liboeffis_dep + endif +endif + xwayland_glamor = [] eglstream_srcs = [] if build_glamor diff --git a/hw/xwayland/xwayland-input.c b/hw/xwayland/xwayland-input.c index 6e0600e4e..d6fda338d 100644 --- a/hw/xwayland/xwayland-input.c +++ b/hw/xwayland/xwayland-input.c @@ -44,6 +44,10 @@ #include "xwayland-window.h" #include "xwayland-screen.h" +#ifdef XWL_HAS_EI +#include "xwayland-xtest.h" +#endif + #include "pointer-constraints-unstable-v1-client-protocol.h" #include "relative-pointer-unstable-v1-client-protocol.h" #include "tablet-unstable-v2-client-protocol.h" @@ -3492,6 +3496,10 @@ InitInput(int argc, char *argv[]) pScreen->XYToWindow = xwl_xy_to_window; xwl_screen_roundtrip(xwl_screen); +#ifdef XWL_HAS_EI + if (xwl_screen->rootless) + xwayland_override_xtest(); +#endif } void diff --git a/hw/xwayland/xwayland-screen.c b/hw/xwayland/xwayland-screen.c index 41e7393eb..0ac44e17b 100644 --- a/hw/xwayland/xwayland-screen.c +++ b/hw/xwayland/xwayland-screen.c @@ -51,6 +51,9 @@ #include "xwayland-pixmap.h" #include "xwayland-present.h" #include "xwayland-shm.h" +#ifdef XWL_HAS_EI +#include "xwayland-xtest.h" +#endif #ifdef MITSHM #include "shmint.h" @@ -748,6 +751,11 @@ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv) dixSetPrivate(&pScreen->devPrivates, &xwl_screen_private_key, xwl_screen); xwl_screen->screen = pScreen; +#ifdef XWL_HAS_EI + if (!xwayland_ei_init()) + return FALSE; +#endif + #ifdef XWL_HAS_GLAMOR xwl_screen->glamor = 1; #endif diff --git a/hw/xwayland/xwayland-xtest.c b/hw/xwayland/xwayland-xtest.c new file mode 100644 index 000000000..9a61cbad7 --- /dev/null +++ b/hw/xwayland/xwayland-xtest.c @@ -0,0 +1,842 @@ +/* + * Copyright © 2020 Red Hat + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation, and that the name of the + * copyright holders not be used in advertising or publicity + * pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +#include + +#include +#include +#include +#include + +#include + +#ifdef XWL_HAS_EI_PORTAL +#include "liboeffis.h" +#endif + +#include "xwayland-screen.h" +#include "xwayland-xtest.h" + +#define debug_ei(...) DebugF("[xwayland ei] " __VA_ARGS__) +#define error_ei(...) ErrorF("[xwayland ei] " __VA_ARGS__) + +#define SCROLL_STEP 120 /* libei's definition of a logical scroll step */ + +static struct xorg_list clients_for_reuse; + +static DevPrivateKeyRec xwl_ei_private_key; + +struct xwl_emulated_event { + int type; + int detail; + int flags; + ValuatorMask mask; + struct xorg_list link; +}; + +struct xwl_abs_device { + struct xorg_list link; + struct ei_device *device; +}; + +struct xwl_ei_client { + struct xorg_list link; /* in clients_for_reuse */ + ClientPtr client; /* can be NULL if the X11 client is gone */ + char *cmdline; + bool accept_pointer, accept_keyboard, accept_abs; + struct ei *ei; + int ei_fd; +#ifdef XWL_HAS_EI_PORTAL + struct oeffis *oeffis; + int oeffis_fd; +#endif + struct ei_seat *ei_seat; + struct ei_device *ei_pointer; + struct ei_device *ei_keyboard; + struct xorg_list abs_devices; + struct xorg_list pending_emulated_events; + + OsTimerPtr disconnect_timer; +}; + +static void xwl_handle_ei_event(int fd, int ready, void *data); + +static struct xwl_ei_client * +get_xwl_ei_client(ClientPtr client) +{ + return dixLookupPrivate(&client->devPrivates, &xwl_ei_private_key); +} + +static void +xwl_queue_emulated_event(struct xwl_ei_client *xwl_ei_client, + int type, int detail, int flags, const ValuatorMask *mask) +{ + struct xwl_emulated_event *xwl_emulated_event; + + xwl_emulated_event = calloc(1, sizeof *xwl_emulated_event); + if (!xwl_emulated_event) { + error_ei("OOM, cannot queue event\n"); + return; + } + + xwl_emulated_event->type = type; + xwl_emulated_event->detail = detail; + xwl_emulated_event->flags = flags; + valuator_mask_copy(&xwl_emulated_event->mask, mask); + + xorg_list_append(&xwl_emulated_event->link, + &xwl_ei_client->pending_emulated_events); +} + +static void +xwl_clear_emulated_events(struct xwl_ei_client *xwl_ei_client) +{ + struct xwl_emulated_event *xwl_emulated_event, *next_xwl_emulated_event; + + xorg_list_for_each_entry_safe(xwl_emulated_event, next_xwl_emulated_event, + &xwl_ei_client->pending_emulated_events, link) { + xorg_list_del(&xwl_emulated_event->link); + free(xwl_emulated_event); + } +} + +static void +add_ei_device(struct xwl_ei_client *xwl_ei_client, struct ei_device *device) +{ + bool used = true; + + /* Note: pointers in libei are split across four capabilities: + pointer/pointer-absolute/button/scroll. We expect any decent + compositor to give pointers the button + scroll interfaces too, + if that's not the case we can look into *why* and fix this as needed. + Meanwhile, we ignore any device that doesn't have button + scroll + in addition to pointer caps. + */ + + if (ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) && + ei_device_has_capability(device, EI_DEVICE_CAP_BUTTON) && + ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL) && + xwl_ei_client->ei_pointer == NULL) { + + xwl_ei_client->ei_pointer = ei_device_ref(device); + used = true; + } + + if (ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD) && + xwl_ei_client->ei_keyboard == NULL) { + xwl_ei_client->ei_keyboard = ei_device_ref(device); + used = true; + } + + if (ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE) && + ei_device_has_capability(device, EI_DEVICE_CAP_BUTTON) && + ei_device_has_capability(device, EI_DEVICE_CAP_SCROLL)) { + struct xwl_abs_device *abs = calloc(1, sizeof(*abs)); + + if (abs) { + xorg_list_add(&abs->link, &xwl_ei_client->abs_devices); + abs->device = ei_device_ref(device); + used = true; + } + } + + if (!used) + ei_device_close(device); +} + +static void +free_oeffis(struct xwl_ei_client *xwl_ei_client) +{ +#ifdef XWL_HAS_EI_PORTAL + if (xwl_ei_client->oeffis) { + debug_ei("Removing OEFFIS fd=%d\n", xwl_ei_client->oeffis_fd); + if (xwl_ei_client->oeffis_fd >= 0) + RemoveNotifyFd(xwl_ei_client->oeffis_fd); + xwl_ei_client->oeffis = oeffis_unref(xwl_ei_client->oeffis); + } +#endif +} + +static void +free_ei(struct xwl_ei_client *xwl_ei_client) +{ + struct ei *ei = xwl_ei_client->ei; + struct xwl_abs_device *abs, *tmp; + ClientPtr client = xwl_ei_client->client; + + xorg_list_del(&xwl_ei_client->link); + + debug_ei("Removing EI fd=%d\n", xwl_ei_client->ei_fd); + if (xwl_ei_client->ei_fd >= 0) + RemoveNotifyFd(xwl_ei_client->ei_fd); + ei_device_unref(xwl_ei_client->ei_pointer); + ei_device_unref(xwl_ei_client->ei_keyboard); + xorg_list_for_each_entry_safe(abs, tmp, &xwl_ei_client->abs_devices, link) { + xorg_list_del(&abs->link); + ei_device_unref(abs->device); + free(abs); + } + + xwl_clear_emulated_events(xwl_ei_client); + if (client) + dixSetPrivate(&client->devPrivates, &xwl_ei_private_key, NULL); + + free_oeffis(xwl_ei_client); + + ei_seat_unref(xwl_ei_client->ei_seat); + ei_unref(ei); + + free(xwl_ei_client->cmdline); + free(xwl_ei_client); +} + +#ifdef XWL_HAS_EI_PORTAL +static void +setup_ei_from_oeffis(struct xwl_ei_client *xwl_ei_client) +{ + struct oeffis *oeffis = xwl_ei_client->oeffis; + + xwl_ei_client->ei_fd = oeffis_get_eis_fd(oeffis); + if (xwl_ei_client->ei_fd < 0) { + error_ei("Failed to setup EI file descriptor from oeffis\n"); + return; + } + ei_setup_backend_fd(xwl_ei_client->ei, xwl_ei_client->ei_fd); + SetNotifyFd(xwl_ei_client->ei_fd, xwl_handle_ei_event, + X_NOTIFY_READ, xwl_ei_client); +} + +static void +xwl_handle_oeffis_event(int fd, int ready, void *data) +{ + struct xwl_ei_client *xwl_ei_client = data; + struct oeffis *oeffis = xwl_ei_client->oeffis; + enum oeffis_event_type event_type; + bool done = false; + + oeffis_dispatch(oeffis); + + do { + event_type = oeffis_get_event(oeffis); + switch (event_type) { + case OEFFIS_EVENT_NONE: + debug_ei("OEFFIS event none\n"); + done = true; + break; + case OEFFIS_EVENT_CONNECTED_TO_EIS: + debug_ei("OEFFIS connected to EIS\n"); + setup_ei_from_oeffis(xwl_ei_client); + break; + case OEFFIS_EVENT_DISCONNECTED: + debug_ei("OEFFIS disconnected: %s\n", + oeffis_get_error_message(oeffis)); + free_ei(xwl_ei_client); + done = true; + break; + case OEFFIS_EVENT_CLOSED: + debug_ei("OEFFIS closed\n"); + free_ei(xwl_ei_client); + done = true; + break; + } + } + while (!done); +} +#endif + +static bool +setup_oeffis(struct xwl_ei_client *xwl_ei_client) +{ +#ifdef XWL_HAS_EI_PORTAL + xwl_ei_client->oeffis_fd = -1; + xwl_ei_client->oeffis = oeffis_new(NULL); + if (!xwl_ei_client->oeffis) + return false; + + xwl_ei_client->oeffis_fd = oeffis_get_fd(xwl_ei_client->oeffis); + if (xwl_ei_client->oeffis_fd < 0) { + error_ei("Failed to setup OEFFIS file descriptor\n"); + return false; + } + + SetNotifyFd(xwl_ei_client->oeffis_fd, xwl_handle_oeffis_event, + X_NOTIFY_READ, xwl_ei_client); + + oeffis_create_session(xwl_ei_client->oeffis, OEFFIS_DEVICE_ALL_DEVICES); + + return true; +#else + return false; +#endif +} + +static bool +setup_ei_from_socket(struct xwl_ei_client *xwl_ei_client) +{ + int rc; + + rc = ei_setup_backend_socket(xwl_ei_client->ei, NULL); + + if (rc != 0) { + error_ei("Setup failed: %s\n", strerror(-rc)); + return false; + } + + xwl_ei_client->ei_fd = ei_get_fd(xwl_ei_client->ei); + if (xwl_ei_client->ei_fd < 0) { + error_ei("Failed to setup EI file descriptor from socket\n"); + return false; + } + + SetNotifyFd(xwl_ei_client->ei_fd, xwl_handle_ei_event, + X_NOTIFY_READ, xwl_ei_client); + + return true; +} + +static struct xwl_ei_client * +setup_ei(ClientPtr client) +{ + struct xwl_ei_client *xwl_ei_client = NULL; + struct ei *ei = NULL; + char buffer[PATH_MAX]; + const char *cmdname; + char *client_name = NULL; + bool status; + + cmdname = GetClientCmdName(client); + if (cmdname) { + snprintf(buffer, sizeof(buffer) - 1, "%s", cmdname); + client_name = basename(buffer); + } + + if (!client_name) { + error_ei("Failed to retrieve the client command line name\n"); + goto out; + } + + xwl_ei_client = calloc(1, sizeof *xwl_ei_client); + xwl_ei_client->cmdline = xstrdup(cmdname); + if (!xwl_ei_client) { + error_ei("OOM, cannot setup EI\n"); + goto out; + } + xorg_list_init(&xwl_ei_client->link); + + ei = ei_new(NULL); + ei_configure_name(ei, basename(client_name)); + + /* We can't send events to EIS until we have a device and the device + * is resumed. + */ + xwl_ei_client->accept_pointer = false; + xwl_ei_client->accept_keyboard = false; + xwl_ei_client->accept_abs = false; + xwl_ei_client->ei = ei; + xwl_ei_client->ei_fd = -1; + xwl_ei_client->client = client; + xorg_list_init(&xwl_ei_client->pending_emulated_events); + xorg_list_init(&xwl_ei_client->abs_devices); + + status = setup_oeffis(xwl_ei_client); + if (!status) + status = setup_ei_from_socket(xwl_ei_client); + + if (!status) { + free(xwl_ei_client); + xwl_ei_client = NULL; + ei_unref(ei); + error_ei("EI setup failed\n"); + } + + out: + return xwl_ei_client; +} + +static CARD32 +disconnect_timer_cb(OsTimerPtr timer, CARD32 time, void *arg) +{ + struct xwl_ei_client *xwl_ei_client = arg; + + free_ei(xwl_ei_client); + + return 0; +} + +static void +xwl_ei_start_emulating(struct xwl_ei_client *xwl_ei_client) +{ + static uint32_t sequence = 0; + struct xwl_abs_device *abs; + + sequence++; + if (xwl_ei_client->ei_pointer) + ei_device_start_emulating(xwl_ei_client->ei_pointer, sequence); + if (xwl_ei_client->ei_keyboard) + ei_device_start_emulating(xwl_ei_client->ei_keyboard, sequence); + xorg_list_for_each_entry(abs, &xwl_ei_client->abs_devices, link) { + ei_device_start_emulating(abs->device, sequence); + } +} + +static void +xwl_ei_stop_emulating(struct xwl_ei_client *xwl_ei_client) +{ + struct xwl_abs_device *abs; + + if (xwl_ei_client->ei_pointer) + ei_device_stop_emulating(xwl_ei_client->ei_pointer); + if (xwl_ei_client->ei_keyboard) + ei_device_stop_emulating(xwl_ei_client->ei_keyboard); + xorg_list_for_each_entry(abs, &xwl_ei_client->abs_devices, link) { + ei_device_stop_emulating(abs->device); + } +} + +static void +xwl_ei_handle_client_gone(struct xwl_ei_client *xwl_ei_client) +{ + ClientPtr client = xwl_ei_client->client; + + /* Make this EI client struct re-usable. xdotool only exists for a + * fraction of a second, so let's make it re-use the same client every + * time - this makes it easier to e.g. pause it */ + xorg_list_add(&xwl_ei_client->link, &clients_for_reuse); + + if (xorg_list_is_empty(&xwl_ei_client->pending_emulated_events)) + xwl_ei_stop_emulating(xwl_ei_client); + + debug_ei("Client %s is now reusable\n", xwl_ei_client->cmdline); + + /* Otherwise, we keep the EI part but break up with the X11 client */ + assert(client); + dixSetPrivate(&client->devPrivates, &xwl_ei_private_key, NULL); + xwl_ei_client->client = NULL; + + /* Set a timer for 10 minutes. If the same client doesn't reconnect, + * free it properly */ + xwl_ei_client->disconnect_timer = + TimerSet(xwl_ei_client->disconnect_timer, 0, + 10 * 60 * 1000, disconnect_timer_cb, xwl_ei_client); +} + +static void +xwl_ei_state_client_callback(CallbackListPtr *pcbl, void *unused, void *data) +{ + NewClientInfoRec *clientinfo = (NewClientInfoRec *) data; + ClientPtr client = clientinfo->client; + struct xwl_ei_client *xwl_ei_client = get_xwl_ei_client(client); + + switch (client->clientState) { + case ClientStateGone: + case ClientStateRetained: + if (xwl_ei_client) + xwl_ei_handle_client_gone(xwl_ei_client); + break; + default: + break; + } +} + +static inline unsigned int +buttonmap(unsigned int b) +{ + unsigned int button; + + switch (b) { + case 0: + button = 0; + break; + case 1: + button = 0x110; /* BTN_LEFT */ + break; + case 2: + button = 0x112; /* BTN_MIDDLE */ + break; + case 3: + button = 0x111; /* BTN_RIGHT */ + break; + default: + button = b - 8 + 0x113; /* BTN_SIDE */ + break; + } + + return button; +} + +static void +xwl_send_abs_event_to_ei(struct xwl_ei_client *xwl_ei_client, int sx, int sy) +{ + struct xwl_abs_device *abs; + struct ei *ei = xwl_ei_client->ei; + + xorg_list_for_each_entry(abs, &xwl_ei_client->abs_devices, link) { + struct ei_region *r; + size_t idx = 0; + + while ((r = ei_device_get_region(abs->device, idx++))) { + double x = sx, y = sy; + + if (ei_region_contains(r, x, y)) { + ei_device_pointer_motion_absolute(abs->device, sx, sy); + ei_device_frame(abs->device, ei_now(ei)); + return; + } + } + } +} + +static bool +xwl_send_event_to_ei(struct xwl_ei_client *xwl_ei_client, + int type, int detail, int flags, const ValuatorMask *mask) +{ + struct ei *ei = xwl_ei_client->ei; + struct ei_device *ei_device = NULL; + int x = 0, y = 0; + + debug_ei("Sending event type %d to EIS\n", type); + + switch (type) { + case MotionNotify: + valuator_mask_fetch(mask, 0, &x); + valuator_mask_fetch(mask, 1, &y); + + if (flags & POINTER_ABSOLUTE) { + if (!xwl_ei_client->accept_abs) + return false; + + xwl_send_abs_event_to_ei(xwl_ei_client, x, y); + } + else if (x || y) { + if (!xwl_ei_client->accept_pointer) + return false; + + ei_device = xwl_ei_client->ei_pointer; + ei_device_pointer_motion(ei_device, x, y); + ei_device_frame(ei_device, ei_now(ei)); + } + break; + case ButtonPress: + case ButtonRelease: + if (!xwl_ei_client->accept_pointer) + return false; + + ei_device = xwl_ei_client->ei_pointer; + if (detail < 4 || detail > 7) { + ei_device_button_button(ei_device, + buttonmap(detail), type == ButtonPress); + ei_device_frame(ei_device, ei_now(ei)); + /* Scroll only on release */ + } else if (type == ButtonRelease) { + if (detail == 4) { + ei_device_scroll_discrete(ei_device, 0, -SCROLL_STEP); + } else if (detail == 5) { + ei_device_scroll_discrete(ei_device, 0, SCROLL_STEP); + } else if (detail == 6) { + ei_device_scroll_discrete(ei_device, -SCROLL_STEP, 0); + } else if (detail == 7) { + ei_device_scroll_discrete(ei_device, SCROLL_STEP, 0); + } + } + break; + case KeyPress: + case KeyRelease: + if (!xwl_ei_client->accept_keyboard) + return false; + + ei_device = xwl_ei_client->ei_keyboard; + ei_device_keyboard_key(ei_device, detail - 8, type == KeyPress); + ei_device_frame(ei_device, ei_now(ei)); + break; + default: + error_ei("XTEST event type %d is not implemented\n", type); + break; + } + + return true; +} + +static struct xwl_ei_client * +reuse_client(ClientPtr client) +{ + struct xwl_ei_client *xwl_ei_client = NULL; + const char *cmdname = GetClientCmdName(client); + + if (!cmdname) + return NULL; + + debug_ei("Client maybe up for re-use: %s\n", cmdname); + xorg_list_for_each_entry(xwl_ei_client, &clients_for_reuse, link) { + debug_ei("Checking if we can re-use %s\n", xwl_ei_client->cmdline); + if (xwl_ei_client->cmdline && + strcmp(xwl_ei_client->cmdline, cmdname) == 0) { + debug_ei("Re-using client for %s\n", cmdname); + xorg_list_del(&xwl_ei_client->link); + xorg_list_init(&xwl_ei_client->link); + TimerCancel(xwl_ei_client->disconnect_timer); + xwl_ei_start_emulating(xwl_ei_client); + return xwl_ei_client; + } + } + + return NULL; +} + +static void +xwayland_xtest_send_events(DeviceIntPtr dev, + int type, int detail, int flags, const ValuatorMask *mask) +{ + ClientPtr client; + struct xwl_ei_client *xwl_ei_client; + bool accept = false; + + client = GetCurrentClient(); + xwl_ei_client = get_xwl_ei_client(client); + if (!xwl_ei_client) { + xwl_ei_client = reuse_client(client); + if (xwl_ei_client) + xwl_ei_client->client = client; + } + + if (!xwl_ei_client) { + if (!(xwl_ei_client = setup_ei(client))) + return; + } + dixSetPrivate(&client->devPrivates, &xwl_ei_private_key, xwl_ei_client); + + switch (type) { + case MotionNotify: + if (flags & POINTER_ABSOLUTE) + accept = xwl_ei_client->accept_abs; + else + accept = xwl_ei_client->accept_pointer; + break; + case ButtonPress: + case ButtonRelease: + accept = xwl_ei_client->accept_pointer; + break; + case KeyPress: + case KeyRelease: + accept = xwl_ei_client->accept_keyboard; + break; + default: + return; + } + + if (accept) { + xwl_send_event_to_ei(xwl_ei_client, type, detail, flags, mask); + } + else { + debug_ei("Not yet connected to EIS, queueing events\n"); + xwl_queue_emulated_event(xwl_ei_client, type, detail, flags, mask); + } + +} + +static bool +xwl_dequeue_emulated_events(struct xwl_ei_client *xwl_ei_client) +{ + struct xwl_emulated_event *xwl_emulated_event, *next_xwl_emulated_event; + + xorg_list_for_each_entry_safe(xwl_emulated_event, next_xwl_emulated_event, + &xwl_ei_client->pending_emulated_events, link) { + if (!xwl_send_event_to_ei(xwl_ei_client, + xwl_emulated_event->type, + xwl_emulated_event->detail, + xwl_emulated_event->flags, &xwl_emulated_event->mask)) + return false; + xorg_list_del(&xwl_emulated_event->link); + free(xwl_emulated_event); + } + return true; +} + +static void +xwl_handle_ei_event(int fd, int ready, void *data) +{ + struct xwl_ei_client *xwl_ei_client = data; + struct ei *ei; + bool done = false; + + ei = xwl_ei_client->ei; + + ei_dispatch(ei); + do { + enum ei_event_type type; + struct ei_event *e = ei_get_event(ei); + struct ei_device *ei_device; + + if (!e) + break; + + ei_device = ei_event_get_device(e); + type = ei_event_get_type(e); + switch (type) { + case EI_EVENT_CONNECT: + debug_ei("Connected\n"); + break; + case EI_EVENT_SEAT_ADDED: + /* We take the first seat that comes along and + * add our device there */ + if (!xwl_ei_client->ei_seat) { + struct ei_seat *seat = ei_event_get_seat(e); + + xwl_ei_client->ei_seat = ei_seat_ref(seat); + debug_ei("Using seat: %s (caps: %s%s%s%s%s)\n", + ei_seat_get_name(seat), ei_seat_has_capability(seat, + EI_DEVICE_CAP_KEYBOARD) ? "k" : "", + ei_seat_has_capability(seat, + EI_DEVICE_CAP_POINTER) ? "p" : "", + ei_seat_has_capability(seat, + EI_DEVICE_CAP_POINTER_ABSOLUTE) ? "a" : "", + ei_seat_has_capability(seat, + EI_DEVICE_CAP_BUTTON) ? "b" : "", + ei_seat_has_capability(seat, + EI_DEVICE_CAP_SCROLL) ? "s" : ""); + ei_seat_bind_capabilities(seat, + EI_DEVICE_CAP_POINTER, + EI_DEVICE_CAP_POINTER_ABSOLUTE, + EI_DEVICE_CAP_BUTTON, + EI_DEVICE_CAP_SCROLL, + EI_DEVICE_CAP_KEYBOARD, NULL); + } + break; + case EI_EVENT_SEAT_REMOVED: + if (ei_event_get_seat(e) == xwl_ei_client->ei_seat) { + debug_ei("Seat was removed\n"); + xwl_ei_client->ei_seat = + ei_seat_unref(xwl_ei_client->ei_seat); + } + break; + case EI_EVENT_DEVICE_ADDED: + debug_ei("New device: %s\n", ei_device_get_name(ei_device)); + add_ei_device(xwl_ei_client, ei_device); + break; + case EI_EVENT_DEVICE_REMOVED: + debug_ei("Device removed: %s\n", ei_device_get_name(ei_device)); + { + struct xwl_abs_device *abs, *tmp; + + xorg_list_for_each_entry_safe(abs, tmp, + &xwl_ei_client->abs_devices, link) { + if (abs->device != ei_device) + continue; + ei_device_unref(abs->device); + xorg_list_del(&abs->link); + free(abs); + } + } + if (xwl_ei_client->ei_pointer == ei_device) + xwl_ei_client->ei_pointer = + ei_device_unref(xwl_ei_client->ei_pointer); + if (xwl_ei_client->ei_keyboard == ei_device) + xwl_ei_client->ei_keyboard = + ei_device_unref(xwl_ei_client->ei_keyboard); + break; + case EI_EVENT_DISCONNECT: + debug_ei("Disconnected\n"); + free_ei(xwl_ei_client); + done = true; + break; + case EI_EVENT_DEVICE_PAUSED: + debug_ei("Device paused\n"); + if (ei_device == xwl_ei_client->ei_pointer) + xwl_ei_client->accept_pointer = false; + if (ei_device == xwl_ei_client->ei_keyboard) + xwl_ei_client->accept_keyboard = false; + { + struct xwl_abs_device *abs; + + xorg_list_for_each_entry(abs, &xwl_ei_client->abs_devices, + link) { + if (ei_device == abs->device) + xwl_ei_client->accept_abs = false; + } + } + break; + case EI_EVENT_DEVICE_RESUMED: + debug_ei("Device resumed\n"); + if (ei_device == xwl_ei_client->ei_pointer) + xwl_ei_client->accept_pointer = true; + if (ei_device == xwl_ei_client->ei_keyboard) + xwl_ei_client->accept_keyboard = true; + { + struct xwl_abs_device *abs; + + xorg_list_for_each_entry(abs, &xwl_ei_client->abs_devices, + link) { + if (ei_device == abs->device) + xwl_ei_client->accept_abs = true; + } + } + + /* Server has accepted our device (or resumed them), + * we can now start sending events */ + /* FIXME: Maybe add a timestamp and discard old events? */ + xwl_ei_start_emulating(xwl_ei_client); + xwl_dequeue_emulated_events(xwl_ei_client); + if (!xwl_ei_client->client && + xorg_list_is_empty(&xwl_ei_client->pending_emulated_events)) + /* All events dequeued and client has disconnected in the meantime */ + xwl_ei_stop_emulating(xwl_ei_client); + break; + default: + error_ei("Unhandled event %d\n", type); + break; + } + ei_event_unref(e); + } while (!done); +} + +Bool +xwayland_ei_init(void) +{ + xorg_list_init(&clients_for_reuse); + + if (!dixRegisterPrivateKey(&xwl_ei_private_key, PRIVATE_CLIENT, 0)) { + ErrorF("Failed to register EI private key\n"); + return FALSE; + } + + if (!AddCallback(&ClientStateCallback, xwl_ei_state_client_callback, NULL)) { + ErrorF("Failed to add client state callback\n"); + return FALSE; + } + + return TRUE; +} + +void +xwayland_override_xtest(void) +{ + DeviceIntPtr d; + + nt_list_for_each_entry(d, inputInfo.devices, next) { + if (IsXTestDevice(d, NULL)) { + debug_ei("Overriding XTest for %s\n", d->name); + d->sendEventsProc = xwayland_xtest_send_events; + } + } +} diff --git a/hw/xwayland/xwayland-xtest.h b/hw/xwayland/xwayland-xtest.h new file mode 100644 index 000000000..7fbf0183b --- /dev/null +++ b/hw/xwayland/xwayland-xtest.h @@ -0,0 +1,34 @@ +/* + * Copyright © 2020 Red Hat + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation, and that the name of the + * copyright holders not be used in advertising or publicity + * pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +#ifndef XWAYLAND_XTEST_H +#define XWAYLAND_XTEST_H + +#include + +Bool xwayland_ei_init(void); +void xwayland_override_xtest(void); + +#endif diff --git a/include/meson.build b/include/meson.build index fb8568489..3e71e4621 100644 --- a/include/meson.build +++ b/include/meson.build @@ -421,6 +421,8 @@ xwayland_data.set('XWL_HAS_GLAMOR', build_glamor and (gbm_dep.found() or build_e xwayland_data.set('XWL_HAS_EGLSTREAM', build_eglstream ? '1' : false) xwayland_data.set('XWL_HAS_LIBDECOR', have_libdecor ? '1' : false) xwayland_data.set('XWL_HAS_XWAYLAND_EXTENSION', xwaylandproto_dep.found() ? '1' : false) +xwayland_data.set('XWL_HAS_EI', build_ei) +xwayland_data.set('XWL_HAS_EI_PORTAL', build_ei_portal) configure_file(output : 'xwayland-config.h', input : 'xwayland-config.h.meson.in', diff --git a/include/xwayland-config.h.meson.in b/include/xwayland-config.h.meson.in index c254c1fd3..5d65aba79 100644 --- a/include/xwayland-config.h.meson.in +++ b/include/xwayland-config.h.meson.in @@ -15,3 +15,9 @@ /* Build Xwayland with XWAYLAND extension */ #mesondefine XWL_HAS_XWAYLAND_EXTENSION + +/* Enable EI */ +#mesondefine XWL_HAS_EI + +/* Enable XDG portal integration */ +#mesondefine XWL_HAS_EI_PORTAL diff --git a/meson.build b/meson.build index ca0ad94ef..515cc44ea 100644 --- a/meson.build +++ b/meson.build @@ -374,6 +374,17 @@ else build_eglstream = false endif +if build_xwayland + libei_dep = dependency('libei-1.0', version: '>= 1.0.0', required: get_option('xwayland_ei') in ['portal', 'socket']) + liboeffis_dep = dependency('liboeffis-1.0', version: '>= 1.0.0', required: get_option('xwayland_ei') == 'portal') + + build_ei = libei_dep.found() and get_option('xwayland_ei') != 'false' + build_ei_portal = liboeffis_dep.found() and get_option('xwayland_ei') in ['portal', 'auto'] +else + build_ei = false + build_ei_portal = false +endif + # Lots of sha1 options, because Linux is about choice :) # The idea behind the ordering here is that we should first prefer system diff --git a/meson_options.txt b/meson_options.txt index 11d576780..e972717b9 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -8,6 +8,8 @@ option('glamor', type: 'combo', choices: ['true', 'false', 'auto'], value: 'auto description: 'Enable glamor (default yes for Xorg/Xwayland builds)') option('xwayland_eglstream', type: 'combo', choices: ['true', 'false', 'auto'], value: 'auto', description: 'Enable EGLStream support for glamor on Xwayland') +option('xwayland_ei', type: 'combo', choices: ['socket', 'portal', 'false', 'auto'], + value: 'auto', description: 'Enable emulated input support on Xwayland') option('xnest', type: 'combo', choices: ['true', 'false', 'auto'], value: 'auto', description: 'Enable Xnest nested X server') option('xvfb', type: 'boolean', value: true,