Merge branch 'master' into save-previous

This commit is contained in:
Andrew Laucius 2026-03-26 12:54:29 -04:00 committed by GitHub
commit 0c45122862
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 373 additions and 123 deletions

View file

@ -84,28 +84,47 @@ 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, ...) {
if (width) {
*width = 0;
}
if (height) {
*height = 0;
}
if (baseline) {
*baseline = 0;
}
va_list args;
va_start(args, fmt);
char *buf = vformat_str(fmt, args);
va_end(args);
if (buf == NULL) {
sway_log(SWAY_ERROR, "Failed to format string");
return;
}
PangoLayout *layout = get_pango_layout(cairo, desc, buf, scale, markup);
pango_cairo_update_layout(cairo, layout);
cairo_status_t status = cairo_status(cairo);
if (status != CAIRO_STATUS_SUCCESS) {
sway_log(SWAY_ERROR, "pango_cairo_update_layout() failed: %s",
cairo_status_to_string(status));
goto out;
}
pango_layout_get_pixel_size(layout, width, height);
if (baseline) {
*baseline = pango_layout_get_baseline(layout) / PANGO_SCALE;
}
g_object_unref(layout);
out:
g_object_unref(layout);
free(buf);
}
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);
@ -115,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,
@ -125,6 +143,7 @@ void render_text(cairo_t *cairo, const PangoFontDescription *desc,
char *buf = vformat_str(fmt, args);
va_end(args);
if (buf == NULL) {
sway_log(SWAY_ERROR, "Failed to format string");
return;
}
@ -133,9 +152,18 @@ void render_text(cairo_t *cairo, const PangoFontDescription *desc,
cairo_get_font_options(cairo, fo);
pango_cairo_context_set_font_options(pango_layout_get_context(layout), fo);
cairo_font_options_destroy(fo);
pango_cairo_update_layout(cairo, layout);
pango_cairo_show_layout(cairo, layout);
g_object_unref(layout);
pango_cairo_update_layout(cairo, layout);
cairo_status_t status = cairo_status(cairo);
if (status != CAIRO_STATUS_SUCCESS) {
sway_log(SWAY_ERROR, "pango_cairo_update_layout() failed: %s",
cairo_status_to_string(status));
goto out;
}
pango_cairo_show_layout(cairo, layout);
out:
g_object_unref(layout);
free(buf);
}

View file

@ -33,6 +33,7 @@ struct sway_layer_popup {
struct wl_listener destroy;
struct wl_listener new_popup;
struct wl_listener commit;
struct wl_listener reposition;
};
struct sway_output;

View file

@ -65,6 +65,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

View file

@ -93,8 +93,7 @@ struct sway_container {
struct wlr_scene_tree *content_tree;
struct wlr_scene_buffer *output_handler;
struct wl_listener output_enter;
struct wl_listener output_leave;
struct wl_listener outputs_update;
struct wl_listener output_handler_destroy;
struct sway_container_state current;

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"
@ -52,6 +53,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);
@ -158,4 +160,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);
void 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.13-dev',
license: 'MIT',
meson_version: '>=1.3',
default_options: [
@ -39,14 +39,14 @@ if is_freebsd
endif
# Execute the wlroots subproject, if any
wlroots_version = ['>=0.20.0', '<0.21.0']
wlroots_version = ['>=0.21.0', '<0.22.0']
subproject(
'wlroots',
default_options: ['examples=false'],
required: false,
version: wlroots_version,
)
wlroots = dependency('wlroots-0.20', version: wlroots_version, fallback: 'wlroots')
wlroots = dependency('wlroots-0.21', version: wlroots_version, fallback: 'wlroots')
wlroots_features = {
'xwayland': false,
'libinput_backend': false,
@ -110,7 +110,7 @@ conf_data.set10('HAVE_LIBSYSTEMD', sdbus.found() and sdbus.name() == 'libsystemd
conf_data.set10('HAVE_LIBELOGIND', sdbus.found() and sdbus.name() == 'libelogind')
conf_data.set10('HAVE_BASU', sdbus.found() and sdbus.name() == 'basu')
conf_data.set10('HAVE_TRAY', have_tray)
foreach sym : ['LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM', 'LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY']
foreach sym : ['LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM', 'LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY', 'LIBINPUT_SWITCH_KEYPAD_SLIDE']
conf_data.set10('HAVE_' + sym, cc.has_header_symbol('libinput.h', sym, dependencies: libinput))
endforeach

View file

@ -25,7 +25,7 @@
THIS SOFTWARE.
</copyright>
<interface name="zwlr_layer_shell_v1" version="4">
<interface name="zwlr_layer_shell_v1" version="5">
<description summary="create surfaces that are layers of the desktop">
Clients can use this interface to assign the surface_layer role to
wl_surfaces. Such surfaces are assigned to a "layer" of the output and
@ -100,7 +100,7 @@
</request>
</interface>
<interface name="zwlr_layer_surface_v1" version="4">
<interface name="zwlr_layer_surface_v1" version="5">
<description summary="layer metadata interface">
An interface that may be implemented by a wl_surface, for surfaces that
are designed to be rendered as a layer of a stacked desktop-like
@ -367,6 +367,7 @@
<entry name="invalid_size" value="1" summary="size is invalid"/>
<entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
<entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/>
<entry name="invalid_exclusive_edge" value="4" summary="exclusive edge is invalid given the surface anchors"/>
</enum>
<enum name="anchor" bitfield="true">
@ -386,5 +387,21 @@
</description>
<arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
</request>
<!-- Version 5 additions -->
<request name="set_exclusive_edge" since="5">
<description summary="set the edge the exclusive zone will be applied to">
Requests an edge for the exclusive zone to apply. The exclusive
edge will be automatically deduced from anchor points when possible,
but when the surface is anchored to a corner, it will be necessary
to set it explicitly to disambiguate, as it is not possible to deduce
which one of the two corner edges should be used.
The edge must be one the surface is anchored to, otherwise the
invalid_exclusive_edge protocol error will be raised.
</description>
<arg name="edge" type="uint" enum="anchor"/>
</request>
</interface>
</protocol>

View file

@ -50,7 +50,7 @@
<request name="get_output_power">
<description summary="get a power management for an output">
Create a output power management mode control that can be used to
Create an output power management mode control that can be used to
adjust the power management mode for a given output.
</description>
<arg name="id" type="new_id" interface="zwlr_output_power_v1"/>
@ -79,7 +79,7 @@
</enum>
<enum name="error">
<entry name="invalid_mode" value="1" summary="inexistent power save mode"/>
<entry name="invalid_mode" value="1" summary="nonexistent power save mode"/>
</enum>
<request name="set_mode">

View file

@ -543,6 +543,10 @@ struct cmd_results *cmd_bind_or_unbind_switch(int argc, char **argv,
binding->type = WLR_SWITCH_TYPE_TABLET_MODE;
} else if (strcmp(split->items[0], "lid") == 0) {
binding->type = WLR_SWITCH_TYPE_LID;
#if HAVE_LIBINPUT_SWITCH_KEYPAD_SLIDE
} else if (strcmp(split->items[0], "keypad_slide") == 0) {
binding->type = WLR_SWITCH_TYPE_KEYPAD_SLIDE;
#endif
} else {
free_switch_binding(binding);
return cmd_results_new(CMD_FAILURE,

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

@ -555,8 +555,10 @@ static void queue_output_config(struct output_config *oc,
}
bool hdr = oc && oc->hdr == 1;
if (hdr && oc->color_transform != NULL) {
sway_log(SWAY_ERROR, "Cannot HDR on output %s: output has an ICC profile set", wlr_output->name);
bool color_profile = oc && (oc->color_transform != NULL
|| oc->color_profile == COLOR_PROFILE_TRANSFORM_WITH_DEVICE_PRIMARIES);
if (hdr && color_profile) {
sway_log(SWAY_ERROR, "Cannot use HDR on output %s: output has a color profile set", wlr_output->name);
hdr = false;
}
set_hdr(wlr_output, pending, hdr);

View file

@ -196,6 +196,10 @@ static bool criteria_matches_view(struct criteria *criteria,
struct sway_container *focus = seat_get_focused_container(seat);
struct sway_view *focused = focus ? focus->view : NULL;
if (!view->container) {
return false;
}
if (criteria->title) {
const char *title = view_get_title(view);
if (!title) {
@ -204,7 +208,7 @@ static bool criteria_matches_view(struct criteria *criteria,
switch (criteria->title->match_type) {
case PATTERN_FOCUSED:
if (focused && lenient_strcmp(title, view_get_title(focused))) {
if (!focused || lenient_strcmp(title, view_get_title(focused))) {
return false;
}
break;
@ -224,7 +228,7 @@ static bool criteria_matches_view(struct criteria *criteria,
switch (criteria->shell->match_type) {
case PATTERN_FOCUSED:
if (focused && strcmp(shell, view_get_shell(focused))) {
if (!focused || strcmp(shell, view_get_shell(focused))) {
return false;
}
break;
@ -244,7 +248,7 @@ static bool criteria_matches_view(struct criteria *criteria,
switch (criteria->app_id->match_type) {
case PATTERN_FOCUSED:
if (focused && lenient_strcmp(app_id, view_get_app_id(focused))) {
if (!focused || lenient_strcmp(app_id, view_get_app_id(focused))) {
return false;
}
break;
@ -264,7 +268,7 @@ static bool criteria_matches_view(struct criteria *criteria,
switch (criteria->sandbox_engine->match_type) {
case PATTERN_FOCUSED:
if (focused && lenient_strcmp(sandbox_engine, view_get_sandbox_engine(focused))) {
if (!focused || lenient_strcmp(sandbox_engine, view_get_sandbox_engine(focused))) {
return false;
}
break;
@ -284,7 +288,7 @@ static bool criteria_matches_view(struct criteria *criteria,
switch (criteria->sandbox_app_id->match_type) {
case PATTERN_FOCUSED:
if (focused && lenient_strcmp(sandbox_app_id, view_get_sandbox_app_id(focused))) {
if (!focused || lenient_strcmp(sandbox_app_id, view_get_sandbox_app_id(focused))) {
return false;
}
break;
@ -304,7 +308,7 @@ static bool criteria_matches_view(struct criteria *criteria,
switch (criteria->sandbox_instance_id->match_type) {
case PATTERN_FOCUSED:
if (focused && lenient_strcmp(sandbox_instance_id, view_get_sandbox_instance_id(focused))) {
if (!focused || lenient_strcmp(sandbox_instance_id, view_get_sandbox_instance_id(focused))) {
return false;
}
break;
@ -324,7 +328,7 @@ static bool criteria_matches_view(struct criteria *criteria,
switch (criteria->tag->match_type) {
case PATTERN_FOCUSED:
if (focused && lenient_strcmp(tag, view_get_tag(focused))) {
if (!focused || lenient_strcmp(tag, view_get_tag(focused))) {
return false;
}
break;
@ -356,7 +360,7 @@ static bool criteria_matches_view(struct criteria *criteria,
switch (criteria->class->match_type) {
case PATTERN_FOCUSED:
if (focused && lenient_strcmp(class, view_get_class(focused))) {
if (!focused || lenient_strcmp(class, view_get_class(focused))) {
return false;
}
break;
@ -376,7 +380,7 @@ static bool criteria_matches_view(struct criteria *criteria,
switch (criteria->instance->match_type) {
case PATTERN_FOCUSED:
if (focused && lenient_strcmp(instance, view_get_instance(focused))) {
if (!focused || lenient_strcmp(instance, view_get_instance(focused))) {
return false;
}
break;
@ -396,7 +400,7 @@ static bool criteria_matches_view(struct criteria *criteria,
switch (criteria->window_role->match_type) {
case PATTERN_FOCUSED:
if (focused && lenient_strcmp(window_role, view_get_window_role(focused))) {
if (!focused || lenient_strcmp(window_role, view_get_window_role(focused))) {
return false;
}
break;
@ -454,7 +458,7 @@ static bool criteria_matches_view(struct criteria *criteria,
switch (criteria->workspace->match_type) {
case PATTERN_FOCUSED:
if (focused &&
if (!focused ||
strcmp(ws->name, focused->container->pending.workspace->name)) {
return false;
}

View file

@ -321,6 +321,7 @@ static void popup_handle_destroy(struct wl_listener *listener, void *data) {
wl_list_remove(&popup->destroy.link);
wl_list_remove(&popup->new_popup.link);
wl_list_remove(&popup->commit.link);
wl_list_remove(&popup->reposition.link);
free(popup);
}
@ -356,6 +357,11 @@ static void popup_handle_commit(struct wl_listener *listener, void *data) {
}
}
static void popup_handle_reposition(struct wl_listener *listener, void *data) {
struct sway_layer_popup *popup = wl_container_of(listener, popup, reposition);
popup_unconstrain(popup);
}
static void popup_handle_new_popup(struct wl_listener *listener, void *data);
static struct sway_layer_popup *create_popup(struct wlr_xdg_popup *wlr_popup,
@ -376,11 +382,13 @@ static struct sway_layer_popup *create_popup(struct wlr_xdg_popup *wlr_popup,
}
popup->destroy.notify = popup_handle_destroy;
wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy);
wl_signal_add(&wlr_popup->events.destroy, &popup->destroy);
popup->new_popup.notify = popup_handle_new_popup;
wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup);
popup->commit.notify = popup_handle_commit;
wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit);
popup->reposition.notify = popup_handle_reposition;
wl_signal_add(&wlr_popup->events.reposition, &popup->reposition);
return popup;
}

View file

@ -648,15 +648,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

@ -381,14 +381,16 @@ int main(int argc, char **argv) {
struct swaynag_instance nag_gpu = (struct swaynag_instance){
.args = "--type error "
"--message 'Proprietary GPU drivers are not supported by sway. Do not report issues.' ",
.detailed = false,
"--message 'Proprietary GPU drivers are not supported by sway. Do not report issues.' "
"--detailed-message",
.detailed = true,
};
if (unsupported_gpu_detected && !allow_unsupported_gpu) {
if (!swaynag_spawn(config->swaynag_command, &nag_gpu)) {
sway_log(SWAY_ERROR, "Unable to start swaynag");
}
swaynag_log(config->swaynag_command, &nag_gpu,
"To remove this message, launch sway with --unsupported-gpu "
"or set the environment variable SWAY_UNSUPPORTED_GPU=true.");
swaynag_show(&nag_gpu);
}
server_run(&server);

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>
@ -74,7 +75,7 @@
#endif
#define SWAY_XDG_SHELL_VERSION 5
#define SWAY_LAYER_SHELL_VERSION 4
#define SWAY_LAYER_SHELL_VERSION 5
#define SWAY_FOREIGN_TOPLEVEL_LIST_VERSION 1
#define SWAY_PRESENTATION_VERSION 2
@ -272,7 +273,7 @@ bool server_init(struct sway_server *server) {
if (wlr_renderer_get_texture_formats(server->renderer, WLR_BUFFER_CAP_DMABUF) != NULL) {
server->linux_dmabuf_v1 = wlr_linux_dmabuf_v1_create_with_renderer(
server->wl_display, 4, server->renderer);
server->wl_display, 5, server->renderer);
}
if (wlr_renderer_get_drm_fd(server->renderer) >= 0 &&
server->renderer->features.timeline &&
@ -377,6 +378,7 @@ bool server_init(struct sway_server *server) {
wlr_foreign_toplevel_manager_v1_create(server->wl_display);
sway_session_lock_init();
sway_ext_workspace_init();
#if WLR_HAS_DRM_BACKEND
server->drm_lease_manager=
@ -440,7 +442,7 @@ bool server_init(struct sway_server *server) {
&server->xdg_toplevel_tag_manager_v1_set_tag);
struct wlr_cursor_shape_manager_v1 *cursor_shape_manager =
wlr_cursor_shape_manager_v1_create(server->wl_display, 1);
wlr_cursor_shape_manager_v1_create(server->wl_display, 2);
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);
@ -543,6 +545,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

@ -506,11 +506,12 @@ runtime.
*bindswitch* [--locked] [--no-warn] [--reload] <switch>:<state> <command>
Binds <switch> to execute the sway command _command_ on state changes.
Supported switches are _lid_ (laptop lid) and _tablet_ (tablet mode)
switches. Valid values for _state_ are _on_, _off_ and _toggle_. These
switches are on when the device lid is shut and when tablet mode is active
respectively. _toggle_ is also supported to run a command both when the
switch is toggled on or off.
Supported switches are _lid_ (laptop lid), _tablet_ (tablet mode) and
_keypad_slide_ (whether the device keypad is exposed or not) switches. Valid
values for _state_ are _on_, _off_ and _toggle_. These switches are on when
the device lid is shut, when tablet mode is active and when the keypad is
exposed respectively. _toggle_ is also supported to run a command both when
the switch is toggled on or off.
Unless the flag _--locked_ is set, the command will not be run when a
screen locking program is active. If there is a matching binding with

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,27 +25,34 @@
#include "log.h"
#include "stringop.h"
static void handle_output_enter(
static void handle_outputs_update(
struct wl_listener *listener, void *data) {
struct sway_container *con = wl_container_of(
listener, con, output_enter);
struct wlr_scene_output *output = data;
listener, con, outputs_update);
struct wlr_scene_outputs_update_event *event = data;
if (con->view->foreign_toplevel) {
wlr_foreign_toplevel_handle_v1_output_enter(
con->view->foreign_toplevel, output->output);
}
}
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;
}
}
static void handle_output_leave(
struct wl_listener *listener, void *data) {
struct sway_container *con = wl_container_of(
listener, con, output_leave);
struct wlr_scene_output *output = data;
if (!active) {
wlr_foreign_toplevel_handle_v1_output_leave(toplevel, toplevel_output->output);
}
}
if (con->view->foreign_toplevel) {
wlr_foreign_toplevel_handle_v1_output_leave(
con->view->foreign_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);
}
}
}
@ -136,12 +143,9 @@ struct sway_container *container_create(struct sway_view *view) {
}
if (!failed) {
c->output_enter.notify = handle_output_enter;
wl_signal_add(&c->output_handler->events.output_enter,
&c->output_enter);
c->output_leave.notify = handle_output_leave;
wl_signal_add(&c->output_handler->events.output_leave,
&c->output_leave);
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);
@ -562,8 +566,7 @@ void container_begin_destroy(struct sway_container *con) {
}
if (con->view && con->view->container == con) {
wl_list_remove(&con->output_enter.link);
wl_list_remove(&con->output_leave.link);
wl_list_remove(&con->outputs_update.link);
wl_list_remove(&con->output_handler_destroy.link);
}
}

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"
@ -158,6 +160,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;
@ -297,6 +300,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) {
@ -338,6 +342,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

@ -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,124 @@
#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.
void 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;
}
server.workspace_manager_v1_commit.notify = handle_commit;
wl_signal_add(&server.workspace_manager_v1->events.commit,
&server.workspace_manager_v1_commit);
}
// 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 +189,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 +208,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;
}
@ -128,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);
@ -165,6 +302,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);
}
@ -204,8 +344,10 @@ static bool workspace_valid_on_output(const char *output_name,
}
for (int i = 0; i < wsc->outputs->length; i++) {
if (output_match_name_or_id(output, wsc->outputs->items[i])) {
return true;
struct sway_output *ws_output =
output_by_name_or_id(wsc->outputs->items[i]);
if (ws_output) {
return ws_output == output;
}
}
@ -320,10 +462,14 @@ char *workspace_next_name(const char *output_name) {
}
bool found = false;
for (int j = 0; j < wsc->outputs->length; ++j) {
if (output_match_name_or_id(output, wsc->outputs->items[j])) {
found = true;
free(target);
target = strdup(wsc->workspace);
struct sway_output *ws_output =
output_by_name_or_id(wsc->outputs->items[j]);
if (ws_output) {
if (ws_output == output) {
found = true;
free(target);
target = strdup(wsc->workspace);
}
break;
}
}
@ -681,6 +827,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 +1132,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");
}