diff --git a/include/sway/criteria.h b/include/sway/criteria.h index fad278e02..c70299419 100644 --- a/include/sway/criteria.h +++ b/include/sway/criteria.h @@ -84,8 +84,9 @@ struct criteria *criteria_parse(char *raw, char **error); list_t *criteria_for_view(struct sway_view *view, enum criteria_type types); /** - * Compile a list of containers matching the given criteria. + * Compile a list of nodes matching the given criteria. + * Returns a list of struct sway_node *. */ -list_t *criteria_get_containers(struct criteria *criteria); +list_t *criteria_get_nodes(struct criteria *criteria); #endif diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h index 27ed649fd..538cf85d4 100644 --- a/include/sway/tree/workspace.h +++ b/include/sway/tree/workspace.h @@ -48,6 +48,7 @@ struct sway_workspace { list_t *floating; // struct sway_container list_t *tiling; // struct sway_container list_t *output_priority; + list_t *marks; // char * bool urgent; struct sway_workspace_state current; @@ -157,4 +158,14 @@ size_t workspace_num_sticky_containers(struct sway_workspace *ws); */ void workspace_squash(struct sway_workspace *workspace); +bool workspace_has_mark(struct sway_workspace *ws, char *mark); + +void workspace_add_mark(struct sway_workspace *ws, char *mark); + +void workspace_clear_marks(struct sway_workspace *ws); + +bool workspace_find_and_unmark(char *mark); + +struct sway_workspace *workspace_find_mark(char *mark); + #endif diff --git a/sway/commands.c b/sway/commands.c index c2c12ee65..f767927ad 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -206,7 +206,7 @@ list_t *execute_command(char *_exec, struct sway_seat *seat, struct sway_container *con) { char *cmd; char matched_delim = ';'; - list_t *containers = NULL; + list_t *nodes = NULL; bool using_criteria = false; if (seat == NULL) { @@ -241,8 +241,8 @@ list_t *execute_command(char *_exec, struct sway_seat *seat, free(error); goto cleanup; } - list_free(containers); - containers = criteria_get_containers(criteria); + list_free(nodes); + nodes = criteria_get_nodes(criteria); head += strlen(criteria->raw); criteria_destroy(criteria); using_criteria = true; @@ -298,14 +298,14 @@ list_t *execute_command(char *_exec, struct sway_seat *seat, free_argv(argc, argv); goto cleanup; } - } else if (containers->length == 0) { + } else if (nodes->length == 0) { list_add(res_list, cmd_results_new(CMD_FAILURE, "No matching node.")); } else { struct cmd_results *fail_res = NULL; - for (int i = 0; i < containers->length; ++i) { - struct sway_container *container = containers->items[i]; - set_config_node(&container->node, true); + for (int i = 0; i < nodes->length; ++i) { + struct sway_node *node = nodes->items[i]; + set_config_node(node, true); struct cmd_results *res = handler->handle(argc-1, argv+1); if (res->status == CMD_SUCCESS) { free_cmd_results(res); @@ -329,7 +329,7 @@ list_t *execute_command(char *_exec, struct sway_seat *seat, } while(head); cleanup: free(exec); - list_free(containers); + list_free(nodes); return res_list; } diff --git a/sway/commands/mark.c b/sway/commands/mark.c index 77c8d2394..4bb960fcb 100644 --- a/sway/commands/mark.c +++ b/sway/commands/mark.c @@ -2,6 +2,7 @@ #include "sway/commands.h" #include "sway/config.h" #include "sway/tree/view.h" +#include "sway/tree/workspace.h" #include "list.h" #include "log.h" #include "stringop.h" @@ -18,8 +19,13 @@ struct cmd_results *cmd_mark(int argc, char **argv) { return error; } struct sway_container *container = config->handler_context.container; - if (!container) { - return cmd_results_new(CMD_INVALID, "Only containers can have marks"); + struct sway_workspace *workspace = config->handler_context.workspace; + + // If no container but we have a workspace (targeted via con_id criteria), + // apply marks to the workspace + if (!container && !workspace) { + return cmd_results_new(CMD_INVALID, + "Only containers and workspaces can have marks"); } bool add = false, toggle = false; @@ -44,23 +50,44 @@ struct cmd_results *cmd_mark(int argc, char **argv) { } char *mark = join_args(argv, argc); - bool had_mark = container_has_mark(container, mark); - if (!add) { - // Replacing - container_clear_marks(container); - } + if (container) { + bool had_mark = container_has_mark(container, mark); - container_find_and_unmark(mark); + if (!add) { + // Replacing + container_clear_marks(container); + } - if (!toggle || !had_mark) { - container_add_mark(container, mark); - } + container_find_and_unmark(mark); + workspace_find_and_unmark(mark); - free(mark); - container_update_marks(container); - if (container->view) { - view_execute_criteria(container->view); + if (!toggle || !had_mark) { + container_add_mark(container, mark); + } + + free(mark); + container_update_marks(container); + if (container->view) { + view_execute_criteria(container->view); + } + } else { + // Workspace + bool had_mark = workspace_has_mark(workspace, mark); + + if (!add) { + // Replacing + workspace_clear_marks(workspace); + } + + container_find_and_unmark(mark); + workspace_find_and_unmark(mark); + + if (!toggle || !had_mark) { + workspace_add_mark(workspace, mark); + } + + free(mark); } return cmd_results_new(CMD_SUCCESS, NULL); diff --git a/sway/commands/move.c b/sway/commands/move.c index 90e8585b4..09f94c9f3 100644 --- a/sway/commands/move.c +++ b/sway/commands/move.c @@ -525,11 +525,17 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth, destination = seat_get_focus_inactive(seat, &new_output->node); } else if (strcasecmp(argv[0], "mark") == 0) { struct sway_container *dest_con = container_find_mark(argv[1]); - if (dest_con == NULL) { - return cmd_results_new(CMD_FAILURE, - "Mark '%s' not found", argv[1]); + if (dest_con) { + destination = &dest_con->node; + } else { + struct sway_workspace *dest_ws = workspace_find_mark(argv[1]); + if (dest_ws) { + destination = &dest_ws->node; + } else { + return cmd_results_new(CMD_FAILURE, + "Mark '%s' not found", argv[1]); + } } - destination = &dest_con->node; } else { return cmd_results_new(CMD_INVALID, "%s", expected_syntax); } @@ -560,8 +566,33 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth, root_scratchpad_show(container); } switch (destination->type) { - case N_WORKSPACE: - container_move_to_workspace(container, destination->sway_workspace); + case N_WORKSPACE: { + struct sway_workspace *dest_ws = destination->sway_workspace; + // Match i3's con_move_to_target behavior: + // - Empty workspace: move directly to workspace + // - Non-empty workspace: descend to focused child, add as sibling + // (i3's _con_move_to_con goes up to parent, making con a sibling) + if (workspace_is_empty(dest_ws)) { + container_move_to_workspace(container, dest_ws); + } else { + struct sway_node *focus = + seat_get_focus_inactive(seat, destination); + if (focus && focus->type == N_CONTAINER) { + struct sway_container *focus_con = focus->sway_container; + // Match i3: add as sibling of focused child, not as child of it + if (container != focus_con && + !container_has_ancestor(focus_con, container)) { + container_detach(container); + container->pending.width = container->pending.height = 0; + container->width_fraction = container->height_fraction = 0; + container_add_sibling(focus_con, container, 1); + if (container->view) { + ipc_event_window(container, "move"); + } + } + } + } + } break; case N_OUTPUT: { struct sway_output *output = destination->sway_output; @@ -573,8 +604,32 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth, container_move_to_workspace(container, ws); } break; - case N_CONTAINER: - container_move_to_container(container, destination->sway_container); + case N_CONTAINER: { + struct sway_container *dest_con = destination->sway_container; + // Match i3's con_move_to_target behavior: + // If destination is a split (not a leaf), descend to focused child + // and add as sibling of that child + if (!dest_con->view) { + struct sway_node *focus = + seat_get_focus_inactive(seat, destination); + if (focus && focus->type == N_CONTAINER) { + struct sway_container *focus_con = focus->sway_container; + // Add as sibling of focused child + if (container != focus_con && + !container_has_ancestor(focus_con, container)) { + container_detach(container); + container->pending.width = container->pending.height = 0; + container->width_fraction = container->height_fraction = 0; + container_add_sibling(focus_con, container, 1); + if (container->view) { + ipc_event_window(container, "move"); + } + } + } + } else { + container_move_to_container(container, dest_con); + } + } break; case N_ROOT: break; diff --git a/sway/commands/unmark.c b/sway/commands/unmark.c index 4aba5bae0..e4b585a0a 100644 --- a/sway/commands/unmark.c +++ b/sway/commands/unmark.c @@ -3,29 +3,38 @@ #include "sway/config.h" #include "sway/tree/root.h" #include "sway/tree/view.h" +#include "sway/tree/workspace.h" #include "list.h" #include "log.h" #include "stringop.h" -static void remove_mark(struct sway_container *con) { +static void remove_container_mark(struct sway_container *con) { container_clear_marks(con); container_update_marks(con); } -static void remove_all_marks_iterator(struct sway_container *con, void *data) { - remove_mark(con); +static void remove_all_container_marks_iterator(struct sway_container *con, void *data) { + remove_container_mark(con); } -// unmark Remove all marks from all views -// unmark foo Remove single mark from whichever view has it -// [criteria] unmark Remove all marks from matched view -// [criteria] unmark foo Remove single mark from matched view +static void remove_all_workspace_marks_iterator(struct sway_workspace *ws, void *data) { + workspace_clear_marks(ws); +} + +// unmark Remove all marks from all views/workspaces +// unmark foo Remove single mark from whichever view/workspace has it +// [criteria] unmark Remove all marks from matched view/workspace +// [criteria] unmark foo Remove single mark from matched view/workspace struct cmd_results *cmd_unmark(int argc, char **argv) { - // Determine the container + // Determine the container or workspace struct sway_container *con = NULL; + struct sway_workspace *ws = NULL; if (config->handler_context.node_overridden) { con = config->handler_context.container; + if (!con) { + ws = config->handler_context.workspace; + } } // Determine the mark @@ -41,13 +50,23 @@ struct cmd_results *cmd_unmark(int argc, char **argv) { } } else if (con && !mark) { // Clear all marks from the given container - remove_mark(con); - } else if (!con && mark) { - // Remove mark from whichever container has it + remove_container_mark(con); + } else if (ws && mark) { + // Remove the mark from the given workspace + if (workspace_has_mark(ws, mark)) { + workspace_find_and_unmark(mark); + } + } else if (ws && !mark) { + // Clear all marks from the given workspace + workspace_clear_marks(ws); + } else if (!con && !ws && mark) { + // Remove mark from whichever container/workspace has it container_find_and_unmark(mark); + workspace_find_and_unmark(mark); } else { - // Remove all marks from all containers - root_for_each_container(remove_all_marks_iterator, NULL); + // Remove all marks from all containers and workspaces + root_for_each_container(remove_all_container_marks_iterator, NULL); + root_for_each_workspace(remove_all_workspace_marks_iterator, NULL); } free(mark); diff --git a/sway/criteria.c b/sway/criteria.c index e200d4c8f..e9eafc12b 100644 --- a/sway/criteria.c +++ b/sway/criteria.c @@ -493,27 +493,63 @@ struct match_data { list_t *matches; }; -static void criteria_get_containers_iterator(struct sway_container *container, +static void criteria_get_nodes_container_iterator(struct sway_container *container, void *data) { struct match_data *match_data = data; if (container->view) { if (criteria_matches_view(match_data->criteria, container->view)) { - list_add(match_data->matches, container); + list_add(match_data->matches, &container->node); } } else if (has_container_criteria(match_data->criteria)) { if (criteria_matches_container(match_data->criteria, container)) { - list_add(match_data->matches, container); + list_add(match_data->matches, &container->node); } } } -list_t *criteria_get_containers(struct criteria *criteria) { +static bool criteria_matches_workspace(struct criteria *criteria, + struct sway_workspace *workspace) { + if (criteria->con_mark) { + bool exists = false; + for (int i = 0; i < workspace->marks->length; ++i) { + if (regex_cmp(workspace->marks->items[i], criteria->con_mark->regex) >= 0) { + exists = true; + break; + } + } + if (!exists) { + return false; + } + } + + if (criteria->con_id) { + if (workspace->node.id != criteria->con_id) { + return false; + } + } + + return true; +} + +static void criteria_get_nodes_workspace_iterator(struct sway_workspace *workspace, + void *data) { + struct match_data *match_data = data; + if (criteria_matches_workspace(match_data->criteria, workspace)) { + list_add(match_data->matches, &workspace->node); + } +} + +list_t *criteria_get_nodes(struct criteria *criteria) { list_t *matches = create_list(); struct match_data data = { .criteria = criteria, .matches = matches, }; - root_for_each_container(criteria_get_containers_iterator, &data); + root_for_each_container(criteria_get_nodes_container_iterator, &data); + // Also check workspaces for con_id and con_mark matching + if (criteria->con_id || criteria->con_mark) { + root_for_each_workspace(criteria_get_nodes_workspace_iterator, &data); + } return matches; } diff --git a/sway/ipc-json.c b/sway/ipc-json.c index 3b69ad384..ae76f8ce1 100644 --- a/sway/ipc-json.c +++ b/sway/ipc-json.c @@ -530,6 +530,14 @@ static void ipc_json_describe_workspace(struct sway_workspace *workspace, json_object_new_string( ipc_json_orientation_description(workspace->layout))); + // Marks + json_object *marks = json_object_new_array(); + for (int i = 0; i < workspace->marks->length; ++i) { + json_object_array_add(marks, + json_object_new_string(workspace->marks->items[i])); + } + json_object_object_add(object, "marks", marks); + // Floating json_object *floating_array = json_object_new_array(); for (int i = 0; i < workspace->floating->length; ++i) { diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c index 733a002b4..e24fa0e7b 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c @@ -89,6 +89,7 @@ struct sway_workspace *workspace_create(struct sway_output *output, ws->floating = create_list(); ws->tiling = create_list(); ws->output_priority = create_list(); + ws->marks = create_list(); ws->gaps_outer = config->gaps_outer; ws->gaps_inner = config->gaps_inner; @@ -151,6 +152,7 @@ void workspace_destroy(struct sway_workspace *workspace) { free(workspace->name); free(workspace->representation); list_free_items_and_destroy(workspace->output_priority); + list_free_items_and_destroy(workspace->marks); list_free(workspace->floating); list_free(workspace->tiling); list_free(workspace->current.floating); @@ -979,3 +981,54 @@ void workspace_squash(struct sway_workspace *workspace) { i += container_squash(child); } } + +bool workspace_has_mark(struct sway_workspace *ws, char *mark) { + for (int i = 0; i < ws->marks->length; ++i) { + char *item = ws->marks->items[i]; + if (strcmp(item, mark) == 0) { + return true; + } + } + return false; +} + +void workspace_add_mark(struct sway_workspace *ws, char *mark) { + list_add(ws->marks, strdup(mark)); + ipc_event_workspace(NULL, ws, "mark"); +} + +void workspace_clear_marks(struct sway_workspace *ws) { + for (int i = 0; i < ws->marks->length; ++i) { + free(ws->marks->items[i]); + } + ws->marks->length = 0; + ipc_event_workspace(NULL, ws, "mark"); +} + +static bool find_by_mark_iterator_ws(struct sway_workspace *ws, void *data) { + char *mark = data; + return workspace_has_mark(ws, mark); +} + +struct sway_workspace *workspace_find_mark(char *mark) { + return root_find_workspace(find_by_mark_iterator_ws, mark); +} + +bool workspace_find_and_unmark(char *mark) { + struct sway_workspace *ws = root_find_workspace( + find_by_mark_iterator_ws, mark); + if (!ws) { + return false; + } + + for (int i = 0; i < ws->marks->length; ++i) { + char *ws_mark = ws->marks->items[i]; + if (strcmp(ws_mark, mark) == 0) { + free(ws_mark); + list_del(ws->marks, i); + ipc_event_workspace(NULL, ws, "mark"); + return true; + } + } + return false; +}