ipc: implement events, cleanup the code a bit

This commit is contained in:
Michael Stapelberg 2010-03-12 21:05:05 +01:00
parent 69ed573422
commit 3db4890683
5 changed files with 192 additions and 57 deletions

View File

@ -29,6 +29,9 @@
/** Requests the current workspaces from i3 */ /** Requests the current workspaces from i3 */
#define I3_IPC_MESSAGE_TYPE_GET_WORKSPACES 1 #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 * Messages from i3 to clients
* *
@ -40,4 +43,14 @@
/** Workspaces reply type */ /** Workspaces reply type */
#define I3_IPC_REPLY_TYPE_WORKSPACES 1 #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 #endif

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -16,6 +16,34 @@
#include "i3/ipc.h" #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 * 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 * 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); 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 #endif

View File

@ -39,6 +39,7 @@
#include "workspace.h" #include "workspace.h"
#include "log.h" #include "log.h"
#include "container.h" #include "container.h"
#include "ipc.h"
/* After mapping/unmapping windows, a notify event is generated. However, we dont want it, /* After mapping/unmapping windows, a notify event is generated. However, we dont want it,
since itd trigger an infinite loop of switching between the different windows when since itd 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; break;
} }
if (workspace_empty) if (workspace_empty) {
client->workspace->output = NULL; client->workspace->output = NULL;
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
}
/* Remove the urgency flag if set */ /* Remove the urgency flag if set */
client->urgent = false; client->urgent = false;

186
src/ipc.c
View File

@ -23,9 +23,10 @@
#include <stdio.h> #include <stdio.h>
#include <ev.h> #include <ev.h>
#include <yajl/yajl_gen.h> #include <yajl/yajl_gen.h>
#include <yajl/yajl_parse.h>
#include "queue.h" #include "queue.h"
#include "i3/ipc.h" #include "ipc.h"
#include "i3.h" #include "i3.h"
#include "util.h" #include "util.h"
#include "commands.h" #include "commands.h"
@ -36,12 +37,6 @@
#define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__)
#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str)) #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); 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"); 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, static void ipc_send_message(int fd, const unsigned char *payload,
int message_type, int message_size) { int message_type, int message_size) {
int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + 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 * Formats the reply message for a GET_WORKSPACES request and sends it to the
* client * client
* *
*/ */
static void ipc_send_workspaces(int fd) { IPC_HANDLER(get_workspaces) {
Workspace *ws; Workspace *ws;
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); 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 void ipc_handle_message(int fd, uint8_t *message, int size, static int add_subscription(void *extra, const unsigned char *s,
uint32_t message_size, uint32_t message_type) { unsigned int len) {
DLOG("handling message of size %d\n", size); ipc_client *client = extra;
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) { DLOG("should add subscription to extra %p, sub %.*s\n", client, len, s);
case I3_IPC_MESSAGE_TYPE_COMMAND: { int event = client->num_events;
/* 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 client->num_events++;
* (will change with the new command parser) */ client->events = realloc(client->events, client->num_events * sizeof(char*));
const char *reply = "{\"success\":true}"; /* We copy the string because it is not null-terminated and strndup()
ipc_send_message(fd, (const unsigned char*)reply, * is missing on some BSD systems */
I3_IPC_REPLY_TYPE_COMMAND, strlen(reply)); client->events[event] = scalloc(len+1);
memcpy(client->events[event], s, len);
break; DLOG("client is now subscribed to:\n");
} for (int i = 0; i < client->num_events; i++)
case I3_IPC_MESSAGE_TYPE_GET_WORKSPACES: DLOG("event %s\n", client->events[i]);
ipc_send_workspaces(fd); DLOG("(done)\n");
break;
default: return 1;
DLOG("unhandled ipc message\n");
break;
}
} }
/*
* 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));
}
handler_t handlers[3] = {
handle_command,
handle_get_workspaces,
handle_subscribe
};
/* /*
* Handler for activity on a client connection, receives a message from a * Handler for activity on a client connection, receives a message from a
* client. * client.
@ -227,11 +296,13 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
close(w->fd); close(w->fd);
/* Delete the client from the list of clients */ /* Delete the client from the list of clients */
struct ipc_client *current; ipc_client *current;
TAILQ_FOREACH(current, &all_clients, clients) { TAILQ_FOREACH(current, &all_clients, clients) {
if (current->fd != w->fd) if (current->fd != w->fd)
continue; 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 /* We can call TAILQ_REMOVE because we break out of the
* TAILQ_FOREACH afterwards */ * TAILQ_FOREACH afterwards */
TAILQ_REMOVE(&all_clients, current, clients); 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); message += sizeof(uint32_t);
n -= 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; n -= message_size;
message += 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"); 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; new->fd = client;
TAILQ_INSERT_TAIL(&all_clients, new, clients); TAILQ_INSERT_TAIL(&all_clients, new, clients);

View File

@ -28,6 +28,7 @@
#include "client.h" #include "client.h"
#include "log.h" #include "log.h"
#include "ewmh.h" #include "ewmh.h"
#include "ipc.h"
/* /*
* Returns a pointer to the workspace with the given number (starting at 0), * 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); workspace_set_name(ws, NULL);
TAILQ_INSERT_TAIL(workspaces, ws, workspaces); TAILQ_INSERT_TAIL(workspaces, ws, workspaces);
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
} }
DLOG("done\n"); DLOG("done\n");
@ -165,6 +168,8 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
xcb_flush(conn); xcb_flush(conn);
} }
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
return; return;
} }
@ -178,6 +183,8 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
current_col = c_ws->current_col; current_col = c_ws->current_col;
DLOG("new current row = %d, current col = %d\n", current_row, 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); workspace_map_clients(conn, c_ws);
/* POTENTIAL TO IMPROVE HERE: due to the call to _map_clients first and /* POTENTIAL TO IMPROVE HERE: due to the call to _map_clients first and