Compare commits

...

13 commits

Author SHA1 Message Date
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
21 changed files with 524 additions and 153 deletions

View file

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

View file

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

View file

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

View file

@ -84,7 +84,15 @@ PangoLayout *get_pango_layout(cairo_t *cairo, const PangoFontDescription *desc,
void get_text_size(cairo_t *cairo, const PangoFontDescription *desc, int *width, int *height,
int *baseline, double scale, bool markup, const char *fmt, ...) {
*width = *height = *baseline = 0;
if (width) {
*width = 0;
}
if (height) {
*height = 0;
}
if (baseline) {
*baseline = 0;
}
va_list args;
va_start(args, fmt);
@ -115,8 +123,8 @@ out:
}
void get_text_metrics(const PangoFontDescription *description, int *height, int *baseline) {
cairo_t *cairo = cairo_create(NULL);
PangoContext *pango = pango_cairo_create_context(cairo);
PangoFontMap *fontmap = pango_cairo_font_map_get_default();
PangoContext *pango = pango_font_map_create_context(fontmap);
pango_context_set_round_glyph_positions(pango, false);
// When passing NULL as a language, pango uses the current locale.
PangoFontMetrics *metrics = pango_context_get_metrics(pango, description, NULL);
@ -126,7 +134,6 @@ void get_text_metrics(const PangoFontDescription *description, int *height, int
pango_font_metrics_unref(metrics);
g_object_unref(pango);
cairo_destroy(cairo);
}
void render_text(cairo_t *cairo, const PangoFontDescription *desc,

View file

@ -63,6 +63,7 @@ struct sway_output {
struct wl_listener request_state;
struct wlr_color_transform *color_transform;
struct wlr_ext_workspace_group_handle_v1 *ext_workspace_group;
struct timespec last_presentation;
uint32_t refresh_nsec;

View file

@ -127,6 +127,9 @@ struct sway_server {
struct wl_listener tearing_control_new_object;
struct wl_list tearing_controllers; // sway_tearing_controller::link
struct wlr_ext_workspace_manager_v1 *workspace_manager_v1;
struct wl_listener workspace_manager_v1_commit;
struct wl_list pending_launcher_ctxs; // launcher_ctx::link
// The timeout for transactions, after which a transaction is applied
@ -173,7 +176,7 @@ void handle_new_output(struct wl_listener *listener, void *data);
void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data);
void handle_layer_shell_surface(struct wl_listener *listener, void *data);
void sway_session_lock_init(void);
bool sway_session_lock_init(void);
void sway_session_lock_add_output(struct sway_session_lock *lock,
struct sway_output *output);
bool sway_session_lock_has_surface(struct sway_session_lock *lock,

View file

@ -91,10 +91,6 @@ struct sway_container {
} border;
struct wlr_scene_tree *content_tree;
struct wlr_scene_buffer *output_handler;
struct wl_listener outputs_update;
struct wl_listener output_handler_destroy;
struct sway_container_state current;
struct sway_container_state pending;

View file

@ -69,6 +69,9 @@ struct sway_view {
struct wlr_scene_tree *scene_tree;
struct wlr_scene_tree *content_tree;
struct wlr_scene_tree *saved_surface_tree;
struct wlr_scene_buffer *output_handler;
struct wl_listener outputs_update;
struct wlr_scene *image_capture_scene;
struct wlr_ext_image_capture_source_v1 *image_capture_source;

View file

@ -3,6 +3,7 @@
#include <stdbool.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_ext_workspace_v1.h>
#include "sway/config.h"
#include "sway/tree/container.h"
#include "sway/tree/node.h"
@ -51,6 +52,7 @@ struct sway_workspace {
bool urgent;
struct sway_workspace_state current;
struct wlr_ext_workspace_handle_v1 *ext_workspace; // Always set.
};
struct workspace_config *workspace_find_config(const char *ws_name);
@ -157,4 +159,11 @@ size_t workspace_num_sticky_containers(struct sway_workspace *ws);
*/
void workspace_squash(struct sway_workspace *workspace);
void workspace_move_to_output(struct sway_workspace *workspace,
struct sway_output *output);
bool sway_ext_workspace_init(void);
void sway_ext_workspace_output_enable(struct sway_output *output);
void sway_ext_workspace_output_disable(struct sway_output *output);
#endif

View file

@ -1,7 +1,7 @@
project(
'sway',
'c',
version: '1.12-dev',
version: '1.12-rc2',
license: 'MIT',
meson_version: '>=1.3',
default_options: [

View file

@ -627,40 +627,6 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth,
return cmd_results_new(CMD_SUCCESS, NULL);
}
static void workspace_move_to_output(struct sway_workspace *workspace,
struct sway_output *output) {
if (workspace->output == output) {
return;
}
struct sway_output *old_output = workspace->output;
workspace_detach(workspace);
struct sway_workspace *new_output_old_ws =
output_get_active_workspace(output);
if (!sway_assert(new_output_old_ws, "Expected output to have a workspace")) {
return;
}
output_add_workspace(output, workspace);
// If moving the last workspace from the old output, create a new workspace
// on the old output
struct sway_seat *seat = config->handler_context.seat;
if (old_output->workspaces->length == 0) {
char *ws_name = workspace_next_name(old_output->wlr_output->name);
struct sway_workspace *ws = workspace_create(old_output, ws_name);
free(ws_name);
seat_set_raw_focus(seat, &ws->node);
}
workspace_consider_destroy(new_output_old_ws);
output_sort_workspaces(output);
struct sway_node *focus = seat_get_focus_inactive(seat, &workspace->node);
seat_set_focus(seat, focus);
workspace_output_raise_priority(workspace, old_output, output);
ipc_event_workspace(NULL, workspace, "move");
}
static struct cmd_results *cmd_move_workspace(int argc, char **argv) {
struct cmd_results *error = NULL;
if ((error = checkarg(argc, "move workspace", EXPECTED_AT_LEAST, 1))) {
@ -696,6 +662,8 @@ static struct cmd_results *cmd_move_workspace(int argc, char **argv) {
arrange_output(new_output);
struct sway_seat *seat = config->handler_context.seat;
struct sway_node *focus = seat_get_focus_inactive(seat, &workspace->node);
seat_set_focus(seat, focus);
seat_consider_warp_to_focus(seat);
return cmd_results_new(CMD_SUCCESS, NULL);

View file

@ -1,6 +1,7 @@
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <wlr/types/wlr_ext_workspace_v1.h>
#include "log.h"
#include "stringop.h"
#include "sway/commands.h"
@ -95,6 +96,8 @@ struct cmd_results *cmd_rename(int argc, char **argv) {
free(workspace->name);
workspace->name = new_name;
wlr_ext_workspace_handle_v1_set_name(workspace->ext_workspace, workspace->name);
output_sort_workspaces(workspace->output);
ipc_event_workspace(NULL, workspace, "rename");

View file

@ -395,10 +395,6 @@ static void arrange_container(struct sway_container *con,
// make sure it's enabled for viewing
wlr_scene_node_set_enabled(&con->scene_tree->node, true);
if (con->output_handler) {
wlr_scene_buffer_set_dest_size(con->output_handler, width, height);
}
if (con->view) {
int border_top = container_titlebar_height();
int border_width = con->current.border_thickness;
@ -456,6 +452,13 @@ static void arrange_container(struct sway_container *con,
wlr_scene_node_reparent(&con->view->scene_tree->node, con->content_tree);
wlr_scene_node_set_position(&con->view->scene_tree->node,
border_left, border_top);
// the output handler for the view wants to detect events for the entire
// container so give it negative coordinates to move it back over the
// decorations
wlr_scene_node_set_position(&con->view->output_handler->node,
-border_left, -border_top);
wlr_scene_buffer_set_dest_size(con->view->output_handler, width, height);
} else {
// make sure to disable the title bar if the parent is not managing it
if (title_bar) {
@ -495,6 +498,11 @@ static void arrange_fullscreen(struct wlr_scene_tree *tree,
// if we only care about the view, disable any decorations
wlr_scene_node_set_enabled(&fs->scene_tree->node, false);
// reconfigure the output handler (for foreign toplevel) to cover the
// view without container decorations
wlr_scene_node_set_position(&fs->view->output_handler->node, 0, 0);
wlr_scene_buffer_set_dest_size(fs->view->output_handler, width, height);
} else {
fs_node = &fs->scene_tree->node;
arrange_container(fs, width, height, true, container_get_gaps(fs));
@ -648,15 +656,7 @@ static void arrange_root(struct sway_root *root) {
for (int i = 0; i < root->scratchpad->length; i++) {
struct sway_container *con = root->scratchpad->items[i];
// When a container is moved to a scratchpad, it's possible that it
// was moved into a floating container as part of the same transaction.
// In this case, we need to make sure we reparent all the container's
// children so that disabling the container will disable all descendants.
if (!con->view) for (int ii = 0; ii < con->current.children->length; ii++) {
struct sway_container *child = con->current.children->items[ii];
wlr_scene_node_reparent(&child->scene_tree->node, con->content_tree);
}
disable_container(con);
wlr_scene_node_set_enabled(&con->scene_tree->node, false);
}

View file

@ -6,6 +6,7 @@
#include <wlr/config.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_ext_workspace_v1.h>
#include <wlr/types/wlr_idle_notify_v1.h>
#include <wlr/types/wlr_keyboard_group.h>
#include <wlr/types/wlr_output_layout.h>
@ -1204,6 +1205,15 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n
ipc_event_window(container, "focus");
}
if (last_workspace && last_workspace != new_workspace) {
wlr_ext_workspace_handle_v1_set_active(last_workspace->ext_workspace,
workspace_is_visible(last_workspace));
}
if (new_workspace) {
wlr_ext_workspace_handle_v1_set_active(new_workspace->ext_workspace,
workspace_is_visible(new_workspace));
}
// Move sticky containers to new workspace
if (new_workspace && new_output_last_ws
&& new_workspace != new_output_last_ws) {

View file

@ -344,8 +344,12 @@ bool sway_session_lock_has_surface(struct sway_session_lock *lock,
return false;
}
void sway_session_lock_init(void) {
bool sway_session_lock_init(void) {
server.session_lock.manager = wlr_session_lock_manager_v1_create(server.wl_display);
if (!server.session_lock.manager) {
sway_log(SWAY_ERROR, "Failed to create session lock manager");
return false;
}
server.session_lock.new_lock.notify = handle_session_lock;
server.session_lock.manager_destroy.notify = handle_session_lock_destroy;
@ -353,4 +357,5 @@ void sway_session_lock_init(void) {
&server.session_lock.new_lock);
wl_signal_add(&server.session_lock.manager->events.destroy,
&server.session_lock.manager_destroy);
return true;
}

View file

@ -63,6 +63,7 @@
#include "sway/server.h"
#include "sway/input/cursor.h"
#include "sway/tree/root.h"
#include "sway/tree/workspace.h"
#if WLR_HAS_XWAYLAND
#include <wlr/xwayland/shell.h>
@ -243,13 +244,25 @@ static void handle_new_foreign_toplevel_capture_request(struct wl_listener *list
bool server_init(struct sway_server *server) {
sway_log(SWAY_DEBUG, "Initializing Wayland server");
server->wl_display = wl_display_create();
if (!server->wl_display) {
sway_log(SWAY_ERROR, "Failed to create wl_display");
return false;
}
server->wl_event_loop = wl_display_get_event_loop(server->wl_display);
wl_display_set_global_filter(server->wl_display, filter_global, NULL);
wl_display_set_default_max_buffer_size(server->wl_display, 1024 * 1024);
wlr_fixes_create(server->wl_display, 1);
if (!wlr_fixes_create(server->wl_display, 1)) {
sway_log(SWAY_ERROR, "Failed to create wp_fixes global");
return false;
}
root = root_create(server->wl_display);
if (!root) {
sway_log(SWAY_ERROR, "Failed to create root");
wl_display_destroy(server->wl_display);
return false;
}
server->backend = wlr_backend_autocreate(server->wl_event_loop, &server->session);
if (!server->backend) {
@ -270,15 +283,23 @@ bool server_init(struct sway_server *server) {
wlr_renderer_init_wl_shm(server->renderer, server->wl_display);
if (wlr_renderer_get_texture_formats(server->renderer, WLR_BUFFER_CAP_DMABUF) != NULL) {
if (wlr_renderer_get_drm_fd(server->renderer) >= 0 &&
wlr_renderer_get_texture_formats(server->renderer, WLR_BUFFER_CAP_DMABUF) != NULL) {
server->linux_dmabuf_v1 = wlr_linux_dmabuf_v1_create_with_renderer(
server->wl_display, 5, server->renderer);
if (!server->linux_dmabuf_v1) {
sway_log(SWAY_ERROR, "Failed to create linux-dmabuf v1");
return false;
}
}
if (wlr_renderer_get_drm_fd(server->renderer) >= 0 &&
server->renderer->features.timeline &&
server->backend->features.timeline) {
wlr_linux_drm_syncobj_manager_v1_create(server->wl_display, 1,
wlr_renderer_get_drm_fd(server->renderer));
if (!wlr_linux_drm_syncobj_manager_v1_create(server->wl_display, 1,
wlr_renderer_get_drm_fd(server->renderer))) {
sway_log(SWAY_ERROR, "Failed to create linux-drm-syncobj v1");
return false;
}
}
server->allocator = wlr_allocator_autocreate(server->backend,
@ -290,14 +311,29 @@ bool server_init(struct sway_server *server) {
server->compositor = wlr_compositor_create(server->wl_display, 6,
server->renderer);
if (!server->compositor) {
sway_log(SWAY_ERROR, "Failed to create compositor");
return false;
}
wlr_subcompositor_create(server->wl_display);
if (!wlr_subcompositor_create(server->wl_display)) {
sway_log(SWAY_ERROR, "Failed to create subcompositor");
return false;
}
server->data_device_manager =
wlr_data_device_manager_create(server->wl_display);
if (!server->data_device_manager) {
sway_log(SWAY_ERROR, "Failed to create data device manager");
return false;
}
server->gamma_control_manager_v1 =
wlr_gamma_control_manager_v1_create(server->wl_display);
if (!server->gamma_control_manager_v1) {
sway_log(SWAY_ERROR, "Failed to create gamma control manager");
return false;
}
wlr_scene_set_gamma_control_manager_v1(root->root_scene,
server->gamma_control_manager_v1);
@ -306,26 +342,53 @@ bool server_init(struct sway_server *server) {
server->xdg_output_manager_v1 =
wlr_xdg_output_manager_v1_create(server->wl_display, root->output_layout);
if (!server->xdg_output_manager_v1) {
sway_log(SWAY_ERROR, "Failed to create XDG output manager");
return false;
}
server->idle_notifier_v1 = wlr_idle_notifier_v1_create(server->wl_display);
sway_idle_inhibit_manager_v1_init();
if (!server->idle_notifier_v1) {
sway_log(SWAY_ERROR, "Failed to create idle notifier");
return false;
}
if (!sway_idle_inhibit_manager_v1_init()) {
sway_log(SWAY_ERROR, "Failed to init idle inhibit manager");
return false;
}
server->layer_shell = wlr_layer_shell_v1_create(server->wl_display,
SWAY_LAYER_SHELL_VERSION);
if (!server->layer_shell) {
sway_log(SWAY_ERROR, "Failed to create layer shell");
return false;
}
wl_signal_add(&server->layer_shell->events.new_surface,
&server->layer_shell_surface);
server->layer_shell_surface.notify = handle_layer_shell_surface;
server->xdg_shell = wlr_xdg_shell_create(server->wl_display,
SWAY_XDG_SHELL_VERSION);
if (!server->xdg_shell) {
sway_log(SWAY_ERROR, "Failed to create XDG shell");
return false;
}
wl_signal_add(&server->xdg_shell->events.new_toplevel,
&server->xdg_shell_toplevel);
server->xdg_shell_toplevel.notify = handle_xdg_shell_toplevel;
server->tablet_v2 = wlr_tablet_v2_create(server->wl_display);
if (!server->tablet_v2) {
sway_log(SWAY_ERROR, "Failed to create tablet manager");
return false;
}
server->server_decoration_manager =
wlr_server_decoration_manager_create(server->wl_display);
if (!server->server_decoration_manager) {
sway_log(SWAY_ERROR, "Failed to create server decoration manager");
return false;
}
wlr_server_decoration_manager_set_default_mode(
server->server_decoration_manager,
WLR_SERVER_DECORATION_MANAGER_MODE_SERVER);
@ -336,6 +399,10 @@ bool server_init(struct sway_server *server) {
server->xdg_decoration_manager =
wlr_xdg_decoration_manager_v1_create(server->wl_display);
if (!server->xdg_decoration_manager) {
sway_log(SWAY_ERROR, "Failed to create XDG decoration manager");
return false;
}
wl_signal_add(
&server->xdg_decoration_manager->events.new_toplevel_decoration,
&server->xdg_decoration);
@ -344,18 +411,36 @@ bool server_init(struct sway_server *server) {
server->relative_pointer_manager =
wlr_relative_pointer_manager_v1_create(server->wl_display);
if (!server->relative_pointer_manager) {
sway_log(SWAY_ERROR, "Failed to create relative pointer manager");
return false;
}
server->pointer_constraints =
wlr_pointer_constraints_v1_create(server->wl_display);
if (!server->pointer_constraints) {
sway_log(SWAY_ERROR, "Failed to create pointer constraints");
return false;
}
server->pointer_constraint.notify = handle_pointer_constraint;
wl_signal_add(&server->pointer_constraints->events.new_constraint,
&server->pointer_constraint);
wlr_presentation_create(server->wl_display, server->backend, SWAY_PRESENTATION_VERSION);
wlr_alpha_modifier_v1_create(server->wl_display);
if (!wlr_presentation_create(server->wl_display, server->backend, SWAY_PRESENTATION_VERSION)) {
sway_log(SWAY_ERROR, "Failed to create presentation");
return false;
}
if (!wlr_alpha_modifier_v1_create(server->wl_display)) {
sway_log(SWAY_ERROR, "Failed to create alpha modifier");
return false;
}
server->output_manager_v1 =
wlr_output_manager_v1_create(server->wl_display);
if (!server->output_manager_v1) {
sway_log(SWAY_ERROR, "Failed to create output manager");
return false;
}
server->output_manager_apply.notify = handle_output_manager_apply;
wl_signal_add(&server->output_manager_v1->events.apply,
&server->output_manager_apply);
@ -365,18 +450,43 @@ bool server_init(struct sway_server *server) {
server->output_power_manager_v1 =
wlr_output_power_manager_v1_create(server->wl_display);
if (!server->output_power_manager_v1) {
sway_log(SWAY_ERROR, "Failed to create output power manager");
return false;
}
server->output_power_manager_set_mode.notify =
handle_output_power_manager_set_mode;
wl_signal_add(&server->output_power_manager_v1->events.set_mode,
&server->output_power_manager_set_mode);
server->input_method = wlr_input_method_manager_v2_create(server->wl_display);
if (!server->input_method) {
sway_log(SWAY_ERROR, "Failed to create input method manager");
return false;
}
server->text_input = wlr_text_input_manager_v3_create(server->wl_display);
if (!server->text_input) {
sway_log(SWAY_ERROR, "Failed to create text input manager");
return false;
}
server->foreign_toplevel_list =
wlr_ext_foreign_toplevel_list_v1_create(server->wl_display, SWAY_FOREIGN_TOPLEVEL_LIST_VERSION);
if (!server->foreign_toplevel_list) {
sway_log(SWAY_ERROR, "Failed to create foreign toplevel list");
return false;
}
server->foreign_toplevel_manager =
wlr_foreign_toplevel_manager_v1_create(server->wl_display);
if (!server->foreign_toplevel_manager) {
sway_log(SWAY_ERROR, "Failed to create foreign toplevel manager");
return false;
}
sway_session_lock_init();
if (!sway_session_lock_init()) {
return false;
}
if (!sway_ext_workspace_init()) {
return false;
}
#if WLR_HAS_DRM_BACKEND
server->drm_lease_manager=
@ -392,26 +502,74 @@ bool server_init(struct sway_server *server) {
#endif
server->export_dmabuf_manager_v1 = wlr_export_dmabuf_manager_v1_create(server->wl_display);
if (!server->export_dmabuf_manager_v1) {
sway_log(SWAY_ERROR, "Failed to create export dmabuf manager");
return false;
}
server->screencopy_manager_v1 = wlr_screencopy_manager_v1_create(server->wl_display);
if (!server->screencopy_manager_v1) {
sway_log(SWAY_ERROR, "Failed to create screencopy manager");
return false;
}
server->ext_image_copy_capture_manager_v1 = wlr_ext_image_copy_capture_manager_v1_create(server->wl_display, 1);
wlr_ext_output_image_capture_source_manager_v1_create(server->wl_display, 1);
if (!server->ext_image_copy_capture_manager_v1) {
sway_log(SWAY_ERROR, "Failed to create ext image copy capture manager");
return false;
}
if (!wlr_ext_output_image_capture_source_manager_v1_create(server->wl_display, 1)) {
sway_log(SWAY_ERROR, "Failed to create ext output image capture source manager");
return false;
}
server->wlr_data_control_manager_v1 = wlr_data_control_manager_v1_create(server->wl_display);
if (!server->wlr_data_control_manager_v1) {
sway_log(SWAY_ERROR, "Failed to create data control manager");
return false;
}
server->ext_data_control_manager_v1 = wlr_ext_data_control_manager_v1_create(server->wl_display, 1);
if (!server->ext_data_control_manager_v1) {
sway_log(SWAY_ERROR, "Failed to create ext data control manager");
return false;
}
server->security_context_manager_v1 = wlr_security_context_manager_v1_create(server->wl_display);
wlr_viewporter_create(server->wl_display);
wlr_single_pixel_buffer_manager_v1_create(server->wl_display);
if (!server->security_context_manager_v1) {
sway_log(SWAY_ERROR, "Failed to create security context manager");
return false;
}
if (!wlr_viewporter_create(server->wl_display)) {
sway_log(SWAY_ERROR, "Failed to create viewporter");
return false;
}
if (!wlr_single_pixel_buffer_manager_v1_create(server->wl_display)) {
sway_log(SWAY_ERROR, "Failed to create single pixel buffer manager");
return false;
}
server->content_type_manager_v1 =
wlr_content_type_manager_v1_create(server->wl_display, 1);
wlr_fractional_scale_manager_v1_create(server->wl_display, 1);
if (!server->content_type_manager_v1) {
sway_log(SWAY_ERROR, "Failed to create content type manager");
return false;
}
if (!wlr_fractional_scale_manager_v1_create(server->wl_display, 1)) {
sway_log(SWAY_ERROR, "Failed to create fractional scale manager");
return false;
}
server->ext_foreign_toplevel_image_capture_source_manager_v1 =
wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(server->wl_display, 1);
if (!server->ext_foreign_toplevel_image_capture_source_manager_v1) {
sway_log(SWAY_ERROR, "Failed to create ext foreign toplevel image capture source manager");
return false;
}
server->new_foreign_toplevel_capture_request.notify = handle_new_foreign_toplevel_capture_request;
wl_signal_add(&server->ext_foreign_toplevel_image_capture_source_manager_v1->events.new_request,
&server->new_foreign_toplevel_capture_request);
server->tearing_control_v1 =
wlr_tearing_control_manager_v1_create(server->wl_display, 1);
if (!server->tearing_control_v1) {
sway_log(SWAY_ERROR, "Failed to create tearing control manager");
return false;
}
server->tearing_control_new_object.notify = handle_new_tearing_hint;
wl_signal_add(&server->tearing_control_v1->events.new_object,
&server->tearing_control_new_object);
@ -419,10 +577,24 @@ bool server_init(struct sway_server *server) {
struct wlr_xdg_foreign_registry *foreign_registry =
wlr_xdg_foreign_registry_create(server->wl_display);
wlr_xdg_foreign_v1_create(server->wl_display, foreign_registry);
wlr_xdg_foreign_v2_create(server->wl_display, foreign_registry);
if (!foreign_registry) {
sway_log(SWAY_ERROR, "Failed to create XDG foreign registry");
return false;
}
if (!wlr_xdg_foreign_v1_create(server->wl_display, foreign_registry)) {
sway_log(SWAY_ERROR, "Failed to create XDG foreign v1");
return false;
}
if (!wlr_xdg_foreign_v2_create(server->wl_display, foreign_registry)) {
sway_log(SWAY_ERROR, "Failed to create XDG foreign v2");
return false;
}
server->xdg_activation_v1 = wlr_xdg_activation_v1_create(server->wl_display);
if (!server->xdg_activation_v1) {
sway_log(SWAY_ERROR, "Failed to create XDG activation");
return false;
}
server->xdg_activation_v1_request_activate.notify =
xdg_activation_v1_handle_request_activate;
wl_signal_add(&server->xdg_activation_v1->events.request_activate,
@ -434,6 +606,10 @@ bool server_init(struct sway_server *server) {
struct wlr_xdg_toplevel_tag_manager_v1 *xdg_toplevel_tag_manager_v1 =
wlr_xdg_toplevel_tag_manager_v1_create(server->wl_display, 1);
if (!xdg_toplevel_tag_manager_v1) {
sway_log(SWAY_ERROR, "Failed to create XDG toplevel tag manager");
return false;
}
server->xdg_toplevel_tag_manager_v1_set_tag.notify =
xdg_toplevel_tag_manager_v1_handle_set_tag;
wl_signal_add(&xdg_toplevel_tag_manager_v1->events.set_tag,
@ -441,6 +617,10 @@ bool server_init(struct sway_server *server) {
struct wlr_cursor_shape_manager_v1 *cursor_shape_manager =
wlr_cursor_shape_manager_v1_create(server->wl_display, 2);
if (!cursor_shape_manager) {
sway_log(SWAY_ERROR, "Failed to create cursor shape manager");
return false;
}
server->request_set_cursor_shape.notify = handle_request_set_cursor_shape;
wl_signal_add(&cursor_shape_manager->events.request_set_shape, &server->request_set_cursor_shape);
@ -469,11 +649,18 @@ bool server_init(struct sway_server *server) {
});
free(transfer_functions);
free(primaries);
if (!cm) {
sway_log(SWAY_ERROR, "Failed to create color manager");
return false;
}
wlr_scene_set_color_manager_v1(root->root_scene, cm);
}
wlr_color_representation_manager_v1_create_with_renderer(
server->wl_display, 1, server->renderer);
if (!wlr_color_representation_manager_v1_create_with_renderer(
server->wl_display, 1, server->renderer)) {
sway_log(SWAY_ERROR, "Failed to create color representation manager");
return false;
}
wl_list_init(&server->pending_launcher_ctxs);
@ -513,8 +700,16 @@ bool server_init(struct sway_server *server) {
}
server->dirty_nodes = create_list();
if (!server->dirty_nodes) {
sway_log(SWAY_ERROR, "Failed to create dirty nodes list");
return false;
}
server->input = input_manager_create(server);
if (!server->input) {
sway_log(SWAY_ERROR, "Failed to create input manager");
return false;
}
input_manager_get_default_seat(); // create seat0
return true;
@ -543,6 +738,7 @@ void server_fini(struct sway_server *server) {
wl_list_remove(&server->xdg_toplevel_tag_manager_v1_set_tag.link);
wl_list_remove(&server->request_set_cursor_shape.link);
wl_list_remove(&server->new_foreign_toplevel_capture_request.link);
wl_list_remove(&server->workspace_manager_v1_commit.link);
input_manager_finish(server->input);
// TODO: free sway-specific resources

View file

@ -198,19 +198,24 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
static void text_calc_size(struct text_buffer *buffer) {
struct sway_text_node *props = &buffer->props;
cairo_t *c = cairo_create(NULL);
if (!c) {
sway_log(SWAY_ERROR, "cairo_t allocation failed");
return;
cairo_surface_t *recorder = cairo_recording_surface_create(
CAIRO_CONTENT_COLOR_ALPHA, NULL);
cairo_t *c = cairo_create(recorder);
cairo_surface_destroy(recorder);
if (cairo_status(c) != CAIRO_STATUS_SUCCESS) {
sway_log(SWAY_ERROR, "cairo_t allocation failed: %s",
cairo_status_to_string(cairo_status(c)));
goto out;
}
cairo_set_antialias(c, CAIRO_ANTIALIAS_BEST);
get_text_size(c, config->font_description, &props->width, NULL,
&props->baseline, 1, props->pango_markup, "%s", buffer->text);
cairo_destroy(c);
wlr_scene_buffer_set_dest_size(buffer->buffer_node,
get_text_width(props), props->height);
out:
cairo_destroy(c);
}
struct sway_text_node *sway_text_node_create(struct wlr_scene_tree *parent,

View file

@ -25,50 +25,6 @@
#include "log.h"
#include "stringop.h"
static void handle_outputs_update(
struct wl_listener *listener, void *data) {
struct sway_container *con = wl_container_of(
listener, con, outputs_update);
struct wlr_scene_outputs_update_event *event = data;
struct wlr_foreign_toplevel_handle_v1 *toplevel = con->view->foreign_toplevel;
if (toplevel) {
struct wlr_foreign_toplevel_handle_v1_output *toplevel_output, *tmp;
wl_list_for_each_safe(toplevel_output, tmp, &toplevel->outputs, link) {
bool active = false;
for (size_t i = 0; i < event->size; i++) {
struct wlr_scene_output *scene_output = event->active[i];
if (scene_output->output == toplevel_output->output) {
active = true;
break;
}
}
if (!active) {
wlr_foreign_toplevel_handle_v1_output_leave(toplevel, toplevel_output->output);
}
}
for (size_t i = 0; i < event->size; i++) {
struct wlr_scene_output *scene_output = event->active[i];
wlr_foreign_toplevel_handle_v1_output_enter(toplevel, scene_output->output);
}
}
}
static void handle_destroy(
struct wl_listener *listener, void *data) {
struct sway_container *con = wl_container_of(
listener, con, output_handler_destroy);
container_begin_destroy(con);
}
static bool handle_point_accepts_input(
struct wlr_scene_buffer *buffer, double *x, double *y) {
return false;
}
static struct wlr_scene_rect *alloc_rect_node(struct wlr_scene_tree *parent,
bool *failed) {
if (*failed) {
@ -135,22 +91,6 @@ struct sway_container *container_create(struct sway_view *view) {
c->border.bottom = alloc_rect_node(c->border.tree, &failed);
c->border.left = alloc_rect_node(c->border.tree, &failed);
c->border.right = alloc_rect_node(c->border.tree, &failed);
c->output_handler = wlr_scene_buffer_create(c->border.tree, NULL);
if (!c->output_handler) {
sway_log(SWAY_ERROR, "Failed to allocate a scene node");
failed = true;
}
if (!failed) {
c->outputs_update.notify = handle_outputs_update;
wl_signal_add(&c->output_handler->events.outputs_update,
&c->outputs_update);
c->output_handler_destroy.notify = handle_destroy;
wl_signal_add(&c->output_handler->node.events.destroy,
&c->output_handler_destroy);
c->output_handler->point_accepts_input = handle_point_accepts_input;
}
}
if (!failed && !scene_descriptor_assign(&c->scene_tree->node,
@ -522,7 +462,6 @@ void container_destroy(struct sway_container *con) {
if (con->view && con->view->container == con) {
con->view->container = NULL;
wlr_scene_node_destroy(&con->output_handler->node);
if (con->view->destroying) {
view_destroy(con->view);
}
@ -564,11 +503,6 @@ void container_begin_destroy(struct sway_container *con) {
if (con->pending.parent || con->pending.workspace) {
container_detach(con);
}
if (con->view && con->view->container == con) {
wl_list_remove(&con->outputs_update.link);
wl_list_remove(&con->output_handler_destroy.link);
}
}
void container_reap_empty(struct sway_container *con) {

View file

@ -2,6 +2,8 @@
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <wlr/types/wlr_ext_workspace_v1.h>
#include "sway/tree/workspace.h"
#include "sway/ipc-server.h"
#include "sway/layers.h"
#include "sway/output.h"
@ -153,6 +155,7 @@ void output_enable(struct sway_output *output) {
output->enabled = true;
list_add(root->outputs, output);
sway_ext_workspace_output_enable(output);
restore_workspaces(output);
struct sway_workspace *ws = NULL;
@ -292,6 +295,7 @@ void output_disable(struct sway_output *output) {
destroy_layers(output);
output_evacuate(output);
sway_ext_workspace_output_disable(output);
}
void output_begin_destroy(struct sway_output *output) {
@ -333,6 +337,10 @@ void output_add_workspace(struct sway_output *output,
}
list_add(output->workspaces, workspace);
workspace->output = output;
if (workspace->output && workspace->output->ext_workspace_group) {
wlr_ext_workspace_handle_v1_set_group(workspace->ext_workspace,
workspace->output->ext_workspace_group);
}
node_set_dirty(&output->node);
node_set_dirty(&workspace->node);
}

View file

@ -38,6 +38,41 @@
#include "sway/xdg_decoration.h"
#include "stringop.h"
static void handle_outputs_update(
struct wl_listener *listener, void *data) {
struct sway_view *view = wl_container_of(listener, view, outputs_update);
struct wlr_scene_outputs_update_event *event = data;
struct wlr_foreign_toplevel_handle_v1 *toplevel = view->foreign_toplevel;
if (toplevel) {
struct wlr_foreign_toplevel_handle_v1_output *toplevel_output, *tmp;
wl_list_for_each_safe(toplevel_output, tmp, &toplevel->outputs, link) {
bool active = false;
for (size_t i = 0; i < event->size; i++) {
struct wlr_scene_output *scene_output = event->active[i];
if (scene_output->output == toplevel_output->output) {
active = true;
break;
}
}
if (!active) {
wlr_foreign_toplevel_handle_v1_output_leave(toplevel, toplevel_output->output);
}
}
for (size_t i = 0; i < event->size; i++) {
struct wlr_scene_output *scene_output = event->active[i];
wlr_foreign_toplevel_handle_v1_output_enter(toplevel, scene_output->output);
}
}
}
static bool handle_point_accepts_input(
struct wlr_scene_buffer *buffer, double *x, double *y) {
return false;
}
bool view_init(struct sway_view *view, enum sway_view_type type,
const struct sway_view_impl *impl) {
bool failed = false;
@ -51,12 +86,23 @@ bool view_init(struct sway_view *view, enum sway_view_type type,
goto err;
}
view->output_handler = wlr_scene_buffer_create(view->scene_tree, NULL);
if (!view->output_handler) {
sway_log(SWAY_ERROR, "Failed to allocate a scene node");
goto err;
}
view->image_capture_scene = wlr_scene_create();
if (view->image_capture_scene == NULL) {
goto err;
}
view->image_capture_scene->restack_xwayland_surfaces = false;
view->outputs_update.notify = handle_outputs_update;
wl_signal_add(&view->output_handler->events.outputs_update,
&view->outputs_update);
view->output_handler->point_accepts_input = handle_point_accepts_input;
view->type = type;
view->impl = impl;
view->executed_criteria = create_list();
@ -102,6 +148,7 @@ void view_begin_destroy(struct sway_view *view) {
return;
}
view->destroying = true;
wl_list_remove(&view->outputs_update.link);
if (!view->container) {
view_destroy(view);
@ -988,7 +1035,7 @@ void view_center_and_clip_surface(struct sway_view *view) {
bool clip_to_geometry = true;
if (container_is_floating(con)) {
if (container_is_floating(con) || con->pending.fullscreen_mode != FULLSCREEN_NONE) {
// We always center the current coordinates rather than the next, as the
// geometry immediately affects the currently active rendering.
int x = (int) fmax(0, (con->current.content_width - view->geometry.width) / 2);
@ -1229,8 +1276,10 @@ void view_save_buffer(struct sway_view *view) {
return;
}
// Enable and disable the saved surface tree like so to atomitaclly update
// the tree. This will prevent over damaging or other weirdness.
// Make sure the output handler is placed above the saved surface so we don't send
// spurious events to the foreign toplevel handler. Also, make the saved surface tree
// is disabled until it is ready to replace the real surface.
wlr_scene_node_place_below(&view->saved_surface_tree->node, &view->output_handler->node);
wlr_scene_node_set_enabled(&view->saved_surface_tree->node, false);
wlr_scene_node_for_each_buffer(&view->content_tree->node,

View file

@ -3,8 +3,12 @@
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <wlr/types/wlr_ext_workspace_v1.h>
#include "log.h"
#include "stringop.h"
#include "sway/desktop/transaction.h"
#include "sway/input/input-manager.h"
#include "sway/input/cursor.h"
#include "sway/input/seat.h"
@ -17,9 +21,125 @@
#include "sway/tree/view.h"
#include "sway/tree/workspace.h"
#include "list.h"
#include "log.h"
#include "util.h"
static const uint32_t WORKSPACE_CAPABILITIES =
EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE |
EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ASSIGN;
static const uint32_t GROUP_CAPABILITIES =
EXT_WORKSPACE_GROUP_HANDLE_V1_GROUP_CAPABILITIES_CREATE_WORKSPACE;
// Helper to find the output associated with a workspace group.
static struct sway_output *group_to_output(
struct wlr_ext_workspace_group_handle_v1 *group) {
for (int i = 0; i < root->outputs->length; i++) {
struct sway_output *output = root->outputs->items[i];
if (output->ext_workspace_group == group) {
return output;
}
}
abort(); // unreachable
}
// Callback for ext-workspace-v1 commit events.
static void handle_commit(struct wl_listener *listener, void *data) {
struct sway_server *server =
wl_container_of(listener, server, workspace_manager_v1_commit);
struct wlr_ext_workspace_v1_commit_event *event = data;
struct wlr_ext_workspace_v1_request *req, *tmp;
wl_list_for_each_safe(req, tmp, event->requests, link) {
switch (req->type) {
case WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE:
if (req->activate.workspace) {
workspace_switch(req->activate.workspace->data);
}
break;
case WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE:;
struct sway_output *output = group_to_output(req->create_workspace.group);
sway_assert(output, "NULL output given to create_workspace");
char *name;
if (req->create_workspace.name) {
if (workspace_by_name(req->create_workspace.name)) {
sway_log(SWAY_ERROR, "Refusing to create workspace with duplicate name.");
break; // Already exists.
}
name = strdup(req->create_workspace.name);
} else {
name = workspace_next_name(output->wlr_output->name);
}
struct sway_workspace *new_ws = workspace_create(output, name);
if (new_ws) {
workspace_switch(new_ws);
}
free(name);
break;
case WLR_EXT_WORKSPACE_V1_REQUEST_ASSIGN:;
if (!req->assign.workspace || !req->assign.group) break;
struct sway_workspace *ws = req->assign.workspace->data;
struct sway_output *new_output = group_to_output(req->assign.group);
struct sway_output *old_output = ws->output;
workspace_move_to_output(ws, new_output);
arrange_output(old_output);
arrange_output(new_output);
break;
case WLR_EXT_WORKSPACE_V1_REQUEST_DEACTIVATE:
case WLR_EXT_WORKSPACE_V1_REQUEST_REMOVE:
break; // No-op.
}
}
transaction_commit_dirty();
}
// Initialize ext-workspace. Must be called once at startup.
bool sway_ext_workspace_init(void) {
server.workspace_manager_v1 =
wlr_ext_workspace_manager_v1_create(server.wl_display, 1);
if (!server.workspace_manager_v1) {
sway_log(SWAY_ERROR, "Failed to create ext_workspace_manager_v1");
return false;
}
server.workspace_manager_v1_commit.notify = handle_commit;
wl_signal_add(&server.workspace_manager_v1->events.commit,
&server.workspace_manager_v1_commit);
return true;
}
// Must be called whenever an output is enabled.
void sway_ext_workspace_output_enable(struct sway_output *output) {
if (!output->wlr_output) {
return;
}
output->ext_workspace_group =
wlr_ext_workspace_group_handle_v1_create(
server.workspace_manager_v1, GROUP_CAPABILITIES);
if (!output->ext_workspace_group) {
sway_log(SWAY_ERROR, "Failed to create workspace group for output '%s'",
output->wlr_output->name);
return;
}
wlr_ext_workspace_group_handle_v1_output_enter(
output->ext_workspace_group, output->wlr_output);
}
// Must be called whenever an output is disabled.
void sway_ext_workspace_output_disable(struct sway_output *output) {
if (!output->ext_workspace_group) {
return;
}
wlr_ext_workspace_group_handle_v1_destroy(output->ext_workspace_group);
output->ext_workspace_group = NULL;
}
struct workspace_config *workspace_find_config(const char *ws_name) {
for (int i = 0; i < config->workspace_configs->length; ++i) {
struct workspace_config *wsc = config->workspace_configs->items[i];
@ -70,6 +190,16 @@ struct sway_workspace *workspace_create(struct sway_output *output,
sway_log(SWAY_ERROR, "Unable to allocate sway_workspace");
return NULL;
}
ws->ext_workspace = wlr_ext_workspace_handle_v1_create(
server.workspace_manager_v1, NULL, WORKSPACE_CAPABILITIES);
if (!ws->ext_workspace) {
sway_log(SWAY_ERROR, "Failed to create ext_workspace for '%s'", name);
free(ws);
return NULL;
}
ws->ext_workspace->data = ws;
node_init(&ws->node, N_WORKSPACE, ws);
bool failed = false;
@ -79,6 +209,7 @@ struct sway_workspace *workspace_create(struct sway_output *output,
if (failed) {
wlr_scene_node_destroy(&ws->layers.tiling->node);
wlr_scene_node_destroy(&ws->layers.fullscreen->node);
wlr_ext_workspace_handle_v1_destroy(ws->ext_workspace);
free(ws);
return NULL;
}
@ -127,6 +258,13 @@ struct sway_workspace *workspace_create(struct sway_output *output,
output_add_workspace(output, ws);
output_sort_workspaces(output);
wlr_ext_workspace_handle_v1_set_name(ws->ext_workspace, ws->name);
if (ws->output && ws->output->ext_workspace_group) {
wlr_ext_workspace_handle_v1_set_group(ws->ext_workspace,
ws->output->ext_workspace_group);
}
wlr_ext_workspace_handle_v1_set_active(ws->ext_workspace,
workspace_is_visible(ws));
ipc_event_workspace(NULL, ws, "init");
wl_signal_emit_mutable(&root->events.new_node, &ws->node);
@ -163,6 +301,9 @@ void workspace_begin_destroy(struct sway_workspace *workspace) {
ipc_event_workspace(NULL, workspace, "empty"); // intentional
wl_signal_emit_mutable(&workspace->node.events.destroy, &workspace->node);
wlr_ext_workspace_handle_v1_destroy(workspace->ext_workspace);
workspace->ext_workspace = NULL;
if (workspace->output) {
workspace_detach(workspace);
}
@ -687,6 +828,7 @@ void workspace_detect_urgent(struct sway_workspace *workspace) {
if (workspace->urgent != new_urgent) {
workspace->urgent = new_urgent;
wlr_ext_workspace_handle_v1_set_urgent(workspace->ext_workspace, workspace->urgent);
ipc_event_workspace(NULL, workspace, "urgent");
}
}
@ -985,3 +1127,35 @@ void workspace_squash(struct sway_workspace *workspace) {
i += container_squash(child);
}
}
void workspace_move_to_output(struct sway_workspace *workspace,
struct sway_output *output) {
if (workspace->output == output) {
return;
}
struct sway_output *old_output = workspace->output;
workspace_detach(workspace);
struct sway_workspace *new_output_old_ws =
output_get_active_workspace(output);
if (!sway_assert(new_output_old_ws, "Expected output to have a workspace")) {
return;
}
output_add_workspace(output, workspace);
// If moving the last workspace from the old output, create a new workspace
// on the old output
if (old_output->workspaces->length == 0) {
char *ws_name = workspace_next_name(old_output->wlr_output->name);
struct sway_workspace *ws = workspace_create(old_output, ws_name);
free(ws_name);
struct sway_seat *seat = input_manager_current_seat();
seat_set_raw_focus(seat, &ws->node);
}
workspace_consider_destroy(new_output_old_ws);
output_sort_workspaces(output);
workspace_output_raise_priority(workspace, old_output, output);
ipc_event_workspace(NULL, workspace, "move");
}