#include #include #include #include #include #include #include #include #include "scenefx/types/fx/clipped_region.h" #include "sway/animation_manager.h" #include "sway/config.h" #include "sway/scene_descriptor.h" #include "sway/desktop/idle_inhibit_v1.h" #include "sway/desktop/transaction.h" #include "sway/input/cursor.h" #include "sway/input/input-manager.h" #include "sway/output.h" #include "sway/server.h" #include "sway/tree/container.h" #include "sway/tree/mosaic.h" #include "sway/tree/node.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "list.h" #include "log.h" #define POPIN_FACTOR 0.8f struct sway_transaction { struct wl_event_source *timer; list_t *instructions; // struct sway_transaction_instruction * size_t num_waiting; size_t num_configures; struct timespec commit_time; }; struct sway_transaction_instruction { struct sway_transaction *transaction; struct sway_node *node; union { struct sway_output_state output_state; struct sway_workspace_state workspace_state; struct sway_container_state container_state; }; uint32_t serial; bool server_request; bool waiting; }; static struct sway_transaction *transaction_create(void) { struct sway_transaction *transaction = calloc(1, sizeof(struct sway_transaction)); if (!sway_assert(transaction, "Unable to allocate transaction")) { return NULL; } transaction->instructions = create_list(); return transaction; } static bool con_has_title_bar(struct sway_container *con) { return !(con->current.parent && (con->current.parent->current.layout == L_STACKED || con->current.parent->current.layout == L_TABBED)); } static void _arrange_container(struct sway_container *con, int width, int height, int x, int y, bool title_bar, int gaps); static void anim_update_callback(void *data) { struct sway_container *con = data; // if there's a pending transaction there will be a re-arrange anyway if (server.pending_transaction) { return; } if (con->current.fullscreen_mode != FULLSCREEN_NONE) { return; } int width = get_animated_value(con->animation_state.from_width, con->animation_state.to_width, &con->animation_state.animation); int height = get_animated_value(con->animation_state.from_height, con->animation_state.to_height, &con->animation_state.animation); int x = get_animated_value(con->animation_state.from_x, con->animation_state.to_x, &con->animation_state.animation); int y = get_animated_value(con->animation_state.from_y, con->animation_state.to_y, &con->animation_state.animation); bool title_bar = con_has_title_bar(con); _arrange_container(con, width, height, x, y, title_bar, 0); // refresh decorations for parent-managed title bars (tabbed/stacked) if (!title_bar) { container_update(con); } } static void close_anim_complete_callback(void *data) { struct sway_container *con = data; view_remove_saved_buffer(con->view); container_destroy(con); } static void _fade_container_update(struct sway_container *con, void *data) { // skip redundant update if the container is also being animated if (con->animation_state.animation.initialized) { return; } container_update(con); } static void workspace_fade_update_callback(void *data) { struct sway_workspace *ws = data; if (!ws || !ws->output) { return; } // TODO: needed? output_configure_scene(ws->output, &ws->layers.tiling->node, false, NULL); workspace_for_each_container(ws, _fade_container_update, NULL); } static void workspace_fade_complete_callback(void *data) { struct sway_workspace *ws = data; if (!ws || !ws->output) { return; } struct sway_output *output = ws->output; if (output->current.active_workspace == ws) { return; } wlr_scene_node_set_enabled(&ws->layers.tiling->node, false); wlr_scene_node_set_enabled(&ws->layers.fullscreen->node, false); for (int i = 0; i < ws->current.floating->length; i++) { struct sway_container *floater = ws->current.floating->items[i]; wlr_scene_node_set_enabled(&floater->scene_tree->node, false); } } /* Compensate for scene-graph reparenting by computing the drift between * the last tracked global coordinates and the actual current global position, * then applying that delta to the local scene node position */ static void snap_animation_position(struct sway_container *con) { int lx, ly; wlr_scene_node_coords(&con->scene_tree->node, &lx, &ly); int global_delta_x = con->animation_state.current_global_x - lx; int global_delta_y = con->animation_state.current_global_y - ly; con->animation_state.from_x = con->scene_tree->node.x + global_delta_x; con->animation_state.from_y = con->scene_tree->node.y + global_delta_y; } static void transaction_destroy(struct sway_transaction *transaction) { // Free instructions for (int i = 0; i < transaction->instructions->length; ++i) { struct sway_transaction_instruction *instruction = transaction->instructions->items[i]; struct sway_node *node = instruction->node; node->ntxnrefs--; if (node->instruction == instruction) { node->instruction = NULL; } if (node->destroying && node->ntxnrefs == 0 && !node->dirty) { switch (node->type) { case N_ROOT: sway_assert(false, "Never reached"); break; case N_OUTPUT: output_destroy(node->sway_output); break; case N_WORKSPACE: workspace_destroy(node->sway_workspace); break; case N_CONTAINER: // close animation — pop-out: shrink and fade out centered if (node->sway_container->view) { struct sway_container *con = node->sway_container; snap_animation_position(con); con->animation_state.from_alpha = get_animated_value(con->animation_state.from_alpha, con->animation_state.to_alpha, &con->animation_state.animation); con->animation_state.to_alpha = 0.0f; con->animation_state.from_width = get_animated_value(con->animation_state.from_width, con->animation_state.to_width, &con->animation_state.animation); con->animation_state.from_height = get_animated_value(con->animation_state.from_height, con->animation_state.to_height, &con->animation_state.animation); con->animation_state.to_x = con->animation_state.from_x + (con->animation_state.from_width * (1.0f - POPIN_FACTOR)) / 2.0f; con->animation_state.to_y = con->animation_state.from_y + (con->animation_state.from_height * (1.0f - POPIN_FACTOR)) / 2.0f; con->animation_state.to_width = con->animation_state.from_width * POPIN_FACTOR; con->animation_state.to_height = con->animation_state.from_height * POPIN_FACTOR; add_animation(&con->animation_state.animation, anim_update_callback, close_anim_complete_callback); } else { container_destroy(node->sway_container); } break; } } free(instruction); } list_free(transaction->instructions); if (transaction->timer) { wl_event_source_remove(transaction->timer); } free(transaction); } static void copy_output_state(struct sway_output *output, struct sway_transaction_instruction *instruction) { struct sway_output_state *state = &instruction->output_state; if (state->workspaces) { state->workspaces->length = 0; } else { state->workspaces = create_list(); } list_cat(state->workspaces, output->workspaces); state->active_workspace = output_get_active_workspace(output); } static void copy_workspace_state(struct sway_workspace *ws, struct sway_transaction_instruction *instruction) { struct sway_workspace_state *state = &instruction->workspace_state; state->fullscreen = ws->fullscreen; state->x = ws->x; state->y = ws->y; state->width = ws->width; state->height = ws->height; state->layout = ws->layout; state->output = ws->output; if (state->floating) { state->floating->length = 0; } else { state->floating = create_list(); } if (state->tiling) { state->tiling->length = 0; } else { state->tiling = create_list(); } list_cat(state->floating, ws->floating); list_cat(state->tiling, ws->tiling); struct sway_seat *seat = input_manager_current_seat(); state->focused = seat_get_focus(seat) == &ws->node; // Set focused_inactive_child to the direct tiling child struct sway_container *focus = seat_get_focus_inactive_tiling(seat, ws); if (focus) { while (focus->pending.parent) { focus = focus->pending.parent; } } state->focused_inactive_child = focus; } static void copy_container_state(struct sway_container *container, struct sway_transaction_instruction *instruction) { struct sway_container_state *state = &instruction->container_state; if (state->children) { list_free(state->children); } memcpy(state, &container->pending, sizeof(struct sway_container_state)); if (!container->view) { // We store a copy of the child list to avoid having it mutated after // we copy the state. state->children = create_list(); list_cat(state->children, container->pending.children); } else { state->children = NULL; } struct sway_seat *seat = input_manager_current_seat(); state->focused = seat_get_focus(seat) == &container->node; if (!container->view) { struct sway_node *focus = seat_get_active_tiling_child(seat, &container->node); state->focused_inactive_child = focus ? focus->sway_container : NULL; } } static void transaction_add_node(struct sway_transaction *transaction, struct sway_node *node, bool server_request) { struct sway_transaction_instruction *instruction = NULL; // Check if we have an instruction for this node already, in which case we // update that instead of creating a new one. if (node->ntxnrefs > 0) { for (int idx = 0; idx < transaction->instructions->length; idx++) { struct sway_transaction_instruction *other = transaction->instructions->items[idx]; if (other->node == node) { instruction = other; break; } } } if (!instruction) { instruction = calloc(1, sizeof(struct sway_transaction_instruction)); if (!sway_assert(instruction, "Unable to allocate instruction")) { return; } instruction->transaction = transaction; instruction->node = node; instruction->server_request = server_request; list_add(transaction->instructions, instruction); node->ntxnrefs++; } else if (server_request) { instruction->server_request = true; } switch (node->type) { case N_ROOT: break; case N_OUTPUT: copy_output_state(node->sway_output, instruction); break; case N_WORKSPACE: copy_workspace_state(node->sway_workspace, instruction); break; case N_CONTAINER: copy_container_state(node->sway_container, instruction); break; } } static void apply_output_state(struct sway_output *output, struct sway_output_state *state) { list_free(output->current.workspaces); memcpy(&output->current, state, sizeof(struct sway_output_state)); } static void apply_workspace_state(struct sway_workspace *ws, struct sway_workspace_state *state) { list_free(ws->current.floating); list_free(ws->current.tiling); memcpy(&ws->current, state, sizeof(struct sway_workspace_state)); } static void apply_container_state(struct sway_container *container, struct sway_container_state *state) { struct sway_view *view = container->view; // There are separate children lists for each instruction state, the // container's current state and the container's pending state // (ie. con->children). The list itself needs to be freed here. // Any child containers which are being deleted will be cleaned up in // transaction_destroy(). list_free(container->current.children); memcpy(&container->current, state, sizeof(struct sway_container_state)); if (view) { if (view->saved_surface_tree) { if (!container->node.destroying) { view_remove_saved_buffer(view); } } // If the view hasn't responded to the configure, center it within // the container. This is important for fullscreen views which // refuse to resize to the size of the output. if (view->surface) { view_center_and_clip_surface(view); } } } static void arrange_title_bar(struct sway_container *con, int x, int y, int width, int height) { container_update(con); bool has_title_bar = height > 0; wlr_scene_node_set_enabled(&con->title_bar.tree->node, has_title_bar); if (!has_title_bar) { return; } wlr_scene_node_set_position(&con->title_bar.tree->node, x, y); con->title_width = width; container_arrange_title_bar(con); } static void disable_container(struct sway_container *con) { if (con->view) { wlr_scene_node_reparent(&con->view->scene_tree->node, con->content_tree); } else { for (int i = 0; i < con->current.children->length; i++) { struct sway_container *child = con->current.children->items[i]; wlr_scene_node_reparent(&child->scene_tree->node, con->content_tree); disable_container(child); } } } static void arrange_container(struct sway_container *con, int width, int height, int x, int y, bool title_bar, int gaps); static void arrange_inactive_child(struct sway_container *child, int width, int height, int y_pos) { finish_animation(&child->animation_state.animation); wlr_scene_node_set_position(&child->scene_tree->node, 0, y_pos); child->animation_state.current_width = width; child->animation_state.current_height = height; if (child->view) { wlr_scene_node_coords(&child->scene_tree->node, &child->animation_state.current_global_x, &child->animation_state.current_global_y); } child->animation_state.from_x = 0; child->animation_state.from_y = y_pos; disable_container(child); } static void arrange_children(enum sway_container_layout layout, list_t *children, struct sway_container *active, struct wlr_scene_tree *content, int width, int height, int gaps) { int title_bar_height = container_titlebar_height(); if (layout == L_TABBED) { struct sway_container *first = children->length == 1 ? ((struct sway_container *)children->items[0]) : NULL; if (config->hide_lone_tab && first && first->view && first->current.border != B_NORMAL) { title_bar_height = 0; } double w = (double) width / children->length; int title_offset = 0; for (int i = 0; i < children->length; i++) { struct sway_container *child = children->items[i]; bool activated = child == active; int next_title_offset = round(w * i + w); arrange_title_bar(child, title_offset, -title_bar_height, next_title_offset - title_offset, title_bar_height); wlr_scene_node_set_enabled(&child->border.tree->node, activated); wlr_scene_node_set_enabled(&child->blur->node, activated); wlr_scene_node_set_enabled(&child->shadow->node, false); wlr_scene_node_set_enabled(&child->scene_tree->node, true); wlr_scene_node_reparent(&child->scene_tree->node, content); int net_height = height - title_bar_height; if (activated && width > 0 && net_height > 0) { arrange_container(child, width, net_height, 0, title_bar_height, title_bar_height == 0, 0); } else { arrange_inactive_child(child, width, net_height, title_bar_height); } title_offset = next_title_offset; } } else if (layout == L_STACKED) { struct sway_container *first = children->length == 1 ? ((struct sway_container *)children->items[0]) : NULL; if (config->hide_lone_tab && first && first->view && first->current.border != B_NORMAL) { title_bar_height = 0; } int title_height = title_bar_height * children->length; int y = 0; for (int i = 0; i < children->length; i++) { struct sway_container *child = children->items[i]; bool activated = child == active; arrange_title_bar(child, 0, y - title_height, width, title_bar_height); wlr_scene_node_set_enabled(&child->border.tree->node, activated); wlr_scene_node_set_enabled(&child->blur->node, activated); wlr_scene_node_set_enabled(&child->shadow->node, false); wlr_scene_node_set_enabled(&child->scene_tree->node, true); wlr_scene_node_reparent(&child->scene_tree->node, content); int net_height = height - title_height; if (activated && width > 0 && net_height > 0) { arrange_container(child, width, net_height, 0, title_height, title_bar_height == 0, 0); } else { arrange_inactive_child(child, width, net_height, title_height); } y += title_bar_height; } } else if (layout == L_VERT) { int off = 0; for (int i = 0; i < children->length; i++) { struct sway_container *child = children->items[i]; int cheight = child->current.height; wlr_scene_node_set_enabled(&child->border.tree->node, true); wlr_scene_node_set_enabled(&child->blur->node, true); wlr_scene_node_set_enabled(&child->shadow->node, container_has_shadow(child) && child->view); wlr_scene_node_reparent(&child->scene_tree->node, content); if (width > 0 && cheight > 0) { arrange_container(child, width, cheight, 0, off, true, gaps); off += cheight + gaps; } else { disable_container(child); } } } else if (layout == L_HORIZ || layout == L_MOSAIC) { // Scene node positions are int; keep the logical canvas accumulator // wide so a very large Mosaic row cannot overflow signed arithmetic. int64_t off = 0; for (int i = 0; i < children->length; i++) { struct sway_container *child = children->items[i]; int cwidth = child->current.width; wlr_scene_node_set_enabled(&child->border.tree->node, true); wlr_scene_node_set_enabled(&child->blur->node, true); wlr_scene_node_set_enabled(&child->shadow->node, container_has_shadow(child) && child->view); wlr_scene_node_reparent(&child->scene_tree->node, content); if (cwidth > 0 && height > 0 && off <= INT_MAX) { arrange_container(child, cwidth, height, (int)off, 0, true, gaps); off += (int64_t)cwidth + gaps; } else { disable_container(child); } } } else { sway_assert(false, "unreachable"); } } static void _arrange_container(struct sway_container *con, int width, int height, int x, int y, bool title_bar, int gaps) { wlr_scene_node_set_position(&con->scene_tree->node, x, y); // this container might have previously been in the scratchpad, // make sure it's enabled for viewing wlr_scene_node_set_enabled(&con->scene_tree->node, true); bool has_corner_radius = container_has_corner_radius(con); if (container_has_shadow(con)) { int corner_radius = has_corner_radius ? con->corner_radius + con->current.border_thickness : 0; if (!con->view && title_bar) { // Stacking/Tabbed containers don't have a border_thickness, so we // use the config default corner_radius = con->corner_radius + config->border_thickness; } wlr_scene_shadow_set_size(con->shadow, width + config->shadow_blur_sigma * 2, height + config->shadow_blur_sigma * 2); int shadow_x = config->shadow_offset_x - config->shadow_blur_sigma; int shadow_y = config->shadow_offset_y - config->shadow_blur_sigma; wlr_scene_node_set_position(&con->shadow->node, shadow_x, shadow_y); wlr_scene_shadow_set_clipped_region(con->shadow, (struct clipped_region) { .corners = corner_radii_all(corner_radius), .area = { .x = config->shadow_blur_sigma - config->shadow_offset_x, .y = config->shadow_blur_sigma - config->shadow_offset_y, .width = width, .height = height, } }); wlr_scene_shadow_set_blur_sigma(con->shadow, config->shadow_blur_sigma); wlr_scene_shadow_set_corner_radius(con->shadow, !has_corner_radius ? 0 : corner_radius); } if (con->view) { wlr_scene_node_coords(&con->scene_tree->node, &con->animation_state.current_global_x, &con->animation_state.current_global_y); con->animation_state.current_width = width; con->animation_state.current_height = height; int corner_radius = has_corner_radius ? con->corner_radius : 0; int vert_border_offset = corner_radius; int border_top = container_titlebar_height(); int border_width = con->current.border_thickness; if (title_bar && con->current.border != B_NORMAL) { wlr_scene_node_set_enabled(&con->title_bar.tree->node, false); wlr_scene_node_set_enabled(&con->border.top->node, true); } else { wlr_scene_node_set_enabled(&con->border.top->node, false); } if (con->current.border == B_NORMAL) { vert_border_offset = 0; if (title_bar) { arrange_title_bar(con, 0, 0, width, border_top); } else { border_top = 0; // should be handled by the parent container } } else if (con->current.border == B_PIXEL) { container_update(con); border_top = title_bar && con->current.border_top ? border_width : 0; if (!title_bar && !con->current.border_top) { vert_border_offset = 0; } } else if (con->current.border == B_NONE) { container_update(con); border_top = 0; border_width = 0; } else if (con->current.border == B_CSD) { border_top = 0; border_width = 0; } else { sway_assert(false, "unreachable"); } int border_bottom = con->current.border_bottom ? border_width : 0; int border_left = con->current.border_left ? border_width : 0; int border_right = con->current.border_right ? border_width : 0; int vert_border_height = MAX(0, height - border_top - border_bottom - vert_border_offset - corner_radius); wlr_scene_rect_set_size(con->border.left, border_left, vert_border_height); wlr_scene_rect_set_size(con->border.right, border_right, vert_border_height); if (border_top) { wlr_scene_rect_set_size(con->border.top, width, border_top + corner_radius); wlr_scene_rect_set_corner_radii(con->border.top, corner_radii_top(!has_corner_radius ? 0 : corner_radius + border_width)); wlr_scene_rect_set_clipped_region(con->border.top, (struct clipped_region) { .corners = corner_radii_top(corner_radius), .area = { .x = border_width, .y = border_width, .width = width - 2 * border_width, .height = border_top + corner_radius } }); } else { wlr_scene_rect_set_size(con->border.top, 0, 0); } if (border_bottom) { wlr_scene_rect_set_size(con->border.bottom, width, border_bottom + corner_radius); wlr_scene_rect_set_corner_radii(con->border.bottom, corner_radii_bottom(!has_corner_radius ? 0 : corner_radius + border_width)); wlr_scene_rect_set_clipped_region(con->border.bottom, (struct clipped_region) { .corners = corner_radii_bottom(corner_radius), // shift up one px to fix https://github.com/WillPower3309/swayfx/issues/386 // TODO: proper fix .area = { .x = border_width, .y = -1, .width = width - 2 * border_width, .height = border_bottom - border_width + corner_radius + 1, } }); } else { wlr_scene_rect_set_size(con->border.bottom, 0, 0); } wlr_scene_node_set_position(&con->border.top->node, 0, 0); wlr_scene_node_set_position(&con->border.bottom->node, 0, height - border_bottom - corner_radius); wlr_scene_node_set_position(&con->border.left->node, 0, border_top + vert_border_offset); wlr_scene_node_set_position(&con->border.right->node, width - border_right, border_top + vert_border_offset); int content_width = width - border_left - border_right; int content_height = height - border_top - border_bottom; // only make sure to clip the content if there is content to clip if (!wl_list_empty(&con->view->content_tree->children)) { struct wlr_box clip = (struct wlr_box){ .x = con->view->geometry.x, .y = con->view->geometry.y, .width = content_width, .height = content_height, }; wlr_scene_subsurface_tree_set_clip(&con->view->content_tree->node, &clip); } con->animation_state.current_content_width = content_width; con->animation_state.current_content_height = content_height; if (content_width <= 0 || content_height <= 0) { return; } // Dim if (con->dim_rect) { wlr_scene_node_set_position(&con->dim_rect->node, border_left, border_top); wlr_scene_rect_set_size(con->dim_rect, content_width, content_height); bool has_titlebar = !title_bar || con->current.border == B_NORMAL; wlr_scene_rect_set_corner_radii( con->dim_rect, has_titlebar ? corner_radii_bottom(con->corner_radius) : corner_radii_all(con->corner_radius) ); } // make sure to reparent, it's possible that the client just came out of // fullscreen mode where the parent of the surface is not the container wlr_scene_node_reparent(&con->view->scene_tree->node, con->content_tree); wlr_scene_node_set_position(&con->view->scene_tree->node, border_left, border_top); wlr_scene_node_set_enabled(&con->blur->node, con->blur_enabled); wlr_scene_node_set_position(&con->blur->node, border_left, border_top); wlr_scene_blur_set_size(con->blur, content_width, content_height); // the output handler for the view wants to detect events for the entire // container so give it negative coordinates to move it back over the // decorations wlr_scene_node_set_position(&con->view->output_handler->node, -border_left, -border_top); wlr_scene_buffer_set_dest_size(con->view->output_handler, width, height); } else { // make sure to disable the title bar if the parent is not managing it if (title_bar) { wlr_scene_node_set_enabled(&con->title_bar.tree->node, false); } wlr_scene_node_set_enabled(&con->shadow->node, container_has_shadow(con) && (con->current.layout == L_TABBED || con->current.layout == L_STACKED)); arrange_children(con->current.layout, con->current.children, con->current.focused_inactive_child, con->content_tree, width, height, gaps); } } static void arrange_container(struct sway_container *con, int width, int height, int x, int y, bool title_bar, int gaps) { if (!config->animation_duration_ms || !con->view || con->animation_state.seat_is_resizing || con->animation_state.seat_is_moving_float) { finish_animation(&con->animation_state.animation); _arrange_container(con, width, height, x, y, title_bar, gaps); return; } if (con->animation_state.to_x == x && con->animation_state.to_y == y && con->animation_state.to_width == width && con->animation_state.to_height == height) { if (!con->animation_state.animation.initialized || con->animation_state.animation.progress >= 1.0f) { _arrange_container(con, width, height, x, y, title_bar, gaps); } return; } con->animation_state.to_x = x; con->animation_state.to_y = y; con->animation_state.to_width = width; con->animation_state.to_height = height; con->animation_state.to_alpha = 1.0f; // open animation — pop-in: grow from center while fading in if (con->animation_state.from_x == -1) { con->animation_state.from_x = x + width * (1.0f - POPIN_FACTOR) / 2.0f; con->animation_state.from_y = y + height * (1.0f - POPIN_FACTOR) / 2.0f; con->animation_state.from_width = width * POPIN_FACTOR; con->animation_state.from_height = height * POPIN_FACTOR; con->animation_state.from_alpha = 0.0f; add_animation(&con->animation_state.animation, anim_update_callback, NULL); } else { // move animation snap_animation_position(con); con->animation_state.from_width = con->animation_state.current_width; con->animation_state.from_height = con->animation_state.current_height; con->animation_state.from_alpha = get_animated_value(con->animation_state.from_alpha, con->animation_state.to_alpha, &con->animation_state.animation); add_animation(&con->animation_state.animation, anim_update_callback, NULL); } // arrange at starting state to "win" position race between animation start and the reparent _arrange_container(con, con->animation_state.from_width, con->animation_state.from_height, con->animation_state.from_x, con->animation_state.from_y, con_has_title_bar(con), 0); } static int container_get_gaps(struct sway_container *con) { struct sway_workspace *ws = con->current.workspace; struct sway_container *temp = con; while (temp) { enum sway_container_layout layout; if (temp->current.parent) { layout = temp->current.parent->current.layout; } else { layout = ws->current.layout; } if (layout == L_TABBED || layout == L_STACKED) { return 0; } temp = temp->pending.parent; } return ws->gaps_inner; } static void arrange_fullscreen(struct wlr_scene_tree *tree, struct sway_container *fs, struct sway_workspace *ws, int width, int height) { struct wlr_scene_node *fs_node; if (fs->view) { fs_node = &fs->view->scene_tree->node; // if we only care about the view, disable any decorations wlr_scene_node_set_enabled(&fs->scene_tree->node, false); // reconfigure the output handler (for foreign toplevel) to cover the // view without container decorations wlr_scene_node_set_position(&fs->view->output_handler->node, 0, 0); wlr_scene_buffer_set_dest_size(fs->view->output_handler, width, height); } else { fs_node = &fs->scene_tree->node; arrange_container(fs, width, height, 0, 0, true, container_get_gaps(fs)); } wlr_scene_node_reparent(fs_node, tree); wlr_scene_node_lower_to_bottom(fs_node); wlr_scene_node_set_position(fs_node, 0, 0); } static void arrange_workspace_floating(struct sway_workspace *ws) { for (int i = 0; i < ws->current.floating->length; i++) { struct sway_container *floater = ws->current.floating->items[i]; struct wlr_scene_tree *layer = root->layers.floating; if (floater->current.fullscreen_mode != FULLSCREEN_NONE) { continue; } if (root->fullscreen_global) { if (container_is_transient_for(floater, root->fullscreen_global)) { layer = root->layers.fullscreen_global; } } else { for (int i = 0; i < root->outputs->length; i++) { struct sway_output *output = root->outputs->items[i]; struct sway_workspace *active = output->current.active_workspace; if (active && active->fullscreen && container_is_transient_for(floater, active->fullscreen)) { layer = root->layers.fullscreen; } } } wlr_scene_node_reparent(&floater->scene_tree->node, layer); wlr_scene_node_set_enabled(&floater->scene_tree->node, true); wlr_scene_node_set_enabled(&floater->shadow->node, container_has_shadow(floater) && floater->view); wlr_scene_node_set_enabled(&floater->border.tree->node, true); arrange_container(floater, floater->current.width, floater->current.height, floater->current.x, floater->current.y, true, ws->gaps_inner); } } static void arrange_workspace_tiling(struct sway_workspace *ws, int width, int height) { arrange_children(ws->current.layout, ws->current.tiling, ws->current.focused_inactive_child, ws->layers.tiling, width, height, ws->gaps_inner); } static int workspace_tiling_scene_x(struct sway_workspace *ws, struct wlr_box *area, struct side_gaps *gaps) { int64_t x = (int64_t)area->x + gaps->left; if (ws->current.layout == L_MOSAIC && ws->mosaic) { struct mosaic_row *row = ws->mosaic->rows->items[0]; x -= (int64_t)row->state.scroll_x; } // The camera is kept in logical pixels; this conversion is the sole // output-scene mapping and must never overflow wlroots' int positions. return x < INT_MIN ? INT_MIN : x > INT_MAX ? INT_MAX : (int)x; } static void disable_workspace(struct sway_workspace *ws) { // if any containers were just moved to a disabled workspace it will // have the parent of the old workspace. Move the workspace so that it won't // be shown. for (int i = 0; i < ws->current.tiling->length; i++) { struct sway_container *child = ws->current.tiling->items[i]; wlr_scene_node_reparent(&child->scene_tree->node, ws->layers.tiling); disable_container(child); } for (int i = 0; i < ws->current.floating->length; i++) { struct sway_container *floater = ws->current.floating->items[i]; wlr_scene_node_reparent(&floater->scene_tree->node, root->layers.floating); disable_container(floater); wlr_scene_node_set_enabled(&floater->scene_tree->node, false); } } static void arrange_output(struct sway_output *output, int width, int height) { struct sway_workspace *new_active = output->current.active_workspace; struct sway_workspace *old_active = output->prev_active_workspace; bool is_ws_switch = old_active && old_active != new_active && output->wlr_output->enabled && config->animation_duration_ms > 0 && !(old_active->current.fullscreen || new_active->current.fullscreen); if (is_ws_switch) { new_active->animation_state.from_alpha = 0.0f; if (old_active->current.tiling->length == 0 && old_active->current.floating->length == 0) { new_active->animation_state.to_alpha = 1.0f; add_animation(&new_active->animation_state.animation, workspace_fade_update_callback, NULL); } else { new_active->animation_state.to_alpha = 0.0f; float current_alpha = get_animated_value(old_active->animation_state.from_alpha, old_active->animation_state.to_alpha, &old_active->animation_state.animation); old_active->animation_state.from_alpha = current_alpha; old_active->animation_state.to_alpha = 0.0f; finish_animation(&new_active->animation_state.animation); new_active->animation_state.from_alpha = 0.0f; new_active->animation_state.to_alpha = 1.0f; add_animation(&old_active->animation_state.animation, workspace_fade_update_callback, workspace_fade_complete_callback); add_animation(&new_active->animation_state.animation, workspace_fade_update_callback, workspace_fade_complete_callback); } } for (int i = 0; i < output->current.workspaces->length; i++) { struct sway_workspace *child = output->current.workspaces->items[i]; bool activated = output->current.active_workspace == child && output->wlr_output->enabled; bool animating = child->animation_state.animation.initialized; wlr_scene_node_reparent(&child->layers.tiling->node, output->layers.tiling); wlr_scene_node_reparent(&child->layers.fullscreen->node, output->layers.fullscreen); for (int i = 0; i < child->current.floating->length; i++) { struct sway_container *floater = child->current.floating->items[i]; wlr_scene_node_reparent(&floater->scene_tree->node, root->layers.floating); wlr_scene_node_set_enabled(&floater->scene_tree->node, activated || animating); } if (activated) { struct sway_container *fs = child->current.fullscreen; wlr_scene_node_set_enabled(&child->layers.tiling->node, !fs); wlr_scene_node_set_enabled(&child->layers.fullscreen->node, fs); wlr_scene_node_set_enabled(&output->layers.shell_background->node, !fs); wlr_scene_node_set_enabled(&output->layers.shell_bottom->node, !fs); wlr_scene_node_set_enabled(&output->layers.blur_layer->node, !fs); wlr_scene_node_set_enabled(&output->layers.fullscreen->node, fs); if (fs) { disable_workspace(child); wlr_scene_rect_set_size(output->fullscreen_background, width, height); arrange_workspace_floating(child); arrange_fullscreen(child->layers.fullscreen, fs, child, width, height); } else { struct wlr_box *area = &output->usable_area; struct side_gaps *gaps = &child->current_gaps; wlr_scene_node_set_position(&child->layers.tiling->node, workspace_tiling_scene_x(child, area, gaps), gaps->top + area->y); arrange_workspace_tiling(child, area->width - gaps->left - gaps->right, area->height - gaps->top - gaps->bottom); arrange_workspace_floating(child); } } else if (animating && !activated) { // Workspace fading out - keep visible, alpha handled by render path struct wlr_box *area = &output->usable_area; struct side_gaps *gaps = &child->current_gaps; wlr_scene_node_set_enabled(&child->layers.tiling->node, true); wlr_scene_node_set_enabled(&child->layers.fullscreen->node, false); wlr_scene_node_set_position(&child->layers.tiling->node, workspace_tiling_scene_x(child, area, gaps), gaps->top + area->y); arrange_workspace_tiling(child, area->width - gaps->left - gaps->right, area->height - gaps->top - gaps->bottom); } else { wlr_scene_node_set_enabled(&child->layers.tiling->node, false); wlr_scene_node_set_enabled(&child->layers.fullscreen->node, false); disable_workspace(child); } } output->prev_active_workspace = new_active; } void arrange_popups(struct wlr_scene_tree *popups) { struct wlr_scene_node *node; wl_list_for_each(node, &popups->children, link) { struct sway_popup_desc *popup = scene_descriptor_try_get(node, SWAY_SCENE_DESC_POPUP); if (popup) { int lx, ly; wlr_scene_node_coords(popup->relative, &lx, &ly); wlr_scene_node_set_position(node, lx, ly); } } } static void arrange_root(struct sway_root *root) { struct sway_container *fs = root->fullscreen_global; wlr_scene_node_set_enabled(&root->layers.shell_background->node, !fs); wlr_scene_node_set_enabled(&root->layers.shell_bottom->node, !fs); wlr_scene_node_set_enabled(&root->layers.blur_tree->node, !fs); wlr_scene_node_set_enabled(&root->layers.tiling->node, !fs); wlr_scene_node_set_enabled(&root->layers.floating->node, !fs); wlr_scene_node_set_enabled(&root->layers.shell_top->node, !fs); wlr_scene_node_set_enabled(&root->layers.fullscreen->node, !fs); // hide all contents in the scratchpad for (int i = 0; i < root->scratchpad->length; i++) { struct sway_container *con = root->scratchpad->items[i]; disable_container(con); wlr_scene_node_set_enabled(&con->scene_tree->node, false); } if (fs) { for (int i = 0; i < root->outputs->length; i++) { struct sway_output *output = root->outputs->items[i]; struct sway_workspace *ws = output->current.active_workspace; wlr_scene_output_set_position(output->scene_output, output->lx, output->ly); // disable all workspaces to get to a known state for (int j = 0; j < output->current.workspaces->length; j++) { struct sway_workspace *workspace = output->current.workspaces->items[j]; disable_workspace(workspace); } // arrange the active workspace if (ws) { arrange_workspace_floating(ws); } } arrange_fullscreen(root->layers.fullscreen_global, fs, NULL, root->width, root->height); } else { for (int i = 0; i < root->outputs->length; i++) { struct sway_output *output = root->outputs->items[i]; wlr_scene_output_set_position(output->scene_output, output->lx, output->ly); wlr_scene_node_reparent(&output->layers.shell_background->node, root->layers.shell_background); wlr_scene_node_reparent(&output->layers.shell_bottom->node, root->layers.shell_bottom); wlr_scene_node_reparent(&output->layers.blur_layer->node, root->layers.blur_tree); wlr_scene_node_reparent(&output->layers.tiling->node, root->layers.tiling); wlr_scene_node_reparent(&output->layers.shell_top->node, root->layers.shell_top); wlr_scene_node_reparent(&output->layers.shell_overlay->node, root->layers.shell_overlay); wlr_scene_node_reparent(&output->layers.fullscreen->node, root->layers.fullscreen); wlr_scene_node_reparent(&output->layers.session_lock->node, root->layers.session_lock); wlr_scene_node_set_position(&output->layers.shell_background->node, output->lx, output->ly); wlr_scene_node_set_position(&output->layers.shell_bottom->node, output->lx, output->ly); wlr_scene_node_set_position(&output->layers.blur_layer->node, output->lx, output->ly); wlr_scene_node_set_position(&output->layers.tiling->node, output->lx, output->ly); wlr_scene_node_set_position(&output->layers.fullscreen->node, output->lx, output->ly); wlr_scene_node_set_position(&output->layers.shell_top->node, output->lx, output->ly); wlr_scene_node_set_position(&output->layers.shell_overlay->node, output->lx, output->ly); wlr_scene_node_set_position(&output->layers.session_lock->node, output->lx, output->ly); arrange_output(output, output->width, output->height); } } arrange_popups(root->layers.popup); } /** * Apply a transaction to the "current" state of the tree. */ static void transaction_apply(struct sway_transaction *transaction) { sway_log(SWAY_DEBUG, "Applying transaction %p", transaction); if (debug.txn_timings) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); struct timespec *commit = &transaction->commit_time; float ms = (now.tv_sec - commit->tv_sec) * 1000 + (now.tv_nsec - commit->tv_nsec) / 1000000.0; sway_log(SWAY_DEBUG, "Transaction %p: %.1fms waiting " "(%.1f frames if 60Hz)", transaction, ms, ms / (1000.0f / 60)); } // Apply the instruction state to the node's current state for (int i = 0; i < transaction->instructions->length; ++i) { struct sway_transaction_instruction *instruction = transaction->instructions->items[i]; struct sway_node *node = instruction->node; switch (node->type) { case N_ROOT: break; case N_OUTPUT: apply_output_state(node->sway_output, &instruction->output_state); break; case N_WORKSPACE: apply_workspace_state(node->sway_workspace, &instruction->workspace_state); break; case N_CONTAINER: apply_container_state(node->sway_container, &instruction->container_state); break; } node->instruction = NULL; } } static void transaction_commit_pending(void); static void transaction_progress(void) { if (!server.queued_transaction) { return; } if (server.queued_transaction->num_waiting > 0) { return; } transaction_apply(server.queued_transaction); arrange_root(root); cursor_rebase_all(); transaction_destroy(server.queued_transaction); start_animations(); server.queued_transaction = NULL; if (!server.pending_transaction) { sway_idle_inhibit_v1_check_active(); return; } transaction_commit_pending(); } static int handle_timeout(void *data) { struct sway_transaction *transaction = data; sway_log(SWAY_DEBUG, "Transaction %p timed out (%zi waiting)", transaction, transaction->num_waiting); transaction->num_waiting = 0; transaction_progress(); return 0; } static bool should_configure(struct sway_node *node, struct sway_transaction_instruction *instruction) { if (!node_is_view(node)) { return false; } if (node->destroying) { return false; } if (!instruction->server_request) { return false; } struct sway_container_state *cstate = &node->sway_container->current; struct sway_container_state *istate = &instruction->container_state; #if WLR_HAS_XWAYLAND // Xwayland views are position-aware and need to be reconfigured // when their position changes. if (node->sway_container->view->type == SWAY_VIEW_XWAYLAND) { // Sway logical coordinates are doubles, but they get truncated to // integers when sent to Xwayland through `xcb_configure_window`. // X11 apps will not respond to duplicate configure requests (from their // truncated point of view) and cause transactions to time out. if ((int)cstate->content_x != (int)istate->content_x || (int)cstate->content_y != (int)istate->content_y) { return true; } } #endif if (cstate->content_width == istate->content_width && cstate->content_height == istate->content_height) { return false; } return true; } static void transaction_commit(struct sway_transaction *transaction) { sway_log(SWAY_DEBUG, "Transaction %p committing with %i instructions", transaction, transaction->instructions->length); transaction->num_waiting = 0; for (int i = 0; i < transaction->instructions->length; ++i) { struct sway_transaction_instruction *instruction = transaction->instructions->items[i]; struct sway_node *node = instruction->node; bool hidden = node_is_view(node) && !node->destroying && !view_is_visible(node->sway_container->view); if (should_configure(node, instruction)) { instruction->serial = view_configure(node->sway_container->view, instruction->container_state.content_x, instruction->container_state.content_y, instruction->container_state.content_width, instruction->container_state.content_height); if (!hidden) { instruction->waiting = true; ++transaction->num_waiting; } view_send_frame_done(node->sway_container->view); } if (!hidden && node_is_view(node) && !node->sway_container->view->saved_surface_tree) { view_save_buffer(node->sway_container->view); } node->instruction = instruction; } transaction->num_configures = transaction->num_waiting; if (debug.txn_timings) { clock_gettime(CLOCK_MONOTONIC, &transaction->commit_time); } if (debug.noatomic) { transaction->num_waiting = 0; } else if (debug.txn_wait) { // Force the transaction to time out even if all views are ready. // We do this by inflating the waiting counter. transaction->num_waiting += 1000000; } if (transaction->num_waiting) { // Set up a timer which the views must respond within transaction->timer = wl_event_loop_add_timer(server.wl_event_loop, handle_timeout, transaction); if (transaction->timer) { wl_event_source_timer_update(transaction->timer, server.txn_timeout_ms); } else { sway_log_errno(SWAY_ERROR, "Unable to create transaction timer " "(some imperfect frames might be rendered)"); transaction->num_waiting = 0; } } } static void transaction_commit_pending(void) { if (server.queued_transaction) { return; } struct sway_transaction *transaction = server.pending_transaction; server.pending_transaction = NULL; server.queued_transaction = transaction; transaction_commit(transaction); transaction_progress(); } static void set_instruction_ready( struct sway_transaction_instruction *instruction) { struct sway_transaction *transaction = instruction->transaction; if (debug.txn_timings) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); struct timespec *start = &transaction->commit_time; float ms = (now.tv_sec - start->tv_sec) * 1000 + (now.tv_nsec - start->tv_nsec) / 1000000.0; sway_log(SWAY_DEBUG, "Transaction %p: %zi/%zi ready in %.1fms (%s)", transaction, transaction->num_configures - transaction->num_waiting + 1, transaction->num_configures, ms, instruction->node->sway_container->title); } // If the transaction has timed out then its num_waiting will be 0 already. if (instruction->waiting && transaction->num_waiting > 0 && --transaction->num_waiting == 0) { sway_log(SWAY_DEBUG, "Transaction %p is ready", transaction); wl_event_source_timer_update(transaction->timer, 0); } instruction->node->instruction = NULL; transaction_progress(); } bool transaction_notify_view_ready_by_serial(struct sway_view *view, uint32_t serial) { struct sway_transaction_instruction *instruction = view->container->node.instruction; if (instruction != NULL && instruction->serial == serial) { set_instruction_ready(instruction); return true; } return false; } bool transaction_notify_view_ready_by_geometry(struct sway_view *view, double x, double y, int width, int height) { struct sway_transaction_instruction *instruction = view->container->node.instruction; if (instruction != NULL && (int)instruction->container_state.content_x == (int)x && (int)instruction->container_state.content_y == (int)y && instruction->container_state.content_width == width && instruction->container_state.content_height == height) { set_instruction_ready(instruction); return true; } return false; } static void _transaction_commit_dirty(bool server_request) { if (!server.dirty_nodes->length) { return; } if (!server.pending_transaction) { server.pending_transaction = transaction_create(); if (!server.pending_transaction) { return; } } for (int i = 0; i < server.dirty_nodes->length; ++i) { struct sway_node *node = server.dirty_nodes->items[i]; transaction_add_node(server.pending_transaction, node, server_request); node->dirty = false; } server.dirty_nodes->length = 0; transaction_commit_pending(); } void transaction_commit_dirty(void) { _transaction_commit_dirty(true); } void transaction_commit_dirty_client(void) { _transaction_commit_dirty(false); }