mirror of
https://github.com/swaywm/sway.git
synced 2026-04-28 06:46:26 -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,
|
||||
int amount);
|
||||
|
||||
sway_cmd cmd_append_layout;
|
||||
sway_cmd cmd_assign;
|
||||
sway_cmd cmd_bar;
|
||||
sway_cmd cmd_bindcode;
|
||||
|
|
|
|||
|
|
@ -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
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 */
|
||||
static struct cmd_handler handlers[] = {
|
||||
{ "append_layout", cmd_append_layout },
|
||||
{ "assign", cmd_assign },
|
||||
{ "bar", cmd_bar },
|
||||
{ "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/input.c',
|
||||
|
||||
'commands/append_layout.c',
|
||||
'commands/assign.c',
|
||||
'commands/bar.c',
|
||||
'commands/bind.c',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue