aboutsummaryrefslogtreecommitdiffstats
path: root/swaybar/status_line.c
diff options
context:
space:
mode:
Diffstat (limited to 'swaybar/status_line.c')
-rw-r--r--swaybar/status_line.c594
1 files changed, 97 insertions, 497 deletions
diff --git a/swaybar/status_line.c b/swaybar/status_line.c
index 87e90caf..8afe4707 100644
--- a/swaybar/status_line.c
+++ b/swaybar/status_line.c
@@ -1,530 +1,130 @@
1#define _XOPEN_SOURCE 700 1#define _POSIX_C_SOURCE
2#include <fcntl.h>
3#include <json-c/json.h>
2#include <stdlib.h> 4#include <stdlib.h>
3#include <string.h> 5#include <string.h>
6#include <stdio.h>
4#include <unistd.h> 7#include <unistd.h>
5#include <json-c/json.h> 8#include <wlr/util/log.h>
6
7#include "swaybar/config.h" 9#include "swaybar/config.h"
8#include "swaybar/status_line.h" 10#include "swaybar/status_line.h"
9#include "log.h" 11#include "readline.h"
10#include "util.h"
11
12#define I3JSON_MAXDEPTH 4
13#define I3JSON_UNKNOWN 0
14#define I3JSON_ARRAY 1
15#define I3JSON_STRING 2
16
17struct {
18 int bufsize;
19 char *buffer;
20 char *line_start;
21 char *parserpos;
22 bool escape;
23 int depth;
24 int bar[I3JSON_MAXDEPTH+1];
25} i3json_state = { 0, NULL, NULL, NULL, false, 0, { I3JSON_UNKNOWN } };
26
27static char line[1024];
28static char line_rest[1024];
29
30static char event_buff[1024];
31
32static void free_status_block(void *item) {
33 if (!item) {
34 return;
35 }
36 struct status_block *sb = (struct status_block*)item;
37 if (sb->full_text) {
38 free(sb->full_text);
39 }
40 if (sb->short_text) {
41 free(sb->short_text);
42 }
43 if (sb->align) {
44 free(sb->align);
45 }
46 if (sb->name) {
47 free(sb->name);
48 }
49 if (sb->instance) {
50 free(sb->instance);
51 }
52 free(sb);
53}
54
55static void parse_json(struct bar *bar, const char *text) {
56 json_object *results = json_tokener_parse(text);
57 if (!results) {
58 sway_log(L_DEBUG, "Failed to parse json");
59 return;
60 }
61
62 if (json_object_array_length(results) < 1) {
63 return;
64 }
65
66 if (bar->status->block_line) {
67 list_foreach(bar->status->block_line, free_status_block);
68 list_free(bar->status->block_line);
69 }
70
71 bar->status->block_line = create_list();
72
73 int i;
74 for (i = 0; i < json_object_array_length(results); ++i) {
75 json_object *full_text, *short_text, *color, *min_width, *align, *urgent;
76 json_object *name, *instance, *separator, *separator_block_width;
77 json_object *background, *border, *border_top, *border_bottom;
78 json_object *border_left, *border_right, *markup;
79
80 json_object *json = json_object_array_get_idx(results, i);
81 if (!json) {
82 continue;
83 }
84
85 json_object_object_get_ex(json, "full_text", &full_text);
86 json_object_object_get_ex(json, "short_text", &short_text);
87 json_object_object_get_ex(json, "color", &color);
88 json_object_object_get_ex(json, "min_width", &min_width);
89 json_object_object_get_ex(json, "align", &align);
90 json_object_object_get_ex(json, "urgent", &urgent);
91 json_object_object_get_ex(json, "name", &name);
92 json_object_object_get_ex(json, "instance", &instance);
93 json_object_object_get_ex(json, "markup", &markup);
94 json_object_object_get_ex(json, "separator", &separator);
95 json_object_object_get_ex(json, "separator_block_width", &separator_block_width);
96 json_object_object_get_ex(json, "background", &background);
97 json_object_object_get_ex(json, "border", &border);
98 json_object_object_get_ex(json, "border_top", &border_top);
99 json_object_object_get_ex(json, "border_bottom", &border_bottom);
100 json_object_object_get_ex(json, "border_left", &border_left);
101 json_object_object_get_ex(json, "border_right", &border_right);
102
103 struct status_block *new = calloc(1, sizeof(struct status_block));
104
105 if (full_text) {
106 new->full_text = strdup(json_object_get_string(full_text));
107 }
108
109 if (short_text) {
110 new->short_text = strdup(json_object_get_string(short_text));
111 }
112
113 if (color) {
114 new->color = parse_color(json_object_get_string(color));
115 } else {
116 new->color = bar->config->colors.statusline;
117 }
118
119 if (min_width) {
120 json_type type = json_object_get_type(min_width);
121 if (type == json_type_int) {
122 new->min_width = json_object_get_int(min_width);
123 } else if (type == json_type_string) {
124 /* the width will be calculated when rendering */
125 new->min_width = 0;
126 }
127 }
128
129 if (align) {
130 new->align = strdup(json_object_get_string(align));
131 } else {
132 new->align = strdup("left");
133 }
134
135 if (urgent) {
136 new->urgent = json_object_get_int(urgent);
137 }
138 12
139 if (name) { 13void status_error(struct status_line *status, const char *text) {
140 new->name = strdup(json_object_get_string(name)); 14 close(status->read_fd);
141 } 15 close(status->write_fd);
142 16 status->protocol = PROTOCOL_ERROR;
143 if (instance) { 17 status->text = text;
144 new->instance = strdup(json_object_get_string(instance));
145 }
146
147 if (markup) {
148 new->markup = false;
149 const char *markup_str = json_object_get_string(markup);
150 if (strcmp(markup_str, "pango") == 0) {
151 new->markup = true;
152 }
153 }
154
155 if (separator) {
156 new->separator = json_object_get_int(separator);
157 } else {
158 new->separator = true; // i3bar spec
159 }
160
161 if (separator_block_width) {
162 new->separator_block_width = json_object_get_int(separator_block_width);
163 } else {
164 new->separator_block_width = 9; // i3bar spec
165 }
166
167 // Airblader features
168 if (background) {
169 new->background = parse_color(json_object_get_string(background));
170 } else {
171 new->background = 0x0; // transparent
172 }
173
174 if (border) {
175 new->border = parse_color(json_object_get_string(border));
176 } else {
177 new->border = 0x0; // transparent
178 }
179
180 if (border_top) {
181 new->border_top = json_object_get_int(border_top);
182 } else {
183 new->border_top = 1;
184 }
185
186 if (border_bottom) {
187 new->border_bottom = json_object_get_int(border_bottom);
188 } else {
189 new->border_bottom = 1;
190 }
191
192 if (border_left) {
193 new->border_left = json_object_get_int(border_left);
194 } else {
195 new->border_left = 1;
196 }
197
198 if (border_right) {
199 new->border_right = json_object_get_int(border_right);
200 } else {
201 new->border_right = 1;
202 }
203
204 list_add(bar->status->block_line, new);
205 }
206
207 json_object_put(results);
208} 18}
209 19
210// continue parsing from last parserpos 20bool status_handle_readable(struct status_line *status) {
211static int i3json_parse(struct bar *bar) { 21 char *line;
212 char *c = i3json_state.parserpos; 22 switch (status->protocol) {
213 int handled = 0; 23 case PROTOCOL_ERROR:
214 while (*c) {
215 if (i3json_state.bar[i3json_state.depth] == I3JSON_STRING) {
216 if (!i3json_state.escape && *c == '"') {
217 --i3json_state.depth;
218 }
219 i3json_state.escape = !i3json_state.escape && *c == '\\';
220 } else {
221 switch (*c) {
222 case '[':
223 ++i3json_state.depth;
224 if (i3json_state.depth > I3JSON_MAXDEPTH) {
225 sway_abort("JSON too deep");
226 }
227 i3json_state.bar[i3json_state.depth] = I3JSON_ARRAY;
228 if (i3json_state.depth == 2) {
229 i3json_state.line_start = c;
230 }
231 break;
232 case ']':
233 if (i3json_state.bar[i3json_state.depth] != I3JSON_ARRAY) {
234 sway_abort("JSON malformed");
235 }
236 --i3json_state.depth;
237 if (i3json_state.depth == 1) {
238 // c[1] is valid since c[0] != '\0'
239 char p = c[1];
240 c[1] = '\0';
241 parse_json(bar, i3json_state.line_start);
242 c[1] = p;
243 ++handled;
244 i3json_state.line_start = c+1;
245 }
246 break;
247 case '"':
248 ++i3json_state.depth;
249 if (i3json_state.depth > I3JSON_MAXDEPTH) {
250 sway_abort("JSON too deep");
251 }
252 i3json_state.bar[i3json_state.depth] = I3JSON_STRING;
253 break;
254 }
255 }
256 ++c;
257 }
258 i3json_state.parserpos = c;
259 return handled;
260}
261
262// Read line from file descriptor, only show the line tail if it is too long.
263// In non-blocking mode treat "no more data" as a linebreak.
264// If data after a line break has been read, return it in rest.
265// If rest is non-empty, then use that as the start of the next line.
266static int read_line_tail(int fd, char *buf, int nbyte, char *rest) {
267 if (fd < 0 || !buf || !nbyte) {
268 return -1;
269 }
270 int l;
271 char *buffer = malloc(nbyte*2+1);
272 char *readpos = buffer;
273 char *lf;
274 // prepend old data to new line if necessary
275 if (rest) {
276 l = strlen(rest);
277 if (l > nbyte) {
278 strcpy(buffer, rest + l - nbyte);
279 readpos += nbyte;
280 } else if (l) {
281 strcpy(buffer, rest);
282 readpos += l;
283 }
284 }
285 // read until a linefeed is found or no more data is available
286 while ((l = read(fd, readpos, nbyte)) > 0) {
287 readpos[l] = '\0';
288 lf = strchr(readpos, '\n');
289 if (lf) {
290 // linefeed found, replace with \0
291 *lf = '\0';
292 // give data from the end of the line, try to fill the buffer
293 if (lf-buffer > nbyte) {
294 strcpy(buf, lf - nbyte + 1);
295 } else {
296 strcpy(buf, buffer);
297 }
298 // we may have read data from the next line, save it to rest
299 if (rest) {
300 rest[0] = '\0';
301 strcpy(rest, lf + 1);
302 }
303 free(buffer);
304 return strlen(buf);
305 } else {
306 // no linefeed found, slide data back.
307 int overflow = readpos - buffer + l - nbyte;
308 if (overflow > 0) {
309 memmove(buffer, buffer + overflow , nbyte + 1);
310 }
311 }
312 }
313 if (l < 0) {
314 free(buffer);
315 return l;
316 }
317 readpos[l]='\0';
318 if (rest) {
319 rest[0] = '\0';
320 }
321 if (nbyte < readpos - buffer + l - 1) {
322 memcpy(buf, readpos - nbyte + l + 1, nbyte);
323 } else {
324 strncpy(buf, buffer, nbyte);
325 }
326 buf[nbyte-1] = '\0';
327 free(buffer);
328 return strlen(buf);
329}
330
331// make sure that enough buffer space is available starting from parserpos
332static void i3json_ensure_free(int min_free) {
333 int _step = 10240;
334 int r = min_free % _step;
335 if (r) {
336 min_free += _step - r;
337 }
338 if (!i3json_state.buffer) {
339 i3json_state.buffer = malloc(min_free);
340 i3json_state.bufsize = min_free;
341 i3json_state.parserpos = i3json_state.buffer;
342 } else {
343 int len = 0;
344 int pos = 0;
345 if (i3json_state.line_start) {
346 len = strlen(i3json_state.line_start);
347 pos = i3json_state.parserpos - i3json_state.line_start;
348 if (i3json_state.line_start != i3json_state.buffer) {
349 memmove(i3json_state.buffer, i3json_state.line_start, len+1);
350 }
351 } else {
352 len = strlen(i3json_state.buffer);
353 }
354 if (i3json_state.bufsize < len+min_free) {
355 i3json_state.bufsize += min_free;
356 if (i3json_state.bufsize > 1024000) {
357 sway_abort("Status line json too long or malformed.");
358 }
359 i3json_state.buffer = realloc(i3json_state.buffer, i3json_state.bufsize);
360 if (!i3json_state.buffer) {
361 sway_abort("Could not allocate json buffer");
362 }
363 }
364 if (i3json_state.line_start) {
365 i3json_state.line_start = i3json_state.buffer;
366 i3json_state.parserpos = i3json_state.buffer + pos;
367 } else {
368 i3json_state.parserpos = i3json_state.buffer;
369 }
370 }
371 if (!i3json_state.buffer) {
372 sway_abort("Could not allocate buffer.");
373 }
374}
375
376// append data and parse it.
377static int i3json_handle_data(struct bar *bar, char *data) {
378 int len = strlen(data);
379 i3json_ensure_free(len);
380 strcpy(i3json_state.parserpos, data);
381 return i3json_parse(bar);
382}
383
384// read data from fd and parse it.
385static int i3json_handle_fd(struct bar *bar) {
386 i3json_ensure_free(10240);
387 // get fresh data at the end of the buffer
388 int readlen = read(bar->status_read_fd, i3json_state.parserpos, 10239);
389 if (readlen < 0) {
390 return readlen;
391 }
392 i3json_state.parserpos[readlen] = '\0';
393 return i3json_parse(bar);
394}
395
396bool status_line_mouse_event(struct bar *bar, int x, int y, uint32_t button) {
397 sway_log(L_DEBUG, "status_line_mouse_event.");
398 if (!bar->status->click_events) {
399 sway_log(L_DEBUG, "click_events are not enabled.");
400 return false; 24 return false;
401 } 25 case PROTOCOL_I3BAR:
402 26 if (i3bar_handle_readable(status) > 0) {
403 if (bar->status->protocol == I3BAR) {
404 sway_log(L_DEBUG, "Sending click event.");
405
406 // find clicked block
407 struct status_block *clicked_block = NULL;
408 struct status_block *current_block = NULL;
409 int num_blocks = bar->status->block_line->length;
410
411 if (num_blocks == 0) {
412 return false;
413 } else {
414 current_block = bar->status->block_line->items[0];
415 if (x < current_block->x) {
416 return false;
417 }
418 }
419
420 for (int i = 0; i < num_blocks; i++) {
421 current_block = bar->status->block_line->items[i];
422 if (x < (current_block->x + current_block->width)) {
423 clicked_block = current_block;
424 break;
425 }
426 }
427
428 if (!clicked_block || !clicked_block->name) {
429 return false;
430 }
431
432 // event example {"name":"capture","instance":"label","button":1,"x":3431,"y":18}
433
434 struct json_object *event_json = json_object_new_object();
435 json_object_object_add(event_json, "name", json_object_new_string(clicked_block->name));
436 if (clicked_block->instance) {
437 json_object_object_add(event_json, "instance", json_object_new_string(clicked_block->instance));
438 }
439 json_object_object_add(event_json, "button", json_object_new_int(button));
440 json_object_object_add(event_json, "x", json_object_new_int(x));
441 json_object_object_add(event_json, "y", json_object_new_int(y));
442
443 int len = snprintf(event_buff, sizeof(event_buff), "%s\n", json_object_to_json_string(event_json));
444
445 json_object_put(event_json);
446
447 if (len <= (int)sizeof(event_buff)) { // if not truncated
448 write(bar->status_write_fd, event_buff, len);
449 return true; 27 return true;
450 } 28 }
451 }
452
453 return false;
454}
455
456bool handle_status_line(struct bar *bar) {
457 bool dirty = false;
458
459 switch (bar->status->protocol) {
460 case I3BAR:
461 sway_log(L_DEBUG, "Got i3bar protocol.");
462 if (i3json_handle_fd(bar) > 0) {
463 dirty = true;
464 }
465 break; 29 break;
466 case TEXT: 30 case PROTOCOL_TEXT:
467 sway_log(L_DEBUG, "Got text protocol."); 31 line = read_line_buffer(status->read,
468 read_line_tail(bar->status_read_fd, line, sizeof(line), line_rest); 32 status->text_state.buffer, status->text_state.buffer_size);
469 dirty = true; 33 if (!line) {
470 bar->status->text_line = line; 34 status_error(status, "[error reading from status command]");
471 break; 35 } else {
472 case UNDEF: 36 status->text = line;
473 sway_log(L_DEBUG, "Detecting protocol..."); 37 }
474 if (read_line_tail(bar->status_read_fd, line, sizeof(line), line_rest) < 0) { 38 return true;
475 break; 39 case PROTOCOL_UNDEF:
40 line = read_line_buffer(status->read,
41 status->text_state.buffer, status->text_state.buffer_size);
42 if (!line) {
43 status_error(status, "[error reading from status command]");
44 return false;
476 } 45 }
477 dirty = true;
478 bar->status->text_line = line;
479 bar->status->protocol = TEXT;
480 if (line[0] == '{') { 46 if (line[0] == '{') {
481 // detect i3bar json protocol
482 json_object *proto = json_tokener_parse(line); 47 json_object *proto = json_tokener_parse(line);
483 if (proto) { 48 if (proto) {
484
485 json_object *version; 49 json_object *version;
486 if (json_object_object_get_ex(proto, "version", &version) 50 if (json_object_object_get_ex(proto, "version", &version)
487 && json_object_get_int(version) == 1 51 && json_object_get_int(version) == 1) {
488 ) { 52 wlr_log(L_DEBUG, "Switched to i3bar protocol.");
489 sway_log(L_DEBUG, "Switched to i3bar protocol."); 53 status->protocol = PROTOCOL_I3BAR;
490 bar->status->protocol = I3BAR;
491 } 54 }
492
493 json_object *click_events; 55 json_object *click_events;
494 if (json_object_object_get_ex(proto, "click_events", &click_events) 56 if (json_object_object_get_ex(
57 proto, "click_events", &click_events)
495 && json_object_get_boolean(click_events)) { 58 && json_object_get_boolean(click_events)) {
496 59 wlr_log(L_DEBUG, "Enabled click events.");
497 sway_log(L_DEBUG, "Enabling click events."); 60 status->i3bar_state.click_events = true;
498 bar->status->click_events = true;
499
500 const char *events_array = "[\n"; 61 const char *events_array = "[\n";
501 write(bar->status_write_fd, events_array, strlen(events_array)); 62 ssize_t len = strlen(events_array);
63 if (write(status->write_fd, events_array, len) != len) {
64 status_error(status,
65 "[failed to write to status command]");
66 }
502 } 67 }
503
504 i3json_handle_data(bar, line_rest);
505
506 json_object_put(proto); 68 json_object_put(proto);
507 } 69 }
70
71 status->protocol = PROTOCOL_I3BAR;
72 free(status->text_state.buffer);
73 wl_list_init(&status->blocks);
74 status->i3bar_state.buffer_size = 4096;
75 status->i3bar_state.buffer =
76 malloc(status->i3bar_state.buffer_size);
77 } else {
78 status->protocol = PROTOCOL_TEXT;
79 status->text = line;
508 } 80 }
509 break; 81 return true;
510 } 82 }
511 83 return false;
512 return dirty;
513} 84}
514 85
515struct status_line *init_status_line() { 86struct status_line *status_line_init(char *cmd) {
516 struct status_line *line = malloc(sizeof(struct status_line)); 87 struct status_line *status = calloc(1, sizeof(struct status_line));
517 line->block_line = create_list(); 88 status->text_state.buffer_size = 8192;
518 line->text_line = NULL; 89 status->text_state.buffer = malloc(status->text_state.buffer_size);
519 line->protocol = UNDEF;
520 line->click_events = false;
521 90
522 return line; 91 int pipe_read_fd[2];
523} 92 int pipe_write_fd[2];
93 if (pipe(pipe_read_fd) != 0 || pipe(pipe_write_fd) != 0) {
94 wlr_log(L_ERROR, "Unable to create pipes for status_command fork");
95 exit(1);
96 }
97
98 status->pid = fork();
99 if (status->pid == 0) {
100 dup2(pipe_read_fd[1], STDOUT_FILENO);
101 close(pipe_read_fd[0]);
102 close(pipe_read_fd[1]);
524 103
525void free_status_line(struct status_line *line) { 104 dup2(pipe_write_fd[0], STDIN_FILENO);
526 if (line->block_line) { 105 close(pipe_write_fd[0]);
527 list_foreach(line->block_line, free_status_block); 106 close(pipe_write_fd[1]);
528 list_free(line->block_line); 107
108 char *const _cmd[] = { "sh", "-c", cmd, NULL, };
109 execvp(_cmd[0], _cmd);
110 exit(1);
529 } 111 }
112
113 close(pipe_read_fd[1]);
114 status->read_fd = pipe_read_fd[0];
115 fcntl(status->read_fd, F_SETFL, O_NONBLOCK);
116 close(pipe_write_fd[0]);
117 status->write_fd = pipe_write_fd[1];
118 fcntl(status->write_fd, F_SETFL, O_NONBLOCK);
119
120 status->read = fdopen(status->read_fd, "r");
121 status->write = fdopen(status->write_fd, "w");
122 return status;
123}
124
125void status_line_free(struct status_line *status) {
126 close(status->read_fd);
127 close(status->write_fd);
128 kill(status->pid, SIGTERM);
129 free(status);
530} 130}