From 37d15096f928d277a125a30156c6dee7a017f85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 13 Feb 2019 21:53:53 +0100 Subject: [PATCH 01/11] module/i3: break out get_socket_address() --- modules/CMakeLists.txt | 4 +- modules/i3-common.c | 88 ++++++++++++++++++++++++++++++++++++++++++ modules/i3-common.h | 9 +++++ modules/i3.c | 82 +-------------------------------------- 4 files changed, 102 insertions(+), 81 deletions(-) create mode 100644 modules/i3-common.c create mode 100644 modules/i3-common.h diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 723537d..e1f09cc 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -37,8 +37,10 @@ find_file(I3_IPC_H i3/ipc.h) if (NOT I3_IPC_H) message(FATAL_ERROR "cannot find header file: i3/ipc.h") endif () +add_library(i3-common STATIC EXCLUDE_FROM_ALL i3-common.c i3-common.h) + add_library(i3 ${lib_type} i3.c) -target_link_libraries(i3 module-sdk dynlist PkgConfig::json) +target_link_libraries(i3 module-sdk i3-common dynlist PkgConfig::json) if (ENABLE_X11) target_link_libraries(i3 xcb-stuff) endif () diff --git a/modules/i3-common.c b/modules/i3-common.c new file mode 100644 index 0000000..68919e4 --- /dev/null +++ b/modules/i3-common.c @@ -0,0 +1,88 @@ +#include "i3-common.h" + +#include +#include + +#if defined(ENABLE_X11) + #include + #include +#endif + +#define LOG_MODULE "i3:common" +#include "../log.h" + +#if defined(ENABLE_X11) + #include "../xcb.h" +#endif + +#if defined(ENABLE_X11) +static bool +get_socket_address_x11(struct sockaddr_un *addr) +{ + int default_screen; + xcb_connection_t *conn = xcb_connect(NULL, &default_screen); + if (xcb_connection_has_error(conn) > 0) { + LOG_ERR("failed to connect to X"); + xcb_disconnect(conn); + return false; + } + + xcb_screen_t *screen = xcb_aux_get_screen(conn, default_screen); + + xcb_atom_t atom = get_atom(conn, "I3_SOCKET_PATH"); + assert(atom != XCB_ATOM_NONE); + + xcb_get_property_cookie_t cookie + = xcb_get_property_unchecked( + conn, false, screen->root, atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, sizeof(addr->sun_path)); + + xcb_generic_error_t *err; + xcb_get_property_reply_t *reply = + xcb_get_property_reply(conn, cookie, &err); + + if (err != NULL) { + LOG_ERR("failed to get i3 socket path: %s", xcb_error(err)); + free(err); + free(reply); + return false; + } + + const int len = xcb_get_property_value_length(reply); + assert(len < sizeof(addr->sun_path)); + + if (len == 0) { + LOG_ERR("failed to get i3 socket path: empty reply"); + free(reply); + return false; + } + + memcpy(addr->sun_path, xcb_get_property_value(reply), len); + addr->sun_path[len] = '\0'; + + free(reply); + xcb_disconnect(conn); + return true; +} +#endif + +bool +i3_get_socket_address(struct sockaddr_un *addr) +{ + *addr = (struct sockaddr_un){.sun_family = AF_UNIX}; + + const char *sway_sock = getenv("SWAYSOCK"); + if (sway_sock == NULL) { + sway_sock = getenv("I3SOCK"); + if (sway_sock == NULL) { +#if defined(ENABLE_X11) + return get_socket_address_x11(addr); +#else + return false; +#endif + } + } + + strncpy(addr->sun_path, sway_sock, sizeof(addr->sun_path) - 1); + return true; +} diff --git a/modules/i3-common.h b/modules/i3-common.h new file mode 100644 index 0000000..91ac777 --- /dev/null +++ b/modules/i3-common.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#include +#include +#include + +bool i3_get_socket_address(struct sockaddr_un *addr); diff --git a/modules/i3.c b/modules/i3.c index 65dc702..c7cebb2 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -13,11 +13,6 @@ #include #include -#if defined(ENABLE_X11) - #include - #include -#endif - #include #include @@ -33,9 +28,7 @@ #include "../particles/dynlist.h" #include "../plugin.h" -#if defined(ENABLE_X11) - #include "../xcb.h" -#endif +#include "i3-common.h" struct ws_content { char *name; @@ -479,77 +472,6 @@ handle_window_event(struct private *m, const struct json_object *json) return true; } -#if defined(ENABLE_X11) -static bool -get_socket_address_x11(struct sockaddr_un *addr) -{ - int default_screen; - xcb_connection_t *conn = xcb_connect(NULL, &default_screen); - if (xcb_connection_has_error(conn) > 0) { - LOG_ERR("failed to connect to X"); - xcb_disconnect(conn); - return false; - } - - xcb_screen_t *screen = xcb_aux_get_screen(conn, default_screen); - - xcb_atom_t atom = get_atom(conn, "I3_SOCKET_PATH"); - assert(atom != XCB_ATOM_NONE); - - xcb_get_property_cookie_t cookie - = xcb_get_property_unchecked( - conn, false, screen->root, atom, - XCB_GET_PROPERTY_TYPE_ANY, 0, sizeof(addr->sun_path)); - - xcb_generic_error_t *err; - xcb_get_property_reply_t *reply = - xcb_get_property_reply(conn, cookie, &err); - - if (err != NULL) { - LOG_ERR("failed to get i3 socket path: %s", xcb_error(err)); - free(err); - free(reply); - return false; - } - - const int len = xcb_get_property_value_length(reply); - assert(len < sizeof(addr->sun_path)); - - if (len == 0) { - LOG_ERR("failed to get i3 socket path: empty reply"); - free(reply); - return false; - } - - memcpy(addr->sun_path, xcb_get_property_value(reply), len); - addr->sun_path[len] = '\0'; - - free(reply); - xcb_disconnect(conn); - return true; -} -#endif - -static bool -get_socket_address(struct sockaddr_un *addr) -{ - *addr = (struct sockaddr_un){.sun_family = AF_UNIX}; - - const char *sway_sock = getenv("SWAYSOCK"); - if (sway_sock == NULL) { - sway_sock = getenv("I3SOCK"); - if (sway_sock == NULL) { -#if defined(ENABLE_X11) - return get_socket_address_x11(addr); -#else - return false; -#endif - } - } - - strncpy(addr->sun_path, sway_sock, sizeof(addr->sun_path) - 1); - return true; -} static int run(struct module *mod) @@ -557,7 +479,7 @@ run(struct module *mod) struct private *m = mod->private; struct sockaddr_un addr; - if (!get_socket_address(&addr)) + if (!i3_get_socket_address(&addr)) return 1; int sock = socket(AF_UNIX, SOCK_STREAM, 0); From 76dc4f82cdff1ffe57bec01832940e8758f6cdb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 13 Feb 2019 21:57:36 +0100 Subject: [PATCH 02/11] module/i3-common: always disconnect from XCB on error --- modules/i3-common.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/i3-common.c b/modules/i3-common.c index 68919e4..41295fa 100644 --- a/modules/i3-common.c +++ b/modules/i3-common.c @@ -37,15 +37,14 @@ get_socket_address_x11(struct sockaddr_un *addr) conn, false, screen->root, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, sizeof(addr->sun_path)); - xcb_generic_error_t *err; + xcb_generic_error_t *err = NULL; xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, cookie, &err); + bool ret = false; if (err != NULL) { LOG_ERR("failed to get i3 socket path: %s", xcb_error(err)); - free(err); - free(reply); - return false; + goto err; } const int len = xcb_get_property_value_length(reply); @@ -53,16 +52,19 @@ get_socket_address_x11(struct sockaddr_un *addr) if (len == 0) { LOG_ERR("failed to get i3 socket path: empty reply"); - free(reply); - return false; + goto err; } memcpy(addr->sun_path, xcb_get_property_value(reply), len); addr->sun_path[len] = '\0'; + ret = true; + +err: + free(err); free(reply); xcb_disconnect(conn); - return true; + return ret; } #endif From 92319714c78c035506577ab6a4f32e5909054250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 13 Feb 2019 22:00:13 +0100 Subject: [PATCH 03/11] module/i3: break out send_pkg() --- modules/i3-common.c | 24 ++++++++++++++++++++++++ modules/i3-common.h | 1 + modules/i3.c | 26 +++----------------------- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/modules/i3-common.c b/modules/i3-common.c index 41295fa..c876652 100644 --- a/modules/i3-common.c +++ b/modules/i3-common.c @@ -1,6 +1,7 @@ #include "i3-common.h" #include +#include #include #if defined(ENABLE_X11) @@ -8,6 +9,8 @@ #include #endif +#include + #define LOG_MODULE "i3:common" #include "../log.h" @@ -88,3 +91,24 @@ i3_get_socket_address(struct sockaddr_un *addr) strncpy(addr->sun_path, sway_sock, sizeof(addr->sun_path) - 1); return true; } + +bool +i3_send_pkg(int sock, int cmd, char *data) +{ + size_t size = data != NULL ? strlen(data) : 0; + i3_ipc_header_t hdr = { + .magic = I3_IPC_MAGIC, + .size = size, + .type = cmd + }; + + if (write(sock, &hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr)) + return false; + + if (data != NULL) { + if (write(sock, data, size) != (ssize_t)size) + return false; + } + + return true; +} diff --git a/modules/i3-common.h b/modules/i3-common.h index 91ac777..51e837c 100644 --- a/modules/i3-common.h +++ b/modules/i3-common.h @@ -7,3 +7,4 @@ #include bool i3_get_socket_address(struct sockaddr_un *addr); +bool i3_send_pkg(int sock, int cmd, char *data); diff --git a/modules/i3.c b/modules/i3.c index c7cebb2..98e0f82 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -178,26 +178,6 @@ workspace_lookup(struct private *m, const char *name) return NULL; } -static bool -send_pkg(int sock, int cmd, char *data) -{ - size_t size = data != NULL ? strlen(data) : 0; - i3_ipc_header_t hdr = { - .magic = I3_IPC_MAGIC, - .size = size, - .type = cmd - }; - - if (write(sock, &hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr)) - return false; - - if (data != NULL) { - if (write(sock, data, size) != (ssize_t)size) - return false; - } - - return true; -} static bool handle_get_version_reply(struct private *m, const struct json_object *json) @@ -495,9 +475,9 @@ run(struct module *mod) return 1; } - send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_VERSION, NULL); - send_pkg(sock, I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[\"workspace\", \"window\"]"); - send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); + i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_VERSION, NULL); + 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 From 6df68f1c23587f49b5d48c3c64884f28f2d99f74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 13 Feb 2019 22:00:42 +0100 Subject: [PATCH 04/11] module/i3-common: const:ify --- modules/i3-common.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/i3-common.c b/modules/i3-common.c index c876652..c6aea3e 100644 --- a/modules/i3-common.c +++ b/modules/i3-common.c @@ -95,8 +95,8 @@ i3_get_socket_address(struct sockaddr_un *addr) bool i3_send_pkg(int sock, int cmd, char *data) { - size_t size = data != NULL ? strlen(data) : 0; - i3_ipc_header_t hdr = { + const size_t size = data != NULL ? strlen(data) : 0; + const i3_ipc_header_t hdr = { .magic = I3_IPC_MAGIC, .size = size, .type = cmd 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 05/11] 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 From e36ba56cafbf3a8ef617f998d71a985a5c316167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 15 Feb 2019 20:57:31 +0100 Subject: [PATCH 06/11] module/i3-common: being disconnected is usually not an error --- modules/i3-common.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/i3-common.c b/modules/i3-common.c index ab7b405..939fe30 100644 --- a/modules/i3-common.c +++ b/modules/i3-common.c @@ -142,11 +142,13 @@ i3_receive_loop(int abort_fd, int sock, break; } - if (fds[0].revents & POLLIN) + if (fds[0].revents & POLLIN) { + LOG_DBG("aborted"); break; + } if (fds[1].revents & POLLHUP) { - LOG_WARN("disconnected"); + LOG_DBG("disconnected"); break; } From 5e97c77c2413353fff66c242672a57744eac748f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 15 Feb 2019 20:58:43 +0100 Subject: [PATCH 07/11] module/i3: remove unneeded includes --- modules/i3.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/modules/i3.c b/modules/i3.c index f35c94d..3373f44 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -1,16 +1,10 @@ #include -#include #include #include #include #include -#include - #include -#include -#include -#include #include #include From f9044ec88339ea1f4b0abb9875d61a71a507b853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 15 Feb 2019 22:01:07 +0100 Subject: [PATCH 08/11] module/i3: add a 'burst_done()' callback This callback is called after all received packets have been processed, before going into a blocking poll() again. --- modules/i3-common.c | 3 +++ modules/i3-common.h | 2 ++ 2 files changed, 5 insertions(+) diff --git a/modules/i3-common.c b/modules/i3-common.c index 939fe30..0da04a5 100644 --- a/modules/i3-common.c +++ b/modules/i3-common.c @@ -298,6 +298,9 @@ i3_receive_loop(int abort_fd, int sock, memmove(buf, &buf[total_size], buf_idx - total_size); buf_idx -= total_size; } + + if (cbs->burst_done != NULL) + cbs->burst_done(data); } free(buf); diff --git a/modules/i3-common.h b/modules/i3-common.h index bbfe80f..975d42a 100644 --- a/modules/i3-common.h +++ b/modules/i3-common.h @@ -15,6 +15,8 @@ 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 { + void (*burst_done)(void *data); + i3_ipc_callback_t reply_command; i3_ipc_callback_t reply_workspaces; i3_ipc_callback_t reply_subscribe; From ae7f554b8c85ed592b5097f97fc632b02ae414cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 15 Feb 2019 22:01:58 +0100 Subject: [PATCH 09/11] module/i3: only call bar->refresh() from burst_done() callback IPC reply and event handlers no longer call bar->refresh() directly. Instead, they set a 'dirty' bit. Then, in burst_done(), we call bar->refresh() when the dirty bit has been set. --- modules/i3.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/modules/i3.c b/modules/i3.c index 3373f44..6ec1b20 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -43,6 +43,8 @@ struct private { int left_spacing; int right_spacing; + bool dirty; + struct { struct ws_content *v; size_t count; @@ -225,6 +227,7 @@ handle_get_workspaces_reply(int type, const struct json_object *json, void *_mod mtx_lock(&mod->lock); workspaces_free(m); + m->dirty = true; size_t count = json_object_array_length(json); m->workspaces.count = count; @@ -242,7 +245,6 @@ handle_get_workspaces_reply(int type, const struct json_object *json, void *_mod } mtx_unlock(&mod->lock); - mod->bar->refresh(mod->bar); return true; } @@ -362,8 +364,8 @@ handle_workspace_event(int type, const struct json_object *json, void *_mod) else if (strcmp(change_str, "reload") == 0) LOG_WARN("unimplemented: 'reload' event"); + m->dirty = true; mtx_unlock(&mod->lock); - mod->bar->refresh(mod->bar); return true; err: @@ -402,6 +404,7 @@ handle_window_event(int type, const struct json_object *json, void *_mod) goto err; } + m->dirty = true; const char *change_str = json_object_get_string(change); if (strcmp(change_str, "close") == 0 || strcmp(change_str, "new") == 0) { @@ -412,7 +415,6 @@ handle_window_event(int type, const struct json_object *json, void *_mod) ws->window.pid = -1; mtx_unlock(&mod->lock); - mod->bar->refresh(mod->bar); return true; } @@ -456,7 +458,6 @@ handle_window_event(int type, const struct json_object *json, void *_mod) ws->window.pid = -1; mtx_unlock(&mod->lock); - mod->bar->refresh(mod->bar); return true; } @@ -467,8 +468,6 @@ handle_window_event(int type, const struct json_object *json, void *_mod) application[bytes - 1] = '\0'; ws->window.application = strdup(application); close(fd); - - mod->bar->refresh(mod->bar); } mtx_unlock(&mod->lock); @@ -479,6 +478,17 @@ err: return false; } +static void +burst_done(void *_mod) +{ + struct module *mod = _mod; + struct private *m = mod->private; + + if (m->dirty) { + m->dirty = false; + mod->bar->refresh(mod->bar); + } +} static int run(struct module *mod) @@ -505,6 +515,7 @@ run(struct module *mod) i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); static const struct i3_ipc_callbacks callbacks = { + .burst_done = &burst_done, .reply_version = &handle_get_version_reply, .reply_subscribe = &handle_subscribe_reply, .reply_workspaces = &handle_get_workspaces_reply, From e40b50bf5e8da5d4d07a8cc4f1f8b09e1c5aa6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 15 Feb 2019 22:06:03 +0100 Subject: [PATCH 10/11] module/i3-common: add missing include Needed for e.g strncpy() and strcmp() --- modules/i3-common.c | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/i3-common.c b/modules/i3-common.c index 0da04a5..225678b 100644 --- a/modules/i3-common.c +++ b/modules/i3-common.c @@ -1,6 +1,7 @@ #include "i3-common.h" #include +#include #include #include From 06ae704ded346cca4b6eab6c95ca37f3955c3f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 15 Feb 2019 22:06:24 +0100 Subject: [PATCH 11/11] module/i3-commonh: I3_IPC_REPLY_TYPE_SYNC is relatively new And is for example not present in our CI. --- modules/i3-common.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/i3-common.c b/modules/i3-common.c index 225678b..31546b5 100644 --- a/modules/i3-common.c +++ b/modules/i3-common.c @@ -253,9 +253,11 @@ i3_receive_loop(int abort_fd, int sock, case I3_IPC_REPLY_TYPE_TICK: pkt_handler = cbs->reply_tick; break; +#if defined(I3_IPC_REPLY_TYPE_SYNC) case I3_IPC_REPLY_TYPE_SYNC: pkt_handler = cbs->reply_sync; break; +#endif case I3_IPC_EVENT_WORKSPACE: pkt_handler = cbs->event_workspace;