diff --git a/common/readline.c b/common/readline.c index a2c69018..58652429 100644 --- a/common/readline.c +++ b/common/readline.c @@ -70,28 +70,3 @@ char *peek_line(FILE *file, int line_offset, long *position) { fseek(file, pos, SEEK_SET); return line; } - -char *read_line_buffer(FILE *file, char *string, size_t string_len) { - size_t length = 0; - if (!string) { - return NULL; - } - while (1) { - int c = getc(file); - if (c == EOF || c == '\n' || c == '\0') { - break; - } - if (c == '\r') { - continue; - } - string[length++] = c; - if (string_len <= length) { - return NULL; - } - } - if (length + 1 == string_len) { - return NULL; - } - string[length] = '\0'; - return string; -} diff --git a/include/sway/commands.h b/include/sway/commands.h index e51b12fd..226cf932 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -103,6 +103,7 @@ sway_cmd cmd_client_urgent; sway_cmd cmd_client_placeholder; sway_cmd cmd_client_background; sway_cmd cmd_commands; +sway_cmd cmd_create_output; sway_cmd cmd_debuglog; sway_cmd cmd_default_border; sway_cmd cmd_default_floating_border; diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h index 439dc1bf..d10251dd 100644 --- a/include/sway/tree/view.h +++ b/include/sway/tree/view.h @@ -61,6 +61,8 @@ struct sway_view { struct sway_container *container; // NULL if unmapped and transactions finished struct wlr_surface *surface; // NULL for unmapped views + pid_t pid; + // Geometry of the view itself (excludes borders) in layout coordinates double x, y; int width, height; diff --git a/include/swaybar/event_loop.h b/include/swaybar/event_loop.h index 99f6ed36..47be5b79 100644 --- a/include/swaybar/event_loop.h +++ b/include/swaybar/event_loop.h @@ -19,8 +19,8 @@ bool remove_event(int fd); bool remove_timer(timer_t timer); // Blocks and returns after sending callbacks -void event_loop_poll(); +void event_loop_poll(void); -void init_event_loop(); +void init_event_loop(void); #endif diff --git a/include/swaybar/status_line.h b/include/swaybar/status_line.h index 150267cd..d3eabdf6 100644 --- a/include/swaybar/status_line.h +++ b/include/swaybar/status_line.h @@ -1,5 +1,6 @@ #ifndef _SWAYBAR_STATUS_LINE_H #define _SWAYBAR_STATUS_LINE_H +#include #include #include #include @@ -12,28 +13,6 @@ enum status_protocol { PROTOCOL_I3BAR, }; -struct text_protocol_state { - char *buffer; - size_t buffer_size; -}; - -enum json_node_type { - JSON_NODE_UNKNOWN, - JSON_NODE_ARRAY, - JSON_NODE_STRING, -}; - -struct i3bar_protocol_state { - bool click_events; - char *buffer; - size_t buffer_size; - size_t buffer_index; - const char *current_node; - bool escape; - size_t depth; - enum json_node_type nodes[16]; -}; - struct i3bar_block { struct wl_list link; int ref_count; @@ -63,8 +42,13 @@ struct status_line { const char *text; struct wl_list blocks; // i3bar_block::link - struct text_protocol_state text_state; - struct i3bar_protocol_state i3bar_state; + bool click_events; + char *buffer; + size_t buffer_size; + size_t buffer_index; + bool started; + bool expecting_comma; + json_tokener *tokener; }; struct status_line *status_line_init(char *cmd); diff --git a/sway/commands.c b/sway/commands.c index 41e1c653..07169f1e 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -143,6 +143,7 @@ static struct cmd_handler config_handlers[] = { /* Runtime-only commands. Keep alphabetized */ static struct cmd_handler command_handlers[] = { { "border", cmd_border }, + { "create_output", cmd_create_output }, { "exit", cmd_exit }, { "floating", cmd_floating }, { "fullscreen", cmd_fullscreen }, @@ -215,18 +216,23 @@ struct cmd_handler *find_handler(char *line, struct cmd_handler *cmd_handlers, static void set_config_node(struct sway_node *node) { config->handler_context.node = node; + config->handler_context.container = NULL; + config->handler_context.workspace = NULL; + + if (node == NULL) { + return; + } + switch (node->type) { case N_CONTAINER: config->handler_context.container = node->sway_container; config->handler_context.workspace = node->sway_container->workspace; break; case N_WORKSPACE: - config->handler_context.container = NULL; config->handler_context.workspace = node->sway_workspace; break; - default: - config->handler_context.container = NULL; - config->handler_context.workspace = NULL; + case N_ROOT: + case N_OUTPUT: break; } } diff --git a/sway/commands/create_output.c b/sway/commands/create_output.c new file mode 100644 index 00000000..a852c2a0 --- /dev/null +++ b/sway/commands/create_output.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include "sway/commands.h" +#include "sway/server.h" +#include "log.h" + +static void create_output(struct wlr_backend *backend, void *data) { + bool *done = data; + if (*done) { + return; + } + + if (wlr_backend_is_wl(backend)) { + wlr_wl_output_create(backend); + *done = true; + } else if (wlr_backend_is_x11(backend)) { + wlr_x11_output_create(backend); + *done = true; + } +} + +/** + * This command is intended for developer use only. + */ +struct cmd_results *cmd_create_output(int argc, char **argv) { + sway_assert(wlr_backend_is_multi(server.backend), + "Expected a multi backend"); + + bool done = false; + wlr_multi_for_each_backend(server.backend, create_output, &done); + + if (!done) { + return cmd_results_new(CMD_INVALID, "create_output", + "Can only create outputs for Wayland or X11 backends"); + } + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} diff --git a/sway/config/input.c b/sway/config/input.c index 9885e85c..ad5b96c8 100644 --- a/sway/config/input.c +++ b/sway/config/input.c @@ -140,6 +140,13 @@ void free_input_config(struct input_config *ic) { return; } free(ic->identifier); + free(ic->xkb_layout); + free(ic->xkb_model); + free(ic->xkb_options); + free(ic->xkb_rules); + free(ic->xkb_variant); + free(ic->mapped_from_region); + free(ic->mapped_to_output); free(ic); } diff --git a/sway/ipc-json.c b/sway/ipc-json.c index 52278be2..f054ac9f 100644 --- a/sway/ipc-json.c +++ b/sway/ipc-json.c @@ -221,6 +221,8 @@ static const char *describe_container_border(enum sway_container_border border) } static void ipc_json_describe_view(struct sway_container *c, json_object *object) { + json_object_object_add(object, "pid", json_object_new_int(c->view->pid)); + const char *app_id = view_get_app_id(c->view); json_object_object_add(object, "app_id", app_id ? json_object_new_string(app_id) : NULL); diff --git a/sway/meson.build b/sway/meson.build index 01c83a33..d67a4c64 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -35,6 +35,7 @@ sway_sources = files( 'commands/bind.c', 'commands/border.c', 'commands/client.c', + 'commands/create_output.c', 'commands/default_border.c', 'commands/default_floating_border.c', 'commands/default_orientation.c', diff --git a/sway/tree/view.c b/sway/tree/view.c index e4e1c161..4398f518 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -470,6 +470,7 @@ static struct sway_workspace *select_workspace(struct sway_view *view) { wl_resource_get_client(view->surface->resource); wl_client_get_credentials(client, &pid, NULL, NULL); #endif + view->pid = pid; ws = root_workspace_for_pid(pid); if (ws) { return ws; diff --git a/swaybar/event_loop.c b/swaybar/event_loop.c index bc4053be..686b9962 100644 --- a/swaybar/event_loop.c +++ b/swaybar/event_loop.c @@ -105,7 +105,7 @@ bool remove_timer(timer_t timer) { return false; } -void event_loop_poll() { +void event_loop_poll(void) { poll(event_loop.fds.items, event_loop.fds.length, -1); for (int i = 0; i < event_loop.fds.length; ++i) { @@ -146,7 +146,7 @@ void event_loop_poll() { } } -void init_event_loop() { +void init_event_loop(void) { event_loop.fds.length = 0; event_loop.fds.capacity = 10; event_loop.fds.items = malloc( diff --git a/swaybar/i3bar.c b/swaybar/i3bar.c index 0becae5d..88404703 100644 --- a/swaybar/i3bar.c +++ b/swaybar/i3bar.c @@ -1,6 +1,7 @@ #define _POSIX_C_SOURCE 200809L #include #include +#include #include #include #include @@ -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,149 @@ 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; + + // in order to print the json for debugging purposes + // the last character is temporarily replaced with a null character + // (the last character is used in case the buffer is full) + char *last_char_pos = + &status->buffer[buffer_pos + status->tokener->char_offset - 1]; + char last_char = *last_char_pos; + while (isspace(last_char)) { + last_char = *--last_char_pos; } - --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; + *last_char_pos = '\0'; + size_t offset = strspn(&status->buffer[buffer_pos], " \f\n\r\t\v"); + wlr_log(WLR_DEBUG, "Received i3bar json: '%s%c'", + &status->buffer[buffer_pos + offset], last_char); + *last_char_pos = last_char; + + buffer_pos += status->tokener->char_offset; + status->expecting_comma = true; + + if (buffer_pos < status->buffer_index) { + continue; // look for comma without reading more input } - break; - case '"': - ++state->depth; - if (state->depth > - sizeof(state->nodes) / sizeof(state->nodes[0])) { - status_error(status, "[i3bar json too deep]"); - return false; + 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; + } } - 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) { + wlr_log(WLR_DEBUG, "Rendering last received json"); + 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; } @@ -214,7 +263,7 @@ enum hotspot_event_handling i3bar_block_send_click(struct status_line *status, 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)); - if (dprintf(status->write_fd, "%s\n", + if (dprintf(status->write_fd, "%s,\n", json_object_to_json_string(event_json)) < 0) { status_error(status, "[failed to write click event]"); } diff --git a/swaybar/render.c b/swaybar/render.c index 2d848bfa..97690338 100644 --- a/swaybar/render.c +++ b/swaybar/render.c @@ -120,14 +120,14 @@ static void i3bar_block_unref_callback(void *data) { } static uint32_t render_status_block(cairo_t *cairo, - struct swaybar_config *config, struct swaybar_output *output, - struct i3bar_block *block, double *x, + struct swaybar_output *output, struct i3bar_block *block, double *x, uint32_t surface_height, bool focused, bool edge) { if (!block->full_text || !*block->full_text) { return 0; } uint32_t height = surface_height * output->scale; + struct swaybar_config *config = output->bar->config; int text_width, text_height; get_text_size(cairo, config->font, &text_width, &text_height, NULL, @@ -177,16 +177,18 @@ static uint32_t render_status_block(cairo_t *cairo, *x -= margin; } - struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot)); - hotspot->x = *x; - hotspot->y = 0; - hotspot->width = width; - hotspot->height = height; - hotspot->callback = block_hotspot_callback; - hotspot->destroy = i3bar_block_unref_callback; - hotspot->data = block; - block->ref_count++; - wl_list_insert(&output->hotspots, &hotspot->link); + if (output->bar->status->click_events) { + struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot)); + hotspot->x = *x; + hotspot->y = 0; + hotspot->width = width; + hotspot->height = height; + hotspot->callback = block_hotspot_callback; + hotspot->destroy = i3bar_block_unref_callback; + hotspot->data = block; + block->ref_count++; + wl_list_insert(&output->hotspots, &hotspot->link); + } double pos = *x; if (block->background) { @@ -268,7 +270,7 @@ static uint32_t render_status_line_i3bar(cairo_t *cairo, bool edge = true; struct i3bar_block *block; wl_list_for_each(block, &status->blocks, link) { - uint32_t h = render_status_block(cairo, config, output, + uint32_t h = render_status_block(cairo, output, block, x, surface_height, focused, edge); max_height = h > max_height ? h : max_height; edge = false; diff --git a/swaybar/status_line.c b/swaybar/status_line.c index 3ba990bd..401bf6f6 100644 --- a/swaybar/status_line.c +++ b/swaybar/status_line.c @@ -7,86 +7,102 @@ #include #include #include "swaybar/config.h" +#include "swaybar/event_loop.h" #include "swaybar/status_line.h" #include "readline.h" +static void status_line_close_fds(struct status_line *status) { + if (status->read_fd != -1) { + remove_event(status->read_fd); + close(status->read_fd); + status->read_fd = -1; + } + if (status->write_fd != -1) { + close(status->write_fd); + status->write_fd = -1; + } +} + void status_error(struct status_line *status, const char *text) { - close(status->read_fd); - close(status->write_fd); + status_line_close_fds(status); status->protocol = PROTOCOL_ERROR; status->text = text; } bool status_handle_readable(struct status_line *status) { - char *line; + ssize_t read_bytes = 1; switch (status->protocol) { - case PROTOCOL_ERROR: - return false; - case PROTOCOL_I3BAR: - if (i3bar_handle_readable(status) > 0) { - return true; - } - 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) { + errno = 0; + read_bytes = getline(&status->buffer, + &status->buffer_size, status->read); + if (errno == EAGAIN) { + clearerr(status->read); + } else if (errno) { status_error(status, "[error reading from status command]"); - return false; + return true; } - if (line[0] == '{') { - 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) { - wlr_log(WLR_DEBUG, "Switched to i3bar protocol."); - status->protocol = PROTOCOL_I3BAR; - } - json_object *click_events; - if (json_object_object_get_ex( - proto, "click_events", &click_events) - && json_object_get_boolean(click_events)) { - wlr_log(WLR_DEBUG, "Enabled click events."); - status->i3bar_state.click_events = true; - const char *events_array = "[\n"; - ssize_t len = strlen(events_array); - if (write(status->write_fd, events_array, len) != len) { - status_error(status, - "[failed to write to status command]"); - } + + // the header must be sent completely the first time round + json_object *header, *version; + if (status->buffer[read_bytes - 1] == '\n' + && (header = json_tokener_parse(status->buffer)) + && json_object_object_get_ex(header, "version", &version) + && json_object_get_int(version) == 1) { + wlr_log(WLR_DEBUG, "Using i3bar protocol."); + status->protocol = PROTOCOL_I3BAR; + + json_object *click_events; + if (json_object_object_get_ex(header, "click_events", &click_events) + && json_object_get_boolean(click_events)) { + wlr_log(WLR_DEBUG, "Enabling click events."); + status->click_events = true; + if (write(status->write_fd, "[\n", 2) != 2) { + status_error(status, "[failed to write to status command]"); + json_object_put(header); + return true; } - json_object_put(proto); } + json_object_put(header); - 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; + status->tokener = json_tokener_new(); + status->buffer_index = getdelim(&status->buffer, + &status->buffer_size, EOF, status->read); + return i3bar_handle_readable(status); } - return true; + + wlr_log(WLR_DEBUG, "Using text protocol."); + status->protocol = PROTOCOL_TEXT; + status->text = status->buffer; + // intentional fall-through + case PROTOCOL_TEXT: + errno = 0; + while (true) { + if (status->buffer[read_bytes - 1] == '\n') { + status->buffer[read_bytes - 1] = '\0'; + } + read_bytes = getline(&status->buffer, + &status->buffer_size, status->read); + if (errno == EAGAIN) { + clearerr(status->read); + return true; + } else if (errno) { + status_error(status, "[error reading from status command]"); + return true; + } + } + case PROTOCOL_I3BAR: + return i3bar_handle_readable(status); + default: + return false; } - return 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); + status->buffer_size = 8192; + status->buffer = malloc(status->buffer_size); int pipe_read_fd[2]; int pipe_write_fd[2]; @@ -123,20 +139,16 @@ struct status_line *status_line_init(char *cmd) { } void status_line_free(struct status_line *status) { - close(status->read_fd); - close(status->write_fd); + status_line_close_fds(status); kill(status->pid, SIGTERM); - switch (status->protocol) { - case PROTOCOL_I3BAR:; + if (status->protocol == PROTOCOL_I3BAR) { struct i3bar_block *block, *tmp; wl_list_for_each_safe(block, tmp, &status->blocks, link) { + wl_list_remove(&block->link); i3bar_block_unref(block); } - free(status->i3bar_state.buffer); - break; - default: - free(status->text_state.buffer); - break; + json_tokener_free(status->tokener); } + free(status->buffer); free(status); }