diff --git a/README.md b/README.md index fd72785..b8cddb4 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,7 @@ given the base on which it is built. Implemented default features are: - Support screen lockers via ext-session-lock-v1 protocol - Various Wayland protocols - XWayland support as provided by wlroots (can be enabled in `config.mk`) +- Zero flickering - Wayland users naturally expect that "every frame is perfect" - Layer shell popups (used by Waybar) - Damage tracking provided by scenegraph API diff --git a/client.h b/client.h index eb1bb47..ebe665b 100644 --- a/client.h +++ b/client.h @@ -258,48 +258,6 @@ client_is_float_type(Client *c) (min.width == max.width || min.height == max.height)); } -static inline int -client_is_rendered_on_mon(Client *c, Monitor *m) -{ - /* This is needed for when you don't want to check formal assignment, - * but rather actual displaying of the pixels. - * Usually VISIBLEON suffices and is also faster. */ - struct wlr_surface_output *s; - int unused_lx, unused_ly; - if (!wlr_scene_node_coords(&c->scene->node, &unused_lx, &unused_ly)) - return 0; - wl_list_for_each(s, &client_surface(c)->current_outputs, link) - if (s->output == m->wlr_output) - return 1; - return 0; -} - -static inline int -client_is_stopped(Client *c) -{ - int pid; - siginfo_t in = {0}; -#ifdef XWAYLAND - if (client_is_x11(c)) - return 0; -#endif - - wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); - if (waitid(P_PID, pid, &in, WNOHANG|WCONTINUED|WSTOPPED|WNOWAIT) < 0) { - /* This process is not our child process, while is very unlikely that - * it is stopped, in order to do not skip frames, assume that it is. */ - if (errno == ECHILD) - return 1; - } else if (in.si_pid) { - if (in.si_code == CLD_STOPPED || in.si_code == CLD_TRAPPED) - return 1; - if (in.si_code == CLD_CONTINUED) - return 0; - } - - return 0; -} - static inline int client_is_unmanaged(Client *c) { @@ -366,19 +324,36 @@ client_set_scale(struct wlr_surface *s, float scale) wlr_surface_set_preferred_buffer_scale(s, (int32_t)ceilf(scale)); } -static inline void +/* Returns nonzero if the client has to commit a buffer with the new size */ +static inline uint32_t client_set_size(Client *c, uint32_t width, uint32_t height) { #ifdef XWAYLAND if (client_is_x11(c)) { wlr_xwayland_surface_configure(c->surface.xwayland, c->geom.x + c->bw, c->geom.y + c->bw, width, height); - return; + return (int)width != client_surface(c)->current.width + || (int)height != client_surface(c)->current.height; } #endif - if ((int32_t)width != c->surface.xdg->toplevel->current.width - || (int32_t)height != c->surface.xdg->toplevel->current.height) - wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, (int32_t)width, (int32_t)height); + if ((int32_t)width == c->surface.xdg->toplevel->scheduled.width + && (int32_t)height == c->surface.xdg->toplevel->scheduled.height) + return 0; + return wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, (int32_t)width, (int32_t)height); +} + +static inline void +client_send_frame_done_iterator(struct wlr_surface *s, int sx, int sy, void *data) +{ + wlr_surface_send_frame_done(s, data); +} + +static inline void +client_send_frame_done(Client *c) +{ + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_surface_for_each_surface(client_surface(c), client_send_frame_done_iterator, &now); } static inline void diff --git a/dwl.c b/dwl.c index e796d05..a813938 100644 --- a/dwl.c +++ b/dwl.c @@ -126,6 +126,9 @@ typedef struct { struct wl_list flink; struct wlr_box geom; /* layout-relative, includes border */ struct wlr_box prev; /* layout-relative, includes border */ + struct wlr_box current; /* geom currently shown in the scene graph */ + struct wlr_scene_node *snapshot; /* content shown while resizing */ + uint32_t resize_serial; /* nonzero while waiting for a resized buffer */ struct wlr_box bounds; /* only width and height are used */ union { struct wlr_xdg_surface *xdg; @@ -262,6 +265,7 @@ typedef struct { /* function declarations */ static void applybounds(Client *c, struct wlr_box *bbox); +static void applygeom(Client *c); static void applyrules(Client *c); static void arrange(Monitor *m); static void arrangelayer(Monitor *m, struct wl_list *list, @@ -324,6 +328,7 @@ static void mapnotify(struct wl_listener *listener, void *data); static void maximizenotify(struct wl_listener *listener, void *data); static void monocle(Monitor *m); static void motionabsolute(struct wl_listener *listener, void *data); +static char **monoff(struct wlr_output *wlr_output); static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, double sy, double sx_unaccel, double sy_unaccel); static void motionrelative(struct wl_listener *listener, void *data); @@ -349,9 +354,12 @@ static void setfullscreen(Client *c, int fullscreen); static void setlayout(const Arg *arg); static void setmfact(const Arg *arg); static void setmon(Client *c, Monitor *m, uint32_t newtags); +static void setmonoff(struct wlr_output *wlr_output, int off); static void setpsel(struct wl_listener *listener, void *data); static void setsel(struct wl_listener *listener, void *data); static void setup(void); +static struct wlr_scene_node *snapshot(struct wlr_scene_node *node, + struct wlr_scene_tree *parent, void *data); static void spawn(const Arg *arg); static void startdrag(struct wl_listener *listener, void *data); static void tag(const Arg *arg); @@ -361,6 +369,11 @@ static void togglefloating(const Arg *arg); static void togglefullscreen(const Arg *arg); static void toggletag(const Arg *arg); static void toggleview(const Arg *arg); +static void transactionapply(void); +static void transactioncheck(void *data); +static void transactioncommit(Client *c, int ready); +static void transactionschedule(void); +static int transactiontimeout(void *data); static void unlocksession(struct wl_listener *listener, void *data); static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); static void unmapnotify(struct wl_listener *listener, void *data); @@ -432,6 +445,15 @@ static struct wl_list mons; static Monitor *selmon; static int consumed[KEY_MAX + 1]; +/* Transactions: the scene graph is updated only once all visible clients have + * committed buffers with their new size, or after TRANSACTION_TIMEOUT ms */ +#define TRANSACTION_TIMEOUT 100 +static struct wl_event_source *transaction_idle; +static struct wl_event_source *transaction_timer; +static int transaction_waiting; +static struct wl_array ghosts; /* struct wlr_scene_node *, unmapped clients */ +static struct wl_array offmons; /* char *, names of outputs turned off by clients */ + /* global event handlers */ static struct wl_listener cursor_axis = {.notify = axisnotify}; static struct wl_listener cursor_button = {.notify = buttonpress}; @@ -460,12 +482,14 @@ static struct wl_listener request_set_sel = {.notify = setsel}; static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape}; static struct wl_listener request_start_drag = {.notify = requeststartdrag}; static struct wl_listener start_drag = {.notify = startdrag}; +static struct wl_listener drag_icon_destroy = {.notify = destroydragicon}; static struct wl_listener new_session_lock = {.notify = locksession}; static struct wl_listener new_foreign_toplevel_capture_request = {.notify = capturerequest}; #ifdef XWAYLAND static void activatex11(struct wl_listener *listener, void *data); static void associatex11(struct wl_listener *listener, void *data); +static void commitx11(struct wl_listener *listener, void *data); static void configurex11(struct wl_listener *listener, void *data); static void createnotifyx11(struct wl_listener *listener, void *data); static void dissociatex11(struct wl_listener *listener, void *data); @@ -516,6 +540,41 @@ applybounds(Client *c, struct wlr_box *bbox) c->geom.y = bbox->y; } +void +applygeom(Client *c) +{ + struct wlr_box clip; + Client *p = client_get_parent(c); + + /* We move all floating clients to LyrTile while in floating layout + * to avoid "real" floating clients be always on top */ + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen + || (p && p->isfullscreen) ? LyrFS + : c->isfloating && c->mon->lt[c->mon->sellt]->arrange ? LyrFloat + : LyrTile]); + + c->current = c->geom; + if (c->snapshot) { + wlr_scene_node_destroy(c->snapshot); + c->snapshot = NULL; + wlr_scene_node_set_enabled(&c->scene_surface->node, 1); + } + + /* Update scene-graph, including borders */ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); + wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); + wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); + wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); + + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); +} + void applyrules(Client *c) { @@ -555,37 +614,18 @@ arrange(Monitor *m) return; wl_list_for_each(c, &clients, link) { - if (c->mon == m) { - wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); + if (c->mon == m) client_set_suspended(c, !VISIBLEON(c, m) && (!c->image_capture_source || wl_list_empty(&c->image_capture_source->resources))); - } } - wlr_scene_node_set_enabled(&m->fullscreen_bg->node, - (c = focustop(m)) && c->isfullscreen); - strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); - /* We move all clients (except fullscreen and unmanaged) to LyrTile while - * in floating layout to avoid "real" floating clients be always on top */ - wl_list_for_each(c, &clients, link) { - if (c->mon != m || c->scene->node.parent == layers[LyrFS]) - continue; - - wlr_scene_node_reparent(&c->scene->node, - (!m->lt[m->sellt]->arrange && c->isfloating) - ? layers[LyrTile] - : (m->lt[m->sellt]->arrange && c->isfloating) - ? layers[LyrFloat] - : c->scene->node.parent); - } - if (m->lt[m->sellt]->arrange) m->lt[m->sellt]->arrange(m); - motionnotify(0, NULL, 0, 0, 0, 0); - checkidleinhibitor(NULL); + + transactionschedule(); } void @@ -642,7 +682,6 @@ arrangelayers(Monitor *m) l->layer_surface->current.keyboard_interactive != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE || !l->mapped) continue; - /* Deactivate the focused client. */ focusclient(NULL, l, 0); return; } @@ -789,6 +828,8 @@ checkidleinhibitor(struct wlr_surface *exclude) void cleanup(void) { + char **name; + cleanuplisteners(); #ifdef XWAYLAND wlr_xwayland_destroy(xwayland); @@ -810,6 +851,14 @@ cleanup(void) * Destroy it until it's fixed on the wlroots side */ wlr_backend_destroy(backend); + if (transaction_idle) + wl_event_source_remove(transaction_idle); + wl_event_source_remove(transaction_timer); + wl_array_release(&ghosts); + wl_array_for_each(name, &offmons) + free(*name); + wl_array_release(&offmons); + wl_display_destroy(dpy); /* Destroy after the wayland display (when the monitors are already destroyed) to avoid destroying them with an invalid scene output. */ @@ -877,8 +926,10 @@ cleanuplisteners(void) wl_list_remove(&new_session_lock.link); wl_list_remove(&new_foreign_toplevel_capture_request.link); #ifdef XWAYLAND - wl_list_remove(&new_xwayland_surface.link); - wl_list_remove(&xwayland_ready.link); + if (xwayland) { + wl_list_remove(&new_xwayland_surface.link); + wl_list_remove(&xwayland_ready.link); + } #endif } @@ -989,6 +1040,8 @@ commitnotify(struct wl_listener *listener, void *data) return; } + transactioncommit(c, c->resize_serial && c->surface.xdg->current.configure_serial + >= c->resize_serial); resize(c, c->geom, (c->isfloating && !c->isfullscreen)); } @@ -1015,8 +1068,8 @@ commitpopup(struct wl_listener *listener, void *data) return; } box = type == LayerShell ? l->mon->m : c->mon->w; - box.x -= (type == LayerShell ? l->scene->node.x : c->geom.x); - box.y -= (type == LayerShell ? l->scene->node.y : c->geom.y); + box.x -= (type == LayerShell ? l->scene->node.x : c->geom.x - c->surface.xdg->geometry.x); + box.y -= (type == LayerShell ? l->scene->node.y : c->geom.y - c->surface.xdg->geometry.y); wlr_xdg_popup_unconstrain_from_box(popup, &box); wl_list_remove(&listener->link); free(listener); @@ -1185,14 +1238,17 @@ createmon(struct wl_listener *listener, void *data) * monitor supports only a specific set of modes. We just pick the * monitor's preferred mode; a more sophisticated compositor would let * the user configure it. */ - wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); - /* Set up event listeners */ LISTEN(&wlr_output->events.frame, &m->frame, rendermon); LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon); LISTEN(&wlr_output->events.request_state, &m->request_state, requestmonstate); - wlr_output_state_set_enabled(&state, 1); + /* Some monitors disconnect when they are turned off and reconnect shortly + * after, so keep outputs that were turned off disabled. */ + if (!monoff(wlr_output)) { + wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); + wlr_output_state_set_enabled(&state, 1); + } wlr_output_commit_state(wlr_output, &state); wlr_output_state_finish(&state); @@ -1218,6 +1274,12 @@ createmon(struct wl_listener *listener, void *data) * output (such as DPI, scale factor, manufacturer, etc). */ m->scene_output = wlr_scene_output_create(scene, wlr_output); + + if (!wlr_output->enabled) { + updatemons(NULL, NULL); + return; + } + if (m->m.x == -1 && m->m.y == -1) wlr_output_layout_add_auto(output_layout, wlr_output); else @@ -1362,7 +1424,6 @@ destroydragicon(struct wl_listener *listener, void *data) focusclient(focustop(selmon), NULL, 1); motionnotify(0, NULL, 0, 0, 0, 0); wl_list_remove(&listener->link); - free(listener); } void @@ -1518,7 +1579,7 @@ dirtomon(enum wlr_direction dir) void focusclient(Client *c, LayerSurface *l, int lift) { - struct wlr_surface *old = seat->keyboard_state.focused_surface; + struct wlr_surface *surface, *old = seat->keyboard_state.focused_surface; int unused_lx, unused_ly, old_client_type; Client *old_c = NULL; LayerSurface *old_l = NULL; @@ -1549,7 +1610,6 @@ focusclient(Client *c, LayerSurface *l, int lift) /* Don't change border color if there is an exclusive focus or we are * handling a drag operation */ if (!(old_l && old_l->layer_surface->current.keyboard_interactive == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) - && !(old_c && client_wants_focus(old_c)) && !seat->drag) client_set_border_color(c, focuscolor); } @@ -1574,31 +1634,23 @@ focusclient(Client *c, LayerSurface *l, int lift) } printstatus(); - if (l) { - /* Focus a layer surface and let the input method follow it */ - input_method_relay_set_focus(input_method_relay, l->layer_surface->surface); - client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat)); - return; - } - - if (!c) { + if (!c && !l) { /* With no client, all we have left is to clear focus */ input_method_relay_set_focus(input_method_relay, NULL); wlr_seat_keyboard_notify_clear_focus(seat); return; } - /* Change cursor surface */ + surface = c ? client_surface(c) : l->layer_surface->surface; + input_method_relay_set_focus(input_method_relay, surface); + client_notify_enter(surface, wlr_seat_get_keyboard(seat)); + + if (l) + return; + + client_activate_surface(surface, 1); motionnotify(0, NULL, 0, 0, 0, 0); - input_method_relay_set_focus(input_method_relay, client_surface(c)); - - /* Have a client, so focus its top-level wlr_surface */ - client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat)); - - /* Activate the new client */ - client_activate_surface(client_surface(c), 1); - if (!client_is_unmanaged(c)) focused_client = c; } @@ -1924,8 +1976,15 @@ mapnotify(struct wl_listener *listener, void *data) } /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | + WLR_EDGE_RIGHT); c->geom.width += 2 * c->bw; c->geom.height += 2 * c->bw; + if (client_is_x11(c)) { + int x = c->geom.x - (int)c->bw, y = c->geom.y - (int)c->bw; + c->geom.x = c->geom.x < sgeom.x ? x : MAX(x, sgeom.x); + c->geom.y = c->geom.y < sgeom.y ? y : MAX(y, sgeom.y); + } /* Insert this client into client lists. */ wl_list_insert(&clients, &c->link); @@ -2019,6 +2078,17 @@ motionabsolute(struct wl_listener *listener, void *data) motionnotify(event->time_msec, &event->pointer->base, dx, dy, dx, dy); } +char ** +monoff(struct wlr_output *wlr_output) +{ + char **name; + wl_array_for_each(name, &offmons) { + if (!strcmp(*name, wlr_output->name)) + return name; + } + return NULL; +} + void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, double dx_unaccel, double dy_unaccel) @@ -2205,6 +2275,7 @@ outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test) /* Ensure displays previously disabled by wlr-output-power-management-v1 * are properly handled*/ m->asleep = 0; + setmonoff(wlr_output, !config_head->state.enabled); /* Don't move monitors if position wouldn't change. This avoids * wlroots marking the output as manually configured. @@ -2323,6 +2394,10 @@ powermgrsetmode(struct wl_listener *listener, void *data) m->gamma_lut_changed = 1; /* Reapply gamma LUT when re-enabling the output */ wlr_output_state_set_enabled(&state, event->mode); + /* The output was reconnected while off and was never modeset */ + if (event->mode && !m->wlr_output->current_mode) + wlr_output_state_set_mode(&state, wlr_output_preferred_mode(m->wlr_output)); + setmonoff(m->wlr_output, !event->mode); wlr_output_commit_state(m->wlr_output, &state); m->asleep = !event->mode; @@ -2385,7 +2460,7 @@ void resize(Client *c, struct wlr_box geo, int interact) { struct wlr_box *bbox; - struct wlr_box clip; + uint32_t serial; if (!c->mon || !client_surface(c)->mapped) return; @@ -2396,20 +2471,27 @@ resize(Client *c, struct wlr_box geo, int interact) c->geom = geo; applybounds(c, bbox); - /* Update scene-graph, including borders */ - wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); - wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); - wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); - wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); - wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); - wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); - wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); - wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); - wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); + if ((serial = client_set_size(c, c->geom.width - 2 * c->bw, + c->geom.height - 2 * c->bw))) + c->resize_serial = serial; - client_set_size(c, c->geom.width - 2 * c->bw, c->geom.height - 2 * c->bw); - client_get_clip(c, &clip); - wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); + if (interact || (!c->resize_serial && wlr_box_equal(&c->geom, &c->current))) { + c->resize_serial = 0; + applygeom(c); + return; + } + + /* Until the transaction is applied, keep showing the current content + * instead of buffers with the new size at the old position. */ + if (c->resize_serial) { + if (!c->snapshot && c->scene->node.enabled + && (c->snapshot = snapshot(&c->scene_surface->node, c->scene, c))) + wlr_scene_node_set_enabled(&c->scene_surface->node, 0); + /* The scene graph doesn't send frame done events to hidden + * surfaces, but the client may be waiting for one to redraw */ + client_send_frame_done(c); + } + transactionschedule(); } void @@ -2510,14 +2592,10 @@ setcursorshape(struct wl_listener *listener, void *data) void setfloating(Client *c, int floating) { - Client *p = client_get_parent(c); c->isfloating = floating; /* If in floating layout do not change the client's layer */ if (!c->mon || !client_surface(c)->mapped || !c->mon->lt[c->mon->sellt]->arrange) return; - wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen || - (p && p->isfullscreen) ? LyrFS - : c->isfloating ? LyrFloat : LyrTile]); arrange(c->mon); printstatus(); } @@ -2530,8 +2608,6 @@ setfullscreen(Client *c, int fullscreen) return; c->bw = fullscreen ? 0 : borderpx; client_set_fullscreen(c, fullscreen); - wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen - ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); if (fullscreen) { c->prev = c->geom; @@ -2597,6 +2673,22 @@ setmon(Client *c, Monitor *m, uint32_t newtags) focusclient(focustop(selmon), NULL, 1); } +void +setmonoff(struct wlr_output *wlr_output, int off) +{ + char **name = monoff(wlr_output), **last; + + if (off && !name && (name = wl_array_add(&offmons, sizeof(*name)))) { + if (!(*name = strdup(wlr_output->name))) + offmons.size -= sizeof(*name); + } else if (!off && name) { + free(*name); + last = (char **)((char *)offmons.data + offmons.size) - 1; + *name = *last; + offmons.size -= sizeof(*name); + } +} + void setpsel(struct wl_listener *listener, void *data) { @@ -2735,6 +2827,10 @@ setup(void) wl_list_init(&clients); wl_list_init(&fstack); + transaction_timer = wl_event_loop_add_timer(event_loop, transactiontimeout, NULL); + wl_array_init(&ghosts); + wl_array_init(&offmons); + xdg_shell = wlr_xdg_shell_create(dpy, 6); wl_signal_add(&xdg_shell->events.new_toplevel, &new_xdg_toplevel); wl_signal_add(&xdg_shell->events.new_popup, &new_xdg_popup); @@ -2856,6 +2952,57 @@ setup(void) #endif } +struct wlr_scene_node * +snapshot(struct wlr_scene_node *node, struct wlr_scene_tree *parent, void *data) +{ + /* Copy the visible part of a scene graph subtree, keeping references to + * the buffers so that they can be shown after the client changes them */ + struct wlr_scene_node *child, *copy; + struct wlr_scene_tree *tree; + struct wlr_scene_rect *rect; + struct wlr_scene_buffer *src, *buffer; + + if (!node->enabled) + return NULL; + + switch (node->type) { + case WLR_SCENE_NODE_TREE: + tree = wlr_scene_tree_create(parent); + wl_list_for_each(child, &wlr_scene_tree_from_node(node)->children, link) + snapshot(child, tree, data); + copy = &tree->node; + break; + case WLR_SCENE_NODE_RECT: + rect = wlr_scene_rect_from_node(node); + copy = &wlr_scene_rect_create(parent, rect->width, rect->height, rect->color)->node; + break; + case WLR_SCENE_NODE_BUFFER: + src = wlr_scene_buffer_from_node(node); + buffer = wlr_scene_buffer_create(parent, NULL); + wlr_scene_buffer_set_dest_size(buffer, src->dst_width, src->dst_height); + wlr_scene_buffer_set_opaque_region(buffer, &src->opaque_region); + wlr_scene_buffer_set_opacity(buffer, src->opacity); + wlr_scene_buffer_set_filter_mode(buffer, src->filter_mode); + wlr_scene_buffer_set_transfer_function(buffer, src->transfer_function); + wlr_scene_buffer_set_primaries(buffer, src->primaries); + wlr_scene_buffer_set_color_encoding(buffer, src->color_encoding); + wlr_scene_buffer_set_color_range(buffer, src->color_range); + wlr_scene_buffer_set_source_box(buffer, &src->src_box); + wlr_scene_buffer_set_transform(buffer, src->transform); + wlr_scene_buffer_set_buffer(buffer, src->buffer); + copy = &buffer->node; + break; + default: + return NULL; + } + + copy->data = data; + wlr_scene_node_set_position(copy, node->x, node->y); + if (node->parent == parent) + wlr_scene_node_place_above(copy, node); + return copy; +} + void spawn(const Arg *arg) { @@ -2875,7 +3022,7 @@ startdrag(struct wl_listener *listener, void *data) return; drag->icon->data = &wlr_scene_drag_icon_create(drag_icon, drag->icon)->node; - LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); + wl_signal_add(&drag->icon->events.destroy, &drag_icon_destroy); } void @@ -2977,6 +3124,88 @@ toggleview(const Arg *arg) printstatus(); } +void +transactionapply(void) +{ + struct wlr_scene_node **ghost; + Client *c; + Monitor *m; + + wl_event_source_timer_update(transaction_timer, 0); + transaction_waiting = 0; + + wl_array_for_each(ghost, &ghosts) + wlr_scene_node_destroy(*ghost); + ghosts.size = 0; + + wl_list_for_each(c, &clients, link) { + c->resize_serial = 0; + if (!c->mon) + continue; + if (c->mon->wlr_output->enabled) + wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, c->mon)); + applygeom(c); + } + + wl_list_for_each(m, &mons, link) { + if (m->wlr_output->enabled) + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, + (c = focustop(m)) && c->isfullscreen); + } + + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); +} + +void +transactioncheck(void *data) +{ + Client *c; + + transaction_idle = NULL; + + /* Hidden clients don't have to redraw before the layout is shown */ + wl_list_for_each(c, &clients, link) { + if (c->resize_serial && VISIBLEON(c, c->mon) && c->mon->wlr_output->enabled) { + if (!transaction_waiting) { + /* Don't restart the timer on updates of the pending + * layout, so the delay is bounded */ + wl_event_source_timer_update(transaction_timer, TRANSACTION_TIMEOUT); + transaction_waiting = 1; + } + return; + } + } + + transactionapply(); +} + +void +transactioncommit(Client *c, int ready) +{ + if (c->resize_serial && ready) { + c->resize_serial = 0; + transactionschedule(); + } else if (c->snapshot) { + /* This buffer is not shown, let the client draw again */ + client_send_frame_done(c); + } +} + +void +transactionschedule(void) +{ + if (!transaction_idle) + transaction_idle = wl_event_loop_add_idle(event_loop, transactioncheck, NULL); +} + +int +transactiontimeout(void *data) +{ + transactionapply(); + return 0; +} + void unlocksession(struct wl_listener *listener, void *data) { @@ -3003,6 +3232,7 @@ unmapnotify(struct wl_listener *listener, void *data) { /* Called when the surface is unmapped, and should no longer be shown. */ Client *c = wl_container_of(listener, c, unmap); + struct wlr_scene_node *ghost, **slot; if (c == grabc) { cursor_mode = CurNormal; grabc = NULL; @@ -3011,6 +3241,15 @@ unmapnotify(struct wl_listener *listener, void *data) focused_client = NULL; if (!client_is_unmanaged(c)) { + /* Keep showing the client until the transaction rearranging the + * remaining ones is applied, to not show a gap */ + if ((ghost = snapshot(&c->scene->node, c->scene->node.parent, NULL))) { + if ((slot = wl_array_add(&ghosts, sizeof(ghost)))) + *slot = ghost; + else + wlr_scene_node_destroy(ghost); + transactionschedule(); + } wl_list_remove(&c->link); setmon(c, NULL, 0); wl_list_remove(&c->flink); @@ -3034,6 +3273,9 @@ unmapnotify(struct wl_listener *listener, void *data) c->image_capture_source = NULL; wlr_scene_node_destroy(&c->scene->node); client_surface(c)->data = NULL; + c->snapshot = NULL; + c->resize_serial = 0; + c->current = (struct wlr_box){0}; printstatus(); motionnotify(0, NULL, 0, 0, 0, 0); } @@ -3233,20 +3475,26 @@ xytonode(double x, double y, struct wlr_surface **psurface, Client **pc, LayerSurface **pl, double *nx, double *ny) { struct wlr_scene_node *node; + struct wlr_scene_surface *scene_surface; struct wlr_surface *surface = NULL; + Client *c = NULL; int layer; - for (layer = NUM_LAYERS - 1; !surface && layer >= (locked ? LyrBlock : 0); layer--) { + for (layer = NUM_LAYERS - 1; !surface && !c && layer >= (locked ? LyrBlock : 0); layer--) { if (layer == LyrIMPopup || - !(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) + !(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny)) + || node->type != WLR_SCENE_NODE_BUFFER) continue; - if (node->type == WLR_SCENE_NODE_BUFFER) - surface = wlr_scene_surface_try_from_buffer( - wlr_scene_buffer_from_node(node))->surface; + if ((scene_surface = wlr_scene_surface_try_from_buffer( + wlr_scene_buffer_from_node(node)))) + surface = scene_surface->surface; + else /* snapshot of a client during a transaction */ + c = node->data; } if (psurface) *psurface = surface; + if (pc) *pc = c; toplevel_from_wlr_surface(surface, pc, pl); } @@ -3299,10 +3547,21 @@ associatex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, associate); + LISTEN(&client_surface(c)->events.commit, &c->commit, commitx11); LISTEN(&client_surface(c)->events.map, &c->map, mapnotify); LISTEN(&client_surface(c)->events.unmap, &c->unmap, unmapnotify); } +void +commitx11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, commit); + struct wlr_surface *surface = client_surface(c); + + transactioncommit(c, surface->current.width == c->geom.width - 2 * (int)c->bw + && surface->current.height == c->geom.height - 2 * (int)c->bw); +} + void configurex11(struct wl_listener *listener, void *data) { @@ -3351,6 +3610,7 @@ void dissociatex11(struct wl_listener *listener, void *data) { Client *c = wl_container_of(listener, c, dissociate); + wl_list_remove(&c->commit.link); wl_list_remove(&c->map.link); wl_list_remove(&c->unmap.link); }