diff --git a/i3-msg/main.c b/i3-msg/main.c index 124663d6..5bc35b88 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -10,7 +10,10 @@ * i3-msg/main.c: Utility which sends messages to a running i3-instance using * IPC via UNIX domain sockets. * - * This serves as an example for how to send your own messages to i3. + * This (in combination with libi3/ipc_send_message.c and + * libi3/ipc_recv_message.c) serves as an example for how to send your own + * messages to i3. + * * Additionally, it’s even useful sometimes :-). * */ @@ -37,82 +40,6 @@ static char *socket_path; -/* - * Formats a message (payload) of the given size and type and sends it to i3 via - * the given socket file descriptor. - * - */ -static void ipc_send_message(int sockfd, uint32_t message_size, - uint32_t message_type, uint8_t *payload) { - int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size; - char msg[buffer_size]; - char *walk = msg; - - strcpy(walk, I3_IPC_MAGIC); - walk += strlen(I3_IPC_MAGIC); - memcpy(walk, &message_size, sizeof(uint32_t)); - walk += sizeof(uint32_t); - memcpy(walk, &message_type, sizeof(uint32_t)); - walk += sizeof(uint32_t); - memcpy(walk, payload, message_size); - - int sent_bytes = 0; - int bytes_to_go = buffer_size; - while (sent_bytes < bytes_to_go) { - int n = write(sockfd, msg + sent_bytes, bytes_to_go); - if (n == -1) - err(EXIT_FAILURE, "write() failed"); - - sent_bytes += n; - bytes_to_go -= n; - } -} - -static void ipc_recv_message(int sockfd, uint32_t message_type, - uint32_t *reply_length, uint8_t **reply) { - /* Read the message header first */ - 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(sockfd, msg + read_bytes, to_read); - if (n == -1) - err(EXIT_FAILURE, "read() failed"); - if (n == 0) - errx(EXIT_FAILURE, "received EOF instead of reply"); - - read_bytes += n; - to_read -= n; - } - - if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) - errx(EXIT_FAILURE, "invalid magic in reply"); - - walk += strlen(I3_IPC_MAGIC); - *reply_length = *((uint32_t*)walk); - walk += sizeof(uint32_t); - if (*((uint32_t*)walk) != message_type) - errx(EXIT_FAILURE, "unexpected reply type (got %d, expected %d)", *((uint32_t*)walk), message_type); - walk += sizeof(uint32_t); - - *reply = malloc(*reply_length); - if ((*reply) == NULL) - err(EXIT_FAILURE, "malloc() failed"); - - to_read = *reply_length; - read_bytes = 0; - while (read_bytes < to_read) { - int n = read(sockfd, *reply + read_bytes, to_read); - if (n == -1) - err(EXIT_FAILURE, "read() failed"); - - read_bytes += n; - to_read -= n; - } -} - int main(int argc, char *argv[]) { socket_path = getenv("I3SOCK"); int o, option_index = 0; @@ -135,7 +62,7 @@ int main(int argc, char *argv[]) { if (o == 's') { if (socket_path != NULL) free(socket_path); - socket_path = strdup(optarg); + socket_path = sstrdup(optarg); } else if (o == 't') { if (strcasecmp(optarg, "command") == 0) message_type = I3_IPC_MESSAGE_TYPE_COMMAND; @@ -169,15 +96,14 @@ int main(int argc, char *argv[]) { /* Fall back to the default socket path */ if (socket_path == NULL) - socket_path = strdup("/tmp/i3-ipc.sock"); + socket_path = sstrdup("/tmp/i3-ipc.sock"); /* Use all arguments, separated by whitespace, as payload. * This way, you don’t have to do i3-msg 'mark foo', you can use * i3-msg mark foo */ while (optind < argc) { if (!payload) { - if (!(payload = strdup(argv[optind]))) - err(EXIT_FAILURE, "strdup(argv[optind])"); + payload = sstrdup(argv[optind]); } else { char *both; if (asprintf(&both, "%s %s", payload, argv[optind]) == -1) @@ -202,14 +128,20 @@ int main(int argc, char *argv[]) { if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) err(EXIT_FAILURE, "Could not connect to i3"); - ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload); + if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload) == -1) + err(EXIT_FAILURE, "IPC: write()"); if (quiet) return 0; uint32_t reply_length; uint8_t *reply; - ipc_recv_message(sockfd, message_type, &reply_length, &reply); + int ret; + if ((ret = ipc_recv_message(sockfd, message_type, &reply_length, &reply)) != 0) { + if (ret == -1) + err(EXIT_FAILURE, "IPC: read()"); + exit(1); + } printf("%.*s\n", reply_length, reply); free(reply); diff --git a/include/libi3.h b/include/libi3.h index 3883ba82..079d160b 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -14,4 +14,57 @@ */ char *socket_path_from_x11(); +/** + * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that + * there is no more memory available) + * + */ +void *smalloc(size_t size); + +/** + * Safe-wrapper around calloc which exits if malloc returns NULL (meaning that + * there is no more memory available) + * + */ +void *scalloc(size_t size); + +/** + * Safe-wrapper around realloc which exits if realloc returns NULL (meaning + * that there is no more memory available). + * + */ +void *srealloc(void *ptr, size_t size); + +/** + * Safe-wrapper around strdup which exits if malloc returns NULL (meaning that + * there is no more memory available) + * + */ +char *sstrdup(const char *str); + +/** + * 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(int sockfd, uint32_t message_size, + uint32_t message_type, const uint8_t *payload); + +/** + * 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 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(int sockfd, uint32_t message_type, + uint32_t *reply_length, uint8_t **reply); + #endif diff --git a/include/util.h b/include/util.h index edc51d81..7c7b819a 100644 --- a/include/util.h +++ b/include/util.h @@ -66,34 +66,6 @@ Rect rect_add(Rect a, Rect b); */ bool update_if_necessary(uint32_t *destination, const uint32_t new_value); -/** - * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that - * there is no more memory available) - * - */ -void *smalloc(size_t size); - -/** - * Safe-wrapper around calloc which exits if malloc returns NULL (meaning that - * there is no more memory available) - * - */ -void *scalloc(size_t size); - -/** - * Safe-wrapper around realloc which exits if realloc returns NULL (meaning - * that there is no more memory available). - * - */ -void *srealloc(void *ptr, size_t size); - -/** - * Safe-wrapper around strdup which exits if malloc returns NULL (meaning that - * there is no more memory available) - * - */ -char *sstrdup(const char *str); - /** * Starts the given application by passing it through a shell. We use double * fork to avoid zombie processes. As the started application’s parent exits diff --git a/libi3/ipc_recv_message.c b/libi3/ipc_recv_message.c new file mode 100644 index 00000000..47d6dea3 --- /dev/null +++ b/libi3/ipc_recv_message.c @@ -0,0 +1,81 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009-2011 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include +#include +#include +#include +#include + +#include + +#include "libi3.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 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(int sockfd, uint32_t message_type, + uint32_t *reply_length, uint8_t **reply) { + /* Read the message header first */ + 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(sockfd, msg + read_bytes, to_read); + if (n == -1) + return -1; + if (n == 0) { + fprintf(stderr, "IPC: received EOF instead of reply\n"); + return -2; + } + + read_bytes += n; + to_read -= n; + } + + if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) { + fprintf(stderr, "IPC: invalid magic in reply\n"); + return -2; + } + + walk += strlen(I3_IPC_MAGIC); + *reply_length = *((uint32_t*)walk); + walk += sizeof(uint32_t); + if (*((uint32_t*)walk) != message_type) { + fprintf(stderr, "IPC: unexpected reply type (got %d, expected %d)\n", *((uint32_t*)walk), message_type); + return -2; + } + walk += sizeof(uint32_t); + + *reply = smalloc(*reply_length); + + to_read = *reply_length; + read_bytes = 0; + while (read_bytes < to_read) { + int n = read(sockfd, *reply + read_bytes, to_read); + if (n == -1) + return -1; + + read_bytes += n; + to_read -= n; + } + + return 0; +} diff --git a/libi3/ipc_send_message.c b/libi3/ipc_send_message.c new file mode 100644 index 00000000..ff395ada --- /dev/null +++ b/libi3/ipc_send_message.c @@ -0,0 +1,53 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009-2011 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include +#include +#include +#include +#include + +#include + +/* + * 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(int sockfd, uint32_t message_size, + uint32_t message_type, const uint8_t *payload) { + int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size; + char msg[buffer_size]; + char *walk = msg; + + strncpy(walk, I3_IPC_MAGIC, buffer_size - 1); + walk += strlen(I3_IPC_MAGIC); + memcpy(walk, &message_size, sizeof(uint32_t)); + walk += sizeof(uint32_t); + memcpy(walk, &message_type, sizeof(uint32_t)); + walk += sizeof(uint32_t); + memcpy(walk, payload, message_size); + + int sent_bytes = 0; + int bytes_to_go = buffer_size; + while (sent_bytes < bytes_to_go) { + int n = write(sockfd, msg + sent_bytes, bytes_to_go); + if (n == -1) + return -1; + + sent_bytes += n; + bytes_to_go -= n; + } + + return 0; +} diff --git a/libi3/safewrappers.c b/libi3/safewrappers.c new file mode 100644 index 00000000..82311fe3 --- /dev/null +++ b/libi3/safewrappers.c @@ -0,0 +1,47 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009-2011 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include +#include +#include + + +/* + * The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of + * the called functions returns NULL, meaning that there is no more memory available + * + */ +void *smalloc(size_t size) { + void *result = malloc(size); + if (result == NULL) + err(EXIT_FAILURE, "malloc(%zd)", size); + return result; +} + +void *scalloc(size_t size) { + void *result = calloc(size, 1); + if (result == NULL) + err(EXIT_FAILURE, "calloc(%zd)", size); + return result; +} + +void *srealloc(void *ptr, size_t size) { + void *result = realloc(ptr, size); + if (result == NULL && size > 0) + err(EXIT_FAILURE, "realloc(%zd)", size); + return result; +} + +char *sstrdup(const char *str) { + char *result = strdup(str); + if (result == NULL) + err(EXIT_FAILURE, "strdup()"); + return result; +} diff --git a/src/cfgparse.l b/src/cfgparse.l index dad5a915..49714401 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -17,6 +17,7 @@ #include "config.h" #include "log.h" #include "util.h" +#include "libi3.h" #include "cfgparse.tab.h" diff --git a/src/cmdparse.l b/src/cmdparse.l index 968b7e52..f6b132ca 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -19,6 +19,7 @@ #include "config.h" #include "util.h" +#include "libi3.h" int cmdyycolumn = 1; diff --git a/src/ipc.c b/src/ipc.c index 031ee9ab..eba778cd 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -72,35 +72,6 @@ static bool mkdirp(const char *path) { return result; } -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) + - sizeof(uint32_t) + message_size; - char msg[buffer_size]; - char *walk = msg; - - strncpy(walk, "i3-ipc", buffer_size - 1); - walk += strlen("i3-ipc"); - memcpy(walk, &message_size, sizeof(uint32_t)); - walk += sizeof(uint32_t); - memcpy(walk, &message_type, sizeof(uint32_t)); - walk += sizeof(uint32_t); - memcpy(walk, payload, message_size); - - int sent_bytes = 0; - int bytes_to_go = buffer_size; - while (sent_bytes < bytes_to_go) { - int n = write(fd, msg + sent_bytes, bytes_to_go); - if (n == -1) { - DLOG("write() failed: %s\n", strerror(errno)); - return; - } - - sent_bytes += n; - bytes_to_go -= n; - } -} - /* * Sends the specified event to all IPC clients which are currently connected * and subscribed to this kind of event. @@ -120,8 +91,7 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa if (!interested) continue; - ipc_send_message(current->fd, (const unsigned char*)payload, - message_type, strlen(payload)); + ipc_send_message(current->fd, strlen(payload), message_type, (const uint8_t*)payload); } } @@ -156,8 +126,7 @@ IPC_HANDLER(command) { /* If no reply was provided, we just use the default success message */ if (reply == NULL) reply = "{\"success\":true}"; - ipc_send_message(fd, (const unsigned char*)reply, - I3_IPC_REPLY_TYPE_COMMAND, strlen(reply)); + ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_COMMAND, (const uint8_t*)reply); FREE(save_reply); } @@ -339,7 +308,7 @@ IPC_HANDLER(tree) { #endif y(get_buf, &payload, &length); - ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_TREE, length); + ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_TREE, payload); y(free); } @@ -412,7 +381,7 @@ IPC_HANDLER(get_workspaces) { #endif y(get_buf, &payload, &length); - ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length); + ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_WORKSPACES, payload); y(free); } @@ -470,7 +439,7 @@ IPC_HANDLER(get_outputs) { #endif y(get_buf, &payload, &length); - ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_OUTPUTS, length); + ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_OUTPUTS, payload); y(free); } @@ -502,7 +471,7 @@ IPC_HANDLER(get_marks) { #endif y(get_buf, &payload, &length); - ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_MARKS, length); + ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_MARKS, payload); y(free); } @@ -580,15 +549,13 @@ IPC_HANDLER(subscribe) { 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)); + ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t*)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)); + ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t*)reply); } /* The index of each callback function corresponds to the numeric diff --git a/src/main.c b/src/main.c index 19c45681..3ebfb5f4 100644 --- a/src/main.c +++ b/src/main.c @@ -3,6 +3,9 @@ */ #include #include +#include +#include +#include #include "all.h" #include "sd-daemon.h" @@ -266,20 +269,92 @@ int main(int argc, char *argv[]) { default: fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]); fprintf(stderr, "\n"); - fprintf(stderr, "-a: disable autostart\n"); - fprintf(stderr, "-L : load the layout from \n"); - fprintf(stderr, "-v: display version and exit\n"); - fprintf(stderr, "-V: enable verbose mode\n"); - fprintf(stderr, "-d : enable debug loglevel \n"); - fprintf(stderr, "-c : use the provided configfile instead\n"); - fprintf(stderr, "-C: check configuration file and exit\n"); - fprintf(stderr, "--force-xinerama: Use Xinerama instead of RandR. This " - "option should only be used if you are stuck with the " - "nvidia closed source driver which does not support RandR.\n"); + fprintf(stderr, "\t-a disable autostart ('exec' lines in config)\n"); + fprintf(stderr, "\t-c use the provided configfile instead\n"); + fprintf(stderr, "\t-C validate configuration file and exit\n"); + fprintf(stderr, "\t-d enable debug output with the specified loglevel\n"); + fprintf(stderr, "\t-L path to the serialized layout during restarts\n"); + fprintf(stderr, "\t-v display version and exit\n"); + fprintf(stderr, "\t-V enable verbose mode\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "\t--force-xinerama\n" + "\tUse Xinerama instead of RandR.\n" + "\tThis option should only be used if you are stuck with the\n" + "\tnvidia closed source driver which does not support RandR.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "\t--get-socketpath\n" + "\tRetrieve the i3 IPC socket path from X11, print it, then exit.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "If you pass plain text arguments, i3 will interpret them as a command\n" + "to send to a currently running i3 (like i3-msg). This allows you to\n" + "use nice and logical commands, such as:\n" + "\n" + "\ti3 border none\n" + "\ti3 floating toggle\n" + "\ti3 kill window\n" + "\n"); exit(EXIT_FAILURE); } } + /* If the user passes more arguments, we act like i3-msg would: Just send + * the arguments as an IPC message to i3. This allows for nice semantic + * commands such as 'i3 border none'. */ + if (optind < argc) { + /* We enable verbose mode so that the user knows what’s going on. + * This should make it easier to find mistakes when the user passes + * arguments by mistake. */ + set_verbosity(true); + + LOG("Additional arguments passed. Sending them as a command to i3.\n"); + char *payload = NULL; + while (optind < argc) { + if (!payload) { + payload = sstrdup(argv[optind]); + } else { + char *both; + if (asprintf(&both, "%s %s", payload, argv[optind]) == -1) + err(EXIT_FAILURE, "asprintf"); + free(payload); + payload = both; + } + optind++; + } + LOG("Command is: %s (%d bytes)\n", payload, strlen(payload)); + char *socket_path = socket_path_from_x11(); + if (!socket_path) { + ELOG("Could not get i3 IPC socket path\n"); + return 1; + } + + int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (sockfd == -1) + err(EXIT_FAILURE, "Could not create socket"); + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_LOCAL; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) + err(EXIT_FAILURE, "Could not connect to i3"); + + if (ipc_send_message(sockfd, strlen(payload), I3_IPC_MESSAGE_TYPE_COMMAND, + (uint8_t*)payload) == -1) + err(EXIT_FAILURE, "IPC: write()"); + + uint32_t reply_length; + uint8_t *reply; + int ret; + if ((ret = ipc_recv_message(sockfd, I3_IPC_MESSAGE_TYPE_COMMAND, + &reply_length, &reply)) != 0) { + if (ret == -1) + err(EXIT_FAILURE, "IPC: read()"); + return 1; + } + printf("%.*s\n", reply_length, reply); + return 0; + } + LOG("i3 (tree) version " I3_VERSION " starting\n"); conn = xcb_connect(NULL, &screens); diff --git a/src/util.c b/src/util.c index 2d6c3e14..30371bcd 100644 --- a/src/util.c +++ b/src/util.c @@ -58,36 +58,6 @@ bool update_if_necessary(uint32_t *destination, const uint32_t new_value) { return ((*destination = new_value) != old_value); } -/* - * The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of - * the called functions returns NULL, meaning that there is no more memory available - * - */ -void *smalloc(size_t size) { - void *result = malloc(size); - exit_if_null(result, "Error: out of memory (malloc(%zd))\n", size); - return result; -} - -void *scalloc(size_t size) { - void *result = calloc(size, 1); - exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size); - return result; -} - -void *srealloc(void *ptr, size_t size) { - void *result = realloc(ptr, size); - if (result == NULL && size > 0) - die("Error: out memory (realloc(%zd))\n", size); - return result; -} - -char *sstrdup(const char *str) { - char *result = strdup(str); - exit_if_null(result, "Error: out of memory (strdup())\n"); - return result; -} - /* * Starts the given application by passing it through a shell. We use double fork * to avoid zombie processes. As the started application’s parent exits (immediately),