From 3a49409dae9b8d579a4b92370ff5ba4a48cb5156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Piwo=C5=84ski?= Date: Wed, 12 Mar 2025 11:56:52 +0000 Subject: [PATCH 01/51] sway/commands: Return error if container is not in scratchpad --- sway/commands/scratchpad.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sway/commands/scratchpad.c b/sway/commands/scratchpad.c index c995f2f08..8a63740c3 100644 --- a/sway/commands/scratchpad.c +++ b/sway/commands/scratchpad.c @@ -118,10 +118,10 @@ struct cmd_results *cmd_scratchpad(int argc, char **argv) { // If using criteria, this command is executed for every container which // matches the criteria. If this container isn't in the scratchpad, - // we'll just silently return a success. The same is true if the + // we'll return an error. The same is true if the // overridden node is not a container. if (!con || !con->scratchpad) { - return cmd_results_new(CMD_SUCCESS, NULL); + return cmd_results_new(CMD_INVALID, "Container is not in scratchpad."); } scratchpad_toggle_container(con); } else { From 30434b2beb0c621015452775011426da8d5e4705 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Wed, 19 Mar 2025 00:15:01 +0100 Subject: [PATCH 02/51] desktop/output: Skip repaint if wlr_output is disabled When the repaint timer fires, we check if the sway_output is disabled, and if so, skip the output commit after having reset frame_pending. The sway_output enable flag is only updated if the output is disabled and removed from the layout, not if the power is disabled for e.g. idle. This can lead to situations where a commit is attempted on a disabled output, which will lead to an attempted and failed primary swapchain allocation. Use the wlr_output.enabled state to check if the output is active. --- sway/desktop/output.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sway/desktop/output.c b/sway/desktop/output.c index e24193175..aec1d0a6b 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -264,7 +264,7 @@ static int output_repaint_timer_handler(void *data) { struct sway_output *output = data; output->wlr_output->frame_pending = false; - if (!output->enabled) { + if (!output->wlr_output->enabled) { return 0; } From d148560f50ce81bd5ca0e0f0d52c65c21f8b751d Mon Sep 17 00:00:00 2001 From: ShootingStarDragons Date: Thu, 20 Mar 2025 21:58:21 +0900 Subject: [PATCH 03/51] text_input: Fix ime panic in ext-session-lock in the origin text_input.c, we only check the sway_view and layershell, but now we have the third shell named sessionlock, so we need to modify both text_input.c and view.c to handle the new type of shell --- sway/input/text_input.c | 25 +++++++++++++++++++++++++ sway/tree/view.c | 4 ++++ 2 files changed, 29 insertions(+) diff --git a/sway/input/text_input.c b/sway/input/text_input.c index e16724671..a46f833c9 100644 --- a/sway/input/text_input.c +++ b/sway/input/text_input.c @@ -10,6 +10,7 @@ #include "sway/input/text_input_popup.h" #include "sway/layers.h" #include "sway/server.h" +#include static struct sway_text_input *relay_get_focusable_text_input( struct sway_input_method_relay *relay) { @@ -385,6 +386,8 @@ static void input_popup_set_focus(struct sway_input_popup *popup, struct wlr_layer_surface_v1 *layer_surface = wlr_layer_surface_v1_try_from_wlr_surface(surface); + struct wlr_session_lock_surface_v1 *lock_surface = + wlr_session_lock_surface_v1_try_from_wlr_surface(surface); struct wlr_scene_tree *relative_parent; if (layer_surface) { @@ -404,8 +407,30 @@ static void input_popup_set_focus(struct sway_input_popup *popup, // surface. Layer surfaces get destroyed as part of the output being // destroyed, thus also trickling down to popups. popup->fixed_output = layer->layer_surface->output; + } else if (lock_surface) { + wl_signal_add(&lock_surface->surface->events.unmap, + &popup->focused_surface_unmap); + + struct sway_layer_surface *lock = lock_surface->data; + if (lock == NULL) { + return; + } + + relative_parent = lock->scene->tree; + popup->desc.view = NULL; + + // we don't need to add an event here to NULL out this field because + // this field will only be initialized if the popup is part of a layer + // surface. Layer surfaces get destroyed as part of the output being + // destroyed, thus also trickling down to popups. + popup->fixed_output = lock->layer_surface->output; } else { struct sway_view *view = view_from_wlr_surface(surface); + // In the future there may be other shells been added, so we also need to check here. + if (view == NULL) { + sway_log(SWAY_DEBUG, "Unsupported IME focus surface"); + return; + } wl_signal_add(&view->events.unmap, &popup->focused_surface_unmap); relative_parent = view->scene_tree; popup->desc.view = view; diff --git a/sway/tree/view.c b/sway/tree/view.c index 33161cc53..a5617fb4c 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -12,6 +12,7 @@ #include #include #include +#include #if WLR_HAS_XWAYLAND #include #endif @@ -1010,6 +1011,9 @@ struct sway_view *view_from_wlr_surface(struct wlr_surface *wlr_surface) { if (wlr_layer_surface_v1_try_from_wlr_surface(wlr_surface) != NULL) { return NULL; } + if (wlr_session_lock_surface_v1_try_from_wlr_surface(wlr_surface) != NULL) { + return NULL; + } const char *role = wlr_surface->role ? wlr_surface->role->name : NULL; sway_log(SWAY_DEBUG, "Surface of unknown type (role %s): %p", From 4b185a0fe0031455d5ceab1eda2b9d9ffe0c81de Mon Sep 17 00:00:00 2001 From: Paul Riou Date: Thu, 20 Mar 2025 20:01:36 +0000 Subject: [PATCH 04/51] stringop: fix has_prefix() arg order in config parsing has_prefix() expects the prefix to be the 2nd argument, not the first. The config parsing was broken when using `--input-device=`. Introduced by: 0c60d1581f7b12ae472c786b7dfe27a1c6ec9a47 "Use has_prefix() instead of strncmp() throughout" --- sway/commands/bind.c | 2 +- sway/commands/gesture.c | 2 +- swaybar/ipc.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sway/commands/bind.c b/sway/commands/bind.c index 32d4f8916..15373d5a8 100644 --- a/sway/commands/bind.c +++ b/sway/commands/bind.c @@ -367,7 +367,7 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, } } else if (strcmp("--exclude-titlebar", argv[0]) == 0) { exclude_titlebar = true; - } else if (has_prefix("--input-device=", argv[0])) { + } else if (has_prefix(argv[0], "--input-device=")) { free(binding->input); binding->input = strdup(argv[0] + strlen("--input-device=")); strip_quotes(binding->input); diff --git a/sway/commands/gesture.c b/sway/commands/gesture.c index e63b7ac6e..8dff29a34 100644 --- a/sway/commands/gesture.c +++ b/sway/commands/gesture.c @@ -121,7 +121,7 @@ static struct cmd_results *cmd_bind_or_unbind_gesture(int argc, char **argv, boo binding->flags |= BINDING_EXACT; } else if (strcmp("--no-warn", argv[0]) == 0) { warn = false; - } else if (has_prefix("--input-device=", argv[0])) { + } else if (has_prefix(argv[0], "--input-device=")) { free(binding->input); binding->input = strdup(argv[0] + strlen("--input-device=")); } else { diff --git a/swaybar/ipc.c b/swaybar/ipc.c index f651f0359..68d8dd32d 100644 --- a/swaybar/ipc.c +++ b/swaybar/ipc.c @@ -46,7 +46,7 @@ void ipc_send_workspace_command(struct swaybar *bar, const char *ws) { char *parse_font(const char *font) { char *new_font = NULL; - if (has_prefix("pango:", font)) { + if (has_prefix(font, "pango:")) { font += strlen("pango:"); } new_font = strdup(font); From c2d6aff64c1e265c8f1d95b780b54193defae18a Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Wed, 12 Mar 2025 16:35:41 +0100 Subject: [PATCH 05/51] Avoid crashing on too many containers If far too many containers are created, they can become so small that their size calculations come out negative, leading to crashes on asserts. Instead, set a lower bound for sizes and disable the container entirely if it goes below it, giving whatever space it used to the last container. The splits are not recalculated, so currently the effect is that if all containers have the same width fraction, they keep getting narrower until at some point they all round to zero and the last container will be given all the available space. A better behavior would have been if the additional container did not contribute to size and fraction calculations at all, but it's an extreme edge-case, anything is better than crashing, and this is easier to implement. --- sway/desktop/transaction.c | 27 ++++++++++++++++++--------- sway/tree/arrange.c | 18 +++++++++++++++--- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c index 52c03b18a..16ce8e2ed 100644 --- a/sway/desktop/transaction.c +++ b/sway/desktop/transaction.c @@ -312,9 +312,9 @@ static void arrange_children(enum sway_container_layout layout, list_t *children wlr_scene_node_set_position(&child->scene_tree->node, 0, title_bar_height); wlr_scene_node_reparent(&child->scene_tree->node, content); - if (activated) { - arrange_container(child, width, height - title_bar_height, - title_bar_height == 0, 0); + height -= title_bar_height; + if (activated && width > 0 && height > 0) { + arrange_container(child, width, height, title_bar_height == 0, 0); } else { disable_container(child); } @@ -341,9 +341,9 @@ static void arrange_children(enum sway_container_layout layout, list_t *children wlr_scene_node_set_position(&child->scene_tree->node, 0, title_height); wlr_scene_node_reparent(&child->scene_tree->node, content); - if (activated) { - arrange_container(child, width, height - title_height, - title_bar_height == 0, 0); + height -= title_bar_height; + if (activated && width > 0 && height > 0) { + arrange_container(child, width, height, title_bar_height == 0, 0); } else { disable_container(child); } @@ -359,8 +359,12 @@ static void arrange_children(enum sway_container_layout layout, list_t *children wlr_scene_node_set_enabled(&child->border.tree->node, true); wlr_scene_node_set_position(&child->scene_tree->node, 0, off); wlr_scene_node_reparent(&child->scene_tree->node, content); - arrange_container(child, width, cheight, true, gaps); - off += cheight + gaps; + if (width > 0 && cheight > 0) { + arrange_container(child, width, cheight, true, gaps); + off += cheight + gaps; + } else { + disable_container(child); + } } } else if (layout == L_HORIZ) { int off = 0; @@ -372,7 +376,12 @@ static void arrange_children(enum sway_container_layout layout, list_t *children wlr_scene_node_set_position(&child->scene_tree->node, off, 0); wlr_scene_node_reparent(&child->scene_tree->node, content); arrange_container(child, cwidth, height, true, gaps); - off += cwidth + gaps; + if (cwidth > 0 && height > 0) { + arrange_container(child, cwidth, height, true, gaps); + off += cwidth + gaps; + } else { + disable_container(child); + } } } else { sway_assert(false, "unreachable"); diff --git a/sway/tree/arrange.c b/sway/tree/arrange.c index 2b95a6dc9..faf54d02a 100644 --- a/sway/tree/arrange.c +++ b/sway/tree/arrange.c @@ -29,7 +29,7 @@ static void apply_horiz_layout(list_t *children, struct wlr_box *parent) { } } - // Calculate each height fraction + // Calculate each width fraction double total_width_fraction = 0; for (int i = 0; i < children->length; ++i) { struct sway_container *child = children->items[i]; @@ -82,12 +82,18 @@ static void apply_horiz_layout(list_t *children, struct wlr_box *parent) { child->pending.y = parent->y; child->pending.width = round(child->width_fraction * child_total_width); child->pending.height = parent->height; - child_x += child->pending.width + inner_gap; // Make last child use remaining width of parent if (i == children->length - 1) { child->pending.width = parent->x + parent->width - child->pending.x; } + + // Arbitrary lower bound for window size + if (child->pending.width < 10 || child->pending.height < 10) { + child->pending.width = 0; + child->pending.height = 0; + } + child_x += child->pending.width + inner_gap; } } @@ -161,12 +167,18 @@ static void apply_vert_layout(list_t *children, struct wlr_box *parent) { child->pending.y = child_y; child->pending.width = parent->width; child->pending.height = round(child->height_fraction * child_total_height); - child_y += child->pending.height + inner_gap; // Make last child use remaining height of parent if (i == children->length - 1) { child->pending.height = parent->y + parent->height - child->pending.y; } + + // Arbitrary lower bound for window size + if (child->pending.width < 10 || child->pending.height < 10) { + child->pending.width = 0; + child->pending.height = 0; + } + child_y += child->pending.height + inner_gap; } } From ab455bbadae5f115262161c165fdd46d1cc4295d Mon Sep 17 00:00:00 2001 From: Dennis Baurichter Date: Tue, 11 Feb 2025 18:06:19 +0100 Subject: [PATCH 06/51] man: clarify criteria (incl. PCRE2 usage) Replace the XWayland-only class attribute in the examples: - The first example given should work for Wayland-native windows. - The example 'Kill all windows with the title "Emacs"' should use title, not class. Also, it's a substring (regex) match. There are many different implementations of regular expressions with incompatible syntax. For example, GNU grep alone provides three different ones. Clarify the use of PCRE2 by sway criteria. --- sway/sway.5.scd | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/sway/sway.5.scd b/sway/sway.5.scd index f580b2af3..9bc03c9de 100644 --- a/sway/sway.5.scd +++ b/sway/sway.5.scd @@ -972,7 +972,7 @@ The default colors are: A criteria is a string in the form of, for example: ``` -[class="[Rr]egex.*" title="some title"] +[app_id="some-application" title="[Rr]egex.*"] ``` The string contains one or more (space separated) attribute/value pairs. They @@ -992,10 +992,19 @@ Focus on a window with the mark "IRC": [con_mark="IRC"] focus ``` -Kill all windows with the title "Emacs": +Kill all windows where the title contains "Emacs": ``` -[class="Emacs"] kill +[title="Emacs"] kill +``` + +Several attributes allow regular expressions. These use Perl-compatible regular +expressions (PCRE2), which are documented in *pcre2pattern*(3) and summarized in +*pcre2syntax*(3). For example, this moves all windows with titles ending in +"sway" or "Sway" to workspace 1: + +``` +[title="[Ss]way$"] move workspace 1 ``` You may like to use swaymsg -t get_tree for finding the values of these @@ -1094,3 +1103,4 @@ The following attributes may be matched with: # SEE ALSO *sway*(1) *sway-input*(5) *sway-output*(5) *sway-bar*(5) *sway-ipc*(7) +*pcre2pattern*(3) *pcre2syntax*(3) From a25645a5a6e205fccee1ca5abc00f8ee31b3bf44 Mon Sep 17 00:00:00 2001 From: Claudia Date: Wed, 26 Mar 2025 19:34:44 +0100 Subject: [PATCH 07/51] Fix tabbed/stacking container height regression Commit c2d6aff added a bounds check on `height - title_bar_height`, repurposing the local variable `height` in an attempt to DRY out the expression. However, because re-assignment occurs inside the loop body, its result would leak across loop iterations, compounding its effect and leading to the artifact reported in issue #8625, where each child except the first in a tabbed container would acquire a visible waterline. Introduce a second variable and reset it in each loop iteration to get rid of the waterline. Fixes #8625. --- sway/desktop/transaction.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c index 16ce8e2ed..01fe31289 100644 --- a/sway/desktop/transaction.c +++ b/sway/desktop/transaction.c @@ -312,9 +312,9 @@ static void arrange_children(enum sway_container_layout layout, list_t *children wlr_scene_node_set_position(&child->scene_tree->node, 0, title_bar_height); wlr_scene_node_reparent(&child->scene_tree->node, content); - height -= title_bar_height; - if (activated && width > 0 && height > 0) { - arrange_container(child, width, height, title_bar_height == 0, 0); + int net_height = height - title_bar_height; + if (activated && width > 0 && net_height > 0) { + arrange_container(child, width, net_height, title_bar_height == 0, 0); } else { disable_container(child); } @@ -341,9 +341,9 @@ static void arrange_children(enum sway_container_layout layout, list_t *children wlr_scene_node_set_position(&child->scene_tree->node, 0, title_height); wlr_scene_node_reparent(&child->scene_tree->node, content); - height -= title_bar_height; - if (activated && width > 0 && height > 0) { - arrange_container(child, width, height, title_bar_height == 0, 0); + int net_height = height - title_bar_height; + if (activated && width > 0 && net_height > 0) { + arrange_container(child, width, net_height, title_bar_height == 0, 0); } else { disable_container(child); } From cb246cb9c2c1440e05998d82eada21eaba107d59 Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Tue, 8 Apr 2025 17:13:03 -0400 Subject: [PATCH 08/51] ipc: standardize pretty print with raw print `swaymsg -t get_inputs --raw` calls it a pointer but `--pretty` calls it a Mouse. Previous commit 6737b90cb that set this to pointer probably forgo to update the pretty one. closes #8584 --- swaymsg/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swaymsg/main.c b/swaymsg/main.c index 7534ea6db..dc1c74072 100644 --- a/swaymsg/main.c +++ b/swaymsg/main.c @@ -99,7 +99,7 @@ static const char *pretty_type_name(const char *name) { const char *b; } type_names[] = { { "keyboard", "Keyboard" }, - { "pointer", "Mouse" }, + { "pointer", "Pointer" }, { "touchpad", "Touchpad" }, { "tablet_pad", "Tablet pad" }, { "tablet_tool", "Tablet tool" }, From 8f089f0229eabbcd3f68d641d5b826220f1edb0c Mon Sep 17 00:00:00 2001 From: Daniel De Graaf Date: Sun, 9 Mar 2025 22:51:48 -0400 Subject: [PATCH 09/51] Use wl_event_loop_add_signal for exit signals This avoids calling non-async-signal-safe functions from within a signal handler. --- sway/main.c | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/sway/main.c b/sway/main.c index cabdd3aa0..0af977d4d 100644 --- a/sway/main.c +++ b/sway/main.c @@ -44,10 +44,6 @@ void sway_terminate(int exit_code) { } } -void sig_handler(int signal) { - sway_terminate(EXIT_SUCCESS); -} - void run_as_ipc_client(char *command, char *socket_path) { int socketfd = ipc_open_socket(socket_path); uint32_t len = strlen(command); @@ -152,6 +148,22 @@ void restore_nofile_limit(void) { } } +static int term_signal(int signal, void *data) { + sway_terminate(EXIT_SUCCESS); + return 0; +} + +static void init_signals(void) { + wl_event_loop_add_signal(server.wl_event_loop, SIGTERM, term_signal, NULL); + wl_event_loop_add_signal(server.wl_event_loop, SIGINT, term_signal, NULL); + + // avoid need to reap children + signal(SIGCHLD, SIG_IGN); + + // prevent ipc write errors from crashing sway + signal(SIGPIPE, SIG_IGN); +} + void restore_signals(void) { sigset_t set; sigemptyset(&set); @@ -330,22 +342,14 @@ int main(int argc, char **argv) { increase_nofile_limit(); - // handle SIGTERM signals - signal(SIGTERM, sig_handler); - signal(SIGINT, sig_handler); - - // avoid need to reap children - signal(SIGCHLD, SIG_IGN); - - // prevent ipc from crashing sway - signal(SIGPIPE, SIG_IGN); - sway_log(SWAY_INFO, "Starting sway version " SWAY_VERSION); if (!server_init(&server)) { return 1; } + init_signals(); + if (server.linux_dmabuf_v1) { wlr_scene_set_linux_dmabuf_v1(root->root_scene, server.linux_dmabuf_v1); } From 5e6a6ea3404330f99d2425d20cd9e298164d5988 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 23 May 2024 14:34:24 +0200 Subject: [PATCH 10/51] idle_inhibit: Ignore inhibitors when locked When a session is locked, no views are visible and there is no way to interact with the user session. This means that all inhibitors based on visibility - with the exception being inhibitors on the session lock surfaces themselves - become inert, allowing the session to go idle. The only inhibitor type on normal views that one could argue should remain active is INHIBIT_IDLE_OPEN, but for now we disable all view inhibitors regardless of type. --- sway/desktop/idle_inhibit_v1.c | 14 ++++++++++++++ sway/lock.c | 7 +++++++ sway/tree/view.c | 4 ++++ 3 files changed, 25 insertions(+) diff --git a/sway/desktop/idle_inhibit_v1.c b/sway/desktop/idle_inhibit_v1.c index f3af7aa1b..d241cdaf4 100644 --- a/sway/desktop/idle_inhibit_v1.c +++ b/sway/desktop/idle_inhibit_v1.c @@ -1,5 +1,6 @@ #include #include +#include #include "log.h" #include "sway/desktop/idle_inhibit_v1.h" #include "sway/input/seat.h" @@ -103,6 +104,19 @@ void sway_idle_inhibit_v1_user_inhibitor_destroy( } bool sway_idle_inhibit_v1_is_active(struct sway_idle_inhibitor_v1 *inhibitor) { + if (server.session_lock.lock) { + // A session lock is active. In this case, only application inhibitors + // on the session lock surface can have any effect. + if (inhibitor->mode != INHIBIT_IDLE_APPLICATION) { + return false; + } + struct wlr_surface *wlr_surface = inhibitor->wlr_inhibitor->surface; + if (!wlr_session_lock_surface_v1_try_from_wlr_surface(wlr_surface)) { + return false; + } + return wlr_surface->mapped; + } + switch (inhibitor->mode) { case INHIBIT_IDLE_APPLICATION:; // If there is no view associated with the inhibitor, assume visible diff --git a/sway/lock.c b/sway/lock.c index 43f313308..c8975c747 100644 --- a/sway/lock.c +++ b/sway/lock.c @@ -234,6 +234,9 @@ static void handle_unlock(struct wl_listener *listener, void *data) { struct sway_output *output = root->outputs->items[i]; arrange_layers(output); } + + // Views are now visible, so check if we need to activate inhibition again. + sway_idle_inhibit_v1_check_active(); } static void handle_abandon(struct wl_listener *listener, void *data) { @@ -297,6 +300,10 @@ static void handle_session_lock(struct wl_listener *listener, void *data) { wlr_session_lock_v1_send_locked(lock); server.session_lock.lock = sway_lock; + + // The lock screen covers everything, so check if any active inhibition got + // deactivated due to lost visibility. + sway_idle_inhibit_v1_check_active(); } static void handle_session_lock_destroy(struct wl_listener *listener, void *data) { diff --git a/sway/tree/view.c b/sway/tree/view.c index a5617fb4c..16080a2f9 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -218,6 +218,10 @@ uint32_t view_configure(struct sway_view *view, double lx, double ly, int width, } bool view_inhibit_idle(struct sway_view *view) { + if (server.session_lock.lock) { + return false; + } + struct sway_idle_inhibitor_v1 *user_inhibitor = sway_idle_inhibit_v1_user_inhibitor_for_view(view); From 541183b3228b986b5d09f12513506c386456df7e Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 23 May 2024 15:01:46 +0200 Subject: [PATCH 11/51] idle_inhibit: Explicitly handle layer surfaces Layer surfaces do not have a view, and while they can be occluded they are always visible on their associated output - assuming it is enabled. --- sway/desktop/idle_inhibit_v1.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sway/desktop/idle_inhibit_v1.c b/sway/desktop/idle_inhibit_v1.c index d241cdaf4..0fc79989a 100644 --- a/sway/desktop/idle_inhibit_v1.c +++ b/sway/desktop/idle_inhibit_v1.c @@ -119,8 +119,15 @@ bool sway_idle_inhibit_v1_is_active(struct sway_idle_inhibitor_v1 *inhibitor) { switch (inhibitor->mode) { case INHIBIT_IDLE_APPLICATION:; + struct wlr_surface *wlr_surface = inhibitor->wlr_inhibitor->surface; + if (wlr_layer_surface_v1_try_from_wlr_surface(wlr_surface)) { + // Layer surfaces can be occluded but are always on screen after + // they have been mapped. + return wlr_surface->mapped; + } + // If there is no view associated with the inhibitor, assume visible - struct sway_view *view = view_from_wlr_surface(inhibitor->wlr_inhibitor->surface); + struct sway_view *view = view_from_wlr_surface(wlr_surface); return !view || !view->container || view_is_visible(view); case INHIBIT_IDLE_FOCUS:; struct sway_seat *seat = NULL; From cc482228a41e9e3e31b6b27143d5ed24e7dc5069 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 23 May 2024 15:03:44 +0200 Subject: [PATCH 12/51] idle_inhibit: Assume view is invisible by default We have historically considered surfaces without a view visible. This made sense in case of layer surfaces which do not have a view, but it also allows unmapped surfaces to act as global inhibitors irrespective of the current view state, which is not the intention fo the protocol. As we now explicitly handle layer surfaces, assume that views are only visible if they can be found and their visibility checked. --- sway/desktop/idle_inhibit_v1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sway/desktop/idle_inhibit_v1.c b/sway/desktop/idle_inhibit_v1.c index 0fc79989a..9fc223d06 100644 --- a/sway/desktop/idle_inhibit_v1.c +++ b/sway/desktop/idle_inhibit_v1.c @@ -126,9 +126,9 @@ bool sway_idle_inhibit_v1_is_active(struct sway_idle_inhibitor_v1 *inhibitor) { return wlr_surface->mapped; } - // If there is no view associated with the inhibitor, assume visible + // If there is no view associated with the inhibitor, assume invisible struct sway_view *view = view_from_wlr_surface(wlr_surface); - return !view || !view->container || view_is_visible(view); + return view && view->container && view_is_visible(view); case INHIBIT_IDLE_FOCUS:; struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { From 583862e6d11c44bb7a66dbc4dbdf029b517df18b Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 11 Jun 2024 00:12:02 +0200 Subject: [PATCH 13/51] idle_inhibit: Check if layer surface output is enabled While we we cannot easily check for true visibility of layer surfaces as easily as for views, we can check at least check that the output associated with the surface is enabled. --- sway/desktop/idle_inhibit_v1.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sway/desktop/idle_inhibit_v1.c b/sway/desktop/idle_inhibit_v1.c index 9fc223d06..b495c204c 100644 --- a/sway/desktop/idle_inhibit_v1.c +++ b/sway/desktop/idle_inhibit_v1.c @@ -120,10 +120,13 @@ bool sway_idle_inhibit_v1_is_active(struct sway_idle_inhibitor_v1 *inhibitor) { switch (inhibitor->mode) { case INHIBIT_IDLE_APPLICATION:; struct wlr_surface *wlr_surface = inhibitor->wlr_inhibitor->surface; - if (wlr_layer_surface_v1_try_from_wlr_surface(wlr_surface)) { + struct wlr_layer_surface_v1 *layer_surface = + wlr_layer_surface_v1_try_from_wlr_surface(wlr_surface); + if (layer_surface) { // Layer surfaces can be occluded but are always on screen after // they have been mapped. - return wlr_surface->mapped; + return layer_surface->output && layer_surface->output->enabled && + wlr_surface->mapped; } // If there is no view associated with the inhibitor, assume invisible From 0a9b0b83ebfffc3b5b456b49d8cfe76736fe011b Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann Date: Tue, 18 Feb 2025 01:51:49 +0100 Subject: [PATCH 14/51] server: remove event listeners on fini This fixes a crash in wlroots listener checks. See #8509. --- sway/server.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/sway/server.c b/sway/server.c index 5de6648f6..fc2ba8191 100644 --- a/sway/server.c +++ b/sway/server.c @@ -460,8 +460,29 @@ bool server_init(struct sway_server *server) { } void server_fini(struct sway_server *server) { + // remove listeners + wl_list_remove(&server->renderer_lost.link); + wl_list_remove(&server->new_output.link); + wl_list_remove(&server->layer_shell_surface.link); + wl_list_remove(&server->xdg_shell_toplevel.link); + wl_list_remove(&server->server_decoration.link); + wl_list_remove(&server->xdg_decoration.link); + wl_list_remove(&server->pointer_constraint.link); + wl_list_remove(&server->output_manager_apply.link); + wl_list_remove(&server->output_manager_test.link); + wl_list_remove(&server->output_power_manager_set_mode.link); +#if WLR_HAS_DRM_BACKEND + wl_list_remove(&server->drm_lease_request.link); +#endif + wl_list_remove(&server->tearing_control_new_object.link); + wl_list_remove(&server->xdg_activation_v1_request_activate.link); + wl_list_remove(&server->xdg_activation_v1_new_token.link); + wl_list_remove(&server->request_set_cursor_shape.link); + // TODO: free sway-specific resources #if WLR_HAS_XWAYLAND + wl_list_remove(&server->xwayland_surface.link); + wl_list_remove(&server->xwayland_ready.link); wlr_xwayland_destroy(server->xwayland.wlr_xwayland); #endif wl_display_destroy_clients(server->wl_display); From e51ecf71aa0afdcce4c9768f0a614222ee79cee2 Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann Date: Tue, 18 Feb 2025 01:52:25 +0100 Subject: [PATCH 15/51] input/input-manager: remove event listeners on fini This fixes a crash in wlroots listener checks. See #8509. --- include/sway/input/input-manager.h | 2 ++ sway/input/input-manager.c | 8 ++++++++ sway/server.c | 1 + 3 files changed, 11 insertions(+) diff --git a/include/sway/input/input-manager.h b/include/sway/input/input-manager.h index b014e18f8..5113844d8 100644 --- a/include/sway/input/input-manager.h +++ b/include/sway/input/input-manager.h @@ -39,6 +39,8 @@ struct sway_input_manager { struct sway_input_manager *input_manager_create(struct sway_server *server); +void input_manager_finish(struct sway_input_manager *input); + bool input_manager_has_focus(struct sway_node *node); void input_manager_set_focus(struct sway_node *node); diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c index 99af4c691..ffcf8fc52 100644 --- a/sway/input/input-manager.c +++ b/sway/input/input-manager.c @@ -493,6 +493,14 @@ struct sway_input_manager *input_manager_create(struct sway_server *server) { return input; } +void input_manager_finish(struct sway_input_manager *input) { + wl_list_remove(&input->new_input.link); + wl_list_remove(&input->virtual_keyboard_new.link); + wl_list_remove(&input->virtual_pointer_new.link); + wl_list_remove(&input->keyboard_shortcuts_inhibit_new_inhibitor.link); + wl_list_remove(&input->transient_seat_create.link); +} + bool input_manager_has_focus(struct sway_node *node) { struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { diff --git a/sway/server.c b/sway/server.c index fc2ba8191..ff7ed42e1 100644 --- a/sway/server.c +++ b/sway/server.c @@ -478,6 +478,7 @@ void server_fini(struct sway_server *server) { wl_list_remove(&server->xdg_activation_v1_request_activate.link); wl_list_remove(&server->xdg_activation_v1_new_token.link); wl_list_remove(&server->request_set_cursor_shape.link); + input_manager_finish(server->input); // TODO: free sway-specific resources #if WLR_HAS_XWAYLAND From 92c82e6952394b714d7818deb1d46d616a39dfc0 Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann Date: Tue, 18 Feb 2025 01:52:57 +0100 Subject: [PATCH 16/51] desktop/idle_inhibit: remove event listeners on destroy This fixes a crash in wlroots listener checks. See #8509. --- include/sway/desktop/idle_inhibit_v1.h | 1 + sway/desktop/idle_inhibit_v1.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/include/sway/desktop/idle_inhibit_v1.h b/include/sway/desktop/idle_inhibit_v1.h index 84cc666d0..447ac8708 100644 --- a/include/sway/desktop/idle_inhibit_v1.h +++ b/include/sway/desktop/idle_inhibit_v1.h @@ -13,6 +13,7 @@ enum sway_idle_inhibit_mode { struct sway_idle_inhibit_manager_v1 { struct wlr_idle_inhibit_manager_v1 *wlr_manager; struct wl_listener new_idle_inhibitor_v1; + struct wl_listener manager_destroy; struct wl_list inhibitors; }; diff --git a/sway/desktop/idle_inhibit_v1.c b/sway/desktop/idle_inhibit_v1.c index b495c204c..6b2761fcd 100644 --- a/sway/desktop/idle_inhibit_v1.c +++ b/sway/desktop/idle_inhibit_v1.c @@ -45,6 +45,14 @@ void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data) { sway_idle_inhibit_v1_check_active(); } +void handle_manager_destroy(struct wl_listener *listener, void *data) { + struct sway_idle_inhibit_manager_v1 *manager = + wl_container_of(listener, manager, manager_destroy); + + wl_list_remove(&manager->manager_destroy.link); + wl_list_remove(&manager->new_idle_inhibitor_v1.link); +} + void sway_idle_inhibit_v1_user_inhibitor_register(struct sway_view *view, enum sway_idle_inhibit_mode mode) { struct sway_idle_inhibit_manager_v1 *manager = &server.idle_inhibit_manager_v1; @@ -177,6 +185,9 @@ bool sway_idle_inhibit_manager_v1_init(void) { wl_signal_add(&manager->wlr_manager->events.new_inhibitor, &manager->new_idle_inhibitor_v1); manager->new_idle_inhibitor_v1.notify = handle_idle_inhibitor_v1; + wl_signal_add(&manager->wlr_manager->events.destroy, + &manager->manager_destroy); + manager->manager_destroy.notify = handle_manager_destroy; wl_list_init(&manager->inhibitors); return true; From 53126cdceb6dbf6ee163ca0db960cf3900870075 Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann Date: Tue, 18 Feb 2025 22:45:53 +0100 Subject: [PATCH 17/51] input/text_input: remove event listeners on destroy sway_input_method_relay can be destroyed from two sources, either the seat is destroyed or the manager protocol objects are destroyed due compositor exit. This fixes a crash in wlroots listener checks. See #8509. --- include/sway/input/text_input.h | 2 ++ sway/input/text_input.c | 38 +++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/include/sway/input/text_input.h b/include/sway/input/text_input.h index 1993f928f..1818749ad 100644 --- a/include/sway/input/text_input.h +++ b/include/sway/input/text_input.h @@ -25,8 +25,10 @@ struct sway_input_method_relay { struct wlr_input_method_v2 *input_method; // doesn't have to be present struct wl_listener text_input_new; + struct wl_listener text_input_manager_destroy; struct wl_listener input_method_new; + struct wl_listener input_method_manager_destroy; struct wl_listener input_method_commit; struct wl_listener input_method_new_popup_surface; struct wl_listener input_method_grab_keyboard; diff --git a/sway/input/text_input.c b/sway/input/text_input.c index a46f833c9..c84fac8f6 100644 --- a/sway/input/text_input.c +++ b/sway/input/text_input.c @@ -597,6 +597,34 @@ static void relay_handle_input_method(struct wl_listener *listener, } } +static void sway_input_method_relay_finish_text_input(struct sway_input_method_relay *relay) { + wl_list_remove(&relay->text_input_new.link); + wl_list_remove(&relay->text_input_manager_destroy.link); + wl_list_init(&relay->text_input_new.link); + wl_list_init(&relay->text_input_manager_destroy.link); +} + +static void relay_handle_text_input_manager_destroy(struct wl_listener *listener, void *data) { + struct sway_input_method_relay *relay = wl_container_of(listener, relay, + text_input_manager_destroy); + + sway_input_method_relay_finish_text_input(relay); +} + +static void sway_input_method_relay_finish_input_method(struct sway_input_method_relay *relay) { + wl_list_remove(&relay->input_method_new.link); + wl_list_remove(&relay->input_method_manager_destroy.link); + wl_list_init(&relay->input_method_new.link); + wl_list_init(&relay->input_method_manager_destroy.link); +} + +static void relay_handle_input_method_manager_destroy(struct wl_listener *listener, void *data) { + struct sway_input_method_relay *relay = wl_container_of(listener, relay, + input_method_manager_destroy); + + sway_input_method_relay_finish_input_method(relay); +} + void sway_input_method_relay_init(struct sway_seat *seat, struct sway_input_method_relay *relay) { relay->seat = seat; @@ -606,16 +634,22 @@ void sway_input_method_relay_init(struct sway_seat *seat, relay->text_input_new.notify = relay_handle_text_input; wl_signal_add(&server.text_input->events.text_input, &relay->text_input_new); + relay->text_input_manager_destroy.notify = relay_handle_text_input_manager_destroy; + wl_signal_add(&server.text_input->events.destroy, + &relay->text_input_manager_destroy); relay->input_method_new.notify = relay_handle_input_method; wl_signal_add( &server.input_method->events.input_method, &relay->input_method_new); + relay->input_method_manager_destroy.notify = relay_handle_input_method_manager_destroy; + wl_signal_add(&server.input_method->events.destroy, + &relay->input_method_manager_destroy); } void sway_input_method_relay_finish(struct sway_input_method_relay *relay) { - wl_list_remove(&relay->input_method_new.link); - wl_list_remove(&relay->text_input_new.link); + sway_input_method_relay_finish_text_input(relay); + sway_input_method_relay_finish_input_method(relay); } void sway_input_method_relay_set_focus(struct sway_input_method_relay *relay, From ab2e1f5817a8024366fcb02285c978c5fef7dae1 Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann Date: Tue, 18 Feb 2025 23:14:06 +0100 Subject: [PATCH 18/51] tree/container: remove event listeners on destroy Change begin_destroy to remove event listeners before the final destroy, since otherwise event listeners would be removed twice, which crashes. This fixes a crash in wlroots listener checks. See #8509. --- include/sway/tree/container.h | 1 + sway/tree/container.c | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h index 4608b8ac2..4fb2d7208 100644 --- a/include/sway/tree/container.h +++ b/include/sway/tree/container.h @@ -95,6 +95,7 @@ struct sway_container { struct wl_listener output_enter; struct wl_listener output_leave; + struct wl_listener output_handler_destroy; struct sway_container_state current; struct sway_container_state pending; diff --git a/sway/tree/container.c b/sway/tree/container.c index 6ff4036fa..0385d7c17 100644 --- a/sway/tree/container.c +++ b/sway/tree/container.c @@ -50,6 +50,14 @@ static void handle_output_leave( } } +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; @@ -135,6 +143,9 @@ struct sway_container *container_create(struct sway_view *view) { c->output_leave.notify = handle_output_leave; wl_signal_add(&c->output_handler->events.output_leave, &c->output_leave); + 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; } } @@ -508,8 +519,6 @@ void container_destroy(struct sway_container *con) { if (con->view && con->view->container == con) { con->view->container = NULL; - wl_list_remove(&con->output_enter.link); - wl_list_remove(&con->output_leave.link); wlr_scene_node_destroy(&con->output_handler->node); if (con->view->destroying) { view_destroy(con->view); @@ -552,6 +561,12 @@ 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->output_enter.link); + wl_list_remove(&con->output_leave.link); + wl_list_remove(&con->output_handler_destroy.link); + } } void container_reap_empty(struct sway_container *con) { From 240a69ad63ad36893132ab1187035654d9478436 Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann Date: Fri, 21 Mar 2025 18:35:36 +0100 Subject: [PATCH 19/51] server: recreate renderer in idle callback to avoid UAF Destroying the wlr_renderer in a callback to its own renderer_lost event is unsafe due to wl_signal_emit*() still accessing it after it was destroyed. Delegate recreation of renderer to an idle callback and ensure that only one such idle callback is scheduled at a time by storing the returned event source. --- include/sway/server.h | 1 + sway/server.c | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/include/sway/server.h b/include/sway/server.h index feb516c5b..b1d7523ca 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -46,6 +46,7 @@ struct sway_server { struct wl_listener new_output; struct wl_listener renderer_lost; + struct wl_event_source *recreating_renderer; struct wlr_idle_notifier_v1 *idle_notifier_v1; struct sway_idle_inhibit_manager_v1 idle_inhibit_manager_v1; diff --git a/sway/server.c b/sway/server.c index ff7ed42e1..9d882862a 100644 --- a/sway/server.c +++ b/sway/server.c @@ -182,11 +182,11 @@ static void detect_proprietary(struct wlr_backend *backend, void *data) { drmFreeVersion(version); } -static void handle_renderer_lost(struct wl_listener *listener, void *data) { - struct sway_server *server = wl_container_of(listener, server, renderer_lost); +static void do_renderer_recreate(void *data) { + struct sway_server *server = data; + server->recreating_renderer = NULL; sway_log(SWAY_INFO, "Re-creating renderer after GPU reset"); - struct wlr_renderer *renderer = wlr_renderer_autocreate(server->backend); if (renderer == NULL) { sway_log(SWAY_ERROR, "Unable to create renderer"); @@ -221,6 +221,18 @@ static void handle_renderer_lost(struct wl_listener *listener, void *data) { wlr_renderer_destroy(old_renderer); } +static void handle_renderer_lost(struct wl_listener *listener, void *data) { + struct sway_server *server = wl_container_of(listener, server, renderer_lost); + + if (server->recreating_renderer != NULL) { + sway_log(SWAY_DEBUG, "Re-creation of renderer already scheduled"); + return; + } + + sway_log(SWAY_INFO, "Scheduling re-creation of renderer after GPU reset"); + server->recreating_renderer = wl_event_loop_add_idle(server->wl_event_loop, do_renderer_recreate, server); +} + bool server_init(struct sway_server *server) { sway_log(SWAY_DEBUG, "Initializing Wayland server"); server->wl_display = wl_display_create(); From 4943534929dfd3f0ea55e26241544354f8365e60 Mon Sep 17 00:00:00 2001 From: Loukas Agorgianitis Date: Mon, 14 Apr 2025 08:57:40 +0200 Subject: [PATCH 20/51] server: fix shutdown crash when running on x11 backend Signed-off-by: Loukas Agorgianitis --- sway/server.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sway/server.c b/sway/server.c index 9d882862a..f7155a77d 100644 --- a/sway/server.c +++ b/sway/server.c @@ -484,7 +484,9 @@ void server_fini(struct sway_server *server) { wl_list_remove(&server->output_manager_test.link); wl_list_remove(&server->output_power_manager_set_mode.link); #if WLR_HAS_DRM_BACKEND - wl_list_remove(&server->drm_lease_request.link); + if (server->drm_lease_manager) { + wl_list_remove(&server->drm_lease_request.link); + } #endif wl_list_remove(&server->tearing_control_new_object.link); wl_list_remove(&server->xdg_activation_v1_request_activate.link); From 3f0b3f8f9b3b737fd0e6d36e2a2c469b07268ec2 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 14 Apr 2025 09:29:45 +0200 Subject: [PATCH 21/51] Fix crash on shutdown when Xwayland is disabled --- sway/server.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sway/server.c b/sway/server.c index f7155a77d..5aa5f4f99 100644 --- a/sway/server.c +++ b/sway/server.c @@ -496,9 +496,11 @@ void server_fini(struct sway_server *server) { // TODO: free sway-specific resources #if WLR_HAS_XWAYLAND - wl_list_remove(&server->xwayland_surface.link); - wl_list_remove(&server->xwayland_ready.link); - wlr_xwayland_destroy(server->xwayland.wlr_xwayland); + if (server->xwayland.wlr_xwayland != NULL) { + wl_list_remove(&server->xwayland_surface.link); + wl_list_remove(&server->xwayland_ready.link); + wlr_xwayland_destroy(server->xwayland.wlr_xwayland); + } #endif wl_display_destroy_clients(server->wl_display); wlr_backend_destroy(server->backend); From 7733bf9963d6a18df548164642e6637c2e71f1f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Vuji=C4=8Di=C4=87?= Date: Mon, 14 Apr 2025 20:58:20 +1200 Subject: [PATCH 22/51] Remove duplicate arrange_container --- sway/desktop/transaction.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c index 01fe31289..0b3cbfb4d 100644 --- a/sway/desktop/transaction.c +++ b/sway/desktop/transaction.c @@ -375,7 +375,6 @@ static void arrange_children(enum sway_container_layout layout, list_t *children wlr_scene_node_set_enabled(&child->border.tree->node, true); wlr_scene_node_set_position(&child->scene_tree->node, off, 0); wlr_scene_node_reparent(&child->scene_tree->node, content); - arrange_container(child, cwidth, height, true, gaps); if (cwidth > 0 && height > 0) { arrange_container(child, cwidth, height, true, gaps); off += cwidth + gaps; From 8a8c78deacd388dabbe82a4a5055458494cd6258 Mon Sep 17 00:00:00 2001 From: llyyr Date: Wed, 16 Apr 2025 17:32:47 +0530 Subject: [PATCH 23/51] layer_shell: destroy layer_surface on assigned output destruction According to the spec, the closed event should be sent when the surface is no longer shown, because the output may have been destroyed or the user may have asked for it to be removed. In such cases, the clients should destroy the resource. This fixes mako not being able to show notifications if the assigned output was destroyed while a notificataion was still visible Fixes: 188811f80861 ("scene_graph: Port layer_shell") --- sway/desktop/layer_shell.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sway/desktop/layer_shell.c b/sway/desktop/layer_shell.c index 05faa4656..b14a9a4b1 100644 --- a/sway/desktop/layer_shell.c +++ b/sway/desktop/layer_shell.c @@ -221,7 +221,7 @@ static void handle_output_destroy(struct wl_listener *listener, void *data) { wl_container_of(listener, layer, output_destroy); layer->output = NULL; - wlr_scene_node_destroy(&layer->scene->tree->node); + wlr_layer_surface_v1_destroy(layer->layer_surface); } static void handle_node_destroy(struct wl_listener *listener, void *data) { From d3e1c13e1f40d38a454fa0236975c4d5196bd77e Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 17 Apr 2025 19:11:10 +0200 Subject: [PATCH 24/51] swaymsg, swaynag: drop sway_terminate() definitions These are unused. --- swaymsg/main.c | 4 ---- swaynag/main.c | 5 ----- 2 files changed, 9 deletions(-) diff --git a/swaymsg/main.c b/swaymsg/main.c index dc1c74072..6a9eb1987 100644 --- a/swaymsg/main.c +++ b/swaymsg/main.c @@ -16,10 +16,6 @@ #include "ipc-client.h" #include "log.h" -void sway_terminate(int exit_code) { - exit(exit_code); -} - static bool success_object(json_object *result) { json_object *success; diff --git a/swaynag/main.c b/swaynag/main.c index 634bddbfc..54317dce2 100644 --- a/swaynag/main.c +++ b/swaynag/main.c @@ -13,11 +13,6 @@ void sig_handler(int signal) { exit(EXIT_FAILURE); } -void sway_terminate(int code) { - swaynag_destroy(&swaynag); - exit(code); -} - int main(int argc, char **argv) { int status = EXIT_SUCCESS; From 0153bc92abb4974c1a3421a79e976dcf9938e50a Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 17 Apr 2025 19:11:37 +0200 Subject: [PATCH 25/51] server: move sway_terminate() definition to header --- include/sway/server.h | 2 ++ sway/commands/exit.c | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/sway/server.h b/include/sway/server.h index b1d7523ca..6152651e6 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -158,6 +158,8 @@ extern struct sway_debug debug; extern bool allow_unsupported_gpu; +void sway_terminate(int exit_code); + bool server_init(struct sway_server *server); void server_fini(struct sway_server *server); bool server_start(struct sway_server *server); diff --git a/sway/commands/exit.c b/sway/commands/exit.c index 10cde640c..0f326cea6 100644 --- a/sway/commands/exit.c +++ b/sway/commands/exit.c @@ -1,8 +1,7 @@ #include #include "sway/commands.h" #include "sway/config.h" - -void sway_terminate(int exit_code); +#include "sway/server.h" struct cmd_results *cmd_exit(int argc, char **argv) { struct cmd_results *error = NULL; From 1d4632f97fb6ee61abe350ae9f76270562396553 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 13 Jul 2024 13:13:08 +0200 Subject: [PATCH 26/51] Drop wl_drm again In [1] we re-introduced a debug flag to enable wl_drm. Time has passed and Xwayland + VA-API + amdvlk now all support linux-dmabuf-v1. [1]: https://github.com/swaywm/sway/pull/7916 --- include/sway/server.h | 1 - sway/main.c | 2 -- sway/server.c | 4 ---- 3 files changed, 7 deletions(-) diff --git a/include/sway/server.h b/include/sway/server.h index 6152651e6..66f0967ce 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -151,7 +151,6 @@ struct sway_debug { bool noatomic; // Ignore atomic layout updates bool txn_timings; // Log verbose messages about transactions bool txn_wait; // Always wait for the timeout before applying - bool legacy_wl_drm; // Enable the legacy wl_drm interface }; extern struct sway_debug debug; diff --git a/sway/main.c b/sway/main.c index 0af977d4d..bebb5e1b5 100644 --- a/sway/main.c +++ b/sway/main.c @@ -181,8 +181,6 @@ void enable_debug_flag(const char *flag) { debug.txn_timings = true; } else if (has_prefix(flag, "txn-timeout=")) { server.txn_timeout_ms = atoi(&flag[strlen("txn-timeout=")]); - } else if (strcmp(flag, "legacy-wl-drm") == 0) { - debug.legacy_wl_drm = true; } else { sway_log(SWAY_ERROR, "Unknown debug flag: %s", flag); } diff --git a/sway/server.c b/sway/server.c index 5aa5f4f99..979761486 100644 --- a/sway/server.c +++ b/sway/server.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -265,9 +264,6 @@ bool server_init(struct sway_server *server) { if (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, 4, server->renderer); - if (debug.legacy_wl_drm) { - wlr_drm_create(server->wl_display, server->renderer); - } } if (wlr_renderer_get_drm_fd(server->renderer) >= 0 && server->renderer->features.timeline && From 86ff19fadeaa45f7f6398d62be1ee6149a0889a8 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 20 Apr 2025 13:31:10 +0200 Subject: [PATCH 27/51] build: bump version to 1.11-rc1 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 9ce5723e7..2d406dec0 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'sway', 'c', - version: '1.10-dev', + version: '1.11-rc1', license: 'MIT', meson_version: '>=1.3', default_options: [ From 0e19d85d37e556721f982c3f63d4e2927f306b18 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 20 Apr 2025 21:09:57 +0200 Subject: [PATCH 28/51] Use pthread_atfork() to restore signals and NOFILE limit This ensures these functions are always called (even when a library such as wlroots or libc perform the fork) and removes the need to manually call them. --- include/sway/server.h | 3 --- meson.build | 2 +- sway/commands/exec_always.c | 2 -- sway/config/bar.c | 2 -- sway/config/output.c | 2 -- sway/main.c | 38 +++++++++++++++++++++---------------- sway/swaynag.c | 2 -- 7 files changed, 23 insertions(+), 28 deletions(-) diff --git a/include/sway/server.h b/include/sway/server.h index 66f0967ce..e7d7094f1 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -164,9 +164,6 @@ void server_fini(struct sway_server *server); bool server_start(struct sway_server *server); void server_run(struct sway_server *server); -void restore_nofile_limit(void); -void restore_signals(void); - void handle_new_output(struct wl_listener *listener, void *data); void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data); diff --git a/meson.build b/meson.build index 2d406dec0..9e8e798e1 100644 --- a/meson.build +++ b/meson.build @@ -79,7 +79,7 @@ libudev = wlroots_features['libinput_backend'] ? dependency('libudev') : null_de math = cc.find_library('m') rt = cc.find_library('rt') xcb_icccm = wlroots_features['xwayland'] ? dependency('xcb-icccm') : null_dep -threads = dependency('threads') # for pthread_setschedparam +threads = dependency('threads') # for pthread_setschedparam and pthread_atfork if get_option('sd-bus-provider') == 'auto' if not get_option('tray').disabled() diff --git a/sway/commands/exec_always.c b/sway/commands/exec_always.c index 8f02bbdc7..a966696c5 100644 --- a/sway/commands/exec_always.c +++ b/sway/commands/exec_always.c @@ -51,8 +51,6 @@ struct cmd_results *cmd_exec_process(int argc, char **argv) { // Fork process pid_t child = fork(); if (child == 0) { - restore_nofile_limit(); - restore_signals(); setsid(); if (ctx) { diff --git a/sway/config/bar.c b/sway/config/bar.c index 6cace0da2..f4efb276c 100644 --- a/sway/config/bar.c +++ b/sway/config/bar.c @@ -213,8 +213,6 @@ static void invoke_swaybar(struct bar_config *bar) { sway_log(SWAY_ERROR, "Failed to create fork for swaybar"); return; } else if (pid == 0) { - restore_nofile_limit(); - restore_signals(); if (!sway_set_cloexec(sockets[1], false)) { _exit(EXIT_FAILURE); } diff --git a/sway/config/output.c b/sway/config/output.c index b8a613cc5..5ed518bf4 100644 --- a/sway/config/output.c +++ b/sway/config/output.c @@ -1060,8 +1060,6 @@ static bool _spawn_swaybg(char **command) { sway_log_errno(SWAY_ERROR, "fork failed"); return false; } else if (pid == 0) { - restore_nofile_limit(); - restore_signals(); if (!sway_set_cloexec(sockets[1], false)) { _exit(EXIT_FAILURE); } diff --git a/sway/main.c b/sway/main.c index bebb5e1b5..56f09b7e7 100644 --- a/sway/main.c +++ b/sway/main.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -121,6 +122,16 @@ static bool detect_suid(void) { return true; } +static void restore_nofile_limit(void) { + if (original_nofile_rlimit.rlim_cur == 0) { + return; + } + if (setrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) { + sway_log_errno(SWAY_ERROR, "Failed to restore max open files limit: " + "setrlimit(NOFILE) failed"); + } +} + static void increase_nofile_limit(void) { if (getrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) { sway_log_errno(SWAY_ERROR, "Failed to bump max open files limit: " @@ -135,17 +146,10 @@ static void increase_nofile_limit(void) { "setrlimit(NOFILE) failed"); sway_log(SWAY_INFO, "Running with %d max open files", (int)original_nofile_rlimit.rlim_cur); - } -} - -void restore_nofile_limit(void) { - if (original_nofile_rlimit.rlim_cur == 0) { return; } - if (setrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) { - sway_log_errno(SWAY_ERROR, "Failed to restore max open files limit: " - "setrlimit(NOFILE) failed"); - } + + pthread_atfork(NULL, NULL, restore_nofile_limit); } static int term_signal(int signal, void *data) { @@ -153,6 +157,14 @@ static int term_signal(int signal, void *data) { return 0; } +static void restore_signals(void) { + sigset_t set; + sigemptyset(&set); + sigprocmask(SIG_SETMASK, &set, NULL); + signal(SIGCHLD, SIG_DFL); + signal(SIGPIPE, SIG_DFL); +} + static void init_signals(void) { wl_event_loop_add_signal(server.wl_event_loop, SIGTERM, term_signal, NULL); wl_event_loop_add_signal(server.wl_event_loop, SIGINT, term_signal, NULL); @@ -162,14 +174,8 @@ static void init_signals(void) { // prevent ipc write errors from crashing sway signal(SIGPIPE, SIG_IGN); -} -void restore_signals(void) { - sigset_t set; - sigemptyset(&set); - sigprocmask(SIG_SETMASK, &set, NULL); - signal(SIGCHLD, SIG_DFL); - signal(SIGPIPE, SIG_DFL); + pthread_atfork(NULL, NULL, restore_signals); } void enable_debug_flag(const char *flag) { diff --git a/sway/swaynag.c b/sway/swaynag.c index f0a31218a..204a5791a 100644 --- a/sway/swaynag.c +++ b/sway/swaynag.c @@ -63,7 +63,6 @@ bool swaynag_spawn(const char *swaynag_command, sway_log(SWAY_ERROR, "Failed to create fork for swaynag"); goto failed; } else if (pid == 0) { - restore_nofile_limit(); if (!sway_set_cloexec(sockets[1], false)) { _exit(EXIT_FAILURE); } @@ -148,4 +147,3 @@ void swaynag_show(struct swaynag_instance *swaynag) { close(swaynag->fd[1]); } } - From 38a42f97d46931e97693610c999ea51834b71352 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 27 Apr 2025 18:36:18 +0200 Subject: [PATCH 29/51] Replace signal() with sigaction() The man page for signal(3) reads: > new applications should use sigaction() rather than signal() --- sway/main.c | 12 +++++++----- swaybar/main.c | 5 +++-- swaynag/main.c | 3 ++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/sway/main.c b/sway/main.c index 56f09b7e7..69efd6cb4 100644 --- a/sway/main.c +++ b/sway/main.c @@ -161,19 +161,21 @@ static void restore_signals(void) { sigset_t set; sigemptyset(&set); sigprocmask(SIG_SETMASK, &set, NULL); - signal(SIGCHLD, SIG_DFL); - signal(SIGPIPE, SIG_DFL); + + struct sigaction sa_dfl = { .sa_handler = SIG_DFL }; + sigaction(SIGCHLD, &sa_dfl, NULL); + sigaction(SIGPIPE, &sa_dfl, NULL); } static void init_signals(void) { wl_event_loop_add_signal(server.wl_event_loop, SIGTERM, term_signal, NULL); wl_event_loop_add_signal(server.wl_event_loop, SIGINT, term_signal, NULL); + struct sigaction sa_ign = { .sa_handler = SIG_IGN }; // avoid need to reap children - signal(SIGCHLD, SIG_IGN); - + sigaction(SIGCHLD, &sa_ign, NULL); // prevent ipc write errors from crashing sway - signal(SIGPIPE, SIG_IGN); + sigaction(SIGPIPE, &sa_ign, NULL); pthread_atfork(NULL, NULL, restore_signals); } diff --git a/swaybar/main.c b/swaybar/main.c index 3dc672334..e1b0cecac 100644 --- a/swaybar/main.c +++ b/swaybar/main.c @@ -93,8 +93,9 @@ int main(int argc, char **argv) { free(socket_path); - signal(SIGINT, sig_handler); - signal(SIGTERM, sig_handler); + struct sigaction sa = { .sa_handler = sig_handler }; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); swaybar.running = true; bar_run(&swaybar); diff --git a/swaynag/main.c b/swaynag/main.c index 54317dce2..b68157ff2 100644 --- a/swaynag/main.c +++ b/swaynag/main.c @@ -102,7 +102,8 @@ int main(int argc, char **argv) { sway_log(SWAY_DEBUG, "\t[%s] `%s`", button->text, button->action); } - signal(SIGTERM, sig_handler); + struct sigaction sa = { .sa_handler = sig_handler }; + sigaction(SIGTERM, &sa, NULL); swaynag_setup(&swaynag); swaynag_run(&swaynag); From 5b8874e3f428b54aaa2676346954dc712320d219 Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Thu, 24 Apr 2025 15:41:20 -0400 Subject: [PATCH 30/51] sway/commands: Handle incorrect resize unit problem: an invalid usage of the command resize set will cause sway to crash because it doesn't check for an invalid height. solution: validate height along with width. --- sway/commands/resize.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sway/commands/resize.c b/sway/commands/resize.c index 32b746eaf..49731a641 100644 --- a/sway/commands/resize.c +++ b/sway/commands/resize.c @@ -457,7 +457,7 @@ static struct cmd_results *cmd_resize_set(int argc, char **argv) { if (argc > num_consumed_args) { return cmd_results_new(CMD_INVALID, "%s", usage); } - if (width.unit == MOVEMENT_UNIT_INVALID) { + if (height.unit == MOVEMENT_UNIT_INVALID) { return cmd_results_new(CMD_INVALID, "%s", usage); } } From 6894b498a889d809093c920d397a42db7f5e4970 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 27 Apr 2025 22:52:48 +0200 Subject: [PATCH 31/51] build: bump version to 1.11-rc2 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 9e8e798e1..646f694c2 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'sway', 'c', - version: '1.11-rc1', + version: '1.11-rc2', license: 'MIT', meson_version: '>=1.3', default_options: [ From 6cac61b6b96c4a48a69f8ec3c06c2df560b01827 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Fri, 3 May 2024 09:09:14 +0200 Subject: [PATCH 32/51] Fix includes with relative paths The function `load_include_configs` already changes the directory to the one containing the parent config. Therefore, `load_include_config` trying to assemble the "full" path leads to repetition of path segments, making the `realpath` call fail with ENOENT. Just calling `realpath` on the path itself from the directory with the parent configuration is sufficient, so there is no point in passing `parent_dir` to `load_include_config`. --- sway/config.c | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/sway/config.c b/sway/config.c index ec7059687..d579022d3 100644 --- a/sway/config.c +++ b/sway/config.c @@ -552,28 +552,12 @@ bool load_main_config(const char *file, bool is_active, bool validating) { return success; } -static bool load_include_config(const char *path, const char *parent_dir, - struct sway_config *config, struct swaynag_instance *swaynag) { +static bool load_include_config(const char *path, struct sway_config *config, + struct swaynag_instance *swaynag) { // save parent config const char *parent_config = config->current_config_path; - char *full_path; - int len = strlen(path); - if (len >= 1 && path[0] != '/') { - len = len + strlen(parent_dir) + 2; - full_path = malloc(len * sizeof(char)); - if (!full_path) { - sway_log(SWAY_ERROR, - "Unable to allocate full path to included config"); - return false; - } - snprintf(full_path, len, "%s/%s", parent_dir, path); - } else { - full_path = strdup(path); - } - - char *real_path = realpath(full_path, NULL); - free(full_path); + char *real_path = realpath(path, NULL); if (real_path == NULL) { sway_log(SWAY_DEBUG, "%s not found.", path); @@ -625,7 +609,7 @@ void load_include_configs(const char *path, struct sway_config *config, char **w = p.we_wordv; size_t i; for (i = 0; i < p.we_wordc; ++i) { - load_include_config(w[i], parent_dir, config, swaynag); + load_include_config(w[i], config, swaynag); } wordfree(&p); } From 8ac1f72c9ef88b2919457e3598059f963df69305 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Wed, 30 Apr 2025 13:34:31 +0200 Subject: [PATCH 33/51] config/output: Use INT_MAX as x/y unset value We oftne use -1 to indicate unset values. In case of output (x, y), we would consider the fields set if they are not both -1. This means that (0, -1) and (-1, 0) are valid coordinates, but (-1, -1) is not. We support negative output positioning, so we cannot use -1 to mean unset. Zero is also not an option as that would disallow reverting a set position back to (0, 0). INT_MAX is an unreasonable output position, so use it to indicate unset values, and only use the value when both are set. --- sway/config/output.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sway/config/output.c b/sway/config/output.c index 5ed518bf4..e061e25bd 100644 --- a/sway/config/output.c +++ b/sway/config/output.c @@ -67,7 +67,7 @@ struct output_config *new_output_config(const char *name) { oc->refresh_rate = -1; oc->custom_mode = -1; oc->drm_mode.type = -1; - oc->x = oc->y = -1; + oc->x = oc->y = INT_MAX; oc->scale = -1; oc->scale_filter = SCALE_FILTER_DEFAULT; oc->transform = -1; @@ -93,11 +93,11 @@ static void supersede_output_config(struct output_config *dst, struct output_con if (src->height != -1) { dst->height = -1; } - if (src->x != -1) { - dst->x = -1; + if (src->x != INT_MAX) { + dst->x = INT_MAX; } - if (src->y != -1) { - dst->y = -1; + if (src->y != INT_MAX) { + dst->y = INT_MAX; } if (src->scale != -1) { dst->scale = -1; @@ -157,10 +157,10 @@ static void merge_output_config(struct output_config *dst, struct output_config if (src->height != -1) { dst->height = src->height; } - if (src->x != -1) { + if (src->x != INT_MAX) { dst->x = src->x; } - if (src->y != -1) { + if (src->y != INT_MAX) { dst->y = src->y; } if (src->scale != -1) { @@ -527,7 +527,7 @@ static bool finalize_output_config(struct output_config *oc, struct sway_output } // Find position for it - if (oc && (oc->x != -1 || oc->y != -1)) { + if (oc && oc->x != INT_MAX && oc->y != INT_MAX) { sway_log(SWAY_DEBUG, "Set %s position to %d, %d", oc->name, oc->x, oc->y); wlr_output_layout_add(root->output_layout, wlr_output, oc->x, oc->y); } else { From f9945d81fb52c81ab60034dcfc41a2f36f0ed226 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 29 Apr 2025 13:39:00 +0200 Subject: [PATCH 34/51] config/output: Fix missing output config supersedes color_transform and allow_tearing was not handled by supersede_output_config which could lead to configuration being incorrectly applied. --- sway/config/output.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sway/config/output.c b/sway/config/output.c index e061e25bd..df80cab64 100644 --- a/sway/config/output.c +++ b/sway/config/output.c @@ -129,6 +129,13 @@ static void supersede_output_config(struct output_config *dst, struct output_con if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { dst->render_bit_depth = RENDER_BIT_DEPTH_DEFAULT; } + if (src->set_color_transform) { + if (dst->color_transform) { + wlr_color_transform_unref(dst->color_transform); + dst->color_transform = NULL; + } + dst->set_color_transform = false; + } if (src->background) { free(dst->background); dst->background = NULL; @@ -144,6 +151,9 @@ static void supersede_output_config(struct output_config *dst, struct output_con if (src->power != -1) { dst->power = -1; } + if (src->allow_tearing != -1) { + dst->allow_tearing = -1; + } } // merge_output_config sets all fields in dst that were set in src From 4ab411cab0f441994fd41ef8ac80ef849bfab271 Mon Sep 17 00:00:00 2001 From: alex-huff Date: Sun, 4 May 2025 17:41:44 -0500 Subject: [PATCH 35/51] transaction: Ensure all tabs are visible in tabbed mode Before this commit when a child of a tabbed or stacking container was taken out of fullscreen and a different sibling was focused in the same transaction, the titlebar of the previously fullscreen container would remain hidden. This commit makes sure that scene tree for decorations is enabled for all containers within a tabbed or stacking container when it is arranged. --- sway/desktop/transaction.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c index 0b3cbfb4d..781e0008c 100644 --- a/sway/desktop/transaction.c +++ b/sway/desktop/transaction.c @@ -309,6 +309,7 @@ static void arrange_children(enum sway_container_layout layout, list_t *children arrange_title_bar(child, title_offset, -title_bar_height, next_title_offset - title_offset, title_bar_height); wlr_scene_node_set_enabled(&child->border.tree->node, activated); + wlr_scene_node_set_enabled(&child->scene_tree->node, true); wlr_scene_node_set_position(&child->scene_tree->node, 0, title_bar_height); wlr_scene_node_reparent(&child->scene_tree->node, content); @@ -338,6 +339,7 @@ static void arrange_children(enum sway_container_layout layout, list_t *children arrange_title_bar(child, 0, y - title_height, width, title_bar_height); wlr_scene_node_set_enabled(&child->border.tree->node, activated); + wlr_scene_node_set_enabled(&child->scene_tree->node, true); wlr_scene_node_set_position(&child->scene_tree->node, 0, title_height); wlr_scene_node_reparent(&child->scene_tree->node, content); From 6021f4d83fc1d7e179c334a010af9b37e44a1bb2 Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Fri, 25 Apr 2025 11:50:34 -0400 Subject: [PATCH 36/51] input/seatop_down: Update decorations for touchscreen inputs fixes #8675 --- sway/input/seatop_down.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sway/input/seatop_down.c b/sway/input/seatop_down.c index 340e334bf..f64e5a4f2 100644 --- a/sway/input/seatop_down.c +++ b/sway/input/seatop_down.c @@ -100,6 +100,7 @@ static void handle_touch_down(struct sway_seat *seat, if (focused_node) { seat_set_focus(seat, focused_node); + transaction_commit_dirty(); } } From 8d3a52aa3076a34b008aed8ba142c955329a8874 Mon Sep 17 00:00:00 2001 From: alex-huff Date: Wed, 7 May 2025 21:00:03 -0500 Subject: [PATCH 37/51] move: fix broken titlebar when moving child to new workspace Before this commit, when moving a non-leaf child of a tabbed or stacking container to a new workspace, the child would be detached from the parent container and the grandchildren would be sent to the new workspace but the child itself wouldn't be destroyed causing the titlebar to still be rendered as part of the parent container. Fixes #8648. --- sway/commands/move.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sway/commands/move.c b/sway/commands/move.c index 8891514ce..90e8585b4 100644 --- a/sway/commands/move.c +++ b/sway/commands/move.c @@ -222,6 +222,7 @@ static void container_move_to_workspace(struct sway_container *container, container_detach(container); if (workspace_is_empty(workspace) && container->pending.children) { workspace_unwrap_children(workspace, container); + container_reap_empty(container); } else { container->pending.width = container->pending.height = 0; container->width_fraction = container->height_fraction = 0; From 3fbff5b4bb600efa10630013546115a4f6e0d74a Mon Sep 17 00:00:00 2001 From: GreyXor <79602273+GreyXor@users.noreply.github.com> Date: Thu, 15 May 2025 11:50:05 +0200 Subject: [PATCH 38/51] build: bump wlroots version --- meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 646f694c2..07a6bccff 100644 --- a/meson.build +++ b/meson.build @@ -38,14 +38,14 @@ if is_freebsd endif # Execute the wlroots subproject, if any -wlroots_version = ['>=0.19.0', '<0.20.0'] +wlroots_version = ['>=0.20.0', '<0.21.0'] subproject( 'wlroots', default_options: ['examples=false'], required: false, version: wlroots_version, ) -wlroots = dependency('wlroots-0.19', version: wlroots_version, fallback: 'wlroots') +wlroots = dependency('wlroots-0.20', version: wlroots_version, fallback: 'wlroots') wlroots_features = { 'xwayland': false, 'libinput_backend': false, From 652019d6da630e58ed212632f0c3a4231eaab4e4 Mon Sep 17 00:00:00 2001 From: Bill Li Date: Thu, 15 May 2025 22:23:10 +0800 Subject: [PATCH 39/51] input/text_input: chase wlroots update References: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5032 Fix #8718 --- sway/input/text_input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sway/input/text_input.c b/sway/input/text_input.c index c84fac8f6..0eb76eb16 100644 --- a/sway/input/text_input.c +++ b/sway/input/text_input.c @@ -632,7 +632,7 @@ void sway_input_method_relay_init(struct sway_seat *seat, wl_list_init(&relay->input_popups); relay->text_input_new.notify = relay_handle_text_input; - wl_signal_add(&server.text_input->events.text_input, + wl_signal_add(&server.text_input->events.new_text_input, &relay->text_input_new); relay->text_input_manager_destroy.notify = relay_handle_text_input_manager_destroy; wl_signal_add(&server.text_input->events.destroy, From a4072486ded62fcde586c10230cc79af833c55b6 Mon Sep 17 00:00:00 2001 From: alex-huff Date: Thu, 15 May 2025 17:51:39 -0500 Subject: [PATCH 40/51] transaction: ensure border scene is enabled for floating containers When a container that was previously an inactive child of a tabbed or stacking layout becomes floating it's border scene-tree remains disabled. This results in only the titlebar being rendered for the container. This commit ensures the border scene-tree is enabled when arranging floating containers. Fixes #8721 --- sway/desktop/transaction.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c index 781e0008c..74929f8c9 100644 --- a/sway/desktop/transaction.c +++ b/sway/desktop/transaction.c @@ -534,6 +534,7 @@ static void arrange_workspace_floating(struct sway_workspace *ws) { wlr_scene_node_set_position(&floater->scene_tree->node, floater->current.x, floater->current.y); wlr_scene_node_set_enabled(&floater->scene_tree->node, true); + wlr_scene_node_set_enabled(&floater->border.tree->node, true); arrange_container(floater, floater->current.width, floater->current.height, true, ws->gaps_inner); From 810142dcc4228941b644ad68334d0b6f539b6fab Mon Sep 17 00:00:00 2001 From: odyxz Date: Mon, 6 Jan 2025 13:21:08 +0200 Subject: [PATCH 41/51] raise scratchpad container --- sway/tree/root.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sway/tree/root.c b/sway/tree/root.c index 19d072b5c..33c29d381 100644 --- a/sway/tree/root.c +++ b/sway/tree/root.c @@ -199,6 +199,8 @@ void root_scratchpad_show(struct sway_container *con) { if (old_ws) { workspace_consider_destroy(old_ws); } + + container_raise_floating(con); } static void disable_fullscreen(struct sway_container *con, void *data) { From fb6d61b58f4deeea4a5738473424fc80c00f9415 Mon Sep 17 00:00:00 2001 From: alex-huff Date: Tue, 6 May 2025 17:36:16 -0500 Subject: [PATCH 42/51] transaction: fix size of child container decorations in stacking layouts Before this commit stacking containers with more than one child sized the active container's decorations as if there was only one titlebar. Commit a25645a introduced the local variable 'net_height' but incorrectly calculated it for stacking containers. Fixes #8686. --- 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 74929f8c9..f8bc64b57 100644 --- a/sway/desktop/transaction.c +++ b/sway/desktop/transaction.c @@ -343,7 +343,7 @@ static void arrange_children(enum sway_container_layout layout, list_t *children wlr_scene_node_set_position(&child->scene_tree->node, 0, title_height); wlr_scene_node_reparent(&child->scene_tree->node, content); - int net_height = height - title_bar_height; + int net_height = height - title_height; if (activated && width > 0 && net_height > 0) { arrange_container(child, width, net_height, title_bar_height == 0, 0); } else { From 8fecf3aa8c0cdff23a75a5f20b4a3423a43e9bc5 Mon Sep 17 00:00:00 2001 From: alex-huff Date: Fri, 9 May 2025 14:35:30 -0500 Subject: [PATCH 43/51] transaction: reparent scenes of containers behind fullscreen containers Currently we do a good job of reparenting the scenes of a container when it moves into a disabled workspace. We need to do this since normally the scenes are reparented in the 'arrange_{children,container}' functions but these don't get called for disabled workspaces. However, the 'arrange_{children,container}' functions also don't get called when there is a fullscreen container hiding them. This commit makes sure to call 'disable_workspace' on workspaces with a fullscreen container so that when a container is moved into the workspace its scenes will be properly reparented. Also, when there is a fullscreen global container 'disable_workspace' is called for all workspaces since the scenes of a previously fullscreen global container may still be parented in the 'fullscreen_global' layer. Fixes #8705 #8659 #8432 --- sway/desktop/transaction.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c index f8bc64b57..7ec9b68d2 100644 --- a/sway/desktop/transaction.c +++ b/sway/desktop/transaction.c @@ -587,13 +587,13 @@ static void arrange_output(struct sway_output *output, int width, int height) { wlr_scene_node_set_enabled(&child->layers.tiling->node, !fs); wlr_scene_node_set_enabled(&child->layers.fullscreen->node, fs); - arrange_workspace_floating(child); - wlr_scene_node_set_enabled(&output->layers.shell_background->node, !fs); wlr_scene_node_set_enabled(&output->layers.shell_bottom->node, !fs); wlr_scene_node_set_enabled(&output->layers.fullscreen->node, fs); if (fs) { + disable_workspace(child); + wlr_scene_rect_set_size(output->fullscreen_background, width, height); arrange_fullscreen(child->layers.fullscreen, fs, child, @@ -609,6 +609,8 @@ static void arrange_output(struct sway_output *output, int width, int height) { area->width - gaps->left - gaps->right, area->height - gaps->top - gaps->bottom); } + + arrange_workspace_floating(child); } else { wlr_scene_node_set_enabled(&child->layers.tiling->node, false); wlr_scene_node_set_enabled(&child->layers.fullscreen->node, false); @@ -665,6 +667,13 @@ static void arrange_root(struct sway_root *root) { wlr_scene_output_set_position(output->scene_output, output->lx, output->ly); + // disable all workspaces to get to a known state + for (int j = 0; j < output->current.workspaces->length; j++) { + struct sway_workspace *workspace = output->current.workspaces->items[j]; + disable_workspace(workspace); + } + + // arrange the active workspace if (ws) { arrange_workspace_floating(ws); } From 5cfcd1c7c2ee1e0a199fd5d62b1da962f2102a85 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 19 May 2025 18:25:16 +0200 Subject: [PATCH 44/51] input: fix udev_device leak libinput_device_get_udev_device() returns a ref'ed handle: https://wayland.freedesktop.org/libinput/doc/latest/api/group__device.html#gac13c64ba19fc19094cff0e5354a2a7ce Similar to this wlroots MR: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5074 --- sway/input/libinput.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/sway/input/libinput.c b/sway/input/libinput.c index f76c6505c..b98c4cc86 100644 --- a/sway/input/libinput.c +++ b/sway/input/libinput.c @@ -399,6 +399,19 @@ void sway_input_reset_libinput_device(struct sway_input_device *input_device) { } } +static bool sway_udev_device_is_builtin(struct udev_device *udev_device) { + const char *id_path = udev_device_get_property_value(udev_device, "ID_PATH"); + if (!id_path) { + return false; + } + + if (has_prefix(id_path, "platform-")) { + return true; + } + + return has_prefix(id_path, "pci-") && strstr(id_path, "-platform-"); +} + bool sway_libinput_device_is_builtin(struct sway_input_device *sway_device) { if (!wlr_input_device_is_libinput(sway_device->wlr_device)) { return false; @@ -412,14 +425,7 @@ bool sway_libinput_device_is_builtin(struct sway_input_device *sway_device) { return false; } - const char *id_path = udev_device_get_property_value(udev_device, "ID_PATH"); - if (!id_path) { - return false; - } - - if (has_prefix(id_path, "platform-")) { - return true; - } - - return has_prefix(id_path, "pci-") && strstr(id_path, "-platform-"); + bool is_builtin = sway_udev_device_is_builtin(udev_device); + udev_device_unref(udev_device); + return is_builtin; } From 1b47277962fa04624a0b6835e81089110d258e7c Mon Sep 17 00:00:00 2001 From: alex-huff Date: Wed, 21 May 2025 18:47:10 -0500 Subject: [PATCH 45/51] layer-shell: reclaim space from unmapped layer surfaces wlroots resets 'initialized' when a layer surface is unmapped and sway doesn't rearrange the layer surfaces in response to a commit of a surface where 'initialized' is false. This results in space not getting reclaimed from a recently unmapped layer surface until some other action causes 'arrange_layers' to get called. This commit makes sure all layer surfaces get rearranged when a layer surface is unmapped. --- sway/desktop/layer_shell.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sway/desktop/layer_shell.c b/sway/desktop/layer_shell.c index b14a9a4b1..08b5b1de2 100644 --- a/sway/desktop/layer_shell.c +++ b/sway/desktop/layer_shell.c @@ -269,12 +269,8 @@ static void handle_surface_commit(struct wl_listener *listener, void *data) { wl_container_of(listener, surface, surface_commit); struct wlr_layer_surface_v1 *layer_surface = surface->layer_surface; - if (!layer_surface->initialized) { - return; - } - uint32_t committed = layer_surface->current.committed; - if (committed & WLR_LAYER_SURFACE_V1_STATE_LAYER) { + if (layer_surface->initialized && committed & WLR_LAYER_SURFACE_V1_STATE_LAYER) { enum zwlr_layer_shell_v1_layer layer_type = layer_surface->current.layer; struct wlr_scene_tree *output_layer = sway_layer_get_scene( surface->output, layer_type); From 88c7b4a7ebe281ee771e3a1bfd8968046918a01a Mon Sep 17 00:00:00 2001 From: alex-huff Date: Wed, 21 May 2025 20:31:46 -0500 Subject: [PATCH 46/51] transaction: fix floating fullscreen containers 8fecf3a introduced a regression where fullscreening a child of a floating container would result in a black screen. This is because the order of 'arrange_fullscreen' and 'arrange_worksplace_floating' was swapped causing the fullscreen container's scene to get reparented after it was parented in the fullscreen layer. Fixes #8729 --- sway/desktop/transaction.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c index 7ec9b68d2..325a30226 100644 --- a/sway/desktop/transaction.c +++ b/sway/desktop/transaction.c @@ -596,6 +596,7 @@ static void arrange_output(struct sway_output *output, int width, int height) { wlr_scene_rect_set_size(output->fullscreen_background, width, height); + arrange_workspace_floating(child); arrange_fullscreen(child->layers.fullscreen, fs, child, width, height); } else { @@ -608,9 +609,8 @@ static void arrange_output(struct sway_output *output, int width, int height) { arrange_workspace_tiling(child, area->width - gaps->left - gaps->right, area->height - gaps->top - gaps->bottom); + arrange_workspace_floating(child); } - - arrange_workspace_floating(child); } else { wlr_scene_node_set_enabled(&child->layers.tiling->node, false); wlr_scene_node_set_enabled(&child->layers.fullscreen->node, false); From 005924f2609029612d4468c3436a816a48991246 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 6 May 2025 01:35:05 +0200 Subject: [PATCH 47/51] output: Minimize interaction with output after destroy When an output is destroyed, we go through the process of disabling it. This includes evacuating all content away from the output, which can lead to various modifications to the scene. With the scene_output still present, this can lead to things like output_enter events being emitted for the output currently being destroyed. Ensure that the scene output is destroyed first and that the output is immediately considered disabled. References: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3974 --- sway/desktop/output.c | 16 +++++++++------- sway/tree/output.c | 13 ++++++------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/sway/desktop/output.c b/sway/desktop/output.c index aec1d0a6b..e6fe2ee4d 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -422,13 +422,6 @@ void force_modeset(void) { } static void begin_destroy(struct sway_output *output) { - if (output->enabled) { - output_disable(output); - } - - output_begin_destroy(output); - - wl_list_remove(&output->link); wl_list_remove(&output->layout_destroy.link); wl_list_remove(&output->destroy.link); @@ -436,8 +429,17 @@ static void begin_destroy(struct sway_output *output) { wl_list_remove(&output->frame.link); wl_list_remove(&output->request_state.link); + // Remove the scene_output first to ensure that the scene does not emit + // events for this output. wlr_scene_output_destroy(output->scene_output); output->scene_output = NULL; + + if (output->enabled) { + output_disable(output); + } + output_begin_destroy(output); + wl_list_remove(&output->link); + output->wlr_output->data = NULL; output->wlr_output = NULL; diff --git a/sway/tree/output.c b/sway/tree/output.c index 65d9e3e71..bc3806298 100644 --- a/sway/tree/output.c +++ b/sway/tree/output.c @@ -205,11 +205,8 @@ static void output_evacuate(struct sway_output *output) { return; } struct sway_output *fallback_output = NULL; - if (root->outputs->length > 1) { + if (root->outputs->length > 0) { fallback_output = root->outputs->items[0]; - if (fallback_output == output) { - fallback_output = root->outputs->items[1]; - } } while (output->workspaces->length) { @@ -289,11 +286,13 @@ void output_disable(struct sway_output *output) { sway_log(SWAY_DEBUG, "Disabling output '%s'", output->wlr_output->name); wl_signal_emit_mutable(&output->events.disable, output); - output_evacuate(output); - + // Remove the output now to avoid interacting with it during e.g., + // transactions, as the output might be physically removed with the scene + // output destroyed. list_del(root->outputs, index); - output->enabled = false; + + output_evacuate(output); } void output_begin_destroy(struct sway_output *output) { From 534491d3aa0c60d5c941f2f46b5d7f8978901f84 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 6 May 2025 11:46:35 +0200 Subject: [PATCH 48/51] tree/workspace: Remove exclude arg from get_highest_available workspace_output_get_highest_available took an output to exclude as argument, meant to avoid accidentally reselecting an output we are evacuating workspaces from. Outputs are now removed from the list before we evacuate, making exclusion unnecessary. Remove the argument. --- include/sway/tree/workspace.h | 2 +- sway/tree/output.c | 4 ++-- sway/tree/workspace.c | 6 +----- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h index 58bde20cb..27ed649fd 100644 --- a/include/sway/tree/workspace.h +++ b/include/sway/tree/workspace.h @@ -96,7 +96,7 @@ void workspace_output_add_priority(struct sway_workspace *workspace, struct sway_output *output); struct sway_output *workspace_output_get_highest_available( - struct sway_workspace *ws, struct sway_output *exclude); + struct sway_workspace *ws); void workspace_detect_urgent(struct sway_workspace *workspace); diff --git a/sway/tree/output.c b/sway/tree/output.c index bc3806298..b02c1a2ce 100644 --- a/sway/tree/output.c +++ b/sway/tree/output.c @@ -37,7 +37,7 @@ static void restore_workspaces(struct sway_output *output) { for (int j = 0; j < other->workspaces->length; j++) { struct sway_workspace *ws = other->workspaces->items[j]; struct sway_output *highest = - workspace_output_get_highest_available(ws, NULL); + workspace_output_get_highest_available(ws); if (highest == output) { workspace_detach(ws); output_add_workspace(output, ws); @@ -215,7 +215,7 @@ static void output_evacuate(struct sway_output *output) { workspace_detach(workspace); struct sway_output *new_output = - workspace_output_get_highest_available(workspace, output); + workspace_output_get_highest_available(workspace); if (!new_output) { new_output = fallback_output; } diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c index a09dc3a45..f2be4cd13 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c @@ -659,13 +659,9 @@ void workspace_output_add_priority(struct sway_workspace *workspace, } struct sway_output *workspace_output_get_highest_available( - struct sway_workspace *ws, struct sway_output *exclude) { + struct sway_workspace *ws) { for (int i = 0; i < ws->output_priority->length; i++) { const char *name = ws->output_priority->items[i]; - if (exclude && output_match_name_or_id(exclude, name)) { - continue; - } - struct sway_output *output = output_by_name_or_id(name); if (output) { return output; From 45267bb576559ac583daa4c73c64baef1d4f5ef3 Mon Sep 17 00:00:00 2001 From: Bonsaiiv Date: Sat, 24 May 2025 16:03:45 +0200 Subject: [PATCH 49/51] Improve example of input section in default config Previous example included a specific device name. This can be confusing for beginners, as the default did not work on most devices. --- config.in | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/config.in b/config.in index a2a01dda9..872ef0574 100644 --- a/config.in +++ b/config.in @@ -46,14 +46,18 @@ output * bg @datadir@/backgrounds/sway/Sway_Wallpaper_Blue_1920x1080.png fill # # Example configuration: # -# input "2:14:SynPS/2_Synaptics_TouchPad" { +# input type:touchpad { # dwt enabled # tap enabled # natural_scroll enabled # middle_emulation enabled # } # -# You can get the names of your inputs by running: swaymsg -t get_inputs +# input type:keyboard { +# xkb_layout "eu" +# } +# +# You can also configure each device individually. # Read `man 5 sway-input` for more information about this section. ### Key bindings From 63689bfb830b68eba8062aedef9928c55713c9bc Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 27 Apr 2025 22:11:41 +0200 Subject: [PATCH 50/51] Log message on for_window command error --- sway/tree/view.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sway/tree/view.c b/sway/tree/view.c index 16080a2f9..9a5cf06e0 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -517,10 +517,12 @@ void view_execute_criteria(struct sway_view *view) { sway_log(SWAY_DEBUG, "for_window '%s' matches view %p, cmd: '%s'", criteria->raw, view, criteria->cmdlist); list_add(view->executed_criteria, criteria); - list_t *res_list = execute_command( - criteria->cmdlist, NULL, view->container); + list_t *res_list = execute_command(criteria->cmdlist, NULL, view->container); while (res_list->length) { struct cmd_results *res = res_list->items[0]; + if (res->status != CMD_SUCCESS) { + sway_log(SWAY_ERROR, "for_window '%s' failed: %s", criteria->raw, res->error); + } free_cmd_results(res); list_del(res_list, 0); } From 7e7994dbb2a2c04f55b3c74eb61577c51e9a43ae Mon Sep 17 00:00:00 2001 From: Konstantin Pospelov Date: Thu, 27 Mar 2025 20:11:59 +0100 Subject: [PATCH 51/51] swaybar: deduplicate mode and workspace rendering code The render_workspace_button and render_binding_mode_indicator functions are almost the same. This commit extracts the common rendering code into a new render_box function. --- include/swaybar/config.h | 5 ++ swaybar/render.c | 169 ++++++++++++++++----------------------- 2 files changed, 73 insertions(+), 101 deletions(-) diff --git a/include/swaybar/config.h b/include/swaybar/config.h index 361acd991..ad58b3c3c 100644 --- a/include/swaybar/config.h +++ b/include/swaybar/config.h @@ -14,6 +14,11 @@ struct box_colors { uint32_t text; }; +struct box_size { + uint32_t width; + uint32_t height; +}; + struct config_output { struct wl_list link; // swaybar_config::outputs char *name; diff --git a/swaybar/render.c b/swaybar/render.c index 45faefa97..c51321f18 100644 --- a/swaybar/render.c +++ b/swaybar/render.c @@ -13,7 +13,6 @@ #include "swaybar/ipc.h" #include "swaybar/render.h" #include "swaybar/status_line.h" -#include "log.h" #if HAVE_TRAY #include "swaybar/tray/tray.h" #endif @@ -21,7 +20,7 @@ static const int WS_HORIZONTAL_PADDING = 5; static const double WS_VERTICAL_PADDING = 1.5; -static const double BORDER_WIDTH = 1; +static const int BORDER_WIDTH = 1; struct render_context { cairo_t *cairo; @@ -541,6 +540,63 @@ static uint32_t render_status_line(struct render_context *ctx, double *x) { return 0; } +static struct box_size render_box(struct render_context *ctx, double x, + struct box_colors colors, const char *label, bool pango_markup) { + struct swaybar_output *output = ctx->output; + struct swaybar_config *config = output->bar->config; + cairo_t *cairo = ctx->cairo; + + int text_width, text_height; + get_text_size(cairo, config->font_description, &text_width, &text_height, NULL, + 1, pango_markup, "%s", label); + + uint32_t width = text_width + WS_HORIZONTAL_PADDING * 2 + BORDER_WIDTH * 2; + if (width < config->workspace_min_width) { + width = config->workspace_min_width; + } + + uint32_t ideal_height = text_height + WS_VERTICAL_PADDING * 2 + + BORDER_WIDTH * 2; + uint32_t ideal_surface_height = ideal_height; + if (!output->bar->config->height && + output->height < ideal_surface_height) { + return (struct box_size) { + .width = width, + .height = ideal_surface_height, + }; + } + + uint32_t height = output->height; + cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); + cairo_set_source_u32(cairo, colors.background); + ctx->background_color = colors.background; + ctx->has_transparency |= (colors.background & 0xFF) != 0xFF; + cairo_rectangle(cairo, x, 0, width, height); + cairo_fill(cairo); + + cairo_set_source_u32(cairo, colors.border); + cairo_rectangle(cairo, x, 0, width, BORDER_WIDTH); + cairo_fill(cairo); + cairo_rectangle(cairo, x, 0, BORDER_WIDTH, height); + cairo_fill(cairo); + cairo_rectangle(cairo, x + width - BORDER_WIDTH, 0, BORDER_WIDTH, height); + cairo_fill(cairo); + cairo_rectangle(cairo, x, height - BORDER_WIDTH, width, BORDER_WIDTH); + cairo_fill(cairo); + + double text_y = height / 2.0 - text_height / 2.0; + cairo_set_source_u32(cairo, colors.text); + cairo_move_to(cairo, x + width / 2 - text_width / 2, (int)floor(text_y)); + choose_text_aa_mode(ctx, colors.text); + render_text(cairo, config->font_description, 1, pango_markup, + "%s", label); + + return (struct box_size) { + .width = width, + .height = output->height, + }; +} + static uint32_t render_binding_mode_indicator(struct render_context *ctx, double x) { struct swaybar_output *output = ctx->output; @@ -549,54 +605,9 @@ static uint32_t render_binding_mode_indicator(struct render_context *ctx, return 0; } - cairo_t *cairo = ctx->cairo; - struct swaybar_config *config = output->bar->config; - int text_width, text_height; - get_text_size(cairo, config->font_description, &text_width, &text_height, NULL, - 1, output->bar->mode_pango_markup, - "%s", mode); - - int ws_vertical_padding = WS_VERTICAL_PADDING; - int ws_horizontal_padding = WS_HORIZONTAL_PADDING; - int border_width = BORDER_WIDTH; - - uint32_t ideal_height = text_height + ws_vertical_padding * 2 - + border_width * 2; - uint32_t ideal_surface_height = ideal_height; - if (!output->bar->config->height && - output->height < ideal_surface_height) { - return ideal_surface_height; - } - uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2; - if (width < config->workspace_min_width) { - width = config->workspace_min_width; - } - - uint32_t height = output->height; - cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); - cairo_set_source_u32(cairo, config->colors.binding_mode.background); - ctx->background_color = config->colors.binding_mode.background; - ctx->has_transparency |= (config->colors.binding_mode.background & 0xFF) != 0xFF; - cairo_rectangle(cairo, x, 0, width, height); - cairo_fill(cairo); - - cairo_set_source_u32(cairo, config->colors.binding_mode.border); - cairo_rectangle(cairo, x, 0, width, border_width); - cairo_fill(cairo); - cairo_rectangle(cairo, x, 0, border_width, height); - cairo_fill(cairo); - cairo_rectangle(cairo, x + width - border_width, 0, border_width, height); - cairo_fill(cairo); - cairo_rectangle(cairo, x, height - border_width, width, border_width); - cairo_fill(cairo); - - double text_y = height / 2.0 - text_height / 2.0; - cairo_set_source_u32(cairo, config->colors.binding_mode.text); - cairo_move_to(cairo, x + width / 2 - text_width / 2, (int)floor(text_y)); - choose_text_aa_mode(ctx, config->colors.binding_mode.text); - render_text(cairo, config->font_description, 1, output->bar->mode_pango_markup, - "%s", mode); - return output->height; + struct box_size size = render_box(ctx, x, output->bar->config->colors.binding_mode, + mode, output->bar->mode_pango_markup); + return size.height; } static enum hotspot_event_handling workspace_hotspot_callback( @@ -618,6 +629,7 @@ static uint32_t render_workspace_button(struct render_context *ctx, struct swaybar_workspace *ws, double *x) { struct swaybar_output *output = ctx->output; struct swaybar_config *config = output->bar->config; + struct box_colors box_colors; if (ws->urgent) { box_colors = config->colors.urgent_workspace; @@ -629,66 +641,21 @@ static uint32_t render_workspace_button(struct render_context *ctx, box_colors = config->colors.inactive_workspace; } - uint32_t height = output->height; - - cairo_t *cairo = ctx->cairo; - int text_width, text_height; - get_text_size(cairo, config->font_description, &text_width, &text_height, NULL, - 1, config->pango_markup, "%s", ws->label); - - int ws_vertical_padding = WS_VERTICAL_PADDING; - int ws_horizontal_padding = WS_HORIZONTAL_PADDING; - int border_width = BORDER_WIDTH; - - uint32_t ideal_height = ws_vertical_padding * 2 + text_height - + border_width * 2; - uint32_t ideal_surface_height = ideal_height; - if (!output->bar->config->height && - output->height < ideal_surface_height) { - return ideal_surface_height; - } - - uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2; - if (width < config->workspace_min_width) { - width = config->workspace_min_width; - } - - cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); - cairo_set_source_u32(cairo, box_colors.background); - ctx->background_color = box_colors.background; - ctx->has_transparency |= (box_colors.background & 0xFF) != 0xFF; - cairo_rectangle(cairo, *x, 0, width, height); - cairo_fill(cairo); - - cairo_set_source_u32(cairo, box_colors.border); - cairo_rectangle(cairo, *x, 0, width, border_width); - cairo_fill(cairo); - cairo_rectangle(cairo, *x, 0, border_width, height); - cairo_fill(cairo); - cairo_rectangle(cairo, *x + width - border_width, 0, border_width, height); - cairo_fill(cairo); - cairo_rectangle(cairo, *x, height - border_width, width, border_width); - cairo_fill(cairo); - - double text_y = height / 2.0 - text_height / 2.0; - cairo_set_source_u32(cairo, box_colors.text); - cairo_move_to(cairo, *x + width / 2 - text_width / 2, (int)floor(text_y)); - choose_text_aa_mode(ctx, box_colors.text); - render_text(cairo, config->font_description, 1, config->pango_markup, - "%s", ws->label); + struct box_size size = render_box(ctx, *x, box_colors, + ws->label, config->pango_markup); struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot)); hotspot->x = *x; hotspot->y = 0; - hotspot->width = width; - hotspot->height = height; + hotspot->width = size.width; + hotspot->height = size.height; hotspot->callback = workspace_hotspot_callback; hotspot->destroy = free; hotspot->data = strdup(ws->name); wl_list_insert(&output->hotspots, &hotspot->link); - *x += width; - return output->height; + *x += size.width; + return size.height; } static uint32_t render_to_cairo(struct render_context *ctx) {