diff options
author | Ian Fan <ianfan0@gmail.com> | 2018-09-17 14:10:57 +0100 |
---|---|---|
committer | Ian Fan <ianfan0@gmail.com> | 2018-09-18 11:36:33 +0100 |
commit | 7882ac66ef4308922045fd100e6a9e12942a240b (patch) | |
tree | 67ce4f14f56372c86eb9992eef354d1d76f3081d /swaybar/i3bar.c | |
parent | 8cbce77e1deb811754a6175388e5ef89f274ff05 (diff) |
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.
Diffstat (limited to 'swaybar/i3bar.c')
-rw-r--r-- | swaybar/i3bar.c | 189 |
1 files changed, 111 insertions, 78 deletions
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 <json-c/json.h> #include <linux/input-event-codes.h> +#include <ctype.h> #include <stdlib.h> #include <string.h> #include <unistd.h> @@ -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; - - 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; - } + 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; + } - 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; + } + } + if (buffer_pos < status->buffer_index) { + continue; // look for new object without reading more input } - state->escape = !state->escape && *cur == '\\'; + 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; - } - state->nodes[state->depth] = JSON_NODE_ARRAY; - if (state->depth == 1) { - state->current_node = cur; + 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); } - break; - case ']': - if (state->nodes[state->depth] != JSON_NODE_ARRAY) { - status_error(status, "[failed to parse i3bar json]"); - return false; + + buffer_pos += status->tokener->char_offset; + status->expecting_comma = true; + + if (buffer_pos < status->buffer_index) { + continue; // look for comma without reading more input } - --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; + 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; + } } - 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; } |