]> git.sur5r.net Git - i3/i3/blobdiff - src/ipc.c
Merge branch 'release-4.16.1'
[i3/i3] / src / ipc.c
index c884cc8e8819ec9d4872ea051aa03c71b4bed290..d18675f781b9913e151ba75eac56119277f59a79 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -1,5 +1,3 @@
-#undef I3__FILE__
-#define I3__FILE__ "ipc.c"
 /*
  * vim:ts=4:sw=4:expandtab
  *
@@ -10,8 +8,10 @@
  *
  */
 #include "all.h"
+
 #include "yajl_utils.h"
 
+#include <stdint.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <fcntl.h>
@@ -22,7 +22,8 @@
 
 char *current_socketpath = NULL;
 
-TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
+TAILQ_HEAD(ipc_client_head, ipc_client)
+all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
 
 /*
  * Puts the given socket file descriptor into non-blocking mode or dies if
@@ -37,6 +38,116 @@ static void set_nonblock(int sockfd) {
         err(-1, "Could not set O_NONBLOCK");
 }
 
+static void ipc_client_timeout(EV_P_ ev_timer *w, int revents);
+static void ipc_socket_writeable_cb(EV_P_ struct ev_io *w, int revents);
+
+static ev_tstamp kill_timeout = 10.0;
+
+void ipc_set_kill_timeout(ev_tstamp new) {
+    kill_timeout = new;
+}
+
+/*
+ * Try to write the contents of the pending buffer to the client's subscription
+ * socket. Will set, reset or clear the timeout and io write callbacks depending
+ * on the result of the write operation.
+ *
+ */
+static void ipc_push_pending(ipc_client *client) {
+    const ssize_t result = writeall_nonblock(client->fd, client->buffer, client->buffer_size);
+    if (result < 0) {
+        return;
+    }
+
+    if ((size_t)result == client->buffer_size) {
+        /* Everything was written successfully: clear the timer and stop the io
+         * callback. */
+        FREE(client->buffer);
+        client->buffer_size = 0;
+        if (client->timeout) {
+            ev_timer_stop(main_loop, client->timeout);
+            FREE(client->timeout);
+        }
+        ev_io_stop(main_loop, client->write_callback);
+        return;
+    }
+
+    /* Otherwise, make sure that the io callback is enabled and create a new
+     * timer if needed. */
+    ev_io_start(main_loop, client->write_callback);
+
+    if (!client->timeout) {
+        struct ev_timer *timeout = scalloc(1, sizeof(struct ev_timer));
+        ev_timer_init(timeout, ipc_client_timeout, kill_timeout, 0.);
+        timeout->data = client;
+        client->timeout = timeout;
+        ev_set_priority(timeout, EV_MINPRI);
+        ev_timer_start(main_loop, client->timeout);
+    } else if (result > 0) {
+        /* Keep the old timeout when nothing is written. Otherwise, we would
+         * keep a dead connection by continuously renewing its timeouts. */
+        ev_timer_stop(main_loop, client->timeout);
+        ev_timer_set(client->timeout, kill_timeout, 0.0);
+        ev_timer_start(main_loop, client->timeout);
+    }
+    if (result == 0) {
+        return;
+    }
+
+    /* Shift the buffer to the left and reduce the allocated space. */
+    client->buffer_size -= (size_t)result;
+    memmove(client->buffer, client->buffer + result, client->buffer_size);
+    client->buffer = srealloc(client->buffer, client->buffer_size);
+}
+
+/*
+ * Given a message and a message type, create the corresponding header, merge it
+ * with the message and append it to the given client's output buffer. Also,
+ * send the message if the client's buffer was empty.
+ *
+ */
+static void ipc_send_client_message(ipc_client *client, size_t size, const uint32_t message_type, const uint8_t *payload) {
+    const i3_ipc_header_t header = {
+        .magic = {'i', '3', '-', 'i', 'p', 'c'},
+        .size = size,
+        .type = message_type};
+    const size_t header_size = sizeof(i3_ipc_header_t);
+    const size_t message_size = header_size + size;
+
+    const bool push_now = (client->buffer_size == 0);
+    client->buffer = srealloc(client->buffer, client->buffer_size + message_size);
+    memcpy(client->buffer + client->buffer_size, ((void *)&header), header_size);
+    memcpy(client->buffer + client->buffer_size + header_size, payload, size);
+    client->buffer_size += message_size;
+
+    if (push_now) {
+        ipc_push_pending(client);
+    }
+}
+
+static void free_ipc_client(ipc_client *client) {
+    DLOG("Disconnecting client on fd %d\n", client->fd);
+    close(client->fd);
+
+    ev_io_stop(main_loop, client->read_callback);
+    FREE(client->read_callback);
+    ev_io_stop(main_loop, client->write_callback);
+    FREE(client->write_callback);
+    if (client->timeout) {
+        ev_timer_stop(main_loop, client->timeout);
+        FREE(client->timeout);
+    }
+
+    free(client->buffer);
+
+    for (int i = 0; i < client->num_events; i++) {
+        free(client->events[i]);
+    }
+    free(client->events);
+    TAILQ_REMOVE(&all_clients, client, clients);
+    free(client);
+}
+
 /*
  * Sends the specified event to all IPC clients which are currently connected
  * and subscribed to this kind of event.
@@ -45,34 +156,54 @@ static void set_nonblock(int sockfd) {
 void ipc_send_event(const char *event, uint32_t message_type, const char *payload) {
     ipc_client *current;
     TAILQ_FOREACH(current, &all_clients, clients) {
-        /* see if this client is interested in this event */
-        bool interested = false;
         for (int i = 0; i < current->num_events; i++) {
-            if (strcasecmp(current->events[i], event) != 0)
-                continue;
-            interested = true;
-            break;
+            if (strcasecmp(current->events[i], event) == 0) {
+                ipc_send_client_message(current, strlen(payload), message_type, (uint8_t *)payload);
+                break;
+            }
         }
