tree/view: swallow matching views into placeholders

Before container_create in view_map, look up an unfilled
placeholder whose swallows match. On hit, install the view in
place and skip the workspace/sibling placement path. Border
config is preserved; for_window still runs post-placement.
This commit is contained in:
codegax 2026-04-30 22:18:00 -06:00
parent cdb955f699
commit 40bb60a91d
2 changed files with 137 additions and 38 deletions

View file

@ -11,9 +11,11 @@
#include "stringop.h"
#include "sway/criteria.h"
#include "sway/desktop/transaction.h"
#include "sway/output.h"
#include "sway/tree/arrange.h"
#include "sway/tree/container.h"
#include "sway/tree/load_layout.h"
#include "sway/tree/root.h"
#include "sway/tree/view.h"
#include "sway/tree/workspace.h"
@ -452,13 +454,53 @@ bool load_layout_from_file(struct sway_workspace *ws, const char *path,
return true;
}
struct sway_container *find_swallow_match(struct sway_view *view) {
// Recursive depth-first walker. Returns NULL if no match.
struct sway_container *match = NULL;
(void)view;
(void)match;
// Implementation lives in the swallow-on-map commit. Keeping the
// declaration here makes the loader self-contained for now; until the
// hook lands, placeholders simply render as empty bordered slots.
static bool placeholder_matches_view(struct sway_container *placeholder,
struct sway_view *view) {
if (!placeholder->is_placeholder || !placeholder->swallows) {
return false;
}
for (int i = 0; i < placeholder->swallows->length; i++) {
struct criteria *c = placeholder->swallows->items[i];
if (criteria_matches_view_unmapped(c, view)) {
return true;
}
}
return false;
}
static struct sway_container *search_swallow(struct sway_container *con,
struct sway_view *view) {
if (placeholder_matches_view(con, view)) {
return con;
}
if (con->pending.children) {
for (int i = 0; i < con->pending.children->length; i++) {
struct sway_container *child = con->pending.children->items[i];
struct sway_container *match = search_swallow(child, view);
if (match) {
return match;
}
}
}
return NULL;
}
struct sway_container *find_swallow_match(struct sway_view *view) {
if (!view) {
return NULL;
}
for (int o = 0; o < root->outputs->length; o++) {
struct sway_output *output = root->outputs->items[o];
for (int w = 0; w < output->workspaces->length; w++) {
struct sway_workspace *ws = output->workspaces->items[w];
for (int t = 0; t < ws->tiling->length; t++) {
struct sway_container *con = ws->tiling->items[t];
struct sway_container *match = search_swallow(con, view);
if (match) {
return match;
}
}
}
}
return NULL;
}

View file

@ -32,6 +32,7 @@
#include "sway/sway_text_node.h"
#include "sway/tree/arrange.h"
#include "sway/tree/container.h"
#include "sway/tree/load_layout.h"
#include "sway/tree/view.h"
#include "sway/tree/workspace.h"
#include "sway/config.h"
@ -809,6 +810,41 @@ static void handle_foreign_destroy(
wl_list_remove(&view->foreign_destroy.link);
}
// Promote a placeholder into a view-backed container in place, fixing up
// the bits container_create normally branches on `view` for: border rects
// (only allocated for view-backed) and children lists (only allocated for
// view-less). The transaction reparents view->scene_tree into content_tree.
static void promote_placeholder(struct sway_container *placeholder,
struct sway_view *view) {
if (placeholder->swallows) {
for (int i = 0; i < placeholder->swallows->length; i++) {
criteria_destroy(placeholder->swallows->items[i]);
}
list_free(placeholder->swallows);
placeholder->swallows = NULL;
}
placeholder->is_placeholder = false;
if (placeholder->pending.children) {
list_free(placeholder->pending.children);
placeholder->pending.children = NULL;
}
if (placeholder->current.children) {
list_free(placeholder->current.children);
placeholder->current.children = NULL;
}
bool failed = false;
container_init_border_rects(placeholder, &failed);
if (failed) {
sway_log(SWAY_ERROR, "promote_placeholder: border rect alloc failed");
}
placeholder->view = view;
view->container = placeholder;
node_set_dirty(&placeholder->node);
}
void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
bool fullscreen, struct wlr_output *fullscreen_output,
bool decoration) {
@ -817,7 +853,15 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
}
view->surface = wlr_surface;
view_populate_pid(view);
view->container = container_create(view);
// On a swallow match, the placeholder is already in the tree; skip the
// workspace/target-sibling placement path below.
struct sway_container *placeholder = find_swallow_match(view);
if (placeholder) {
promote_placeholder(placeholder, view);
} else {
view->container = container_create(view);
}
if (view->ctx == NULL) {
struct launcher_ctx *ctx = launcher_ctx_find_pid(view->pid);
@ -828,14 +872,19 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
// 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
// focused workspace, etc. A swallowed view stays on the placeholder's
// workspace.
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 (placeholder) {
ws = view->container->pending.workspace;
} else {
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) {
@ -846,28 +895,30 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
wlr_surface_set_preferred_buffer_scale(wlr_surface, ceil(scale));
}
struct sway_seat *seat = input_manager_current_seat();
struct sway_node *node =
seat_get_focus_inactive(seat, ws ? &ws->node : &root->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.
if (ws) {
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;
if (!placeholder) {
struct sway_seat *seat = input_manager_current_seat();
struct sway_node *node =
seat_get_focus_inactive(seat, ws ? &ws->node : &root->node);
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.
if (ws) {
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 {
ws = seat_get_last_known_workspace(seat);
}
} else {
ws = seat_get_last_known_workspace(seat);
target_sibling = node->sway_container;
}
} else {
target_sibling = node->sway_container;
}
}
@ -895,10 +946,12 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
&view->foreign_destroy);
struct sway_container *container = view->container;
if (target_sibling) {
container_add_sibling(target_sibling, container, 1);
} else if (ws) {
container = workspace_add_tiling(ws, container);
if (!placeholder) {
if (target_sibling) {
container_add_sibling(target_sibling, container, 1);
} else if (ws) {
container = workspace_add_tiling(ws, container);
}
}
ipc_event_window(view->container, "new");
@ -906,7 +959,11 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
view_update_csd_from_client(view, decoration);
}
if (view->impl->wants_floating && view->impl->wants_floating(view)) {
if (placeholder) {
// Keep the JSON-loaded border; ignore wants_floating since the user
// explicitly placed a tiling slot.
view_set_tiled(view, true);
} else if (view->impl->wants_floating && view->impl->wants_floating(view)) {
view->container->pending.border = config->floating_border;
view->container->pending.border_thickness = config->floating_border_thickness;
container_set_floating(view->container, true);