mirror of
https://github.com/swaywm/sway.git
synced 2026-04-29 06:46:22 -04:00
WIP
This commit is contained in:
parent
1c2a356dcf
commit
791c216cb8
6 changed files with 450 additions and 0 deletions
|
|
@ -97,6 +97,7 @@ struct cmd_results *add_color(const char *name,
|
||||||
void container_resize_tiled(struct sway_container *parent, enum wlr_edges edge,
|
void container_resize_tiled(struct sway_container *parent, enum wlr_edges edge,
|
||||||
int amount);
|
int amount);
|
||||||
|
|
||||||
|
sway_cmd cmd_append_layout;
|
||||||
sway_cmd cmd_assign;
|
sway_cmd cmd_assign;
|
||||||
sway_cmd cmd_bar;
|
sway_cmd cmd_bar;
|
||||||
sway_cmd cmd_bindcode;
|
sway_cmd cmd_bindcode;
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ struct sway_container_state {
|
||||||
struct sway_container {
|
struct sway_container {
|
||||||
struct sway_node node;
|
struct sway_node node;
|
||||||
struct sway_view *view;
|
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.
|
// 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.
|
// 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
112
sway-save-tree/sway-save-tree
Executable 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)
|
||||||
|
|
@ -60,6 +60,7 @@ void apply_seat_config(struct seat_config *seat_config) {
|
||||||
|
|
||||||
/* Keep alphabetized */
|
/* Keep alphabetized */
|
||||||
static struct cmd_handler handlers[] = {
|
static struct cmd_handler handlers[] = {
|
||||||
|
{ "append_layout", cmd_append_layout },
|
||||||
{ "assign", cmd_assign },
|
{ "assign", cmd_assign },
|
||||||
{ "bar", cmd_bar },
|
{ "bar", cmd_bar },
|
||||||
{ "bindcode", cmd_bindcode },
|
{ "bindcode", cmd_bindcode },
|
||||||
|
|
|
||||||
334
sway/commands/append_layout.c
Normal file
334
sway/commands/append_layout.c
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -31,6 +31,7 @@ sway_sources = files(
|
||||||
'config/seat.c',
|
'config/seat.c',
|
||||||
'config/input.c',
|
'config/input.c',
|
||||||
|
|
||||||
|
'commands/append_layout.c',
|
||||||
'commands/assign.c',
|
'commands/assign.c',
|
||||||
'commands/bar.c',
|
'commands/bar.c',
|
||||||
'commands/bind.c',
|
'commands/bind.c',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue