From 791c216cb82533aad0d4e60a0c08f8270ac12738 Mon Sep 17 00:00:00 2001 From: Ryan Dwyer Date: Mon, 29 Oct 2018 23:28:23 +1000 Subject: [PATCH] WIP --- include/sway/commands.h | 1 + include/sway/tree/container.h | 1 + sway-save-tree/sway-save-tree | 112 ++++++++++++ sway/commands.c | 1 + sway/commands/append_layout.c | 334 ++++++++++++++++++++++++++++++++++ sway/meson.build | 1 + 6 files changed, 450 insertions(+) create mode 100755 sway-save-tree/sway-save-tree create mode 100644 sway/commands/append_layout.c diff --git a/include/sway/commands.h b/include/sway/commands.h index 6606775aa..3297a334d 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -97,6 +97,7 @@ struct cmd_results *add_color(const char *name, void container_resize_tiled(struct sway_container *parent, enum wlr_edges edge, int amount); +sway_cmd cmd_append_layout; sway_cmd cmd_assign; sway_cmd cmd_bar; sway_cmd cmd_bindcode; diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h index 920ef0382..e47d80dca 100644 --- a/include/sway/tree/container.h +++ b/include/sway/tree/container.h @@ -69,6 +69,7 @@ struct sway_container_state { struct sway_container { struct sway_node node; struct sway_view *view; + list_t *swallow_criteria; // The pending state is the main container properties, and the current state is in the below struct. // This means most places of the code can refer to the main variables (pending state) and it'll just work. diff --git a/sway-save-tree/sway-save-tree b/sway-save-tree/sway-save-tree new file mode 100755 index 000000000..fe51b4033 --- /dev/null +++ b/sway-save-tree/sway-save-tree @@ -0,0 +1,112 @@ +#!/usr/bin/env python + +import argparse +import json +import re +import subprocess + +parser = argparse.ArgumentParser() +parser.add_argument('-s', '--socket', help='Socket') +parser.add_argument('-w', '--workspace', + help='Specifies the workspace that should be dumped, e.g. 1. ' \ + 'This can either be a name or the number of a workspace.') +parser.add_argument('-o', '--output', + help='Specifies the output that should be dumped, e.g. LVDS-1') +parser.add_argument('-v', '--version', action='store_true', help='Version') +args = parser.parse_args() + +if args.version: + print('sway-save-tree version %s' % '1.0.0') + exit(0) + +cmd = ['swaymsg', '-t', 'get_tree'] +if args.socket: + cmd.concat(['-s', args.socket]) + +result = subprocess.check_output(cmd) +tree = json.loads(result) + +def filter_tree(tree, args): + if args.workspace: + for output in tree['nodes']: + for ws in output['nodes']: + if ws['name'] == args.workspace: + return ws + if args.output: + for output in tree['nodes']: + if output['name'] == args.output: + return outut + + # Focused workspace + output = next(o for o in tree['nodes'] if o['id'] == tree['focus'][0]) + ws = next(ws for ws in output['nodes'] if ws['id'] == output['focus'][0]) + return ws + +root = filter_tree(tree, args) + +def prop(node, *keys): + for key in keys: + try: + node = node[key] + except KeyError: + return None + return re.escape('^%s$' % node) + +def convert_children(nodes): + children = [] + for node in nodes: + child = {} + has_children = len(node['nodes']) + if has_children: + child['_comment'] = '%s container with %i children' % ( + node['layout'], len(node['nodes'])) + child['border'] = node['border'] + child['current_border_width'] = node['current_border_width'] + child['floating'] = node['floating'] if 'floating' in node else 'auto_off' + if has_children: + child['layout'] = node['layout'] + child['geometry'] = node['rect'] + child['name'] = node['name'] + child['percent'] = round(node['percent'], 2) + child['swallows'] = {} + child['swallows']['app_id'] = prop(node, ('app_id')) + child['swallows']['class'] = prop(node, ('window_properties', 'class')) + child['swallows']['instance'] = prop(node, ('window_properties', 'instance')) + child['swallows']['title'] = prop(node, ('name')) + child['swallows']['transient_for'] = prop(node, ('window_properties', 'transient_for')) + child['type'] = node['type'] + if has_children: + child['nodes'] = convert_children(node['nodes']) + if len(node['floating_nodes']): + child['floating_nodes'] = convert_children(node['floating_nodes']) + if 'marks' in node and len(node['marks']): + child['marks'] = node['marks'] + if 'fullscreen_mode' in node and node['fullscreen_mode']: + child['fullscreen_mode'] = True + + children.append(child) + + return children + +# Build new tree containing only the properties we want +newtree = convert_children(root['nodes']) + +# Convert it to a JSON string. The root children list should not be an array, +# but rather concatenated json objects. +output = '' +for node in newtree: + output += json.dumps(node, indent=4) + "\n" +output = output.rstrip() + +# Replace the _comment element with an actual comment +output = re.sub(r'"_comment": "(.*?)",', '// \\1', output) + +# Comment the swallows criteria +output = re.sub(r'("app_id":)', '// \\1', output) +output = re.sub(r'("class":)', '// \\1', output) +output = re.sub(r'("instance":)', '// \\1', output) +output = re.sub(r'("title":)', '// \\1', output) +output = re.sub(r'("transient_for":)', '// \\1', output) + +print('// vim:ts=4:sw=4:et') +print(output) diff --git a/sway/commands.c b/sway/commands.c index 37c7169af..9ad82ed0e 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -60,6 +60,7 @@ void apply_seat_config(struct seat_config *seat_config) { /* Keep alphabetized */ static struct cmd_handler handlers[] = { + { "append_layout", cmd_append_layout }, { "assign", cmd_assign }, { "bar", cmd_bar }, { "bindcode", cmd_bindcode }, diff --git a/sway/commands/append_layout.c b/sway/commands/append_layout.c new file mode 100644 index 000000000..af2184a03 --- /dev/null +++ b/sway/commands/append_layout.c @@ -0,0 +1,334 @@ +#define _XOPEN_SOURCE 500 +#include +#include +#include +#include +#include +#include "log.h" +#include "sway/commands.h" +#include "sway/output.h" +#include "sway/tree/arrange.h" +#include "sway/tree/container.h" +#include "sway/tree/workspace.h" +#include "stringop.h" + +static enum sway_container_layout parse_layout(const char *layout) { + if (!layout) { + return L_NONE; + } + if (strcasecmp(layout, "splith") == 0) { + return L_HORIZ; + } else if (strcasecmp(layout, "splitv") == 0) { + return L_VERT; + } else if (strcasecmp(layout, "tabbed") == 0) { + return L_TABBED; + } else if (strcasecmp(layout, "stacked") == 0 || + strcasecmp(layout, "stacking") == 0) { + return L_STACKED; + } + return L_NONE; +} + +/** + * Parse a JSON object representing a container and return a container node. + * Child containers are also parsed and attached to the parent container. + */ +static struct sway_node *parse_container(json_object *object, char **error) { + struct sway_container *con = container_create(NULL); + // TODO: + // border + // current_border_width + // layout + // floating + // geometry (box) + // name + // percent + // swallows (app_id, class, instance, title, transient_for) + // marks + // fullscreen_mode + return &con->node; +} + +/** + * Parse a JSON object representing a workspace and return a workspace node. + * Child containers are also parsed and attached to the workspace. + */ +static struct sway_node *parse_workspace(json_object *object, char **error) { + json_object *tmp = NULL; + + // name + json_object_object_get_ex(object, "name", &tmp); + const char *name = json_object_get_string(tmp); + + if (!name) { + *error = strdup("Workspace 'name' element is missing"); + return NULL; + } + + struct sway_workspace *ws = workspace_create(NULL, name); + + // layout + json_object_object_get_ex(object, "layout", &tmp); + const char *layout = json_object_get_string(tmp); + + ws->layout = parse_layout(layout); + if (ws->layout == L_NONE) { + workspace_begin_destroy(ws); + int len = strlen("Unrecognized workspace layout ''") + strlen(layout); + *error = malloc(len + 1); + sprintf(*error, "Unrecognized workspace layout '%s'", layout); + return NULL; + } + + // TODO: Tiling and floating children + + return &ws->node; +} + +/** + * Parse a single JSON object which represents either a workspace or container, + * as well as all of its children. + */ +static struct sway_node *parse_object(json_object *object, char **error) { + json_object *tmp = NULL; + json_object_object_get_ex(object, "type", &tmp); + const char *type = json_object_get_string(tmp); + + if (type && strcasecmp(type, "con") == 0) { + return parse_container(object, error); + } else if (type && strcasecmp(type, "workspace") == 0) { + return parse_workspace(object, error); + } else { + int len = strlen("Unexpected type: ") + strlen(type); + *error = malloc(len + 1); + sprintf(*error, "Unexpected type: %s", type); + } + + return NULL; +} + +/** + * Parse the given null-terminated buffer and return a list of the root children + * nodes. + * + * If an error occurs, the error pointer will point to an error message and the + * parsed nodes will be returned. + */ +static list_t *parse_buffer(char *buffer, char **error) { + json_tokener *tokener = json_tokener_new(); + json_object *object = NULL; + list_t *root_children = create_list(); // Either workspaces or containers + bool done = false; + + while (!done) { + json_object_put(object); + object = json_tokener_parse_ex(tokener, buffer, strlen(buffer)); + enum json_tokener_error err = json_tokener_get_error(tokener); + + switch (err) { + case json_tokener_success: { + struct sway_node *node = parse_object(object, error); + if (*error) { + done = true; + } + list_add(root_children, node); + buffer += tokener->char_offset; + json_tokener_reset(tokener); + } + break; + case json_tokener_continue: // No more objects left to read + done = true; + break; + default: { // Error + const char *tokener_error = json_tokener_error_desc(err); + int len = strlen("Error parsing JSON: ") + strlen(tokener_error); + *error = malloc(len + 1); + sprintf(*error, "Error parsing JSON: %s", tokener_error); + done = true; + } + } + } + + json_object_put(object); + json_tokener_free(tokener); + + return root_children; +} + +/** + * Parse the given file and return a list of the root children nodes. + * The root children will usually be containers, but may be workspaces + * (if they were dumped with --output). + * + * If an error occurs, the error pointer will point to an error message and the + * parsed nodes will be returned. + */ +static list_t *parse_file(char *filename, char **error) { + wlr_log(WLR_DEBUG, "Reading '%s'", filename); + + FILE *fp = fopen(filename, "r"); + if (!fp) { + int len = strlen("Unable to open '' for reading") + strlen(filename); + *error = malloc(len + 1); + sprintf(*error, "Unable to open '%s' for reading", filename); + return NULL; + } + + fseek(fp, 0, SEEK_END); + size_t len = ftell(fp); + fseek(fp, 0, SEEK_SET); + + char *buffer = malloc(len + 1); + fread(buffer, len, 1, fp); + buffer[len] = '\0'; + fclose(fp); + + list_t *root_children = parse_buffer(buffer, error); + + free(buffer); + + return root_children; +} + +/** + * Free everything in the nodes list recursively. + */ +static void destroy_children(list_t *nodes) { + for (int i = 0; i < nodes->length; ++i) { + struct sway_node *node = nodes->items[i]; + switch (node->type) { + case N_CONTAINER: + if (node->sway_container->children->length) { + destroy_children(node->sway_container->children); + } + container_begin_destroy(node->sway_container); + break; + case N_WORKSPACE: + if (node->sway_workspace->tiling->length) { + destroy_children(node->sway_workspace->tiling); + } + if (node->sway_workspace->floating->length) { + destroy_children(node->sway_workspace->floating); + } + workspace_begin_destroy(node->sway_workspace); + break; + case N_OUTPUT: + case N_ROOT: + sway_assert(false, "Never reached"); + break; + } + } +} + +/** + * Free anything in the root_children list recursively. + * Used when the command is invalid and we need to discard what we've collected. + */ +static void destroy_root_children(list_t *root_children) { + if (root_children) { + destroy_children(root_children); + list_free(root_children); + } +} + +/** + * Append all the containers in the nodes list to the given workspace. + * At this point we know that the items in the nodes list are containers. + * The containers may contain child containers too, but we don't have to do + * anything with them here. + */ +static void workspace_append_containers( + struct sway_workspace *ws, list_t *nodes) { + for (int i = 0; i < nodes->length; ++i) { + struct sway_node *node = nodes->items[i]; + struct sway_container *con = node->sway_container; + workspace_add_tiling(ws, con); + } +} + +/** + * Append all the workspaces in the nodes list to the given output. + * At this point we know that the items in the nodes list are workspaces. + * The workspaces probably contain child containers too, but we don't have to do + * anything with them here. + */ +static void output_append_workspaces( + struct sway_output *output, list_t *nodes) { + for (int i = 0; i < nodes->length; ++i) { + struct sway_node *node = nodes->items[i]; + struct sway_workspace *ws = + workspace_by_name(node->sway_workspace->name); + if (ws) { + // A workspace already exists with this name, so copy some + // properties over before destroying our temporary workspace. + ws->layout = node->sway_workspace->layout; + list_cat(ws->tiling, node->sway_workspace->tiling); + list_cat(ws->floating, node->sway_workspace->floating); + node->sway_workspace->tiling->length = 0; + node->sway_workspace->floating->length = 0; + workspace_begin_destroy(node->sway_workspace); + } else { + ws = node->sway_workspace; + output_add_workspace(output, ws); + } + } + output_sort_workspaces(output); +} + +struct cmd_results *cmd_append_layout(int argc, char **argv) { + struct cmd_results *err = NULL; + if ((err = checkarg(argc, "append_layout", EXPECTED_AT_LEAST, 1))) { + return err; + } + char *error = NULL; + char *filename = join_args(argv, argc); + list_t *root_children = parse_file(filename, &error); + free(filename); + + if (error) { + struct cmd_results *result = cmd_results_new( + CMD_INVALID, "append_layout", error); + free(error); + destroy_root_children(root_children); + return result; + } + + if (root_children->length == 0) { + list_free(root_children); + return cmd_results_new(CMD_INVALID, "append_layout", + "Couldn't find any containers in your layout file"); + } + + // Make sure all the root children are the same type + bool have_workspace = false; + bool have_container = false; + for (int i = 0; i < root_children->length; ++i) { + struct sway_node *node = root_children->items[i]; + if (node->type == N_CONTAINER) { + have_container = true; + } else { + have_workspace = true; + } + } + if (have_workspace && have_container) { + destroy_root_children(root_children); + return cmd_results_new(CMD_INVALID, "append_layout", + "All root children must be the same type"); + } + + if (have_workspace) { + struct sway_seat *seat = config->handler_context.seat; + struct sway_node *focus = seat_get_focus_inactive(seat, &root->node); + struct sway_output *output = node_get_output(focus); + output_append_workspaces(output, root_children); + arrange_output(output); + } else { + struct sway_workspace *ws = config->handler_context.workspace; + workspace_append_containers(ws, root_children); + arrange_workspace(ws); + } + + list_free(root_children); + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/meson.build b/sway/meson.build index cde09a020..daef2e670 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -31,6 +31,7 @@ sway_sources = files( 'config/seat.c', 'config/input.c', + 'commands/append_layout.c', 'commands/assign.c', 'commands/bar.c', 'commands/bind.c',