From fce37e86e468450e21e6b7251f6dd1e9d47e2ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 15 Feb 2019 18:58:21 +0100 Subject: [PATCH] module/i3: break out I3 IPC receive loop --- modules/i3-common.c | 189 +++++++++++++++++++++++++++++++++++++ modules/i3-common.h | 33 +++++++ modules/i3.c | 224 ++++++++++++-------------------------------- 3 files changed, 283 insertions(+), 163 deletions(-) diff --git a/modules/i3-common.c b/modules/i3-common.c index c6aea3e..ab7b405 100644 --- a/modules/i3-common.c +++ b/modules/i3-common.c @@ -4,12 +4,15 @@ #include #include +#include + #if defined(ENABLE_X11) #include #include #endif #include +#include #define LOG_MODULE "i3:common" #include "../log.h" @@ -112,3 +115,189 @@ i3_send_pkg(int sock, int cmd, char *data) return true; } + +bool +i3_receive_loop(int abort_fd, int sock, + const struct i3_ipc_callbacks *cbs, void *data) +{ + /* Initial reply typically requires a couple of KB. But we often + * need more later. For example, switching workspaces can result + * in quite big notification messages. */ + size_t reply_buf_size = 4096; + char *buf = malloc(reply_buf_size); + size_t buf_idx = 0; + + bool err = false; + + while (!err) { + struct pollfd fds[] = { + {.fd = abort_fd, .events = POLLIN}, + {.fd = sock, .events = POLLIN} + }; + + int res = poll(fds, 2, -1); + if (res <= 0) { + LOG_ERRNO("failed to poll()"); + err = true; + break; + } + + if (fds[0].revents & POLLIN) + break; + + if (fds[1].revents & POLLHUP) { + LOG_WARN("disconnected"); + break; + } + + assert(fds[1].revents & POLLIN); + + /* Grow receive buffer, if necessary */ + if (buf_idx == reply_buf_size) { + LOG_DBG("growing reply buffer: %zu -> %zu", + reply_buf_size, reply_buf_size * 2); + + char *new_buf = realloc(buf, reply_buf_size * 2); + if (new_buf == NULL) { + LOG_ERR("failed to grow reply buffer from %zu to %zu bytes", + reply_buf_size, reply_buf_size * 2); + err = true; + break; + } + + buf = new_buf; + reply_buf_size *= 2; + } + + assert(reply_buf_size > buf_idx); + + ssize_t bytes = read(sock, &buf[buf_idx], reply_buf_size - buf_idx); + if (bytes < 0) { + LOG_ERRNO("failed to read from i3's socket"); + err = true; + break; + } + + buf_idx += bytes; + + while (!err && buf_idx >= sizeof(i3_ipc_header_t)) { + const i3_ipc_header_t *hdr = (const i3_ipc_header_t *)buf; + if (strncmp(hdr->magic, I3_IPC_MAGIC, sizeof(hdr->magic)) != 0) { + LOG_ERR( + "i3 IPC header magic mismatch: expected \"%.*s\", got \"%.*s\"", + (int)sizeof(hdr->magic), I3_IPC_MAGIC, + (int)sizeof(hdr->magic), hdr->magic); + + err = true; + break; + } + + size_t total_size = sizeof(i3_ipc_header_t) + hdr->size; + + if (total_size > buf_idx) { + LOG_DBG("got %zd bytes, need %zu", bytes, total_size); + break; + } + + /* Json-c expects a NULL-terminated string */ + char json_str[hdr->size + 1]; + memcpy(json_str, &buf[sizeof(*hdr)], hdr->size); + json_str[hdr->size] = '\0'; + //printf("raw: %s\n", json_str); + LOG_DBG("raw: %s\n", json_str); + + //json_tokener *tokener = json_tokener_new(); + struct json_object *json = json_tokener_parse(json_str); + if (json == NULL) { + LOG_ERR("failed to parse json"); + err = true; + break; + } + + //err = pkt_handler(hdr, json, data); + i3_ipc_callback_t pkt_handler = NULL; + switch (hdr->type) { + case I3_IPC_REPLY_TYPE_COMMAND: + pkt_handler = cbs->reply_command; + break; + case I3_IPC_REPLY_TYPE_WORKSPACES: + pkt_handler = cbs->reply_workspaces; + break; + case I3_IPC_REPLY_TYPE_SUBSCRIBE: + pkt_handler = cbs->reply_subscribe; + break; + case I3_IPC_REPLY_TYPE_OUTPUTS: + pkt_handler = cbs->reply_outputs; + break; + case I3_IPC_REPLY_TYPE_TREE: + pkt_handler = cbs->reply_tree; + break; + case I3_IPC_REPLY_TYPE_MARKS: + pkt_handler = cbs->reply_marks; + break; + case I3_IPC_REPLY_TYPE_BAR_CONFIG: + pkt_handler = cbs->reply_bar_config; + break; + case I3_IPC_REPLY_TYPE_VERSION: + pkt_handler = cbs->reply_version; + break; + case I3_IPC_REPLY_TYPE_BINDING_MODES: + pkt_handler = cbs->reply_binding_modes; + break; + case I3_IPC_REPLY_TYPE_CONFIG: + pkt_handler = cbs->reply_config; + break; + case I3_IPC_REPLY_TYPE_TICK: + pkt_handler = cbs->reply_tick; + break; + case I3_IPC_REPLY_TYPE_SYNC: + pkt_handler = cbs->reply_sync; + break; + + case I3_IPC_EVENT_WORKSPACE: + pkt_handler = cbs->event_workspace; + break; + case I3_IPC_EVENT_OUTPUT: + pkt_handler = cbs->event_output; + break; + case I3_IPC_EVENT_MODE: + pkt_handler = cbs->event_mode; + break; + case I3_IPC_EVENT_WINDOW: + pkt_handler = cbs->event_window; + break; + case I3_IPC_EVENT_BARCONFIG_UPDATE: + pkt_handler = cbs->event_barconfig_update; + break; + case I3_IPC_EVENT_BINDING: + pkt_handler = cbs->event_binding; + break; + case I3_IPC_EVENT_SHUTDOWN: + pkt_handler = cbs->event_shutdown; + break; + case I3_IPC_EVENT_TICK: + pkt_handler = cbs->event_tick; + break; + + default: + LOG_ERR("unimplemented IPC reply type: %d", hdr->type); + pkt_handler = NULL; + break; + } + + if (pkt_handler != NULL) + err = !pkt_handler(hdr->type, json, data); + else + LOG_DBG("no handler for reply/event %d; ignoring", hdr->type); + + json_object_put(json); + + assert(total_size <= buf_idx); + memmove(buf, &buf[total_size], buf_idx - total_size); + buf_idx -= total_size; + } + } + + free(buf); + return !err; +} diff --git a/modules/i3-common.h b/modules/i3-common.h index 51e837c..bbfe80f 100644 --- a/modules/i3-common.h +++ b/modules/i3-common.h @@ -6,5 +6,38 @@ #include #include +//#include +#include + bool i3_get_socket_address(struct sockaddr_un *addr); bool i3_send_pkg(int sock, int cmd, char *data); + +typedef bool (*i3_ipc_callback_t)(int type, const struct json_object *json, void *data); + +struct i3_ipc_callbacks { + i3_ipc_callback_t reply_command; + i3_ipc_callback_t reply_workspaces; + i3_ipc_callback_t reply_subscribe; + i3_ipc_callback_t reply_outputs; + i3_ipc_callback_t reply_tree; + i3_ipc_callback_t reply_marks; + i3_ipc_callback_t reply_bar_config; + i3_ipc_callback_t reply_version; + i3_ipc_callback_t reply_binding_modes; + i3_ipc_callback_t reply_config; + i3_ipc_callback_t reply_tick; + i3_ipc_callback_t reply_sync; + + i3_ipc_callback_t event_workspace; + i3_ipc_callback_t event_output; + i3_ipc_callback_t event_mode; + i3_ipc_callback_t event_window; + i3_ipc_callback_t event_barconfig_update; + i3_ipc_callback_t event_binding; + i3_ipc_callback_t event_shutdown; + i3_ipc_callback_t event_tick; +}; + +bool i3_receive_loop( + int abort_fd, int sock, + const struct i3_ipc_callbacks *callbacks, void *data); diff --git a/modules/i3.c b/modules/i3.c index 98e0f82..f35c94d 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -15,10 +15,6 @@ #include -#include -#include -#include - #define LOG_MODULE "i3" #define LOG_ENABLE_DBG 0 #include "../log.h" @@ -180,7 +176,7 @@ workspace_lookup(struct private *m, const char *name) static bool -handle_get_version_reply(struct private *m, const struct json_object *json) +handle_get_version_reply(int type, const struct json_object *json, void *_m) { if (!json_object_is_type(json, json_type_object)) { LOG_ERR("'version' reply is not of type 'object'"); @@ -199,7 +195,7 @@ handle_get_version_reply(struct private *m, const struct json_object *json) } static bool -handle_subscribe_reply(struct private *m, const struct json_object *json) +handle_subscribe_reply(int type, const struct json_object *json, void *_m) { if (!json_object_is_type(json, json_type_object)) { LOG_ERR("'subscribe' reply is not of type 'object'"); @@ -222,13 +218,18 @@ handle_subscribe_reply(struct private *m, const struct json_object *json) } static bool -handle_get_workspaces_reply(struct private *m, const struct json_object *json) +handle_get_workspaces_reply(int type, const struct json_object *json, void *_mod) { + struct module *mod = _mod; + struct private *m = mod->private; + if (!json_object_is_type(json, json_type_array)) { LOG_ERR("'workspaces' reply is not of type 'array'"); return false; } + mtx_lock(&mod->lock); + workspaces_free(m); size_t count = json_object_array_length(json); @@ -239,18 +240,24 @@ handle_get_workspaces_reply(struct private *m, const struct json_object *json) if (!workspace_from_json( json_object_array_get_idx(json, i), &m->workspaces.v[i])) { workspaces_free(m); + mtx_unlock(&mod->lock); return false; } LOG_DBG("#%zu: %s", i, m->workspaces.v[i].name); } + mtx_unlock(&mod->lock); + mod->bar->refresh(mod->bar); return true; } static bool -handle_workspace_event(struct private *m, const struct json_object *json) +handle_workspace_event(int type, const struct json_object *json, void *_mod) { + struct module *mod = _mod; + struct private *m = mod->private; + if (!json_object_is_type(json, json_type_object)) { LOG_ERR("'workspace' event is not of type 'object'"); return false; @@ -285,12 +292,14 @@ handle_workspace_event(struct private *m, const struct json_object *json) } } + mtx_lock(&mod->lock); + if (strcmp(change_str, "init") == 0) { assert(workspace_lookup(m, json_object_get_string(current_name)) == NULL); struct workspace ws; if (!workspace_from_json(current, &ws)) - return false; + goto err; workspace_add(m, ws); } @@ -303,14 +312,14 @@ handle_workspace_event(struct private *m, const struct json_object *json) else if (strcmp(change_str, "focus") == 0) { if (old == NULL || !json_object_is_type(old, json_type_object)) { LOG_ERR("'workspace' event did not contain a 'old' object value"); - return false; + goto err; } struct json_object *old_name = json_object_object_get(old, "name"); if (old_name == NULL || !json_object_is_type(old_name, json_type_string)) { LOG_ERR("'workspace' event's 'old' object did not " "contain a 'name' string value"); - return false; + goto err; } struct workspace *w = workspace_lookup( @@ -329,7 +338,7 @@ handle_workspace_event(struct private *m, const struct json_object *json) if (urgent == NULL || !json_object_is_type(urgent, json_type_boolean)) { LOG_ERR("'workspace' event's 'current' object did not " "contain a 'urgent' boolean value"); - return false; + goto err; } w->urgent = json_object_get_boolean(urgent); @@ -348,7 +357,7 @@ handle_workspace_event(struct private *m, const struct json_object *json) if (urgent == NULL || !json_object_is_type(urgent, json_type_boolean)) { LOG_ERR("'workspace' event's 'current' object did not " "contain a 'urgent' boolean value"); - return false; + goto err; } struct workspace *w = workspace_lookup( @@ -359,12 +368,23 @@ handle_workspace_event(struct private *m, const struct json_object *json) else if (strcmp(change_str, "reload") == 0) LOG_WARN("unimplemented: 'reload' event"); + mtx_unlock(&mod->lock); + mod->bar->refresh(mod->bar); return true; + +err: + mtx_unlock(&mod->lock); + return false; } static bool -handle_window_event(struct private *m, const struct json_object *json) +handle_window_event(int type, const struct json_object *json, void *_mod) { + struct module *mod = _mod; + struct private *m = mod->private; + + mtx_lock(&mod->lock); + struct workspace *ws = NULL; size_t focused = 0; for (size_t i = 0; i < m->workspaces.count; i++) { @@ -379,13 +399,13 @@ handle_window_event(struct private *m, const struct json_object *json) if (!json_object_is_type(json, json_type_object)) { LOG_ERR("'window' event is not of type 'object'"); - return false; + goto err; } struct json_object *change = json_object_object_get(json, "change"); if (change == NULL || !json_object_is_type(change, json_type_string)) { LOG_ERR("'window' event did not contain a 'change' string value"); - return false; + goto err; } const char *change_str = json_object_get_string(change); @@ -396,13 +416,16 @@ handle_window_event(struct private *m, const struct json_object *json) ws->window.title = ws->window.application = NULL; ws->window.pid = -1; + + mtx_unlock(&mod->lock); + mod->bar->refresh(mod->bar); return true; } const struct json_object *container = json_object_object_get(json, "container"); if (container == NULL || !json_object_is_type(container, json_type_object)) { LOG_ERR("'window' event (%s) did not contain a 'container' object", change_str); - return false; + goto err; } struct json_object *name = json_object_object_get(container, "name"); @@ -410,7 +433,7 @@ handle_window_event(struct private *m, const struct json_object *json) LOG_ERR( "'window' event (%s) did not contain a 'container.name' string value", change_str); - return false; + goto err; } free(ws->window.title); @@ -421,7 +444,7 @@ handle_window_event(struct private *m, const struct json_object *json) LOG_ERR( "'window' event (%s) did not contain a 'container.pid' integer value", change_str); - return false; + goto err; } /* If PID has changed, update application name from /proc//comm */ @@ -437,6 +460,9 @@ handle_window_event(struct private *m, const struct json_object *json) /* Application may simply have terminated */ free(ws->window.application); ws->window.application = NULL; ws->window.pid = -1; + + mtx_unlock(&mod->lock); + mod->bar->refresh(mod->bar); return true; } @@ -447,17 +473,22 @@ handle_window_event(struct private *m, const struct json_object *json) application[bytes - 1] = '\0'; ws->window.application = strdup(application); close(fd); + + mod->bar->refresh(mod->bar); } + mtx_unlock(&mod->lock); return true; + +err: + mtx_unlock(&mod->lock); + return false; } static int run(struct module *mod) { - struct private *m = mod->private; - struct sockaddr_un addr; if (!i3_get_socket_address(&addr)) return 1; @@ -479,150 +510,17 @@ run(struct module *mod) i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[\"workspace\", \"window\"]"); i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); - /* Initial reply typically requires a couple of KB. But we often - * need more later. For example, switching workspaces can result - * in quite big notification messages. */ - size_t reply_buf_size = 4096; - char *buf = malloc(reply_buf_size); - size_t buf_idx = 0; + static const struct i3_ipc_callbacks callbacks = { + .reply_version = &handle_get_version_reply, + .reply_subscribe = &handle_subscribe_reply, + .reply_workspaces = &handle_get_workspaces_reply, + .event_workspace = &handle_workspace_event, + .event_window = &handle_window_event, + }; - while (true) { - struct pollfd fds[] = { - {.fd = mod->abort_fd, .events = POLLIN}, - {.fd = sock, .events = POLLIN} - }; - - int res = poll(fds, 2, -1); - if (res <= 0) { - LOG_ERRNO("failed to poll()"); - break; - } - - if (fds[0].revents & POLLIN) - break; - - assert(fds[1].revents & POLLIN); - - /* Grow receive buffer, if necessary */ - if (buf_idx == reply_buf_size) { - LOG_DBG("growing reply buffer: %zu -> %zu", - reply_buf_size, reply_buf_size * 2); - - char *new_buf = realloc(buf, reply_buf_size * 2); - if (new_buf == NULL) { - LOG_ERR("failed to grow reply buffer from %zu to %zu bytes", - reply_buf_size, reply_buf_size * 2); - break; - } - - buf = new_buf; - reply_buf_size *= 2; - } - - assert(reply_buf_size > buf_idx); - - ssize_t bytes = read(sock, &buf[buf_idx], reply_buf_size - buf_idx); - if (bytes < 0) { - LOG_ERRNO("failed to read from i3's socket"); - break; - } - - buf_idx += bytes; - - bool err = false; - bool need_bar_refresh = false; - - while (!err && buf_idx >= sizeof(i3_ipc_header_t)) { - const i3_ipc_header_t *hdr = (const i3_ipc_header_t *)buf; - if (strncmp(hdr->magic, I3_IPC_MAGIC, sizeof(hdr->magic)) != 0) { - LOG_ERR( - "i3 IPC header magic mismatch: expected \"%.*s\", got \"%.*s\"", - (int)sizeof(hdr->magic), I3_IPC_MAGIC, - (int)sizeof(hdr->magic), hdr->magic); - - err = true; - break; - } - - size_t total_size = sizeof(i3_ipc_header_t) + hdr->size; - - if (total_size > buf_idx) { - LOG_DBG("got %zd bytes, need %zu", bytes, total_size); - break; - } - - /* Json-c expects a NULL-terminated string */ - char json_str[hdr->size + 1]; - memcpy(json_str, &buf[sizeof(*hdr)], hdr->size); - json_str[hdr->size] = '\0'; - //printf("raw: %s\n", json_str); - LOG_DBG("raw: %s\n", json_str); - - json_tokener *tokener = json_tokener_new(); - struct json_object *json = json_tokener_parse(json_str); - if (json == NULL) { - LOG_ERR("failed to parse json"); - err = true; - break; - } - - mtx_lock(&mod->lock); - - switch (hdr->type) { - case I3_IPC_REPLY_TYPE_VERSION: - handle_get_version_reply(m, json); - break; - - case I3_IPC_REPLY_TYPE_SUBSCRIBE: - handle_subscribe_reply(m, json); - break; - - case I3_IPC_REPLY_TYPE_WORKSPACES: - handle_get_workspaces_reply(m, json); - need_bar_refresh = true; - break; - - case I3_IPC_EVENT_WORKSPACE: - handle_workspace_event(m, json); - need_bar_refresh = true; - break; - - case I3_IPC_EVENT_WINDOW: - handle_window_event(m, json); - need_bar_refresh = true; - break; - - case I3_IPC_EVENT_OUTPUT: - case I3_IPC_EVENT_MODE: - case I3_IPC_EVENT_BARCONFIG_UPDATE: - case I3_IPC_EVENT_BINDING: - case I3_IPC_EVENT_SHUTDOWN: - case I3_IPC_EVENT_TICK: - break; - - default: assert(false); - } - - mtx_unlock(&mod->lock); - - json_object_put(json); - json_tokener_free(tokener); - - assert(total_size <= buf_idx); - memmove(buf, &buf[total_size], buf_idx - total_size); - buf_idx -= total_size; - } - - if (err) - break; - - if (need_bar_refresh) - mod->bar->refresh(mod->bar); - } - - free(buf); + bool ret = i3_receive_loop(mod->abort_fd, sock, &callbacks, mod); close(sock); - return 0; + return ret ? 0 : 1; } static void