dwm

Kris's build of dwm
git clone git clone https://git.krisyotam.com/krisyotam/dwm.git
Log | Files | Refs | README | LICENSE

dwm-msg.c (13408B)


      1 #include <ctype.h>
      2 #include <errno.h>
      3 #include <inttypes.h>
      4 #include <stdarg.h>
      5 #include <stdint.h>
      6 #include <stdio.h>
      7 #include <stdlib.h>
      8 #include <string.h>
      9 #include <sys/socket.h>
     10 #include <sys/un.h>
     11 #include <unistd.h>
     12 #include <yajl/yajl_gen.h>
     13 
     14 #define IPC_MAGIC "DWM-IPC"
     15 // clang-format off
     16 #define IPC_MAGIC_ARR { 'D', 'W', 'M', '-', 'I', 'P', 'C' }
     17 // clang-format on
     18 #define IPC_MAGIC_LEN 7  // Not including null char
     19 
     20 #define IPC_EVENT_TAG_CHANGE "tag_change_event"
     21 #define IPC_EVENT_CLIENT_FOCUS_CHANGE "client_focus_change_event"
     22 #define IPC_EVENT_LAYOUT_CHANGE "layout_change_event"
     23 #define IPC_EVENT_MONITOR_FOCUS_CHANGE "monitor_focus_change_event"
     24 #define IPC_EVENT_FOCUSED_TITLE_CHANGE "focused_title_change_event"
     25 #define IPC_EVENT_FOCUSED_STATE_CHANGE "focused_state_change_event"
     26 
     27 #define YSTR(str) yajl_gen_string(gen, (unsigned char *)str, strlen(str))
     28 #define YINT(num) yajl_gen_integer(gen, num)
     29 #define YDOUBLE(num) yajl_gen_double(gen, num)
     30 #define YBOOL(v) yajl_gen_bool(gen, v)
     31 #define YNULL() yajl_gen_null(gen)
     32 #define YARR(body)                                                             \
     33   {                                                                            \
     34     yajl_gen_array_open(gen);                                                  \
     35     body;                                                                      \
     36     yajl_gen_array_close(gen);                                                 \
     37   }
     38 #define YMAP(body)                                                             \
     39   {                                                                            \
     40     yajl_gen_map_open(gen);                                                    \
     41     body;                                                                      \
     42     yajl_gen_map_close(gen);                                                   \
     43   }
     44 
     45 typedef unsigned long Window;
     46 
     47 const char *DEFAULT_SOCKET_PATH = "/tmp/dwm.sock";
     48 static int sock_fd = -1;
     49 static unsigned int ignore_reply = 0;
     50 
     51 typedef enum IPCMessageType {
     52   IPC_TYPE_RUN_COMMAND = 0,
     53   IPC_TYPE_GET_MONITORS = 1,
     54   IPC_TYPE_GET_TAGS = 2,
     55   IPC_TYPE_GET_LAYOUTS = 3,
     56   IPC_TYPE_GET_DWM_CLIENT = 4,
     57   IPC_TYPE_SUBSCRIBE = 5,
     58   IPC_TYPE_EVENT = 6
     59 } IPCMessageType;
     60 
     61 // Every IPC message must begin with this
     62 typedef struct dwm_ipc_header {
     63   uint8_t magic[IPC_MAGIC_LEN];
     64   uint32_t size;
     65   uint8_t type;
     66 } __attribute((packed)) dwm_ipc_header_t;
     67 
     68 static int
     69 recv_message(uint8_t *msg_type, uint32_t *reply_size, uint8_t **reply)
     70 {
     71   uint32_t read_bytes = 0;
     72   const int32_t to_read = sizeof(dwm_ipc_header_t);
     73   char header[to_read];
     74   char *walk = header;
     75 
     76   // Try to read header
     77   while (read_bytes < to_read) {
     78     ssize_t n = read(sock_fd, header + read_bytes, to_read - read_bytes);
     79 
     80     if (n == 0) {
     81       if (read_bytes == 0) {
     82         fprintf(stderr, "Unexpectedly reached EOF while reading header.");
     83         fprintf(stderr,
     84                 "Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n",
     85                 read_bytes, to_read);
     86         return -2;
     87       } else {
     88         fprintf(stderr, "Unexpectedly reached EOF while reading header.");
     89         fprintf(stderr,
     90                 "Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n",
     91                 read_bytes, to_read);
     92         return -3;
     93       }
     94     } else if (n == -1) {
     95       return -1;
     96     }
     97 
     98     read_bytes += n;
     99   }
    100 
    101   // Check if magic string in header matches
    102   if (memcmp(walk, IPC_MAGIC, IPC_MAGIC_LEN) != 0) {
    103     fprintf(stderr, "Invalid magic string. Got '%.*s', expected '%s'\n",
    104             IPC_MAGIC_LEN, walk, IPC_MAGIC);
    105     return -3;
    106   }
    107 
    108   walk += IPC_MAGIC_LEN;
    109 
    110   // Extract reply size
    111   memcpy(reply_size, walk, sizeof(uint32_t));
    112   walk += sizeof(uint32_t);
    113 
    114   // Extract message type
    115   memcpy(msg_type, walk, sizeof(uint8_t));
    116   walk += sizeof(uint8_t);
    117 
    118   (*reply) = malloc(*reply_size);
    119 
    120   // Extract payload
    121   read_bytes = 0;
    122   while (read_bytes < *reply_size) {
    123     ssize_t n = read(sock_fd, *reply + read_bytes, *reply_size - read_bytes);
    124 
    125     if (n == 0) {
    126       fprintf(stderr, "Unexpectedly reached EOF while reading payload.");
    127       fprintf(stderr, "Read %" PRIu32 " bytes, expected %" PRIu32 " bytes.\n",
    128               read_bytes, *reply_size);
    129       free(*reply);
    130       return -2;
    131     } else if (n == -1) {
    132       if (errno == EINTR || errno == EAGAIN) continue;
    133       free(*reply);
    134       return -1;
    135     }
    136 
    137     read_bytes += n;
    138   }
    139 
    140   return 0;
    141 }
    142 
    143 static int
    144 read_socket(IPCMessageType *msg_type, uint32_t *msg_size, char **msg)
    145 {
    146   int ret = -1;
    147 
    148   while (ret != 0) {
    149     ret = recv_message((uint8_t *)msg_type, msg_size, (uint8_t **)msg);
    150 
    151     if (ret < 0) {
    152       // Try again (non-fatal error)
    153       if (ret == -1 && (errno == EINTR || errno == EAGAIN)) continue;
    154 
    155       fprintf(stderr, "Error receiving response from socket. ");
    156       fprintf(stderr, "The connection might have been lost.\n");
    157       exit(2);
    158     }
    159   }
    160 
    161   return 0;
    162 }
    163 
    164 static ssize_t
    165 write_socket(const void *buf, size_t count)
    166 {
    167   size_t written = 0;
    168 
    169   while (written < count) {
    170     const ssize_t n =
    171         write(sock_fd, ((uint8_t *)buf) + written, count - written);
    172 
    173     if (n == -1) {
    174       if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
    175         continue;
    176       else
    177         return n;
    178     }
    179     written += n;
    180   }
    181   return written;
    182 }
    183 
    184 static void
    185 connect_to_socket()
    186 {
    187   struct sockaddr_un addr;
    188 
    189   int sock = socket(AF_UNIX, SOCK_STREAM, 0);
    190 
    191   // Initialize struct to 0
    192   memset(&addr, 0, sizeof(struct sockaddr_un));
    193 
    194   addr.sun_family = AF_UNIX;
    195   strcpy(addr.sun_path, DEFAULT_SOCKET_PATH);
    196 
    197   connect(sock, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un));
    198 
    199   sock_fd = sock;
    200 }
    201 
    202 static int
    203 send_message(IPCMessageType msg_type, uint32_t msg_size, uint8_t *msg)
    204 {
    205   dwm_ipc_header_t header = {
    206       .magic = IPC_MAGIC_ARR, .size = msg_size, .type = msg_type};
    207 
    208   size_t header_size = sizeof(dwm_ipc_header_t);
    209   size_t total_size = header_size + msg_size;
    210 
    211   uint8_t buffer[total_size];
    212 
    213   // Copy header to buffer
    214   memcpy(buffer, &header, header_size);
    215   // Copy message to buffer
    216   memcpy(buffer + header_size, msg, header.size);
    217 
    218   write_socket(buffer, total_size);
    219 
    220   return 0;
    221 }
    222 
    223 static int
    224 is_float(const char *s)
    225 {
    226   size_t len = strlen(s);
    227   int is_dot_used = 0;
    228   int is_minus_used = 0;
    229 
    230   // Floats can only have one decimal point in between or digits
    231   // Optionally, floats can also be below zero (negative)
    232   for (int i = 0; i < len; i++) {
    233     if (isdigit(s[i]))
    234       continue;
    235     else if (!is_dot_used && s[i] == '.' && i != 0 && i != len - 1) {
    236       is_dot_used = 1;
    237       continue;
    238     } else if (!is_minus_used && s[i] == '-' && i == 0) {
    239       is_minus_used = 1;
    240       continue;
    241     } else
    242       return 0;
    243   }
    244 
    245   return 1;
    246 }
    247 
    248 static int
    249 is_unsigned_int(const char *s)
    250 {
    251   size_t len = strlen(s);
    252 
    253   // Unsigned int can only have digits
    254   for (int i = 0; i < len; i++) {
    255     if (isdigit(s[i]))
    256       continue;
    257     else
    258       return 0;
    259   }
    260 
    261   return 1;
    262 }
    263 
    264 static int
    265 is_signed_int(const char *s)
    266 {
    267   size_t len = strlen(s);
    268 
    269   // Signed int can only have digits and a negative sign at the start
    270   for (int i = 0; i < len; i++) {
    271     if (isdigit(s[i]))
    272       continue;
    273     else if (i == 0 && s[i] == '-') {
    274       continue;
    275     } else
    276       return 0;
    277   }
    278 
    279   return 1;
    280 }
    281 
    282 static void
    283 flush_socket_reply()
    284 {
    285   IPCMessageType reply_type;
    286   uint32_t reply_size;
    287   char *reply;
    288 
    289   read_socket(&reply_type, &reply_size, &reply);
    290 
    291   free(reply);
    292 }
    293 
    294 static void
    295 print_socket_reply()
    296 {
    297   IPCMessageType reply_type;
    298   uint32_t reply_size;
    299   char *reply;
    300 
    301   read_socket(&reply_type, &reply_size, &reply);
    302 
    303   printf("%.*s\n", reply_size, reply);
    304   fflush(stdout);
    305   free(reply);
    306 }
    307 
    308 static int
    309 run_command(const char *name, char *args[], int argc)
    310 {
    311   const unsigned char *msg;
    312   size_t msg_size;
    313 
    314   yajl_gen gen = yajl_gen_alloc(NULL);
    315 
    316   // Message format:
    317   // {
    318   //   "command": "<name>",
    319   //   "args": [ ... ]
    320   // }
    321   // clang-format off
    322   YMAP(
    323     YSTR("command"); YSTR(name);
    324     YSTR("args"); YARR(
    325       for (int i = 0; i < argc; i++) {
    326         if (is_signed_int(args[i])) {
    327           long long num = atoll(args[i]);
    328           YINT(num);
    329         } else if (is_float(args[i])) {
    330           float num = atof(args[i]);
    331           YDOUBLE(num);
    332         } else {
    333           YSTR(args[i]);
    334         }
    335       }
    336     )
    337   )
    338   // clang-format on
    339 
    340   yajl_gen_get_buf(gen, &msg, &msg_size);
    341 
    342   send_message(IPC_TYPE_RUN_COMMAND, msg_size, (uint8_t *)msg);
    343 
    344   if (!ignore_reply)
    345     print_socket_reply();
    346   else
    347     flush_socket_reply();
    348 
    349   yajl_gen_free(gen);
    350 
    351   return 0;
    352 }
    353 
    354 static int
    355 get_monitors()
    356 {
    357   send_message(IPC_TYPE_GET_MONITORS, 1, (uint8_t *)"");
    358   print_socket_reply();
    359   return 0;
    360 }
    361 
    362 static int
    363 get_tags()
    364 {
    365   send_message(IPC_TYPE_GET_TAGS, 1, (uint8_t *)"");
    366   print_socket_reply();
    367 
    368   return 0;
    369 }
    370 
    371 static int
    372 get_layouts()
    373 {
    374   send_message(IPC_TYPE_GET_LAYOUTS, 1, (uint8_t *)"");
    375   print_socket_reply();
    376 
    377   return 0;
    378 }
    379 
    380 static int
    381 get_dwm_client(Window win)
    382 {
    383   const unsigned char *msg;
    384   size_t msg_size;
    385 
    386   yajl_gen gen = yajl_gen_alloc(NULL);
    387 
    388   // Message format:
    389   // {
    390   //   "client_window_id": "<win>"
    391   // }
    392   // clang-format off
    393   YMAP(
    394     YSTR("client_window_id"); YINT(win);
    395   )
    396   // clang-format on
    397 
    398   yajl_gen_get_buf(gen, &msg, &msg_size);
    399 
    400   send_message(IPC_TYPE_GET_DWM_CLIENT, msg_size, (uint8_t *)msg);
    401 
    402   print_socket_reply();
    403 
    404   yajl_gen_free(gen);
    405 
    406   return 0;
    407 }
    408 
    409 static int
    410 subscribe(const char *event)
    411 {
    412   const unsigned char *msg;
    413   size_t msg_size;
    414 
    415   yajl_gen gen = yajl_gen_alloc(NULL);
    416 
    417   // Message format:
    418   // {
    419   //   "event": "<event>",
    420   //   "action": "subscribe"
    421   // }
    422   // clang-format off
    423   YMAP(
    424     YSTR("event"); YSTR(event);
    425     YSTR("action"); YSTR("subscribe");
    426   )
    427   // clang-format on
    428 
    429   yajl_gen_get_buf(gen, &msg, &msg_size);
    430 
    431   send_message(IPC_TYPE_SUBSCRIBE, msg_size, (uint8_t *)msg);
    432 
    433   if (!ignore_reply)
    434     print_socket_reply();
    435   else
    436     flush_socket_reply();
    437 
    438   yajl_gen_free(gen);
    439 
    440   return 0;
    441 }
    442 
    443 static void
    444 usage_error(const char *prog_name, const char *format, ...)
    445 {
    446   va_list args;
    447   va_start(args, format);
    448 
    449   fprintf(stderr, "Error: ");
    450   vfprintf(stderr, format, args);
    451   fprintf(stderr, "\nusage: %s <command> [...]\n", prog_name);
    452   fprintf(stderr, "Try '%s help'\n", prog_name);
    453 
    454   va_end(args);
    455   exit(1);
    456 }
    457 
    458 static void
    459 print_usage(const char *name)
    460 {
    461   printf("usage: %s [options] <command> [...]\n", name);
    462   puts("");
    463   puts("Commands:");
    464   puts("  run_command <name> [args...]    Run an IPC command");
    465   puts("");
    466   puts("  get_monitors                    Get monitor properties");
    467   puts("");
    468   puts("  get_tags                        Get list of tags");
    469   puts("");
    470   puts("  get_layouts                     Get list of layouts");
    471   puts("");
    472   puts("  get_dwm_client <window_id>      Get dwm client proprties");
    473   puts("");
    474   puts("  subscribe [events...]           Subscribe to specified events");
    475   puts("                                  Options: " IPC_EVENT_TAG_CHANGE ",");
    476   puts("                                  " IPC_EVENT_LAYOUT_CHANGE ",");
    477   puts("                                  " IPC_EVENT_CLIENT_FOCUS_CHANGE ",");
    478   puts("                                  " IPC_EVENT_MONITOR_FOCUS_CHANGE ",");
    479   puts("                                  " IPC_EVENT_FOCUSED_TITLE_CHANGE ",");
    480   puts("                                  " IPC_EVENT_FOCUSED_STATE_CHANGE);
    481   puts("");
    482   puts("  help                            Display this message");
    483   puts("");
    484   puts("Options:");
    485   puts("  --ignore-reply                  Don't print reply messages from");
    486   puts("                                  run_command and subscribe.");
    487   puts("");
    488 }
    489 
    490 int
    491 main(int argc, char *argv[])
    492 {
    493   const char *prog_name = argv[0];
    494 
    495   connect_to_socket();
    496   if (sock_fd == -1) {
    497     fprintf(stderr, "Failed to connect to socket\n");
    498     return 1;
    499   }
    500 
    501   int i = 1;
    502   if (i < argc && strcmp(argv[i], "--ignore-reply") == 0) {
    503     ignore_reply = 1;
    504     i++;
    505   }
    506 
    507   if (i >= argc) usage_error(prog_name, "Expected an argument, got none");
    508 
    509   if (!argc || strcmp(argv[i], "help") == 0)
    510     print_usage(prog_name);
    511   else if (strcmp(argv[i], "run_command") == 0) {
    512     if (++i >= argc) usage_error(prog_name, "No command specified");
    513     // Command name
    514     char *command = argv[i];
    515     // Command arguments are everything after command name
    516     char **command_args = argv + ++i;
    517     // Number of command arguments
    518     int command_argc = argc - i;
    519     run_command(command, command_args, command_argc);
    520   } else if (strcmp(argv[i], "get_monitors") == 0) {
    521     get_monitors();
    522   } else if (strcmp(argv[i], "get_tags") == 0) {
    523     get_tags();
    524   } else if (strcmp(argv[i], "get_layouts") == 0) {
    525     get_layouts();
    526   } else if (strcmp(argv[i], "get_dwm_client") == 0) {
    527     if (++i < argc) {
    528       if (is_unsigned_int(argv[i])) {
    529         Window win = atol(argv[i]);
    530         get_dwm_client(win);
    531       } else
    532         usage_error(prog_name, "Expected unsigned integer argument");
    533     } else
    534       usage_error(prog_name, "Expected the window id");
    535   } else if (strcmp(argv[i], "subscribe") == 0) {
    536     if (++i < argc) {
    537       for (int j = i; j < argc; j++) subscribe(argv[j]);
    538     } else
    539       usage_error(prog_name, "Expected event name");
    540     // Keep listening for events forever
    541     while (1) {
    542       print_socket_reply();
    543     }
    544   } else
    545     usage_error(prog_name, "Invalid argument '%s'", argv[i]);
    546 
    547   return 0;
    548 }
    549