* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
+#include <unistd.h>
#include "bspwm.h"
#include "desktop.h"
#include "ewmh.h"
#include "tree.h"
#include "settings.h"
#include "restore.h"
+#include "window.h"
+#include "parse.h"
-void restore_tree(char *file_path)
+bool restore_tree(const char *file_path)
{
- if (file_path == NULL)
- return;
+ size_t jslen;
+ char *json = read_string(file_path, &jslen);
- FILE *snapshot = fopen(file_path, "r");
- if (snapshot == NULL) {
- warn("Restore tree: can't open '%s'.\n", file_path);
- return;
+ if (json == NULL) {
+ return false;
}
- char line[MAXLEN];
- char name[MAXLEN];
- coordinates_t loc;
- monitor_t *m = NULL;
- desktop_t *d = NULL;
- node_t *n = NULL;
- unsigned int level, last_level = 0;
-
- while (fgets(line, sizeof(line), snapshot) != NULL) {
- unsigned int len = strlen(line);
- level = 0;
-
- while (level < len && isspace(line[level]))
- level++;
-
- if (level == 0) {
- int x, y, top, right, bottom, left;
- unsigned int w, h;
- char end = 0;
- name[0] = '\0';
- sscanf(line + level, "%s %ux%u%i%i %i,%i,%i,%i %c", name, &w, &h, &x, &y,
- &top, &right, &bottom, &left, &end);
- m = find_monitor(name);
- if (m == NULL)
- continue;
- m->rectangle = (xcb_rectangle_t) {x, y, w, h};
- m->top_padding = top;
- m->right_padding = right;
- m->bottom_padding = bottom;
- m->left_padding = left;
- if (end != 0)
- mon = m;
- } else if (level == 1) {
- if (m == NULL)
- continue;
- int wg, top, right, bottom, left;
- unsigned int bw;
- char layout = 0, end = 0;
- name[0] = '\0';
- loc.desktop = NULL;
- sscanf(line + level, "%s %u %i %i,%i,%i,%i %c %c", name,
- &bw, &wg, &top, &right, &bottom, &left, &layout, &end);
- locate_desktop(name, &loc);
- d = loc.desktop;
- if (d == NULL) {
- continue;
- }
- d->border_width = bw;
- d->window_gap = wg;
- d->top_padding = top;
- d->right_padding = right;
- d->bottom_padding = bottom;
- d->left_padding = left;
- if (layout == 'M') {
- d->layout = LAYOUT_MONOCLE;
- } else if (layout == 'T') {
- d->layout = LAYOUT_TILED;
- }
- if (end != 0) {
- m->desk = d;
- }
+ int nbtok = 256;
+ jsmn_parser parser;
+ jsmntok_t *tokens = malloc(nbtok * sizeof(jsmntok_t));
+
+ if (tokens == NULL) {
+ perror("Restore tree: malloc");
+ free(json);
+ return false;
+ }
+
+ jsmn_init(&parser);
+ int ret;
+
+ while ((ret = jsmn_parse(&parser, json, jslen, tokens, nbtok)) == JSMN_ERROR_NOMEM) {
+ nbtok *= 2;
+ jsmntok_t *rtokens = realloc(tokens, nbtok * sizeof(jsmntok_t));
+ if (rtokens == NULL) {
+ perror("Restore tree: realloc");
+ free(tokens);
+ free(json);
+ return false;
} else {
- if (m == NULL || d == NULL)
- continue;
- node_t *birth = make_node();
- if (level == 2) {
- empty_desktop(d);
- d->root = birth;
- } else if (n != NULL) {
- if (level > last_level) {
- n->first_child = birth;
- } else {
- do {
- n = n->parent;
- } while (n != NULL && n->second_child != NULL);
- if (n == NULL)
- continue;
- n->second_child = birth;
- }
- birth->parent = n;
- }
- n = birth;
- char birth_rotation;
- if (isupper(line[level])) {
- char split_type;
- sscanf(line + level, "%c %c %lf", &split_type, &birth_rotation, &n->split_ratio);
- if (split_type == 'H') {
- n->split_type = TYPE_HORIZONTAL;
- } else if (split_type == 'V') {
- n->split_type = TYPE_VERTICAL;
- }
- } else {
- client_t *c = make_client(XCB_NONE, d->border_width);
- num_clients++;
- char urgent, locked, sticky, private, split_dir, split_mode, state, layer, end = 0;
- sscanf(line + level, "%c %s %s %X %u %hux%hu%hi%hi %c%c %c%c %c%c%c%c %c", &birth_rotation,
- c->class_name, c->instance_name, &c->window, &c->border_width,
- &c->floating_rectangle.width, &c->floating_rectangle.height,
- &c->floating_rectangle.x, &c->floating_rectangle.y,
- &split_dir, &split_mode, &state, &layer,
- &urgent, &locked, &sticky, &private, &end);
- n->split_mode = (split_mode == '-' ? MODE_AUTOMATIC : MODE_MANUAL);
- if (split_dir == 'U') {
- n->split_dir = DIR_UP;
- } else if (split_dir == 'R') {
- n->split_dir = DIR_RIGHT;
- } else if (split_dir == 'D') {
- n->split_dir = DIR_DOWN;
- } else if (split_dir == 'L') {
- n->split_dir = DIR_LEFT;
- }
- if (state == 'f') {
- c->state = STATE_FLOATING;
- } else if (state == 'F') {
- c->state = STATE_FULLSCREEN;
- } else if (state == 'p') {
- c->state = STATE_PSEUDO_TILED;
- }
- if (layer == 'b') {
- c->layer = LAYER_BELOW;
- } else if (layer == 'a') {
- c->layer = LAYER_ABOVE;
- }
- c->urgent = (urgent == '-' ? false : true);
- c->locked = (locked == '-' ? false : true);
- c->sticky = (sticky == '-' ? false : true);
- c->private = (private == '-' ? false : true);
- n->client = c;
- if (end != 0) {
- d->focus = n;
- }
- if (c->sticky) {
- m->num_sticky++;
+ tokens = rtokens;
+ }
+ }
+
+ if (ret < 0) {
+ warn("Restore tree: jsmn_parse: ");
+ switch (ret) {
+ case JSMN_ERROR_NOMEM:
+ warn("not enough memory.\n");
+ break;
+ case JSMN_ERROR_INVAL:
+ warn("found invalid character inside JSON string.\n");
+ break;
+ case JSMN_ERROR_PART:
+ warn("not a full JSON packet.\n");
+ break;
+ default:
+ warn("unknown error.\n");
+ break;
+ }
+
+ free(tokens);
+ free(json);
+
+ return false;
+ }
+
+ int num = tokens[0].size;
+
+ if (num < 1) {
+ free(tokens);
+ free(json);
+
+ return false;
+ }
+
+ mon = NULL;
+ while (mon_head != NULL) {
+ remove_monitor(mon_head);
+ }
+
+ jsmntok_t *t = tokens + 1;
+ uint32_t focused_monitor_id = 0, primary_monitor_id = 0;
+
+ for (int i = 0; i < num; i++) {
+ if (keyeq("focusedMonitorId", t, json)) {
+ t++;
+ sscanf(json + t->start, "%u", &focused_monitor_id);
+ } else if (keyeq("primaryMonitorId", t, json)) {
+ t++;
+ sscanf(json + t->start, "%u", &primary_monitor_id);
+ } else if (keyeq("clientsCount", t, json)) {
+ t++;
+ sscanf(json + t->start, "%u", &clients_count);
+ } else if (keyeq("monitors", t, json)) {
+ t++;
+ int s = t->size;
+ t++;
+ for (int j = 0; j < s; j++) {
+ monitor_t *m = restore_monitor(&t, json);
+ if (m->desk == NULL) {
+ add_desktop(m, make_desktop(NULL, XCB_NONE));
}
+ add_monitor(m);
}
- if (birth_rotation == 'a') {
- n->birth_rotation = 90;
- } else if (birth_rotation == 'c') {
- n->birth_rotation = 270;
- } else if (birth_rotation == 'm') {
- n->birth_rotation = 0;
- }
+ continue;
+ } else if (keyeq("focusHistory", t, json)) {
+ t++;
+ restore_history(&t, json);
+ continue;
+ } else if (keyeq("stackingList", t, json)) {
+ t++;
+ restore_stack(&t, json);
+ continue;
}
- last_level = level;
+ t++;
}
- fclose(snapshot);
+ if (focused_monitor_id != 0) {
+ coordinates_t loc;
+ if (monitor_from_id(focused_monitor_id, &loc)) {
+ mon = loc.monitor;
+ }
+ }
+
+ if (primary_monitor_id != 0) {
+ coordinates_t loc;
+ if (monitor_from_id(primary_monitor_id, &loc)) {
+ pri_mon = loc.monitor;
+ }
+ }
for (monitor_t *m = mon_head; m != NULL; m = m->next) {
for (desktop_t *d = m->desk_head; d != NULL; d = d->next) {
+ refresh_presel_feebacks_in(d->root, d, m);
+ restack_presel_feedback(d);
for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) {
- uint32_t values[] = {CLIENT_EVENT_MASK | (focus_follows_pointer ? XCB_EVENT_MASK_ENTER_WINDOW : 0)};
- xcb_change_window_attributes(dpy, n->client->window, XCB_CW_EVENT_MASK, values);
- if (!IS_TILED(n->client)) {
- n->vacant = true;
- update_vacant_state(n->parent);
- }
- if (n->client->private) {
- update_privacy_level(n, true);
+ if (n->client == NULL) {
+ continue;
}
+ initialize_client(n);
+ uint32_t values[] = {CLIENT_EVENT_MASK | (focus_follows_pointer ? XCB_EVENT_MASK_ENTER_WINDOW : 0)};
+ xcb_change_window_attributes(dpy, n->id, XCB_CW_EVENT_MASK, values);
}
- /* Has the side effect of restoring the node's rectangles and the client's tiled rectangles */
- arrange(m, d);
}
}
+ ewmh_update_client_list(false);
+ ewmh_update_client_list(true);
+ ewmh_update_active_window();
+ ewmh_update_number_of_desktops();
ewmh_update_current_desktop();
+ ewmh_update_desktop_names();
+
+ free(tokens);
+ free(json);
+
+ return true;
}
-void restore_history(char *file_path)
+#define RESTORE_INT(k, p) \
+ } else if (keyeq(#k, *t, json)) { \
+ (*t)++; \
+ sscanf(json + (*t)->start, "%i", p);
+
+#define RESTORE_UINT(k, p) \
+ } else if (keyeq(#k, *t, json)) { \
+ (*t)++; \
+ sscanf(json + (*t)->start, "%u", p);
+
+#define RESTORE_USINT(k, p) \
+ } else if (keyeq(#k, *t, json)) { \
+ (*t)++; \
+ sscanf(json + (*t)->start, "%hu", p);
+
+#define RESTORE_DOUBLE(k, p) \
+ } else if (keyeq(#k, *t, json)) { \
+ (*t)++; \
+ sscanf(json + (*t)->start, "%lf", p);
+
+#define RESTORE_ANY(k, p, f) \
+ } else if (keyeq(#k, *t, json)) { \
+ (*t)++; \
+ char *val = copy_string(json + (*t)->start, (*t)->end - (*t)->start); \
+ f(val, p); \
+ free(val);
+
+#define RESTORE_BOOL(k, p) RESTORE_ANY(k, p, parse_bool)
+
+monitor_t *restore_monitor(jsmntok_t **t, char *json)
{
- if (file_path == NULL)
- return;
+ int num = (*t)->size;
+ (*t)++;
+ monitor_t *m = make_monitor(NULL, UINT32_MAX);
+ uint32_t focused_desktop_id = 0;
+
+ for (int i = 0; i < num; i++) {
+ if (keyeq("name", *t, json)) {
+ (*t)++;
+ snprintf(m->name, (*t)->end - (*t)->start + 1, "%s", json + (*t)->start);
+ RESTORE_UINT(id, &m->id)
+ RESTORE_UINT(randrId, &m->randr_id)
+ RESTORE_BOOL(wired, &m->wired)
+ RESTORE_UINT(stickyCount, &m->sticky_count)
+ RESTORE_INT(windowGap, &m->window_gap)
+ RESTORE_UINT(borderWidth, &m->border_width)
+ RESTORE_UINT(focusedDesktopId, &focused_desktop_id)
+ } else if (keyeq("padding", *t, json)) {
+ (*t)++;
+ restore_padding(&m->padding, t, json);
+ continue;
+ } else if (keyeq("rectangle", *t, json)) {
+ (*t)++;
+ restore_rectangle(&m->rectangle, t, json);
+ update_root(m, &m->rectangle);
+ continue;
+ } else if (keyeq("desktops", *t, json)) {
+ (*t)++;
+ int s = (*t)->size;
+ (*t)++;
+ for (int j = 0; j < s; j++) {
+ desktop_t *d = restore_desktop(t, json);
+ add_desktop(m, d);
+ }
+ continue;
+ } else {
+ warn("Restore monitor: unknown key: '%.*s'.\n", (*t)->end - (*t)->start, json + (*t)->start);
+ (*t)++;
+ }
+ (*t)++;
+ }
- FILE *snapshot = fopen(file_path, "r");
- if (snapshot == NULL) {
- warn("Restore history: can't open '%s'.\n", file_path);
- return;
+ if (focused_desktop_id != 0) {
+ for (desktop_t *d = m->desk_head; d != NULL; d = d->next) {
+ if (d->id == focused_desktop_id) {
+ m->desk = d;
+ break;
+ }
+ }
}
- char line[MAXLEN];
- char mnm[SMALEN];
- char dnm[SMALEN];
- xcb_window_t win;
+ return m;
+}
+
+desktop_t *restore_desktop(jsmntok_t **t, char *json)
+{
+ int s = (*t)->size;
+ (*t)++;
+ desktop_t *d = make_desktop(NULL, UINT32_MAX);
+ xcb_window_t focusedNodeId = XCB_NONE;
- while (fgets(line, sizeof(line), snapshot) != NULL) {
- if (sscanf(line, "%s %s %X", mnm, dnm, &win) == 3) {
- coordinates_t loc;
- if (win != XCB_NONE && !locate_window(win, &loc)) {
- warn("Can't locate window 0x%X.\n", win);
+ for (int i = 0; i < s; i++) {
+ if (keyeq("name", *t, json)) {
+ (*t)++;
+ snprintf(d->name, (*t)->end - (*t)->start + 1, "%s", json + (*t)->start);
+ RESTORE_UINT(id, &d->id)
+ RESTORE_ANY(layout, &d->layout, parse_layout)
+ RESTORE_INT(windowGap, &d->window_gap)
+ RESTORE_UINT(borderWidth, &d->border_width)
+ } else if (keyeq("focusedNodeId", *t, json)) {
+ (*t)++;
+ sscanf(json + (*t)->start, "%u", &focusedNodeId);
+ } else if (keyeq("padding", *t, json)) {
+ (*t)++;
+ restore_padding(&d->padding, t, json);
+ continue;
+ } else if (keyeq("root", *t, json)) {
+ (*t)++;
+ d->root = restore_node(t, json);
+ continue;
+ } else {
+ warn("Restore desktop: unknown key: '%.*s'.\n", (*t)->end - (*t)->start, json + (*t)->start);
+ (*t)++;
+ }
+ (*t)++;
+ }
+
+ if (focusedNodeId != XCB_NONE) {
+ d->focus = find_by_id_in(d->root, focusedNodeId);
+ }
+
+ return d;
+}
+
+node_t *restore_node(jsmntok_t **t, char *json)
+{
+ if ((*t)->type == JSMN_PRIMITIVE) {
+ (*t)++;
+ return NULL;
+ } else {
+ int s = (*t)->size;
+ (*t)++;
+ /* hack to prevent a new ID from being generated */
+ node_t *n = make_node(UINT32_MAX);
+
+ for (int i = 0; i < s; i++) {
+ if (keyeq("id", *t, json)) {
+ (*t)++;
+ sscanf(json + (*t)->start, "%u", &n->id);
+ RESTORE_ANY(splitType, &n->split_type, parse_split_type)
+ RESTORE_DOUBLE(splitRatio, &n->split_ratio)
+ RESTORE_INT(birthRotation, &n->birth_rotation)
+ RESTORE_BOOL(vacant, &n->vacant)
+ RESTORE_BOOL(hidden, &n->hidden)
+ RESTORE_BOOL(sticky, &n->sticky)
+ RESTORE_BOOL(private, &n->private)
+ RESTORE_BOOL(locked, &n->locked)
+ } else if (keyeq("presel", *t, json)) {
+ (*t)++;
+ n->presel = restore_presel(t, json);
continue;
- }
- node_t *n = (win == XCB_NONE ? NULL : loc.node);
- if (!locate_desktop(dnm, &loc)) {
- warn("Can't locate desktop '%s'.\n", dnm);
+ } else if (keyeq("rectangle", *t, json)) {
+ (*t)++;
+ restore_rectangle(&n->rectangle, t, json);
+ continue;
+ } else if (keyeq("firstChild", *t, json)) {
+ (*t)++;
+ node_t *fc = restore_node(t, json);
+ n->first_child = fc;
+ if (fc != NULL) {
+ fc->parent = n;
+ }
+ continue;
+ } else if (keyeq("secondChild", *t, json)) {
+ (*t)++;
+ node_t *sc = restore_node(t, json);
+ n->second_child = sc;
+ if (sc != NULL) {
+ sc->parent = n;
+ }
+ continue;
+ } else if (keyeq("client", *t, json)) {
+ (*t)++;
+ n->client = restore_client(t, json);
continue;
+ } else {
+ warn("Restore node: unknown key: '%.*s'.\n", (*t)->end - (*t)->start, json + (*t)->start);
+ (*t)++;
+ }
+ (*t)++;
+ }
+
+ return n;
+ }
+}
+
+presel_t *restore_presel(jsmntok_t **t, char *json)
+{
+ if ((*t)->type == JSMN_PRIMITIVE) {
+ (*t)++;
+ return NULL;
+ } else {
+ int s = (*t)->size;
+ (*t)++;
+ presel_t *p = make_presel();
+
+ for (int i = 0; i < s; i++) {
+ if (keyeq("splitRatio", *t, json)) {
+ (*t)++;
+ sscanf(json + (*t)->start, "%lf", &p->split_ratio);
+ RESTORE_ANY(splitDir, &p->split_dir, parse_direction)
}
- desktop_t *d = loc.desktop;
- if (!locate_monitor(mnm, &loc)) {
- warn("Can't locate monitor '%s'.\n", mnm);
+
+ (*t)++;
+ }
+
+ return p;
+ }
+}
+
+
+client_t *restore_client(jsmntok_t **t, char *json)
+{
+ if ((*t)->type == JSMN_PRIMITIVE) {
+ (*t)++;
+ return NULL;
+ } else {
+ int s = (*t)->size;
+ (*t)++;
+ client_t *c = make_client();
+
+ for (int i = 0; i < s; i++) {
+ if (keyeq("className", *t, json)) {
+ (*t)++;
+ snprintf(c->class_name, (*t)->end - (*t)->start + 1, "%s", json + (*t)->start);
+ } else if (keyeq("instanceName", *t, json)) {
+ (*t)++;
+ snprintf(c->instance_name, (*t)->end - (*t)->start + 1, "%s", json + (*t)->start);
+ RESTORE_ANY(state, &c->state, parse_client_state)
+ RESTORE_ANY(lastState, &c->last_state, parse_client_state)
+ RESTORE_ANY(layer, &c->layer, parse_stack_layer)
+ RESTORE_ANY(lastLayer, &c->last_layer, parse_stack_layer)
+ RESTORE_UINT(borderWidth, &c->border_width)
+ RESTORE_BOOL(urgent, &c->urgent)
+ RESTORE_BOOL(shown, &c->shown)
+ } else if (keyeq("tiledRectangle", *t, json)) {
+ (*t)++;
+ restore_rectangle(&c->tiled_rectangle, t, json);
continue;
+ } else if (keyeq("floatingRectangle", *t, json)) {
+ (*t)++;
+ restore_rectangle(&c->floating_rectangle, t, json);
+ continue;
+ } else {
+ warn("Restore client: unknown key: '%.*s'.\n", (*t)->end - (*t)->start, json + (*t)->start);
+ (*t)++;
}
- monitor_t *m = loc.monitor;
- history_add(m, d, n);
- } else {
- warn("Can't parse history entry: '%s'\n", line);
+
+ (*t)++;
}
+
+ return c;
}
+}
- fclose(snapshot);
+void restore_rectangle(xcb_rectangle_t *r, jsmntok_t **t, char *json)
+{
+ int s = (*t)->size;
+ (*t)++;
+
+ for (int i = 0; i < s; i++) {
+ if (keyeq("x", *t, json)) {
+ (*t)++;
+ sscanf(json + (*t)->start, "%hi", &r->x);
+ } else if (keyeq("y", *t, json)) {
+ (*t)++;
+ sscanf(json + (*t)->start, "%hi", &r->y);
+ } else if (keyeq("width", *t, json)) {
+ (*t)++;
+ sscanf(json + (*t)->start, "%hu", &r->width);
+ } else if (keyeq("height", *t, json)) {
+ (*t)++;
+ sscanf(json + (*t)->start, "%hu", &r->height);
+ }
+ (*t)++;
+ }
}
-void restore_stack(char *file_path)
+void restore_padding(padding_t *p, jsmntok_t **t, char *json)
{
- if (file_path == NULL)
- return;
+ int s = (*t)->size;
+ (*t)++;
- FILE *snapshot = fopen(file_path, "r");
- if (snapshot == NULL) {
- warn("Restore stack: can't open '%s'.\n", file_path);
- return;
+ for (int i = 0; i < s; i++) {
+ if (keyeq("top", *t, json)) {
+ (*t)++;
+ sscanf(json + (*t)->start, "%i", &p->top);
+ } else if (keyeq("right", *t, json)) {
+ (*t)++;
+ sscanf(json + (*t)->start, "%i", &p->right);
+ } else if (keyeq("bottom", *t, json)) {
+ (*t)++;
+ sscanf(json + (*t)->start, "%i", &p->bottom);
+ } else if (keyeq("left", *t, json)) {
+ (*t)++;
+ sscanf(json + (*t)->start, "%i", &p->left);
+ }
+ (*t)++;
}
+}
- char line[MAXLEN];
- xcb_window_t win;
+void restore_history(jsmntok_t **t, char *json)
+{
+ int s = (*t)->size;
+ (*t)++;
- while (fgets(line, sizeof(line), snapshot) != NULL) {
- if (sscanf(line, "%X", &win) == 1) {
- coordinates_t loc;
- if (locate_window(win, &loc))
- stack_insert_after(stack_tail, loc.node);
- else
- warn("Can't locate window 0x%X.\n", win);
- } else {
- warn("Can't parse stack entry: '%s'\n", line);
+ for (int i = 0; i < s; i++) {
+ coordinates_t loc = {NULL, NULL, NULL};
+ restore_coordinates(&loc, t, json);
+ if (loc.monitor != NULL && loc.desktop != NULL) {
+ history_add(loc.monitor, loc.desktop, loc.node);
}
}
+}
+
+void restore_coordinates(coordinates_t *loc, jsmntok_t **t, char *json)
+{
+ int s = (*t)->size;
+ (*t)++;
+ uint32_t id = 0;
+
+ for (int i = 0; i < s; i++) {
+ if (keyeq("monitorId", *t, json)) {
+ (*t)++;
+ sscanf(json + (*t)->start, "%u", &id);
+ loc->monitor = find_monitor(id);
+ } else if (keyeq("desktopId", *t, json)) {
+ (*t)++;
+ sscanf(json + (*t)->start, "%u", &id);
+ loc->desktop = find_desktop_in(id, loc->monitor);
+ } else if (keyeq("nodeId", *t, json)) {
+ (*t)++;
+ sscanf(json + (*t)->start, "%u", &id);
+ loc->node = find_by_id_in(loc->desktop != NULL ? loc->desktop->root : NULL, id);
+ }
+ (*t)++;
+ }
+}
- fclose(snapshot);
+void restore_stack(jsmntok_t **t, char *json)
+{
+ int s = (*t)->size;
+ (*t)++;
+
+ for (int i = 0; i < s; i++) {
+ uint32_t id;
+ sscanf(json + (*t)->start, "%u", &id);
+ coordinates_t loc;
+ if (locate_window(id, &loc)) {
+ stack_insert_after(stack_tail, loc.node);
+ }
+ (*t)++;
+ }
+}
+
+#undef RESTORE_INT
+#undef RESTORE_UINT
+#undef RESTORE_USINT
+#undef RESTORE_DOUBLE
+#undef RESTORE_ANY
+#undef RESTORE_BOOL
+
+bool keyeq(char *s, jsmntok_t *key, char *json)
+{
+ size_t n = key->end - key->start;
+ return (strlen(s) == n && strncmp(s, json + key->start, n) == 0);
}