ipc: implement events, cleanup the code a bit
This commit is contained in:
parent
69ed573422
commit
3db4890683
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 don’t want it,
|
/* 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
|
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;
|
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;
|
||||||
|
|
184
src/ipc.c
184
src/ipc.c
|
@ -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,47 +186,86 @@ 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*));
|
||||||
|
/* 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}";
|
const char *reply = "{\"success\":true}";
|
||||||
ipc_send_message(fd, (const unsigned char*)reply,
|
ipc_send_message(fd, (const unsigned char*)reply,
|
||||||
I3_IPC_REPLY_TYPE_COMMAND, strlen(reply));
|
I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply));
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
handler_t handlers[3] = {
|
||||||
}
|
handle_command,
|
||||||
case I3_IPC_MESSAGE_TYPE_GET_WORKSPACES:
|
handle_get_workspaces,
|
||||||
ipc_send_workspaces(fd);
|
handle_subscribe
|
||||||
break;
|
};
|
||||||
default:
|
|
||||||
DLOG("unhandled ipc message\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Handler for activity on a client connection, receives a message from a
|
* Handler for activity on a client connection, receives a message from a
|
||||||
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue