Compare commits

..

21 commits

Author SHA1 Message Date
Simon Ser
0251e51986 build: bump version to 1.12-rc3 2026-04-26 16:35:34 +02:00
Furkan Sahin
d9ae930729 tree/workspace: fix crash on dragging container to workspace edge
When a container is moved, `finalize_move` previously assumed that
calling `workspace_split` would always result in a workspace with exactly
1 child (the wrapper container). Consequently, it safely assumed that
inserting a container with `after = 1` was always valid.

However, if the moved container was the only child of its workspace,
calling `container_detach` drops the workspace's tiling length to 0.
Calling `workspace_split` on an empty workspace simply changes its
layout enum and returns, leaving the length at 0. Passing `after`
(which evaluates to 1 when moving right/down) into
`workspace_insert_tiling` then causes an out-of-bounds insertion and a
subsequent segmentation fault during `container_build_representation`.

This commit fixes the issue by dynamically calculating the insertion
index based on the actual length of the workspace's tiling list at the
moment of insertion, rather than overloading the `after` boolean flag
as a hardcoded index.

(cherry picked from commit c857ca3a97)
2026-04-26 16:34:14 +02:00
llyyr
655b9a7e55 sway/desktop/transaction: skip freeing dirty nodes
This fixes a race that causes UAF when turning on multiple outputs after
they've been off for a while.

When output_begin_destroy is called while a transaction that references
the output is in-flight, node_set_dirty adds the node to
server.dirty_nodes list and ntxnrefs is still held by that transaction.
Once the transaction completes and ntxnrefs drops to 0,
transaction_destroy frees the output, leaving a dangling pointer in
server.dirty_nodes. The next transaction_commit_dirty call then walks
the dirty_nodes list and crashes

The fix is to skip the free in transaction_destroy if node->dirty is
set, this means transaction_commit_dirty hasn't processed this node yet
and will bump ntxnrefs shortly. The free will happen once that
transaction completes and ntxnrefs reaches 0 again and
transaction_commit_dirty will allocate a fresh instruction and
increment ntxnrefs again when it processes the node.

(cherry picked from commit 1084d2e87a)
2026-04-26 16:34:14 +02:00
Hugo Osvaldo Barrera
7afe37fcf3 Ignore failures creating linux-drm-syncobj
Failures creating this global are non-fatal.

Fixes: 1606311553
Fixes: https://github.com/swaywm/sway/issues/9110
(cherry picked from commit 1cbb8a440f)
2026-04-26 16:34:14 +02:00
Hugo Osvaldo Barrera
4cc78e9434 Treat ext-workspace-v1 as privileged
And do not expose it to clients in security contexts.

Fixes: https://github.com/swaywm/sway/issues/9120
(cherry picked from commit 80a940a992)
2026-04-26 16:34:14 +02:00
Furkan Sahin
2617212f62 input/seat: fix drag-and-drop regression and improve popup dismissal
(cherry picked from commit 9a5f09c867)
2026-04-26 16:34:14 +02:00
Furkan Sahin
0fbae654f7 input/seat: end keyboard grab when clearing focus
When focus leaves a surface, ensure any active keyboard grab is
terminated. This prevents stale xdg-popup grabs from persisting
after the popup is destroyed, which could otherwise cause keyboard
input to remain stuck on the old window.

fixes #8919

(cherry picked from commit 136765a530)
2026-04-26 16:34:14 +02:00
someever
e2178261fd Fix typos in sway-ipc.7.scd
Some sections of the man page were difficult to understand. This fixes
several typos and grammatical errors.

(cherry picked from commit e51f9d7183)
2026-04-26 16:34:14 +02:00
Simon Ser
93bba7dad5 build: bump version to 1.12-rc2 2026-04-12 18:33:52 +02:00
Alexander Orzechowski
41c94c2c72 container: Move foreign toplevel enter/leave events to view
It made sense to put it on the container level because the protocol cares
about the toplevel and that includes its decorations. But, this breaks
down when we consider if the container's view is fullscreen and the container
decorations are disabled. Moving it to the view manages this expected lifetime
better. Since the buffer is now part of the view, the buffer will get
negative coordinates to act as if it's part of the container when we
want to.

