diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2420d7f7..347d22d9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,7 +43,7 @@ variables: FDO_UPSTREAM_REPO: wayland/weston FDO_REPO_SUFFIX: "$BUILD_OS/$BUILD_ARCH" - FDO_DISTRIBUTION_TAG: '2023-02-21-bump-meson-to-1.0.0' + FDO_DISTRIBUTION_TAG: '2023-02-28-libxcb-migration' include: diff --git a/.gitlab-ci/debian-install.sh b/.gitlab-ci/debian-install.sh index 8adfd835..e89f6176 100644 --- a/.gitlab-ci/debian-install.sh +++ b/.gitlab-ci/debian-install.sh @@ -95,6 +95,7 @@ apt-get -y --no-install-recommends install \ libxcb-xfixes0-dev \ libxcb-xkb-dev \ libxcursor-dev \ + libxcb-cursor-dev \ libxdamage-dev \ libxext-dev \ libxfixes-dev \ diff --git a/tests/meson.build b/tests/meson.build index 331afda3..7ff56dcb 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -309,14 +309,33 @@ tests_standalone = [ ] if get_option('xwayland') - d = dependency('x11', required: false) - if not d.found() - error('Xwayland tests require libX11 which was not found. Or, you can use \'-Dxwayland=false\'.') + xcb_dep = dependency('xcb', required: false) + xcb_cursor_dep = dependency('xcb-cursor', required: false) + + if not xcb_dep.found() or not xcb_cursor_dep.found() + error('xcb and xcb-cursor required for running xwayland tests') endif - tests += { + + libxwayland_test_client = static_library( + 'test-xwayland-client', + [ 'xcb-client-helper.c', weston_test_client_protocol_h ], + include_directories: common_inc, + dependencies: [ + dep_pixman, dep_xcb_xwayland, + xcb_dep, xcb_cursor_dep + ], + install: false, + ) + + dep_libxwayland_test = declare_dependency( + dependencies: [ xcb_dep, xcb_cursor_dep ], + link_with: libxwayland_test_client, + ) + + tests += [ { 'name': 'xwayland', - 'dep_objs': d, - } + 'dep_objs': dep_libxwayland_test, + } ] endif # Manual test plugin, not used in the automatic suite diff --git a/tests/xcb-client-helper.c b/tests/xcb-client-helper.c new file mode 100644 index 00000000..a2845c5f --- /dev/null +++ b/tests/xcb-client-helper.c @@ -0,0 +1,776 @@ +/* + * Copyright 2022 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "test-config.h" +#include "shared/os-compatibility.h" +#include "shared/helpers.h" +#include "shared/xalloc.h" +#include "shared/xcb-xwayland.h" +#include +#include "xcb-client-helper.h" + +//#define DEBUG + +#ifdef DEBUG +#define printfd(fmt, args...) do { \ + fprintf(stderr, fmt, ##args); \ +} while (0) +#else +#define printfd(fmt, args...) {} +#endif + +struct event_response { + uint8_t response_type; + bool (*eventcb)(xcb_generic_event_t *e, struct window_x11 *win); + const char *name; +}; + +const char *to_event_name(uint8_t event); + +static xcb_drawable_t +handle_event_to_wid(xcb_generic_event_t *ev) +{ + xcb_drawable_t wid; + + switch (EVENT_TYPE(ev)) { + case XCB_CREATE_NOTIFY: { + xcb_create_notify_event_t *ce = (xcb_create_notify_event_t *) ev; + wid = ce->window; + break; + } + case XCB_DESTROY_NOTIFY: { + xcb_destroy_notify_event_t *de = (xcb_destroy_notify_event_t *) ev; + wid = de->window; + break; + } + case XCB_MAP_NOTIFY: { + xcb_map_notify_event_t *mn = (xcb_map_notify_event_t *) ev; + wid = mn->window; + break; + } + case XCB_UNMAP_NOTIFY: { + xcb_unmap_notify_event_t *un = (xcb_unmap_notify_event_t *) ev; + wid = un->window; + break; + } + case XCB_PROPERTY_NOTIFY: { + xcb_property_notify_event_t *pn = (xcb_property_notify_event_t *) ev; + wid = pn->window; + break; + } + case XCB_CONFIGURE_NOTIFY: { + xcb_configure_notify_event_t *cn = (xcb_configure_notify_event_t *) ev; + wid = cn->window; + break; + } + case XCB_EXPOSE: { + xcb_expose_event_t *ep = (xcb_expose_event_t *) ev; + wid = ep->window; + break; + } + case XCB_REPARENT_NOTIFY: { + xcb_reparent_notify_event_t *re = (xcb_reparent_notify_event_t *) ev; + wid = re->window; + break; + } + default: + wid = 0; + } + + return wid; +} + +void +handle_event_remove_pending(struct window_state *wstate) +{ + wl_list_remove(&wstate->link); + free(wstate); +} + +/* returns true if all events in the pending_list has been accounted for */ +static bool +handle_event_check_pending(struct window_x11 *window, xcb_generic_event_t *ev) +{ + + struct window_state *wstate, *wstate_next; + struct wl_list *pending_events = + &window->tentative_state.pending_events_list; + xcb_drawable_t wid; + uint8_t event; + bool found = false; + + event = EVENT_TYPE(ev); + wid = handle_event_to_wid(ev); + + wl_list_for_each_safe(wstate, wstate_next, pending_events, link) { + if (wstate->event != event) + continue; + + if (wstate->wid == wid) { + handle_event_remove_pending(wstate); + found = true; + printfd("%s: removed event %d - %s\n", __func__, + event, to_event_name(event)); + break; + } + } + + if (!found) { + printfd("%s(): event id %d, name %s not found\n", __func__, + event, to_event_name(event)); + return false; + } + + /* still need to get events? -> wait one more round */ + if (!wl_list_empty(pending_events)) { + printfd("%s(): still have %d events to handle!\n", __func__, + wl_list_length(pending_events)); + return false; + } + + return true; +} + +/** In case you need to wait for a notify event call use this function to do so + * and then call handle_events_x11() at the end, to wait for the events to be + * delivered/handled. Note that handle_events_x11() will wait forever if + * handle_event_set_pending() was called for an event which never arrives. + * + * You can call this function multiple times for the same wid, in case you + * expect multiple events to be delivered (i.e., a map notify event and a + * expose one). All functions in this XCB wrapper library calls this function, + * with the user only needing to call handle_events_x11() at the end. This + * function is only needed if the test itself requires to wait for additional + * events, or when expanding this library with other states changes + * (max/fullscreen). + * + * \param window the window_x11 in question + * \param event the event to wait for, like XCB_MAP_NOTIFY, XCB_EXPOSE, etc. + * \param pending_state the pending event to wait for, similar to the XCB ones + * but defined in enum w_state + * \param wid the window id, could be different than that of the window itself + */ +void +handle_event_set_pending(struct window_x11 *window, uint8_t event, + enum w_state pending_state, xcb_drawable_t wid) +{ + struct window_state *wstate = xzalloc(sizeof(*wstate)); + + wstate->event = event; + wstate->wid = wid; + wstate->pending_state = pending_state; + wl_list_insert(&window->tentative_state.pending_events_list, + &wstate->link); + + printfd("%s: Added pending event id %d - name %s, wid %d\n", + __func__, event, to_event_name(event), wid); +} + +static bool +handle_map_notify(xcb_generic_event_t *e, struct window_x11 *window) +{ + xcb_map_notify_event_t *ce = (xcb_map_notify_event_t *) e; + + if (ce->window != window->win_id) + return false; + + window_state_set_flag(window, MAPPED); + + return true; +} + +static bool +handle_unmap_notify(xcb_generic_event_t *e, struct window_x11 *window) +{ + xcb_unmap_notify_event_t *ce = (xcb_unmap_notify_event_t*) e; + + if (ce->window != window->win_id && ce->window != window->frame_id) + return false; + + assert(window_state_has_flag(window, MAPPED)); + window_state_set_flag(window, UNMAPPED); + + return true; +} + +static bool +handle_create_notify(xcb_generic_event_t *e, struct window_x11 *window) +{ + xcb_create_notify_event_t *ce = (xcb_create_notify_event_t*) e; + + if (ce->window != window->win_id) + return false; + + window_state_set_flag(window, CREATED); + + return true; +} + + +static bool +handle_destroy_notify(xcb_generic_event_t *e, struct window_x11 *window) +{ + xcb_destroy_notify_event_t *dn = (xcb_destroy_notify_event_t*) e; + + if (window->win_id != dn->window) + return false; + + assert(window_state_has_flag(window, CREATED)); + window_state_set_flag(window, DESTROYED); + + return true; +} + +static bool +handle_property_notify(xcb_generic_event_t *e, struct window_x11 *window) +{ + xcb_property_notify_event_t *pn = (xcb_property_notify_event_t *) e; + struct atom_x11 *atoms = window->conn->atoms; + + if (pn->window != window->win_id) + return false; + + if (pn->atom == atoms->net_wm_name) { + window_state_set_flag(window, PROPERTY_NAME); + return true; + } + + return false; +} + +static bool +handle_expose(xcb_generic_event_t *e, struct window_x11 *window) +{ + xcb_expose_event_t *ep = (xcb_expose_event_t *) e; + + if (ep->window != window->win_id) + return false; + + window_state_set_flag(window, EXPOSE); + return true; +} + +static bool +handle_configure_notify(xcb_generic_event_t *e, struct window_x11 *window) +{ + xcb_configure_notify_event_t *cn = (xcb_configure_notify_event_t*) e; + + /* we're not interested into other's windows */ + if (cn->window != window->win_id) + return false; + + return true; +} + +static bool +handle_reparent_notify(xcb_generic_event_t *e, struct window_x11 *window) +{ + xcb_reparent_notify_event_t *re = (xcb_reparent_notify_event_t *) e; + + if (re->window == window->win_id && window->frame_id == 0) { + window->frame_id = re->parent; + window_state_set_flag(window, REPARENT); + printfd("Window reparent frame id %d\n", window->frame_id); + return true; + } + + return false; +} + +/* the event handlers should return a boolean that denotes that fact + * they've been handled. One can customize that behaviour such that + * it forces handle_events_x11() to wait for additional events, in case + * that's needed. */ +static const struct event_response events[] = { + { XCB_CREATE_NOTIFY, handle_create_notify, "CREATE_NOTIFY" }, + { XCB_MAP_NOTIFY, handle_map_notify, "MAP_NOTIFY" }, + { XCB_UNMAP_NOTIFY, handle_unmap_notify, "UNMAP_NOTIFY" }, + { XCB_EXPOSE, handle_expose, "EXPOSE_NOTIFY" }, + { XCB_PROPERTY_NOTIFY, handle_property_notify, "PROPERTY_NOTIFY" }, + { XCB_CONFIGURE_NOTIFY, handle_configure_notify, "CONFIGURE_NOTIFY" }, + { XCB_DESTROY_NOTIFY, handle_destroy_notify, "DESTROY_NOTIFY" }, + { XCB_REPARENT_NOTIFY, handle_reparent_notify, "REPARENT_NOTIFY" }, +}; + +const char * +to_event_name(uint8_t event) +{ + size_t i; + for (i = 0; i < ARRAY_LENGTH(events); i++) + if (events[i].response_type == event) + return events[i].name; + + return "(unknown event)"; +} + +/** + * Tells the X server to display the window. Call handle_events_x11() after + * calling this function to wait for events to be delivered. Note there's no + * need to include additional events, they're already added. + * + * \sa handle_events_x11(). + * + * \param window the window in question + */ +void +window_x11_map(struct window_x11 *window) +{ + handle_event_set_pending(window, XCB_MAP_NOTIFY, MAPPED, window->win_id); + handle_event_set_pending(window, XCB_EXPOSE, EXPOSE, window->win_id); + + /* doing a synchronization for the frame wid helps with other potential + * states changes like max/fullscreen or if we try to map the window + * from the beginning with a max/fullscreen state rather than normal + * state (!max && !fullscreen) + */ + handle_event_set_pending(window, XCB_REPARENT_NOTIFY, REPARENT, window->win_id); + + xcb_map_window(window->conn->connection, window->win_id); + xcb_flush(window->conn->connection); +} + +/** + * \sa window_x11_map, handle_events_x11. Tells the X server to unmap the + * window. Call handle_events_x11() to wait for the events to be delivered. + * + * \param window the window in question + */ +void +window_x11_unmap(struct window_x11 *window) +{ + handle_event_set_pending(window, XCB_UNMAP_NOTIFY, UNMAPPED, window->win_id); + + xcb_unmap_window(window->conn->connection, window->win_id); + xcb_flush(window->conn->connection); +} + +static xcb_generic_event_t * +poll_for_event(xcb_connection_t *conn) +{ + int fd = xcb_get_file_descriptor(conn); + struct pollfd pollfds = {}; + int rpol; + + pollfds.fd = fd; + pollfds.events = POLLIN; + + rpol = ppoll(&pollfds, 1, NULL, NULL); + if (rpol > 0 && (pollfds.revents & POLLIN)) + return xcb_wait_for_event(conn); + + return NULL; +} + +static void +window_x11_set_cursor(struct window_x11 *window, const char *cursor_name) +{ + assert(window); + assert(window->ctx == NULL); + + if (xcb_cursor_context_new(window->conn->connection, + window->screen, &window->ctx) < 0) { + fprintf(stderr, "Error creating context!\n"); + return; + } + + window->cursor = xcb_cursor_load_cursor(window->ctx, cursor_name); + + xcb_change_window_attributes(window->conn->connection, + window->root_win_id, XCB_CW_CURSOR, + &window->cursor); + xcb_flush(window->conn->connection); +} + +static bool +handle_event(xcb_generic_event_t *ev, struct window_x11 *window) +{ + uint8_t event = EVENT_TYPE(ev); + bool events_handled = false; + size_t i; + + for (i = 0; i < ARRAY_LENGTH(events); i++) { + if (event == events[i].response_type) { + bool ev_cb_handled = events[i].eventcb(ev, window); + if (!ev_cb_handled) + return false; + + events_handled = + handle_event_check_pending(window, ev); + } + } + + return events_handled; +} + +/** + * Each operation on 'window_x11' requires calling handle_events_x11() to a) flush + * out the connection, b) poll for a xcb_generic_event_t and c) to call the + * appropriate event handler; + * + * This function should never block to allow a programmatic way of applying + * different operations/states to the window. If that happens, running it + * under meson test will cause a test fail (with a timeout). + * + * Before calling this function, one *shall* use handle_event_set_pending() to + * explicitly set which events to wait for. Not doing so will effectively + * deadlock the test, as it will wait for pending events never being set. + * + * Note that all state change functions, including map and unmap, would + * implicitly, if not otherwise stated, set the pending events to wait for. + * + * \sa handle_event_set_pending() + * \param window the X11 window in question + */ +int +handle_events_x11(struct window_x11 *window) +{ + bool running = true; + xcb_generic_event_t *ev; + int ret = 0; + + assert(window->handle_in_progress == false); + window->handle_in_progress = true; + + do { + bool events_handled = false; + + xcb_flush(window->conn->connection); + + if (xcb_connection_has_error(window->conn->connection)) { + fprintf(stderr, "X11 connection got interrupted\n"); + ret = -1; + break; + } + + ev = poll_for_event(window->conn->connection); + if (!ev) { + fprintf(stderr, "Error, no event received, " + "although we requested for one!\n"); + break; + } + + events_handled = handle_event(ev, window); + + /* signals that we've done processing all the pending events */ + if (events_handled) { + running = false; + } + + free(ev); + } while (running); + + window->handle_in_progress = false; + return ret; +} + +/* Might be useful in case you'd want to receive create notify for our own window. */ +void +window_x11_notify_for_root_events(struct window_x11 *window) +{ + int mask_values = + XCB_EVENT_MASK_STRUCTURE_NOTIFY | + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; + + xcb_change_window_attributes(window->conn->connection, + window->root_win_id, + XCB_CW_EVENT_MASK, &mask_values); + xcb_flush(window->conn->connection); +} + +/** + * Sets the x11 window a property name. Call handle_events_x11() to + * wait for the events to be delivered. + * + * \param window the window in question + * \param name the name, as string + * + */ +void +window_x11_set_win_name(struct window_x11 *window, const char *name) +{ + struct atom_x11 *atoms = window->conn->atoms; + + handle_event_set_pending(window, XCB_PROPERTY_NOTIFY, PROPERTY_NAME, window->win_id); + + xcb_change_property(window->conn->connection, XCB_PROP_MODE_REPLACE, + window->win_id, atoms->net_wm_name, + atoms->string, 8, + strlen(name), name); + xcb_flush(window->conn->connection); +} + +/** Create a x11 connection. + * + * \sa window_get_connection() to retrieve it in the same tests to avoid + * creating a new connection + * \sa create_x11_window(), where you need to pass this connection_x11 object. + * + * \return a struct connection_x11 which defines a x11 connection. + */ +struct connection_x11 * +create_x11_connection(void) +{ + struct connection_x11 *conn; + + if (access(XSERVER_PATH, X_OK) != 0) + return NULL; + + conn = xzalloc(sizeof(*conn)); + conn->connection = xcb_connect(NULL, NULL); + if (!conn->connection) + return NULL; + + conn->atoms = xzalloc(sizeof(struct atom_x11)); + + /* retrieve atoms */ + x11_get_atoms(conn->connection, conn->atoms); + + return conn; +} + +/** Destroys a x11 connection. Use this at the end (of the test) to destroy the + * x11 connection. + * + * \param conn the x11 connection in question. + */ +void +destroy_x11_connection(struct connection_x11 *conn) +{ + xcb_disconnect(conn->connection); + + free(conn->atoms); + free(conn); +} + + +/** + * creates a X window, based on the initial supplied values. All operations + * performed will work on this window_x11 object. + * + * The creation and destruction of the window_x11 object is handled implictly + * so there's no need wait for (additional) events, like it is required for + * all other change state operations. + * + * The window is not mapped/displayed so that needs to happen explictly, by + * calling window_x11_map() and then waiting for events using + * handle_events_x11(). + * + * \param width initial size, width value + * \param height initial size, height value + * \param pos_x initial position, x value + * \param pos_y initial position, y value + * \param conn X11 connection + * \param bg_color a background color + * \param parent the window_x11 parent + * \return a pointer to window_x11, which gets destroyed with destroy_x11_window() + */ +struct window_x11 * +create_x11_window(int width, int height, int pos_x, int pos_y, + struct connection_x11 *conn, pixman_color_t bg_color, + struct window_x11 *parent) +{ + uint32_t colorpixel = 0x0; + uint32_t values[2]; + uint32_t mask = 0; + + xcb_colormap_t colormap; + struct window_x11 *window; + xcb_window_t parent_win_id; + xcb_alloc_color_cookie_t cookie; + xcb_alloc_color_reply_t *reply; + xcb_void_cookie_t cookie_create; + xcb_generic_error_t *error_create; + const struct xcb_setup_t *xcb_setup; + + assert(conn); + window = xzalloc(sizeof(*window)); + + window->conn = conn; + xcb_setup = xcb_get_setup(window->conn->connection); + window->screen = xcb_setup_roots_iterator(xcb_setup).data; + + wl_list_init(&window->window_list); + wl_list_init(&window->tentative_state.pending_events_list); + + window->root_win_id = window->screen->root; + window->parent = parent; + if (window->parent) { + parent_win_id = window->parent->win_id; + wl_list_insert(&parent->window_list, &window->window_link); + } else { + parent_win_id = window->root_win_id; + } + + colormap = window->screen->default_colormap; + cookie = xcb_alloc_color(window->conn->connection, colormap, + bg_color.red, bg_color.blue, bg_color.green); + reply = xcb_alloc_color_reply(window->conn->connection, cookie, NULL); + assert(reply); + + colorpixel = reply->pixel; + free(reply); + + window->background = xcb_generate_id(window->conn->connection); + mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES; + values[0] = colorpixel; + values[1] = 0; + window->bg_color = bg_color; + + xcb_create_gc(window->conn->connection, window->background, + window->root_win_id, mask, values); + + /* create the window */ + window->win_id = xcb_generate_id(window->conn->connection); + mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; + values[0] = colorpixel; + values[1] = XCB_EVENT_MASK_EXPOSURE | + XCB_EVENT_MASK_KEY_PRESS | + XCB_EVENT_MASK_VISIBILITY_CHANGE | + XCB_EVENT_MASK_STRUCTURE_NOTIFY | + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | + XCB_EVENT_MASK_PROPERTY_CHANGE; + + window->pos_x = pos_x; + window->pos_y = pos_y; + + window->width = width; + window->height = height; + + cookie_create = xcb_create_window_checked(window->conn->connection, + XCB_COPY_FROM_PARENT, + window->win_id, parent_win_id, + window->pos_x, window->pos_y, + window->width, window->height, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + window->screen->root_visual, + mask, values); + error_create = xcb_request_check(window->conn->connection, cookie_create); + assert(error_create == NULL); + + window_state_set_flag(window, CREATED); + window_x11_set_cursor(window, "left_ptr"); + + return window; +} + +static void +kill_window(struct window_x11 *window) +{ + handle_event_set_pending(window, XCB_DESTROY_NOTIFY, DESTROYED, window->win_id); + + xcb_destroy_window(window->conn->connection, window->win_id); + xcb_flush(window->conn->connection); +} + +/** + * \sa create_x11_window(). The creation and destruction of the window_x11 is + * handled implicitly so there's no wait for (additional) events. + * + * This function would wait for destroy notify event and will disconnect from + * the server. No further operation can happen on the window_x11, except + * for destroying the x11 connection using destroy_x11_connection(). + * + * \param window the window in question + */ +void +destroy_x11_window(struct window_x11 *window) +{ + struct window_state *wstate, *wstate_next; + + xcb_free_cursor(window->conn->connection, window->cursor); + xcb_cursor_context_free(window->ctx); + xcb_flush(window->conn->connection); + + kill_window(window); + handle_events_x11(window); + + /* in case we're called before any events have been handled */ + wl_list_for_each_safe(wstate, wstate_next, + &window->tentative_state.pending_events_list, link) + handle_event_remove_pending(wstate); + + free(window); +} + +/** + * Return the reply_t for an atom + * + * \param window the window in question + * \param win the handle for the window; could be different from the window itself! + * \param atom the atom in question + * + */ +xcb_get_property_reply_t * +window_x11_dump_prop(struct window_x11 *window, xcb_drawable_t win, xcb_atom_t atom) +{ + xcb_get_property_cookie_t prop_cookie; + xcb_get_property_reply_t *prop_reply; + + prop_cookie = xcb_get_property(window->conn->connection, 0, win, atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, 2048); + + prop_reply = xcb_get_property_reply(window->conn->connection, prop_cookie, NULL); + + /* callers needs to free it */ + return prop_reply; +} + +/** Retrieve the atoms + * + * \param win the window in question from which to retrieve the atoms + * + */ +struct atom_x11 * +window_get_atoms(struct window_x11 *win) +{ + return win->conn->atoms; +} + +/** Retrive the connection_x11 from the window_x11 + * + * \param win the window in question from which to retrieve the connection + * + */ +struct xcb_connection_t * +window_get_connection(struct window_x11 *win) +{ + return win->conn->connection; +} diff --git a/tests/xcb-client-helper.h b/tests/xcb-client-helper.h new file mode 100644 index 00000000..e8684bec --- /dev/null +++ b/tests/xcb-client-helper.h @@ -0,0 +1,195 @@ +/* + * Copyright 2022 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include "config.h" + +#include +#include +#include +#include + +#include "shared/xcb-xwayland.h" +#include +#include + +#include +#include + +enum w_state { + CREATED = 1 << 0, + MAPPED = 1 << 1, + UNMAPPED = 1 << 2, + PROPERTY_NAME = 1 << 3, + DESTROYED = 1 << 4, + EXPOSE = 1 << 5, + REPARENT = 1 << 6, +}; + +struct window_state { + uint8_t event; + enum w_state pending_state; + xcb_drawable_t wid; + struct wl_list link; /** window_x11.tentative_state::pending_events_list */ +}; + +struct connection_x11 { + struct atom_x11 *atoms; + struct xcb_connection_t *connection; +}; + +struct window_x11 { + struct window_x11 *parent; + struct xcb_screen_t *screen; + struct connection_x11 *conn; + bool handle_in_progress; + + xcb_drawable_t root_win_id; /* screen root */ + xcb_drawable_t win_id; /* this window */ + xcb_drawable_t parent_win_id; /* the parent, if set */ + + xcb_gcontext_t background; + + xcb_cursor_context_t *ctx; + xcb_cursor_t cursor; + + int width; + int height; + + int pos_x; + int pos_y; + + pixman_color_t bg_color; + + /* these track what the X11 client does */ + struct { + /* pending queue events */ + struct wl_list pending_events_list; /** window_state::link */ + } tentative_state; + + /* these track what we got back from the server */ + struct { + /* applied, received event */ + uint32_t win_state; + } state; + + struct wl_list window_list; + struct wl_list window_link; + + xcb_window_t frame_id; +}; + +void +window_x11_map(struct window_x11 *window); + +void +window_x11_unmap(struct window_x11 *window); + + +struct connection_x11 * +create_x11_connection(void); + +void +destroy_x11_connection(struct connection_x11 *conn); + +struct window_x11 * +create_x11_window(int width, int height, int pos_x, int pos_y, struct connection_x11 *conn, + pixman_color_t bg_color, struct window_x11 *parent); +void +destroy_x11_window(struct window_x11 *window); + +void +window_x11_set_win_name(struct window_x11 *window, const char *name); + +xcb_get_property_reply_t * +window_x11_dump_prop(struct window_x11 *window, xcb_drawable_t win, xcb_atom_t atom); + +void +handle_event_set_pending(struct window_x11 *window, uint8_t event, + enum w_state pending_state, xcb_drawable_t wid); + +void +handle_event_remove_pending(struct window_state *wstate); + +int +handle_events_x11(struct window_x11 *window); + +struct atom_x11 * +window_get_atoms(struct window_x11 *win); + +struct xcb_connection_t * +window_get_connection(struct window_x11 *win); + +void +window_x11_notify_for_root_events(struct window_x11 *window); + +/* note that the flag is already bitshiftted */ +static inline bool +window_state_has_flag(struct window_x11 *win, enum w_state flag) +{ + return (win->state.win_state & flag) == flag; +} + +static inline void +window_state_set_flag(struct window_x11 *win, enum w_state flag) +{ + win->state.win_state |= flag; +} + +static inline void +window_state_clear_flag(struct window_x11 *win, enum w_state flag) +{ + win->state.win_state &= ~flag; +} + +static inline void +window_state_clear_all_flags(struct window_x11 *win) +{ + win->state.win_state = 0; +} + +/** + * A wrapper over handle_events_x11() to check if the pending flag has been + * set, that waits for events calling handle_events_x11() and that verifies + * afterwards if the flag has indeed been applied. + */ +static inline void +handle_events_and_check_flags(struct window_x11 *win, enum w_state flag) +{ + struct wl_list *pending_events = + &win->tentative_state.pending_events_list; + struct window_state *wstate; + bool found_pending_flag = false; + + wl_list_for_each(wstate, pending_events, link) { + if ((wstate->pending_state & flag) == flag) + found_pending_flag = true; + } + assert(found_pending_flag); + + handle_events_x11(win); + assert(window_state_has_flag(win, flag)); +} diff --git a/tests/xwayland-test.c b/tests/xwayland-test.c index f2e83812..9fb30512 100644 --- a/tests/xwayland-test.c +++ b/tests/xwayland-test.c @@ -1,5 +1,6 @@ /* * Copyright © 2015 Samsung Electronics Co., Ltd + * Copyright 2022 Collabora, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -27,8 +28,10 @@ * * This is done in steps: * 1) Confirm that the WL_SURFACE_ID atom exists - * 2) Confirm that the window manager's name is "Weston WM" - * 3) Make sure we can map a window + * 2) Confirm that our window name is "Xwayland Test Window" + * 3) Confirm that there's conforming Window Manager + * 4) Confirm that the window manager's name is "Weston WM" + * 5) Make sure we can map a window */ #include "config.h" @@ -37,12 +40,13 @@ #include #include #include -#include -#include #include #include "weston-test-runner.h" #include "weston-test-fixture-compositor.h" +#include "shared/string-helpers.h" +#include "weston-test-client-helper.h" +#include "xcb-client-helper.h" static enum test_result_code fixture_setup(struct weston_test_harness *harness) @@ -52,68 +56,120 @@ fixture_setup(struct weston_test_harness *harness) compositor_setup_defaults(&setup); setup.shell = SHELL_TEST_DESKTOP; setup.xwayland = true; + /* setup.logging_scopes = "xwm-wm-x11"; */ return weston_test_harness_execute_as_client(harness, &setup); } DECLARE_FIXTURE_SETUP(fixture_setup); +static char * +get_x11_window_name(struct window_x11 *window, xcb_drawable_t win) +{ + xcb_get_property_reply_t *reply; + int reply_len; + char *name; + struct atom_x11 *atoms = window_get_atoms(window); + + reply = window_x11_dump_prop(window, win, atoms->net_wm_name); + assert(reply); + + assert(reply->type == atoms->string || + reply->type == atoms->utf8_string); + reply_len = xcb_get_property_value_length(reply); + assert(reply_len > 0); + + str_printf(&name, "%.*s", reply_len, + (char *) xcb_get_property_value(reply)); + free(reply); + return name; +} + +static char * +get_wm_name(struct window_x11 *window) +{ + xcb_get_property_reply_t *reply; + xcb_generic_error_t *error; + xcb_get_property_cookie_t prop_cookie; + char *wm_name = NULL; + struct atom_x11 *atoms = window_get_atoms(window); + struct xcb_connection_t *conn = window_get_connection(window); + + prop_cookie = xcb_get_property(conn, 0, window->root_win_id, + atoms->net_supporting_wm_check, + XCB_ATOM_WINDOW, 0, 1024); + reply = xcb_get_property_reply(conn, prop_cookie, &error); + assert(reply); + assert(reply->type == XCB_ATOM_WINDOW); + assert(reply->format == 32); + + xcb_window_t wm_id = *(xcb_window_t *) xcb_get_property_value(reply); + wm_name = get_x11_window_name(window, wm_id); + + free(error); + return wm_name; +} + TEST(xwayland_client_test) { - Display *display; - Window window, root, *support; - XEvent event; - int screen, status, actual_format; - unsigned long nitems, bytes; - Atom atom, type_atom, actual_type; + struct window_x11 *window; + struct connection_x11 *conn; + xcb_get_property_reply_t *reply; + char *win_name; char *wm_name; + pixman_color_t bg_color; + struct atom_x11 *atoms; - if (access(XSERVER_PATH, X_OK) != 0) - exit(77); + color_rgb888(&bg_color, 255, 0, 0); - display = XOpenDisplay(NULL); - if (!display) - exit(EXIT_FAILURE); + conn = create_x11_connection(); + assert(conn); + window = create_x11_window(100, 100, 100, 100, conn, bg_color, NULL); + assert(window); - atom = XInternAtom(display, "WL_SURFACE_ID", True); - assert(atom != None); + window_x11_set_win_name(window, "Xwayland Test Window"); + handle_events_and_check_flags(window, PROPERTY_NAME); - atom = XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", True); - assert(atom != None); - screen = DefaultScreen(display); - root = RootWindow(display, screen); + /* The Window Manager MUST set _NET_SUPPORTING_WM_CHECK on the root + * window to be the ID of a child window created by himself, to + * indicate that a compliant window manager is active. + * + * The child window MUST also have the _NET_SUPPORTING_WM_CHECK + * property set to the ID of the child window. The child window MUST + * also have the _NET_WM_NAME property set to the name of the Window + * Manager. + * + * See Extended Window Manager Hints, + * https://specifications.freedesktop.org/wm-spec/latest/ar01s03.html, + * _NET_SUPPORTING_WM_CHECK + * */ + atoms = window_get_atoms(window); + assert(atoms->net_supporting_wm_check != XCB_ATOM_NONE); + assert(atoms->wl_surface_id != XCB_ATOM_NONE); + assert(atoms->net_wm_name != XCB_ATOM_NONE); + assert(atoms->utf8_string != XCB_ATOM_NONE); - status = XGetWindowProperty(display, root, atom, 0L, ~0L, - False, XA_WINDOW, &actual_type, - &actual_format, &nitems, &bytes, - (void *)&support); - assert(status == Success); + reply = window_x11_dump_prop(window, window->root_win_id, + atoms->net_supporting_wm_check); + assert(reply); + assert(reply->type == XCB_ATOM_WINDOW); + free(reply); - atom = XInternAtom(display, "_NET_WM_NAME", True); - assert(atom != None); - type_atom = XInternAtom(display, "UTF8_STRING", True); - assert(atom != None); - status = XGetWindowProperty(display, *support, atom, 0L, BUFSIZ, - False, type_atom, &actual_type, - &actual_format, &nitems, &bytes, - (void *)&wm_name); - assert(status == Success); - assert(nitems); - assert(strcmp("Weston WM", wm_name) == 0); - free(support); + window_x11_map(window); + handle_events_and_check_flags(window, MAPPED); + + win_name = get_x11_window_name(window, window->win_id); + assert(strcmp(win_name, "Xwayland Test Window") == 0); + free(win_name); + + wm_name = get_wm_name(window); + assert(wm_name); + assert(strcmp(wm_name, "Weston WM") == 0); free(wm_name); - window = XCreateSimpleWindow(display, root, 100, 100, 300, 300, 1, - BlackPixel(display, screen), - WhitePixel(display, screen)); - XSelectInput(display, window, ExposureMask); - XMapWindow(display, window); + window_x11_unmap(window); + handle_events_and_check_flags(window, UNMAPPED); - while (1) { - XNextEvent(display, &event); - if (event.type == Expose) - break; - } - - XCloseDisplay(display); + destroy_x11_window(window); + destroy_x11_connection(conn); }