]> git.sur5r.net Git - i3/i3/blobdiff - src/ipc.c
parser: return a proper JSON reply on parse errors
[i3/i3] / src / ipc.c
index 629ec89d4624adfb3fc37afa4882e040f9e3438b..eed63bd4e0072820095561d5b044b1feb19b9f81 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
  * 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 "log.h"
-#include "table.h"
+#include "all.h"
 
 /* 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))
 
-typedef struct ipc_client {
-        int fd;
-
-        TAILQ_ENTRY(ipc_client) clients;
-} ipc_client;
-
 TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
 
 /*
@@ -57,15 +38,6 @@ static void set_nonblock(int sockfd) {
                 err(-1, "Could not set O_NONBLOCK");
 }
 
-#if 0
-void broadcast(EV_P_ struct ev_timer *t, int revents) {
-        ipc_client *current;
-        TAILQ_FOREACH(current, &all_clients, clients) {
-                write(current->fd, "hi there!\n", strlen("hi there!\n"));
-        }
-}
-#endif
-
 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) +
@@ -95,12 +67,169 @@ static void ipc_send_message(int fd, const unsigned char *payload,
         }
 }
 
+/*
+ * 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 (!interested)
+                        continue;
+
+                ipc_send_message(current->fd, (const unsigned char*)payload,
+                                 message_type, strlen(payload));
+        }
+}
+
+/*
+ * Calls shutdown() on each socket and closes it. This function to be called
+ * when exiting or restarting only!
+ *
+ */
+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).
+ *
+ */
+IPC_HANDLER(command) {
+        /* To get a properly terminated buffer, we copy
+         * message_size bytes out of the buffer */
+        char *command = scalloc(message_size + 1);
+        strncpy(command, (const char*)message, message_size);
+        LOG("IPC: received: *%s*\n", command);
+        const char *reply = parse_cmd((const char*)command);
+        tree_render();
+        free(command);
+
+        /* If no reply was provided, we just use the default success message */
+        if (reply == NULL)
+                reply = "{\"success\":true}";
+        ipc_send_message(fd, (const unsigned char*)reply,
+                         I3_IPC_REPLY_TYPE_COMMAND, strlen(reply));
+}
+
+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("urgent");
+        y(integer, con->urgent);
+
+        ystr("focused");
+        y(integer, (con == focused));
+
+        ystr("layout");
+        y(integer, con->layout);
+
+        ystr("border");
+        y(integer, con->border_style);
+
+        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 *node;
+        TAILQ_FOREACH(node, &(con->nodes_head), nodes) {
+                dump_node(gen, node, inplace_restart);
+        }
+        y(array_close);
+
+        ystr("floating-nodes");
+        y(array_open);
+        TAILQ_FOREACH(node, &(con->floating_head), floating_windows) {
+                dump_node(gen, node, inplace_restart);
+        }
+        y(array_close);
+
+        ystr("focus");
+        y(array_open);
+        TAILQ_FOREACH(node, &(con->focus_head), nodes) {
+                y(integer, (long int)node);
+        }
+        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);
+                }
+        }
+
+        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
  *
  */
-static void ipc_send_workspaces(int fd) {
+IPC_HANDLER(get_workspaces) {
         Workspace *ws;
 
         Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
@@ -116,7 +245,7 @@ static void ipc_send_workspaces(int fd) {
 
                 y(map_open);
                 ystr("num");
-                y(integer, ws->num);
+                y(integer, ws->num + 1);
 
                 ystr("name");
                 ystr(ws->utf8_name);
@@ -125,7 +254,7 @@ static void ipc_send_workspaces(int fd) {
                 y(bool, ws->output->current_workspace == ws);
 
                 ystr("focused");
-                y(bool, (last_focused != NULL && last_focused->workspace == ws));
+                y(bool, c_ws == ws);
 
                 ystr("rect");
                 y(map_open);
@@ -142,6 +271,9 @@ static void ipc_send_workspaces(int fd) {
                 ystr("output");
                 ystr(ws->output->name);
 
+                ystr("urgent");
+                y(bool, ws->urgent);
+
                 y(map_close);
         }
 
@@ -156,41 +288,138 @@ static void ipc_send_workspaces(int fd) {
 }
 
 /*
- * Decides what to do with the received message.
+ * Formats the reply message for a GET_OUTPUTS request and sends it to the
+ * client
  *
- * message is the raw packet, as received from the UNIX domain socket. size
- * is the remaining size of bytes for this packet.
+ */
+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).
  *
- * 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.
+ */
+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.
  *
  */
-static void ipc_handle_message(int fd, uint8_t *message, int size,
-                               uint32_t message_size, uint32_t message_type) {
-        DLOG("handling message of size %d\n", size);
-        DLOG("sender specified size %d\n", message_size);
-        DLOG("sender specified type %d\n", message_type);
-        DLOG("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(subscribe) {
+        yajl_handle p;
+        yajl_callbacks callbacks;
+        yajl_status stat;
+        ipc_client *current, *client = NULL;
 
-                        break;
-                }
-                case I3_IPC_MESSAGE_TYPE_GET_WORKSPACES:
-                        ipc_send_workspaces(fd);
-                        break;
-                default:
-                        DLOG("unhandled ipc message\n");
-                        break;
+        /* 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
@@ -221,11 +450,13 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
                 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);
@@ -273,7 +504,12 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
                 message += sizeof(uint32_t);
                 n -= sizeof(uint32_t);
 
-                ipc_handle_message(w->fd, 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;
         }
@@ -305,7 +541,7 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
 
         DLOG("IPC: new client connected\n");
 
-        struct ipc_client *new = scalloc(sizeof(struct ipc_client));
+        ipc_client *new = scalloc(sizeof(ipc_client));
         new->fd = client;
 
         TAILQ_INSERT_TAIL(&all_clients, new, clients);