summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Stapelberg <michael@stapelberg.de>2010-03-12 21:05:05 +0100
committerMichael Stapelberg <michael@stapelberg.de>2010-03-12 21:05:05 +0100
commit3db4890683e8783b7fbf85099a517046ac64d613 (patch)
tree018cafdb41e0a8361c45ab77d422c58b39ff00c0
parent69ed5734221deaff4320f8c4f342d42b8cae3a63 (diff)
ipc: implement events, cleanup the code a bit
-rw-r--r--include/i3/ipc.h13
-rw-r--r--include/ipc.h38
-rw-r--r--src/handlers.c5
-rw-r--r--src/ipc.c186
-rw-r--r--src/workspace.c7
5 files changed, 192 insertions, 57 deletions
diff --git a/include/i3/ipc.h b/include/i3/ipc.h
index 19d9329..aad3497 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 de4e226..b798b5f 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 b43cc4f..109281a 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 0d1d5b5..f396c43 100644
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -23,9 +23,10 @@
#include <stdio.h>
#include <ev.h>
#include <yajl/yajl_gen.h>
+#include <yajl/yajl_parse.h>
#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) +
@@ -96,11 +82,55 @@ 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 7a9a959..3b8c6ba 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