From 88cd7616dbeff4367c41757490aa5f6f663a7318 Mon Sep 17 00:00:00 2001 From: emersion Date: Sat, 9 Jun 2018 11:38:16 +0100 Subject: [PATCH 1/3] Fix compilation errors related to disabled outputs --- sway/desktop/output.c | 23 +++++++++++++---------- sway/ipc-json.c | 7 ++++--- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/sway/desktop/output.c b/sway/desktop/output.c index 3142bdb4b..bd1d760d1 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -1169,12 +1169,10 @@ static void handle_destroy(struct wl_listener *listener, void *data) { container_destroy(output->swayc); } - if (&output->link) { - wl_list_remove(&output->link); - wl_list_remove(&output->destroy.link); - output->wlr_output = NULL; - free(output); - } + wl_list_remove(&output->link); + wl_list_remove(&output->destroy.link); + output->wlr_output->data = NULL; + free(output); } static void handle_mode(struct wl_listener *listener, void *data) { @@ -1212,10 +1210,13 @@ void handle_new_output(struct wl_listener *listener, void *data) { output->wlr_output = wlr_output; wlr_output->data = output; output->server = server; - wl_list_insert(&root_container.sway_root->outputs, &output->link); - output->damage = wlr_output_damage_create(wlr_output); + wl_signal_add(&wlr_output->events.destroy, &output->destroy); + output->destroy.notify = handle_destroy; + + wl_list_insert(&root_container.sway_root->outputs, &output->link); + if (!wl_list_empty(&wlr_output->modes)) { struct wlr_output_mode *mode = wl_container_of(wlr_output->modes.prev, mode, link); @@ -1228,6 +1229,10 @@ void handle_new_output(struct wl_listener *listener, void *data) { void output_enable(struct sway_output *output) { struct wlr_output *wlr_output = output->wlr_output; + if (!sway_assert(output->swayc == NULL, "output is already enabled")) { + return; + } + output->swayc = output_create(output); if (!output->swayc) { // Output is disabled @@ -1241,8 +1246,6 @@ void output_enable(struct sway_output *output) { input_manager_configure_xcursor(input_manager); - wl_signal_add(&wlr_output->events.destroy, &output->destroy); - output->destroy.notify = handle_destroy; wl_signal_add(&wlr_output->events.mode, &output->mode); output->mode.notify = handle_mode; wl_signal_add(&wlr_output->events.transform, &output->transform); diff --git a/sway/ipc-json.c b/sway/ipc-json.c index 5d402d1b3..b9289e256 100644 --- a/sway/ipc-json.c +++ b/sway/ipc-json.c @@ -83,7 +83,8 @@ static const char *ipc_json_get_output_transform(enum wl_output_transform transf return NULL; } -static void ipc_json_describe_output(struct sway_container *container, json_object *object) { +static void ipc_json_describe_output(struct sway_container *container, + json_object *object) { struct wlr_output *wlr_output = container->sway_output->wlr_output; json_object_object_add(object, "type", json_object_new_string("output")); @@ -141,12 +142,12 @@ static void ipc_json_describe_output(struct sway_container *container, json_obje json_object *ipc_json_describe_disabled_output(struct sway_output *output) { struct wlr_output *wlr_output = output->wlr_output; - + json_object *object = json_object_new_object(); json_object_object_add(object, "type", json_object_new_string("output")); json_object_object_add(object, "name", - wlr_output->name ? json_object_new_string(wlr_output->name) : NULL); + json_object_new_string(wlr_output->name)); json_object_object_add(object, "active", json_object_new_boolean(false)); json_object_object_add(object, "make", json_object_new_string(wlr_output->make)); From cfd02918c0d5dc539bc8858c7d0fab378145b38c Mon Sep 17 00:00:00 2001 From: emersion Date: Sat, 9 Jun 2018 13:26:03 +0100 Subject: [PATCH 2/3] Render drag icons --- include/sway/input/seat.h | 20 ++++++++ include/sway/tree/layout.h | 1 + sway/desktop/output.c | 34 +++++++++++++ sway/input/cursor.c | 62 ++++++++++++++++-------- sway/input/seat.c | 98 ++++++++++++++++++++++++++++++++++++-- sway/tree/layout.c | 1 + 6 files changed, 193 insertions(+), 23 deletions(-) diff --git a/include/sway/input/seat.h b/include/sway/input/seat.h index 2e4da438a..1f7792ba4 100644 --- a/include/sway/input/seat.h +++ b/include/sway/input/seat.h @@ -21,6 +21,19 @@ struct sway_seat_container { struct wl_listener destroy; }; +struct sway_drag_icon { + struct sway_seat *seat; + struct wlr_drag_icon *wlr_drag_icon; + struct wl_list link; // sway_root::drag_icons + + double x, y; // in layout-local coordinates + + struct wl_listener surface_commit; + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener destroy; +}; + struct sway_seat { struct wlr_seat *wlr_seat; struct sway_cursor *cursor; @@ -35,8 +48,13 @@ struct sway_seat { // If exclusive_client is set, no other clients will receive input events struct wl_client *exclusive_client; + // Last touch point + int32_t touch_id; + double touch_x, touch_y; + struct wl_listener focus_destroy; struct wl_listener new_container; + struct wl_listener new_drag_icon; struct wl_list devices; // sway_seat_device::link @@ -114,4 +132,6 @@ struct seat_config *seat_get_config(struct sway_seat *seat); bool seat_is_input_allowed(struct sway_seat *seat, struct wlr_surface *surface); +void drag_icon_update_position(struct sway_drag_icon *icon); + #endif diff --git a/include/sway/tree/layout.h b/include/sway/tree/layout.h index cd131056f..ba2656233 100644 --- a/include/sway/tree/layout.h +++ b/include/sway/tree/layout.h @@ -28,6 +28,7 @@ struct sway_root { struct wl_listener output_layout_change; struct wl_list xwayland_unmanaged; // sway_xwayland_unmanaged::link + struct wl_list drag_icons; // sway_drag_icon::link struct wlr_texture *debug_tree; diff --git a/sway/desktop/output.c b/sway/desktop/output.c index bd1d760d1..29666c00b 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -161,6 +161,21 @@ static void unmanaged_for_each_surface(struct wl_list *unmanaged, } } +static void drag_icons_for_each_surface(struct wl_list *drag_icons, + struct sway_output *output, struct root_geometry *geo, + wlr_surface_iterator_func_t iterator, void *user_data) { + struct sway_drag_icon *drag_icon; + wl_list_for_each(drag_icon, drag_icons, link) { + double ox = drag_icon->x - output->swayc->x; + double oy = drag_icon->y - output->swayc->y; + + if (drag_icon->wlr_drag_icon->mapped) { + surface_for_each_surface(drag_icon->wlr_drag_icon->surface, + ox, oy, geo, iterator, user_data); + } + } +} + static void scale_box(struct wlr_box *box, float scale) { box->x *= scale; box->y *= scale; @@ -270,6 +285,17 @@ static void render_unmanaged(struct sway_output *output, render_surface_iterator, &data); } +static void render_drag_icons(struct sway_output *output, + pixman_region32_t *damage, struct wl_list *drag_icons) { + struct render_data data = { + .output = output, + .damage = damage, + .alpha = 1.0f, + }; + drag_icons_for_each_surface(drag_icons, output, &data.root_geo, + render_surface_iterator, &data); +} + static void render_rect(struct wlr_output *wlr_output, pixman_region32_t *output_damage, const struct wlr_box *_box, float color[static 4]) { @@ -911,6 +937,7 @@ static void render_output(struct sway_output *output, struct timespec *when, } render_layer(output, damage, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]); + render_drag_icons(output, damage, &root_container.sway_root->drag_icons); renderer_end: if (root_container.sway_root->debug_tree) { @@ -961,6 +988,12 @@ static void send_frame_done_unmanaged(struct send_frame_done_data *data, send_frame_done_iterator, data); } +static void send_frame_done_drag_icons(struct send_frame_done_data *data, + struct wl_list *drag_icons) { + drag_icons_for_each_surface(drag_icons, data->output, &data->root_geo, + send_frame_done_iterator, data); +} + static void send_frame_done_container_iterator(struct sway_container *con, void *_data) { struct send_frame_done_data *data = _data; @@ -1014,6 +1047,7 @@ static void send_frame_done(struct sway_output *output, struct timespec *when) { send_frame_done_layer(&data, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]); + send_frame_done_drag_icons(&data, &root_container.sway_root->drag_icons); } static void damage_handle_frame(struct wl_listener *listener, void *data) { diff --git a/sway/input/cursor.c b/sway/input/cursor.c index 4100479c9..37a877568 100644 --- a/sway/input/cursor.c +++ b/sway/input/cursor.c @@ -144,21 +144,22 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec, time_msec = get_current_time_msec(); } - struct wlr_seat *seat = cursor->seat->wlr_seat; + struct sway_seat *seat = cursor->seat; + struct wlr_seat *wlr_seat = seat->wlr_seat; struct wlr_surface *surface = NULL; double sx, sy; // Find the container beneath the pointer's previous position - struct sway_container *prev_c = container_at_coords(cursor->seat, + struct sway_container *prev_c = container_at_coords(seat, cursor->previous.x, cursor->previous.y, &surface, &sx, &sy); // Update the stored previous position cursor->previous.x = cursor->cursor->x; cursor->previous.y = cursor->cursor->y; - struct sway_container *c = container_at_coords(cursor->seat, + struct sway_container *c = container_at_coords(seat, cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); if (c && config->focus_follows_mouse && allow_refocusing) { - struct sway_container *focus = seat_get_focus(cursor->seat); + struct sway_container *focus = seat_get_focus(seat); if (focus && c->type == C_WORKSPACE) { // Only follow the mouse if it would move to a new output // Otherwise we'll focus the workspace, which is probably wrong @@ -170,20 +171,20 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec, output = container_parent(c, C_OUTPUT); } if (output != focus) { - seat_set_focus_warp(cursor->seat, c, false); + seat_set_focus_warp(seat, c, false); } } else if (c->type == C_VIEW) { // Focus c if both of the following are true: // - cursor is over a new view, i.e. entered a new window; and // - the new view is visible, i.e. not hidden in a stack or tab. if (c != prev_c && view_is_visible(c->sway_view)) { - seat_set_focus_warp(cursor->seat, c, false); + seat_set_focus_warp(seat, c, false); } else { struct sway_container *next_focus = - seat_get_focus_inactive(cursor->seat, &root_container); + seat_get_focus_inactive(seat, &root_container); if (next_focus && next_focus->type == C_VIEW && view_is_visible(next_focus->sway_view)) { - seat_set_focus_warp(cursor->seat, next_focus, false); + seat_set_focus_warp(seat, next_focus, false); } } } @@ -202,12 +203,18 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec, // send pointer enter/leave if (surface != NULL) { - if (seat_is_input_allowed(cursor->seat, surface)) { - wlr_seat_pointer_notify_enter(seat, surface, sx, sy); - wlr_seat_pointer_notify_motion(seat, time_msec, sx, sy); + if (seat_is_input_allowed(seat, surface)) { + wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy); + wlr_seat_pointer_notify_motion(wlr_seat, time_msec, sx, sy); } } else { - wlr_seat_pointer_clear_focus(seat); + wlr_seat_pointer_clear_focus(wlr_seat); + } + + struct wlr_drag_icon *wlr_drag_icon; + wl_list_for_each(wlr_drag_icon, &wlr_seat->drag_icons, link) { + struct sway_drag_icon *drag_icon = wlr_drag_icon->data; + drag_icon_update_position(drag_icon); } } @@ -293,22 +300,27 @@ static void handle_touch_down(struct wl_listener *listener, void *data) { wlr_idle_notify_activity(cursor->seat->input->server->idle, cursor->seat->wlr_seat); struct wlr_event_touch_down *event = data; - struct wlr_seat *seat = cursor->seat->wlr_seat; + struct sway_seat *seat = cursor->seat; + struct wlr_seat *wlr_seat = seat->wlr_seat; struct wlr_surface *surface = NULL; double lx, ly; wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device, event->x, event->y, &lx, &ly); double sx, sy; - container_at_coords(cursor->seat, lx, ly, &surface, &sx, &sy); + container_at_coords(seat, lx, ly, &surface, &sx, &sy); + + seat->touch_id = event->touch_id; + seat->touch_x = lx; + seat->touch_y = ly; if (!surface) { return; } // TODO: fall back to cursor simulation if client has not bound to touch - if (seat_is_input_allowed(cursor->seat, surface)) { - wlr_seat_touch_notify_down(seat, surface, event->time_msec, + if (seat_is_input_allowed(seat, surface)) { + wlr_seat_touch_notify_down(wlr_seat, surface, event->time_msec, event->touch_id, sx, sy); cursor->image_client = NULL; wlr_cursor_set_image(cursor->cursor, NULL, 0, 0, 0, 0, 0, 0); @@ -330,7 +342,8 @@ static void handle_touch_motion(struct wl_listener *listener, void *data) { wlr_idle_notify_activity(cursor->seat->input->server->idle, cursor->seat->wlr_seat); struct wlr_event_touch_motion *event = data; - struct wlr_seat *seat = cursor->seat->wlr_seat; + struct sway_seat *seat = cursor->seat; + struct wlr_seat *wlr_seat = seat->wlr_seat; struct wlr_surface *surface = NULL; double lx, ly; @@ -339,14 +352,25 @@ static void handle_touch_motion(struct wl_listener *listener, void *data) { double sx, sy; container_at_coords(cursor->seat, lx, ly, &surface, &sx, &sy); + if (seat->touch_id == event->touch_id) { + seat->touch_x = lx; + seat->touch_y = ly; + + struct wlr_drag_icon *wlr_drag_icon; + wl_list_for_each(wlr_drag_icon, &wlr_seat->drag_icons, link) { + struct sway_drag_icon *drag_icon = wlr_drag_icon->data; + drag_icon_update_position(drag_icon); + } + } + if (!surface) { return; } // TODO: fall back to cursor simulation if client has not bound to touch if (seat_is_input_allowed(cursor->seat, surface)) { - wlr_seat_touch_notify_motion( - seat, event->time_msec, event->touch_id, sx, sy); + wlr_seat_touch_notify_motion(wlr_seat, event->time_msec, + event->touch_id, sx, sy); } } diff --git a/sway/input/seat.c b/sway/input/seat.c index 071ef0202..1ea364666 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -6,21 +6,22 @@ #include #include #include +#include "log.h" #include "sway/debug.h" -#include "sway/tree/container.h" -#include "sway/tree/workspace.h" -#include "sway/input/seat.h" +#include "sway/desktop.h" #include "sway/input/cursor.h" #include "sway/input/input-manager.h" #include "sway/input/keyboard.h" +#include "sway/input/seat.h" #include "sway/ipc-server.h" #include "sway/layers.h" #include "sway/output.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" +#include "sway/tree/container.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" -#include "log.h" +#include "sway/tree/workspace.h" static void seat_device_destroy(struct sway_seat_device *seat_device) { if (!seat_device) { @@ -40,6 +41,8 @@ void seat_destroy(struct sway_seat *seat) { seat_device_destroy(seat_device); } sway_cursor_destroy(seat->cursor); + wl_list_remove(&seat->new_container.link); + wl_list_remove(&seat->new_drag_icon.link); wl_list_remove(&seat->link); wlr_seat_destroy(seat->wlr_seat); } @@ -234,6 +237,90 @@ static void handle_new_container(struct wl_listener *listener, void *data) { seat_container_from_container(seat, con); } +static void drag_icon_damage_whole(struct sway_drag_icon *icon) { + if (!icon->wlr_drag_icon->mapped) { + return; + } + desktop_damage_surface(icon->wlr_drag_icon->surface, icon->x, icon->y, true); +} + +void drag_icon_update_position(struct sway_drag_icon *icon) { + drag_icon_damage_whole(icon); + + struct wlr_drag_icon *wlr_icon = icon->wlr_drag_icon; + struct sway_seat *seat = icon->seat; + struct wlr_cursor *cursor = seat->cursor->cursor; + if (wlr_icon->is_pointer) { + icon->x = cursor->x + wlr_icon->sx; + icon->y = cursor->y + wlr_icon->sy; + } else { + struct wlr_touch_point *point = + wlr_seat_touch_get_point(seat->wlr_seat, wlr_icon->touch_id); + if (point == NULL) { + return; + } + icon->x = seat->touch_x + wlr_icon->sx; + icon->y = seat->touch_y + wlr_icon->sy; + } + + drag_icon_damage_whole(icon); +} + +static void drag_icon_handle_surface_commit(struct wl_listener *listener, + void *data) { + struct sway_drag_icon *icon = + wl_container_of(listener, icon, surface_commit); + drag_icon_update_position(icon); +} + +static void drag_icon_handle_map(struct wl_listener *listener, void *data) { + struct sway_drag_icon *icon = wl_container_of(listener, icon, map); + drag_icon_damage_whole(icon); +} + +static void drag_icon_handle_unmap(struct wl_listener *listener, void *data) { + struct sway_drag_icon *icon = wl_container_of(listener, icon, unmap); + drag_icon_damage_whole(icon); +} + +static void drag_icon_handle_destroy(struct wl_listener *listener, + void *data) { + struct sway_drag_icon *icon = wl_container_of(listener, icon, destroy); + icon->wlr_drag_icon->data = NULL; + wl_list_remove(&icon->link); + wl_list_remove(&icon->surface_commit.link); + wl_list_remove(&icon->unmap.link); + wl_list_remove(&icon->destroy.link); + free(icon); +} + +static void handle_new_drag_icon(struct wl_listener *listener, void *data) { + struct sway_seat *seat = wl_container_of(listener, seat, new_drag_icon); + struct wlr_drag_icon *wlr_drag_icon = data; + + struct sway_drag_icon *icon = calloc(1, sizeof(struct sway_drag_icon)); + if (icon == NULL) { + wlr_log(L_ERROR, "Allocation failed"); + return; + } + icon->seat = seat; + icon->wlr_drag_icon = wlr_drag_icon; + wlr_drag_icon->data = icon; + + icon->surface_commit.notify = drag_icon_handle_surface_commit; + wl_signal_add(&wlr_drag_icon->surface->events.commit, &icon->surface_commit); + icon->unmap.notify = drag_icon_handle_unmap; + wl_signal_add(&wlr_drag_icon->events.unmap, &icon->unmap); + icon->map.notify = drag_icon_handle_map; + wl_signal_add(&wlr_drag_icon->events.map, &icon->map); + icon->destroy.notify = drag_icon_handle_destroy; + wl_signal_add(&wlr_drag_icon->events.destroy, &icon->destroy); + + wl_list_insert(&root_container.sway_root->drag_icons, &icon->link); + + drag_icon_update_position(icon); +} + static void collect_focus_iter(struct sway_container *con, void *data) { struct sway_seat *seat = data; if (con->type > C_WORKSPACE) { @@ -278,6 +365,9 @@ struct sway_seat *seat_create(struct sway_input_manager *input, &seat->new_container); seat->new_container.notify = handle_new_container; + wl_signal_add(&seat->wlr_seat->events.new_drag_icon, &seat->new_drag_icon); + seat->new_drag_icon.notify = handle_new_drag_icon; + seat->input = input; wl_list_init(&seat->devices); diff --git a/sway/tree/layout.c b/sway/tree/layout.c index 6d4cd0889..f56fbffaa 100644 --- a/sway/tree/layout.c +++ b/sway/tree/layout.c @@ -37,6 +37,7 @@ void layout_init(void) { root_container.sway_root->output_layout = wlr_output_layout_create(); wl_list_init(&root_container.sway_root->outputs); wl_list_init(&root_container.sway_root->xwayland_unmanaged); + wl_list_init(&root_container.sway_root->drag_icons); wl_signal_init(&root_container.sway_root->events.new_container); root_container.sway_root->output_layout_change.notify = From 6a910b9ba5443aa31f7cc2468c943c2f9da4854b Mon Sep 17 00:00:00 2001 From: Nate Symer Date: Sat, 9 Jun 2018 09:34:56 -0400 Subject: [PATCH 3/3] Implement gaps (PR #2047) --- include/sway/tree/arrange.h | 6 ++ include/sway/tree/container.h | 7 ++ sway/commands.c | 3 + sway/commands/gaps.c | 183 ++++++++++++++++++++++++++++++++++ sway/commands/smart_gaps.c | 28 ++++++ sway/meson.build | 2 + sway/tree/arrange.c | 88 ++++++++++++++-- sway/tree/container.c | 5 + sway/tree/layout.c | 7 +- 9 files changed, 320 insertions(+), 9 deletions(-) create mode 100644 sway/commands/gaps.c create mode 100644 sway/commands/smart_gaps.c diff --git a/include/sway/tree/arrange.h b/include/sway/tree/arrange.h index ce95cfe97..a14bc5dcf 100644 --- a/include/sway/tree/arrange.h +++ b/include/sway/tree/arrange.h @@ -3,6 +3,12 @@ struct sway_container; +// Remove gaps around container +void remove_gaps(struct sway_container *c); + +// Add gaps around container +void add_gaps(struct sway_container *c); + // Determine the root container's geometry, then iterate to everything below void arrange_root(void); diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h index 7ed6aab1a..b3406bbe5 100644 --- a/include/sway/tree/container.h +++ b/include/sway/tree/container.h @@ -86,6 +86,13 @@ struct sway_container { double saved_x, saved_y; double saved_width, saved_height; + // The gaps currently applied to the container. + double current_gaps; + + bool has_gaps; + double gaps_inner; + double gaps_outer; + list_t *children; struct sway_container *parent; diff --git a/sway/commands.c b/sway/commands.c index 1523fdd1f..5b20857ab 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -105,6 +105,8 @@ static struct cmd_handler handlers[] = { { "font", cmd_font }, { "for_window", cmd_for_window }, { "force_focus_wrapping", cmd_force_focus_wrapping }, + { "fullscreen", cmd_fullscreen }, + { "gaps", cmd_gaps }, { "hide_edge_borders", cmd_hide_edge_borders }, { "include", cmd_include }, { "input", cmd_input }, @@ -114,6 +116,7 @@ static struct cmd_handler handlers[] = { { "seat", cmd_seat }, { "set", cmd_set }, { "show_marks", cmd_show_marks }, + { "smart_gaps", cmd_smart_gaps }, { "workspace", cmd_workspace }, { "workspace_auto_back_and_forth", cmd_ws_auto_back_and_forth }, }; diff --git a/sway/commands/gaps.c b/sway/commands/gaps.c new file mode 100644 index 000000000..aacb792b8 --- /dev/null +++ b/sway/commands/gaps.c @@ -0,0 +1,183 @@ +#include +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/tree/arrange.h" +#include "log.h" +#include "stringop.h" +#include + +enum gaps_op { + GAPS_OP_SET, + GAPS_OP_ADD, + GAPS_OP_SUBTRACT +}; + +enum gaps_scope { + GAPS_SCOPE_ALL, + GAPS_SCOPE_WORKSPACE, + GAPS_SCOPE_CURRENT +}; + +struct cmd_results *cmd_gaps(int argc, char **argv) { + struct cmd_results *error = checkarg(argc, "gaps", EXPECTED_AT_LEAST, 1); + if (error) { + return error; + } + + if (strcmp(argv[0], "edge_gaps") == 0) { + if ((error = checkarg(argc, "gaps", EXPECTED_AT_LEAST, 2))) { + return error; + } + + if (strcmp(argv[1], "on") == 0) { + config->edge_gaps = true; + arrange_root(); + } else if (strcmp(argv[1], "off") == 0) { + config->edge_gaps = false; + arrange_root(); + } else if (strcmp(argv[1], "toggle") == 0) { + if (!config->active) { + return cmd_results_new(CMD_INVALID, "gaps", + "Cannot toggle gaps while not running."); + } + config->edge_gaps = !config->edge_gaps; + arrange_root(); + } else { + return cmd_results_new(CMD_INVALID, "gaps", + "gaps edge_gaps on|off|toggle"); + } + } else { + int amount_idx = 0; // the current index in argv + enum gaps_op op = GAPS_OP_SET; + enum gaps_scope scope = GAPS_SCOPE_ALL; + bool inner = true; + + if (strcmp(argv[0], "inner") == 0) { + amount_idx++; + inner = true; + } else if (strcmp(argv[0], "outer") == 0) { + amount_idx++; + inner = false; + } + + // If one of the long variants of the gaps command is used + // (which starts with inner|outer) check the number of args + if (amount_idx > 0) { // if we've seen inner|outer + if (argc > 2) { // check the longest variant + error = checkarg(argc, "gaps", EXPECTED_EQUAL_TO, 4); + if (error) { + return error; + } + } else { // check the next longest format + error = checkarg(argc, "gaps", EXPECTED_EQUAL_TO, 2); + if (error) { + return error; + } + } + } else { + error = checkarg(argc, "gaps", EXPECTED_EQUAL_TO, 1); + if (error) { + return error; + } + } + + if (argc == 4) { + // Long format: all|workspace|current. + if (strcmp(argv[amount_idx], "all") == 0) { + amount_idx++; + scope = GAPS_SCOPE_ALL; + } else if (strcmp(argv[amount_idx], "workspace") == 0) { + amount_idx++; + scope = GAPS_SCOPE_WORKSPACE; + } else if (strcmp(argv[amount_idx], "current") == 0) { + amount_idx++; + scope = GAPS_SCOPE_CURRENT; + } + + // Long format: set|plus|minus + if (strcmp(argv[amount_idx], "set") == 0) { + amount_idx++; + op = GAPS_OP_SET; + } else if (strcmp(argv[amount_idx], "plus") == 0) { + amount_idx++; + op = GAPS_OP_ADD; + } else if (strcmp(argv[amount_idx], "minus") == 0) { + amount_idx++; + op = GAPS_OP_SUBTRACT; + } + } + + char *end; + double val = strtod(argv[amount_idx], &end); + + if (strlen(end) && val == 0.0) { // invalid + // guess which variant of the command was attempted + if (argc == 1) { + return cmd_results_new(CMD_INVALID, "gaps", "gaps "); + } + if (argc == 2) { + return cmd_results_new(CMD_INVALID, "gaps", + "gaps inner|outer "); + } + return cmd_results_new(CMD_INVALID, "gaps", + "gaps inner|outer all|workspace|current set|plus|minus "); + } + + if (amount_idx == 0) { // gaps + config->gaps_inner = val; + config->gaps_outer = val; + arrange_root(); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); + } + // Other variants. The middle-length variant (gaps inner|outer ) + // just defaults the scope to "all" and defaults the op to "set". + + double total; + switch (op) { + case GAPS_OP_SUBTRACT: { + total = (inner ? config->gaps_inner : config->gaps_outer) - val; + if (total < 0) { + total = 0; + } + break; + } + case GAPS_OP_ADD: { + total = (inner ? config->gaps_inner : config->gaps_outer) + val; + break; + } + case GAPS_OP_SET: { + total = val; + break; + } + } + + if (scope == GAPS_SCOPE_ALL) { + if (inner) { + config->gaps_inner = total; + } else { + config->gaps_outer = total; + } + arrange_root(); + } else { + struct sway_container *c = + config->handler_context.current_container; + if (scope == GAPS_SCOPE_WORKSPACE && c->type != C_WORKSPACE) { + c = container_parent(c, C_WORKSPACE); + } + c->has_gaps = true; + if (inner) { + c->gaps_inner = total; + } else { + c->gaps_outer = total; + } + + if (c->parent) { + arrange_children_of(c->parent); + } else { + arrange_root(); + } + } + } + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/commands/smart_gaps.c b/sway/commands/smart_gaps.c new file mode 100644 index 000000000..38700d65b --- /dev/null +++ b/sway/commands/smart_gaps.c @@ -0,0 +1,28 @@ +#include +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/tree/arrange.h" +#include "sway/tree/view.h" +#include "sway/tree/container.h" +#include "log.h" +#include "stringop.h" + +struct cmd_results *cmd_smart_gaps(int argc, char **argv) { + struct cmd_results *error = checkarg(argc, "smart_gaps", EXPECTED_AT_LEAST, 1); + + if (error) { + return error; + } + + if (strcmp(argv[0], "on") == 0) { + config->smart_gaps = true; + arrange_root(); + } else if (strcmp(argv[0], "off") == 0) { + config->smart_gaps = false; + arrange_root(); + } else { + return cmd_results_new(CMD_INVALID, "smart_gaps", + "Expected 'smart_gaps ' "); + } + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/meson.build b/sway/meson.build index b6bb02a7a..0da67ed7d 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -44,6 +44,7 @@ sway_sources = files( 'commands/for_window.c', 'commands/force_focus_wrapping.c', 'commands/fullscreen.c', + 'commands/gaps.c', 'commands/hide_edge_borders.c', 'commands/kill.c', 'commands/mark.c', @@ -64,6 +65,7 @@ sway_sources = files( 'commands/seat/fallback.c', 'commands/set.c', 'commands/show_marks.c', + 'commands/smart_gaps.c', 'commands/split.c', 'commands/sticky.c', 'commands/swaybg_command.c', diff --git a/sway/tree/arrange.c b/sway/tree/arrange.c index 721b557e4..53c95820c 100644 --- a/sway/tree/arrange.c +++ b/sway/tree/arrange.c @@ -43,6 +43,7 @@ void arrange_output(struct sway_container *output) { "called arrange_output() on non-output container")) { return; } + const struct wlr_box *output_box = wlr_output_layout_get_box( root_container.sway_root->output_layout, output->sway_output->wlr_output); @@ -52,6 +53,7 @@ void arrange_output(struct sway_container *output) { output->height = output_box->height; wlr_log(L_DEBUG, "Arranging output '%s' at %f,%f", output->name, output->x, output->y); + for (int i = 0; i < output->children->length; ++i) { struct sway_container *workspace = output->children->items[i]; arrange_workspace(workspace); @@ -67,18 +69,62 @@ void arrange_workspace(struct sway_container *workspace) { "called arrange_workspace() on non-workspace container")) { return; } + struct sway_container *output = workspace->parent; struct wlr_box *area = &output->sway_output->usable_area; wlr_log(L_DEBUG, "Usable area for ws: %dx%d@%d,%d", area->width, area->height, area->x, area->y); + + remove_gaps(workspace); + workspace->width = area->width; workspace->height = area->height; workspace->x = output->x + area->x; workspace->y = output->y + area->y; + + add_gaps(workspace); + wlr_log(L_DEBUG, "Arranging workspace '%s' at %f, %f", workspace->name, workspace->x, workspace->y); arrange_children_of(workspace); - container_damage_whole(workspace); +} + +void remove_gaps(struct sway_container *c) { + if (c->current_gaps == 0) { + wlr_log(L_DEBUG, "Removing gaps: not gapped: %p", c); + return; + } + + c->width += c->current_gaps * 2; + c->height += c->current_gaps * 2; + c->x -= c->current_gaps; + c->y -= c->current_gaps; + + c->current_gaps = 0; + + wlr_log(L_DEBUG, "Removing gaps %p", c); +} + +void add_gaps(struct sway_container *c) { + if (c->current_gaps > 0 || c->type == C_CONTAINER) { + wlr_log(L_DEBUG, "Not adding gaps: %p", c); + return; + } + + if (c->type == C_WORKSPACE && + !(config->edge_gaps || (config->smart_gaps && c->children->length > 1))) { + return; + } + + double gaps = c->has_gaps ? c->gaps_inner : config->gaps_inner; + + c->x += gaps; + c->y += gaps; + c->width -= 2 * gaps; + c->height -= 2 * gaps; + c->current_gaps = gaps; + + wlr_log(L_DEBUG, "Adding gaps: %p", c); } static void apply_horiz_layout(struct sway_container *parent) { @@ -99,6 +145,7 @@ static void apply_horiz_layout(struct sway_container *parent) { double total_width = 0; for (size_t i = 0; i < num_children; ++i) { struct sway_container *child = parent->children->items[i]; + if (child->width <= 0) { if (num_children > 1) { child->width = parent->width / (num_children - 1); @@ -106,6 +153,7 @@ static void apply_horiz_layout(struct sway_container *parent) { child->width = parent->width; } } + remove_gaps(child); total_width += child->width; } double scale = parent->width / total_width; @@ -121,12 +169,19 @@ static void apply_horiz_layout(struct sway_container *parent) { child, child->type, child->width, scale); child->x = child_x; child->y = parent->y + parent_offset; - child->width = floor(child->width * scale); child->height = parent_height; + + if (i == num_children - 1) { + // Make last child use remaining width of parent + child->width = parent->x + parent->width - child->x; + } else { + child->width = floor(child->width * scale); + } + child_x += child->width; + + add_gaps(child); } - // Make last child use remaining width of parent - child->width = parent->x + parent->width - child->x; } static void apply_vert_layout(struct sway_container *parent) { @@ -141,12 +196,13 @@ static void apply_vert_layout(struct sway_container *parent) { parent_offset = container_titlebar_height() * parent->parent->children->length; } - size_t parent_height = parent->height - parent_offset; + size_t parent_height = parent->height + parent_offset; // Calculate total height of children double total_height = 0; for (size_t i = 0; i < num_children; ++i) { struct sway_container *child = parent->children->items[i]; + if (child->height <= 0) { if (num_children > 1) { child->height = parent_height / (num_children - 1); @@ -154,6 +210,7 @@ static void apply_vert_layout(struct sway_container *parent) { child->height = parent_height; } } + remove_gaps(child); total_height += child->height; } double scale = parent_height / total_height; @@ -170,11 +227,18 @@ static void apply_vert_layout(struct sway_container *parent) { child->x = parent->x; child->y = child_y; child->width = parent->width; - child->height = floor(child->height * scale); + + if (i == num_children - 1) { + // Make last child use remaining height of parent + child->height = parent->y + parent_offset + parent_height - child->y; + } else { + child->height = floor(child->height * scale); + } + child_y += child->height; + + add_gaps(child); } - // Make last child use remaining height of parent - child->height = parent->y + parent_offset + parent_height - child->y; } static void apply_tabbed_or_stacked_layout(struct sway_container *parent) { @@ -191,10 +255,12 @@ static void apply_tabbed_or_stacked_layout(struct sway_container *parent) { size_t parent_height = parent->height - parent_offset; for (int i = 0; i < parent->children->length; ++i) { struct sway_container *child = parent->children->items[i]; + remove_gaps(child); child->x = parent->x; child->y = parent->y + parent_offset; child->width = parent->width; child->height = parent_height; + add_gaps(child); } } @@ -211,6 +277,7 @@ void arrange_children_of(struct sway_container *parent) { if (workspace->type != C_WORKSPACE) { workspace = container_parent(workspace, C_WORKSPACE); } + if (workspace->sway_workspace->fullscreen) { // Just arrange the fullscreen view and jump out view_autoconfigure(workspace->sway_workspace->fullscreen); @@ -241,6 +308,11 @@ void arrange_children_of(struct sway_container *parent) { // Apply x, y, width and height to children and recurse if needed for (int i = 0; i < parent->children->length; ++i) { struct sway_container *child = parent->children->items[i]; + if (parent->has_gaps && !child->has_gaps) { + child->has_gaps = true; + child->gaps_inner = parent->gaps_inner; + child->gaps_outer = parent->gaps_outer; + } if (child->type == C_VIEW) { view_autoconfigure(child->sway_view); } else { diff --git a/sway/tree/container.c b/sway/tree/container.c index cd2c083c9..af55a54e7 100644 --- a/sway/tree/container.c +++ b/sway/tree/container.c @@ -124,6 +124,11 @@ struct sway_container *container_create(enum sway_container_type type) { wl_signal_add(&c->events.reparent, &c->reparent); c->reparent.notify = handle_reparent; + c->has_gaps = false; + c->gaps_inner = 0; + c->gaps_outer = 0; + c->current_gaps = 0; + return c; } diff --git a/sway/tree/layout.c b/sway/tree/layout.c index 6d4cd0889..a8aba6186 100644 --- a/sway/tree/layout.c +++ b/sway/tree/layout.c @@ -871,6 +871,8 @@ struct sway_container *container_split(struct sway_container *child, wlr_log(L_DEBUG, "creating container %p around %p", cont, child); + remove_gaps(child); + cont->prev_layout = L_NONE; cont->width = child->width; cont->height = child->height; @@ -879,6 +881,9 @@ struct sway_container *container_split(struct sway_container *child, struct sway_seat *seat = input_manager_get_default_seat(input_manager); bool set_focus = (seat_get_focus(seat) == child); + + add_gaps(cont); + if (child->type == C_WORKSPACE) { struct sway_container *workspace = child; while (workspace->children->length) { @@ -906,7 +911,7 @@ struct sway_container *container_split(struct sway_container *child, } container_notify_subtree_changed(cont); - + arrange_children_of(cont); return cont; }