From 7882ac66ef4308922045fd100e6a9e12942a240b Mon Sep 17 00:00:00 2001 From: Ian Fan Date: Mon, 17 Sep 2018 14:10:57 +0100 Subject: [PATCH] swaybar: rewrite i3bar protocol handling This now correctly handles an incoming json infinite array by shifting most of the heavy listing to the json-c parser, as well as sending multiple statuses at once. It also removes the struct i3bar_protocol_state and moves its members into the status_line struct, allowing the same buffer to be used for both protocols. --- include/swaybar/status_line.h | 24 ++--- swaybar/i3bar.c | 189 ++++++++++++++++++++-------------- swaybar/render.c | 2 +- swaybar/status_line.c | 31 ++---- 4 files changed, 128 insertions(+), 118 deletions(-) diff --git a/include/swaybar/status_line.h b/include/swaybar/status_line.h index 857948a5..d3eabdf6 100644 --- a/include/swaybar/status_line.h +++ b/include/swaybar/status_line.h @@ -1,5 +1,6 @@ #ifndef _SWAYBAR_STATUS_LINE_H #define _SWAYBAR_STATUS_LINE_H +#include #include #include #include @@ -12,23 +13,6 @@ enum status_protocol { PROTOCOL_I3BAR, }; -enum json_node_type { - JSON_NODE_UNKNOWN, - JSON_NODE_ARRAY, - JSON_NODE_STRING, -}; - -struct i3bar_protocol_state { - bool click_events; - char *buffer; - size_t buffer_size; - size_t buffer_index; - const char *current_node; - bool escape; - size_t depth; - enum json_node_type nodes[16]; -}; - struct i3bar_block { struct wl_list link; int ref_count; @@ -58,9 +42,13 @@ struct status_line { const char *text; struct wl_list blocks; // i3bar_block::link + bool click_events; char *buffer; size_t buffer_size; - struct i3bar_protocol_state i3bar_state; + size_t buffer_index; + bool started; + bool expecting_comma; + json_tokener *tokener; }; struct status_line *status_line_init(char *cmd); diff --git a/swaybar/i3bar.c b/swaybar/i3bar.c index f4ca5504..78cb5bd4 100644 --- a/swaybar/i3bar.c +++ b/swaybar/i3bar.c @@ -1,6 +1,7 @@ #define _POSIX_C_SOURCE 200809L #include #include +#include #include #include #include @@ -24,24 +25,19 @@ void i3bar_block_unref(struct i3bar_block *block) { } } -static bool i3bar_parse_json(struct status_line *status, const char *text) { +static void i3bar_parse_json(struct status_line *status, + struct json_object *json_array) { struct i3bar_block *block, *tmp; wl_list_for_each_safe(block, tmp, &status->blocks, link) { wl_list_remove(&block->link); i3bar_block_unref(block); } - json_object *results = json_tokener_parse(text); - if (!results) { - status_error(status, "[failed to parse i3bar json]"); - return false; - } - wlr_log(WLR_DEBUG, "Got i3bar json: '%s'", text); - for (size_t i = 0; i < json_object_array_length(results); ++i) { + for (size_t i = 0; i < json_object_array_length(json_array); ++i) { json_object *full_text, *short_text, *color, *min_width, *align, *urgent; json_object *name, *instance, *separator, *separator_block_width; json_object *background, *border, *border_top, *border_bottom; json_object *border_left, *border_right, *markup; - json_object *json = json_object_array_get_idx(results, i); + json_object *json = json_object_array_get_idx(json_array, i); if (!json) { continue; } @@ -110,96 +106,133 @@ static bool i3bar_parse_json(struct status_line *status, const char *text) { json_object_get_int(border_right) : 1; wl_list_insert(&status->blocks, &block->link); } - json_object_put(results); - return true; } bool i3bar_handle_readable(struct status_line *status) { - struct i3bar_protocol_state *state = &status->i3bar_state; + while (!status->started) { // look for opening bracket + for (size_t c = 0; c < status->buffer_index; ++c) { + if (status->buffer[c] == '[') { + status->started = true; + status->buffer_index -= ++c; + memmove(status->buffer, &status->buffer[c], status->buffer_index); + break; + } else if (!isspace(status->buffer[c])) { + status_error(status, "[invalid json]"); + return true; + } + } + if (status->started) { + break; + } - char *cur = &state->buffer[state->buffer_index]; - ssize_t n = read(status->read_fd, cur, - state->buffer_size - state->buffer_index); - if (n == -1) { - status_error(status, "[failed to read from status command]"); - return false; - } - - if (n == (ssize_t)(state->buffer_size - state->buffer_index)) { - state->buffer_size = state->buffer_size * 2; - char *new_buffer = realloc(state->buffer, state->buffer_size); - if (!new_buffer) { - free(state->buffer); - status_error(status, "[failed to allocate buffer]"); + errno = 0; + ssize_t read_bytes = read(status->read_fd, status->buffer, status->buffer_size); + if (read_bytes > -1) { + status->buffer_index = read_bytes; + } else if (errno == EAGAIN) { + return false; + } else { + status_error(status, "[error reading from status command]"); return true; } - state->current_node += new_buffer - state->buffer; - cur += new_buffer - state->buffer; - state->buffer = new_buffer; } - cur[n] = '\0'; - bool redraw = false; - while (*cur) { - if (state->nodes[state->depth] == JSON_NODE_STRING) { - if (!state->escape && *cur == '"') { - --state->depth; + struct json_object *last_object = NULL; + struct json_object *test_object; + size_t buffer_pos = 0; + while (true) { + // since the incoming stream is an infinite array + // parsing is split into two parts + // first, attempt to parse the current object, reading more if the + // parser indicates that the current object is incomplete, and failing + // if the parser fails + // second, look for separating comma, ignoring whitespace, failing if + // any other characters are encountered + if (status->expecting_comma) { + for (; buffer_pos < status->buffer_index; ++buffer_pos) { + if (status->buffer[buffer_pos] == ',') { + status->expecting_comma = false; + ++buffer_pos; + break; + } else if (!isspace(status->buffer[buffer_pos])) { + status_error(status, "[invalid i3bar json]"); + return true; + } } - state->escape = !state->escape && *cur == '\\'; + if (buffer_pos < status->buffer_index) { + continue; // look for new object without reading more input + } + buffer_pos = status->buffer_index = 0; } else { - switch (*cur) { - case '[': - ++state->depth; - if (state->depth > - sizeof(state->nodes) / sizeof(state->nodes[0])) { - status_error(status, "[i3bar json too deep]"); - return false; + test_object = json_tokener_parse_ex(status->tokener, + &status->buffer[buffer_pos], status->buffer_index - buffer_pos); + if (json_tokener_get_error(status->tokener) == json_tokener_success) { + if (json_object_get_type(test_object) == json_type_array) { + if (last_object) { + json_object_put(last_object); + } + last_object = test_object; + } else { + json_object_put(test_object); } - state->nodes[state->depth] = JSON_NODE_ARRAY; - if (state->depth == 1) { - state->current_node = cur; + + buffer_pos += status->tokener->char_offset; + status->expecting_comma = true; + + if (buffer_pos < status->buffer_index) { + continue; // look for comma without reading more input } - break; - case ']': - if (state->nodes[state->depth] != JSON_NODE_ARRAY) { - status_error(status, "[failed to parse i3bar json]"); - return false; + buffer_pos = status->buffer_index = 0; + } else if (json_tokener_get_error(status->tokener) == json_tokener_continue) { + if (status->buffer_index < status->buffer_size) { + // move the object to the start of the buffer + status->buffer_index -= buffer_pos; + memmove(status->buffer, &status->buffer[buffer_pos], + status->buffer_index); + } else { + // expand buffer + status->buffer_size *= 2; + char *new_buffer = realloc(status->buffer, status->buffer_size); + if (new_buffer) { + status->buffer = new_buffer; + } else { + free(status->buffer); + status_error(status, "[failed to allocate buffer]"); + return true; + } } - --state->depth; - if (state->depth == 0) { - // cur[1] is valid since cur[0] != '\0' - char p = cur[1]; - cur[1] = '\0'; - redraw = i3bar_parse_json( - status, state->current_node) || redraw; - cur[1] = p; - memmove(state->buffer, cur, - state->buffer_size - (cur - state->buffer)); - cur = state->buffer; - state->current_node = cur + 1; - } - break; - case '"': - ++state->depth; - if (state->depth > - sizeof(state->nodes) / sizeof(state->nodes[0])) { - status_error(status, "[i3bar json too deep]"); - return false; - } - state->nodes[state->depth] = JSON_NODE_STRING; - break; + } else { + status_error(status, "[failed to parse i3bar json]"); + return true; } } - ++cur; + + errno = 0; + ssize_t read_bytes = read(status->read_fd, &status->buffer[status->buffer_index], + status->buffer_size - status->buffer_index); + if (read_bytes > -1) { + status->buffer_index += read_bytes; + } else if (errno == EAGAIN) { + break; + } else { + status_error(status, "[error reading from status command]"); + return true; + } + } + + if (last_object) { + i3bar_parse_json(status, last_object); + json_object_put(last_object); + return true; + } else { + return false; } - state->buffer_index = cur - state->buffer; - return redraw; } enum hotspot_event_handling i3bar_block_send_click(struct status_line *status, struct i3bar_block *block, int x, int y, enum x11_button button) { wlr_log(WLR_DEBUG, "block %s clicked", block->name ? block->name : "(nil)"); - if (!block->name || !status->i3bar_state.click_events) { + if (!block->name || !status->click_events) { return HOTSPOT_PROCESS; } diff --git a/swaybar/render.c b/swaybar/render.c index b2c1c710..97690338 100644 --- a/swaybar/render.c +++ b/swaybar/render.c @@ -177,7 +177,7 @@ static uint32_t render_status_block(cairo_t *cairo, *x -= margin; } - if (output->bar->status->i3bar_state.click_events) { + if (output->bar->status->click_events) { struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot)); hotspot->x = *x; hotspot->y = 0; diff --git a/swaybar/status_line.c b/swaybar/status_line.c index fcc0cb93..01ed70d9 100644 --- a/swaybar/status_line.c +++ b/swaybar/status_line.c @@ -32,11 +32,6 @@ void status_error(struct status_line *status, const char *text) { bool status_handle_readable(struct status_line *status) { ssize_t read_bytes = 1; switch (status->protocol) { - case PROTOCOL_I3BAR: - if (i3bar_handle_readable(status) > 0) { - return true; - } - break; case PROTOCOL_UNDEF: errno = 0; read_bytes = getline(&status->buffer, @@ -61,7 +56,7 @@ bool status_handle_readable(struct status_line *status) { if (json_object_object_get_ex(header, "click_events", &click_events) && json_object_get_boolean(click_events)) { wlr_log(WLR_DEBUG, "Enabling click events."); - status->i3bar_state.click_events = true; + status->click_events = true; if (write(status->write_fd, "[\n", 2) != 2) { status_error(status, "[failed to write to status command]"); json_object_put(header); @@ -70,13 +65,11 @@ bool status_handle_readable(struct status_line *status) { } json_object_put(header); - status->protocol = PROTOCOL_I3BAR; - free(status->buffer); wl_list_init(&status->blocks); - status->i3bar_state.buffer_size = 4096; - status->i3bar_state.buffer = - malloc(status->i3bar_state.buffer_size); - return false; + status->tokener = json_tokener_new(); + status->buffer_index = getdelim(&status->buffer, + &status->buffer_size, EOF, status->read); + return i3bar_handle_readable(status); } wlr_log(WLR_DEBUG, "Using text protocol."); @@ -99,10 +92,11 @@ bool status_handle_readable(struct status_line *status) { return true; } } + case PROTOCOL_I3BAR: + return i3bar_handle_readable(status); default: return false; } - return false; } struct status_line *status_line_init(char *cmd) { @@ -147,19 +141,14 @@ struct status_line *status_line_init(char *cmd) { void status_line_free(struct status_line *status) { status_line_close_fds(status); kill(status->pid, SIGTERM); - switch (status->protocol) { - case PROTOCOL_I3BAR: { + if (status->protocol == PROTOCOL_I3BAR) { struct i3bar_block *block, *tmp; wl_list_for_each_safe(block, tmp, &status->blocks, link) { wl_list_remove(&block->link); i3bar_block_unref(block); } - free(status->i3bar_state.buffer); - break; - } - default: - free(status->buffer); - break; } + json_tokener_free(status->tokener); + free(status->buffer); free(status); }