-#undef I3__FILE__
-#define I3__FILE__ "ipc.c"
/*
* vim:ts=4:sw=4:expandtab
*
*
*/
#include "all.h"
+
#include "yajl_utils.h"
+#include <stdint.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>
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
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.
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);
- for (int i = 0; i < current->num_events; i++)
- free(current->events[i]);
- free(current->events);
- TAILQ_REMOVE(&all_clients, current, clients);
- free(current);
+ free_ipc_client(current);
}
}
* 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);
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);
}
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) {
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. */
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:
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);
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");
y(integer, con->depth);
}
+ if (inplace_restart && con->type == CT_ROOT && previous_workspace_name) {
+ ystr("previous_workspace_name");
+ ystr(previous_workspace_name);
+ }
+
y(map_close);
}
y(integer, current->input_code);
ystr("command");
ystr(current->command);
+ ystr("release");
+ y(bool, current->release == B_UPON_KEYRELEASE);
y(map_close);
}
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);
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);
}
struct tray_output_t *tray_output;
TAILQ_FOREACH(tray_output, &(config->tray_outputs), tray_outputs) {
- ystr(tray_output->output);
+ ystr(canonicalize_output_name(tray_output->output));
}
y(array_close);
}
ystr("modifier");
- switch (config->modifier) {
- case M_NONE:
- ystr("none");
- break;
- 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_MOD5:
- ystr("Mod5");
- break;
- default:
- ystr("Mod4");
- break;
- }
+ y(integer, config->modifier);
dump_bar_bindings(gen, 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);
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);
}
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);
}
y(map_open);
ystr("name");
- ystr(output->name);
+ ystr(output_primary_name(output));
ystr("active");
y(bool, output->active);
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);
}
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);
}
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);
}
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;
}
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);
}
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;
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,
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,
handle_get_marks,
handle_get_bar_config,
handle_get_version,
+ handle_get_binding_modes,
+ handle_get_config,
+ handle_send_tick,
+ handle_sync,
};
/*
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 */
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]);
- free(current->events);
- /* 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;
}
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
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);
}
/*
y(free);
}
-/**
+/*
* For the window events we send, along the usual "change" field,
* also the window container, in "container".
*/
setlocale(LC_NUMERIC, "");
}
-/**
+/*
* For the barconfig update events, we send the serialized barconfig.
*/
void ipc_send_barconfig_update_event(Barconfig *barconfig) {