From 1606311553cb58a280e320907098bd0f443fef6e Mon Sep 17 00:00:00 2001 From: Hugo Osvaldo Barrera Date: Thu, 9 Apr 2026 02:09:59 +0200 Subject: [PATCH 01/16] Don't ignore initialisation errors server_init ignores all errors. In practice, theses result in a segfault, potentially much later and losing any unsaved work. Properly handle initialisation errors and bail immediately. --- include/sway/server.h | 2 +- include/sway/tree/workspace.h | 2 +- sway/lock.c | 7 +- sway/server.c | 229 +++++++++++++++++++++++++++++++--- sway/tree/workspace.c | 5 +- 5 files changed, 222 insertions(+), 23 deletions(-) diff --git a/include/sway/server.h b/include/sway/server.h index 318cd39c1..8c8114882 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -176,7 +176,7 @@ void handle_new_output(struct wl_listener *listener, void *data); void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data); void handle_layer_shell_surface(struct wl_listener *listener, void *data); -void sway_session_lock_init(void); +bool sway_session_lock_init(void); void sway_session_lock_add_output(struct sway_session_lock *lock, struct sway_output *output); bool sway_session_lock_has_surface(struct sway_session_lock *lock, diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h index 7ce48f173..50bf02cd1 100644 --- a/include/sway/tree/workspace.h +++ b/include/sway/tree/workspace.h @@ -162,7 +162,7 @@ void workspace_squash(struct sway_workspace *workspace); void workspace_move_to_output(struct sway_workspace *workspace, struct sway_output *output); -void sway_ext_workspace_init(void); +bool sway_ext_workspace_init(void); void sway_ext_workspace_output_enable(struct sway_output *output); void sway_ext_workspace_output_disable(struct sway_output *output); diff --git a/sway/lock.c b/sway/lock.c index c8975c747..b1df65718 100644 --- a/sway/lock.c +++ b/sway/lock.c @@ -344,8 +344,12 @@ bool sway_session_lock_has_surface(struct sway_session_lock *lock, return false; } -void sway_session_lock_init(void) { +bool sway_session_lock_init(void) { server.session_lock.manager = wlr_session_lock_manager_v1_create(server.wl_display); + if (!server.session_lock.manager) { + sway_log(SWAY_ERROR, "Failed to create session lock manager"); + return false; + } server.session_lock.new_lock.notify = handle_session_lock; server.session_lock.manager_destroy.notify = handle_session_lock_destroy; @@ -353,4 +357,5 @@ void sway_session_lock_init(void) { &server.session_lock.new_lock); wl_signal_add(&server.session_lock.manager->events.destroy, &server.session_lock.manager_destroy); + return true; } diff --git a/sway/server.c b/sway/server.c index a49017424..8bdafb674 100644 --- a/sway/server.c +++ b/sway/server.c @@ -244,13 +244,25 @@ static void handle_new_foreign_toplevel_capture_request(struct wl_listener *list bool server_init(struct sway_server *server) { sway_log(SWAY_DEBUG, "Initializing Wayland server"); server->wl_display = wl_display_create(); + if (!server->wl_display) { + sway_log(SWAY_ERROR, "Failed to create wl_display"); + return false; + } server->wl_event_loop = wl_display_get_event_loop(server->wl_display); wl_display_set_global_filter(server->wl_display, filter_global, NULL); wl_display_set_default_max_buffer_size(server->wl_display, 1024 * 1024); - wlr_fixes_create(server->wl_display, 1); + if (!wlr_fixes_create(server->wl_display, 1)) { + sway_log(SWAY_ERROR, "Failed to create wp_fixes global"); + return false; + } root = root_create(server->wl_display); + if (!root) { + sway_log(SWAY_ERROR, "Failed to create root"); + wl_display_destroy(server->wl_display); + return false; + } server->backend = wlr_backend_autocreate(server->wl_event_loop, &server->session); if (!server->backend) { @@ -271,15 +283,23 @@ bool server_init(struct sway_server *server) { wlr_renderer_init_wl_shm(server->renderer, server->wl_display); - if (wlr_renderer_get_texture_formats(server->renderer, WLR_BUFFER_CAP_DMABUF) != NULL) { + if (wlr_renderer_get_drm_fd(server->renderer) >= 0 && + wlr_renderer_get_texture_formats(server->renderer, WLR_BUFFER_CAP_DMABUF) != NULL) { server->linux_dmabuf_v1 = wlr_linux_dmabuf_v1_create_with_renderer( server->wl_display, 5, server->renderer); + if (!server->linux_dmabuf_v1) { + sway_log(SWAY_ERROR, "Failed to create linux-dmabuf v1"); + return false; + } } if (wlr_renderer_get_drm_fd(server->renderer) >= 0 && server->renderer->features.timeline && server->backend->features.timeline) { - wlr_linux_drm_syncobj_manager_v1_create(server->wl_display, 1, - wlr_renderer_get_drm_fd(server->renderer)); + if (!wlr_linux_drm_syncobj_manager_v1_create(server->wl_display, 1, + wlr_renderer_get_drm_fd(server->renderer))) { + sway_log(SWAY_ERROR, "Failed to create linux-drm-syncobj v1"); + return false; + } } server->allocator = wlr_allocator_autocreate(server->backend, @@ -291,14 +311,29 @@ bool server_init(struct sway_server *server) { server->compositor = wlr_compositor_create(server->wl_display, 6, server->renderer); + if (!server->compositor) { + sway_log(SWAY_ERROR, "Failed to create compositor"); + return false; + } - wlr_subcompositor_create(server->wl_display); + if (!wlr_subcompositor_create(server->wl_display)) { + sway_log(SWAY_ERROR, "Failed to create subcompositor"); + return false; + } server->data_device_manager = wlr_data_device_manager_create(server->wl_display); + if (!server->data_device_manager) { + sway_log(SWAY_ERROR, "Failed to create data device manager"); + return false; + } server->gamma_control_manager_v1 = wlr_gamma_control_manager_v1_create(server->wl_display); + if (!server->gamma_control_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create gamma control manager"); + return false; + } wlr_scene_set_gamma_control_manager_v1(root->root_scene, server->gamma_control_manager_v1); @@ -307,26 +342,53 @@ bool server_init(struct sway_server *server) { server->xdg_output_manager_v1 = wlr_xdg_output_manager_v1_create(server->wl_display, root->output_layout); + if (!server->xdg_output_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create XDG output manager"); + return false; + } server->idle_notifier_v1 = wlr_idle_notifier_v1_create(server->wl_display); - sway_idle_inhibit_manager_v1_init(); + if (!server->idle_notifier_v1) { + sway_log(SWAY_ERROR, "Failed to create idle notifier"); + return false; + } + if (!sway_idle_inhibit_manager_v1_init()) { + sway_log(SWAY_ERROR, "Failed to init idle inhibit manager"); + return false; + } server->layer_shell = wlr_layer_shell_v1_create(server->wl_display, SWAY_LAYER_SHELL_VERSION); + if (!server->layer_shell) { + sway_log(SWAY_ERROR, "Failed to create layer shell"); + return false; + } wl_signal_add(&server->layer_shell->events.new_surface, &server->layer_shell_surface); server->layer_shell_surface.notify = handle_layer_shell_surface; server->xdg_shell = wlr_xdg_shell_create(server->wl_display, SWAY_XDG_SHELL_VERSION); + if (!server->xdg_shell) { + sway_log(SWAY_ERROR, "Failed to create XDG shell"); + return false; + } wl_signal_add(&server->xdg_shell->events.new_toplevel, &server->xdg_shell_toplevel); server->xdg_shell_toplevel.notify = handle_xdg_shell_toplevel; server->tablet_v2 = wlr_tablet_v2_create(server->wl_display); + if (!server->tablet_v2) { + sway_log(SWAY_ERROR, "Failed to create tablet manager"); + return false; + } server->server_decoration_manager = wlr_server_decoration_manager_create(server->wl_display); + if (!server->server_decoration_manager) { + sway_log(SWAY_ERROR, "Failed to create server decoration manager"); + return false; + } wlr_server_decoration_manager_set_default_mode( server->server_decoration_manager, WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); @@ -337,6 +399,10 @@ bool server_init(struct sway_server *server) { server->xdg_decoration_manager = wlr_xdg_decoration_manager_v1_create(server->wl_display); + if (!server->xdg_decoration_manager) { + sway_log(SWAY_ERROR, "Failed to create XDG decoration manager"); + return false; + } wl_signal_add( &server->xdg_decoration_manager->events.new_toplevel_decoration, &server->xdg_decoration); @@ -345,18 +411,36 @@ bool server_init(struct sway_server *server) { server->relative_pointer_manager = wlr_relative_pointer_manager_v1_create(server->wl_display); + if (!server->relative_pointer_manager) { + sway_log(SWAY_ERROR, "Failed to create relative pointer manager"); + return false; + } server->pointer_constraints = wlr_pointer_constraints_v1_create(server->wl_display); + if (!server->pointer_constraints) { + sway_log(SWAY_ERROR, "Failed to create pointer constraints"); + return false; + } server->pointer_constraint.notify = handle_pointer_constraint; wl_signal_add(&server->pointer_constraints->events.new_constraint, &server->pointer_constraint); - wlr_presentation_create(server->wl_display, server->backend, SWAY_PRESENTATION_VERSION); - wlr_alpha_modifier_v1_create(server->wl_display); + if (!wlr_presentation_create(server->wl_display, server->backend, SWAY_PRESENTATION_VERSION)) { + sway_log(SWAY_ERROR, "Failed to create presentation"); + return false; + } + if (!wlr_alpha_modifier_v1_create(server->wl_display)) { + sway_log(SWAY_ERROR, "Failed to create alpha modifier"); + return false; + } server->output_manager_v1 = wlr_output_manager_v1_create(server->wl_display); + if (!server->output_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create output manager"); + return false; + } server->output_manager_apply.notify = handle_output_manager_apply; wl_signal_add(&server->output_manager_v1->events.apply, &server->output_manager_apply); @@ -366,19 +450,43 @@ bool server_init(struct sway_server *server) { server->output_power_manager_v1 = wlr_output_power_manager_v1_create(server->wl_display); + if (!server->output_power_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create output power manager"); + return false; + } server->output_power_manager_set_mode.notify = handle_output_power_manager_set_mode; wl_signal_add(&server->output_power_manager_v1->events.set_mode, &server->output_power_manager_set_mode); server->input_method = wlr_input_method_manager_v2_create(server->wl_display); + if (!server->input_method) { + sway_log(SWAY_ERROR, "Failed to create input method manager"); + return false; + } server->text_input = wlr_text_input_manager_v3_create(server->wl_display); + if (!server->text_input) { + sway_log(SWAY_ERROR, "Failed to create text input manager"); + return false; + } server->foreign_toplevel_list = wlr_ext_foreign_toplevel_list_v1_create(server->wl_display, SWAY_FOREIGN_TOPLEVEL_LIST_VERSION); + if (!server->foreign_toplevel_list) { + sway_log(SWAY_ERROR, "Failed to create foreign toplevel list"); + return false; + } server->foreign_toplevel_manager = wlr_foreign_toplevel_manager_v1_create(server->wl_display); + if (!server->foreign_toplevel_manager) { + sway_log(SWAY_ERROR, "Failed to create foreign toplevel manager"); + return false; + } - sway_session_lock_init(); - sway_ext_workspace_init(); + if (!sway_session_lock_init()) { + return false; + } + if (!sway_ext_workspace_init()) { + return false; + } #if WLR_HAS_DRM_BACKEND server->drm_lease_manager= @@ -394,26 +502,74 @@ bool server_init(struct sway_server *server) { #endif server->export_dmabuf_manager_v1 = wlr_export_dmabuf_manager_v1_create(server->wl_display); + if (!server->export_dmabuf_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create export dmabuf manager"); + return false; + } server->screencopy_manager_v1 = wlr_screencopy_manager_v1_create(server->wl_display); + if (!server->screencopy_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create screencopy manager"); + return false; + } server->ext_image_copy_capture_manager_v1 = wlr_ext_image_copy_capture_manager_v1_create(server->wl_display, 1); - wlr_ext_output_image_capture_source_manager_v1_create(server->wl_display, 1); + if (!server->ext_image_copy_capture_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create ext image copy capture manager"); + return false; + } + if (!wlr_ext_output_image_capture_source_manager_v1_create(server->wl_display, 1)) { + sway_log(SWAY_ERROR, "Failed to create ext output image capture source manager"); + return false; + } server->wlr_data_control_manager_v1 = wlr_data_control_manager_v1_create(server->wl_display); + if (!server->wlr_data_control_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create data control manager"); + return false; + } server->ext_data_control_manager_v1 = wlr_ext_data_control_manager_v1_create(server->wl_display, 1); + if (!server->ext_data_control_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create ext data control manager"); + return false; + } server->security_context_manager_v1 = wlr_security_context_manager_v1_create(server->wl_display); - wlr_viewporter_create(server->wl_display); - wlr_single_pixel_buffer_manager_v1_create(server->wl_display); + if (!server->security_context_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create security context manager"); + return false; + } + if (!wlr_viewporter_create(server->wl_display)) { + sway_log(SWAY_ERROR, "Failed to create viewporter"); + return false; + } + if (!wlr_single_pixel_buffer_manager_v1_create(server->wl_display)) { + sway_log(SWAY_ERROR, "Failed to create single pixel buffer manager"); + return false; + } server->content_type_manager_v1 = wlr_content_type_manager_v1_create(server->wl_display, 1); - wlr_fractional_scale_manager_v1_create(server->wl_display, 1); + if (!server->content_type_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create content type manager"); + return false; + } + if (!wlr_fractional_scale_manager_v1_create(server->wl_display, 1)) { + sway_log(SWAY_ERROR, "Failed to create fractional scale manager"); + return false; + } server->ext_foreign_toplevel_image_capture_source_manager_v1 = wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(server->wl_display, 1); + if (!server->ext_foreign_toplevel_image_capture_source_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create ext foreign toplevel image capture source manager"); + return false; + } server->new_foreign_toplevel_capture_request.notify = handle_new_foreign_toplevel_capture_request; wl_signal_add(&server->ext_foreign_toplevel_image_capture_source_manager_v1->events.new_request, &server->new_foreign_toplevel_capture_request); server->tearing_control_v1 = wlr_tearing_control_manager_v1_create(server->wl_display, 1); + if (!server->tearing_control_v1) { + sway_log(SWAY_ERROR, "Failed to create tearing control manager"); + return false; + } server->tearing_control_new_object.notify = handle_new_tearing_hint; wl_signal_add(&server->tearing_control_v1->events.new_object, &server->tearing_control_new_object); @@ -421,10 +577,24 @@ bool server_init(struct sway_server *server) { struct wlr_xdg_foreign_registry *foreign_registry = wlr_xdg_foreign_registry_create(server->wl_display); - wlr_xdg_foreign_v1_create(server->wl_display, foreign_registry); - wlr_xdg_foreign_v2_create(server->wl_display, foreign_registry); + if (!foreign_registry) { + sway_log(SWAY_ERROR, "Failed to create XDG foreign registry"); + return false; + } + if (!wlr_xdg_foreign_v1_create(server->wl_display, foreign_registry)) { + sway_log(SWAY_ERROR, "Failed to create XDG foreign v1"); + return false; + } + if (!wlr_xdg_foreign_v2_create(server->wl_display, foreign_registry)) { + sway_log(SWAY_ERROR, "Failed to create XDG foreign v2"); + return false; + } server->xdg_activation_v1 = wlr_xdg_activation_v1_create(server->wl_display); + if (!server->xdg_activation_v1) { + sway_log(SWAY_ERROR, "Failed to create XDG activation"); + return false; + } server->xdg_activation_v1_request_activate.notify = xdg_activation_v1_handle_request_activate; wl_signal_add(&server->xdg_activation_v1->events.request_activate, @@ -436,6 +606,10 @@ bool server_init(struct sway_server *server) { struct wlr_xdg_toplevel_tag_manager_v1 *xdg_toplevel_tag_manager_v1 = wlr_xdg_toplevel_tag_manager_v1_create(server->wl_display, 1); + if (!xdg_toplevel_tag_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create XDG toplevel tag manager"); + return false; + } server->xdg_toplevel_tag_manager_v1_set_tag.notify = xdg_toplevel_tag_manager_v1_handle_set_tag; wl_signal_add(&xdg_toplevel_tag_manager_v1->events.set_tag, @@ -443,6 +617,10 @@ bool server_init(struct sway_server *server) { struct wlr_cursor_shape_manager_v1 *cursor_shape_manager = wlr_cursor_shape_manager_v1_create(server->wl_display, 2); + if (!cursor_shape_manager) { + sway_log(SWAY_ERROR, "Failed to create cursor shape manager"); + return false; + } server->request_set_cursor_shape.notify = handle_request_set_cursor_shape; wl_signal_add(&cursor_shape_manager->events.request_set_shape, &server->request_set_cursor_shape); @@ -471,11 +649,18 @@ bool server_init(struct sway_server *server) { }); free(transfer_functions); free(primaries); + if (!cm) { + sway_log(SWAY_ERROR, "Failed to create color manager"); + return false; + } wlr_scene_set_color_manager_v1(root->root_scene, cm); } - wlr_color_representation_manager_v1_create_with_renderer( - server->wl_display, 1, server->renderer); + if (!wlr_color_representation_manager_v1_create_with_renderer( + server->wl_display, 1, server->renderer)) { + sway_log(SWAY_ERROR, "Failed to create color representation manager"); + return false; + } wl_list_init(&server->pending_launcher_ctxs); @@ -515,8 +700,16 @@ bool server_init(struct sway_server *server) { } server->dirty_nodes = create_list(); + if (!server->dirty_nodes) { + sway_log(SWAY_ERROR, "Failed to create dirty nodes list"); + return false; + } server->input = input_manager_create(server); + if (!server->input) { + sway_log(SWAY_ERROR, "Failed to create input manager"); + return false; + } input_manager_get_default_seat(); // create seat0 return true; diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c index d366d19aa..23311a456 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c @@ -97,17 +97,18 @@ static void handle_commit(struct wl_listener *listener, void *data) { } // Initialize ext-workspace. Must be called once at startup. -void sway_ext_workspace_init(void) { +bool sway_ext_workspace_init(void) { server.workspace_manager_v1 = wlr_ext_workspace_manager_v1_create(server.wl_display, 1); if (!server.workspace_manager_v1) { sway_log(SWAY_ERROR, "Failed to create ext_workspace_manager_v1"); - return; + return false; } server.workspace_manager_v1_commit.notify = handle_commit; wl_signal_add(&server.workspace_manager_v1->events.commit, &server.workspace_manager_v1_commit); + return true; } // Must be called whenever an output is enabled. From 81246fc6dcdb1087fc02ea307b1c4b64e24395c3 Mon Sep 17 00:00:00 2001 From: Alexander Orzechowski Date: Fri, 20 Mar 2026 23:29:30 -0400 Subject: [PATCH 02/16] container: Move foreign toplevel enter/leave events to view It made sense to put it on the container level because the protocol cares about the toplevel and that includes its decorations. But, this breaks down when we consider if the container's view is fullscreen and the container decorations are disabled. Moving it to the view manages this expected lifetime better. Since the buffer is now part of the view, the buffer will get negative coordinates to act as if it's part of the container when we want to. A known issue is that we will send spurious leave/enter events while we reconfigure the scene for entering/exiting fullscreen. The fix for this loops back to atomic updates to scene and that is outside of the scope of this commit. Fixes: #9000 --- include/sway/tree/container.h | 4 --- include/sway/tree/view.h | 3 ++ sway/desktop/transaction.c | 16 ++++++--- sway/tree/container.c | 66 ----------------------------------- sway/tree/view.c | 53 ++++++++++++++++++++++++++-- 5 files changed, 66 insertions(+), 76 deletions(-) diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h index 12a53c843..d8780544d 100644 --- a/include/sway/tree/container.h +++ b/include/sway/tree/container.h @@ -91,10 +91,6 @@ struct sway_container { } border; struct wlr_scene_tree *content_tree; - struct wlr_scene_buffer *output_handler; - - struct wl_listener outputs_update; - struct wl_listener output_handler_destroy; struct sway_container_state current; struct sway_container_state pending; diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h index ae81c5bbf..26148a92a 100644 --- a/include/sway/tree/view.h +++ b/include/sway/tree/view.h @@ -69,6 +69,9 @@ struct sway_view { struct wlr_scene_tree *scene_tree; struct wlr_scene_tree *content_tree; struct wlr_scene_tree *saved_surface_tree; + struct wlr_scene_buffer *output_handler; + + struct wl_listener outputs_update; struct wlr_scene *image_capture_scene; struct wlr_ext_image_capture_source_v1 *image_capture_source; diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c index d912b39bb..d4e853dee 100644 --- a/sway/desktop/transaction.c +++ b/sway/desktop/transaction.c @@ -395,10 +395,6 @@ static void arrange_container(struct sway_container *con, // make sure it's enabled for viewing wlr_scene_node_set_enabled(&con->scene_tree->node, true); - if (con->output_handler) { - wlr_scene_buffer_set_dest_size(con->output_handler, width, height); - } - if (con->view) { int border_top = container_titlebar_height(); int border_width = con->current.border_thickness; @@ -456,6 +452,13 @@ static void arrange_container(struct sway_container *con, 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); + + // 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) { @@ -495,6 +498,11 @@ static void arrange_fullscreen(struct wlr_scene_tree *tree, // 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, true, container_get_gaps(fs)); diff --git a/sway/tree/container.c b/sway/tree/container.c index fd9abadc6..6880841bd 100644 --- a/sway/tree/container.c +++ b/sway/tree/container.c @@ -25,50 +25,6 @@ #include "log.h" #include "stringop.h" -static void handle_outputs_update( - struct wl_listener *listener, void *data) { - struct sway_container *con = wl_container_of( - listener, con, outputs_update); - struct wlr_scene_outputs_update_event *event = data; - - struct wlr_foreign_toplevel_handle_v1 *toplevel = con->view->foreign_toplevel; - if (toplevel) { - struct wlr_foreign_toplevel_handle_v1_output *toplevel_output, *tmp; - wl_list_for_each_safe(toplevel_output, tmp, &toplevel->outputs, link) { - bool active = false; - for (size_t i = 0; i < event->size; i++) { - struct wlr_scene_output *scene_output = event->active[i]; - if (scene_output->output == toplevel_output->output) { - active = true; - break; - } - } - - if (!active) { - wlr_foreign_toplevel_handle_v1_output_leave(toplevel, toplevel_output->output); - } - } - - for (size_t i = 0; i < event->size; i++) { - struct wlr_scene_output *scene_output = event->active[i]; - wlr_foreign_toplevel_handle_v1_output_enter(toplevel, scene_output->output); - } - } -} - -static void handle_destroy( - struct wl_listener *listener, void *data) { - struct sway_container *con = wl_container_of( - listener, con, output_handler_destroy); - - container_begin_destroy(con); -} - -static bool handle_point_accepts_input( - struct wlr_scene_buffer *buffer, double *x, double *y) { - return false; -} - static struct wlr_scene_rect *alloc_rect_node(struct wlr_scene_tree *parent, bool *failed) { if (*failed) { @@ -135,22 +91,6 @@ struct sway_container *container_create(struct sway_view *view) { c->border.bottom = alloc_rect_node(c->border.tree, &failed); c->border.left = alloc_rect_node(c->border.tree, &failed); c->border.right = alloc_rect_node(c->border.tree, &failed); - - c->output_handler = wlr_scene_buffer_create(c->border.tree, NULL); - if (!c->output_handler) { - sway_log(SWAY_ERROR, "Failed to allocate a scene node"); - failed = true; - } - - if (!failed) { - c->outputs_update.notify = handle_outputs_update; - wl_signal_add(&c->output_handler->events.outputs_update, - &c->outputs_update); - c->output_handler_destroy.notify = handle_destroy; - wl_signal_add(&c->output_handler->node.events.destroy, - &c->output_handler_destroy); - c->output_handler->point_accepts_input = handle_point_accepts_input; - } } if (!failed && !scene_descriptor_assign(&c->scene_tree->node, @@ -522,7 +462,6 @@ void container_destroy(struct sway_container *con) { if (con->view && con->view->container == con) { con->view->container = NULL; - wlr_scene_node_destroy(&con->output_handler->node); if (con->view->destroying) { view_destroy(con->view); } @@ -564,11 +503,6 @@ void container_begin_destroy(struct sway_container *con) { if (con->pending.parent || con->pending.workspace) { container_detach(con); } - - if (con->view && con->view->container == con) { - wl_list_remove(&con->outputs_update.link); - wl_list_remove(&con->output_handler_destroy.link); - } } void container_reap_empty(struct sway_container *con) { diff --git a/sway/tree/view.c b/sway/tree/view.c index 6dde3f63c..83b4972b1 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -38,6 +38,41 @@ #include "sway/xdg_decoration.h" #include "stringop.h" +static void handle_outputs_update( + struct wl_listener *listener, void *data) { + struct sway_view *view = wl_container_of(listener, view, outputs_update); + struct wlr_scene_outputs_update_event *event = data; + + struct wlr_foreign_toplevel_handle_v1 *toplevel = view->foreign_toplevel; + if (toplevel) { + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output, *tmp; + wl_list_for_each_safe(toplevel_output, tmp, &toplevel->outputs, link) { + bool active = false; + for (size_t i = 0; i < event->size; i++) { + struct wlr_scene_output *scene_output = event->active[i]; + if (scene_output->output == toplevel_output->output) { + active = true; + break; + } + } + + if (!active) { + wlr_foreign_toplevel_handle_v1_output_leave(toplevel, toplevel_output->output); + } + } + + for (size_t i = 0; i < event->size; i++) { + struct wlr_scene_output *scene_output = event->active[i]; + wlr_foreign_toplevel_handle_v1_output_enter(toplevel, scene_output->output); + } + } +} + +static bool handle_point_accepts_input( + struct wlr_scene_buffer *buffer, double *x, double *y) { + return false; +} + bool view_init(struct sway_view *view, enum sway_view_type type, const struct sway_view_impl *impl) { bool failed = false; @@ -51,12 +86,23 @@ bool view_init(struct sway_view *view, enum sway_view_type type, goto err; } + view->output_handler = wlr_scene_buffer_create(view->scene_tree, NULL); + if (!view->output_handler) { + sway_log(SWAY_ERROR, "Failed to allocate a scene node"); + goto err; + } + view->image_capture_scene = wlr_scene_create(); if (view->image_capture_scene == NULL) { goto err; } view->image_capture_scene->restack_xwayland_surfaces = false; + view->outputs_update.notify = handle_outputs_update; + wl_signal_add(&view->output_handler->events.outputs_update, + &view->outputs_update); + view->output_handler->point_accepts_input = handle_point_accepts_input; + view->type = type; view->impl = impl; view->executed_criteria = create_list(); @@ -102,6 +148,7 @@ void view_begin_destroy(struct sway_view *view) { return; } view->destroying = true; + wl_list_remove(&view->outputs_update.link); if (!view->container) { view_destroy(view); @@ -1229,8 +1276,10 @@ void view_save_buffer(struct sway_view *view) { return; } - // Enable and disable the saved surface tree like so to atomitaclly update - // the tree. This will prevent over damaging or other weirdness. + // Make sure the output handler is placed above the saved surface so we don't send + // spurious events to the foreign toplevel handler. Also, make the saved surface tree + // is disabled until it is ready to replace the real surface. + wlr_scene_node_place_below(&view->saved_surface_tree->node, &view->output_handler->node); wlr_scene_node_set_enabled(&view->saved_surface_tree->node, false); wlr_scene_node_for_each_buffer(&view->content_tree->node, From e51f9d7183c89706558015ce61e0460631ba3577 Mon Sep 17 00:00:00 2001 From: someever Date: Sun, 12 Apr 2026 20:10:59 +0300 Subject: [PATCH 03/16] Fix typos in sway-ipc.7.scd Some sections of the man page were difficult to understand. This fixes several typos and grammatical errors. --- sway/sway-ipc.7.scd | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/sway/sway-ipc.7.scd b/sway/sway-ipc.7.scd index 2658228a1..70c831375 100644 --- a/sway/sway-ipc.7.scd +++ b/sway/sway-ipc.7.scd @@ -802,7 +802,7 @@ node and will have the following properties: Retrieve the currently set marks *REPLY*++ -An array of marks current set. Since each mark can only be set for one +An array of marks currently set. Since each mark can only be set for one container, this is a set so each value is unique and the order is undefined. *Example Reply:* @@ -833,13 +833,13 @@ An array of bar IDs, which are strings *MESSAGE*++ When sent with a bar ID as the payload, this retrieves the config associated -with the specified by the bar ID in the payload. This is used by swaybar, but -could also be used for third party bars +with the bar ID specified in the payload. This is used by swaybar, but could +also be used for third-party bars *REPLY*++ An object that represents the configuration for the bar with the bar ID sent as -the payload. The following properties exists and more information about what -their value mean can be found in *sway-bar*(5): +the payload. The following properties exist, with more information about their +values in *sway-bar*(5): [- *PROPERTY* :- *DATA TYPE* @@ -1040,7 +1040,7 @@ An object containing the following properties: ## 8. GET_BINDING_MODES *MESSAGE*++ -Retrieve the list of binding modes that currently configured +Retrieve the list of binding modes that are currently configured *REPLY*++ An array of strings, with each string being the name of a binding mode. This @@ -1154,13 +1154,13 @@ following properties: _tablet\_tool_, _tablet\_pad_, or _switch_ |- xkb_active_layout_name : string -: (Only keyboards) The name of the active keyboard layout in use +: (Only keyboards) The name of the active keyboard layout |- xkb_layout_names : array : (Only keyboards) A list a layout names configured for the keyboard |- xkb_active_layout_index : integer -: (Only keyboards) The index of the active keyboard layout in use +: (Only keyboards) The index of the active keyboard layout |- scroll_factor : floating : (Only pointers) Multiplier applied on scroll event values. @@ -1467,7 +1467,7 @@ one seat. Each object has the following properties: # EVENTS -Events are a way for client to get notified of changes to sway. A client can +Events are a way for clients to get notified of changes to sway. A client can subscribe to any events it wants to be notified of changes for. The event is sent in the same format as a reply. The following events are currently available: @@ -1478,7 +1478,7 @@ available: |- 0x80000000 : workspace :[ Sent whenever an event involving a workspace occurs such as initialization - of a new workspace or a different workspace gains focus + of a new workspace or another workspace gaining focus |- 0x80000001 : output : Sent when outputs are updated @@ -1487,7 +1487,7 @@ available: : Sent whenever the binding mode changes |- 0x80000003 : window -: Sent whenever an event involving a window occurs such as being reparented, +: Sent whenever an event involving a window occurs such as it being reparented, focused, or closed |- 0x80000004 : barconfig_update @@ -1503,7 +1503,7 @@ available: : Sent when an ipc client sends a _SEND\_TICK_ message |- 0x80000014 : bar_state_update -: Send when the visibility of a bar should change due to a modifier +: Sent when the visibility of a bar should change due to a modifier |- 0x80000015 : input : Sent when something related to input devices changes @@ -1522,10 +1522,10 @@ single object with the following properties: :[ The type of change that occurred. See below for more information |- current : object -: An object representing the workspace effected or _null_ for _reload_ changes +: An object representing the affected workspace or _null_ for _reload_ changes |- old : object -: For a _focus_ change, this is will be an object representing the workspace +: For a _focus_ change, this will be an object representing the workspace being switched from. Otherwise, it is _null_ @@ -1543,7 +1543,7 @@ The following change types are currently available: |- rename : The workspace was renamed |- urgent -: A window on the workspace has had their urgency hint set or all urgency hints +: A window on the workspace has had its urgency hint set or all urgency hints for windows on the workspace have been cleared |- reload : The configuration file has been reloaded @@ -1653,7 +1653,7 @@ object with the following properties: :[ The type of change that occurred. See below for more information |- container : object -: An object representing the window effected +: An object representing the affected window The following change types are currently available: @@ -1816,7 +1816,7 @@ event is a single object with the following properties: : Whether this event was triggered by subscribing to the tick events |- payload : string -: The payload given with a _SEND\_TICK_ message, if any. Otherwise, an empty +: The payload provided in a _SEND\_TICK_ message, if any. Otherwise, an empty string @@ -1838,7 +1838,7 @@ event is a single object with the following properties: :- *DESCRIPTION* |- id : string -:[ The bar ID effected +:[ The bar ID affected |- visible_by_modifier : boolean : Whether the bar should be made visible due to a modifier being pressed @@ -1854,7 +1854,7 @@ event is a single object with the following properties: ## 0x80000015. INPUT -Sent when something related to the input devices changes. The event is a single +Sent when something related to input devices changes. The event is a single object with the following properties: [- *PROPERTY* @@ -1865,7 +1865,7 @@ object with the following properties: :[ What has changed |- input : object -: An object representing the input that is identical the ones GET_INPUTS gives +: An object representing the input that is identical to the ones GET_INPUTS gives The following change types are currently available: [- *TYPE* From 136765a530f9cd2242c152292aaaeec46443c968 Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Wed, 15 Apr 2026 16:14:40 -0400 Subject: [PATCH 04/16] input/seat: end keyboard grab when clearing focus When focus leaves a surface, ensure any active keyboard grab is terminated. This prevents stale xdg-popup grabs from persisting after the popup is destroyed, which could otherwise cause keyboard input to remain stuck on the old window. fixes #8919 --- sway/input/seat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sway/input/seat.c b/sway/input/seat.c index ebdbd91ed..42e37bf03 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -14,7 +14,6 @@ #include #include #include -#include "config.h" #include "list.h" #include "log.h" #include "sway/config.h" @@ -1080,6 +1079,7 @@ static void send_unfocus(struct sway_container *con, void *data) { static void seat_send_unfocus(struct sway_node *node, struct sway_seat *seat) { sway_cursor_constrain(seat->cursor, NULL); wlr_seat_keyboard_notify_clear_focus(seat->wlr_seat); + wlr_seat_keyboard_end_grab(seat->wlr_seat); if (node->type == N_WORKSPACE) { workspace_for_each_container(node->sway_workspace, send_unfocus, seat); } else { From 9a5f09c867894dacf25f54929cfd808b301712b1 Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Fri, 17 Apr 2026 18:10:35 -0400 Subject: [PATCH 05/16] input/seat: fix drag-and-drop regression and improve popup dismissal --- sway/input/seat.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/sway/input/seat.c b/sway/input/seat.c index 42e37bf03..83772f52c 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -1071,6 +1071,7 @@ bool seat_is_input_allowed(struct sway_seat *seat, static void send_unfocus(struct sway_container *con, void *data) { if (con->view) { + view_close_popups(con->view); view_set_activated(con->view, false); } } @@ -1079,7 +1080,6 @@ static void send_unfocus(struct sway_container *con, void *data) { static void seat_send_unfocus(struct sway_node *node, struct sway_seat *seat) { sway_cursor_constrain(seat->cursor, NULL); wlr_seat_keyboard_notify_clear_focus(seat->wlr_seat); - wlr_seat_keyboard_end_grab(seat->wlr_seat); if (node->type == N_WORKSPACE) { workspace_for_each_container(node->sway_workspace, send_unfocus, seat); } else { @@ -1136,10 +1136,6 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n struct sway_workspace *last_workspace = seat_get_focused_workspace(seat); if (node == NULL) { - // Close any popups on the old focus - if (node_is_view(last_focus)) { - view_close_popups(last_focus->sway_container->view); - } seat_send_unfocus(last_focus, seat); sway_input_method_relay_set_focus(&seat->im_relay, NULL); seat->has_focus = false; @@ -1228,11 +1224,6 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n } } - // Close any popups on the old focus - if (last_focus && node_is_view(last_focus)) { - view_close_popups(last_focus->sway_container->view); - } - // If urgent, either unset the urgency or start a timer to unset it if (container && container->view && view_is_urgent(container->view) && !container->view->urgent_timer) { From 91c662fa59f49e5cb547ca83e5741a8bece3aaf9 Mon Sep 17 00:00:00 2001 From: llyyr Date: Thu, 23 Apr 2026 00:40:25 +0530 Subject: [PATCH 06/16] sway/server: chase wlroots!5327 Ref: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5327 --- sway/server.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sway/server.c b/sway/server.c index 8bdafb674..c481c7e99 100644 --- a/sway/server.c +++ b/sway/server.c @@ -227,7 +227,7 @@ static void handle_renderer_lost(struct wl_listener *listener, void *data) { } static void handle_new_foreign_toplevel_capture_request(struct wl_listener *listener, void *data) { - struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request = data; + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_event *request = data; struct sway_view *view = request->toplevel_handle->data; if (view->image_capture_source == NULL) { @@ -561,7 +561,7 @@ bool server_init(struct sway_server *server) { return false; } server->new_foreign_toplevel_capture_request.notify = handle_new_foreign_toplevel_capture_request; - wl_signal_add(&server->ext_foreign_toplevel_image_capture_source_manager_v1->events.new_request, + wl_signal_add(&server->ext_foreign_toplevel_image_capture_source_manager_v1->events.capture_request, &server->new_foreign_toplevel_capture_request); server->tearing_control_v1 = From 80a940a99254fe5cc2ffce20c0e52aacb60ec7f6 Mon Sep 17 00:00:00 2001 From: Hugo Osvaldo Barrera Date: Thu, 23 Apr 2026 14:09:02 +0200 Subject: [PATCH 07/16] Treat ext-workspace-v1 as privileged And do not expose it to clients in security contexts. Fixes: https://github.com/swaywm/sway/issues/9120 --- sway/server.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sway/server.c b/sway/server.c index c481c7e99..92d4fe5ed 100644 --- a/sway/server.c +++ b/sway/server.c @@ -126,7 +126,8 @@ static bool is_privileged(const struct wl_global *global) { global == server.input->virtual_keyboard->global || global == server.input->virtual_pointer->global || global == server.input->transient_seat_manager->global || - global == server.xdg_output_manager_v1->global; + global == server.xdg_output_manager_v1->global || + global == server.workspace_manager_v1->global; } static bool filter_global(const struct wl_client *client, From 1cbb8a440f157047292709c171e59f0feeb26475 Mon Sep 17 00:00:00 2001 From: Hugo Osvaldo Barrera Date: Thu, 23 Apr 2026 15:30:18 +0200 Subject: [PATCH 08/16] Ignore failures creating linux-drm-syncobj Failures creating this global are non-fatal. Fixes: 1606311553cb58a280e320907098bd0f443fef6e Fixes: https://github.com/swaywm/sway/issues/9110 --- sway/server.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sway/server.c b/sway/server.c index 92d4fe5ed..8c55c0395 100644 --- a/sway/server.c +++ b/sway/server.c @@ -296,11 +296,8 @@ bool server_init(struct sway_server *server) { if (wlr_renderer_get_drm_fd(server->renderer) >= 0 && server->renderer->features.timeline && server->backend->features.timeline) { - if (!wlr_linux_drm_syncobj_manager_v1_create(server->wl_display, 1, - wlr_renderer_get_drm_fd(server->renderer))) { - sway_log(SWAY_ERROR, "Failed to create linux-drm-syncobj v1"); - return false; - } + wlr_linux_drm_syncobj_manager_v1_create(server->wl_display, 1, + wlr_renderer_get_drm_fd(server->renderer)); } server->allocator = wlr_allocator_autocreate(server->backend, From 1084d2e87abc8f608270042df7c88f76302d8d9e Mon Sep 17 00:00:00 2001 From: llyyr Date: Wed, 1 Apr 2026 01:24:28 +0530 Subject: [PATCH 09/16] sway/desktop/transaction: skip freeing dirty nodes This fixes a race that causes UAF when turning on multiple outputs after they've been off for a while. When output_begin_destroy is called while a transaction that references the output is in-flight, node_set_dirty adds the node to server.dirty_nodes list and ntxnrefs is still held by that transaction. Once the transaction completes and ntxnrefs drops to 0, transaction_destroy frees the output, leaving a dangling pointer in server.dirty_nodes. The next transaction_commit_dirty call then walks the dirty_nodes list and crashes The fix is to skip the free in transaction_destroy if node->dirty is set, this means transaction_commit_dirty hasn't processed this node yet and will bump ntxnrefs shortly. The free will happen once that transaction completes and ntxnrefs reaches 0 again and transaction_commit_dirty will allocate a fresh instruction and increment ntxnrefs again when it processes the node. --- sway/desktop/transaction.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c index d4e853dee..082fe20ce 100644 --- a/sway/desktop/transaction.c +++ b/sway/desktop/transaction.c @@ -59,7 +59,7 @@ static void transaction_destroy(struct sway_transaction *transaction) { if (node->instruction == instruction) { node->instruction = NULL; } - if (node->destroying && node->ntxnrefs == 0) { + if (node->destroying && node->ntxnrefs == 0 && !node->dirty) { switch (node->type) { case N_ROOT: sway_assert(false, "Never reached"); From c857ca3a978896f4f9bdd481ec5cf395662a2dc5 Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Sun, 1 Feb 2026 21:53:06 -0500 Subject: [PATCH 10/16] tree/workspace: fix crash on dragging container to workspace edge When a container is moved, `finalize_move` previously assumed that calling `workspace_split` would always result in a workspace with exactly 1 child (the wrapper container). Consequently, it safely assumed that inserting a container with `after = 1` was always valid. However, if the moved container was the only child of its workspace, calling `container_detach` drops the workspace's tiling length to 0. Calling `workspace_split` on an empty workspace simply changes its layout enum and returns, leaving the length at 0. Passing `after` (which evaluates to 1 when moving right/down) into `workspace_insert_tiling` then causes an out-of-bounds insertion and a subsequent segmentation fault during `container_build_representation`. This commit fixes the issue by dynamically calculating the insertion index based on the actual length of the workspace's tiling list at the moment of insertion, rather than overloading the `after` boolean flag as a hardcoded index. --- sway/input/seatop_move_tiling.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sway/input/seatop_move_tiling.c b/sway/input/seatop_move_tiling.c index c525b77a9..f19c3f18b 100644 --- a/sway/input/seatop_move_tiling.c +++ b/sway/input/seatop_move_tiling.c @@ -373,7 +373,7 @@ static void finalize_move(struct sway_seat *seat) { enum sway_container_layout new_layout = edge == WLR_EDGE_TOP || edge == WLR_EDGE_BOTTOM ? L_VERT : L_HORIZ; workspace_split(new_ws, new_layout); - workspace_insert_tiling(new_ws, con, after); + workspace_insert_tiling(new_ws, con, after ? new_ws->tiling->length : 0); } if (old_parent) { From 0bf8731114f8b74d97066cd1d480ed1aad735163 Mon Sep 17 00:00:00 2001 From: Jan Palus Date: Sun, 10 May 2026 01:07:45 +0200 Subject: [PATCH 11/16] man: update drag_lock default ref. #8800 Fixes #9119 --- sway/sway-input.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sway/sway-input.5.scd b/sway/sway-input.5.scd index 18744a22a..389b20570 100644 --- a/sway/sway-input.5.scd +++ b/sway/sway-input.5.scd @@ -154,7 +154,7 @@ The following commands may only be used in the configuration file. *input* drag_lock enabled|disabled|enabled_sticky Enables or disables drag lock for specified input device. The default is - _enabled_sticky_. + _disabled_. *input* dwt enabled|disabled Enables or disables disable-while-typing for the specified input device. From 9c663b1fa1c2ef5c6df0427ba905f347f6594ab6 Mon Sep 17 00:00:00 2001 From: MATHIP6 <116129737+MATHIP6@users.noreply.github.com> Date: Wed, 13 May 2026 20:01:33 -0400 Subject: [PATCH 12/16] Fix typo in README.fr.md Corrected a typographical error in the documentation regarding the usage of 'man 5 sway'. Updated 'pour' to 'sur' for better clarity. --- README.fr.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.fr.md b/README.fr.md index c4ef699a4..a751a52cb 100644 --- a/README.fr.md +++ b/README.fr.md @@ -57,7 +57,7 @@ Si vous utilisez déjà i3, copiez votre configuration i3 vers `~/.config/sway/config` et sway fonctionnera directement. Sinon, copiez l'exemple de fichier de configuration vers `~/.config/sway/config`. Il se trouve généralement dans `/etc/sway/config`. Exécutez `man 5 sway` pour lire la -documentation pour la configuration de sway. +documentation sur la configuration de sway. ## Exécution From e52c14d535ff2956fb66374f36b7dda740eccbac Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Sat, 23 May 2026 10:16:11 +0200 Subject: [PATCH 13/16] i3-compat: swaybar: default to first bar-id When launching swaybar from the command line without a --bar-id flag, default to the first configured bar. This is a mirror of i3 PR https://github.com/i3/i3/pull/4231. This makes it easier to call swaybar from the command line since the majority of swaybar users have only one bar configured. --- swaybar/ipc.c | 22 ++++++++++++++++++++++ swaybar/main.c | 6 ------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/swaybar/ipc.c b/swaybar/ipc.c index 68d8dd32d..c3929a35f 100644 --- a/swaybar/ipc.c +++ b/swaybar/ipc.c @@ -417,6 +417,28 @@ void ipc_execute_binding(struct swaybar *bar, struct swaybar_binding *bind) { } bool ipc_initialize(struct swaybar *bar) { + if (!bar->id) { + uint32_t len = 0; + char *res = ipc_single_command(bar->ipc_socketfd, + IPC_GET_BAR_CONFIG, "", &len); + json_object *bars = json_tokener_parse(res); + if (!json_object_is_type(bars, json_type_array) + || json_object_array_length(bars) == 0) { + sway_log(SWAY_ERROR, "No bar configuration found, " + "please configure a bar block in your sway config file."); + json_object_put(bars); + free(res); + return false; + } + json_object *first = json_object_array_get_idx(bars, 0); + bar->id = strdup(json_object_get_string(first)); + json_object_put(bars); + free(res); + sway_log(SWAY_INFO, "Using first bar config: %s. " + "Use --bar_id to manually select a different bar configuration.", + bar->id); + } + uint32_t len = strlen(bar->id); char *res = ipc_single_command(bar->ipc_socketfd, IPC_GET_BAR_CONFIG, bar->id, &len); diff --git a/swaybar/main.c b/swaybar/main.c index e1b0cecac..c2020ff09 100644 --- a/swaybar/main.c +++ b/swaybar/main.c @@ -72,12 +72,6 @@ int main(int argc, char **argv) { sway_log_init(SWAY_INFO, NULL); } - if (!swaybar.id) { - sway_log(SWAY_ERROR, "No bar_id passed. " - "Provide --bar_id or let sway start swaybar"); - return 1; - } - if (!socket_path) { socket_path = get_socketpath(); if (!socket_path) { From abb959602f063c4c4799456ff38f6b5aeb9b18f0 Mon Sep 17 00:00:00 2001 From: Scott Leggett Date: Fri, 22 May 2026 22:08:41 +0800 Subject: [PATCH 14/16] tree/workspace: fix unwrapping children to avoid redundant split Before this change, with `workspace_layout tabbed`, moving T[app app] to an empty workspace resulted in T[T[app] T[app]], as each child was individually wrapped in a new split container. It looks like commit 92891fb1 introduced workspace_unwrap_children to fix an issue where moving a tabbed/stacked container to a new workspace with workspace_layout tabbed would incorrectly nest the container, creating T[T[app app]]. However, workspace_unwrap_children used workspace_add_tiling to add the detached children to the workspace. Since commit ece6a1d4, workspace_add_tiling checks for a configured default_layout and if it finds one, calls container_split for every added window. This commit changes the unwrapping logic to use workspace_insert_tiling_direct instead of workspace_add_tiling, avoiding the default_layout splitting behaviour. --- sway/tree/workspace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c index 23311a456..9d78d7080 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c @@ -920,7 +920,7 @@ void workspace_unwrap_children(struct sway_workspace *ws, while (wrap->pending.children->length) { struct sway_container *child = wrap->pending.children->items[0]; container_detach(child); - workspace_add_tiling(ws, child); + workspace_insert_tiling_direct(ws, child, ws->tiling->length); } } From 3774506bd092563538af2b6d2e332c84a8ac153c Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Mon, 27 Apr 2026 00:16:26 -0400 Subject: [PATCH 15/16] seatop_move_tiling: clamp edge thresholds to parent box When a fullscreen view is present on a workspace that has panels (e.g. a bar with exclusive zone), the view's content geometry extends beyond the workspace's usable area. The edge-detection thresholds computed from the view's content_x/content_width could then lie outside the parent container's box, causing the indicator width or height to become negative. This triggered an assertion in wlr_scene_rect_set_size. Fix this by clamping the threshold coordinates to the parent container's boundaries before using them to size the drop indicator. fixes #9102 --- sway/input/seatop_move_tiling.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sway/input/seatop_move_tiling.c b/sway/input/seatop_move_tiling.c index f19c3f18b..3a764d9ac 100644 --- a/sway/input/seatop_move_tiling.c +++ b/sway/input/seatop_move_tiling.c @@ -234,18 +234,22 @@ static void handle_motion_postthreshold(struct sway_seat *seat) { if (layout == L_HORIZ || layout == L_TABBED) { if (cursor->cursor->y < thresh_top) { edge = WLR_EDGE_TOP; + if (thresh_top < box.y) thresh_top = box.y; box.height = thresh_top - box.y; } else if (cursor->cursor->y > thresh_bottom) { edge = WLR_EDGE_BOTTOM; + if (thresh_bottom > box.y + box.height) thresh_bottom = box.y + box.height; box.height = box.y + box.height - thresh_bottom; box.y = thresh_bottom; } } else if (layout == L_VERT || layout == L_STACKED) { if (cursor->cursor->x < thresh_left) { edge = WLR_EDGE_LEFT; + if (thresh_left < box.x) thresh_left = box.x; box.width = thresh_left - box.x; } else if (cursor->cursor->x > thresh_right) { edge = WLR_EDGE_RIGHT; + if (thresh_right > box.x + box.width) thresh_right = box.x + box.width; box.width = box.x + box.width - thresh_right; box.x = thresh_right; } From 3cb86e4a2aa8e1938d97e54f15843c4e3dfbae27 Mon Sep 17 00:00:00 2001 From: Konstantin Pospelov Date: Sun, 3 May 2026 21:33:05 +0200 Subject: [PATCH 16/16] input/seat: check only the last output workspace on focus `seat_set_workspace_focus()` contains two references to the "last workspace": - `last_workspace` is the last focused workspace on all outputs. - `new_output_last_ws` is the last focused workspace on the new workspace output. They are the same when using a single output, but with multiple outputs they can diverge: `last_workspace` will be the focused workspace on the previous output, `new_output_last_ws` will be the previously focused workspace on the current output. This confusion seems to have led to #9139 when `last_workspace` was used instead of `new_output_last_ws`. However, we can safely drop `last_workspace` and use `new_output_last_ws` for everything that `seat_set_workspace_focus()` does: - Calling `node_set_dirty()` on the new output. - Calling `wlr_ext_workspace_handle_v1_set_active()` on the last workspace (this fixes #9139). - Skip `workspace_consider_destroy()` on `last_workspace`, since the other output still has it focused. This commit also renames `new_output_last_ws` to `last_workspace` to keep the name simple. --- sway/input/seat.c | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/sway/input/seat.c b/sway/input/seat.c index 83772f52c..0434d637c 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -1133,8 +1133,6 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n return; } - struct sway_workspace *last_workspace = seat_get_focused_workspace(seat); - if (node == NULL) { seat_send_unfocus(last_focus, seat); sway_input_method_relay_set_focus(&seat->im_relay, NULL); @@ -1157,17 +1155,15 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n return; } + // Find the output's last workspace, which might have to be removed if empty struct sway_output *new_output = new_workspace ? new_workspace->output : NULL; - + struct sway_workspace *last_workspace = + new_output ? output_get_active_workspace(new_output) : NULL; if (last_workspace != new_workspace && new_output) { node_set_dirty(&new_output->node); } - // find new output's old workspace, which might have to be removed if empty - struct sway_workspace *new_output_last_ws = - new_output ? output_get_active_workspace(new_output) : NULL; - // Unfocus the previous focus if (last_focus) { seat_send_unfocus(last_focus, seat); @@ -1211,11 +1207,11 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n } // Move sticky containers to new workspace - if (new_workspace && new_output_last_ws - && new_workspace != new_output_last_ws) { - for (int i = 0; i < new_output_last_ws->floating->length; ++i) { + if (new_workspace && last_workspace + && new_workspace != last_workspace) { + for (int i = 0; i < last_workspace->floating->length; ++i) { struct sway_container *floater = - new_output_last_ws->floating->items[i]; + last_workspace->floating->items[i]; if (container_is_sticky(floater)) { container_detach(floater); workspace_add_floating(new_workspace, floater); @@ -1244,13 +1240,9 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n } } - if (new_output_last_ws) { - workspace_consider_destroy(new_output_last_ws); - } - if (last_workspace && last_workspace != new_output_last_ws) { + if (last_workspace) { workspace_consider_destroy(last_workspace); } - seat->has_focus = true; if (config->smart_gaps && new_workspace) {