A known issue is that we will send spurious leave/enter events while
we reconfigure the scene for entering/exiting fullscreen. The fix for
this loops back to atomic updates to scene and that is outside of the scope
of this commit.

Fixes: #9000
(cherry picked from commit 81246fc6dc)
2026-04-12 18:33:42 +02:00
Hugo Osvaldo Barrera
0df0173102 Don't ignore initialisation errors
server_init ignores all errors. In practice, theses result in a
segfault, potentially much later and losing any unsaved work.

Properly handle initialisation errors and bail immediately.

(cherry picked from commit 1606311553)
2026-04-12 18:33:42 +02:00
Simon Ser
faedde4e77 build: bump version to 1.12-rc1 2026-03-26 18:42:46 +01:00
Hugo Osvaldo Barrera
4e673a3515 Centre fullscreen surfaces smaller than output
Sway renders fullscreen surfaces smaller than the output left-aligned.

From xdg-shell:

> If the surface doesn't cover the whole output, the compositor will
> position the surface in the center of the output and compensate with
> with border fill covering the rest of the output. The content of the
> border fill is undefined, but should be assumed to be in some way that
> attempts to blend into the surrounding area (e.g. solid black).

Render surfaces smaller than the output centred. Can be tested easily
with:

    weston-simple-egl -f -r

Fixes: https://github.com/swaywm/sway/issues/8845
(cherry picked from commit 909a2ddb5f)
2026-03-26 18:42:43 +01:00
Hugo Osvaldo Barrera
1e96e73767 ext-workspace-v1: initial implementation
Maintain a 1:1 relationship between workspace groups and outputs, so
that moving a workspace across groups effectively moves it across
outputs.

ext_workspace_handle_v1::id is never emitted; sway has no concept of ids
or of stable vs temporary workspaces. Everything is ephemeral to the
current session.

ext_workspace_handle_v1::coordinates is never emitted; sway does not
organise workspaces into any sort of grid.

ext_workspace_handle_v1::assign is mostly untested, because no client
current implements this. Perhaps it's best to not-advertise the feature
for now?

Deactivating a workspace is a no-op. This functionality doesn't really
align with sway, although it could potentially be implemented to "switch
to previous workspace on this output" as a follow-up.

Removing a workspace is a no-op.

Implements: https://github.com/swaywm/sway/issues/8812
(cherry picked from commit f50f78c0d9)
2026-03-26 16:21:46 +01:00
Hugo Osvaldo Barrera
3aa4c46c13 Make workspace_move_to_output reusable
Move workspace_move_to_output out of the command handler, so it can be
re-used for ext_workspace_handle_v1::assign.

(cherry picked from commit 7ba11d6dee)
2026-03-26 16:21:46 +01:00
llyyr
fa889d020b sway_text_node: properly check cairo_t status in text_calc_size
cairo_create never returns NULL, so the previous null check never
triggered. Use cairo_status instead.

(cherry picked from commit 131045ce55)
2026-03-26 16:21:46 +01:00
llyyr
257a0a7548 common/pango: use pangocairo directly instead of cairo_create(NULL)
We never need a cairo context for anything here. Use
pango_cairo_font_map_get_default() and pango_font_map_create_context()
directly instead of bootstrapping via a nil cairo context.

Same as last commit, but just a cosmetic fix in this case since we don't
actually use the cairo context for anything

(cherry picked from commit dea166a27c)
2026-03-26 16:21:46 +01:00
llyyr
9d77163d6e sway_text_node: fix cairo_create without a backing surface
This fixes sway not being able to draw text on text nodes.

cairo_create(NULL) returns a nil object in an error state rather than
NULL, causing the null check to never trigger and passing a broken cairo
context to get_text_size, which was fine until 40e1dcd29f adding error
handling to it and causing pango_cairo_update_layout to fail with a NULL
pointer.

