diff options
Diffstat (limited to 'swaybar/i3bar.c')
-rw-r--r-- | swaybar/i3bar.c | 216 |
1 files changed, 138 insertions, 78 deletions
diff --git a/swaybar/i3bar.c b/swaybar/i3bar.c index 0becae5d..325aa61a 100644 --- a/swaybar/i3bar.c +++ b/swaybar/i3bar.c | |||
@@ -1,6 +1,7 @@ | |||
1 | #define _POSIX_C_SOURCE 200809L | 1 | #define _POSIX_C_SOURCE 200809L |
2 | #include <json-c/json.h> | 2 | #include <json-c/json.h> |
3 | #include <linux/input-event-codes.h> | 3 | #include <linux/input-event-codes.h> |
4 | #include <ctype.h> | ||
4 | #include <stdlib.h> | 5 | #include <stdlib.h> |
5 | #include <string.h> | 6 | #include <string.h> |
6 | #include <unistd.h> | 7 | #include <unistd.h> |
@@ -24,24 +25,19 @@ void i3bar_block_unref(struct i3bar_block *block) { | |||
24 | } | 25 | } |
25 | } | 26 | } |
26 | 27 | ||
27 | static bool i3bar_parse_json(struct status_line *status, const char *text) { | 28 | static void i3bar_parse_json(struct status_line *status, |
29 | struct json_object *json_array) { | ||
28 | struct i3bar_block *block, *tmp; | 30 | struct i3bar_block *block, *tmp; |
29 | wl_list_for_each_safe(block, tmp, &status->blocks, link) { | 31 | wl_list_for_each_safe(block, tmp, &status->blocks, link) { |
30 | wl_list_remove(&block->link); | 32 | wl_list_remove(&block->link); |
31 | i3bar_block_unref(block); | 33 | i3bar_block_unref(block); |
32 | } | 34 | } |
33 | json_object *results = json_tokener_parse(text); | 35 | for (size_t i = 0; i < json_object_array_length(json_array); ++i) { |
34 | if (!results) { | ||
35 | status_error(status, "[failed to parse i3bar json]"); | ||
36 | return false; | ||
37 | } | ||
38 | wlr_log(WLR_DEBUG, "Got i3bar json: '%s'", text); | ||
39 | for (size_t i = 0; i < json_object_array_length(results); ++i) { | ||
40 | json_object *full_text, *short_text, *color, *min_width, *align, *urgent; | 36 | json_object *full_text, *short_text, *color, *min_width, *align, *urgent; |
41 | json_object *name, *instance, *separator, *separator_block_width; | 37 | json_object *name, *instance, *separator, *separator_block_width; |
42 | json_object *background, *border, *border_top, *border_bottom; | 38 | json_object *background, *border, *border_top, *border_bottom; |
43 | json_object *border_left, *border_right, *markup; | 39 | json_object *border_left, *border_right, *markup; |
44 | json_object *json = json_object_array_get_idx(results, i); | 40 | json_object *json = json_object_array_get_idx(json_array, i); |
45 | if (!json) { | 41 | if (!json) { |
46 | continue; | 42 | continue; |
47 | } | 43 | } |
@@ -110,96 +106,160 @@ static bool i3bar_parse_json(struct status_line *status, const char *text) { | |||
110 | json_object_get_int(border_right) : 1; | 106 | json_object_get_int(border_right) : 1; |
111 | wl_list_insert(&status->blocks, &block->link); | 107 | wl_list_insert(&status->blocks, &block->link); |
112 | } | 108 | } |
113 | json_object_put(results); | ||
114 | return true; | ||
115 | } | 109 | } |
116 | 110 | ||
117 | bool i3bar_handle_readable(struct status_line *status) { | 111 | bool i3bar_handle_readable(struct status_line *status) { |
118 | struct i3bar_protocol_state *state = &status->i3bar_state; | 112 | while (!status->started) { // look for opening bracket |
119 | 113 | for (size_t c = 0; c < status->buffer_index; ++c) { | |
120 | char *cur = &state->buffer[state->buffer_index]; | 114 | if (status->buffer[c] == '[') { |
121 | ssize_t n = read(status->read_fd, cur, | 115 | status->started = true; |
122 | state->buffer_size - state->buffer_index); | 116 | status->buffer_index -= ++c; |
123 | if (n == -1) { | 117 | memmove(status->buffer, &status->buffer[c], status->buffer_index); |
124 | status_error(status, "[failed to read from status command]"); | 118 | break; |
125 | return false; | 119 | } else if (!isspace(status->buffer[c])) { |
126 | } | 120 | wlr_log(WLR_DEBUG, "Invalid i3bar json: expected '[' but encountered '%c'", |
121 | status->buffer[c]); | ||
122 | status_error(status, "[invalid i3bar json]"); | ||
123 | return true; | ||
124 | } | ||
125 | } | ||
126 | if (status->started) { | ||
127 | break; | ||
128 | } | ||
127 | 129 | ||
128 | if (n == (ssize_t)(state->buffer_size - state->buffer_index)) { | 130 | errno = 0; |
129 | state->buffer_size = state->buffer_size * 2; | 131 | ssize_t read_bytes = read(status->read_fd, status->buffer, status->buffer_size); |
130 | char *new_buffer = realloc(state->buffer, state->buffer_size); | 132 | if (read_bytes > -1) { |
131 | if (!new_buffer) { | 133 | status->buffer_index = read_bytes; |
132 | free(state->buffer); | 134 | } else if (errno == EAGAIN) { |
133 | status_error(status, "[failed to allocate buffer]"); | 135 | return false; |
136 | } else { | ||
137 | status_error(status, "[error reading from status command]"); | ||
134 | return true; | 138 | return true; |
135 | } | 139 | } |
136 | state->current_node += new_buffer - state->buffer; | ||
137 | cur += new_buffer - state->buffer; | ||
138 | state->buffer = new_buffer; | ||
139 | } | 140 | } |
140 | 141 | ||
141 | cur[n] = '\0'; | 142 | struct json_object *last_object = NULL; |
142 | bool redraw = false; | 143 | struct json_object *test_object; |
143 | while (*cur) { | 144 | size_t buffer_pos = 0; |
144 | if (state->nodes[state->depth] == JSON_NODE_STRING) { | 145 | while (true) { |
145 | if (!state->escape && *cur == '"') { | 146 | // since the incoming stream is an infinite array |
146 | --state->depth; | 147 | // parsing is split into two parts |
148 | // first, attempt to parse the current object, reading more if the | ||
149 | // parser indicates that the current object is incomplete, and failing | ||
150 | // if the parser fails | ||
151 | // second, look for separating comma, ignoring whitespace, failing if | ||
152 | // any other characters are encountered | ||
153 | if (status->expecting_comma) { | ||
154 | for (; buffer_pos < status->buffer_index; ++buffer_pos) { | ||
155 | if (status->buffer[buffer_pos] == ',') { | ||
156 | status->expecting_comma = false; | ||
157 | ++buffer_pos; | ||
158 | break; | ||
159 | } else if (!isspace(status->buffer[buffer_pos])) { | ||
160 | wlr_log(WLR_DEBUG, "Invalid i3bar json: expected ',' but encountered '%c'", | ||
161 | status->buffer[buffer_pos]); | ||
162 | status_error(status, "[invalid i3bar json]"); | ||
163 | return true; | ||
164 | } | ||
165 | } | ||
166 | if (buffer_pos < status->buffer_index) { | ||
167 | continue; // look for new object without reading more input | ||
147 | } | 168 | } |
148 | state->escape = !state->escape && *cur == '\\'; | 169 | buffer_pos = status->buffer_index = 0; |
149 | } else { | 170 | } else { |
150 | switch (*cur) { | 171 | test_object = json_tokener_parse_ex(status->tokener, |
151 | case '[': | 172 | &status->buffer[buffer_pos], status->buffer_index - buffer_pos); |
152 | ++state->depth; | 173 | enum json_tokener_error err = json_tokener_get_error(status->tokener); |
153 | if (state->depth > | 174 | if (err == json_tokener_success) { |
154 | sizeof(state->nodes) / sizeof(state->nodes[0])) { | 175 | if (json_object_get_type(test_object) == json_type_array) { |
155 | status_error(status, "[i3bar json too deep]"); | 176 | if (last_object) { |
156 | return false; | 177 | json_object_put(last_object); |
157 | } | 178 | } |
158 | state->nodes[state->depth] = JSON_NODE_ARRAY; | 179 | last_object = test_object; |
159 | if (state->depth == 1) { | 180 | } else { |
160 | state->current_node = cur; | 181 | json_object_put(test_object); |
161 | } | 182 | } |
162 | break; | 183 | |
163 | case ']': | 184 | // in order to print the json for debugging purposes |
164 | if (state->nodes[state->depth] != JSON_NODE_ARRAY) { | 185 | // the last character is temporarily replaced with a null character |
165 | status_error(status, "[failed to parse i3bar json]"); | 186 | // (the last character is used in case the buffer is full) |
166 | return false; | 187 | char *last_char_pos = |
188 | &status->buffer[buffer_pos + status->tokener->char_offset - 1]; | ||
189 | char last_char = *last_char_pos; | ||
190 | while (isspace(last_char)) { | ||
191 | last_char = *--last_char_pos; | ||
167 | } | 192 | } |
168 | --state->depth; | 193 | *last_char_pos = '\0'; |
169 | if (state->depth == 0) { | 194 | size_t offset = strspn(&status->buffer[buffer_pos], " \f\n\r\t\v"); |
170 | // cur[1] is valid since cur[0] != '\0' | 195 | wlr_log(WLR_DEBUG, "Received i3bar json: '%s%c'", |
171 | char p = cur[1]; | 196 | &status->buffer[buffer_pos + offset], last_char); |
172 | cur[1] = '\0'; | 197 | *last_char_pos = last_char; |
173 | redraw = i3bar_parse_json( | 198 | |
174 | status, state->current_node) || redraw; | 199 | buffer_pos += status->tokener->char_offset; |
175 | cur[1] = p; | 200 | status->expecting_comma = true; |
176 | memmove(state->buffer, cur, | 201 | |
177 | state->buffer_size - (cur - state->buffer)); | 202 | if (buffer_pos < status->buffer_index) { |
178 | cur = state->buffer; | 203 | continue; // look for comma without reading more input |
179 | state->current_node = cur + 1; | ||
180 | } | 204 | } |
181 | break; | 205 | buffer_pos = status->buffer_index = 0; |
182 | case '"': | 206 | } else if (err == json_tokener_continue) { |
183 | ++state->depth; | 207 | json_tokener_reset(status->tokener); |
184 | if (state->depth > | 208 | if (status->buffer_index < status->buffer_size) { |
185 | sizeof(state->nodes) / sizeof(state->nodes[0])) { | 209 | // move the object to the start of the buffer |
186 | status_error(status, "[i3bar json too deep]"); | 210 | status->buffer_index -= buffer_pos; |
187 | return false; | 211 | memmove(status->buffer, &status->buffer[buffer_pos], |
212 | status->buffer_index); | ||
213 | buffer_pos = 0; | ||
214 | } else { | ||
215 | // expand buffer | ||
216 | status->buffer_size *= 2; | ||
217 | char *new_buffer = realloc(status->buffer, status->buffer_size); | ||
218 | if (new_buffer) { | ||
219 | status->buffer = new_buffer; | ||
220 | } else { | ||
221 | free(status->buffer); | ||
222 | status_error(status, "[failed to allocate buffer]"); | ||
223 | return true; | ||
224 | } | ||
188 | } | 225 | } |
189 | state->nodes[state->depth] = JSON_NODE_STRING; | 226 | } else { |
190 | break; | 227 | char last_char = status->buffer[status->buffer_index - 1]; |
228 | status->buffer[status->buffer_index - 1] = '\0'; | ||
229 | wlr_log(WLR_DEBUG, "Failed to parse i3bar json - %s: '%s%c'", | ||
230 | json_tokener_error_desc(err), &status->buffer[buffer_pos], last_char); | ||
231 | status_error(status, "[failed to parse i3bar json]"); | ||
232 | return true; | ||
191 | } | 233 | } |
192 | } | 234 | } |
193 | ++cur; | 235 | |
236 | errno = 0; | ||
237 | ssize_t read_bytes = read(status->read_fd, &status->buffer[status->buffer_index], | ||
238 | status->buffer_size - status->buffer_index); | ||
239 | if (read_bytes > -1) { | ||
240 | status->buffer_index += read_bytes; | ||
241 | } else if (errno == EAGAIN) { | ||
242 | break; | ||
243 | } else { | ||
244 | status_error(status, "[error reading from status command]"); | ||
245 | return true; | ||
246 | } | ||
247 | } | ||
248 | |||
249 | if (last_object) { | ||
250 | wlr_log(WLR_DEBUG, "Rendering last received json"); | ||
251 | i3bar_parse_json(status, last_object); | ||
252 | json_object_put(last_object); | ||
253 | return true; | ||
254 | } else { | ||
255 | return false; | ||
194 | } | 256 | } |
195 | state->buffer_index = cur - state->buffer; | ||
196 | return redraw; | ||
197 | } | 257 | } |
198 | 258 | ||
199 | enum hotspot_event_handling i3bar_block_send_click(struct status_line *status, | 259 | enum hotspot_event_handling i3bar_block_send_click(struct status_line *status, |
200 | struct i3bar_block *block, int x, int y, enum x11_button button) { | 260 | struct i3bar_block *block, int x, int y, enum x11_button button) { |
201 | wlr_log(WLR_DEBUG, "block %s clicked", block->name ? block->name : "(nil)"); | 261 | wlr_log(WLR_DEBUG, "block %s clicked", block->name ? block->name : "(nil)"); |
202 | if (!block->name || !status->i3bar_state.click_events) { | 262 | if (!block->name || !status->click_events) { |
203 | return HOTSPOT_PROCESS; | 263 | return HOTSPOT_PROCESS; |
204 | } | 264 | } |
205 | 265 | ||
@@ -214,7 +274,7 @@ enum hotspot_event_handling i3bar_block_send_click(struct status_line *status, | |||
214 | json_object_object_add(event_json, "button", json_object_new_int(button)); | 274 | json_object_object_add(event_json, "button", json_object_new_int(button)); |
215 | json_object_object_add(event_json, "x", json_object_new_int(x)); | 275 | json_object_object_add(event_json, "x", json_object_new_int(x)); |
216 | json_object_object_add(event_json, "y", json_object_new_int(y)); | 276 | json_object_object_add(event_json, "y", json_object_new_int(y)); |
217 | if (dprintf(status->write_fd, "%s\n", | 277 | if (dprintf(status->write_fd, "%s,\n", |
218 | json_object_to_json_string(event_json)) < 0) { | 278 | json_object_to_json_string(event_json)) < 0) { |
219 | status_error(status, "[failed to write click event]"); | 279 | status_error(status, "[failed to write click event]"); |
220 | } | 280 | } |