From: Johan Malm Date: Sun, 12 May 2019 20:43:48 +0000 (+0100) Subject: Add XWayland support X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=0e9c4e5891e8fe5046d703ca396e61b1c4da5480;p=proto%2Flabwc.git Add XWayland support --- diff --git a/tinywl.c b/tinywl.c index a9cfe149..30873a06 100644 --- a/tinywl.c +++ b/tinywl.c @@ -21,8 +21,13 @@ #include #include #include +#include #include +#define XCURSOR_DEFAULT "left_ptr" +#define XCURSOR_SIZE 24 +#define XCURSOR_MOVE "grabbing" + /* For brevity's sake, struct members are annotated where they are used. */ enum tinywl_cursor_mode { TINYWL_CURSOR_PASSTHROUGH, @@ -34,9 +39,12 @@ struct tinywl_server { struct wl_display *wl_display; struct wlr_backend *backend; struct wlr_renderer *renderer; + struct wlr_compositor *compositor; struct wlr_xdg_shell *xdg_shell; struct wl_listener new_xdg_surface; + struct wlr_xwayland *xwayland; + struct wl_listener new_xwayland_surface; struct wl_list views; struct wlr_cursor *cursor; @@ -69,16 +77,32 @@ struct tinywl_output { struct wl_listener frame; }; +enum view_type { + LAB_XDG_SHELL_VIEW, + LAB_XWAYLAND_VIEW +}; + struct tinywl_view { + enum view_type type; struct wl_list link; struct tinywl_server *server; struct wlr_xdg_surface *xdg_surface; + struct wlr_xwayland_surface *xwayland_surface; + struct wlr_surface *surface; struct wl_listener map; struct wl_listener unmap; struct wl_listener destroy; struct wl_listener request_move; struct wl_listener request_resize; + struct wl_listener request_configure; + bool mapped; + /* + * Some X11 windows appear to create additional top levels windows + * which we want to ignore. These are never mapped, so we can track + * them that way + */ + bool been_mapped; int x, y; }; @@ -91,6 +115,40 @@ struct tinywl_keyboard { struct wl_listener key; }; +static struct tinywl_view *next_toplevel(struct tinywl_view *current); +static bool is_toplevel(struct tinywl_view *view); + +/** + * Request that this toplevel surface show itself in an activated or + * deactivated state. + */ +static void set_activated(struct wlr_surface *s, bool activated) { + if (wlr_surface_is_xdg_surface(s)) { + struct wlr_xdg_surface *previous; + previous = wlr_xdg_surface_from_wlr_surface(s); + wlr_xdg_toplevel_set_activated(previous, activated); + } else { + struct wlr_xwayland_surface *previous; + previous = wlr_xwayland_surface_from_wlr_surface(s); + wlr_xwayland_surface_activate(previous, activated); + } +} + +static void activate_view(struct tinywl_view *view) { + if (view->type == LAB_XDG_SHELL_VIEW) { + wlr_xdg_toplevel_set_activated(view->xdg_surface, true); + } else if (view->type == LAB_XWAYLAND_VIEW) { + wlr_xwayland_surface_activate(view->xwayland_surface, true); + } else { + fprintf(stderr, "warn: view was of unknown type (%s)\n", __func__); + } +} + +static void move_to_front(struct tinywl_view *view) { + wl_list_remove(&view->link); + wl_list_insert(&view->server->views, &view->link); +} + static void focus_view(struct tinywl_view *view, struct wlr_surface *surface) { /* Note: this function only deals with keyboard focus. */ if (view == NULL) { @@ -98,34 +156,79 @@ static void focus_view(struct tinywl_view *view, struct wlr_surface *surface) { } struct tinywl_server *server = view->server; struct wlr_seat *seat = server->seat; - struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface; + struct wlr_surface *prev_surface; + + prev_surface = seat->keyboard_state.focused_surface; if (prev_surface == surface) { /* Don't re-focus an already focused surface. */ return; } + if (view->type == LAB_XWAYLAND_VIEW) { + /* Don't focus on menus, etc */ + if (!wlr_xwayland_or_surface_wants_focus(view->xwayland_surface)) { + return; + } + } if (prev_surface) { - /* - * Deactivate the previously focused surface. This lets the client know - * it no longer has focus and the client will repaint accordingly, e.g. - * stop displaying a caret. - */ - struct wlr_xdg_surface *previous = wlr_xdg_surface_from_wlr_surface( - seat->keyboard_state.focused_surface); - wlr_xdg_toplevel_set_activated(previous, false); + set_activated(seat->keyboard_state.focused_surface, false); } + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat); - /* Move the view to the front */ - wl_list_remove(&view->link); - wl_list_insert(&server->views, &view->link); - /* Activate the new surface */ - wlr_xdg_toplevel_set_activated(view->xdg_surface, true); + move_to_front(view); + activate_view(view); /* - * Tell the seat to have the keyboard enter this surface. wlroots will keep - * track of this and automatically send key events to the appropriate - * clients without additional work on your part. + * Tell the seat to have the keyboard enter this surface. wlroots will + * keep track of this and automatically send key events to the + * appropriate clients without additional work on your part. */ - wlr_seat_keyboard_notify_enter(seat, view->xdg_surface->surface, - keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); + wlr_seat_keyboard_notify_enter(seat, view->surface, keyboard->keycodes, + keyboard->num_keycodes, &keyboard->modifiers); +} + +static struct tinywl_view *last_toplevel(struct tinywl_server *server) { + struct tinywl_view *view; + + wl_list_for_each_reverse(view, &server->views, link) { + if (!view->been_mapped) { + continue; + } + if (is_toplevel(view)) { + return view; + } + } + fprintf(stderr, "warn: found no toplevel view (%s)\n", __func__); + return NULL; +} + +static struct tinywl_view *first_toplevel(struct tinywl_server *server) { + struct tinywl_view *view; + + wl_list_for_each(view, &server->views, link) { + if (!view->been_mapped) { + continue; + } + if (is_toplevel(view)) { + return view; + } + } + fprintf(stderr, "warn: found no toplevel view (%s)\n", __func__); + return NULL; +} + +static void view_focus_last_toplevel(struct tinywl_server *server) { + /* TODO: write view_nr_toplevel_views() */ + if (wl_list_length(&server->views) < 2) { + return; + } + struct tinywl_view *view = last_toplevel(server); + focus_view(view, view->surface); +} + +static void view_focus_next_toplevel(struct tinywl_server *server) { + struct tinywl_view *view; + view = first_toplevel(server); + view = next_toplevel(view); + focus_view(view, view->surface); } static void keyboard_handle_modifiers( @@ -146,6 +249,114 @@ static void keyboard_handle_modifiers( &keyboard->device->keyboard->modifiers); } +static int xwl_nr_parents(struct tinywl_view *view) { + struct wlr_xwayland_surface *s = view->xwayland_surface; + int i = 0; + + if (!s) { + fprintf(stderr, "warn: (%s) no xwayland surface\n", __func__); + return -1; + } + while (s->parent) { + s = s->parent; + ++i; + } + return i; +} + +static bool is_toplevel(struct tinywl_view *view) { + switch (view->type) { + case LAB_XWAYLAND_VIEW: + return xwl_nr_parents(view) > 0 ? false : true; + case LAB_XDG_SHELL_VIEW: + return view->xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL; + } + return false; +} + +static void xdg_debug_show_one_view(struct tinywl_view *view) { + fprintf(stderr, "XDG "); + switch (view->xdg_surface->role) { + case WLR_XDG_SURFACE_ROLE_NONE: + fprintf(stderr, "- "); + break; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + fprintf(stderr, "0 "); + break; + case WLR_XDG_SURFACE_ROLE_POPUP: + fprintf(stderr, "? "); + break; + } + fprintf(stderr, " %p %s", (void *)view, + view->xdg_surface->toplevel->app_id); + fprintf(stderr, " {%d, %d, %d, %d}\n", + view->xdg_surface->geometry.x, + view->xdg_surface->geometry.y, + view->xdg_surface->geometry.height, + view->xdg_surface->geometry.width); +} + +static void xwl_debug_show_one_view(struct tinywl_view *view) { + fprintf(stderr, "XWL "); + if (!view->been_mapped) { + fprintf(stderr, "- "); + } else { + fprintf(stderr, "%d ", xwl_nr_parents(view)); + } + fprintf(stderr, " %d ", wl_list_length(&view->xwayland_surface->children)); + if (view->mapped) { + fprintf(stderr, "Y"); + } else { + fprintf(stderr, "-"); + } + fprintf(stderr, " %p %s {%d,%d,%d,%d}\n", + (void *)view, + view->xwayland_surface->class, + view->xwayland_surface->x, + view->xwayland_surface->y, + view->xwayland_surface->width, + view->xwayland_surface->height); + /* + * Other variables to consider printing: + * + * view->mapped, + * view->been_mapped, + * view->xwayland_surface->override_redirect, + * wlr_xwayland_or_surface_wants_focus(view->xwayland_surface)); + * view->xwayland_surface->saved_width, + * view->xwayland_surface->saved_height); + * view->xwayland_surface->surface->sx, + * view->xwayland_surface->surface->sy); + */ +} + +static void debug_show_one_view(struct tinywl_view *view) { + if (view->type == LAB_XDG_SHELL_VIEW) + xdg_debug_show_one_view(view); + else if (view->type == LAB_XWAYLAND_VIEW) + xwl_debug_show_one_view(view); +} + +static void debug_show_views(struct tinywl_server *server) { + struct tinywl_view *view; + + fprintf(stderr, "---\n"); + fprintf(stderr, "TYPE NR_PNT NR_CLD MAPPED VIEW-POINTER NAME\n"); + wl_list_for_each_reverse(view, &server->views, link) + debug_show_one_view(view); +} + +static struct tinywl_view *next_toplevel(struct tinywl_view *current) { + struct tinywl_view *tmp = current; + + goto inside; + while (!tmp->been_mapped || !is_toplevel(tmp)) { +inside: + tmp = wl_container_of(tmp->link.next, tmp, link); + } + return tmp; +} + static bool handle_keybinding(struct tinywl_server *server, xkb_keysym_t sym) { /* * Here we handle compositor keybindings. This is when the compositor is @@ -159,18 +370,16 @@ static bool handle_keybinding(struct tinywl_server *server, xkb_keysym_t sym) { wl_display_terminate(server->wl_display); break; case XKB_KEY_F1: - /* Cycle to the next view */ - if (wl_list_length(&server->views) < 2) { - break; + case XKB_KEY_F2: + view_focus_last_toplevel(server); + break; + case XKB_KEY_F3: + if (fork() == 0) { + execl("/bin/dmenu_run", "/bin/dmenu_run", (void *)NULL); } - struct tinywl_view *current_view = wl_container_of( - server->views.next, current_view, link); - struct tinywl_view *next_view = wl_container_of( - current_view->link.next, next_view, link); - focus_view(next_view, next_view->xdg_surface->surface); - /* Move the previous view to the end of the list */ - wl_list_remove(¤t_view->link); - wl_list_insert(server->views.prev, ¤t_view->link); + break; + case XKB_KEY_F12: + debug_show_views(server); break; default: return false; @@ -311,8 +520,20 @@ static bool view_at(struct tinywl_view *view, double view_sy = ly - view->y; double _sx, _sy; struct wlr_surface *_surface = NULL; - _surface = wlr_xdg_surface_surface_at( + + switch (view->type) { + case LAB_XDG_SHELL_VIEW: + _surface = wlr_xdg_surface_surface_at( view->xdg_surface, view_sx, view_sy, &_sx, &_sy); + break; + case LAB_XWAYLAND_VIEW: + if (!view->xwayland_surface->surface) + return false; + _surface = wlr_surface_surface_at( + view->xwayland_surface->surface, + view_sx, view_sy, &_sx, &_sy); + break; + } if (_surface != NULL) { *sx = _sx; @@ -320,7 +541,6 @@ static bool view_at(struct tinywl_view *view, *surface = _surface; return true; } - return false; } @@ -507,8 +727,10 @@ static void server_cursor_frame(struct wl_listener *listener, void *data) { wlr_seat_pointer_notify_frame(server->seat); } -/* Used to move all of the data necessary to render a surface from the top-level - * frame handler to the per-surface render function. */ +/* + * Used to move all of the data necessary to render a surface from the + * top-level frame handler to the per-surface render function. + */ struct render_data { struct wlr_output *output; struct wlr_renderer *renderer; @@ -616,8 +838,13 @@ static void output_frame(struct wl_listener *listener, void *data) { }; /* This calls our render_surface function for each surface among the * xdg_surface's toplevel and popups. */ - wlr_xdg_surface_for_each_surface(view->xdg_surface, + if (view->type == LAB_XDG_SHELL_VIEW) { + wlr_xdg_surface_for_each_surface(view->xdg_surface, render_surface, &rdata); + } else if (view->type == LAB_XWAYLAND_VIEW) { + render_surface(view->xwayland_surface->surface, 0, 0, + &rdata); + } } /* Hardware cursors are rendered by the GPU on a separate plane, and can be @@ -674,10 +901,66 @@ static void server_new_output(struct wl_listener *listener, void *data) { wlr_output_create_global(wlr_output); } +static void xwl_surface_map(struct wl_listener *listener, void *data) { + struct tinywl_view *view = wl_container_of(listener, view, map); + view->mapped = true; + view->been_mapped = true; + view->x = view->xwayland_surface->x; + view->y = view->xwayland_surface->y; + view->surface = view->xwayland_surface->surface; + move_to_front(view); + focus_view(view, view->xwayland_surface->surface); +} + +static void xwl_surface_unmap(struct wl_listener *listener, void *data) { + struct tinywl_view *view = wl_container_of(listener, view, unmap); + view->mapped = false; + if (is_toplevel(view)) + view_focus_next_toplevel(view->server); +} + +static void xwl_surface_destroy(struct wl_listener *listener, void *data) { + struct tinywl_view *view = wl_container_of(listener, view, destroy); + wl_list_remove(&view->link); + free(view); +} + +static void xwl_surface_configure(struct wl_listener *listener, void *data) { + struct tinywl_view *view = wl_container_of(listener, view, request_configure); + struct wlr_xwayland_surface_configure_event *event = data; + wlr_xwayland_surface_configure(view->xwayland_surface, event->x, event->y, + event->width, event->height); +} + +void server_new_xwayland_surface(struct wl_listener *listener, void *data) { + struct tinywl_server *server = + wl_container_of(listener, server, new_xwayland_surface); + struct wlr_xwayland_surface *xwayland_surface = data; + wlr_xwayland_surface_ping(xwayland_surface); + + struct tinywl_view *view = calloc(1, sizeof(struct tinywl_view)); + view->server = server; + view->type = LAB_XWAYLAND_VIEW; + view->xwayland_surface = xwayland_surface; + + view->map.notify = xwl_surface_map; + wl_signal_add(&xwayland_surface->events.map, &view->map); + view->unmap.notify = xwl_surface_unmap; + wl_signal_add(&xwayland_surface->events.unmap, &view->unmap); + view->destroy.notify = xwl_surface_destroy; + wl_signal_add(&xwayland_surface->events.destroy, &view->destroy); + view->request_configure.notify = xwl_surface_configure; + wl_signal_add(&xwayland_surface->events.request_configure, &view->request_configure); + + wl_list_insert(&server->views, &view->link); +} + static void xdg_surface_map(struct wl_listener *listener, void *data) { /* Called when the surface is mapped, or ready to display on-screen. */ struct tinywl_view *view = wl_container_of(listener, view, map); view->mapped = true; + view->been_mapped = true; + view->surface = view->xdg_surface->surface; focus_view(view, view->xdg_surface->surface); } @@ -685,6 +968,7 @@ static void xdg_surface_unmap(struct wl_listener *listener, void *data) { /* Called when the surface is unmapped, and should no longer be shown. */ struct tinywl_view *view = wl_container_of(listener, view, unmap); view->mapped = false; + view_focus_next_toplevel(view->server); } static void xdg_surface_destroy(struct wl_listener *listener, void *data) { @@ -759,6 +1043,7 @@ static void server_new_xdg_surface(struct wl_listener *listener, void *data) { struct tinywl_view *view = calloc(1, sizeof(struct tinywl_view)); view->server = server; + view->type = LAB_XDG_SHELL_VIEW; view->xdg_surface = xdg_surface; /* Listen to the various events it can emit */ @@ -781,7 +1066,7 @@ static void server_new_xdg_surface(struct wl_listener *listener, void *data) { } int main(int argc, char *argv[]) { - wlr_log_init(WLR_DEBUG, NULL); + wlr_log_init(WLR_ERROR, NULL); char *startup_cmd = NULL; int c; @@ -824,7 +1109,7 @@ int main(int argc, char *argv[]) { * necessary for clients to allocate surfaces and the data device manager * handles the clipboard. Each of these wlroots interfaces has room for you * to dig your fingers in and play with their behavior if you want. */ - wlr_compositor_create(server.wl_display, server.renderer); + server.compositor = wlr_compositor_create(server.wl_display, server.renderer); wlr_data_device_manager_create(server.wl_display); /* Creates an output layout, which a wlroots utility for working with an @@ -856,13 +1141,6 @@ int main(int argc, char *argv[]) { server.cursor = wlr_cursor_create(); wlr_cursor_attach_output_layout(server.cursor, server.output_layout); - /* Creates an xcursor manager, another wlroots utility which loads up - * Xcursor themes to source cursor images from and makes sure that cursor - * images are available at all scale factors on the screen (necessary for - * HiDPI support). We add a cursor theme at scale factor 1 to begin with. */ - server.cursor_mgr = wlr_xcursor_manager_create(NULL, 24); - wlr_xcursor_manager_load(server.cursor_mgr, 1); - /* * wlr_cursor *only* displays an image on screen. It does not move around * when the pointer moves. However, we can attach input devices to it, and @@ -919,6 +1197,32 @@ int main(int argc, char *argv[]) { /* Set the WAYLAND_DISPLAY environment variable to our socket and run the * startup command if requested. */ setenv("WAYLAND_DISPLAY", socket, true); + + wl_display_init_shm(server.wl_display); + + /* Init xwayland */ + server.xwayland = wlr_xwayland_create(server.wl_display, server.compositor, false); + server.new_xwayland_surface.notify = server_new_xwayland_surface; + wl_signal_add(&server.xwayland->events.new_surface, &server.new_xwayland_surface); + setenv("DISPLAY", server.xwayland->display_name, true); + wlr_xwayland_set_seat(server.xwayland, server.seat); + + /* Creates an xcursor manager, another wlroots utility which loads up + * Xcursor themes to source cursor images from and makes sure that cursor + * images are available at all scale factors on the screen (necessary for + * HiDPI support). We add a cursor theme at scale factor 1 to begin with. */ + server.cursor_mgr = wlr_xcursor_manager_create(XCURSOR_DEFAULT, XCURSOR_SIZE); + wlr_xcursor_manager_load(server.cursor_mgr, 1); + + struct wlr_xcursor *xcursor = + wlr_xcursor_manager_get_xcursor(server.cursor_mgr, XCURSOR_DEFAULT, 1); + if (xcursor) { + struct wlr_xcursor_image *image = xcursor->images[0]; + wlr_xwayland_set_cursor(server.xwayland, image->buffer, + image->width * 4, image->width, image->height, + image->hotspot_x, image->hotspot_y); + } + if (startup_cmd) { if (fork() == 0) { execl("/bin/sh", "/bin/sh", "-c", startup_cmd, (void *)NULL); @@ -933,6 +1237,8 @@ int main(int argc, char *argv[]) { wl_display_run(server.wl_display); /* Once wl_display_run returns, we shut down the server. */ + wlr_xwayland_destroy(server.xwayland); + wlr_xcursor_manager_destroy(server.cursor_mgr); wl_display_destroy_clients(server.wl_display); wl_display_destroy(server.wl_display); return 0;