mirror of
https://github.com/swaywm/sway.git
synced 2026-06-13 14:33:19 -04:00
ipc-json,swaysavetree: round-trip placeholder swallows
ipc_json_describe_container emits the original swallows JSON for unfilled placeholders. New sway-save-tree binary dumps a workspace tiling tree as append_layout-compatible JSON, synthesising swallows from app_id / class / instance.
This commit is contained in:
parent
40bb60a91d
commit
cc4ba936e9
8 changed files with 319 additions and 0 deletions
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
struct sway_view;
|
||||
struct sway_seat;
|
||||
struct json_object;
|
||||
|
||||
enum sway_container_layout {
|
||||
L_NONE,
|
||||
|
|
@ -146,6 +147,7 @@ struct sway_container {
|
|||
// i3-shaped array so IPC can echo it verbatim for round-trip.
|
||||
bool is_placeholder;
|
||||
list_t *swallows; // struct criteria *
|
||||
struct json_object *swallows_json;
|
||||
|
||||
struct {
|
||||
struct wl_signal destroy;
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@ subdir('protocols')
|
|||
subdir('common')
|
||||
subdir('sway')
|
||||
subdir('swaymsg')
|
||||
subdir('swaysavetree')
|
||||
|
||||
if get_option('swaybar') or get_option('swaynag')
|
||||
subdir('client')
|
||||
|
|
|
|||
|
|
@ -776,6 +776,11 @@ static void ipc_json_describe_container(struct sway_container *c, json_object *o
|
|||
if (c->view) {
|
||||
ipc_json_describe_view(c, object);
|
||||
}
|
||||
|
||||
if (c->is_placeholder && c->swallows_json) {
|
||||
json_object_object_add(object, "swallows",
|
||||
json_object_get(c->swallows_json));
|
||||
}
|
||||
}
|
||||
|
||||
struct focus_inactive_data {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <wlr/types/wlr_linux_dmabuf_v1.h>
|
||||
#include <wlr/types/wlr_output_layout.h>
|
||||
#include <wlr/types/wlr_subcompositor.h>
|
||||
#include <json.h>
|
||||
#include "sway/config.h"
|
||||
#include "sway/criteria.h"
|
||||
#include "sway/desktop/transaction.h"
|
||||
|
|
@ -471,6 +472,9 @@ void container_destroy(struct sway_container *con) {
|
|||
}
|
||||
list_free(con->swallows);
|
||||
}
|
||||
if (con->swallows_json) {
|
||||
json_object_put(con->swallows_json);
|
||||
}
|
||||
|
||||
if (con->view && con->view->container == con) {
|
||||
con->view->container = NULL;
|
||||
|
|
|
|||
|
|
@ -363,6 +363,8 @@ static struct sway_container *build_node(struct json_object *obj,
|
|||
}
|
||||
c->is_placeholder = true;
|
||||
c->swallows = sw;
|
||||
// Retained so IPC can echo it verbatim for round-trip.
|
||||
c->swallows_json = json_object_get(swallows_v);
|
||||
} else {
|
||||
// Leaf without swallows is an empty split with no children. i3 does
|
||||
// not produce these; treat as an error to avoid silently leaving
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#if WLR_HAS_XWAYLAND
|
||||
#include <wlr/xwayland.h>
|
||||
#endif
|
||||
#include <json.h>
|
||||
#include "list.h"
|
||||
#include "log.h"
|
||||
#include "sway/criteria.h"
|
||||
|
|
@ -823,6 +824,10 @@ static void promote_placeholder(struct sway_container *placeholder,
|
|||
list_free(placeholder->swallows);
|
||||
placeholder->swallows = NULL;
|
||||
}
|
||||
if (placeholder->swallows_json) {
|
||||
json_object_put(placeholder->swallows_json);
|
||||
placeholder->swallows_json = NULL;
|
||||
}
|
||||
placeholder->is_placeholder = false;
|
||||
|
||||
if (placeholder->pending.children) {
|
||||
|
|
|
|||
292
swaysavetree/main.c
Normal file
292
swaysavetree/main.c
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
// sway-save-tree: dump a workspace's tiling tree as JSON for `swaymsg
|
||||
// append_layout`. Counterpart to i3-save-tree(1).
|
||||
|
||||
#include <getopt.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <sys/time.h>
|
||||
#include <json.h>
|
||||
#include "ipc-client.h"
|
||||
#include "ipc.h"
|
||||
#include "log.h"
|
||||
|
||||
static void usage(void) {
|
||||
fprintf(stderr,
|
||||
"Usage: sway-save-tree --workspace <name|number>\n"
|
||||
"\n"
|
||||
"Dump the tiling tree of the named workspace as JSON suitable for\n"
|
||||
"`swaymsg append_layout`. Output is sent to stdout.\n"
|
||||
"\n"
|
||||
"Floating windows are skipped in this release.\n");
|
||||
}
|
||||
|
||||
// Anchored, metachar-escaped copy so swallows match the value literally.
|
||||
static char *anchored_regex(const char *s) {
|
||||
if (!s) {
|
||||
return NULL;
|
||||
}
|
||||
size_t n = strlen(s);
|
||||
char *out = malloc(n * 2 + 3);
|
||||
if (!out) {
|
||||
return NULL;
|
||||
}
|
||||
size_t pos = 0;
|
||||
out[pos++] = '^';
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
char c = s[i];
|
||||
if (strchr(".\\+*?()[]{}|^$", c)) {
|
||||
out[pos++] = '\\';
|
||||
}
|
||||
out[pos++] = c;
|
||||
}
|
||||
out[pos++] = '$';
|
||||
out[pos] = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
// Returns NULL if the view has nothing identifiable; caller drops the leaf.
|
||||
static struct json_object *synth_swallows(struct json_object *view_obj) {
|
||||
struct json_object *app_id = NULL, *wp = NULL;
|
||||
const char *class = NULL, *instance = NULL;
|
||||
|
||||
json_object_object_get_ex(view_obj, "app_id", &app_id);
|
||||
|
||||
if (json_object_object_get_ex(view_obj, "window_properties", &wp)) {
|
||||
struct json_object *cls, *inst;
|
||||
if (json_object_object_get_ex(wp, "class", &cls)) {
|
||||
class = json_object_get_string(cls);
|
||||
}
|
||||
if (json_object_object_get_ex(wp, "instance", &inst)) {
|
||||
instance = json_object_get_string(inst);
|
||||
}
|
||||
}
|
||||
|
||||
struct json_object *entry = json_object_new_object();
|
||||
bool added = false;
|
||||
|
||||
if (app_id && json_object_get_type(app_id) == json_type_string) {
|
||||
const char *s = json_object_get_string(app_id);
|
||||
if (s && *s) {
|
||||
char *re = anchored_regex(s);
|
||||
if (re) {
|
||||
json_object_object_add(entry, "app_id",
|
||||
json_object_new_string(re));
|
||||
free(re);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (class) {
|
||||
char *re = anchored_regex(class);
|
||||
if (re) {
|
||||
json_object_object_add(entry, "class",
|
||||
json_object_new_string(re));
|
||||
free(re);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
if (instance) {
|
||||
char *re = anchored_regex(instance);
|
||||
if (re) {
|
||||
json_object_object_add(entry, "instance",
|
||||
json_object_new_string(re));
|
||||
free(re);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!added) {
|
||||
json_object_put(entry);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct json_object *arr = json_object_new_array();
|
||||
json_object_array_add(arr, entry);
|
||||
return arr;
|
||||
}
|
||||
|
||||
static void copy_key(struct json_object *src, struct json_object *dst,
|
||||
const char *key) {
|
||||
struct json_object *v;
|
||||
if (json_object_object_get_ex(src, key, &v)) {
|
||||
json_object_object_add(dst, key, json_object_get(v));
|
||||
}
|
||||
}
|
||||
|
||||
static struct json_object *build_layout_node(struct json_object *src) {
|
||||
struct json_object *out = json_object_new_object();
|
||||
|
||||
copy_key(src, out, "layout");
|
||||
copy_key(src, out, "name");
|
||||
copy_key(src, out, "border");
|
||||
copy_key(src, out, "current_border_width");
|
||||
copy_key(src, out, "percent");
|
||||
copy_key(src, out, "marks");
|
||||
|
||||
// Unfilled placeholders carry their original swallows; pass it through.
|
||||
struct json_object *swallows;
|
||||
if (json_object_object_get_ex(src, "swallows", &swallows) &&
|
||||
json_object_is_type(swallows, json_type_array) &&
|
||||
json_object_array_length(swallows) > 0) {
|
||||
json_object_object_add(out, "swallows", json_object_get(swallows));
|
||||
return out;
|
||||
}
|
||||
|
||||
struct json_object *nodes;
|
||||
bool has_nodes = json_object_object_get_ex(src, "nodes", &nodes) &&
|
||||
json_object_is_type(nodes, json_type_array) &&
|
||||
json_object_array_length(nodes) > 0;
|
||||
|
||||
if (has_nodes) {
|
||||
struct json_object *out_nodes = json_object_new_array();
|
||||
size_t n = json_object_array_length(nodes);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
struct json_object *child = json_object_array_get_idx(nodes, i);
|
||||
struct json_object *built = build_layout_node(child);
|
||||
if (built) {
|
||||
json_object_array_add(out_nodes, built);
|
||||
}
|
||||
}
|
||||
if (json_object_array_length(out_nodes) == 0) {
|
||||
json_object_put(out_nodes);
|
||||
json_object_put(out);
|
||||
return NULL;
|
||||
}
|
||||
json_object_object_add(out, "nodes", out_nodes);
|
||||
return out;
|
||||
}
|
||||
|
||||
struct json_object *synth = synth_swallows(src);
|
||||
if (!synth) {
|
||||
json_object_put(out);
|
||||
return NULL;
|
||||
}
|
||||
json_object_object_add(out, "swallows", synth);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Recursive search of the tree for the requested workspace.
|
||||
static struct json_object *find_workspace(struct json_object *node,
|
||||
const char *name) {
|
||||
struct json_object *type;
|
||||
if (json_object_object_get_ex(node, "type", &type) &&
|
||||
json_object_get_type(type) == json_type_string &&
|
||||
strcmp(json_object_get_string(type), "workspace") == 0) {
|
||||
struct json_object *ws_name;
|
||||
if (json_object_object_get_ex(node, "name", &ws_name) &&
|
||||
strcmp(json_object_get_string(ws_name), name) == 0) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
struct json_object *nodes;
|
||||
if (json_object_object_get_ex(node, "nodes", &nodes) &&
|
||||
json_object_is_type(nodes, json_type_array)) {
|
||||
size_t n = json_object_array_length(nodes);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
struct json_object *child = json_object_array_get_idx(nodes, i);
|
||||
struct json_object *match = find_workspace(child, name);
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *workspace = NULL;
|
||||
static const struct option long_opts[] = {
|
||||
{"workspace", required_argument, NULL, 'w'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{0, 0, 0, 0 },
|
||||
};
|
||||
int opt;
|
||||
while ((opt = getopt_long(argc, argv, "w:h", long_opts, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 'w':
|
||||
workspace = optarg;
|
||||
break;
|
||||
case 'h':
|
||||
default:
|
||||
usage();
|
||||
return opt == 'h' ? 0 : 1;
|
||||
}
|
||||
}
|
||||
if (!workspace) {
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *socket_path = get_socketpath();
|
||||
if (!socket_path) {
|
||||
fprintf(stderr, "sway-save-tree: cannot find sway IPC socket\n");
|
||||
return 1;
|
||||
}
|
||||
int fd = ipc_open_socket(socket_path);
|
||||
free(socket_path);
|
||||
struct timeval timeout = {.tv_sec = 3, .tv_usec = 0};
|
||||
ipc_set_recv_timeout(fd, timeout);
|
||||
uint32_t len = 0;
|
||||
char *resp = ipc_single_command(fd, IPC_GET_TREE, "", &len);
|
||||
if (!resp) {
|
||||
fprintf(stderr, "sway-save-tree: GET_TREE IPC failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct json_tokener *tok = json_tokener_new();
|
||||
struct json_object *root = json_tokener_parse_ex(tok, resp, len);
|
||||
json_tokener_free(tok);
|
||||
free(resp);
|
||||
if (!root) {
|
||||
fprintf(stderr, "sway-save-tree: failed to parse GET_TREE response\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct json_object *ws = find_workspace(root, workspace);
|
||||
if (!ws) {
|
||||
fprintf(stderr, "sway-save-tree: workspace '%s' not found\n",
|
||||
workspace);
|
||||
json_object_put(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct json_object *floating;
|
||||
if (json_object_object_get_ex(ws, "floating_nodes", &floating) &&
|
||||
json_object_is_type(floating, json_type_array) &&
|
||||
json_object_array_length(floating) > 0) {
|
||||
fprintf(stderr, "sway-save-tree: ignoring %zu floating window(s) on "
|
||||
"workspace '%s' (tiling-only in this release)\n",
|
||||
json_object_array_length(floating), workspace);
|
||||
}
|
||||
|
||||
struct json_object *nodes;
|
||||
if (!json_object_object_get_ex(ws, "nodes", &nodes) ||
|
||||
!json_object_is_type(nodes, json_type_array) ||
|
||||
json_object_array_length(nodes) == 0) {
|
||||
fprintf(stderr, "sway-save-tree: workspace '%s' has no tiling "
|
||||
"children\n", workspace);
|
||||
json_object_put(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct json_object *out = json_object_new_array();
|
||||
size_t n = json_object_array_length(nodes);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
struct json_object *child = json_object_array_get_idx(nodes, i);
|
||||
struct json_object *built = build_layout_node(child);
|
||||
if (built) {
|
||||
json_object_array_add(out, built);
|
||||
}
|
||||
}
|
||||
|
||||
const char *str = json_object_to_json_string_ext(out,
|
||||
JSON_C_TO_STRING_PRETTY);
|
||||
puts(str);
|
||||
|
||||
json_object_put(out);
|
||||
json_object_put(root);
|
||||
return 0;
|
||||
}
|
||||
8
swaysavetree/meson.build
Normal file
8
swaysavetree/meson.build
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
executable(
|
||||
'sway-save-tree',
|
||||
'main.c',
|
||||
include_directories: [sway_inc],
|
||||
dependencies: [jsonc],
|
||||
link_with: [lib_sway_common],
|
||||
install: true
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue