diff --git a/include/sway/commands.h b/include/sway/commands.h index b972489ee..e2565f622 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -110,6 +110,10 @@ sway_cmd cmd_bindcode; sway_cmd cmd_bindswitch; sway_cmd cmd_bindsym; sway_cmd cmd_border; +sway_cmd cmd_border_images_focused; +sway_cmd cmd_border_images_focused_inactive; +sway_cmd cmd_border_images_unfocused; +sway_cmd cmd_border_images_urgent; sway_cmd cmd_client_noop; sway_cmd cmd_client_focused; sway_cmd cmd_client_focused_inactive; diff --git a/include/sway/config.h b/include/sway/config.h index a2ef0f317..257b2a224 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -1,9 +1,11 @@ #ifndef _SWAY_CONFIG_H #define _SWAY_CONFIG_H +#include #include #include #include #include +#include #include #include #include @@ -397,6 +399,11 @@ struct border_colors { float child_border[4]; }; +struct border_textures { + cairo_surface_t *image_surface; + struct wlr_texture *texture; +}; + enum edge_border_types { E_NONE, /**< Don't hide edge borders */ E_VERTICAL, /**< hide vertical edge borders */ @@ -506,8 +513,8 @@ struct sway_config { enum focus_follows_mouse_mode focus_follows_mouse; enum mouse_warping_mode mouse_warping; enum focus_wrapping_mode focus_wrapping; - bool ttyaccess; bool active; + bool ttyaccess; bool failed; bool reloading; bool reading; @@ -548,6 +555,14 @@ struct sway_config { float background[4]; } border_colors; + // border textures + struct { + struct border_textures focused; + struct border_textures focused_inactive; + struct border_textures unfocused; + struct border_textures urgent; + } border_textures; + // floating view int32_t floating_maximum_width; int32_t floating_maximum_height; diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h index b3d93a813..44f445001 100644 --- a/include/sway/tree/workspace.h +++ b/include/sway/tree/workspace.h @@ -92,6 +92,9 @@ struct sway_output *workspace_output_get_highest_available( void workspace_detect_urgent(struct sway_workspace *workspace); +void workspace_for_each_tiling_container(struct sway_workspace *ws, + void (*f)(struct sway_container *con, void *data), void *data); + void workspace_for_each_container(struct sway_workspace *ws, void (*f)(struct sway_container *con, void *data), void *data); diff --git a/sway/commands.c b/sway/commands.c index fbc79e045..d1f701f9c 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -44,11 +44,15 @@ struct cmd_results *checkarg(int argc, const char *name, enum expected_args type /* Keep alphabetized */ static const struct cmd_handler handlers[] = { { "assign", cmd_assign }, - { "ttyaccess", cmd_ttyaccess }, + { "ttyaccess", cmd_ttyaccess }, { "bar", cmd_bar }, { "bindcode", cmd_bindcode }, { "bindswitch", cmd_bindswitch }, { "bindsym", cmd_bindsym }, + { "border_images.focused", cmd_border_images_focused }, + { "border_images.focused_inactive", cmd_border_images_focused_inactive }, + { "border_images.unfocused", cmd_border_images_unfocused }, + { "border_images.urgent", cmd_border_images_urgent }, { "client.background", cmd_client_noop }, { "client.focused", cmd_client_focused }, { "client.focused_inactive", cmd_client_focused_inactive }, @@ -111,6 +115,10 @@ static const struct cmd_handler config_handlers[] = { /* Runtime-only commands. Keep alphabetized */ static const struct cmd_handler command_handlers[] = { { "border", cmd_border }, + { "border_images.focused", cmd_border_images_focused }, + { "border_images.focused_inactive", cmd_border_images_focused_inactive }, + { "border_images.unfocused", cmd_border_images_unfocused }, + { "border_images.urgent", cmd_border_images_urgent }, { "create_output", cmd_create_output }, { "exit", cmd_exit }, { "floating", cmd_floating }, diff --git a/sway/commands/border_images.c b/sway/commands/border_images.c new file mode 100644 index 000000000..7d7cf36a9 --- /dev/null +++ b/sway/commands/border_images.c @@ -0,0 +1,76 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include "cairo.h" +#include "log.h" +#include "stringop.h" +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/output.h" + +static void apply_border_textures_for_class(struct border_textures *class) { + struct sway_output *output = root->outputs->items[0]; + struct wlr_renderer *renderer = wlr_backend_get_renderer( + output->wlr_output->backend); + class->texture = wlr_texture_from_pixels(renderer, DRM_FORMAT_ARGB8888, + cairo_image_surface_get_width(class->image_surface) * 4, + cairo_image_surface_get_width(class->image_surface), + cairo_image_surface_get_height(class->image_surface), + cairo_image_surface_get_data(class->image_surface)); +} + +static struct cmd_results *handle_command(int argc, char **argv, char *cmd_name, + struct border_textures *class) { + if (!config->active) return cmd_results_new(CMD_DEFER, NULL); + + struct cmd_results *error = NULL; + if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) { + return error; + } + + char *src = strdup(argv[0]); + if (!expand_path(&src)) { + error = cmd_results_new(CMD_INVALID, "Invalid syntax (%s)", src); + free(src); + src = NULL; + return error; + } + if (!src) { + sway_log(SWAY_ERROR, "Failed to allocate expanded path"); + return cmd_results_new(CMD_FAILURE, "Unable to allocate resource"); + } + + bool can_access = access(src, F_OK) != -1; + if (!can_access) { + sway_log_errno(SWAY_ERROR, "Unable to access border images file '%s'", + src); + config_add_swaynag_warning("Unable to access border images file '%s'", + src); + } + + class->image_surface = cairo_image_surface_create_from_png(src); + apply_border_textures_for_class(class); + + return cmd_results_new(CMD_SUCCESS, NULL); +} + +struct cmd_results *cmd_border_images_focused(int argc, char **argv) { + return handle_command(argc, argv, "border_images.focused", + &config->border_textures.focused); +} + +struct cmd_results *cmd_border_images_focused_inactive(int argc, char **argv) { + return handle_command(argc, argv, "border_images.focused_inactive", + &config->border_textures.focused_inactive); +} + +struct cmd_results *cmd_border_images_unfocused(int argc, char **argv) { + return handle_command(argc, argv, "border_images.unfocused", + &config->border_textures.unfocused); +} + +struct cmd_results *cmd_border_images_urgent(int argc, char **argv) { + return handle_command(argc, argv, "border_images.urgent", + &config->border_textures.urgent); +} diff --git a/sway/desktop/render.c b/sway/desktop/render.c index a5bd8a5fc..111fda31e 100644 --- a/sway/desktop/render.c +++ b/sway/desktop/render.c @@ -349,6 +349,7 @@ static void render_saved_view(struct sway_view *view, // https://github.com/swaywm/sway/pull/4465#discussion_r321082059 } + /** * Render a view's surface and left/bottom/right borders. */ @@ -428,8 +429,8 @@ static void render_view(struct sway_output *output, pixman_region32_t *damage, static void render_titlebar(struct sway_output *output, pixman_region32_t *output_damage, struct sway_container *con, int x, int y, int width, - struct border_colors *colors, struct wlr_texture *title_texture, - struct wlr_texture *marks_texture) { + struct border_colors *colors, + struct wlr_texture *title_texture, struct wlr_texture *marks_texture) { struct wlr_box box; float color[4]; float output_scale = output->wlr_output->scale; @@ -711,6 +712,211 @@ struct parent_data { struct sway_container *active_child; }; +/** + * Render a single border texture. + */ +static void render_border_texture(struct sway_output *output, + pixman_region32_t *damage, struct wlr_box box, struct wlr_fbox src_box, + struct wlr_texture *texture, float alpha) { + struct wlr_output *wlr_output = output->wlr_output; + + box.x -= output->lx; + box.y -= output->ly; + scale_box(&box, wlr_output->scale); + + float matrix[9]; + wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0.0, + output->wlr_output->transform_matrix); + + pixman_region32_t texture_damage; + pixman_region32_init_rect(&texture_damage, box.x, box.y, box.width, box.height); + wlr_output_damage_add(output->damage, &texture_damage); + render_texture(wlr_output, damage, texture, &src_box, &box, matrix, alpha); +} + +/** + * Render all border textures based on a given wlr_box. + */ +static void render_border_textures(struct sway_output *output, + pixman_region32_t *damage, struct wlr_box *full_box, + struct wlr_texture *texture, float alpha) { + if (!texture) { + return; + } + + struct wlr_box box; + struct wlr_fbox src_box; + int tw = texture->width; + int th = texture->height; + + // Top left corner + src_box.x = 0; + src_box.y = 0; + src_box.width = tw / 2; + src_box.height = th / 2; + box.x = full_box->x - src_box.width; + box.y = full_box->y - src_box.height; + box.width = src_box.width; + box.height = src_box.height; + render_border_texture(output, damage, box, src_box, texture, alpha); + + // Top edge + src_box.x = tw / 2; + src_box.y = 0; + src_box.width = 1; + src_box.height = th / 2; + box.x = full_box->x; + box.y = full_box->y - src_box.height; + box.width = full_box->width; + box.height = src_box.height; + render_border_texture(output, damage, box, src_box, texture, alpha); + + // Top right corner + src_box.x = tw / 2 + 1; + src_box.y = 0; + src_box.width = tw / 2; + src_box.height = th / 2; + box.x = full_box->x + full_box->width; + box.y = full_box->y - src_box.height; + box.width = src_box.width; + box.height = src_box.height; + render_border_texture(output, damage, box, src_box, texture, alpha); + + // Right edge + src_box.x = tw / 2 + 1; + src_box.y = th / 2; + src_box.width = tw / 2; + src_box.height = 1; + box.x = full_box->x + full_box->width; + box.y = full_box->y; + box.width = src_box.width; + box.height = full_box->height; + render_border_texture(output, damage, box, src_box, texture, alpha); + + // Bottom right corner + src_box.x = tw / 2 + 1; + src_box.y = th / 2 + 1; + src_box.width = tw / 2; + src_box.height = th / 2; + box.x = full_box->x + full_box->width; + box.y = full_box->y + full_box->height; + box.width = src_box.width; + box.height = src_box.height; + render_border_texture(output, damage, box, src_box, texture, alpha); + + // Bottom edge + src_box.x = tw / 2; + src_box.y = th / 2 + 1; + src_box.width = 1; + src_box.height = th / 2; + box.x = full_box->x; + box.y = full_box->y + full_box->height; + box.width = full_box->width; + box.height = src_box.height; + render_border_texture(output, damage, box, src_box, texture, alpha); + + // Bottom left corner + src_box.x = 0; + src_box.y = th / 2 + 1; + src_box.width = tw / 2; + src_box.height = th / 2; + box.x = full_box->x - src_box.width; + box.y = full_box->y + full_box->height; + box.width = src_box.width; + box.height = src_box.height; + render_border_texture(output, damage, box, src_box, texture, alpha); + + // Left edge + src_box.x = 0; + src_box.y = th / 2; + src_box.width = tw / 2; + src_box.height = 1; + box.x = full_box->x - src_box.width; + box.y = full_box->y; + box.width = src_box.width; + box.height = full_box->height; + render_border_texture(output, damage, box, src_box, texture, alpha); +} + +/** + * Determines which border texture class to render for a given container. + */ +struct border_textures *get_border_textures_for_container( + struct sway_container *con) { + if (container_has_urgent_child(con)) { + return &config->border_textures.urgent; + } + if (container_has_focused_child(con) || con->current.focused) { + return &config->border_textures.focused; + } + if (!con->pending.children) { + return &config->border_textures.unfocused; + } + + for (int i = 0; i < con->pending.children->length; ++i) { + struct sway_container *child = con->pending.children->items[i]; + if (child == con->current.focused_inactive_child) { + return &config->border_textures.focused_inactive; + } + } + return &config->border_textures.unfocused; +} + +/** + * Render all of the border textures for a container. + */ +static void render_border_textures_for_container(struct sway_container *con, + struct sway_output *output, pixman_region32_t *damage) { + if (!con->pending.workspace) { + return; + } + + struct sway_container *temp = con; + while (temp) { + enum sway_container_layout layout = container_parent_layout(temp); + if (layout == L_TABBED || layout == L_STACKED) { + return; + } + temp = temp->pending.parent; + } + + enum sway_container_layout ws_layout = con->pending.workspace->layout; + if ((con->pending.layout == L_VERT || con->pending.layout == L_HORIZ) && + (ws_layout == L_VERT || ws_layout == L_HORIZ)) { + return; + } + + struct border_textures *textures = get_border_textures_for_container(con);; + struct sway_container_state *state = &con->current; + struct wlr_box box = { + .x = state->x, + .y = state->y, + .width = state->width, + .height = state->height, + }; + render_border_textures(output, damage, &box, textures->texture, con->alpha); +} + +/** + * Render the border textures for the overall layout of the container. A tabbed + * or stacked workspace layout means that all containers within the workspace + * are part of a single container, so a single border gets drawn. + */ +static void render_border_textures_for_workspace(struct sway_output *output, + pixman_region32_t *damage, struct sway_workspace *ws) { + if (ws->layout == L_TABBED || ws->layout == L_STACKED) { + struct wlr_box box; + workspace_get_box(ws, &box); + + struct sway_container *con = ws->tiling->items[0]; + if (ws->tiling->length == 0) { + return; + } + struct border_textures *textures = get_border_textures_for_container(con); + render_border_textures(output, damage, &box, textures->texture, con->alpha); + } +} + static void render_container(struct sway_output *output, pixman_region32_t *damage, struct sway_container *con, bool parent_focused); @@ -762,6 +968,8 @@ static void render_containers_linear(struct sway_output *output, render_container(output, damage, child, parent->focused || child->current.focused); } + + render_border_textures_for_container(child, output, damage); } } @@ -819,6 +1027,8 @@ static void render_containers_tabbed(struct sway_output *output, if (child == current) { current_colors = colors; } + + render_border_textures_for_container(child, output, damage); } // Render surface and left/right/bottom borders @@ -873,11 +1083,14 @@ static void render_containers_stacked(struct sway_output *output, int y = parent->box.y + titlebar_height * i; render_titlebar(output, damage, child, parent->box.x, y, - parent->box.width, colors, title_texture, marks_texture); + parent->box.width, colors, title_texture, + marks_texture); if (child == current) { current_colors = colors; } + + render_border_textures_for_container(child, output, damage); } // Render surface and left/right/bottom borders @@ -946,6 +1159,7 @@ static void render_workspace(struct sway_output *output, .active_child = ws->current.focused_inactive_child, }; render_containers(output, damage, &data); + render_border_textures_for_workspace(output, damage, ws); } static void render_floating_container(struct sway_output *soutput, @@ -981,6 +1195,8 @@ static void render_floating_container(struct sway_output *soutput, } else { render_container(soutput, damage, con, con->current.focused); } + + render_border_textures_for_container(con, soutput, damage); } static void render_floating(struct sway_output *soutput, diff --git a/sway/meson.build b/sway/meson.build index 2ce5075f1..c81013290 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -41,10 +41,11 @@ sway_sources = files( 'config/input.c', 'commands/assign.c', - 'commands/ttyaccess.c', + 'commands/ttyaccess.c', 'commands/bar.c', 'commands/bind.c', 'commands/border.c', + 'commands/border_images.c', 'commands/client.c', 'commands/create_output.c', 'commands/default_border.c', diff --git a/sway/sway.5.scd b/sway/sway.5.scd index 88920462a..8dae87d26 100644 --- a/sway/sway.5.scd +++ b/sway/sway.5.scd @@ -111,6 +111,30 @@ They are expected to be used with *bindsym* or at runtime through *swaymsg*(1). *border* toggle Cycles through the available border styles. +*border_images.* + Configures the images used for borders. The _path_ is expected to be an + absolute path to an image with an odd width and height which will be scaled to + container sizes. The edges are expected to be 1 pixel in width for top and + bottom edges, and 1 pixel in height for left edges as they will be stretched + across container edges. + + For the classes below, "container" refers to a container which has gaps + around it. The available classes are: + + *border_images.focused* + The container which is focused or has a window that has focus. + + *border_images.focused_inactive* + The container which has the most recently focused view within a container + which is not focused. + + *border_images.unfocused* + A container with all of its views unfocused. + + *border_images.urgent* + A container which has view with an urgency hint. *Note*: Native Wayland windows do not + support urgency. Urgency only works for Xwayland windows. + *exit* Exit sway and end your Wayland session. @@ -154,9 +178,10 @@ They are expected to be used with *bindsym* or at runtime through *swaymsg*(1). is now. If no argument is given, it does the same as _toggle_. If _global_ is specified, the view will be fullscreen across all outputs. -*ttyaccess* enable|disable +*ttyaccess* enable|disable|toggle Do not switch VTs, block tty access. + *gaps* inner|outer|horizontal|vertical|top|right|bottom|left all|current set|plus|minus Changes the _inner_ or _outer_ gaps for either _all_ workspaces or the diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c index 8dd7789dc..7cbd7ffbe 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c @@ -688,14 +688,18 @@ void workspace_detect_urgent(struct sway_workspace *workspace) { } } -void workspace_for_each_container(struct sway_workspace *ws, +void workspace_for_each_tiling_container(struct sway_workspace *ws, void (*f)(struct sway_container *con, void *data), void *data) { - // Tiling for (int i = 0; i < ws->tiling->length; ++i) { struct sway_container *container = ws->tiling->items[i]; f(container, data); container_for_each_child(container, f, data); } +} + +void workspace_for_each_container(struct sway_workspace *ws, + void (*f)(struct sway_container *con, void *data), void *data) { + workspace_for_each_tiling_container(ws, f, data); // Floating for (int i = 0; i < ws->floating->length; ++i) { struct sway_container *container = ws->floating->items[i];