diff options
Diffstat (limited to 'swaybar/status_line.c')
-rw-r--r-- | swaybar/status_line.c | 594 |
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 @@ -#define _XOPEN_SOURCE 700 +#define _POSIX_C_SOURCE +#include <fcntl.h> +#include <json-c/json.h> #include <stdlib.h> #include <string.h> +#include <stdio.h> #include <unistd.h> -#include <json-c/json.h> - +#include <wlr/util/log.h> #include "swaybar/config.h" #include "swaybar/status_line.h" -#include "log.h" -#include "util.h" - -#define I3JSON_MAXDEPTH 4 -#define I3JSON_UNKNOWN 0 -#define I3JSON_ARRAY 1 -#define I3JSON_STRING 2 - -struct { - int bufsize; - char *buffer; - char *line_start; - char *parserpos; - bool escape; - int depth; - int bar[I3JSON_MAXDEPTH+1]; -} i3json_state = { 0, NULL, NULL, NULL, false, 0, { I3JSON_UNKNOWN } }; - -static char line[1024]; -static char line_rest[1024]; - -static char event_buff[1024]; - -static void free_status_block(void *item) { - if (!item) { - return; - } - struct status_block *sb = (struct status_block*)item; - if (sb->full_text) { - free(sb->full_text); - } - if (sb->short_text) { - free(sb->short_text); - } - if (sb->align) { - free(sb->align); - } - if (sb->name) { - free(sb->name); - } - if (sb->instance) { - free(sb->instance); - } - free(sb); -} - -static void parse_json(struct bar *bar, const char *text) { - json_object *results = json_tokener_parse(text); - if (!results) { - sway_log(L_DEBUG, "Failed to parse json"); - return; - } - - if (json_object_array_length(results) < 1) { - return; - } - - if (bar->status->block_line) { - list_foreach(bar->status->block_line, free_status_block); - list_free(bar->status->block_line); - } - - bar->status->block_line = create_list(); - - int i; - for (i = 0; i < json_object_array_length(results); ++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); - if (!json) { - continue; - } - - json_object_object_get_ex(json, "full_text", &full_text); - json_object_object_get_ex(json, "short_text", &short_text); - json_object_object_get_ex(json, "color", &color); - json_object_object_get_ex(json, "min_width", &min_width); - json_object_object_get_ex(json, "align", &align); - json_object_object_get_ex(json, "urgent", &urgent); - json_object_object_get_ex(json, "name", &name); - json_object_object_get_ex(json, "instance", &instance); - json_object_object_get_ex(json, "markup", &markup); - json_object_object_get_ex(json, "separator", &separator); - json_object_object_get_ex(json, "separator_block_width", &separator_block_width); - json_object_object_get_ex(json, "background", &background); - json_object_object_get_ex(json, "border", &border); - json_object_object_get_ex(json, "border_top", &border_top); - json_object_object_get_ex(json, "border_bottom", &border_bottom); - json_object_object_get_ex(json, "border_left", &border_left); - json_object_object_get_ex(json, "border_right", &border_right); - - struct status_block *new = calloc(1, sizeof(struct status_block)); - - if (full_text) { - new->full_text = strdup(json_object_get_string(full_text)); - } - - if (short_text) { - new->short_text = strdup(json_object_get_string(short_text)); - } - - if (color) { - new->color = parse_color(json_object_get_string(color)); - } else { - new->color = bar->config->colors.statusline; - } - - if (min_width) { - json_type type = json_object_get_type(min_width); - if (type == json_type_int) { - new->min_width = json_object_get_int(min_width); - } else if (type == json_type_string) { - /* the width will be calculated when rendering */ - new->min_width = 0; - } - } - - if (align) { - new->align = strdup(json_object_get_string(align)); - } else { - new->align = strdup("left"); - } - - if (urgent) { - new->urgent = json_object_get_int(urgent); - } +#include "readline.h" - if (name) { - new->name = strdup(json_object_get_string(name)); - } - - if (instance) { - new->instance = strdup(json_object_get_string(instance)); - } - - if (markup) { - new->markup = false; - const char *markup_str = json_object_get_string(markup); - if (strcmp(markup_str, "pango") == 0) { - new->markup = true; - } - } - - if (separator) { - new->separator = json_object_get_int(separator); - } else { - new->separator = true; // i3bar spec - } - - if (separator_block_width) { - new->separator_block_width = json_object_get_int(separator_block_width); - } else { - new->separator_block_width = 9; // i3bar spec - } - - // Airblader features - if (background) { - new->background = parse_color(json_object_get_string(background)); - } else { - new->background = 0x0; // transparent - } - - if (border) { - new->border = parse_color(json_object_get_string(border)); - } else { - new->border = 0x0; // transparent - } - - if (border_top) { - new->border_top = json_object_get_int(border_top); - } else { - new->border_top = 1; - } - - if (border_bottom) { - new->border_bottom = json_object_get_int(border_bottom); - } else { - new->border_bottom = 1; - } - - if (border_left) { - new->border_left = json_object_get_int(border_left); - } else { - new->border_left = 1; - } - - if (border_right) { - new->border_right = json_object_get_int(border_right); - } else { - new->border_right = 1; - } - - list_add(bar->status->block_line, new); - } - - json_object_put(results); +void status_error(struct status_line *status, const char *text) { + close(status->read_fd); + close(status->write_fd); + status->protocol = PROTOCOL_ERROR; + status->text = text; } -// continue parsing from last parserpos -static int i3json_parse(struct bar *bar) { - char *c = i3json_state.parserpos; - int handled = 0; - while (*c) { - if (i3json_state.bar[i3json_state.depth] == I3JSON_STRING) { - if (!i3json_state.escape && *c == '"') { - --i3json_state.depth; - } - i3json_state.escape = !i3json_state.escape && *c == '\\'; - } else { - switch (*c) { - case '[': - ++i3json_state.depth; - if (i3json_state.depth > I3JSON_MAXDEPTH) { - sway_abort("JSON too deep"); - } - i3json_state.bar[i3json_state.depth] = I3JSON_ARRAY; - if (i3json_state.depth == 2) { - i3json_state.line_start = c; - } - break; - case ']': - if (i3json_state.bar[i3json_state.depth] != I3JSON_ARRAY) { - sway_abort("JSON malformed"); - } - --i3json_state.depth; - if (i3json_state.depth == 1) { - // c[1] is valid since c[0] != '\0' - char p = c[1]; - c[1] = '\0'; - parse_json(bar, i3json_state.line_start); - c[1] = p; - ++handled; - i3json_state.line_start = c+1; - } - break; - case '"': - ++i3json_state.depth; - if (i3json_state.depth > I3JSON_MAXDEPTH) { - sway_abort("JSON too deep"); - } - i3json_state.bar[i3json_state.depth] = I3JSON_STRING; - break; - } - } - ++c; - } - i3json_state.parserpos = c; - return handled; -} - -// Read line from file descriptor, only show the line tail if it is too long. -// In non-blocking mode treat "no more data" as a linebreak. -// If data after a line break has been read, return it in rest. -// If rest is non-empty, then use that as the start of the next line. -static int read_line_tail(int fd, char *buf, int nbyte, char *rest) { - if (fd < 0 || !buf || !nbyte) { - return -1; - } - int l; - char *buffer = malloc(nbyte*2+1); - char *readpos = buffer; - char *lf; - // prepend old data to new line if necessary - if (rest) { - l = strlen(rest); - if (l > nbyte) { - strcpy(buffer, rest + l - nbyte); - readpos += nbyte; - } else if (l) { - strcpy(buffer, rest); - readpos += l; - } - } - // read until a linefeed is found or no more data is available - while ((l = read(fd, readpos, nbyte)) > 0) { - readpos[l] = '\0'; - lf = strchr(readpos, '\n'); - if (lf) { - // linefeed found, replace with \0 - *lf = '\0'; - // give data from the end of the line, try to fill the buffer - if (lf-buffer > nbyte) { - strcpy(buf, lf - nbyte + 1); - } else { - strcpy(buf, buffer); - } - // we may have read data from the next line, save it to rest - if (rest) { - rest[0] = '\0'; - strcpy(rest, lf + 1); - } - free(buffer); - return strlen(buf); - } else { - // no linefeed found, slide data back. - int overflow = readpos - buffer + l - nbyte; - if (overflow > 0) { - memmove(buffer, buffer + overflow , nbyte + 1); - } - } - } - if (l < 0) { - free(buffer); - return l; - } - readpos[l]='\0'; - if (rest) { - rest[0] = '\0'; - } - if (nbyte < readpos - buffer + l - 1) { - memcpy(buf, readpos - nbyte + l + 1, nbyte); - } else { - strncpy(buf, buffer, nbyte); - } - buf[nbyte-1] = '\0'; - free(buffer); - return strlen(buf); -} - -// make sure that enough buffer space is available starting from parserpos -static void i3json_ensure_free(int min_free) { - int _step = 10240; - int r = min_free % _step; - if (r) { - min_free += _step - r; - } - if (!i3json_state.buffer) { - i3json_state.buffer = malloc(min_free); - i3json_state.bufsize = min_free; - i3json_state.parserpos = i3json_state.buffer; - } else { - int len = 0; - int pos = 0; - if (i3json_state.line_start) { - len = strlen(i3json_state.line_start); - pos = i3json_state.parserpos - i3json_state.line_start; - if (i3json_state.line_start != i3json_state.buffer) { - memmove(i3json_state.buffer, i3json_state.line_start, len+1); - } - } else { - len = strlen(i3json_state.buffer); - } - if (i3json_state.bufsize < len+min_free) { - i3json_state.bufsize += min_free; - if (i3json_state.bufsize > 1024000) { - sway_abort("Status line json too long or malformed."); - } - i3json_state.buffer = realloc(i3json_state.buffer, i3json_state.bufsize); - if (!i3json_state.buffer) { - sway_abort("Could not allocate json buffer"); - } - } - if (i3json_state.line_start) { - i3json_state.line_start = i3json_state.buffer; - i3json_state.parserpos = i3json_state.buffer + pos; - } else { - i3json_state.parserpos = i3json_state.buffer; - } - } - if (!i3json_state.buffer) { - sway_abort("Could not allocate buffer."); - } -} - -// append data and parse it. -static int i3json_handle_data(struct bar *bar, char *data) { - int len = strlen(data); - i3json_ensure_free(len); - strcpy(i3json_state.parserpos, data); - return i3json_parse(bar); -} - -// read data from fd and parse it. -static int i3json_handle_fd(struct bar *bar) { - i3json_ensure_free(10240); - // get fresh data at the end of the buffer - int readlen = read(bar->status_read_fd, i3json_state.parserpos, 10239); - if (readlen < 0) { - return readlen; - } - i3json_state.parserpos[readlen] = '\0'; - return i3json_parse(bar); -} - -bool status_line_mouse_event(struct bar *bar, int x, int y, uint32_t button) { - sway_log(L_DEBUG, "status_line_mouse_event."); - if (!bar->status->click_events) { - sway_log(L_DEBUG, "click_events are not enabled."); +bool status_handle_readable(struct status_line *status) { + char *line; + switch (status->protocol) { + case PROTOCOL_ERROR: return false; - } - - if (bar->status->protocol == I3BAR) { - sway_log(L_DEBUG, "Sending click event."); - - // find clicked block - struct status_block *clicked_block = NULL; - struct status_block *current_block = NULL; - int num_blocks = bar->status->block_line->length; - - if (num_blocks == 0) { - return false; - } else { - current_block = bar->status->block_line->items[0]; - if (x < current_block->x) { - return false; - } - } - - for (int i = 0; i < num_blocks; i++) { - current_block = bar->status->block_line->items[i]; - if (x < (current_block->x + current_block->width)) { - clicked_block = current_block; - break; - } - } - - if (!clicked_block || !clicked_block->name) { - return false; - } - - // event example {"name":"capture","instance":"label","button":1,"x":3431,"y":18} - - struct json_object *event_json = json_object_new_object(); - json_object_object_add(event_json, "name", json_object_new_string(clicked_block->name)); - if (clicked_block->instance) { - json_object_object_add(event_json, "instance", json_object_new_string(clicked_block->instance)); - } - json_object_object_add(event_json, "button", json_object_new_int(button)); - json_object_object_add(event_json, "x", json_object_new_int(x)); - json_object_object_add(event_json, "y", json_object_new_int(y)); - - int len = snprintf(event_buff, sizeof(event_buff), "%s\n", json_object_to_json_string(event_json)); - - json_object_put(event_json); - - if (len <= (int)sizeof(event_buff)) { // if not truncated - write(bar->status_write_fd, event_buff, len); + case PROTOCOL_I3BAR: + if (i3bar_handle_readable(status) > 0) { return true; } - } - - return false; -} - -bool handle_status_line(struct bar *bar) { - bool dirty = false; - - switch (bar->status->protocol) { - case I3BAR: - sway_log(L_DEBUG, "Got i3bar protocol."); - if (i3json_handle_fd(bar) > 0) { - dirty = true; - } break; - case TEXT: - sway_log(L_DEBUG, "Got text protocol."); - read_line_tail(bar->status_read_fd, line, sizeof(line), line_rest); - dirty = true; - bar->status->text_line = line; - break; - case UNDEF: - sway_log(L_DEBUG, "Detecting protocol..."); - if (read_line_tail(bar->status_read_fd, line, sizeof(line), line_rest) < 0) { - break; + case PROTOCOL_TEXT: + line = read_line_buffer(status->read, + status->text_state.buffer, status->text_state.buffer_size); + if (!line) { + status_error(status, "[error reading from status command]"); + } else { + status->text = line; + } + return true; + case PROTOCOL_UNDEF: + line = read_line_buffer(status->read, + status->text_state.buffer, status->text_state.buffer_size); + if (!line) { + status_error(status, "[error reading from status command]"); + return false; } - dirty = true; - bar->status->text_line = line; - bar->status->protocol = TEXT; if (line[0] == '{') { - // detect i3bar json protocol json_object *proto = json_tokener_parse(line); if (proto) { - json_object *version; if (json_object_object_get_ex(proto, "version", &version) - && json_object_get_int(version) == 1 - ) { - sway_log(L_DEBUG, "Switched to i3bar protocol."); - bar->status->protocol = I3BAR; + && json_object_get_int(version) == 1) { + wlr_log(L_DEBUG, "Switched to i3bar protocol."); + status->protocol = PROTOCOL_I3BAR; } - json_object *click_events; - if (json_object_object_get_ex(proto, "click_events", &click_events) + if (json_object_object_get_ex( + proto, "click_events", &click_events) && json_object_get_boolean(click_events)) { - - sway_log(L_DEBUG, "Enabling click events."); - bar->status->click_events = true; - + wlr_log(L_DEBUG, "Enabled click events."); + status->i3bar_state.click_events = true; const char *events_array = "[\n"; - write(bar->status_write_fd, events_array, strlen(events_array)); + ssize_t len = strlen(events_array); + if (write(status->write_fd, events_array, len) != len) { + status_error(status, + "[failed to write to status command]"); + } } - - i3json_handle_data(bar, line_rest); - json_object_put(proto); } + + status->protocol = PROTOCOL_I3BAR; + free(status->text_state.buffer); + wl_list_init(&status->blocks); + status->i3bar_state.buffer_size = 4096; + status->i3bar_state.buffer = + malloc(status->i3bar_state.buffer_size); + } else { + status->protocol = PROTOCOL_TEXT; + status->text = line; } - break; + return true; } - - return dirty; + return false; } -struct status_line *init_status_line() { - struct status_line *line = malloc(sizeof(struct status_line)); - line->block_line = create_list(); - line->text_line = NULL; - line->protocol = UNDEF; - line->click_events = false; +struct status_line *status_line_init(char *cmd) { + struct status_line *status = calloc(1, sizeof(struct status_line)); + status->text_state.buffer_size = 8192; + status->text_state.buffer = malloc(status->text_state.buffer_size); - return line; -} + int pipe_read_fd[2]; + int pipe_write_fd[2]; + if (pipe(pipe_read_fd) != 0 || pipe(pipe_write_fd) != 0) { + wlr_log(L_ERROR, "Unable to create pipes for status_command fork"); + exit(1); + } + + status->pid = fork(); + if (status->pid == 0) { + dup2(pipe_read_fd[1], STDOUT_FILENO); + close(pipe_read_fd[0]); + close(pipe_read_fd[1]); -void free_status_line(struct status_line *line) { - if (line->block_line) { - list_foreach(line->block_line, free_status_block); - list_free(line->block_line); + dup2(pipe_write_fd[0], STDIN_FILENO); + close(pipe_write_fd[0]); + close(pipe_write_fd[1]); + + char *const _cmd[] = { "sh", "-c", cmd, NULL, }; + execvp(_cmd[0], _cmd); + exit(1); } + + close(pipe_read_fd[1]); + status->read_fd = pipe_read_fd[0]; + fcntl(status->read_fd, F_SETFL, O_NONBLOCK); + close(pipe_write_fd[0]); + status->write_fd = pipe_write_fd[1]; + fcntl(status->write_fd, F_SETFL, O_NONBLOCK); + + status->read = fdopen(status->read_fd, "r"); + status->write = fdopen(status->write_fd, "w"); + return status; +} + +void status_line_free(struct status_line *status) { + close(status->read_fd); + close(status->write_fd); + kill(status->pid, SIGTERM); + free(status); } |