Another copy of my dotfiles. Because I don't completely trust GitHub.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

548 lines
13 KiB

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