From: Michael Stapelberg Date: Fri, 12 Mar 2010 20:05:05 +0000 (+0100) Subject: ipc: implement events, cleanup the code a bit X-Git-Tag: 3.e~6^2~81 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=3db4890683e8783b7fbf85099a517046ac64d613;p=i3%2Fi3 ipc: implement events, cleanup the code a bit --- diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 19d93298..aad34976 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -29,6 +29,9 @@ /** Requests the current workspaces from i3 */ #define I3_IPC_MESSAGE_TYPE_GET_WORKSPACES 1 +/** Subscribe to the specified events */ +#define I3_IPC_MESSAGE_TYPE_SUBSCRIBE 2 + /* * Messages from i3 to clients * @@ -40,4 +43,14 @@ /** Workspaces reply type */ #define I3_IPC_REPLY_TYPE_WORKSPACES 1 +/** Subscription reply type */ +#define I3_IPC_REPLY_TYPE_SUBSCRIBE 2 + +/* + * Events from i3 to clients + * + */ + +#define I3_IPC_EVENT_WORKSPACE 0 + #endif diff --git a/include/ipc.h b/include/ipc.h index de4e2264..b798b5ff 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -16,6 +16,34 @@ #include "i3/ipc.h" +typedef struct ipc_client { + int fd; + + /* The events which this client wants to receive */ + int num_events; + char **events; + + TAILQ_ENTRY(ipc_client) clients; +} ipc_client; + +/* + * Callback type for the different message types. + * + * message is the raw packet, as received from the UNIX domain socket. size + * is the remaining size of bytes for this packet. + * + * 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. + * + */ +typedef void(*handler_t)(int, uint8_t*, int, uint32_t, uint32_t); + +/* Macro to declare a callback */ +#define IPC_HANDLER(name) \ + static void handle_ ## name (int fd, uint8_t *message, \ + int size, uint32_t message_size, \ + uint32_t message_type) + /** * 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 @@ -32,4 +60,12 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents); */ int ipc_create_socket(const char *filename); +/** + * 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); + + #endif diff --git a/src/handlers.c b/src/handlers.c index b43cc4f3..109281a1 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -39,6 +39,7 @@ #include "workspace.h" #include "log.h" #include "container.h" +#include "ipc.h" /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it, since it’d trigger an infinite loop of switching between the different windows when @@ -573,8 +574,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti break; } - if (workspace_empty) + if (workspace_empty) { client->workspace->output = NULL; + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}"); + } /* Remove the urgency flag if set */ client->urgent = false; diff --git a/src/ipc.c b/src/ipc.c index 0d1d5b5c..f396c43b 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -23,9 +23,10 @@ #include #include #include +#include #include "queue.h" -#include "i3/ipc.h" +#include "ipc.h" #include "i3.h" #include "util.h" #include "commands.h" @@ -36,12 +37,6 @@ #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 +52,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 +81,56 @@ 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)); + } +} + +/* + * 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); + strncpy(command, (const char*)message, message_size); + parse_command(global_conn, (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)); +} + /* * 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)); @@ -156,48 +186,87 @@ static void ipc_send_workspaces(int fd) { } /* - * Decides what to do with the received message. + * Callback for the YAJL parser (will be called when a string is parsed). * - * message is the raw packet, as received from the UNIX domain socket. size - * is the remaining size of bytes for this packet. - * - * 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); - - /* 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)); +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)); } +handler_t handlers[3] = { + handle_command, + handle_get_workspaces, + handle_subscribe +}; + /* * Handler for activity on a client connection, receives a message from a * client. @@ -227,11 +296,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); @@ -279,7 +350,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; } @@ -311,7 +387,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); diff --git a/src/workspace.c b/src/workspace.c index 7a9a959c..3b8c6ba4 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -28,6 +28,7 @@ #include "client.h" #include "log.h" #include "ewmh.h" +#include "ipc.h" /* * Returns a pointer to the workspace with the given number (starting at 0), @@ -57,6 +58,8 @@ Workspace *workspace_get(int number) { workspace_set_name(ws, NULL); TAILQ_INSERT_TAIL(workspaces, ws, workspaces); + + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); } DLOG("done\n"); @@ -165,6 +168,8 @@ void workspace_show(xcb_connection_t *conn, int workspace) { xcb_flush(conn); } + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); + return; } @@ -178,6 +183,8 @@ void workspace_show(xcb_connection_t *conn, int workspace) { current_col = c_ws->current_col; DLOG("new current row = %d, current col = %d\n", current_row, current_col); + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); + workspace_map_clients(conn, c_ws); /* POTENTIAL TO IMPROVE HERE: due to the call to _map_clients first and