From e9b15e2497ea040161e54f6293a869335fbda28a Mon Sep 17 00:00:00 2001 From: Valentin Boettcher Date: Sat, 3 Mar 2018 11:44:06 +0100 Subject: [PATCH] init --- include/ipc.h | 103 ++++++++++++ test.c | 423 ++++++++++++++++++++++++++++++++++++++++++++++++++ test.h | 59 +++++++ test.o | Bin 0 -> 19768 bytes yajl | 1 + 5 files changed, 586 insertions(+) create mode 100644 include/ipc.h create mode 100644 test.c create mode 100644 test.h create mode 100755 test.o create mode 160000 yajl 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 0000000000000000000000000000000000000000..a229f2d91edc6ea802c05316bbd4d8a1b62d2a64 GIT binary patch literal 19768 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