This commit is contained in:
Ryan Dwyer 2018-10-29 23:28:23 +10:00
parent 1c2a356dcf
commit 791c216cb8
6 changed files with 450 additions and 0 deletions

View file

@ -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;

View file

@ -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.

112
sway-save-tree/sway-save-tree Executable file
View file

@ -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)

View file

@ -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 },

View file

@ -0,0 +1,334 @@
#define _XOPEN_SOURCE 500
#include <ctype.h>
#include <json-c/json.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#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);
}

View file

@ -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',