(cherry picked from commit e4870d84a2)
2026-03-26 16:21:46 +01:00
Stephane Fontaine
948d481cfa call disable container in arrange_root
(cherry picked from commit 8378c560c1)
2026-03-26 16:21:46 +01:00
Félix Poisot
07e3c06741 common/pango: get_text_size out pointers may be NULL
Fixes: 2c2a2ec380
Closes: https://github.com/swaywm/sway/issues/9082
(cherry picked from commit 82227d6103)
2026-03-26 16:21:46 +01:00
Simon Ser
1826c38ecd ci: pin wlroots to 0.20.x 2026-03-26 16:21:36 +01:00
14 changed files with 44 additions and 59 deletions

View file

@ -25,7 +25,7 @@ packages:
- hwdata-dev - hwdata-dev
sources: sources:
- https://github.com/swaywm/sway - https://github.com/swaywm/sway
- https://gitlab.freedesktop.org/wlroots/wlroots.git - https://gitlab.freedesktop.org/wlroots/wlroots.git#0.20
tasks: tasks:
- wlroots: | - wlroots: |
cd wlroots cd wlroots

View file

@ -22,7 +22,7 @@ packages:
- hwdata - hwdata
sources: sources:
- https://github.com/swaywm/sway - https://github.com/swaywm/sway
- https://gitlab.freedesktop.org/wlroots/wlroots.git - https://gitlab.freedesktop.org/wlroots/wlroots.git#0.20
tasks: tasks:
- wlroots: | - wlroots: |
cd wlroots cd wlroots

View file

@ -31,7 +31,7 @@ packages:
- misc/hwdata - misc/hwdata
sources: sources:
- https://github.com/swaywm/sway - https://github.com/swaywm/sway
- https://gitlab.freedesktop.org/wlroots/wlroots.git - https://gitlab.freedesktop.org/wlroots/wlroots.git#0.20
tasks: tasks:
- setup: | - setup: |
cd sway cd sway

View file

@ -57,7 +57,7 @@ Si vous utilisez déjà i3, copiez votre configuration i3 vers
`~/.config/sway/config` et sway fonctionnera directement. Sinon, copiez `~/.config/sway/config` et sway fonctionnera directement. Sinon, copiez
l'exemple de fichier de configuration vers `~/.config/sway/config`. Il se l'exemple de fichier de configuration vers `~/.config/sway/config`. Il se
trouve généralement dans `/etc/sway/config`. Exécutez `man 5 sway` pour lire la trouve généralement dans `/etc/sway/config`. Exécutez `man 5 sway` pour lire la
documentation sur la configuration de sway. documentation pour la configuration de sway.
## Exécution ## Exécution

View file

