|
|
- #include <ctype.h>
- #include <errno.h>
- #include <inttypes.h>
- #include <stdarg.h>
- #include <stdint.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/socket.h>
- #include <sys/un.h>
- #include <unistd.h>
- #include <yajl/yajl_gen.h>
-
- #define IPC_MAGIC "DWM-IPC"
- // clang-format off
- #define IPC_MAGIC_ARR { 'D', 'W', 'M', '-', 'I', 'P', 'C' }
- // clang-format on
- #define IPC_MAGIC_LEN 7 // Not including null char
-
- #define IPC_EVENT_TAG_CHANGE "tag_change_event"
- #define IPC_EVENT_CLIENT_FOCUS_CHANGE "client_focus_change_event"
- #define IPC_EVENT_LAYOUT_CHANGE "layout_change_event"
- #define IPC_EVENT_MONITOR_FOCUS_CHANGE "monitor_focus_change_event"
- #define IPC_EVENT_FOCUSED_TITLE_CHANGE "focused_title_change_event"
- #define IPC_EVENT_FOCUSED_STATE_CHANGE "focused_state_change_event"
-
- #define YSTR(str) yajl_gen_string(gen, (unsigned char *)str, strlen(str))
- #define YINT(num) yajl_gen_integer(gen, num)
- #define YDOUBLE(num) yajl_gen_double(gen, num)
- #define YBOOL(v) yajl_gen_bool(gen, v)
- #define YNULL() yajl_gen_null(gen)
- #define YARR(body) \
- { \
- yajl_gen_array_open(gen); \
- body; \
- yajl_gen_array_close(gen); \
- }
- #define YMAP(body) \
- { \
- yajl_gen_map_open(gen); \
- body; \
- yajl_gen_map_close(gen); \
- }
-
- typedef unsigned long Window;
-
- const char *DEFAULT_SOCKET_PATH = "/tmp/dwm.sock";
- static int sock_fd = -1;
- static unsigned int ignore_reply = 0;
-
- typedef enum IPCMessageType {
- IPC_TYPE_RUN_COMMAND = 0,
- IPC_TYPE_GET_MONITORS = 1,
- IPC_TYPE_GET_TAGS = 2,
- IPC_TYPE_GET_LAYOUTS = 3,
- IPC_TYPE_GET_DWM_CLIENT = 4,
- IPC_TYPE_SUBSCRIBE = 5,
- IPC_TYPE_EVENT = 6
- } IPCMessageType;
-
- // Every IPC message must begin with this
- typedef struct dwm_ipc_header {
- uint8_t magic[IPC_MAGIC_LEN];
- uint32_t size;
- uint8_t type;
- } __attribute((packed)) dwm_ipc_header_t;
-
- static int
- recv_message(uint8_t *msg_type, uint32_t *reply_size, uint8_t **reply)
- {
- uint32_t read_bytes = 0;
- const int32_t to_read = sizeof(dwm_ipc_header_t);
- char header[to_read];
- char *walk = header;
-
- // Try to read header
- while (read_bytes < to_read) {
- ssize_t n = read(sock_fd, header + read_bytes, to_read - read_bytes);
-
- if (n == 0) {
- if (read_bytes == 0) {
- fprintf(stderr, "Unexpectedly reached EOF while reading header.");
- fprintf(stderr,
- "Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n",
- read_bytes, to_read);
- return -2;
- } else {
- fprintf(stderr, "Unexpectedly reached EOF while reading header.");
- fprintf(stderr,
- "Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n",
- read_bytes, to_read);
- return -3;
- }
- } else if (n == -1) {
- return -1;
- }
-
- read_bytes += n;
- }
-
- // Check if magic string in header matches
- if (memcmp(walk, IPC_MAGIC, IPC_MAGIC_LEN) != 0) {
- fprintf(stderr, "Invalid magic string. Got '%.*s', expected '%s'\n",
- IPC_MAGIC_LEN, walk, IPC_MAGIC);
- return -3;
- }
-
- walk += IPC_MAGIC_LEN;
-
- // Extract reply size
- memcpy(reply_size, walk, sizeof(uint32_t));
- walk += sizeof(uint32_t);
-
- // Extract message type
- memcpy(msg_type, walk, sizeof(uint8_t));
- walk += sizeof(uint8_t);
-
- (*reply) = malloc(*reply_size);
-
- // Extract payload
- read_bytes = 0;
- while (read_bytes < *reply_size) {
- ssize_t n = read(sock_fd, *reply + read_bytes, *reply_size - read_bytes);
-
- if (n == 0) {
- fprintf(stderr, "Unexpectedly reached EOF while reading payload.");
- fprintf(stderr, "Read %" PRIu32 " bytes, expected %" PRIu32 " bytes.\n",
- read_bytes, *reply_size);
- free(*reply);
- return -2;
- } else if (n == -1) {
- if (errno == EINTR || errno == EAGAIN) continue;
- free(*reply);
- return -1;
- }
-
- read_bytes += n;
- }
-
- return 0;
- }
-
- static int
- read_socket(IPCMessageType *msg_type, uint32_t *msg_size, char **msg)
- {
- int ret = -1;
-
- while (ret != 0) {
- ret = recv_message((uint8_t *)msg_type, msg_size, (uint8_t **)msg);
-
- if (ret < 0) {
- // Try again (non-fatal error)
- if (ret == -1 && (errno == EINTR || errno == EAGAIN)) continue;
-
- fprintf(stderr, "Error receiving response from socket. ");
- fprintf(stderr, "The connection might have been lost.\n");
- exit(2);
- }
- }
-
- return 0;
- }
-
- static ssize_t
- write_socket(const void *buf, size_t count)
- {
- size_t written = 0;
-
- while (written < count) {
- const ssize_t n =
- write(sock_fd, ((uint8_t *)buf) + written, count - written);
-
- if (n == -1) {
- if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
- continue;
- else
- return n;
- }
- written += n;
- }
- return written;
- }
-
- static void
- connect_to_socket()
- {
- struct sockaddr_un addr;
-
- int sock = socket(AF_UNIX, SOCK_STREAM, 0);
-
- // Initialize struct to 0
- memset(&addr, 0, sizeof(struct sockaddr_un));
-
- addr.sun_family = AF_UNIX;
- strcpy(addr.sun_path, DEFAULT_SOCKET_PATH);
-
- connect(sock, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un));
-
- sock_fd = sock;
- }
-
- static int
- send_message(IPCMessageType msg_type, uint32_t msg_size, uint8_t *msg)
- {
- dwm_ipc_header_t header = {
- .magic = IPC_MAGIC_ARR, .size = msg_size, .type = msg_type};
-
- size_t header_size = sizeof(dwm_ipc_header_t);
- size_t total_size = header_size + msg_size;
-
- uint8_t buffer[total_size];
-
- // Copy header to buffer
- memcpy(buffer, &header, header_size);
- // Copy message to buffer
- memcpy(buffer + header_size, msg, header.size);
-
- write_socket(buffer, total_size);
-
- return 0;
- }
-
- static int
- is_float(const char *s)
- {
- size_t len = strlen(s);
- int is_dot_used = 0;
- int is_minus_used = 0;
-
- // Floats can only have one decimal point in between or digits
- // Optionally, floats can also be below zero (negative)
- for (int i = 0; i < len; i++) {
- if (isdigit(s[i]))
- continue;
- else if (!is_dot_used && s[i] == '.' && i != 0 && i != len - 1) {
- is_dot_used = 1;
- continue;
- } else if (!is_minus_used && s[i] == '-' && i == 0) {
- is_minus_used = 1;
- continue;
- } else
- return 0;
- }
-
- return 1;
- }
-
- static int
- is_unsigned_int(const char *s)
- {
- size_t len = strlen(s);
-
- // Unsigned int can only have digits
- for (int i = 0; i < len; i++) {
- if (isdigit(s[i]))
- continue;
- else
- return 0;
- }
-
- return 1;
- }
-
- static int
- is_signed_int(const char *s)
- {
- size_t len = strlen(s);
-
- // Signed int can only have digits and a negative sign at the start
- for (int i = 0; i < len; i++) {
- if (isdigit(s[i]))
- continue;
- else if (i == 0 && s[i] == '-') {
- continue;
- } else
- return 0;
- }
-
- return 1;
- }
-
- static void
- flush_socket_reply()
- {
- IPCMessageType reply_type;
- uint32_t reply_size;
- char *reply;
-
- read_socket(&reply_type, &reply_size, &reply);
-
- free(reply);
- }
-
- static void
- print_socket_reply()
- {
- IPCMessageType reply_type;
- uint32_t reply_size;
- char *reply;
-
- read_socket(&reply_type, &reply_size, &reply);
-
- printf("%.*s\n", reply_size, reply);
- fflush(stdout);
- free(reply);
- }
-
- static int
- run_command(const char *name, char *args[], int argc)
- {
- const unsigned char *msg;
- size_t msg_size;
-
- yajl_gen gen = yajl_gen_alloc(NULL);
-
- // Message format:
- // {
- // "command": "<name>",
- // "args": [ ... ]
- // }
- // clang-format off
- YMAP(
- YSTR("command"); YSTR(name);
- YSTR("args"); YARR(
- for (int i = 0; i < argc; i++) {
- if (is_signed_int(args[i])) {
- long long num = atoll(args[i]);
- YINT(num);
- } else if (is_float(args[i])) {
- float num = atof(args[i]);
- YDOUBLE(num);
- } else {
- YSTR(args[i]);
- }
- }
- )
- )
- // clang-format on
-
- yajl_gen_get_buf(gen, &msg, &msg_size);
-
- send_message(IPC_TYPE_RUN_COMMAND, msg_size, (uint8_t *)msg);
-
- if (!ignore_reply)
- print_socket_reply();
- else
- flush_socket_reply();
-
- yajl_gen_free(gen);
-
- return 0;
- }
-
- static int
- get_monitors()
- {
- send_message(IPC_TYPE_GET_MONITORS, 1, (uint8_t *)"");
- print_socket_reply();
- return 0;
- }
-
- static int
- get_tags()
- {
- send_message(IPC_TYPE_GET_TAGS, 1, (uint8_t *)"");
- print_socket_reply();
-
- return 0;
- }
-
- static int
- get_layouts()
- {
- send_message(IPC_TYPE_GET_LAYOUTS, 1, (uint8_t *)"");
- print_socket_reply();
-
- return 0;
- }
-
- static int
- get_dwm_client(Window win)
- {
- const unsigned char *msg;
- size_t msg_size;
-
- yajl_gen gen = yajl_gen_alloc(NULL);
-
- // Message format:
- // {
- // "client_window_id": "<win>"
- // }
- // clang-format off
- YMAP(
- YSTR("client_window_id"); YINT(win);
- )
- // clang-format on
-
- yajl_gen_get_buf(gen, &msg, &msg_size);
-
- send_message(IPC_TYPE_GET_DWM_CLIENT, msg_size, (uint8_t *)msg);
-
- print_socket_reply();
-
- yajl_gen_free(gen);
-
- return 0;
- }
-
- static int
- subscribe(const char *event)
- {
- const unsigned char *msg;
- size_t msg_size;
-
- yajl_gen gen = yajl_gen_alloc(NULL);
-
- // Message format:
- // {
- // "event": "<event>",
- // "action": "subscribe"
- // }
- // clang-format off
- YMAP(
- YSTR("event"); YSTR(event);
- YSTR("action"); YSTR("subscribe");
- )
- // clang-format on
-
- yajl_gen_get_buf(gen, &msg, &msg_size);
-
- send_message(IPC_TYPE_SUBSCRIBE, msg_size, (uint8_t *)msg);
-
- if (!ignore_reply)
- print_socket_reply();
- else
- flush_socket_reply();
-
- yajl_gen_free(gen);
-
- return 0;
- }
-
- static void
- usage_error(const char *prog_name, const char *format, ...)
- {
- va_list args;
- va_start(args, format);
-
- fprintf(stderr, "Error: ");
- vfprintf(stderr, format, args);
- fprintf(stderr, "\nusage: %s <command> [...]\n", prog_name);
- fprintf(stderr, "Try '%s help'\n", prog_name);
-
- va_end(args);
- exit(1);
- }
-
- static void
- print_usage(const char *name)
- {
- printf("usage: %s [options] <command> [...]\n", name);
- puts("");
- puts("Commands:");
- puts(" run_command <name> [args...] Run an IPC command");
- puts("");
- puts(" get_monitors Get monitor properties");
- puts("");
- puts(" get_tags Get list of tags");
- puts("");
- puts(" get_layouts Get list of layouts");
- puts("");
- puts(" get_dwm_client <window_id> Get dwm client proprties");
- puts("");
- puts(" subscribe [events...] Subscribe to specified events");
- puts(" Options: " IPC_EVENT_TAG_CHANGE ",");
- puts(" " IPC_EVENT_LAYOUT_CHANGE ",");
- puts(" " IPC_EVENT_CLIENT_FOCUS_CHANGE ",");
- puts(" " IPC_EVENT_MONITOR_FOCUS_CHANGE ",");
- puts(" " IPC_EVENT_FOCUSED_TITLE_CHANGE ",");
- puts(" " IPC_EVENT_FOCUSED_STATE_CHANGE);
- puts("");
- puts(" help Display this message");
- puts("");
- puts("Options:");
- puts(" --ignore-reply Don't print reply messages from");
- puts(" run_command and subscribe.");
- puts("");
- }
-
- int
- main(int argc, char *argv[])
- {
- const char *prog_name = argv[0];
-
- connect_to_socket();
- if (sock_fd == -1) {
- fprintf(stderr, "Failed to connect to socket\n");
- return 1;
- }
-
- int i = 1;
- if (i < argc && strcmp(argv[i], "--ignore-reply") == 0) {
- ignore_reply = 1;
- i++;
- }
-
- if (i >= argc) usage_error(prog_name, "Expected an argument, got none");
-
- if (!argc || strcmp(argv[i], "help") == 0)
- print_usage(prog_name);
- else if (strcmp(argv[i], "run_command") == 0) {
- if (++i >= argc) usage_error(prog_name, "No command specified");
- // Command name
- char *command = argv[i];
- // Command arguments are everything after command name
- char **command_args = argv + ++i;
- // Number of command arguments
- int command_argc = argc - i;
- run_command(command, command_args, command_argc);
- } else if (strcmp(argv[i], "get_monitors") == 0) {
- get_monitors();
- } else if (strcmp(argv[i], "get_tags") == 0) {
- get_tags();
- } else if (strcmp(argv[i], "get_layouts") == 0) {
- get_layouts();
- } else if (strcmp(argv[i], "get_dwm_client") == 0) {
- if (++i < argc) {
- if (is_unsigned_int(argv[i])) {
- Window win = atol(argv[i]);
- get_dwm_client(win);
- } else
- usage_error(prog_name, "Expected unsigned integer argument");
- } else
- usage_error(prog_name, "Expected the window id");
- } else if (strcmp(argv[i], "subscribe") == 0) {
- if (++i < argc) {
- for (int j = i; j < argc; j++) subscribe(argv[j]);
- } else
- usage_error(prog_name, "Expected event name");
- // Keep listening for events forever
- while (1) {
- print_socket_reply();
- }
- } else
- usage_error(prog_name, "Invalid argument '%s'", argv[i]);
-
- return 0;
- }
|