diff --git a/include/sway/tree/arrange.h b/include/sway/tree/arrange.h index 06a2279c1..2e7ed7c63 100644 --- a/include/sway/tree/arrange.h +++ b/include/sway/tree/arrange.h @@ -7,6 +7,7 @@ struct sway_container; struct sway_node; void arrange_container(struct sway_container *container); +void next_sibling_container_geometry(struct sway_container *child, struct sway_container *sibling, bool fullscreen); void arrange_workspace(struct sway_workspace *workspace); diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h index 14aad1a18..047eefe1d 100644 --- a/include/sway/tree/view.h +++ b/include/sway/tree/view.h @@ -297,6 +297,21 @@ void view_begin_destroy(struct sway_view *view); void view_map(struct sway_view *view, struct wlr_surface *wlr_surface, bool fullscreen, struct wlr_output *fullscreen_output, bool decoration); +/** + * Prepare the view for its upcoming mapping, sending the intended dimensions + * so that the first frame has a chance of being correct. If CSD preferences or + * floating tendency changes, this may turn out to be inaccurate but no worse + * than skipping the step. + * + * `fullscreen` should be set to true (and optionally `fullscreen_output` + * should be populated) if the view should be made fullscreen immediately. + * + * `decoration` should be set to true if the client prefers CSD. The client's + * preference may be ignored. + */ +void view_premap(struct sway_view *view, struct wlr_surface *wlr_surface, + bool fullscreen, struct wlr_output *fullscreen_output, bool decoration); + void view_unmap(struct sway_view *view); void view_update_size(struct sway_view *view); diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c index 3aed4ec7e..c0bf20f48 100644 --- a/sway/desktop/xdg_shell.c +++ b/sway/desktop/xdg_shell.c @@ -287,10 +287,23 @@ static void handle_commit(struct wl_listener *listener, void *data) { if (view->xdg_decoration != NULL) { set_xdg_decoration_mode(view->xdg_decoration); } - // XXX: https://github.com/swaywm/sway/issues/2176 + + bool csd = false; + if (view->xdg_decoration) { + enum wlr_xdg_toplevel_decoration_v1_mode mode = + view->xdg_decoration->wlr_xdg_decoration->requested_mode; + csd = mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; + } else { + struct sway_server_decoration *deco = + decoration_from_surface(xdg_surface->surface); + csd = !deco || deco->wlr_server_decoration->mode == + WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT; + } + wlr_xdg_surface_schedule_configure(xdg_surface); wlr_xdg_toplevel_set_wm_capabilities(view->wlr_xdg_toplevel, XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); + view_premap(&xdg_shell_view->view, xdg_surface->surface, false, NULL, csd); // TODO: wlr_xdg_toplevel_set_bounds() return; } diff --git a/sway/tree/arrange.c b/sway/tree/arrange.c index d4003fe65..7bfd41a06 100644 --- a/sway/tree/arrange.c +++ b/sway/tree/arrange.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -249,6 +250,87 @@ void arrange_container(struct sway_container *container) { node_set_dirty(&container->node); } +void next_sibling_container_geometry(struct sway_container *child, struct sway_container *sibling, bool fullscreen) { + assert(child->view); + struct sway_workspace *workspace = child->pending.workspace; + if (!workspace->output || workspace->width == 0 || workspace->height == 0) { + return; + } + if (workspace->fullscreen && !fullscreen) { + return; + } + + struct sway_output *output = workspace->output; + if (fullscreen) { + child->pending.x = output->lx; + child->pending.y = output->ly; + child->pending.width = output->width; + child->pending.height = output->height; + view_autoconfigure(child->view); + return; + } + + list_t *siblings; + struct wlr_box box; + enum sway_container_layout layout; + + if (sibling && sibling->pending.parent) { + struct sway_container *parent = sibling->pending.parent; + siblings = parent->pending.children; + layout = parent->pending.layout; + workspace_get_box(workspace, &box); + } else { + siblings = workspace->tiling; + layout = workspace->layout; + workspace_get_box(workspace, &box); + } + + // We only want to mutate the specified child, not its siblings. Make a + // shallow cloned list of siblings and containers so that their updated + // geometry can be thrown away. + list_t *children = create_list(); + if (!children) { + return; + } + struct sway_container *cons = calloc(siblings->length-1, sizeof(*cons)); + if (!cons) { + list_free(children); + return; + } + for (int idx = 0, ydx = 0; idx < siblings->length; idx++, ydx++) { + if (siblings->items[idx] == child) { + list_add(children, child); + ydx--; + continue; + } + cons[ydx] = *(struct sway_container *)siblings->items[idx]; + list_add(children, &cons[ydx]); + } + + // Update the geometry + switch (layout) { + case L_HORIZ: + apply_horiz_layout(children, &box); + break; + case L_VERT: + apply_vert_layout(children, &box); + break; + case L_TABBED: + apply_tabbed_layout(children, &box); + break; + case L_STACKED: + apply_stacked_layout(children, &box); + break; + case L_NONE: + apply_horiz_layout(children, &box); + break; + } + + view_autoconfigure(child->view); + list_free(children); + free(cons); +} + void arrange_workspace(struct sway_workspace *workspace) { if (config->reloading) { return; diff --git a/sway/tree/view.c b/sway/tree/view.c index d25a09c2a..d55d48e02 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -714,6 +714,100 @@ static void handle_foreign_destroy( wl_list_remove(&view->foreign_destroy.link); } +void view_premap(struct sway_view *view, struct wlr_surface *wlr_surface, + bool fullscreen, struct wlr_output *fullscreen_output, + bool decoration) { + + // If there is a request to be opened fullscreen on a specific output, try + // to honor that request. Otherwise, fallback to assigns, pid mappings, + // focused workspace, etc + struct sway_workspace *ws = NULL; + if (fullscreen_output && fullscreen_output->data) { + struct sway_output *output = fullscreen_output->data; + ws = output_get_active_workspace(output); + } + if (!ws) { + ws = select_workspace(view); + } + if (!ws || !ws->output) { + // Nothing for us to do if we don't have a workspace on an output + return; + } + + // Once the output is determined, we can notify the client early about + // scale to reduce startup jitter. + float scale = ws->output->wlr_output->scale; + wlr_fractional_scale_v1_notify_scale(wlr_surface, scale); + wlr_surface_set_preferred_buffer_scale(wlr_surface, ceil(scale)); + + if (view->impl->wants_floating && view->impl->wants_floating(view)) { + // Nothing more to do for floating, let it pick its own dimensions + return; + } + + struct sway_seat *seat = input_manager_current_seat(); + struct sway_node *node = seat_get_focus_inactive(seat, &ws->node); + struct sway_container *target_sibling = NULL; + if (node && node->type == N_CONTAINER) { + if (container_is_floating(node->sway_container)) { + // If we're about to launch the view into the floating container, then + // launch it as a tiled view instead. + target_sibling = seat_get_focus_inactive_tiling(seat, ws); + if (target_sibling) { + struct sway_container *con = + seat_get_focus_inactive_view(seat, &target_sibling->node); + if (con) { + target_sibling = con; + } + } + } else { + target_sibling = node->sway_container; + } + } + + // Fill out enough of a dummy container to satisfy configuration + struct sway_container con = { + .view = view, + .pending = (struct sway_container_state) { + .workspace = ws, + .parent = target_sibling ? target_sibling->pending.parent : NULL, + .border = config->border, + .border_thickness = config->border_thickness, + } + }; + view->container = &con; + + // Insert the container into the appropriate children list so that smart + // gaps will work correctly + list_t *siblings; + int sibling_index; + if (target_sibling && target_sibling->pending.parent) { + struct sway_container *parent = target_sibling->pending.parent; + siblings = parent->pending.children; + sibling_index = list_find(siblings, target_sibling) + 1; + } else { + siblings = ws->tiling; + sibling_index = ws->tiling->length; + } + list_insert(siblings, sibling_index, &con); + + view_set_tiled(view, true); + view_update_csd_from_client(view, decoration); + next_sibling_container_geometry(&con, target_sibling, fullscreen); + + // Send the configure event for the calculated dimensions + view_configure(view, + con.pending.content_x, + con.pending.content_y, + con.pending.content_width, + con.pending.content_height); + + sway_assert(siblings->items[sibling_index] == &con, + "container siblings mutated unexpectedly"); + list_del(siblings, sibling_index); + view->container = NULL; +} + void view_map(struct sway_view *view, struct wlr_surface *wlr_surface, bool fullscreen, struct wlr_output *fullscreen_output, bool decoration) {