diff --git a/include/ipc.h b/include/ipc.h new file mode 100644 index 0000000..993a2a2 --- /dev/null +++ b/include/ipc.h @@ -0,0 +1,103 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) + * + * This public header defines the different constants and message types to use + * for the IPC interface to i3 (see docs/ipc for more information). + * + */ +#pragma once + +#include + +typedef struct i3_ipc_header { + /* 6 = strlen(I3_IPC_MAGIC) */ + char magic[6]; + uint32_t size; + uint32_t type; +} __attribute__((packed)) i3_ipc_header_t; + +/* + * Messages from clients to i3 + * + */ + +/** Never change this, only on major IPC breakage (don’t do that) */ +#define I3_IPC_MAGIC "i3-ipc" + +/** Deprecated: use I3_IPC_MESSAGE_TYPE_RUN_COMMAND */ +#define I3_IPC_MESSAGE_TYPE_COMMAND 0 + +/** The payload of the message will be interpreted as a command */ +#define I3_IPC_MESSAGE_TYPE_RUN_COMMAND 0 + +/** 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 + +/** Requests the current outputs from i3 */ +#define I3_IPC_MESSAGE_TYPE_GET_OUTPUTS 3 + +/** Requests the tree layout from i3 */ +#define I3_IPC_MESSAGE_TYPE_GET_TREE 4 + +/** Request the current defined marks from i3 */ +#define I3_IPC_MESSAGE_TYPE_GET_MARKS 5 + +/** Request the configuration for a specific 'bar' */ +#define I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG 6 + +/** Request the i3 version */ +#define I3_IPC_MESSAGE_TYPE_GET_VERSION 7 + +/** Request a list of configured binding modes. */ +#define I3_IPC_MESSAGE_TYPE_GET_BINDING_MODES 8 + +/** Request the raw last loaded i3 config. */ +#define I3_IPC_MESSAGE_TYPE_GET_CONFIG 9 + +/* + * Messages from i3 to clients + * + */ +#define I3_IPC_REPLY_TYPE_COMMAND 0 +#define I3_IPC_REPLY_TYPE_WORKSPACES 1 +#define I3_IPC_REPLY_TYPE_SUBSCRIBE 2 +#define I3_IPC_REPLY_TYPE_OUTPUTS 3 +#define I3_IPC_REPLY_TYPE_TREE 4 +#define I3_IPC_REPLY_TYPE_MARKS 5 +#define I3_IPC_REPLY_TYPE_BAR_CONFIG 6 +#define I3_IPC_REPLY_TYPE_VERSION 7 +#define I3_IPC_REPLY_TYPE_BINDING_MODES 8 +#define I3_IPC_REPLY_TYPE_CONFIG 9 + +/* + * Events from i3 to clients. Events have the first bit set high. + * + */ +#define I3_IPC_EVENT_MASK (1 << 31) + +/* The workspace event will be triggered upon changes in the workspace list */ +#define I3_IPC_EVENT_WORKSPACE (I3_IPC_EVENT_MASK | 0) + +/* The output event will be triggered upon changes in the output list */ +#define I3_IPC_EVENT_OUTPUT (I3_IPC_EVENT_MASK | 1) + +/* The output event will be triggered upon mode changes */ +#define I3_IPC_EVENT_MODE (I3_IPC_EVENT_MASK | 2) + +/* The window event will be triggered upon window changes */ +#define I3_IPC_EVENT_WINDOW (I3_IPC_EVENT_MASK | 3) + +/** Bar config update will be triggered to update the bar config */ +#define I3_IPC_EVENT_BARCONFIG_UPDATE (I3_IPC_EVENT_MASK | 4) + +/** The binding event will be triggered when bindings run */ +#define I3_IPC_EVENT_BINDING (I3_IPC_EVENT_MASK | 5) + +/** The shutdown event will be triggered when the ipc shuts down */ +#define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6) diff --git a/test.c b/test.c new file mode 100644 index 0000000..60825de --- /dev/null +++ b/test.c @@ -0,0 +1,423 @@ +#include "test.h" + +/* + * Reads a message from the given socket file descriptor and stores its length + * (reply_length) as well as a pointer to its contents (reply). + * + * Returns -1 when read() fails, errno will remain. + * Returns -2 on EOF. + * Returns -3 when the IPC protocol is violated (invalid magic, unexpected + * message type, EOF instead of a message). Additionally, the error will be + * printed to stderr. + * Returns 0 on success. + * + */ +int ipc_recv_message(uint32_t *message_type, + uint32_t *reply_length, uint8_t **reply) { + /* Read the message header first */ + const uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t); + char msg[to_read]; + char *walk = msg; + + uint32_t read_bytes = 0; + while (read_bytes < to_read) { + int n = read(i3_sockfd, msg + read_bytes, to_read - read_bytes); + if (n == -1) + return -1; + if (n == 0) { + if (read_bytes == 0) { + return -2; + } else { + return -3; + } + } + + read_bytes += n; + } + + if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) { + return -3; + } + + walk += strlen(I3_IPC_MAGIC); + memcpy(reply_length, walk, sizeof(uint32_t)); + walk += sizeof(uint32_t); + if (message_type != NULL) + memcpy(message_type, walk, sizeof(uint32_t)); + + *reply = malloc(*reply_length); + + read_bytes = 0; + while (read_bytes < *reply_length) { + const int n = read(i3_sockfd, *reply + read_bytes, *reply_length - read_bytes); + if (n == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (n == 0) { + return -3; + } + + read_bytes += n; + } + + return 0; +} + + +/* + * Formats a message (payload) of the given size and type and sends it to i3 via + * the given socket file descriptor. + * + * Returns -1 when write() fails, errno will remain. + * Returns 0 on success. + * + */ +int ipc_send_message_s(const uint32_t message_type, char* msg){ + ipc_send_message(strlen(msg), message_type, (uint8_t *)msg); +} + +int ipc_send_message(const uint32_t message_size, + const uint32_t message_type, const uint8_t *payload) { + const i3_ipc_header_t header = { + /* We don’t use I3_IPC_MAGIC because it’s a 0-terminated C string. */ + .magic = {'i', '3', '-', 'i', 'p', 'c'}, + .size = message_size, + .type = message_type}; + + if (write(i3_sockfd, ((void *)&header), sizeof(i3_ipc_header_t)) == -1) + return -1; + + if (write(i3_sockfd, payload, message_size) == -1) + return -1; + return 0; +} +int i3_connect() { + if((i3_sockfd=socket (PF_LOCAL, SOCK_STREAM, 0)) < 0) + return -1; + + i3_addr.sun_family = AF_LOCAL; + strcpy(i3_addr.sun_path, i3_socket_path); + if (connect ( i3_sockfd, + (struct sockaddr *) &i3_addr, + sizeof (i3_addr)) < 0) + return -1; + + return 0; +} + +workspace get_workspace_by_num(unsigned int num, int * index){ + workspace cur = workspaces.ws; + + int i = 0; + fflush(stdout); + + for(; inum != num; i++){ + //printf("HEY %i, %i\n", cur->num, workspaces.len); + cur = cur->next; + } + + if(index != NULL){ + if(i == workspaces.len) + i--; + + *index = i; + } + + return cur; +} + +workspace get_workspace(int index){ + if(index < 0){ + return NULL; + } + + workspace ret = workspaces.ws; + for(int i = 0; inext; + return ret; +} + +int insert_workspace(workspace ws, int position){ + if(position < 0) { + position = (workspaces.len == 0) ? 0 : workspaces.len; + } + + //printf("Insert %s at %i \n", ws->name, position); + fflush(stdout); + + if(position > workspaces.len) + return -1; + + workspace before = get_workspace(position - 1); + workspace after = (before == NULL) ? NULL : before->next; + + if(workspaces.len == 0 || position == 0){ + after = workspaces.ws; + before = NULL; + workspaces.ws = ws; + } + + if(before != NULL){ + before->next = ws; + ws->prev = before; + } else { + ws->prev = NULL; + } + + if(after != NULL){ + after->prev = ws; + ws->next = after; + } else { + ws->next = NULL; + } + + workspaces.len++; + return 0; +}; + +int remove_workspace(int position){ + if(position < 0 || position > workspaces.len - 1) + return -1; + + workspace to_delete = get_workspace(position); + workspace before = to_delete->prev; + workspace after = to_delete->next; + + free(to_delete); + + + if(before != NULL && after != NULL){ + after->prev = before; + before->next = after; + } if(after == NULL && before != NULL){ + before->next = NULL; + } if (after != NULL && before != NULL){ + after->prev = 0; + } + + if(position == 0){ + workspaces.ws = after; + } + + workspaces.len--; + return 1; +} + +int clean_workspaces(){ + workspace ws = workspaces.ws; + while(ws != NULL) { + workspace next = ws->next; + free(ws); + ws = next; + }; + + workspaces.ws = NULL; + workspaces.len = 0; +} + +int sanitize_reply(uint8_t ** reply, uint32_t reply_length){ + char * reply_san = malloc(reply_length + 1); + memcpy(reply_san, *reply, reply_length); + reply_san[reply_length] = '\0'; + + free(*reply); + + *reply = reply_san; + + return 0; +}; + +int get_workspaces(){ + if (ipc_send_message(0, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL) == -1) + return -1; + + uint32_t reply_length; + uint32_t reply_type; + uint8_t *reply; + int ret; + if ((ret = ipc_recv_message(&reply_type, &reply_length, &reply)) != 0) { + if (ret == -1) + return -1; + } + + if(reply_type != I3_IPC_MESSAGE_TYPE_GET_WORKSPACES) + return -1; + + sanitize_reply(&reply, reply_length); + + yajl_val node = yajl_tree_parse(reply, NULL, 0); + const char * path_num[] = { "num", (const char *) 0 }; + const char * path_name[] = { "name", (const char *) 0 }; + const char * path_focused[] = { "focused", (const char *) 0 }; + + if(node && YAJL_IS_ARRAY(node)){ + size_t len = node->u.array.len; + for(int i = 0; i < len; i++){ + yajl_val obj = node->u.array.values[ i ]; + yajl_val num = yajl_tree_get(obj, path_num, yajl_t_number); + yajl_val name = yajl_tree_get(obj, path_name, yajl_t_string); + yajl_val focused = yajl_tree_get(obj, path_focused, yajl_t_true); + + workspace new_ws = malloc(sizeof(struct workspace_t)); + new_ws->num = (YAJL_GET_INTEGER(num)); + new_ws->active = YAJL_IS_TRUE(focused) ? 1 : 0; + new_ws->name = strdup(YAJL_GET_STRING(name)); + + insert_workspace(new_ws, -1); + } + } else { + return -1; + } + + yajl_tree_free(node); + free(reply); + return 0; +}; + +int format_ws_list(){ + // Let's rock! + char* template_inactive = "%s "; + char* template_active = "%s "; + char* buff = malloc(strlen(template_inactive) * (workspaces.len - 1) + + strlen(template_active)); + char* walk = buff; + + workspace ws = workspaces.ws; + while(ws != NULL) { + char * template = ws->active == 1 ? template_active : template_inactive; + sprintf(walk, template, ws->name); + walk += strlen(template) - 1; + ws = ws->next; + }; + + free(workspaces.workspaces_format); + workspaces.workspaces_format = buff; +} + +int listen_to_events(){ + char *listen_command = "[\"workspace\"]"; + ipc_send_message_s(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, listen_command); + + while(1) { + uint32_t reply_length; + uint32_t reply_type; + uint8_t *reply; + int ret; + fflush(stdout); + + if ((ret = ipc_recv_message(&reply_type, &reply_length, &reply)) != 0){ + free(reply); + continue; + } + + if(reply_type == I3_IPC_EVENT_WORKSPACE){ + sanitize_reply(&reply, reply_length); + + yajl_val node = yajl_tree_parse((const char *) reply, NULL, 0); + const char * type_path[] = { "change", (const char *) 0 }; + const char * curr_num_path[] = { "current", "num", (const char *) 0 }; + const char * curr_name_path[] = { "current", "name", (const char *) 0 }; + + const char * old_num_path[] = { "old", "num", (const char *) 0 }; + + + yajl_val type = yajl_tree_get(node, type_path, yajl_t_string); + yajl_val curr_num = yajl_tree_get(node, curr_num_path, yajl_t_number); + yajl_val old_num = yajl_tree_get(node, old_num_path, yajl_t_number); + + char* type_str = YAJL_GET_STRING(type); + + //printf("EVENT: %s, %i\n", type_str, YAJL_GET_INTEGER(curr_num)); + fflush(stdout); + switch(type_str[0]){ + case 'i': { + int index = -1; + workspace ws = get_workspace_by_num(YAJL_GET_INTEGER(curr_num), &index); + //printf("%i", index); + fflush(stdout); + + yajl_val curr_name = yajl_tree_get(node, curr_name_path, yajl_t_string); + + workspace new = malloc(sizeof(struct workspace_t)); + new->name = strdup(YAJL_GET_STRING(curr_name)); + new->num = YAJL_GET_INTEGER(curr_num); + + insert_workspace(new, index + 1); + break; + } + + case 'f': { + workspace ws = get_workspace_by_num(YAJL_GET_INTEGER(curr_num), NULL); + if(ws != NULL) { + ws->active = 1; + ws = get_workspace_by_num(YAJL_GET_INTEGER(old_num), NULL); + ws->active = 0; + } + break; + + } + + case 'e': { + int index = -1; + get_workspace_by_num(YAJL_GET_INTEGER(curr_num), &index); + remove_workspace(index); + break; + } + + default: + break; + } + + format_ws_list(); + printf("%s\n", workspaces.workspaces_format); + + yajl_tree_free(node); + } + + free(reply); + } +} + +int main(int argc, char **argv){ + workspaces.len = 0; + i3_socket_path = get_i3_socket(); + i3_connect(); + + get_workspaces(); + + format_ws_list(); + listen_to_events(); + + clean_workspaces(); + free(i3_socket_path); + free(workspaces.workspaces_format); +} + +char* get_i3_socket(){ + FILE *pf; + char *command = "i3 --get-socketpath"; + char *tmp = malloc(200); + char* buffer; + + // ask i3 + pf = popen(command,"r"); + + if(!pf){ + fprintf(stderr, "Could not open pipe for output.\n"); + return NULL; + } + + // get the socket path + fscanf(pf, "%s", tmp); + buffer = malloc(strlen(tmp)+1); + strcpy(buffer, tmp); + free(tmp); + + if (pclose(pf) != 0){ // failed to close stream + return NULL; + } + + return buffer; +} diff --git a/test.h b/test.h new file mode 100644 index 0000000..5583d7c --- /dev/null +++ b/test.h @@ -0,0 +1,59 @@ +#include +#include + +#include +#include +#include +#include +#include +#include "include/ipc.h" + +#include +#include +#include +#include +#include +#include +#include + +#define WS_FOCUS "focus" +#define WS_EMPTY "empty" +#define WS_INIT "init" + + +//TODO: dynamic +char *i3_socket_path; +int i3_sockfd; +struct sockaddr_un i3_addr; + +int sanitize_reply(uint8_t ** reply, uint32_t reply_length); + +char* get_i3_socket(); +int ipc_recv_message(uint32_t *message_type, + uint32_t *reply_length, uint8_t **reply); +int ipc_send_message_s(uint32_t message_type, char* msg); +int ipc_send_message(const uint32_t message_size, + const uint32_t message_type, const uint8_t *payload); + +typedef struct workspace_t* workspace; +struct workspace_t { + unsigned int num; + int active; + char* name; + workspace next; + workspace prev; +}; + +struct workspaces_t { + unsigned int len; + workspace ws; + char* workspaces_format; +} workspaces; + + +int insert_workspace(workspace ws, int position); +int remove_workspace(int position); +workspace get_workspace(int index); +workspace get_workspace_by_num(unsigned int num, int * index); +int get_workspaces(); +int clean_workspaces(); diff --git a/test.o b/test.o new file mode 100755 index 0000000..a229f2d Binary files /dev/null and b/test.o differ diff --git a/yajl b/yajl new file mode 160000 index 0000000..5e3a785 --- /dev/null +++ b/yajl @@ -0,0 +1 @@ +Subproject commit 5e3a7856e643b4d6410ddc3f84bc2f38174f2872