From b28602aa7425cf435150e6008624429737e037d3 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Tue, 30 Jan 2018 23:09:21 -0500 Subject: [PATCH] Implement workspaces --- include/sway/container.h | 19 ++- include/sway/input/input-manager.h | 4 + include/sway/workspace.h | 14 ++ sway/commands.c | 1 + sway/commands/kill.c | 7 +- sway/commands/workspace.c | 101 ++++++++++++++ sway/config.c | 15 +- sway/desktop/output.c | 4 +- sway/desktop/xdg_shell_v6.c | 9 +- sway/input/input-manager.c | 8 ++ sway/input/seat.c | 15 +- sway/meson.build | 1 + sway/tree/container.c | 41 +++++- sway/tree/workspace.c | 211 +++++++++++++++++++++++++++++ 14 files changed, 420 insertions(+), 30 deletions(-) create mode 100644 sway/commands/workspace.c diff --git a/include/sway/container.h b/include/sway/container.h index a99e2694..0c66932d 100644 --- a/include/sway/container.h +++ b/include/sway/container.h @@ -11,6 +11,7 @@ typedef struct sway_container swayc_t; extern swayc_t root_container; struct sway_view; +struct sway_seat; /** * Different kinds of containers. @@ -140,11 +141,25 @@ swayc_t *new_view(swayc_t *sibling, struct sway_view *sway_view); swayc_t *destroy_output(swayc_t *output); swayc_t *destroy_view(swayc_t *view); +swayc_t *next_view_sibling(struct sway_seat *seat); + +/** + * Finds a container based on test criteria. Returns the first container that + * passes the test. + */ +swayc_t *swayc_by_test(swayc_t *container, + bool (*test)(swayc_t *view, void *data), void *data); +/** + * Finds a parent container with the given swayc_type. + */ swayc_t *swayc_parent_by_type(swayc_t *container, enum swayc_types type); +/** + * Maps a container's children over a function. + */ +void container_map(swayc_t *container, + void (*f)(swayc_t *view, void *data), void *data); swayc_t *swayc_at(swayc_t *parent, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy); -void container_map(swayc_t *container, void (*f)(swayc_t *view, void *data), void *data); - #endif diff --git a/include/sway/input/input-manager.h b/include/sway/input/input-manager.h index 63806b8e..66ace262 100644 --- a/include/sway/input/input-manager.h +++ b/include/sway/input/input-manager.h @@ -48,4 +48,8 @@ struct sway_seat *sway_input_manager_get_default_seat( struct sway_seat *input_manager_get_seat(struct sway_input_manager *input, const char *seat_name); + +/** Gets the last seat the user interacted with */ +struct sway_seat *input_manager_current_seat(struct sway_input_manager *input); + #endif diff --git a/include/sway/workspace.h b/include/sway/workspace.h index 04b2ea4e..30bbdaa8 100644 --- a/include/sway/workspace.h +++ b/include/sway/workspace.h @@ -1,6 +1,20 @@ #ifndef _SWAY_WORKSPACE_H #define _SWAY_WORKSPACE_H +struct sway_container; + +extern char *prev_workspace_name; + char *workspace_next_name(const char *output_name); +swayc_t *workspace_create(const char *name); +bool workspace_switch(swayc_t *workspace); + +struct sway_container *workspace_by_number(const char* name); +swayc_t *workspace_by_name(const char*); + +struct sway_container *workspace_output_next(struct sway_container *current); +struct sway_container *workspace_next(struct sway_container *current); +struct sway_container *workspace_output_prev(struct sway_container *current); +struct sway_container *workspace_prev(struct sway_container *current); #endif diff --git a/sway/commands.c b/sway/commands.c index d4262c08..0d4aa104 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -139,6 +139,7 @@ static struct cmd_handler handlers[] = { { "reload", cmd_reload }, { "seat", cmd_seat }, { "set", cmd_set }, + { "workspace", cmd_workspace }, }; static int handler_compare(const void *_a, const void *_b) { diff --git a/sway/commands/kill.c b/sway/commands/kill.c index 3804f0b0..cebf7f3c 100644 --- a/sway/commands/kill.c +++ b/sway/commands/kill.c @@ -10,11 +10,10 @@ struct cmd_results *cmd_kill(int argc, char **argv) { return cmd_results_new(CMD_FAILURE, "kill", "Command 'kill' cannot be used in the config file"); } - if (!sway_assert(config->handler_context.current_container, - "cmd_kill called without container context")) { + enum swayc_types type = config->handler_context.current_container->type; + if (type != C_VIEW || type != C_CONTAINER) { return cmd_results_new(CMD_INVALID, NULL, - "cmd_kill called without container context " - "(this is a bug in sway)"); + "Can only kill views and containers with this command"); } // TODO close arbitrary containers without a view struct sway_view *view = diff --git a/sway/commands/workspace.c b/sway/commands/workspace.c new file mode 100644 index 00000000..12984ed4 --- /dev/null +++ b/sway/commands/workspace.c @@ -0,0 +1,101 @@ +#define _XOPEN_SOURCE 500 +#include +#include +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/input/seat.h" +#include "sway/workspace.h" +#include "list.h" +#include "log.h" +#include "stringop.h" + +struct cmd_results *cmd_workspace(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "workspace", EXPECTED_AT_LEAST, 1))) { + return error; + } + + int output_location = -1; + + swayc_t *current_container = config->handler_context.current_container; + swayc_t *old_workspace = NULL, *old_output = NULL; + if (current_container) { + if (current_container->type == C_WORKSPACE) { + old_workspace = current_container; + } else { + old_workspace = swayc_parent_by_type(current_container, C_WORKSPACE); + } + old_output = swayc_parent_by_type(current_container, C_OUTPUT); + } + + for (int i = 0; i < argc; ++i) { + if (strcasecmp(argv[i], "output") == 0) { + output_location = i; + break; + } + } + if (output_location >= 0) { + if ((error = checkarg(argc, "workspace", EXPECTED_EQUAL_TO, output_location + 2))) { + return error; + } + struct workspace_output *wso = calloc(1, sizeof(struct workspace_output)); + if (!wso) { + return cmd_results_new(CMD_FAILURE, "workspace output", + "Unable to allocate workspace output"); + } + wso->workspace = join_args(argv, argc - 2); + wso->output = strdup(argv[output_location + 1]); + int i = -1; + if ((i = list_seq_find(config->workspace_outputs, workspace_output_cmp_workspace, wso)) != -1) { + struct workspace_output *old = config->workspace_outputs->items[i]; + free(old); // workspaces can only be assigned to a single output + list_del(config->workspace_outputs, i); + } + wlr_log(L_DEBUG, "Assigning workspace %s to output %s", wso->workspace, wso->output); + list_add(config->workspace_outputs, wso); + } else { + if (config->reading || !config->active) { + return cmd_results_new(CMD_DEFER, "workspace", NULL); + } + swayc_t *ws = NULL; + if (strcasecmp(argv[0], "number") == 0) { + if (!(ws = workspace_by_number(argv[1]))) { + char *name = join_args(argv + 1, argc - 1); + ws = workspace_create(name); + free(name); + } + } else if (strcasecmp(argv[0], "next") == 0) { + ws = workspace_next(old_workspace); + } else if (strcasecmp(argv[0], "prev") == 0) { + ws = workspace_prev(old_workspace); + } else if (strcasecmp(argv[0], "next_on_output") == 0) { + ws = workspace_output_next(old_output); + } else if (strcasecmp(argv[0], "prev_on_output") == 0) { + ws = workspace_output_prev(old_output); + } else if (strcasecmp(argv[0], "back_and_forth") == 0) { + // if auto_back_and_forth is enabled, workspace_switch will swap + // the workspaces. If we created prev_workspace here, workspace_switch + // would put us back on original workspace. + if (config->auto_back_and_forth) { + ws = old_workspace; + } else if (prev_workspace_name + && !(ws = workspace_by_name(prev_workspace_name))) { + ws = workspace_create(prev_workspace_name); + } + } else { + char *name = join_args(argv, argc); + if (!(ws = workspace_by_name(name))) { + ws = workspace_create(name); + } + free(name); + } + workspace_switch(ws); + current_container = config->handler_context.seat->focus; + swayc_t *new_output = swayc_parent_by_type(current_container, C_OUTPUT); + + if (config->mouse_warping && old_output != new_output) { + // TODO: Warp mouse + } + } + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/config.c b/sway/config.c index a67322d1..213e7680 100644 --- a/sway/config.c +++ b/sway/config.c @@ -318,10 +318,6 @@ static bool load_config(const char *path, struct sway_config *config) { return true; } -static int qstrcmp(const void* a, const void* b) { - return strcmp(*((char**) a), *((char**) b)); -} - bool load_main_config(const char *file, bool is_active) { char *path; if (file != NULL) { @@ -349,7 +345,9 @@ bool load_main_config(const char *file, bool is_active) { config->reading = true; // Read security configs + // TODO: Security bool success = true; + /* DIR *dir = opendir(SYSCONFDIR "/sway/security.d"); if (!dir) { wlr_log(L_ERROR, @@ -392,6 +390,7 @@ bool load_main_config(const char *file, bool is_active) { free_flat_list(secconfigs); } + */ success = success && load_config(path, config); @@ -717,3 +716,11 @@ char *do_var_replacement(char *str) { } return str; } + +// the naming is intentional (albeit long): a workspace_output_cmp function +// would compare two structs in full, while this method only compares the +// workspace. +int workspace_output_cmp_workspace(const void *a, const void *b) { + const struct workspace_output *wsa = a, *wsb = b; + return lenient_strcmp(wsa->workspace, wsb->workspace); +} diff --git a/sway/desktop/output.c b/sway/desktop/output.c index 0f00222b..a650665f 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -219,8 +219,8 @@ static void output_frame_notify(struct wl_listener *listener, void *data) { wlr_output_make_current(wlr_output); wlr_renderer_begin(server->renderer, wlr_output); - swayc_descendants_of_type( - &root_container, C_VIEW, output_frame_view, soutput); + swayc_t *workspace = soutput->swayc->focused; + swayc_descendants_of_type(workspace, C_VIEW, output_frame_view, soutput); // render unmanaged views on top struct sway_view *view; diff --git a/sway/desktop/xdg_shell_v6.c b/sway/desktop/xdg_shell_v6.c index 4b50093f..ca56a9c0 100644 --- a/sway/desktop/xdg_shell_v6.c +++ b/sway/desktop/xdg_shell_v6.c @@ -124,8 +124,6 @@ void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data) { sway_surface->view = sway_view; // TODO: - // - Wire up listeners - // - Handle popups // - Look up pid and open on appropriate workspace // - Set new view to maximized so it behaves nicely // - Criteria @@ -136,11 +134,8 @@ void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data) { sway_surface->destroy.notify = handle_destroy; wl_signal_add(&xdg_surface->events.destroy, &sway_surface->destroy); - // TODO: actual focus semantics - swayc_t *parent = root_container.children->items[0]; - parent = parent->children->items[0]; // workspace - - swayc_t *cont = new_view(parent, sway_view); + struct sway_seat *seat = input_manager_current_seat(input_manager); + swayc_t *cont = new_view(seat->focus, sway_view); sway_view->swayc = cont; arrange_windows(cont->parent, -1, -1); diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c index 12b3a430..d789c7eb 100644 --- a/sway/input/input-manager.c +++ b/sway/input/input-manager.c @@ -23,6 +23,14 @@ struct sway_input_manager *input_manager; struct input_config *current_input_config = NULL; struct seat_config *current_seat_config = NULL; +struct sway_seat *input_manager_current_seat(struct sway_input_manager *input) { + struct sway_seat *seat = config->handler_context.seat; + if (!seat) { + seat = sway_input_manager_get_default_seat(input_manager); + } + return seat; +} + struct sway_seat *input_manager_get_seat( struct sway_input_manager *input, const char *seat_name) { struct sway_seat *seat = NULL; diff --git a/sway/input/seat.c b/sway/input/seat.c index 9ea08eec..5e87986d 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -1,6 +1,7 @@ #define _XOPEN_SOURCE 700 #include #include +#include "sway/container.h" #include "sway/input/seat.h" #include "sway/input/cursor.h" #include "sway/input/input-manager.h" @@ -81,7 +82,7 @@ static void seat_configure_keyboard(struct sway_seat *seat, sway_keyboard_configure(seat_device->keyboard); wlr_seat_set_keyboard(seat->wlr_seat, seat_device->input_device->wlr_device); - if (seat->focus) { + if (seat->focus && seat->focus->type == C_VIEW) { // force notify reenter to pick up the new configuration wlr_seat_keyboard_clear_focus(seat->wlr_seat); wlr_seat_keyboard_notify_enter(seat->wlr_seat, @@ -205,10 +206,8 @@ void sway_seat_configure_xcursor(struct sway_seat *seat) { static void handle_focus_destroy(struct wl_listener *listener, void *data) { struct sway_seat *seat = wl_container_of(listener, seat, focus_destroy); - //swayc_t *container = data; - - // TODO set new focus based on the state of the tree - sway_seat_set_focus(seat, NULL); + swayc_t *container = data; + sway_seat_set_focus(seat, container->parent); } void sway_seat_set_focus(struct sway_seat *seat, swayc_t *container) { @@ -218,11 +217,11 @@ void sway_seat_set_focus(struct sway_seat *seat, swayc_t *container) { return; } - if (last_focus) { + if (last_focus && last_focus->type == C_VIEW) { wl_list_remove(&seat->focus_destroy.link); } - if (container) { + if (container && container->type == C_VIEW) { struct sway_view *view = container->sway_view; view_set_activated(view, true); wl_signal_add(&container->events.destroy, &seat->focus_destroy); @@ -241,7 +240,7 @@ void sway_seat_set_focus(struct sway_seat *seat, swayc_t *container) { seat->focus = container; - if (last_focus && + if (last_focus && last_focus->type == C_VIEW && !sway_input_manager_has_focus(seat->input, last_focus)) { struct sway_view *view = last_focus->sway_view; view_set_activated(view, false); diff --git a/sway/meson.build b/sway/meson.build index 51e9e4db..271d4a99 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -35,6 +35,7 @@ sway_sources = files( 'commands/input/xkb_variant.c', 'commands/output.c', 'commands/reload.c', + 'commands/workspace.c', 'config.c', 'config/output.c', 'config/seat.c', diff --git a/sway/tree/container.c b/sway/tree/container.c index b7b9bc68..48aabd86 100644 --- a/sway/tree/container.c +++ b/sway/tree/container.c @@ -3,10 +3,13 @@ #include #include #include +#include #include #include #include "sway/config.h" #include "sway/container.h" +#include "sway/input/input-manager.h" +#include "sway/input/seat.h" #include "sway/layout.h" #include "sway/output.h" #include "sway/server.h" @@ -14,6 +17,26 @@ #include "sway/workspace.h" #include "log.h" +swayc_t *swayc_by_test(swayc_t *container, + bool (*test)(swayc_t *view, void *data), void *data) { + if (!container->children) { + return NULL; + } + // TODO: floating windows + for (int i = 0; i < container->children->length; ++i) { + swayc_t *child = container->children->items[i]; + if (test(child, data)) { + return child; + } else { + swayc_t *res = swayc_by_test(child, test, data); + if (res) { + return res; + } + } + } + return NULL; +} + void swayc_descendants_of_type(swayc_t *root, enum swayc_types type, void (*func)(swayc_t *item, void *data), void *data) { for (int i = 0; i < root->children->length; ++i) { @@ -127,7 +150,19 @@ swayc_t *new_output(struct sway_output *sway_output) { // Create workspace char *ws_name = workspace_next_name(output->name); wlr_log(L_DEBUG, "Creating default workspace %s", ws_name); - new_workspace(output, ws_name); + swayc_t *ws = new_workspace(output, ws_name); + output->focused = ws; + // Set each seat's focus if not already set + // TODO FOCUS: this is probably stupid, we shouldn't define focus in two + // places. We should probably put the active workspace on the sway_output + // struct instead of trying to do focus semantics like this + struct sway_seat *seat = NULL; + wl_list_for_each(seat, &input_manager->seats, link) { + if (!seat->focus) { + seat->focus = ws; + } + } + free(ws_name); return output; } @@ -159,8 +194,8 @@ swayc_t *new_view(swayc_t *sibling, struct sway_view *sway_view) { } const char *title = view_get_title(sway_view); swayc_t *swayc = new_swayc(C_VIEW); - wlr_log(L_DEBUG, "Adding new view %p:%s to container %p %d", - swayc, title, sibling, sibling ? sibling->type : 0); + wlr_log(L_DEBUG, "Adding new view %p:%s to container %p %d %s", + swayc, title, sibling, sibling ? sibling->type : 0, sibling->name); // Setup values swayc->sway_view = sway_view; swayc->name = title ? strdup(title) : NULL; diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c index c37a873c..23c630b6 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c @@ -2,8 +2,20 @@ #include #include #include +#include #include "sway/container.h" +#include "sway/input/input-manager.h" +#include "sway/input/seat.h" +#include "sway/workspace.h" #include "log.h" +#include "util.h" + +char *prev_workspace_name = NULL; +struct workspace_by_number_data { + int len; + const char *cset; + const char *name; +}; void next_name_map(swayc_t *ws, void *data) { int *count = data; @@ -24,3 +36,202 @@ char *workspace_next_name(const char *output_name) { snprintf(name, len + 1, "%d", count); return name; } + +static bool _workspace_by_number(swayc_t *view, void *data) { + if (view->type != C_WORKSPACE) { + return false; + } + struct workspace_by_number_data *wbnd = data; + int a = strspn(view->name, wbnd->cset); + return a == wbnd->len && strncmp(view->name, wbnd->name, a) == 0; +} + +swayc_t *workspace_by_number(const char* name) { + struct workspace_by_number_data wbnd = {0, "1234567890", name}; + wbnd.len = strspn(name, wbnd.cset); + if (wbnd.len <= 0) { + return NULL; + } + return swayc_by_test(&root_container, _workspace_by_number, (void *) &wbnd); +} + +static bool _workspace_by_name(swayc_t *view, void *data) { + return (view->type == C_WORKSPACE) && + (strcasecmp(view->name, (char *) data) == 0); +} + +swayc_t *workspace_by_name(const char *name) { + struct sway_seat *seat = input_manager_current_seat(input_manager); + swayc_t *current_workspace = NULL, *current_output = NULL; + if (seat->focus) { + current_workspace = swayc_parent_by_type(seat->focus, C_WORKSPACE); + current_output = swayc_parent_by_type(seat->focus, C_OUTPUT); + } + if (strcmp(name, "prev") == 0) { + return workspace_prev(current_workspace); + } else if (strcmp(name, "prev_on_output") == 0) { + return workspace_output_prev(current_output); + } else if (strcmp(name, "next") == 0) { + return workspace_next(current_workspace); + } else if (strcmp(name, "next_on_output") == 0) { + return workspace_output_next(current_output); + } else if (strcmp(name, "current") == 0) { + return current_workspace; + } else { + return swayc_by_test(&root_container, _workspace_by_name, (void *) name); + } +} + +swayc_t *workspace_create(const char *name) { + swayc_t *parent; + // Search for workspace<->output pair + int i, e = config->workspace_outputs->length; + for (i = 0; i < e; ++i) { + struct workspace_output *wso = config->workspace_outputs->items[i]; + if (strcasecmp(wso->workspace, name) == 0) { + // Find output to use if it exists + e = root_container.children->length; + for (i = 0; i < e; ++i) { + parent = root_container.children->items[i]; + if (strcmp(parent->name, wso->output) == 0) { + return new_workspace(parent, name); + } + } + break; + } + } + // Otherwise create a new one + struct sway_seat *seat = input_manager_current_seat(input_manager); + parent = seat->focus; + parent = swayc_parent_by_type(parent, C_OUTPUT); + return new_workspace(parent, name); +} + +/** + * Get the previous or next workspace on the specified output. Wraps around at + * the end and beginning. If next is false, the previous workspace is returned, + * otherwise the next one is returned. + */ +swayc_t *workspace_output_prev_next_impl(swayc_t *output, bool next) { + if (!sway_assert(output->type == C_OUTPUT, + "Argument must be an output, is %d", output->type)) { + return NULL; + } + + int i; + for (i = 0; i < output->children->length; i++) { + if (output->children->items[i] == output->focused) { + return output->children->items[ + wrap(i + (next ? 1 : -1), output->children->length)]; + } + } + + // Doesn't happen, at worst the for loop returns the previously active workspace + return NULL; +} + +/** + * Get the previous or next workspace. If the first/last workspace on an output + * is active, proceed to the previous/next output's previous/next workspace. If + * next is false, the previous workspace is returned, otherwise the next one is + * returned. + */ +swayc_t *workspace_prev_next_impl(swayc_t *workspace, bool next) { + if (!sway_assert(workspace->type == C_WORKSPACE, + "Argument must be a workspace, is %d", workspace->type)) { + return NULL; + } + + swayc_t *current_output = workspace->parent; + int offset = next ? 1 : -1; + int start = next ? 0 : 1; + int end; + if (next) { + end = current_output->children->length - 1; + } else { + end = current_output->children->length; + } + int i; + for (i = start; i < end; i++) { + if (current_output->children->items[i] == workspace) { + return current_output->children->items[i + offset]; + } + } + + // Given workspace is the first/last on the output, jump to the previous/next output + int num_outputs = root_container.children->length; + for (i = 0; i < num_outputs; i++) { + if (root_container.children->items[i] == current_output) { + swayc_t *next_output = root_container.children->items[ + wrap(i + offset, num_outputs)]; + return workspace_output_prev_next_impl(next_output, next); + } + } + + // Doesn't happen, at worst the for loop returns the previously active workspace on the active output + return NULL; +} + +swayc_t *workspace_output_next(swayc_t *current) { + return workspace_output_prev_next_impl(current, true); +} + +swayc_t *workspace_next(swayc_t *current) { + return workspace_prev_next_impl(current, true); +} + +swayc_t *workspace_output_prev(swayc_t *current) { + return workspace_output_prev_next_impl(current, false); +} + +swayc_t *workspace_prev(swayc_t *current) { + return workspace_prev_next_impl(current, false); +} + +bool workspace_switch(swayc_t *workspace) { + if (!workspace) { + return false; + } + struct sway_seat *seat = input_manager_current_seat(input_manager); + if (!seat || !seat->focus) { + return false; + } + swayc_t *active_ws = seat->focus; + if (active_ws->type != C_WORKSPACE) { + swayc_parent_by_type(seat->focus, C_WORKSPACE); + } + + if (config->auto_back_and_forth + && active_ws == workspace + && prev_workspace_name) { + swayc_t *new_ws = workspace_by_name(prev_workspace_name); + workspace = new_ws ? new_ws : workspace_create(prev_workspace_name); + } + + if (!prev_workspace_name || (strcmp(prev_workspace_name, active_ws->name) + && active_ws != workspace)) { + free(prev_workspace_name); + prev_workspace_name = malloc(strlen(active_ws->name) + 1); + if (!prev_workspace_name) { + wlr_log(L_ERROR, "Unable to allocate previous workspace name"); + return false; + } + strcpy(prev_workspace_name, active_ws->name); + } + + // TODO: Deal with sticky containers + + wlr_log(L_DEBUG, "Switching to workspace %p:%s", workspace, workspace->name); + // TODO FOCUS: Focus the last view this seat had focused on this workspace + if (workspace->children->length) { + // TODO FOCUS: This is really fucking stupid + sway_seat_set_focus(seat, workspace->children->items[0]); + } else { + sway_seat_set_focus(seat, workspace); + } + swayc_t *output = swayc_parent_by_type(workspace, C_OUTPUT); + // TODO FOCUS: take a look at this + output->focused = workspace; + arrange_windows(output, -1, -1); + return true; +}