-        if (!interested)
-            continue;
+    }
+}
 
-        ipc_send_message(current->fd, strlen(payload), message_type, (const uint8_t *)payload);
+/*
+ * For shutdown events, we send the reason for the shutdown.
+ */
+static void ipc_send_shutdown_event(shutdown_reason_t reason) {
+    yajl_gen gen = ygenalloc();
+    y(map_open);
+
+    ystr("change");
+
+    if (reason == SHUTDOWN_REASON_RESTART) {
+        ystr("restart");
+    } else if (reason == SHUTDOWN_REASON_EXIT) {
+        ystr("exit");
     }
+
+    y(map_close);
+
+    const unsigned char *payload;
+    ylength length;
+
+    y(get_buf, &payload, &length);
+    ipc_send_event("shutdown", I3_IPC_EVENT_SHUTDOWN, (const char *)payload);
+
+    y(free);
 }
 
 /*
- * Calls shutdown() on each socket and closes it. This function to be called
+ * Calls shutdown() on each socket and closes it. This function is to be called
  * when exiting or restarting only!
  *
  */
-void ipc_shutdown(void) {
+void ipc_shutdown(shutdown_reason_t reason) {
+    ipc_send_shutdown_event(reason);
+
     ipc_client *current;
     while (!TAILQ_EMPTY(&all_clients)) {
         current = TAILQ_FIRST(&all_clients);
         shutdown(current->fd, SHUT_RDWR);
-        close(current->fd);
-        TAILQ_REMOVE(&all_clients, current, clients);
-        free(current);
+        free_ipc_client(current);
     }
 }
 
@@ -81,7 +212,7 @@ void ipc_shutdown(void) {
  * or not (at the moment, always returns true).
  *
  */
-IPC_HANDLER(command) {
+IPC_HANDLER(run_command) {
     /* To get a properly terminated buffer, we copy
      * message_size bytes out of the buffer */
     char *command = scalloc(message_size + 1, 1);
@@ -101,8 +232,8 @@ IPC_HANDLER(command) {
     ylength length;
     yajl_gen_get_buf(gen, &reply, &length);
 
-    ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_COMMAND,
-                     (const uint8_t *)reply);
+    ipc_send_client_message(client, length, I3_IPC_REPLY_TYPE_COMMAND,
+                            (const uint8_t *)reply);
 
     yajl_gen_free(gen);
 }
@@ -214,7 +345,7 @@ static void dump_binding(yajl_gen gen, Binding *bind) {
 void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
     y(map_open);
     ystr("id");
-    y(integer, (long int)con);
+    y(integer, (uintptr_t)con);
 
     ystr("type");
     switch (con->type) {
@@ -236,10 +367,6 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
         case CT_DOCKAREA:
             ystr("dockarea");
             break;
-        default:
-            DLOG("About to dump unknown container type=%d. This is a bug.\n", con->type);
-            assert(false);
-            break;
     }
 
     /* provided for backwards compatibility only. */
@@ -275,14 +402,26 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
     ystr("urgent");
     y(bool, con->urgent);
 
-    if (con->mark != NULL) {
-        ystr("mark");
-        ystr(con->mark);
+    if (!TAILQ_EMPTY(&(con->marks_head))) {
+        ystr("marks");
+        y(array_open);
+
+        mark_t *mark;
+        TAILQ_FOREACH(mark, &(con->marks_head), marks) {
+            ystr(mark->name);
+        }
+
+        y(array_close);
     }
 
     ystr("focused");
     y(bool, (con == focused));
 
+    if (con->type != CT_ROOT && con->type != CT_OUTPUT) {
+        ystr("output");
+        ystr(con_get_output(con)->name);
+    }
+
     ystr("layout");
     switch (con->layout) {
         case L_DEFAULT:
@@ -365,6 +504,11 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
     else
         y(null);
 
+    if (con->title_format != NULL) {
+        ystr("title_format");
+        ystr(con->title_format);
+    }
+
     if (con->type == CT_WORKSPACE) {
         ystr("num");
         y(integer, con->num);
@@ -429,7 +573,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
     ystr("focus");
     y(array_open);
     TAILQ_FOREACH(node, &(con->focus_head), focused) {
-        y(integer, (long int)node);
+        y(integer, (uintptr_t)node);
     }
     y(array_close);
 
@@ -464,7 +608,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
         if (match->restart_mode)
             continue;
         y(map_open);
-        if (match->dock != -1) {
+        if (match->dock != M_DONTCHECK) {
             ystr("dock");
             y(integer, match->dock);
             ystr("insert_where");
@@ -523,6 +667,8 @@ static void dump_bar_bindings(yajl_gen gen, Barconfig *config) {
         y(integer, current->input_code);
         ystr("command");
         ystr(current->command);
+        ystr("release");
+        y(bool, current->release == B_UPON_KEYRELEASE);
 
         y(map_close);
     }
@@ -530,6 +676,15 @@ static void dump_bar_bindings(yajl_gen gen, Barconfig *config) {
     y(array_close);
 }
 
+static char *canonicalize_output_name(char *name) {
+    /* Do not canonicalize special output names. */
+    if (strcasecmp(name, "primary") == 0) {
+        return name;
+    }
+    Output *output = get_output_by_name(name, false);
+    return output ? output_primary_name(output) : name;
+}
+
 static void dump_bar_config(yajl_gen gen, Barconfig *config) {
     y(map_open);
 
@@ -539,8 +694,25 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
     if (config->num_outputs > 0) {
         ystr("outputs");
         y(array_open);
-        for (int c = 0; c < config->num_outputs; c++)
-            ystr(config->outputs[c]);
+        for (int c = 0; c < config->num_outputs; c++) {
+            /* Convert monitor names (RandR ≥ 1.5) or output names
+             * (RandR < 1.5) into monitor names. This way, existing
+             * configs which use output names transparently keep
+             * working. */
+            ystr(canonicalize_output_name(config->outputs[c]));
+        }
+        y(array_close);
+    }
+
+    if (!TAILQ_EMPTY(&(config->tray_outputs))) {
+        ystr("tray_outputs");
+        y(array_open);
+
+        struct tray_output_t *tray_output;
+        TAILQ_FOREACH(tray_output, &(config->tray_outputs), tray_outputs) {
+            ystr(canonicalize_output_name(tray_output->output));
+        }
+
         y(array_close);
     }
 
@@ -552,8 +724,6 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
         }                       \
     } while (0)
 
-    YSTR_IF_SET(tray_output);
-
     ystr("tray_padding");
     y(integer, config->tray_padding);
 
@@ -585,34 +755,7 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
     }
 
     ystr("modifier");
-    switch (config->modifier) {
-        case M_CONTROL:
-            ystr("ctrl");
-            break;
-        case M_SHIFT:
-            ystr("shift");
-            break;
-        case M_MOD1:
-            ystr("Mod1");
-            break;
-        case M_MOD2:
-            ystr("Mod2");
-            break;
-        case M_MOD3:
-            ystr("Mod3");
-            break;
-        /*
-               case M_MOD4:
-               ystr("Mod4");
-               break;
-               */
-        case M_MOD5:
-            ystr("Mod5");
-            break;
-        default:
-            ystr("Mod4");
-            break;
-    }
+    y(integer, config->modifier);
 
     dump_bar_bindings(gen, config);
 
@@ -636,6 +779,9 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
     ystr("strip_workspace_numbers");
     y(bool, config->strip_workspace_numbers);
 
+    ystr("strip_workspace_name");
+    y(bool, config->strip_workspace_name);
+
     ystr("binding_mode_indicator");
     y(bool, !config->hide_binding_mode_indicator);
 
@@ -656,6 +802,9 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
     YSTR_IF_SET(background);
     YSTR_IF_SET(statusline);
     YSTR_IF_SET(separator);
+    YSTR_IF_SET(focused_background);
+    YSTR_IF_SET(focused_statusline);
+    YSTR_IF_SET(focused_separator);
     YSTR_IF_SET(focused_workspace_border);
     YSTR_IF_SET(focused_workspace_bg);
     YSTR_IF_SET(focused_workspace_text);
@@ -687,7 +836,7 @@ IPC_HANDLER(tree) {
     ylength length;
     y(get_buf, &payload, &length);
 
-    ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_TREE, payload);
+    ipc_send_client_message(client, length, I3_IPC_REPLY_TYPE_TREE, payload);
     y(free);
 }
 
@@ -751,7 +900,7 @@ IPC_HANDLER(get_workspaces) {
     ylength length;
     y(get_buf, &payload, &length);
 
-    ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_WORKSPACES, payload);
+    ipc_send_client_message(client, length, I3_IPC_REPLY_TYPE_WORKSPACES, payload);
     y(free);
 }
 
@@ -769,7 +918,7 @@ IPC_HANDLER(get_outputs) {
         y(map_open);
 
         ystr("name");
-        ystr(output->name);
+        ystr(output_primary_name(output));
 
         ystr("active");
         y(bool, output->active);
@@ -805,7 +954,7 @@ IPC_HANDLER(get_outputs) {
     ylength length;
     y(get_buf, &payload, &length);
 
-    ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_OUTPUTS, payload);
+    ipc_send_client_message(client, length, I3_IPC_REPLY_TYPE_OUTPUTS, payload);
     y(free);
 }
 
@@ -819,9 +968,12 @@ IPC_HANDLER(get_marks) {
     y(array_open);
 
     Con *con;
-    TAILQ_FOREACH(con, &all_cons, all_cons)
-    if (con->mark != NULL)
-        ystr(con->mark);
+    TAILQ_FOREACH(con, &all_cons, all_cons) {
+        mark_t *mark;
+        TAILQ_FOREACH(mark, &(con->marks_head), marks) {
+            ystr(mark->name);
+        }
+    }
 
     y(array_close);
 
@@ -829,7 +981,7 @@ IPC_HANDLER(get_marks) {
     ylength length;
     y(get_buf, &payload, &length);
 
-    ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_MARKS, payload);
+    ipc_send_client_message(client, length, I3_IPC_REPLY_TYPE_MARKS, payload);
     y(free);
 }
 
@@ -862,7 +1014,7 @@ IPC_HANDLER(get_version) {
     ylength length;
     y(get_buf, &payload, &length);
 
-    ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_VERSION, payload);
+    ipc_send_client_message(client, length, I3_IPC_REPLY_TYPE_VERSION, payload);
     y(free);
 }
 
@@ -887,7 +1039,7 @@ IPC_HANDLER(get_bar_config) {
         ylength length;
         y(get_buf, &payload, &length);
 
-        ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_BAR_CONFIG, payload);
+        ipc_send_client_message(client, length, I3_IPC_REPLY_TYPE_BAR_CONFIG, payload);
         y(free);
         return;
     }
@@ -924,7 +1076,29 @@ IPC_HANDLER(get_bar_config) {
     ylength length;
     y(get_buf, &payload, &length);
 
-    ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_BAR_CONFIG, payload);
+    ipc_send_client_message(client, length, I3_IPC_REPLY_TYPE_BAR_CONFIG, payload);
+    y(free);
+}
+
+/*
+ * Returns a list of configured binding modes
+ *
+ */
+IPC_HANDLER(get_binding_modes) {
+    yajl_gen gen = ygenalloc();
+
+    y(array_open);
+    struct Mode *mode;
+    SLIST_FOREACH(mode, &modes, modes) {
+        ystr(mode->name);
+    }
+    y(array_close);
+
+    const unsigned char *payload;
+    ylength length;
+    y(get_buf, &payload, &length);
+
+    ipc_send_client_message(client, length, I3_IPC_REPLY_TYPE_BINDING_MODES, payload);
     y(free);
 }
 
@@ -947,8 +1121,9 @@ static int add_subscription(void *extra, const unsigned char *s,
     memcpy(client->events[event], s, len);
 
     DLOG("client is now subscribed to:\n");
-    for (int i = 0; i < client->num_events; i++)
+    for (int i = 0; i < client->num_events; i++) {
         DLOG("event %s\n", client->events[i]);
+    }
     DLOG("(done)\n");
 
     return 1;
@@ -962,29 +1137,139 @@ static int add_subscription(void *extra, const unsigned char *s,
 IPC_HANDLER(subscribe) {
     yajl_handle p;
     yajl_status stat;
-    ipc_client *current, *client = NULL;
 
-    /* Search the ipc_client structure for this connection */
-    TAILQ_FOREACH(current, &all_clients, clients) {
-        if (current->fd != fd)
-            continue;
+    /* Setup the JSON parser */
+    static yajl_callbacks callbacks = {
+        .yajl_string = add_subscription,
+    };
 
-        client = current;
-        break;
+    p = yalloc(&callbacks, (void *)client);
+    stat = yajl_parse(p, (const unsigned char *)message, message_size);
+    if (stat != yajl_status_ok) {
+        unsigned char *err;
+        err = yajl_get_error(p, true, (const unsigned char *)message,
+                             message_size);
+        ELOG("YAJL parse error: %s\n", err);
+        yajl_free_error(p, err);
+
+        const char *reply = "{\"success\":false}";
+        ipc_send_client_message(client, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply);
+        yajl_free(p);
+        return;
+    }
+    yajl_free(p);
+    const char *reply = "{\"success\":true}";
+    ipc_send_client_message(client, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply);
+
+    if (client->first_tick_sent) {
+        return;
     }
 
-    if (client == NULL) {
-        ELOG("Could not find ipc_client data structure for fd %d\n", fd);
+    bool is_tick = false;
+    for (int i = 0; i < client->num_events; i++) {
+        if (strcmp(client->events[i], "tick") == 0) {
+            is_tick = true;
+            break;
+        }
+    }
+    if (!is_tick) {
         return;
     }
 
+    client->first_tick_sent = true;
+    const char *payload = "{\"first\":true,\"payload\":\"\"}";
+    ipc_send_client_message(client, strlen(payload), I3_IPC_EVENT_TICK, (const uint8_t *)payload);
+}
+
+/*
+ * Returns the raw last loaded i3 configuration file contents.
+ */
+IPC_HANDLER(get_config) {
+    yajl_gen gen = ygenalloc();
+
+    y(map_open);
+
+    ystr("config");
+    ystr(current_config);
+
+    y(map_close);
+
+    const unsigned char *payload;
+    ylength length;
+    y(get_buf, &payload, &length);
+
+    ipc_send_client_message(client, length, I3_IPC_REPLY_TYPE_CONFIG, payload);
+    y(free);
+}
+
+/*
+ * Sends the tick event from the message payload to subscribers. Establishes a
+ * synchronization point in event-related tests.
+ */
+IPC_HANDLER(send_tick) {
+    yajl_gen gen = ygenalloc();
+
+    y(map_open);
+
+    ystr("first");
+    y(bool, false);
+
+    ystr("payload");
+    yajl_gen_string(gen, (unsigned char *)message, message_size);
+
+    y(map_close);
+
+    const unsigned char *payload;
+    ylength length;
+    y(get_buf, &payload, &length);
+
+    ipc_send_event("tick", I3_IPC_EVENT_TICK, (const char *)payload);
+    y(free);
+
+    const char *reply = "{\"success\":true}";
+    ipc_send_client_message(client, strlen(reply), I3_IPC_REPLY_TYPE_TICK, (const uint8_t *)reply);
+    DLOG("Sent tick event\n");
+}
+
+struct sync_state {
+    char *last_key;
+    uint32_t rnd;
+    xcb_window_t window;
+};
+
+static int _sync_json_key(void *extra, const unsigned char *val, size_t len) {
+    struct sync_state *state = extra;
+    FREE(state->last_key);
+    state->last_key = scalloc(len + 1, 1);
+    memcpy(state->last_key, val, len);
+    return 1;
+}
+
+static int _sync_json_int(void *extra, long long val) {
+    struct sync_state *state = extra;
+    if (strcasecmp(state->last_key, "rnd") == 0) {
+        state->rnd = val;
+    } else if (strcasecmp(state->last_key, "window") == 0) {
+        state->window = (xcb_window_t)val;
+    }
+    return 1;
+}
+
+IPC_HANDLER(sync) {
+    yajl_handle p;
+    yajl_status stat;
+
     /* Setup the JSON parser */
     static yajl_callbacks callbacks = {
-        .yajl_string = add_subscription,
+        .yajl_map_key = _sync_json_key,
+        .yajl_integer = _sync_json_int,
     };
 
-    p = yalloc(&callbacks, (void *)client);
+    struct sync_state state;
+    memset(&state, '\0', sizeof(struct sync_state));
+    p = yalloc(&callbacks, (void *)&state);
     stat = yajl_parse(p, (const unsigned char *)message, message_size);
+    FREE(state.last_key);
     if (stat != yajl_status_ok) {
         unsigned char *err;
         err = yajl_get_error(p, true, (const unsigned char *)message,
@@ -993,19 +1278,22 @@ IPC_HANDLER(subscribe) {
         yajl_free_error(p, err);
 
         const char *reply = "{\"success\":false}";
-        ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply);
+        ipc_send_client_message(client, strlen(reply), I3_IPC_REPLY_TYPE_SYNC, (const uint8_t *)reply);
         yajl_free(p);
         return;
     }
     yajl_free(p);
+
+    DLOG("received IPC sync request (rnd = %d, window = 0x%08x)\n", state.rnd, state.window);
+    sync_respond(state.window, state.rnd);
     const char *reply = "{\"success\":true}";
-    ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply);
+    ipc_send_client_message(client, strlen(reply), I3_IPC_REPLY_TYPE_SYNC, (const uint8_t *)reply);
 }
 
 /* The index of each callback function corresponds to the numeric
  * value of the message type (see include/i3/ipc.h) */
-handler_t handlers[8] = {
-    handle_command,
+handler_t handlers[12] = {
+    handle_run_command,
     handle_get_workspaces,
     handle_subscribe,
     handle_get_outputs,
@@ -1013,6 +1301,10 @@ handler_t handlers[8] = {
     handle_get_marks,
     handle_get_bar_config,
     handle_get_version,
+    handle_get_binding_modes,
+    handle_get_config,
+    handle_send_tick,
+    handle_sync,
 };
 
 /*
@@ -1029,6 +1321,8 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
     uint32_t message_type;
     uint32_t message_length;
     uint8_t *message = NULL;
+    ipc_client *client = (ipc_client *)w->data;
+    assert(client->fd == w->fd);
 
     int ret = ipc_recv_message(w->fd, &message_type, &message_length, &message);
     /* EOF or other error */
@@ -1039,30 +1333,10 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
             return;
         }
 
-        /* If not, there was some kind of error. We don’t bother
-         * and close the connection */
-        close(w->fd);
-
-        /* Delete the client from the list of clients */
-        ipc_client *current;
-        TAILQ_FOREACH(current, &all_clients, clients) {
-            if (current->fd != w->fd)
-                continue;
-
-            for (int i = 0; i < current->num_events; i++)
-                free(current->events[i]);
-            /* We can call TAILQ_REMOVE because we break out of the
-             * TAILQ_FOREACH afterwards */
-            TAILQ_REMOVE(&all_clients, current, clients);
-            free(current);
-            break;
-        }
-
-        ev_io_stop(EV_A_ w);
-        free(w);
+        /* If not, there was some kind of error. We don’t bother and close the
+         * connection. Delete the client from the list of clients. */
+        free_ipc_client(client);
         FREE(message);
-
-        DLOG("IPC: client disconnected\n");
         return;
     }
 
@@ -1070,12 +1344,68 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
         DLOG("Unhandled message type: %d\n", message_type);
     else {
         handler_t h = handlers[message_type];
-        h(w->fd, message, 0, message_length, message_type);
+        h(client, message, 0, message_length, message_type);
     }
 
     FREE(message);
 }
 
+static void ipc_client_timeout(EV_P_ ev_timer *w, int revents) {
+    /* No need to be polite and check for writeability, the other callback would
+     * have been called by now. */
+    ipc_client *client = (ipc_client *)w->data;
+
+    char *cmdline = NULL;
+#if defined(__linux__) && defined(SO_PEERCRED)
+    struct ucred peercred;
+    socklen_t so_len = sizeof(peercred);
+    if (getsockopt(client->fd, SOL_SOCKET, SO_PEERCRED, &peercred, &so_len) != 0) {
+        goto end;
+    }
+    char *exepath;
+    sasprintf(&exepath, "/proc/%d/cmdline", peercred.pid);
+
+    int fd = open(exepath, O_RDONLY);
+    free(exepath);
+    if (fd == -1) {
+        goto end;
+    }
+    char buf[512] = {'\0'}; /* cut off cmdline for the error message. */
+    const ssize_t n = read(fd, buf, sizeof(buf));
+    close(fd);
+    if (n < 0) {
+        goto end;
+    }
+    for (char *walk = buf; walk < buf + n - 1; walk++) {
+        if (*walk == '\0') {
+            *walk = ' ';
+        }
+    }
+    cmdline = buf;
+
+    if (cmdline) {
+        ELOG("client %p with pid %d and cmdline '%s' on fd %d timed out, killing\n", client, peercred.pid, cmdline, client->fd);
+    }
+
+end:
+#endif
+    if (!cmdline) {
+        ELOG("client %p on fd %d timed out, killing\n", client, client->fd);
+    }
+
+    free_ipc_client(client);
+}
+
+static void ipc_socket_writeable_cb(EV_P_ ev_io *w, int revents) {
+    DLOG("fd %d writeable\n", w->fd);
+    ipc_client *client = (ipc_client *)w->data;
+
+    /* If this callback is called then there should be a corresponding active
+     * timer. */
+    assert(client->timeout != NULL);
+    ipc_push_pending(client);
+}
+
 /*
  * Handler for activity on the listening socket, meaning that a new client
  * has just connected and we should accept() him. Sets up the event handler
@@ -1086,30 +1416,33 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
 void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
     struct sockaddr_un peer;
     socklen_t len = sizeof(struct sockaddr_un);
-    int client;
-    if ((client = accept(w->fd, (struct sockaddr *)&peer, &len)) < 0) {
-        if (errno == EINTR)
-            return;
-        else
+    int fd;
+    if ((fd = accept(w->fd, (struct sockaddr *)&peer, &len)) < 0) {
+        if (errno != EINTR) {
             perror("accept()");
+        }
         return;
     }
 
     /* Close this file descriptor on exec() */
-    (void)fcntl(client, F_SETFD, FD_CLOEXEC);
+    (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
 
-    set_nonblock(client);
+    set_nonblock(fd);
 
-    struct ev_io *package = scalloc(1, sizeof(struct ev_io));
-    ev_io_init(package, ipc_receive_message, client, EV_READ);
-    ev_io_start(EV_A_ package);
+    ipc_client *client = scalloc(1, sizeof(ipc_client));
+    client->fd = fd;
 
-    DLOG("IPC: new client connected on fd %d\n", w->fd);
+    client->read_callback = scalloc(1, sizeof(struct ev_io));
+    client->read_callback->data = client;
+    ev_io_init(client->read_callback, ipc_receive_message, fd, EV_READ);
+    ev_io_start(EV_A_ client->read_callback);
 
-    ipc_client *new = scalloc(1, sizeof(ipc_client));
-    new->fd = client;
+    client->write_callback = scalloc(1, sizeof(struct ev_io));
+    client->write_callback->data = client;
+    ev_io_init(client->write_callback, ipc_socket_writeable_cb, fd, EV_WRITE);
 
-    TAILQ_INSERT_TAIL(&all_clients, new, clients);
+    DLOG("IPC: new client connected on fd %d\n", w->fd);
+    TAILQ_INSERT_TAIL(&all_clients, client, clients);
 }
 
 /*
@@ -1212,7 +1545,7 @@ void ipc_send_workspace_event(const char *change, Con *current, Con *old) {
     y(free);
 }
 
-/**
+/*
  * For the window events we send, along the usual "change" field,
  * also the window container, in "container".
  */
@@ -1242,7 +1575,7 @@ void ipc_send_window_event(const char *property, Con *con) {
     setlocale(LC_NUMERIC, "");
 }
 
-/**
+/*
  * For the barconfig update events, we send the serialized barconfig.
  */
 void ipc_send_barconfig_update_event(Barconfig *barconfig) {