diff --git a/.builds/alpine.yml b/.builds/alpine.yml index fa693906d..55057fa15 100644 --- a/.builds/alpine.yml +++ b/.builds/alpine.yml @@ -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 diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml index 2c8ffcf45..162977e20 100644 --- a/.builds/archlinux.yml +++ b/.builds/archlinux.yml @@ -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 diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index a3df06e6d..8c457b5a3 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -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 diff --git a/common/pango.c b/common/pango.c index 2445b7227..781d7b312 100644 --- a/common/pango.c +++ b/common/pango.c @@ -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, diff --git a/include/sway/output.h b/include/sway/output.h index 787527ee7..ae2e50d36 100644 --- a/include/sway/output.h +++ b/include/sway/output.h @@ -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; diff --git a/include/sway/server.h b/include/sway/server.h index 978f05d0a..8c8114882 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -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, diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h index 12a53c843..d8780544d 100644 --- a/include/sway/tree/container.h +++ b/include/sway/tree/container.h @@ -91,10 +91,6 @@ struct sway_container { } border; struct wlr_scene_tree *content_tree; - struct wlr_scene_buffer *output_handler; - - struct wl_listener outputs_update; - struct wl_listener output_handler_destroy; struct sway_container_state current; struct sway_container_state pending; diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h index ae81c5bbf..26148a92a 100644 --- a/include/sway/tree/view.h +++ b/include/sway/tree/view.h @@ -69,6 +69,9 @@ struct sway_view { struct wlr_scene_tree *scene_tree; struct wlr_scene_tree *content_tree; struct wlr_scene_tree *saved_surface_tree; + struct wlr_scene_buffer *output_handler; + + struct wl_listener outputs_update; struct wlr_scene *image_capture_scene; struct wlr_ext_image_capture_source_v1 *image_capture_source; diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h index 27ed649fd..50bf02cd1 100644 --- a/include/sway/tree/workspace.h +++ b/include/sway/tree/workspace.h @@ -3,6 +3,7 @@ #include #include +#include #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 diff --git a/meson.build b/meson.build index 974a4071b..7a52ce23a 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'sway', 'c', - version: '1.12-dev', + version: '1.12-rc2', license: 'MIT', meson_version: '>=1.3', default_options: [ diff --git a/sway/commands/move.c b/sway/commands/move.c index 90e8585b4..43fce0d6d 100644 --- a/sway/commands/move.c +++ b/sway/commands/move.c @@ -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); diff --git a/sway/commands/rename.c b/sway/commands/rename.c index 0d36cc21e..63fac05cc 100644 --- a/sway/commands/rename.c +++ b/sway/commands/rename.c @@ -1,6 +1,7 @@ #include #include #include +#include #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"); diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c index 325a30226..d4e853dee 100644 --- a/sway/desktop/transaction.c +++ b/sway/desktop/transaction.c @@ -395,10 +395,6 @@ static void arrange_container(struct sway_container *con, // make sure it's enabled for viewing wlr_scene_node_set_enabled(&con->scene_tree->node, true); - if (con->output_handler) { - wlr_scene_buffer_set_dest_size(con->output_handler, width, height); - } - if (con->view) { int border_top = container_titlebar_height(); int border_width = con->current.border_thickness; @@ -456,6 +452,13 @@ static void arrange_container(struct sway_container *con, wlr_scene_node_reparent(&con->view->scene_tree->node, con->content_tree); wlr_scene_node_set_position(&con->view->scene_tree->node, border_left, border_top); + + // the output handler for the view wants to detect events for the entire + // container so give it negative coordinates to move it back over the + // decorations + wlr_scene_node_set_position(&con->view->output_handler->node, + -border_left, -border_top); + wlr_scene_buffer_set_dest_size(con->view->output_handler, width, height); } else { // make sure to disable the title bar if the parent is not managing it if (title_bar) { @@ -495,6 +498,11 @@ static void arrange_fullscreen(struct wlr_scene_tree *tree, // if we only care about the view, disable any decorations wlr_scene_node_set_enabled(&fs->scene_tree->node, false); + + // reconfigure the output handler (for foreign toplevel) to cover the + // view without container decorations + wlr_scene_node_set_position(&fs->view->output_handler->node, 0, 0); + wlr_scene_buffer_set_dest_size(fs->view->output_handler, width, height); } else { fs_node = &fs->scene_tree->node; arrange_container(fs, width, height, true, container_get_gaps(fs)); @@ -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); } diff --git a/sway/input/seat.c b/sway/input/seat.c index ab31b6746..ebdbd91ed 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -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) { diff --git a/sway/lock.c b/sway/lock.c index c8975c747..b1df65718 100644 --- a/sway/lock.c +++ b/sway/lock.c @@ -344,8 +344,12 @@ bool sway_session_lock_has_surface(struct sway_session_lock *lock, return false; } -void sway_session_lock_init(void) { +bool sway_session_lock_init(void) { server.session_lock.manager = wlr_session_lock_manager_v1_create(server.wl_display); + if (!server.session_lock.manager) { + sway_log(SWAY_ERROR, "Failed to create session lock manager"); + return false; + } server.session_lock.new_lock.notify = handle_session_lock; server.session_lock.manager_destroy.notify = handle_session_lock_destroy; @@ -353,4 +357,5 @@ void sway_session_lock_init(void) { &server.session_lock.new_lock); wl_signal_add(&server.session_lock.manager->events.destroy, &server.session_lock.manager_destroy); + return true; } diff --git a/sway/server.c b/sway/server.c index 5d446579f..8bdafb674 100644 --- a/sway/server.c +++ b/sway/server.c @@ -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 @@ -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 diff --git a/sway/sway_text_node.c b/sway/sway_text_node.c index 89ece91e2..c4fd3a260 100644 --- a/sway/sway_text_node.c +++ b/sway/sway_text_node.c @@ -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, diff --git a/sway/tree/container.c b/sway/tree/container.c index fd9abadc6..6880841bd 100644 --- a/sway/tree/container.c +++ b/sway/tree/container.c @@ -25,50 +25,6 @@ #include "log.h" #include "stringop.h" -static void handle_outputs_update( - struct wl_listener *listener, void *data) { - struct sway_container *con = wl_container_of( - listener, con, outputs_update); - struct wlr_scene_outputs_update_event *event = data; - - struct wlr_foreign_toplevel_handle_v1 *toplevel = con->view->foreign_toplevel; - if (toplevel) { - struct wlr_foreign_toplevel_handle_v1_output *toplevel_output, *tmp; - wl_list_for_each_safe(toplevel_output, tmp, &toplevel->outputs, link) { - bool active = false; - for (size_t i = 0; i < event->size; i++) { - struct wlr_scene_output *scene_output = event->active[i]; - if (scene_output->output == toplevel_output->output) { - active = true; - break; - } - } - - if (!active) { - wlr_foreign_toplevel_handle_v1_output_leave(toplevel, toplevel_output->output); - } - } - - for (size_t i = 0; i < event->size; i++) { - struct wlr_scene_output *scene_output = event->active[i]; - wlr_foreign_toplevel_handle_v1_output_enter(toplevel, scene_output->output); - } - } -} - -static void handle_destroy( - struct wl_listener *listener, void *data) { - struct sway_container *con = wl_container_of( - listener, con, output_handler_destroy); - - container_begin_destroy(con); -} - -static bool handle_point_accepts_input( - struct wlr_scene_buffer *buffer, double *x, double *y) { - return false; -} - static struct wlr_scene_rect *alloc_rect_node(struct wlr_scene_tree *parent, bool *failed) { if (*failed) { @@ -135,22 +91,6 @@ struct sway_container *container_create(struct sway_view *view) { c->border.bottom = alloc_rect_node(c->border.tree, &failed); c->border.left = alloc_rect_node(c->border.tree, &failed); c->border.right = alloc_rect_node(c->border.tree, &failed); - - c->output_handler = wlr_scene_buffer_create(c->border.tree, NULL); - if (!c->output_handler) { - sway_log(SWAY_ERROR, "Failed to allocate a scene node"); - failed = true; - } - - if (!failed) { - c->outputs_update.notify = handle_outputs_update; - wl_signal_add(&c->output_handler->events.outputs_update, - &c->outputs_update); - c->output_handler_destroy.notify = handle_destroy; - wl_signal_add(&c->output_handler->node.events.destroy, - &c->output_handler_destroy); - c->output_handler->point_accepts_input = handle_point_accepts_input; - } } if (!failed && !scene_descriptor_assign(&c->scene_tree->node, @@ -522,7 +462,6 @@ void container_destroy(struct sway_container *con) { if (con->view && con->view->container == con) { con->view->container = NULL; - wlr_scene_node_destroy(&con->output_handler->node); if (con->view->destroying) { view_destroy(con->view); } @@ -564,11 +503,6 @@ void container_begin_destroy(struct sway_container *con) { if (con->pending.parent || con->pending.workspace) { container_detach(con); } - - if (con->view && con->view->container == con) { - wl_list_remove(&con->outputs_update.link); - wl_list_remove(&con->output_handler_destroy.link); - } } void container_reap_empty(struct sway_container *con) { diff --git a/sway/tree/output.c b/sway/tree/output.c index 90ec9331d..c401d0f1d 100644 --- a/sway/tree/output.c +++ b/sway/tree/output.c @@ -2,6 +2,8 @@ #include #include #include +#include +#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); } diff --git a/sway/tree/view.c b/sway/tree/view.c index eab2a5e2b..83b4972b1 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -38,6 +38,41 @@ #include "sway/xdg_decoration.h" #include "stringop.h" +static void handle_outputs_update( + struct wl_listener *listener, void *data) { + struct sway_view *view = wl_container_of(listener, view, outputs_update); + struct wlr_scene_outputs_update_event *event = data; + + struct wlr_foreign_toplevel_handle_v1 *toplevel = view->foreign_toplevel; + if (toplevel) { + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output, *tmp; + wl_list_for_each_safe(toplevel_output, tmp, &toplevel->outputs, link) { + bool active = false; + for (size_t i = 0; i < event->size; i++) { + struct wlr_scene_output *scene_output = event->active[i]; + if (scene_output->output == toplevel_output->output) { + active = true; + break; + } + } + + if (!active) { + wlr_foreign_toplevel_handle_v1_output_leave(toplevel, toplevel_output->output); + } + } + + for (size_t i = 0; i < event->size; i++) { + struct wlr_scene_output *scene_output = event->active[i]; + wlr_foreign_toplevel_handle_v1_output_enter(toplevel, scene_output->output); + } + } +} + +static bool handle_point_accepts_input( + struct wlr_scene_buffer *buffer, double *x, double *y) { + return false; +} + bool view_init(struct sway_view *view, enum sway_view_type type, const struct sway_view_impl *impl) { bool failed = false; @@ -51,12 +86,23 @@ bool view_init(struct sway_view *view, enum sway_view_type type, goto err; } + view->output_handler = wlr_scene_buffer_create(view->scene_tree, NULL); + if (!view->output_handler) { + sway_log(SWAY_ERROR, "Failed to allocate a scene node"); + goto err; + } + view->image_capture_scene = wlr_scene_create(); if (view->image_capture_scene == NULL) { goto err; } view->image_capture_scene->restack_xwayland_surfaces = false; + view->outputs_update.notify = handle_outputs_update; + wl_signal_add(&view->output_handler->events.outputs_update, + &view->outputs_update); + view->output_handler->point_accepts_input = handle_point_accepts_input; + view->type = type; view->impl = impl; view->executed_criteria = create_list(); @@ -102,6 +148,7 @@ void view_begin_destroy(struct sway_view *view) { return; } view->destroying = true; + wl_list_remove(&view->outputs_update.link); if (!view->container) { view_destroy(view); @@ -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, diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c index 49a830e7f..23311a456 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c @@ -3,8 +3,12 @@ #include #include #include +#include #include +#include +#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"); +}