From 1606311553cb58a280e320907098bd0f443fef6e Mon Sep 17 00:00:00 2001 From: Hugo Osvaldo Barrera Date: Thu, 9 Apr 2026 02:09:59 +0200 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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) {