diff options
Diffstat (limited to 'swaybar/status_line.c')
-rw-r--r-- | swaybar/status_line.c | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/swaybar/status_line.c b/swaybar/status_line.c new file mode 100644 index 00000000..a9ed8d8c --- /dev/null +++ b/swaybar/status_line.c | |||
@@ -0,0 +1,435 @@ | |||
1 | #include <stdlib.h> | ||
2 | #include <string.h> | ||
3 | #include <unistd.h> | ||
4 | #include <json-c/json.h> | ||
5 | |||
6 | #include "log.h" | ||
7 | #include "config.h" | ||
8 | #include "status_line.h" | ||
9 | |||
10 | #define I3JSON_MAXDEPTH 4 | ||
11 | #define I3JSON_UNKNOWN 0 | ||
12 | #define I3JSON_ARRAY 1 | ||
13 | #define I3JSON_STRING 2 | ||
14 | |||
15 | struct { | ||
16 | int bufsize; | ||
17 | char *buffer; | ||
18 | char *line_start; | ||
19 | char *parserpos; | ||
20 | bool escape; | ||
21 | int depth; | ||
22 | int state[I3JSON_MAXDEPTH+1]; | ||
23 | } i3json_state = { 0, NULL, NULL, NULL, false, 0, { I3JSON_UNKNOWN } }; | ||
24 | |||
25 | static char line[1024]; | ||
26 | static char line_rest[1024]; | ||
27 | |||
28 | static void free_status_block(void *item) { | ||
29 | if (!item) { | ||
30 | return; | ||
31 | } | ||
32 | struct status_block *sb = (struct status_block*)item; | ||
33 | if (sb->full_text) { | ||
34 | free(sb->full_text); | ||
35 | } | ||
36 | if (sb->short_text) { | ||
37 | free(sb->short_text); | ||
38 | } | ||
39 | if (sb->align) { | ||
40 | free(sb->align); | ||
41 | } | ||
42 | if (sb->name) { | ||
43 | free(sb->name); | ||
44 | } | ||
45 | if (sb->instance) { | ||
46 | free(sb->instance); | ||
47 | } | ||
48 | free(sb); | ||
49 | } | ||
50 | |||
51 | static void parse_json(struct swaybar_state *st, const char *text) { | ||
52 | json_object *results = json_tokener_parse(text); | ||
53 | if (!results) { | ||
54 | sway_log(L_DEBUG, "Failed to parse json"); | ||
55 | return; | ||
56 | } | ||
57 | |||
58 | if (json_object_array_length(results) < 1) { | ||
59 | return; | ||
60 | } | ||
61 | |||
62 | if (st->status->block_line) { | ||
63 | list_foreach(st->status->block_line, free_status_block); | ||
64 | list_free(st->status->block_line); | ||
65 | } | ||
66 | |||
67 | st->status->block_line = create_list(); | ||
68 | |||
69 | int i; | ||
70 | for (i = 0; i < json_object_array_length(results); ++i) { | ||
71 | json_object *full_text, *short_text, *color, *min_width, *align, *urgent; | ||
72 | json_object *name, *instance, *separator, *separator_block_width; | ||
73 | json_object *background, *border, *border_top, *border_bottom; | ||
74 | json_object *border_left, *border_right; | ||
75 | |||
76 | json_object *json = json_object_array_get_idx(results, i); | ||
77 | if (!json) { | ||
78 | continue; | ||
79 | } | ||
80 | |||
81 | json_object_object_get_ex(json, "full_text", &full_text); | ||
82 | json_object_object_get_ex(json, "short_text", &short_text); | ||
83 | json_object_object_get_ex(json, "color", &color); | ||
84 | json_object_object_get_ex(json, "min_width", &min_width); | ||
85 | json_object_object_get_ex(json, "align", &align); | ||
86 | json_object_object_get_ex(json, "urgent", &urgent); | ||
87 | json_object_object_get_ex(json, "name", &name); | ||
88 | json_object_object_get_ex(json, "instance", &instance); | ||
89 | json_object_object_get_ex(json, "separator", &separator); | ||
90 | json_object_object_get_ex(json, "separator_block_width", &separator_block_width); | ||
91 | json_object_object_get_ex(json, "background", &background); | ||
92 | json_object_object_get_ex(json, "border", &border); | ||
93 | json_object_object_get_ex(json, "border_top", &border_top); | ||
94 | json_object_object_get_ex(json, "border_bottom", &border_bottom); | ||
95 | json_object_object_get_ex(json, "border_left", &border_left); | ||
96 | json_object_object_get_ex(json, "border_right", &border_right); | ||
97 | |||
98 | struct status_block *new = calloc(1, sizeof(struct status_block)); | ||
99 | |||
100 | if (full_text) { | ||
101 | new->full_text = strdup(json_object_get_string(full_text)); | ||
102 | } | ||
103 | |||
104 | if (short_text) { | ||
105 | new->short_text = strdup(json_object_get_string(short_text)); | ||
106 | } | ||
107 | |||
108 | if (color) { | ||
109 | new->color = parse_color(json_object_get_string(color)); | ||
110 | } else { | ||
111 | new->color = st->config->colors.statusline; | ||
112 | } | ||
113 | |||
114 | if (min_width) { | ||
115 | json_type type = json_object_get_type(min_width); | ||
116 | if (type == json_type_int) { | ||
117 | new->min_width = json_object_get_int(min_width); | ||
118 | } else if (type == json_type_string) { | ||
119 | /* the width will be calculated when rendering */ | ||
120 | new->min_width = 0; | ||
121 | } | ||
122 | } | ||
123 | |||
124 | if (align) { | ||
125 | new->align = strdup(json_object_get_string(align)); | ||
126 | } else { | ||
127 | new->align = strdup("left"); | ||
128 | } | ||
129 | |||
130 | if (urgent) { | ||
131 | new->urgent = json_object_get_int(urgent); | ||
132 | } | ||
133 | |||
134 | if (name) { | ||
135 | new->name = strdup(json_object_get_string(name)); | ||
136 | } | ||
137 | |||
138 | if (instance) { | ||
139 | new->instance = strdup(json_object_get_string(instance)); | ||
140 | } | ||
141 | |||
142 | if (separator) { | ||
143 | new->separator = json_object_get_int(separator); | ||
144 | } else { | ||
145 | new->separator = true; // i3bar spec | ||
146 | } | ||
147 | |||
148 | if (separator_block_width) { | ||
149 | new->separator_block_width = json_object_get_int(separator_block_width); | ||
150 | } else { | ||
151 | new->separator_block_width = 9; // i3bar spec | ||
152 | } | ||
153 | |||
154 | // Airblader features | ||
155 | if (background) { | ||
156 | new->background = parse_color(json_object_get_string(background)); | ||
157 | } else { | ||
158 | new->background = 0x0; // transparent | ||
159 | } | ||
160 | |||
161 | if (border) { | ||
162 | new->border = parse_color(json_object_get_string(border)); | ||
163 | } else { | ||
164 | new->border = 0x0; // transparent | ||
165 | } | ||
166 | |||
167 | if (border_top) { | ||
168 | new->border_top = json_object_get_int(border_top); | ||
169 | } else { | ||
170 | new->border_top = 1; | ||
171 | } | ||
172 | |||
173 | if (border_bottom) { | ||
174 | new->border_bottom = json_object_get_int(border_bottom); | ||
175 | } else { | ||
176 | new->border_bottom = 1; | ||
177 | } | ||
178 | |||
179 | if (border_left) { | ||
180 | new->border_left = json_object_get_int(border_left); | ||
181 | } else { | ||
182 | new->border_left = 1; | ||
183 | } | ||
184 | |||
185 | if (border_right) { | ||
186 | new->border_right = json_object_get_int(border_right); | ||
187 | } else { | ||
188 | new->border_right = 1; | ||
189 | } | ||
190 | |||
191 | list_add(st->status->block_line, new); | ||
192 | } | ||
193 | |||
194 | json_object_put(results); | ||
195 | } | ||
196 | |||
197 | // continue parsing from last parserpos | ||
198 | static int i3json_parse(struct swaybar_state *st) { | ||
199 | char *c = i3json_state.parserpos; | ||
200 | int handled = 0; | ||
201 | while (*c) { | ||
202 | if (i3json_state.state[i3json_state.depth] == I3JSON_STRING) { | ||
203 | if (!i3json_state.escape && *c == '"') { | ||
204 | --i3json_state.depth; | ||
205 | } | ||
206 | i3json_state.escape = !i3json_state.escape && *c == '\\'; | ||
207 | } else { | ||
208 | switch (*c) { | ||
209 | case '[': | ||
210 | ++i3json_state.depth; | ||
211 | if (i3json_state.depth > I3JSON_MAXDEPTH) { | ||
212 | sway_abort("JSON too deep"); | ||
213 | } | ||
214 | i3json_state.state[i3json_state.depth] = I3JSON_ARRAY; | ||
215 | if (i3json_state.depth == 2) { | ||
216 | i3json_state.line_start = c; | ||
217 | } | ||
218 | break; | ||
219 | case ']': | ||
220 | if (i3json_state.state[i3json_state.depth] != I3JSON_ARRAY) { | ||
221 | sway_abort("JSON malformed"); | ||
222 | } | ||
223 | --i3json_state.depth; | ||
224 | if (i3json_state.depth == 1) { | ||
225 | // c[1] is valid since c[0] != '\0' | ||
226 | char p = c[1]; | ||
227 | c[1] = '\0'; | ||
228 | parse_json(st, i3json_state.line_start); | ||
229 | c[1] = p; | ||
230 | ++handled; | ||
231 | i3json_state.line_start = c+1; | ||
232 | } | ||
233 | break; | ||
234 | case '"': | ||
235 | ++i3json_state.depth; | ||
236 | if (i3json_state.depth > I3JSON_MAXDEPTH) { | ||
237 | sway_abort("JSON too deep"); | ||
238 | } | ||
239 | i3json_state.state[i3json_state.depth] = I3JSON_STRING; | ||
240 | break; | ||
241 | } | ||
242 | } | ||
243 | ++c; | ||
244 | } | ||
245 | i3json_state.parserpos = c; | ||
246 | return handled; | ||
247 | } | ||
248 | |||
249 | // Read line from file descriptor, only show the line tail if it is too long. | ||
250 | // In non-blocking mode treat "no more data" as a linebreak. | ||
251 | // If data after a line break has been read, return it in rest. | ||
252 | // If rest is non-empty, then use that as the start of the next line. | ||
253 | static int read_line_tail(int fd, char *buf, int nbyte, char *rest) { | ||
254 | if (fd < 0 || !buf || !nbyte) { | ||
255 | return -1; | ||
256 | } | ||
257 | int l; | ||
258 | char *buffer = malloc(nbyte*2+1); | ||
259 | char *readpos = buffer; | ||
260 | char *lf; | ||
261 | // prepend old data to new line if necessary | ||
262 | if (rest) { | ||
263 | l = strlen(rest); | ||
264 | if (l > nbyte) { | ||
265 | strcpy(buffer, rest + l - nbyte); | ||
266 | readpos += nbyte; | ||
267 | } else if (l) { | ||
268 | strcpy(buffer, rest); | ||
269 | readpos += l; | ||
270 | } | ||
271 | } | ||
272 | // read until a linefeed is found or no more data is available | ||
273 | while ((l = read(fd, readpos, nbyte)) > 0) { | ||
274 | readpos[l] = '\0'; | ||
275 | lf = strchr(readpos, '\n'); | ||
276 | if (lf) { | ||
277 | // linefeed found, replace with \0 | ||
278 | *lf = '\0'; | ||
279 | // give data from the end of the line, try to fill the buffer | ||
280 | if (lf-buffer > nbyte) { | ||
281 | strcpy(buf, lf - nbyte + 1); | ||
282 | } else { | ||
283 | strcpy(buf, buffer); | ||
284 | } | ||
285 | // we may have read data from the next line, save it to rest | ||
286 | if (rest) { | ||
287 | rest[0] = '\0'; | ||
288 | strcpy(rest, lf + 1); | ||
289 | } | ||
290 | free(buffer); | ||
291 | return strlen(buf); | ||
292 | } else { | ||
293 | // no linefeed found, slide data back. | ||
294 | int overflow = readpos - buffer + l - nbyte; | ||
295 | if (overflow > 0) { | ||
296 | memmove(buffer, buffer + overflow , nbyte + 1); | ||
297 | } | ||
298 | } | ||
299 | } | ||
300 | if (l < 0) { | ||
301 | free(buffer); | ||
302 | return l; | ||
303 | } | ||
304 | readpos[l]='\0'; | ||
305 | if (rest) { | ||
306 | rest[0] = '\0'; | ||
307 | } | ||
308 | if (nbyte < readpos - buffer + l - 1) { | ||
309 | memcpy(buf, readpos - nbyte + l + 1, nbyte); | ||
310 | } else { | ||
311 | strncpy(buf, buffer, nbyte); | ||
312 | } | ||
313 | buf[nbyte-1] = '\0'; | ||
314 | free(buffer); | ||
315 | return strlen(buf); | ||
316 | } | ||
317 | |||
318 | // make sure that enough buffer space is available starting from parserpos | ||
319 | static void i3json_ensure_free(int min_free) { | ||
320 | int _step = 10240; | ||
321 | int r = min_free % _step; | ||
322 | if (r) { | ||
323 | min_free += _step - r; | ||
324 | } | ||
325 | if (!i3json_state.buffer) { | ||
326 | i3json_state.buffer = malloc(min_free); | ||
327 | i3json_state.bufsize = min_free; | ||
328 | i3json_state.parserpos = i3json_state.buffer; | ||
329 | } else { | ||
330 | int len = 0; | ||
331 | int pos = 0; | ||
332 | if (i3json_state.line_start) { | ||
333 | len = strlen(i3json_state.line_start); | ||
334 | pos = i3json_state.parserpos - i3json_state.line_start; | ||
335 | if (i3json_state.line_start != i3json_state.buffer) { | ||
336 | memmove(i3json_state.buffer, i3json_state.line_start, len+1); | ||
337 | } | ||
338 | } else { | ||
339 | len = strlen(i3json_state.buffer); | ||
340 | } | ||
341 | if (i3json_state.bufsize < len+min_free) { | ||
342 | i3json_state.bufsize += min_free; | ||
343 | if (i3json_state.bufsize > 1024000) { | ||
344 | sway_abort("Status line json too long or malformed."); | ||
345 | } | ||
346 | i3json_state.buffer = realloc(i3json_state.buffer, i3json_state.bufsize); | ||
347 | if (!i3json_state.buffer) { | ||
348 | sway_abort("Could not allocate json buffer"); | ||
349 | } | ||
350 | } | ||
351 | if (i3json_state.line_start) { | ||
352 | i3json_state.line_start = i3json_state.buffer; | ||
353 | i3json_state.parserpos = i3json_state.buffer + pos; | ||
354 | } else { | ||
355 | i3json_state.parserpos = i3json_state.buffer; | ||
356 | } | ||
357 | } | ||
358 | if (!i3json_state.buffer) { | ||
359 | sway_abort("Could not allocate buffer."); | ||
360 | } | ||
361 | } | ||
362 | |||
363 | // append data and parse it. | ||
364 | static int i3json_handle_data(struct swaybar_state *st, char *data) { | ||
365 | int len = strlen(data); | ||
366 | i3json_ensure_free(len); | ||
367 | strcpy(i3json_state.parserpos, data); | ||
368 | return i3json_parse(st); | ||
369 | } | ||
370 | |||
371 | // read data from fd and parse it. | ||
372 | static int i3json_handle_fd(struct swaybar_state *st) { | ||
373 | i3json_ensure_free(10240); | ||
374 | // get fresh data at the end of the buffer | ||
375 | int readlen = read(st->status_read_fd, i3json_state.parserpos, 10239); | ||
376 | if (readlen < 0) { | ||
377 | return readlen; | ||
378 | } | ||
379 | i3json_state.parserpos[readlen] = '\0'; | ||
380 | return i3json_parse(st); | ||
381 | } | ||
382 | |||
383 | bool handle_status_line(struct swaybar_state *st) { | ||
384 | bool dirty = false; | ||
385 | |||
386 | switch (st->status->protocol) { | ||
387 | case I3BAR: | ||
388 | sway_log(L_DEBUG, "Got i3bar protocol."); | ||
389 | if (i3json_handle_fd(st) > 0) { | ||
390 | dirty = true; | ||
391 | } | ||
392 | break; | ||
393 | case TEXT: | ||
394 | sway_log(L_DEBUG, "Got text protocol."); | ||
395 | read_line_tail(st->status_read_fd, line, sizeof(line), line_rest); | ||
396 | dirty = true; | ||
397 | st->status->text_line = line; | ||
398 | break; | ||
399 | case UNDEF: | ||
400 | sway_log(L_DEBUG, "Detecting protocol..."); | ||
401 | if (read_line_tail(st->status_read_fd, line, sizeof(line), line_rest) < 0) { | ||
402 | break; | ||
403 | } | ||
404 | dirty = true; | ||
405 | st->status->text_line = line; | ||
406 | st->status->protocol = TEXT; | ||
407 | if (line[0] == '{') { | ||
408 | // detect i3bar json protocol | ||
409 | json_object *proto = json_tokener_parse(line); | ||
410 | json_object *version; | ||
411 | if (proto) { | ||
412 | if (json_object_object_get_ex(proto, "version", &version) | ||
413 | && json_object_get_int(version) == 1 | ||
414 | ) { | ||
415 | sway_log(L_DEBUG, "Switched to i3bar protocol."); | ||
416 | st->status->protocol = I3BAR; | ||
417 | i3json_handle_data(st, line_rest); | ||
418 | } | ||
419 | json_object_put(proto); | ||
420 | } | ||
421 | } | ||
422 | break; | ||
423 | } | ||
424 | |||
425 | return dirty; | ||
426 | } | ||
427 | |||
428 | struct status_line *init_status_line() { | ||
429 | struct status_line *line = malloc(sizeof(struct status_line)); | ||
430 | line->block_line = create_list(); | ||
431 | line->text_line = NULL; | ||
432 | line->protocol = UNDEF; | ||
433 | |||
434 | return line; | ||
435 | } | ||