*
* i3 - an improved dynamic tiling window manager
*
- * © 2009 Michael Stapelberg and contributors
+ * © 2009-2010 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* ipc.c: Everything about the UNIX domain sockets for IPC
*
*/
-#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#include <err.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdio.h>
#include <ev.h>
+#include <yajl/yajl_gen.h>
+#include <yajl/yajl_parse.h>
-#include "queue.h"
-#include "i3/ipc.h"
-#include "i3.h"
-#include "util.h"
-#include "commands.h"
+#include "all.h"
-typedef struct ipc_client {
- int fd;
-
- TAILQ_ENTRY(ipc_client) clients;
-} ipc_client;
+/* Shorter names for all those yajl_gen_* functions */
+#define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__)
+#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str))
TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
err(-1, "Could not set O_NONBLOCK");
}
-#if 0
-void broadcast(EV_P_ struct ev_timer *t, int revents) {
+static void ipc_send_message(int fd, const unsigned char *payload,
+ int message_type, int message_size) {
+ int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) +
+ sizeof(uint32_t) + message_size;
+ char msg[buffer_size];
+ char *walk = msg;
+
+ strcpy(walk, "i3-ipc");
+ walk += strlen("i3-ipc");
+ memcpy(walk, &message_size, sizeof(uint32_t));
+ walk += sizeof(uint32_t);
+ memcpy(walk, &message_type, sizeof(uint32_t));
+ walk += sizeof(uint32_t);
+ memcpy(walk, payload, message_size);
+
+ int sent_bytes = 0;
+ int bytes_to_go = buffer_size;
+ while (sent_bytes < bytes_to_go) {
+ int n = write(fd, msg + sent_bytes, bytes_to_go);
+ if (n == -1) {
+ DLOG("write() failed: %s\n", strerror(errno));
+ return;
+ }
+
+ sent_bytes += n;
+ bytes_to_go -= n;
+ }
+}
+
+/*
+ * 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) {
- write(current->fd, "hi there!\n", strlen("hi there!\n"));
+ /* 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 (!interested)
+ continue;
+
+ ipc_send_message(current->fd, (const unsigned char*)payload,
+ message_type, strlen(payload));
}
}
-#endif
/*
- * Decides what to do with the received message.
- *
- * message is the raw packet, as received from the UNIX domain socket. size
- * is the remaining size of bytes for this packet.
+ * Calls shutdown() on each socket and closes it. This function to be called
+ * when exiting or restarting only!
*
- * message_size is the size of the message as the sender specified it.
- * message_type is the type of the message as the sender specified it.
+ */
+void ipc_shutdown() {
+ ipc_client *current;
+ TAILQ_FOREACH(current, &all_clients, clients) {
+ shutdown(current->fd, SHUT_RDWR);
+ close(current->fd);
+ }
+}
+
+/*
+ * Executes the command and returns whether it could be successfully parsed
+ * or not (at the moment, always returns true).
*
*/
-static void ipc_handle_message(uint8_t *message, int size,
- uint32_t message_size, uint32_t message_type) {
- LOG("handling message of size %d\n", size);
- LOG("sender specified size %d\n", message_size);
- LOG("sender specified type %d\n", message_type);
- LOG("payload as a string = %s\n", message);
-
- switch (message_type) {
- case I3_IPC_MESSAGE_TYPE_COMMAND: {
- /* To get a properly terminated buffer, we copy
- * message_size bytes out of the buffer */
- char *command = scalloc(message_size);
- strncpy(command, (const char*)message, message_size);
- parse_command(global_conn, (const char*)command);
- free(command);
+IPC_HANDLER(command) {
+ /* To get a properly terminated buffer, we copy
+ * message_size bytes out of the buffer */
+ char *command = scalloc(message_size);
+ strncpy(command, (const char*)message, message_size);
+ parse_command((const char*)command);
+ free(command);
+
+ /* For now, every command gets a positive acknowledge
+ * (will change with the new command parser) */
+ const char *reply = "{\"success\":true}";
+ ipc_send_message(fd, (const unsigned char*)reply,
+ I3_IPC_REPLY_TYPE_COMMAND, strlen(reply));
+}
- break;
+void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
+ y(map_open);
+ ystr("id");
+ y(integer, (long int)con);
+
+ ystr("type");
+ y(integer, con->type);
+
+ ystr("orientation");
+ y(integer, con->orientation);
+
+ ystr("layout");
+ y(integer, con->layout);
+
+ ystr("rect");
+ y(map_open);
+ ystr("x");
+ y(integer, con->rect.x);
+ ystr("y");
+ y(integer, con->rect.y);
+ ystr("width");
+ y(integer, con->rect.width);
+ ystr("height");
+ y(integer, con->rect.height);
+ y(map_close);
+
+ ystr("name");
+ ystr(con->name);
+
+ ystr("window");
+ if (con->window)
+ y(integer, con->window->id);
+ else y(null);
+
+ ystr("nodes");
+ y(array_open);
+ Con *leaf;
+ TAILQ_FOREACH(leaf, &(con->nodes_head), nodes) {
+ dump_node(gen, leaf, inplace_restart);
+ }
+ y(array_close);
+
+ ystr("focus");
+ y(array_open);
+ TAILQ_FOREACH(leaf, &(con->nodes_head), nodes) {
+ y(integer, (long int)leaf);
+ }
+ y(array_close);
+
+ ystr("fullscreen_mode");
+ y(integer, con->fullscreen_mode);
+
+ if (inplace_restart) {
+ if (con->window != NULL) {
+ ystr("swallows");
+ y(array_open);
+ y(map_open);
+ ystr("id");
+ y(integer, con->window->id);
+ y(map_close);
+ y(array_close);
}
- default:
- LOG("unhandled ipc message\n");
- break;
}
+
+ y(map_close);
+}
+
+IPC_HANDLER(tree) {
+ printf("tree\n");
+ yajl_gen gen = yajl_gen_alloc(NULL, NULL);
+ dump_node(gen, croot, false);
+
+ const unsigned char *payload;
+ unsigned int length;
+ y(get_buf, &payload, &length);
+
+ ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_TREE, length);
+ y(free);
+
+}
+
+#if 0
+/*
+ * Formats the reply message for a GET_WORKSPACES request and sends it to the
+ * client
+ *
+ */
+IPC_HANDLER(get_workspaces) {
+ Workspace *ws;
+
+ Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
+ if (last_focused == SLIST_END(&(c_ws->focus_stack)))
+ last_focused = NULL;
+
+ yajl_gen gen = yajl_gen_alloc(NULL, NULL);
+ y(array_open);
+
+ TAILQ_FOREACH(ws, workspaces, workspaces) {
+ if (ws->output == NULL)
+ continue;
+
+ y(map_open);
+ ystr("num");
+ y(integer, ws->num + 1);
+
+ ystr("name");
+ ystr(ws->utf8_name);
+
+ ystr("visible");
+ y(bool, ws->output->current_workspace == ws);
+
+ ystr("focused");
+ y(bool, c_ws == ws);
+
+ ystr("rect");
+ y(map_open);
+ ystr("x");
+ y(integer, ws->rect.x);
+ ystr("y");
+ y(integer, ws->rect.y);
+ ystr("width");
+ y(integer, ws->rect.width);
+ ystr("height");
+ y(integer, ws->rect.height);
+ y(map_close);
+
+ ystr("output");
+ ystr(ws->output->name);
+
+ ystr("urgent");
+ y(bool, ws->urgent);
+
+ y(map_close);
+ }
+
+ y(array_close);
+
+ const unsigned char *payload;
+ unsigned int length;
+ y(get_buf, &payload, &length);
+
+ ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length);
+ y(free);
}
+/*
+ * Formats the reply message for a GET_OUTPUTS request and sends it to the
+ * client
+ *
+ */
+IPC_HANDLER(get_outputs) {
+ Output *output;
+
+ yajl_gen gen = yajl_gen_alloc(NULL, NULL);
+ y(array_open);
+
+ TAILQ_FOREACH(output, &outputs, outputs) {
+ y(map_open);
+
+ ystr("name");
+ ystr(output->name);
+
+ ystr("active");
+ y(bool, output->active);
+
+ ystr("rect");
+ y(map_open);
+ ystr("x");
+ y(integer, output->rect.x);
+ ystr("y");
+ y(integer, output->rect.y);
+ ystr("width");
+ y(integer, output->rect.width);
+ ystr("height");
+ y(integer, output->rect.height);
+ y(map_close);
+
+ ystr("current_workspace");
+ if (output->current_workspace == NULL)
+ y(null);
+ else y(integer, output->current_workspace->num + 1);
+
+ y(map_close);
+ }
+
+ y(array_close);
+
+ const unsigned char *payload;
+ unsigned int length;
+ y(get_buf, &payload, &length);
+
+ ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_OUTPUTS, length);
+ y(free);
+}
+
+/*
+ * Callback for the YAJL parser (will be called when a string is parsed).
+ *
+ */
+static int add_subscription(void *extra, const unsigned char *s,
+ unsigned int len) {
+ ipc_client *client = extra;
+
+ DLOG("should add subscription to extra %p, sub %.*s\n", client, len, s);
+ int event = client->num_events;
+
+ client->num_events++;
+ client->events = realloc(client->events, client->num_events * sizeof(char*));
+ /* We copy the string because it is not null-terminated and strndup()
+ * is missing on some BSD systems */
+ client->events[event] = scalloc(len+1);
+ memcpy(client->events[event], s, len);
+
+ DLOG("client is now subscribed to:\n");
+ for (int i = 0; i < client->num_events; i++)
+ DLOG("event %s\n", client->events[i]);
+ DLOG("(done)\n");
+
+ return 1;
+}
+
+/*
+ * Subscribes this connection to the event types which were given as a JSON
+ * serialized array in the payload field of the message.
+ *
+ */
+IPC_HANDLER(subscribe) {
+ yajl_handle p;
+ yajl_callbacks callbacks;
+ 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;
+
+ client = current;
+ break;
+ }
+
+ if (client == NULL) {
+ ELOG("Could not find ipc_client data structure for fd %d\n", fd);
+ return;
+ }
+
+ /* Setup the JSON parser */
+ memset(&callbacks, 0, sizeof(yajl_callbacks));
+ callbacks.yajl_string = add_subscription;
+
+ p = yajl_alloc(&callbacks, NULL, NULL, (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_message(fd, (const unsigned char*)reply,
+ I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply));
+ yajl_free(p);
+ return;
+ }
+ yajl_free(p);
+ const char *reply = "{\"success\":true}";
+ ipc_send_message(fd, (const unsigned char*)reply,
+ I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply));
+}
+#endif
+
+/* The index of each callback function corresponds to the numeric
+ * value of the message type (see include/i3/ipc.h) */
+handler_t handlers[2] = {
+ handle_command,
+ handle_tree
+};
+
/*
* Handler for activity on a client connection, receives a message from a
* client.
close(w->fd);
/* Delete the client from the list of clients */
- struct ipc_client *current;
+ 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);
ev_io_stop(EV_A_ w);
- LOG("IPC: client disconnected\n");
+ DLOG("IPC: client disconnected\n");
return;
}
/* Check if the message starts with the i3 IPC magic code */
if (n < strlen(I3_IPC_MAGIC)) {
- LOG("IPC: message too short, ignoring\n");
+ DLOG("IPC: message too short, ignoring\n");
return;
}
if (strncmp(buf, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
- LOG("IPC: message does not start with the IPC magic\n");
+ DLOG("IPC: message does not start with the IPC magic\n");
return;
}
uint8_t *message = (uint8_t*)buf;
while (n > 0) {
- LOG("IPC: n = %d\n", n);
+ DLOG("IPC: n = %d\n", n);
message += strlen(I3_IPC_MAGIC);
n -= strlen(I3_IPC_MAGIC);
n -= sizeof(uint32_t);
if (message_size > n) {
- LOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n");
+ DLOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n");
return;
}
message += sizeof(uint32_t);
n -= sizeof(uint32_t);
- ipc_handle_message(message, n, message_size, message_type);
+ if (message_type >= (sizeof(handlers) / sizeof(handler_t)))
+ DLOG("Unhandled message type: %d\n", message_type);
+ else {
+ handler_t h = handlers[message_type];
+ h(w->fd, message, n, message_size, message_type);
+ }
n -= message_size;
message += message_size;
}
set_nonblock(client);
- struct ev_io *package = calloc(sizeof(struct ev_io), 1);
+ struct ev_io *package = scalloc(sizeof(struct ev_io));
ev_io_init(package, ipc_receive_message, client, EV_READ);
ev_io_start(EV_A_ package);
- LOG("IPC: new client connected\n");
+ DLOG("IPC: new client connected\n");
- struct ipc_client *new = calloc(sizeof(struct ipc_client), 1);
+ ipc_client *new = scalloc(sizeof(ipc_client));
new->fd = client;
TAILQ_INSERT_TAIL(&all_clients, new, clients);
return -1;
}
+ (void)fcntl(sockfd, F_SETFD, FD_CLOEXEC);
+
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;