@ -1,7 +1,7 @@
project( project(
'sway', 'sway',
'c', 'c',
version: '1.13-dev', version: '1.12-rc3',
license: 'MIT', license: 'MIT',
meson_version: '>=1.3', meson_version: '>=1.3',
default_options: [ default_options: [
@ -39,14 +39,14 @@ if is_freebsd
endif endif
# Execute the wlroots subproject, if any # Execute the wlroots subproject, if any
wlroots_version = ['>=0.21.0', '<0.22.0'] wlroots_version = ['>=0.20.0', '<0.21.0']
subproject( subproject(
'wlroots', 'wlroots',
default_options: ['examples=false'], default_options: ['examples=false'],
required: false, required: false,
version: wlroots_version, version: wlroots_version,
) )
wlroots = dependency('wlroots-0.21', version: wlroots_version, fallback: 'wlroots') wlroots = dependency('wlroots-0.20', version: wlroots_version, fallback: 'wlroots')
wlroots_features = { wlroots_features = {
'xwayland': false, 'xwayland': false,
'libinput_backend': false, 'libinput_backend': false,

View file

@ -1133,6 +1133,8 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n
return; return;
} }
struct sway_workspace *last_workspace = seat_get_focused_workspace(seat);
if (node == NULL) { if (node == NULL) {
seat_send_unfocus(last_focus, seat); seat_send_unfocus(last_focus, seat);
sway_input_method_relay_set_focus(&seat->im_relay, NULL); sway_input_method_relay_set_focus(&seat->im_relay, NULL);
@ -1155,15 +1157,17 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n
return; return;
} }
// Find the output's last workspace, which might have to be removed if empty
struct sway_output *new_output = struct sway_output *new_output =
new_workspace ? new_workspace->output : NULL; new_workspace ? new_workspace->output : NULL;
struct sway_workspace *last_workspace =
new_output ? output_get_active_workspace(new_output) : NULL;
if (last_workspace != new_workspace && new_output) { if (last_workspace != new_workspace && new_output) {
node_set_dirty(&new_output->node); node_set_dirty(&new_output->node);
} }
// find new output's old workspace, which might have to be removed if empty
struct sway_workspace *new_output_last_ws =
new_output ? output_get_active_workspace(new_output) : NULL;
// Unfocus the previous focus // Unfocus the previous focus
if (last_focus) { if (last_focus) {
seat_send_unfocus(last_focus, seat); seat_send_unfocus(last_focus, seat);
@ -1207,11 +1211,11 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n
} }
// Move sticky containers to new workspace // Move sticky containers to new workspace
if (new_workspace && last_workspace if (new_workspace && new_output_last_ws
&& new_workspace != last_workspace) { && new_workspace != new_output_last_ws) {
for (int i = 0; i < last_workspace->floating->length; ++i) { for (int i = 0; i < new_output_last_ws->floating->length; ++i) {
struct sway_container *floater = struct sway_container *floater =
last_workspace->floating->items[i]; new_output_last_ws->floating->items[i];
if (container_is_sticky(floater)) { if (container_is_sticky(floater)) {
container_detach(floater); container_detach(floater);
workspace_add_floating(new_workspace, floater); workspace_add_floating(new_workspace, floater);
@ -1240,9 +1244,13 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n
} }
} }
if (last_workspace) { if (new_output_last_ws) {
workspace_consider_destroy(new_output_last_ws);
}
if (last_workspace && last_workspace != new_output_last_ws) {
workspace_consider_destroy(last_workspace); workspace_consider_destroy(last_workspace);
} }
seat->has_focus = true; seat->has_focus = true;
if (config->smart_gaps && new_workspace) { if (config->smart_gaps && new_workspace) {

View file

@ -234,31 +234,26 @@ static void handle_motion_postthreshold(struct sway_seat *seat) {
if (layout == L_HORIZ || layout == L_TABBED) { if (layout == L_HORIZ || layout == L_TABBED) {
if (cursor->cursor->y < thresh_top) { if (cursor->cursor->y < thresh_top) {
edge = WLR_EDGE_TOP; edge = WLR_EDGE_TOP;
if (thresh_top < box.y) thresh_top = box.y;
box.height = thresh_top - box.y; box.height = thresh_top - box.y;
} else if (cursor->cursor->y > thresh_bottom) { } else if (cursor->cursor->y > thresh_bottom) {
edge = WLR_EDGE_BOTTOM; edge = WLR_EDGE_BOTTOM;
if (thresh_bottom > box.y + box.height) thresh_bottom = box.y + box.height;
box.height = box.y + box.height - thresh_bottom; box.height = box.y + box.height - thresh_bottom;
box.y = thresh_bottom; box.y = thresh_bottom;
} }
} else if (layout == L_VERT || layout == L_STACKED) { } else if (layout == L_VERT || layout == L_STACKED) {
if (cursor->cursor->x < thresh_left) { if (cursor->cursor->x < thresh_left) {
edge = WLR_EDGE_LEFT; edge = WLR_EDGE_LEFT;
if (thresh_left < box.x) thresh_left = box.x;
box.width = thresh_left - box.x; box.width = thresh_left - box.x;
} else if (cursor->cursor->x > thresh_right) { } else if (cursor->cursor->x > thresh_right) {
edge = WLR_EDGE_RIGHT; edge = WLR_EDGE_RIGHT;
if (thresh_right > box.x + box.width) thresh_right = box.x + box.width;
box.width = box.x + box.width - thresh_right; box.width = box.x + box.width - thresh_right;
box.x = thresh_right; box.x = thresh_right;
} }
} }
if (edge) { if (edge) {
e->target_node = node_get_parent(&con->node); e->target_node = node_get_parent(&con->node);
if (e->target_node && (e->target_node == &e->con->node || if (e->target_node == &e->con->node) {
node_has_ancestor(e->target_node, &e->con->node))) { e->target_node = node_get_parent(e->target_node);
e->target_node = node_get_parent(&e->con->node);
} }
e->target_edge = edge; e->target_edge = edge;
update_indicator(e, &box); update_indicator(e, &box);

View file

@ -78,7 +78,6 @@
#define SWAY_LAYER_SHELL_VERSION 5 #define SWAY_LAYER_SHELL_VERSION 5
#define SWAY_FOREIGN_TOPLEVEL_LIST_VERSION 1 #define SWAY_FOREIGN_TOPLEVEL_LIST_VERSION 1
#define SWAY_PRESENTATION_VERSION 2 #define SWAY_PRESENTATION_VERSION 2
#define SWAY_XDG_DECORATION_VERSION 2
bool unsupported_gpu_detected = false; bool unsupported_gpu_detected = false;
@ -229,7 +228,7 @@ static void handle_renderer_lost(struct wl_listener *listener, void *data) {
} }
static void handle_new_foreign_toplevel_capture_request(struct wl_listener *listener, void *data) { static void handle_new_foreign_toplevel_capture_request(struct wl_listener *listener, void *data) {
struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_event *request = data; struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request = data;
struct sway_view *view = request->toplevel_handle->data; struct sway_view *view = request->toplevel_handle->data;
if (view->image_capture_source == NULL) { if (view->image_capture_source == NULL) {
@ -397,8 +396,7 @@ bool server_init(struct sway_server *server) {
wl_list_init(&server->decorations); wl_list_init(&server->decorations);
server->xdg_decoration_manager = server->xdg_decoration_manager =
wlr_xdg_decoration_manager_v1_create(server->wl_display, wlr_xdg_decoration_manager_v1_create(server->wl_display);
SWAY_XDG_DECORATION_VERSION);
if (!server->xdg_decoration_manager) { if (!server->xdg_decoration_manager) {
sway_log(SWAY_ERROR, "Failed to create XDG decoration manager"); sway_log(SWAY_ERROR, "Failed to create XDG decoration manager");
return false; return false;
@ -561,7 +559,7 @@ bool server_init(struct sway_server *server) {
return false; return false;
} }
server->new_foreign_toplevel_capture_request.notify = handle_new_foreign_toplevel_capture_request; server->new_foreign_toplevel_capture_request.notify = handle_new_foreign_toplevel_capture_request;
wl_signal_add(&server->ext_foreign_toplevel_image_capture_source_manager_v1->events.capture_request, wl_signal_add(&server->ext_foreign_toplevel_image_capture_source_manager_v1->events.new_request,
&server->new_foreign_toplevel_capture_request); &server->new_foreign_toplevel_capture_request);
server->tearing_control_v1 = server->tearing_control_v1 =

View file

@ -154,7 +154,7 @@ The following commands may only be used in the configuration file.
*input* <identifier> drag_lock enabled|disabled|enabled_sticky *input* <identifier> drag_lock enabled|disabled|enabled_sticky
Enables or disables drag lock for specified input device. The default is Enables or disables drag lock for specified input device. The default is
_disabled_. _enabled_sticky_.
*input* <identifier> dwt enabled|disabled *input* <identifier> dwt enabled|disabled
Enables or disables disable-while-typing for the specified input device. Enables or disables disable-while-typing for the specified input device.

View file

@ -294,10 +294,6 @@ set|plus|minus|toggle <amount>
A no operation command that can be used to override default behaviour. The A no operation command that can be used to override default behaviour. The
optional comment argument is ignored, but logged for debugging purposes. optional comment argument is ignored, but logged for debugging purposes.
*opacity* [set|plus|minus] <value>
Adjusts the opacity of the window between 0 (completely transparent) and
1 (completely opaque). If the operation is omitted, _set_ will be used.
*reload* *reload*
Reloads the sway config file and applies any changes. The config file is Reloads the sway config file and applies any changes. The config file is
located at path specified by the command line arguments when started, located at path specified by the command line arguments when started,
@ -877,6 +873,10 @@ The default colors are:
Any mark that starts with an underscore will not be drawn even if Any mark that starts with an underscore will not be drawn even if
*show_marks* is yes. The default is _yes_. *show_marks* is yes. The default is _yes_.
*opacity* [set|plus|minus] <value>
Adjusts the opacity of the window between 0 (completely transparent) and
1 (completely opaque). If the operation is omitted, _set_ will be used.
*tiling_drag* enable|disable|toggle *tiling_drag* enable|disable|toggle
Sets whether or not tiling containers can be dragged with the mouse. If Sets whether or not tiling containers can be dragged with the mouse. If
_enabled_ (default), the _floating_mod_ can be used to drag tiling, as well _enabled_ (default), the _floating_mod_ can be used to drag tiling, as well

View file

@ -920,7 +920,7 @@ void workspace_unwrap_children(struct sway_workspace *ws,
while (wrap->pending.children->length) { while (wrap->pending.children->length) {
struct sway_container *child = wrap->pending.children->items[0]; struct sway_container *child = wrap->pending.children->items[0];
container_detach(child); container_detach(child);
workspace_insert_tiling_direct(ws, child, ws->tiling->length); workspace_add_tiling(ws, child);
} }
} }

View file

@ -417,28 +417,6 @@ void ipc_execute_binding(struct swaybar *bar, struct swaybar_binding *bind) {
} }
bool ipc_initialize(struct swaybar *bar) { bool ipc_initialize(struct swaybar *bar) {
if (!bar->id) {
uint32_t len = 0;
char *res = ipc_single_command(bar->ipc_socketfd,
IPC_GET_BAR_CONFIG, "", &len);
json_object *bars = json_tokener_parse(res);
if (!json_object_is_type(bars, json_type_array)
|| json_object_array_length(bars) == 0) {
sway_log(SWAY_ERROR, "No bar configuration found, "
"please configure a bar block in your sway config file.");
json_object_put(bars);
free(res);
return false;
}
json_object *first = json_object_array_get_idx(bars, 0);
bar->id = strdup(json_object_get_string(first));
json_object_put(bars);
free(res);
sway_log(SWAY_INFO, "Using first bar config: %s. "
"Use --bar_id to manually select a different bar configuration.",
bar->id);
}
uint32_t len = strlen(bar->id); uint32_t len = strlen(bar->id);
char *res = ipc_single_command(bar->ipc_socketfd, char *res = ipc_single_command(bar->ipc_socketfd,
IPC_GET_BAR_CONFIG, bar->id, &len); IPC_GET_BAR_CONFIG, bar->id, &len);

View file

@ -72,6 +72,12 @@ int main(int argc, char **argv) {
sway_log_init(SWAY_INFO, NULL); sway_log_init(SWAY_INFO, NULL);
} }
if (!swaybar.id) {
sway_log(SWAY_ERROR, "No bar_id passed. "
"Provide --bar_id or let sway start swaybar");
return 1;
}
if (!socket_path) { if (!socket_path) {
socket_path = get_socketpath(); socket_path = get_socketpath();
if (!socket_path) { if (!socket_path) {

View file

@ -66,17 +66,17 @@ static int read_pixmap(sd_bus_message *msg, struct swaybar_sni *sni,
} }
const void *pixels; const void *pixels;
size_t pixel_data_size; // size in bytes, each pixel is 4 bytes size_t npixels;
ret = sd_bus_message_read_array(msg, 'y', &pixels, &pixel_data_size); ret = sd_bus_message_read_array(msg, 'y', &pixels, &npixels);
if (ret < 0) { if (ret < 0) {
sway_log(SWAY_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret)); sway_log(SWAY_ERROR, "%s %s: %s", sni->watcher_id, prop, strerror(-ret));
goto error; goto error;
} }
if (height > 0 && width == height && (size_t)width * height <= pixel_data_size / 4) { if (height > 0 && width == height) {
sway_log(SWAY_DEBUG, "%s %s: found icon w:%d h:%d", sni->watcher_id, prop, width, height); sway_log(SWAY_DEBUG, "%s %s: found icon w:%d h:%d", sni->watcher_id, prop, width, height);
struct swaybar_pixmap *pixmap = struct swaybar_pixmap *pixmap =
malloc(sizeof(struct swaybar_pixmap) + pixel_data_size); malloc(sizeof(struct swaybar_pixmap) + npixels);
pixmap->size = height; pixmap->size = height;
// convert from network byte order to host byte order // convert from network byte order to host byte order