#include "i3-common.h" #include #include #include #include #include #if defined(ENABLE_X11) #include #include #endif #include #define LOG_MODULE "i3:common" #define LOG_ENABLE_DBG 0 #include "../log.h" #if defined(ENABLE_X11) #include "../xcb.h" #endif #include "i3-ipc.h" #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 = 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)); goto err; } 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"); 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 ret; } #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; } bool i3_send_pkg(int sock, int cmd, char *data) { const size_t size = data != NULL ? strlen(data) : 0; const 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; } 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) { LOG_DBG("aborted"); break; } if (fds[1].revents & POLLHUP) { LOG_DBG("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; } LOG_DBG("header: type=%x", hdr->type); /* 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; } 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; #if defined(I3_IPC_REPLY_TYPE_SYNC) case I3_IPC_REPLY_TYPE_SYNC: pkt_handler = cbs->reply_sync; break; #endif /* Sway extensions */ case 100: /* IPC_GET_INPUTS */ pkt_handler = cbs->reply_inputs; break; /* * Events */ 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; /* Sway extensions */ #define SWAY_IPC_EVENT_INPUT ((1u << 31) | 21) case SWAY_IPC_EVENT_INPUT: pkt_handler = cbs->event_input; break; #undef SWAY_IPC_EVENT_INPUT default: LOG_ERR("unimplemented IPC reply type: %d", hdr->type); pkt_handler = NULL; break; } if (pkt_handler != NULL) err = !pkt_handler(sock, 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; } if (cbs->burst_done != NULL) cbs->burst_done(data); } free(buf); return !err; }