dwm-ipc-20201106-f04cac6.diff - sites - public wiki contents of suckless.org
(HTM) git clone git://git.suckless.org/sites
(DIR) Log
(DIR) Files
(DIR) Refs
---
dwm-ipc-20201106-f04cac6.diff (90493B)
---
1 From 9c4c16485ac374583a1055ff7c26cba53ac92c05 Mon Sep 17 00:00:00 2001
2 From: mihirlad55 <mihirlad55@gmail.com>
3 Date: Fri, 6 Nov 2020 17:13:42 +0000
4 Subject: [PATCH] Add IPC support through a unix socket
5
6 This patch currently supports the following requests:
7 * Run custom commands with arguments (similar to key bind functions)
8 * Get monitor properties
9 * Get all available layouts
10 * Get available tags
11 * Get client properties
12 * Subscribe to tag change, client focus change, and layout change,
13 monitor focus change, focused title change, and client state change
14 events
15
16 This patch includes a dwm-msg cli program that supports all of the
17 above requests for easy integration into shell scripts.
18
19 The messages are sent in a JSON format to promote integration to
20 increase scriptability in languages like Python/JavaScript.
21
22 The patch requires YAJL for JSON parsing and a system with epoll
23 support. Portability is planned to be increased in the future.
24
25 This patch is best applied after all other patches to avoid merge
26 conflicts.
27
28 For more info on the IPC implementation and how to send/receive
29 messages, documentation can be found at
30 https://github.com/mihirlad55/dwm-ipc
31 ---
32 IPCClient.c | 66 +++
33 IPCClient.h | 61 +++
34 Makefile | 10 +-
35 config.def.h | 18 +
36 config.mk | 8 +-
37 dwm-msg.c | 548 +++++++++++++++++++++++
38 dwm.c | 150 ++++++-
39 ipc.c | 1202 ++++++++++++++++++++++++++++++++++++++++++++++++++
40 ipc.h | 320 ++++++++++++++
41 util.c | 135 ++++++
42 util.h | 10 +
43 yajl_dumps.c | 351 +++++++++++++++
44 yajl_dumps.h | 65 +++
45 13 files changed, 2931 insertions(+), 13 deletions(-)
46 create mode 100644 IPCClient.c
47 create mode 100644 IPCClient.h
48 create mode 100644 dwm-msg.c
49 create mode 100644 ipc.c
50 create mode 100644 ipc.h
51 create mode 100644 yajl_dumps.c
52 create mode 100644 yajl_dumps.h
53
54 diff --git a/IPCClient.c b/IPCClient.c
55 new file mode 100644
56 index 0000000..0d3eefb
57 --- /dev/null
58 +++ b/IPCClient.c
59 @@ -0,0 +1,66 @@
60 +#include "IPCClient.h"
61 +
62 +#include <string.h>
63 +#include <sys/epoll.h>
64 +
65 +#include "util.h"
66 +
67 +IPCClient *
68 +ipc_client_new(int fd)
69 +{
70 + IPCClient *c = (IPCClient *)malloc(sizeof(IPCClient));
71 +
72 + if (c == NULL) return NULL;
73 +
74 + // Initialize struct
75 + memset(&c->event, 0, sizeof(struct epoll_event));
76 +
77 + c->buffer_size = 0;
78 + c->buffer = NULL;
79 + c->fd = fd;
80 + c->event.data.fd = fd;
81 + c->next = NULL;
82 + c->prev = NULL;
83 + c->subscriptions = 0;
84 +
85 + return c;
86 +}
87 +
88 +void
89 +ipc_list_add_client(IPCClientList *list, IPCClient *nc)
90 +{
91 + DEBUG("Adding client with fd %d to list\n", nc->fd);
92 +
93 + if (*list == NULL) {
94 + // List is empty, point list at first client
95 + *list = nc;
96 + } else {
97 + IPCClient *c;
98 + // Go to last client in list
99 + for (c = *list; c && c->next; c = c->next)
100 + ;
101 + c->next = nc;
102 + nc->prev = c;
103 + }
104 +}
105 +
106 +void
107 +ipc_list_remove_client(IPCClientList *list, IPCClient *c)
108 +{
109 + IPCClient *cprev = c->prev;
110 + IPCClient *cnext = c->next;
111 +
112 + if (cprev != NULL) cprev->next = c->next;
113 + if (cnext != NULL) cnext->prev = c->prev;
114 + if (c == *list) *list = c->next;
115 +}
116 +
117 +IPCClient *
118 +ipc_list_get_client(IPCClientList list, int fd)
119 +{
120 + for (IPCClient *c = list; c; c = c->next) {
121 + if (c->fd == fd) return c;
122 + }
123 +
124 + return NULL;
125 +}
126 diff --git a/IPCClient.h b/IPCClient.h
127 new file mode 100644
128 index 0000000..307dfba
129 --- /dev/null
130 +++ b/IPCClient.h
131 @@ -0,0 +1,61 @@
132 +#ifndef IPC_CLIENT_H_
133 +#define IPC_CLIENT_H_
134 +
135 +#include <stdio.h>
136 +#include <stdlib.h>
137 +#include <sys/epoll.h>
138 +
139 +typedef struct IPCClient IPCClient;
140 +/**
141 + * This structure contains the details of an IPC Client and pointers for a
142 + * linked list
143 + */
144 +struct IPCClient {
145 + int fd;
146 + int subscriptions;
147 +
148 + char *buffer;
149 + uint32_t buffer_size;
150 +
151 + struct epoll_event event;
152 + IPCClient *next;
153 + IPCClient *prev;
154 +};
155 +
156 +typedef IPCClient *IPCClientList;
157 +
158 +/**
159 + * Allocate memory for new IPCClient with the specified file descriptor and
160 + * initialize struct.
161 + *
162 + * @param fd File descriptor of IPC client
163 + *
164 + * @return Address to allocated IPCClient struct
165 + */
166 +IPCClient *ipc_client_new(int fd);
167 +
168 +/**
169 + * Add an IPC Client to the specified list
170 + *
171 + * @param list Address of the list to add the client to
172 + * @param nc Address of the IPCClient
173 + */
174 +void ipc_list_add_client(IPCClientList *list, IPCClient *nc);
175 +
176 +/**
177 + * Remove an IPCClient from the specified list
178 + *
179 + * @param list Address of the list to remove the client from
180 + * @param c Address of the IPCClient
181 + */
182 +void ipc_list_remove_client(IPCClientList *list, IPCClient *c);
183 +
184 +/**
185 + * Get an IPCClient from the specified IPCClient list
186 + *
187 + * @param list List to remove the client from
188 + * @param fd File descriptor of the IPCClient
189 + */
190 +IPCClient *ipc_list_get_client(IPCClientList list, int fd);
191 +
192 +#endif // IPC_CLIENT_H_
193 diff --git a/Makefile b/Makefile
194 index 77bcbc0..0456754 100644
195 --- a/Makefile
196 +++ b/Makefile
197 @@ -6,7 +6,7 @@ include config.mk
198 SRC = drw.c dwm.c util.c
199 OBJ = ${SRC:.c=.o}
200
201 -all: options dwm
202 +all: options dwm dwm-msg
203
204 options:
205 @echo dwm build options:
206 @@ -25,8 +25,11 @@ config.h:
207 dwm: ${OBJ}
208 ${CC} -o $@ ${OBJ} ${LDFLAGS}
209
210 +dwm-msg: dwm-msg.o
211 + ${CC} -o $@ $< ${LDFLAGS}
212 +
213 clean:
214 - rm -f dwm ${OBJ} dwm-${VERSION}.tar.gz
215 + rm -f dwm dwm-msg ${OBJ} dwm-${VERSION}.tar.gz
216
217 dist: clean
218 mkdir -p dwm-${VERSION}
219 @@ -38,8 +41,9 @@ dist: clean
220
221 install: all
222 mkdir -p ${DESTDIR}${PREFIX}/bin
223 - cp -f dwm ${DESTDIR}${PREFIX}/bin
224 + cp -f dwm dwm-msg ${DESTDIR}${PREFIX}/bin
225 chmod 755 ${DESTDIR}${PREFIX}/bin/dwm
226 + chmod 755 ${DESTDIR}${PREFIX}/bin/dwm-msg
227 mkdir -p ${DESTDIR}${MANPREFIX}/man1
228 sed "s/VERSION/${VERSION}/g" < dwm.1 > ${DESTDIR}${MANPREFIX}/man1/dwm.1
229 chmod 644 ${DESTDIR}${MANPREFIX}/man1/dwm.1
230 diff --git a/config.def.h b/config.def.h
231 index 1c0b587..059a831 100644
232 --- a/config.def.h
233 +++ b/config.def.h
234 @@ -113,3 +113,21 @@ static Button buttons[] = {
235 { ClkTagBar, MODKEY, Button3, toggletag, {0} },
236 };
237
238 +static const char *ipcsockpath = "/tmp/dwm.sock";
239 +static IPCCommand ipccommands[] = {
240 + IPCCOMMAND( view, 1, {ARG_TYPE_UINT} ),
241 + IPCCOMMAND( toggleview, 1, {ARG_TYPE_UINT} ),
242 + IPCCOMMAND( tag, 1, {ARG_TYPE_UINT} ),
243 + IPCCOMMAND( toggletag, 1, {ARG_TYPE_UINT} ),
244 + IPCCOMMAND( tagmon, 1, {ARG_TYPE_UINT} ),
245 + IPCCOMMAND( focusmon, 1, {ARG_TYPE_SINT} ),
246 + IPCCOMMAND( focusstack, 1, {ARG_TYPE_SINT} ),
247 + IPCCOMMAND( zoom, 1, {ARG_TYPE_NONE} ),
248 + IPCCOMMAND( incnmaster, 1, {ARG_TYPE_SINT} ),
249 + IPCCOMMAND( killclient, 1, {ARG_TYPE_SINT} ),
250 + IPCCOMMAND( togglefloating, 1, {ARG_TYPE_NONE} ),
251 + IPCCOMMAND( setmfact, 1, {ARG_TYPE_FLOAT} ),
252 + IPCCOMMAND( setlayoutsafe, 1, {ARG_TYPE_PTR} ),
253 + IPCCOMMAND( quit, 1, {ARG_TYPE_NONE} )
254 +};
255 +
256 diff --git a/config.mk b/config.mk
257 index 7084c33..8570938 100644
258 --- a/config.mk
259 +++ b/config.mk
260 @@ -20,9 +20,13 @@ FREETYPEINC = /usr/include/freetype2
261 # OpenBSD (uncomment)
262 #FREETYPEINC = ${X11INC}/freetype2
263
264 +# yajl
265 +YAJLLIBS = -lyajl
266 +YAJLINC = /usr/include/yajl
267 +
268 # includes and libs
269 -INCS = -I${X11INC} -I${FREETYPEINC}
270 -LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS}
271 +INCS = -I${X11INC} -I${FREETYPEINC} -I${YAJLINC}
272 +LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} ${YAJLLIBS}
273
274 # flags
275 CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
276 diff --git a/dwm-msg.c b/dwm-msg.c
277 new file mode 100644
278 index 0000000..1971d32
279 --- /dev/null
280 +++ b/dwm-msg.c
281 @@ -0,0 +1,548 @@
282 +#include <ctype.h>
283 +#include <errno.h>
284 +#include <inttypes.h>
285 +#include <stdarg.h>
286 +#include <stdint.h>
287 +#include <stdio.h>
288 +#include <stdlib.h>
289 +#include <string.h>
290 +#include <sys/socket.h>
291 +#include <sys/un.h>
292 +#include <unistd.h>
293 +#include <yajl/yajl_gen.h>
294 +
295 +#define IPC_MAGIC "DWM-IPC"
296 +// clang-format off
297 +#define IPC_MAGIC_ARR { 'D', 'W', 'M', '-', 'I', 'P', 'C' }
298 +// clang-format on
299 +#define IPC_MAGIC_LEN 7 // Not including null char
300 +
301 +#define IPC_EVENT_TAG_CHANGE "tag_change_event"
302 +#define IPC_EVENT_CLIENT_FOCUS_CHANGE "client_focus_change_event"
303 +#define IPC_EVENT_LAYOUT_CHANGE "layout_change_event"
304 +#define IPC_EVENT_MONITOR_FOCUS_CHANGE "monitor_focus_change_event"
305 +#define IPC_EVENT_FOCUSED_TITLE_CHANGE "focused_title_change_event"
306 +#define IPC_EVENT_FOCUSED_STATE_CHANGE "focused_state_change_event"
307 +
308 +#define YSTR(str) yajl_gen_string(gen, (unsigned char *)str, strlen(str))
309 +#define YINT(num) yajl_gen_integer(gen, num)
310 +#define YDOUBLE(num) yajl_gen_double(gen, num)
311 +#define YBOOL(v) yajl_gen_bool(gen, v)
312 +#define YNULL() yajl_gen_null(gen)
313 +#define YARR(body) \
314 + { \
315 + yajl_gen_array_open(gen); \
316 + body; \
317 + yajl_gen_array_close(gen); \
318 + }
319 +#define YMAP(body) \
320 + { \
321 + yajl_gen_map_open(gen); \
322 + body; \
323 + yajl_gen_map_close(gen); \
324 + }
325 +
326 +typedef unsigned long Window;
327 +
328 +const char *DEFAULT_SOCKET_PATH = "/tmp/dwm.sock";
329 +static int sock_fd = -1;
330 +static unsigned int ignore_reply = 0;
331 +
332 +typedef enum IPCMessageType {
333 + IPC_TYPE_RUN_COMMAND = 0,
334 + IPC_TYPE_GET_MONITORS = 1,
335 + IPC_TYPE_GET_TAGS = 2,
336 + IPC_TYPE_GET_LAYOUTS = 3,
337 + IPC_TYPE_GET_DWM_CLIENT = 4,
338 + IPC_TYPE_SUBSCRIBE = 5,
339 + IPC_TYPE_EVENT = 6
340 +} IPCMessageType;
341 +
342 +// Every IPC message must begin with this
343 +typedef struct dwm_ipc_header {
344 + uint8_t magic[IPC_MAGIC_LEN];
345 + uint32_t size;
346 + uint8_t type;
347 +} __attribute((packed)) dwm_ipc_header_t;
348 +
349 +static int
350 +recv_message(uint8_t *msg_type, uint32_t *reply_size, uint8_t **reply)
351 +{
352 + uint32_t read_bytes = 0;
353 + const int32_t to_read = sizeof(dwm_ipc_header_t);
354 + char header[to_read];
355 + char *walk = header;
356 +
357 + // Try to read header
358 + while (read_bytes < to_read) {
359 + ssize_t n = read(sock_fd, header + read_bytes, to_read - read_bytes);
360 +
361 + if (n == 0) {
362 + if (read_bytes == 0) {
363 + fprintf(stderr, "Unexpectedly reached EOF while reading header.");
364 + fprintf(stderr,
365 + "Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n",
366 + read_bytes, to_read);
367 + return -2;
368 + } else {
369 + fprintf(stderr, "Unexpectedly reached EOF while reading header.");
370 + fprintf(stderr,
371 + "Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n",
372 + read_bytes, to_read);
373 + return -3;
374 + }
375 + } else if (n == -1) {
376 + return -1;
377 + }
378 +
379 + read_bytes += n;
380 + }
381 +
382 + // Check if magic string in header matches
383 + if (memcmp(walk, IPC_MAGIC, IPC_MAGIC_LEN) != 0) {
384 + fprintf(stderr, "Invalid magic string. Got '%.*s', expected '%s'\n",
385 + IPC_MAGIC_LEN, walk, IPC_MAGIC);
386 + return -3;
387 + }
388 +
389 + walk += IPC_MAGIC_LEN;
390 +
391 + // Extract reply size
392 + memcpy(reply_size, walk, sizeof(uint32_t));
393 + walk += sizeof(uint32_t);
394 +
395 + // Extract message type
396 + memcpy(msg_type, walk, sizeof(uint8_t));
397 + walk += sizeof(uint8_t);
398 +
399 + (*reply) = malloc(*reply_size);
400 +
401 + // Extract payload
402 + read_bytes = 0;
403 + while (read_bytes < *reply_size) {
404 + ssize_t n = read(sock_fd, *reply + read_bytes, *reply_size - read_bytes);
405 +
406 + if (n == 0) {
407 + fprintf(stderr, "Unexpectedly reached EOF while reading payload.");
408 + fprintf(stderr, "Read %" PRIu32 " bytes, expected %" PRIu32 " bytes.\n",
409 + read_bytes, *reply_size);
410 + free(*reply);
411 + return -2;
412 + } else if (n == -1) {
413 + if (errno == EINTR || errno == EAGAIN) continue;
414 + free(*reply);
415 + return -1;
416 + }
417 +
418 + read_bytes += n;
419 + }
420 +
421 + return 0;
422 +}
423 +
424 +static int
425 +read_socket(IPCMessageType *msg_type, uint32_t *msg_size, char **msg)
426 +{
427 + int ret = -1;
428 +
429 + while (ret != 0) {
430 + ret = recv_message((uint8_t *)msg_type, msg_size, (uint8_t **)msg);
431 +
432 + if (ret < 0) {
433 + // Try again (non-fatal error)
434 + if (ret == -1 && (errno == EINTR || errno == EAGAIN)) continue;
435 +
436 + fprintf(stderr, "Error receiving response from socket. ");
437 + fprintf(stderr, "The connection might have been lost.\n");
438 + exit(2);
439 + }
440 + }
441 +
442 + return 0;
443 +}
444 +
445 +static ssize_t
446 +write_socket(const void *buf, size_t count)
447 +{
448 + size_t written = 0;
449 +
450 + while (written < count) {
451 + const ssize_t n =
452 + write(sock_fd, ((uint8_t *)buf) + written, count - written);
453 +
454 + if (n == -1) {
455 + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
456 + continue;
457 + else
458 + return n;
459 + }
460 + written += n;
461 + }
462 + return written;
463 +}
464 +
465 +static void
466 +connect_to_socket()
467 +{
468 + struct sockaddr_un addr;
469 +
470 + int sock = socket(AF_UNIX, SOCK_STREAM, 0);
471 +
472 + // Initialize struct to 0
473 + memset(&addr, 0, sizeof(struct sockaddr_un));
474 +
475 + addr.sun_family = AF_UNIX;
476 + strcpy(addr.sun_path, DEFAULT_SOCKET_PATH);
477 +
478 + connect(sock, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un));
479 +
480 + sock_fd = sock;
481 +}
482 +
483 +static int
484 +send_message(IPCMessageType msg_type, uint32_t msg_size, uint8_t *msg)
485 +{
486 + dwm_ipc_header_t header = {
487 + .magic = IPC_MAGIC_ARR, .size = msg_size, .type = msg_type};
488 +
489 + size_t header_size = sizeof(dwm_ipc_header_t);
490 + size_t total_size = header_size + msg_size;
491 +
492 + uint8_t buffer[total_size];
493 +
494 + // Copy header to buffer
495 + memcpy(buffer, &header, header_size);
496 + // Copy message to buffer
497 + memcpy(buffer + header_size, msg, header.size);
498 +
499 + write_socket(buffer, total_size);
500 +
501 + return 0;
502 +}
503 +
504 +static int
505 +is_float(const char *s)
506 +{
507 + size_t len = strlen(s);
508 + int is_dot_used = 0;
509 + int is_minus_used = 0;
510 +
511 + // Floats can only have one decimal point in between or digits
512 + // Optionally, floats can also be below zero (negative)
513 + for (int i = 0; i < len; i++) {
514 + if (isdigit(s[i]))
515 + continue;
516 + else if (!is_dot_used && s[i] == '.' && i != 0 && i != len - 1) {
517 + is_dot_used = 1;
518 + continue;
519 + } else if (!is_minus_used && s[i] == '-' && i == 0) {
520 + is_minus_used = 1;
521 + continue;
522 + } else
523 + return 0;
524 + }
525 +
526 + return 1;
527 +}
528 +
529 +static int
530 +is_unsigned_int(const char *s)
531 +{
532 + size_t len = strlen(s);
533 +
534 + // Unsigned int can only have digits
535 + for (int i = 0; i < len; i++) {
536 + if (isdigit(s[i]))
537 + continue;
538 + else
539 + return 0;
540 + }
541 +
542 + return 1;
543 +}
544 +
545 +static int
546 +is_signed_int(const char *s)
547 +{
548 + size_t len = strlen(s);
549 +
550 + // Signed int can only have digits and a negative sign at the start
551 + for (int i = 0; i < len; i++) {
552 + if (isdigit(s[i]))
553 + continue;
554 + else if (i == 0 && s[i] == '-') {
555 + continue;
556 + } else
557 + return 0;
558 + }
559 +
560 + return 1;
561 +}
562 +
563 +static void
564 +flush_socket_reply()
565 +{
566 + IPCMessageType reply_type;
567 + uint32_t reply_size;
568 + char *reply;
569 +
570 + read_socket(&reply_type, &reply_size, &reply);
571 +
572 + free(reply);
573 +}
574 +
575 +static void
576 +print_socket_reply()
577 +{
578 + IPCMessageType reply_type;
579 + uint32_t reply_size;
580 + char *reply;
581 +
582 + read_socket(&reply_type, &reply_size, &reply);
583 +
584 + printf("%.*s\n", reply_size, reply);
585 + fflush(stdout);
586 + free(reply);
587 +}
588 +
589 +static int
590 +run_command(const char *name, char *args[], int argc)
591 +{
592 + const unsigned char *msg;
593 + size_t msg_size;
594 +
595 + yajl_gen gen = yajl_gen_alloc(NULL);
596 +
597 + // Message format:
598 + // {
599 + // "command": "<name>",
600 + // "args": [ ... ]
601 + // }
602 + // clang-format off
603 + YMAP(
604 + YSTR("command"); YSTR(name);
605 + YSTR("args"); YARR(
606 + for (int i = 0; i < argc; i++) {
607 + if (is_signed_int(args[i])) {
608 + long long num = atoll(args[i]);
609 + YINT(num);
610 + } else if (is_float(args[i])) {
611 + float num = atof(args[i]);
612 + YDOUBLE(num);
613 + } else {
614 + YSTR(args[i]);
615 + }
616 + }
617 + )
618 + )
619 + // clang-format on
620 +
621 + yajl_gen_get_buf(gen, &msg, &msg_size);
622 +
623 + send_message(IPC_TYPE_RUN_COMMAND, msg_size, (uint8_t *)msg);
624 +
625 + if (!ignore_reply)
626 + print_socket_reply();
627 + else
628 + flush_socket_reply();
629 +
630 + yajl_gen_free(gen);
631 +
632 + return 0;
633 +}
634 +
635 +static int
636 +get_monitors()
637 +{
638 + send_message(IPC_TYPE_GET_MONITORS, 1, (uint8_t *)"");
639 + print_socket_reply();
640 + return 0;
641 +}
642 +
643 +static int
644 +get_tags()
645 +{
646 + send_message(IPC_TYPE_GET_TAGS, 1, (uint8_t *)"");
647 + print_socket_reply();
648 +
649 + return 0;
650 +}
651 +
652 +static int
653 +get_layouts()
654 +{
655 + send_message(IPC_TYPE_GET_LAYOUTS, 1, (uint8_t *)"");
656 + print_socket_reply();
657 +
658 + return 0;
659 +}
660 +
661 +static int
662 +get_dwm_client(Window win)
663 +{
664 + const unsigned char *msg;
665 + size_t msg_size;
666 +
667 + yajl_gen gen = yajl_gen_alloc(NULL);
668 +
669 + // Message format:
670 + // {
671 + // "client_window_id": "<win>"
672 + // }
673 + // clang-format off
674 + YMAP(
675 + YSTR("client_window_id"); YINT(win);
676 + )
677 + // clang-format on
678 +
679 + yajl_gen_get_buf(gen, &msg, &msg_size);
680 +
681 + send_message(IPC_TYPE_GET_DWM_CLIENT, msg_size, (uint8_t *)msg);
682 +
683 + print_socket_reply();
684 +
685 + yajl_gen_free(gen);
686 +
687 + return 0;
688 +}
689 +
690 +static int
691 +subscribe(const char *event)
692 +{
693 + const unsigned char *msg;
694 + size_t msg_size;
695 +
696 + yajl_gen gen = yajl_gen_alloc(NULL);
697 +
698 + // Message format:
699 + // {
700 + // "event": "<event>",
701 + // "action": "subscribe"
702 + // }
703 + // clang-format off
704 + YMAP(
705 + YSTR("event"); YSTR(event);
706 + YSTR("action"); YSTR("subscribe");
707 + )
708 + // clang-format on
709 +
710 + yajl_gen_get_buf(gen, &msg, &msg_size);
711 +
712 + send_message(IPC_TYPE_SUBSCRIBE, msg_size, (uint8_t *)msg);
713 +
714 + if (!ignore_reply)
715 + print_socket_reply();
716 + else
717 + flush_socket_reply();
718 +
719 + yajl_gen_free(gen);
720 +
721 + return 0;
722 +}
723 +
724 +static void
725 +usage_error(const char *prog_name, const char *format, ...)
726 +{
727 + va_list args;
728 + va_start(args, format);
729 +
730 + fprintf(stderr, "Error: ");
731 + vfprintf(stderr, format, args);
732 + fprintf(stderr, "\nusage: %s <command> [...]\n", prog_name);
733 + fprintf(stderr, "Try '%s help'\n", prog_name);
734 +
735 + va_end(args);
736 + exit(1);
737 +}
738 +
739 +static void
740 +print_usage(const char *name)
741 +{
742 + printf("usage: %s [options] <command> [...]\n", name);
743 + puts("");
744 + puts("Commands:");
745 + puts(" run_command <name> [args...] Run an IPC command");
746 + puts("");
747 + puts(" get_monitors Get monitor properties");
748 + puts("");
749 + puts(" get_tags Get list of tags");
750 + puts("");
751 + puts(" get_layouts Get list of layouts");
752 + puts("");
753 + puts(" get_dwm_client <window_id> Get dwm client proprties");
754 + puts("");
755 + puts(" subscribe [events...] Subscribe to specified events");
756 + puts(" Options: " IPC_EVENT_TAG_CHANGE ",");
757 + puts(" " IPC_EVENT_LAYOUT_CHANGE ",");
758 + puts(" " IPC_EVENT_CLIENT_FOCUS_CHANGE ",");
759 + puts(" " IPC_EVENT_MONITOR_FOCUS_CHANGE ",");
760 + puts(" " IPC_EVENT_FOCUSED_TITLE_CHANGE ",");
761 + puts(" " IPC_EVENT_FOCUSED_STATE_CHANGE);
762 + puts("");
763 + puts(" help Display this message");
764 + puts("");
765 + puts("Options:");
766 + puts(" --ignore-reply Don't print reply messages from");
767 + puts(" run_command and subscribe.");
768 + puts("");
769 +}
770 +
771 +int
772 +main(int argc, char *argv[])
773 +{
774 + const char *prog_name = argv[0];
775 +
776 + connect_to_socket();
777 + if (sock_fd == -1) {
778 + fprintf(stderr, "Failed to connect to socket\n");
779 + return 1;
780 + }
781 +
782 + int i = 1;
783 + if (i < argc && strcmp(argv[i], "--ignore-reply") == 0) {
784 + ignore_reply = 1;
785 + i++;
786 + }
787 +
788 + if (i >= argc) usage_error(prog_name, "Expected an argument, got none");
789 +
790 + if (strcmp(argv[i], "help") == 0)
791 + print_usage(prog_name);
792 + else if (strcmp(argv[i], "run_command") == 0) {
793 + if (++i >= argc) usage_error(prog_name, "No command specified");
794 + // Command name
795 + char *command = argv[i];
796 + // Command arguments are everything after command name
797 + char **command_args = argv + ++i;
798 + // Number of command arguments
799 + int command_argc = argc - i;
800 + run_command(command, command_args, command_argc);
801 + } else if (strcmp(argv[i], "get_monitors") == 0) {
802 + get_monitors();
803 + } else if (strcmp(argv[i], "get_tags") == 0) {
804 + get_tags();
805 + } else if (strcmp(argv[i], "get_layouts") == 0) {
806 + get_layouts();
807 + } else if (strcmp(argv[i], "get_dwm_client") == 0) {
808 + if (++i < argc) {
809 + if (is_unsigned_int(argv[i])) {
810 + Window win = atol(argv[i]);
811 + get_dwm_client(win);
812 + } else
813 + usage_error(prog_name, "Expected unsigned integer argument");
814 + } else
815 + usage_error(prog_name, "Expected the window id");
816 + } else if (strcmp(argv[i], "subscribe") == 0) {
817 + if (++i < argc) {
818 + for (int j = i; j < argc; j++) subscribe(argv[j]);
819 + } else
820 + usage_error(prog_name, "Expected event name");
821 + // Keep listening for events forever
822 + while (1) {
823 + print_socket_reply();
824 + }
825 + } else
826 + usage_error(prog_name, "Invalid argument '%s'", argv[i]);
827 +
828 + return 0;
829 +}
830 diff --git a/dwm.c b/dwm.c
831 index 9fd0286..c90c61a 100644
832 --- a/dwm.c
833 +++ b/dwm.c
834 @@ -30,6 +30,7 @@
835 #include <unistd.h>
836 #include <sys/types.h>
837 #include <sys/wait.h>
838 +#include <sys/epoll.h>
839 #include <X11/cursorfont.h>
840 #include <X11/keysym.h>
841 #include <X11/Xatom.h>
842 @@ -67,9 +68,21 @@ enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms *
843 enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle,
844 ClkClientWin, ClkRootWin, ClkLast }; /* clicks */
845
846 +typedef struct TagState TagState;
847 +struct TagState {
848 + int selected;
849 + int occupied;
850 + int urgent;
851 +};
852 +
853 +typedef struct ClientState ClientState;
854 +struct ClientState {
855 + int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen;
856 +};
857 +
858 typedef union {
859 - int i;
860 - unsigned int ui;
861 + long i;
862 + unsigned long ui;
863 float f;
864 const void *v;
865 } Arg;
866 @@ -97,6 +110,7 @@ struct Client {
867 Client *snext;
868 Monitor *mon;
869 Window win;
870 + ClientState prevstate;
871 };
872
873 typedef struct {
874 @@ -111,8 +125,10 @@ typedef struct {
875 void (*arrange)(Monitor *);
876 } Layout;
877
878 +
879 struct Monitor {
880 char ltsymbol[16];
881 + char lastltsymbol[16];
882 float mfact;
883 int nmaster;
884 int num;
885 @@ -122,14 +138,17 @@ struct Monitor {
886 unsigned int seltags;
887 unsigned int sellt;
888 unsigned int tagset[2];
889 + TagState tagstate;
890 int showbar;
891 int topbar;
892 Client *clients;
893 Client *sel;
894 + Client *lastsel;
895 Client *stack;
896 Monitor *next;
897 Window barwin;
898 const Layout *lt[2];
899 + const Layout *lastlt;
900 };
901
902 typedef struct {
903 @@ -175,6 +194,7 @@ static long getstate(Window w);
904 static int gettextprop(Window w, Atom atom, char *text, unsigned int size);
905 static void grabbuttons(Client *c, int focused);
906 static void grabkeys(void);
907 +static int handlexevent(struct epoll_event *ev);
908 static void incnmaster(const Arg *arg);
909 static void keypress(XEvent *e);
910 static void killclient(const Arg *arg);
911 @@ -201,8 +221,10 @@ static void setclientstate(Client *c, long state);
912 static void setfocus(Client *c);
913 static void setfullscreen(Client *c, int fullscreen);
914 static void setlayout(const Arg *arg);
915 +static void setlayoutsafe(const Arg *arg);
916 static void setmfact(const Arg *arg);
917 static void setup(void);
918 +static void setupepoll(void);
919 static void seturgent(Client *c, int urg);
920 static void showhide(Client *c);
921 static void sigchld(int unused);
922 @@ -261,17 +283,27 @@ static void (*handler[LASTEvent]) (XEvent *) = {
923 [UnmapNotify] = unmapnotify
924 };
925 static Atom wmatom[WMLast], netatom[NetLast];
926 +static int epoll_fd;
927 +static int dpy_fd;
928 static int running = 1;
929 static Cur *cursor[CurLast];
930 static Clr **scheme;
931 static Display *dpy;
932 static Drw *drw;
933 -static Monitor *mons, *selmon;
934 +static Monitor *mons, *selmon, *lastselmon;
935 static Window root, wmcheckwin;
936
937 +#include "ipc.h"
938 +
939 /* configuration, allows nested code to access above variables */
940 #include "config.h"
941
942 +#ifdef VERSION
943 +#include "IPCClient.c"
944 +#include "yajl_dumps.c"
945 +#include "ipc.c"
946 +#endif
947 +
948 /* compile-time check if all tags fit into an unsigned int bit array. */
949 struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; };
950
951 @@ -492,6 +524,12 @@ cleanup(void)
952 XSync(dpy, False);
953 XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
954 XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
955 +
956 + ipc_cleanup();
957 +
958 + if (close(epoll_fd) < 0) {
959 + fprintf(stderr, "Failed to close epoll file descriptor\n");
960 + }
961 }
962
963 void
964 @@ -964,6 +1002,25 @@ grabkeys(void)
965 }
966 }
967
968 +int
969 +handlexevent(struct epoll_event *ev)
970 +{
971 + if (ev->events & EPOLLIN) {
972 + XEvent ev;
973 + while (running && XPending(dpy)) {
974 + XNextEvent(dpy, &ev);
975 + if (handler[ev.type]) {
976 + handler[ev.type](&ev); /* call handler */
977 + ipc_send_events(mons, &lastselmon, selmon);
978 + }
979 + }
980 + } else if (ev-> events & EPOLLHUP) {
981 + return -1;
982 + }
983 +
984 + return 0;
985 +}
986 +
987 void
988 incnmaster(const Arg *arg)
989 {
990 @@ -1373,12 +1430,40 @@ restack(Monitor *m)
991 void
992 run(void)
993 {
994 - XEvent ev;
995 - /* main event loop */
996 + int event_count = 0;
997 + const int MAX_EVENTS = 10;
998 + struct epoll_event events[MAX_EVENTS];
999 +
1000 XSync(dpy, False);
1001 - while (running && !XNextEvent(dpy, &ev))
1002 - if (handler[ev.type])
1003 - handler[ev.type](&ev); /* call handler */
1004 +
1005 + /* main event loop */
1006 + while (running) {
1007 + event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
1008 +
1009 + for (int i = 0; i < event_count; i++) {
1010 + int event_fd = events[i].data.fd;
1011 + DEBUG("Got event from fd %d\n", event_fd);
1012 +
1013 + if (event_fd == dpy_fd) {
1014 + // -1 means EPOLLHUP
1015 + if (handlexevent(events + i) == -1)
1016 + return;
1017 + } else if (event_fd == ipc_get_sock_fd()) {
1018 + ipc_handle_socket_epoll_event(events + i);
1019 + } else if (ipc_is_client_registered(event_fd)){
1020 + if (ipc_handle_client_epoll_event(events + i, mons, &lastselmon, selmon,
1021 + tags, LENGTH(tags), layouts, LENGTH(layouts)) < 0) {
1022 + fprintf(stderr, "Error handling IPC event on fd %d\n", event_fd);
1023 + }
1024 + } else {
1025 + fprintf(stderr, "Got event from unknown fd %d, ptr %p, u32 %d, u64 %lu",
1026 + event_fd, events[i].data.ptr, events[i].data.u32,
1027 + events[i].data.u64);
1028 + fprintf(stderr, " with events %d\n", events[i].events);
1029 + return;
1030 + }
1031 + }
1032 + }
1033 }
1034
1035 void
1036 @@ -1512,6 +1597,18 @@ setlayout(const Arg *arg)
1037 drawbar(selmon);
1038 }
1039
1040 +void
1041 +setlayoutsafe(const Arg *arg)
1042 +{
1043 + const Layout *ltptr = (Layout *)arg->v;
1044 + if (ltptr == 0)
1045 + setlayout(arg);
1046 + for (int i = 0; i < LENGTH(layouts); i++) {
1047 + if (ltptr == &layouts[i])
1048 + setlayout(arg);
1049 + }
1050 +}
1051 +
1052 /* arg > 1.0 will set mfact absolutely */
1053 void
1054 setmfact(const Arg *arg)
1055 @@ -1595,8 +1692,37 @@ setup(void)
1056 XSelectInput(dpy, root, wa.event_mask);
1057 grabkeys();
1058 focus(NULL);
1059 + setupepoll();
1060 }
1061
1062 +void
1063 +setupepoll(void)
1064 +{
1065 + epoll_fd = epoll_create1(0);
1066 + dpy_fd = ConnectionNumber(dpy);
1067 + struct epoll_event dpy_event;
1068 +
1069 + // Initialize struct to 0
1070 + memset(&dpy_event, 0, sizeof(dpy_event));
1071 +
1072 + DEBUG("Display socket is fd %d\n", dpy_fd);
1073 +
1074 + if (epoll_fd == -1) {
1075 + fputs("Failed to create epoll file descriptor", stderr);
1076 + }
1077 +
1078 + dpy_event.events = EPOLLIN;
1079 + dpy_event.data.fd = dpy_fd;
1080 + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, dpy_fd, &dpy_event)) {
1081 + fputs("Failed to add display file descriptor to epoll", stderr);
1082 + close(epoll_fd);
1083 + exit(1);
1084 + }
1085 +
1086 + if (ipc_init(ipcsockpath, epoll_fd, ipccommands, LENGTH(ipccommands)) < 0) {
1087 + fputs("Failed to initialize IPC\n", stderr);
1088 + }
1089 +}
1090
1091 void
1092 seturgent(Client *c, int urg)
1093 @@ -1998,10 +2124,18 @@ updatestatus(void)
1094 void
1095 updatetitle(Client *c)
1096 {
1097 + char oldname[sizeof(c->name)];
1098 + strcpy(oldname, c->name);
1099 +
1100 if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name))
1101 gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name);
1102 if (c->name[0] == '\0') /* hack to mark broken clients */
1103 strcpy(c->name, broken);
1104 +
1105 + for (Monitor *m = mons; m; m = m->next) {
1106 + if (m->sel == c && strcmp(oldname, c->name) != 0)
1107 + ipc_focused_title_change_event(m->num, c->win, oldname, c->name);
1108 + }
1109 }
1110
1111 void
1112 diff --git a/ipc.c b/ipc.c
1113 new file mode 100644
1114 index 0000000..c404791
1115 --- /dev/null
1116 +++ b/ipc.c
1117 @@ -0,0 +1,1202 @@
1118 +#include "ipc.h"
1119 +
1120 +#include <errno.h>
1121 +#include <fcntl.h>
1122 +#include <inttypes.h>
1123 +#include <stdarg.h>
1124 +#include <stdio.h>
1125 +#include <stdlib.h>
1126 +#include <sys/epoll.h>
1127 +#include <sys/socket.h>
1128 +#include <sys/un.h>
1129 +#include <unistd.h>
1130 +#include <yajl/yajl_gen.h>
1131 +#include <yajl/yajl_tree.h>
1132 +
1133 +#include "util.h"
1134 +#include "yajl_dumps.h"
1135 +
1136 +static struct sockaddr_un sockaddr;
1137 +static struct epoll_event sock_epoll_event;
1138 +static IPCClientList ipc_clients = NULL;
1139 +static int epoll_fd = -1;
1140 +static int sock_fd = -1;
1141 +static IPCCommand *ipc_commands;
1142 +static unsigned int ipc_commands_len;
1143 +// Max size is 1 MB
1144 +static const uint32_t MAX_MESSAGE_SIZE = 1000000;
1145 +static const int IPC_SOCKET_BACKLOG = 5;
1146 +
1147 +/**
1148 + * Create IPC socket at specified path and return file descriptor to socket.
1149 + * This initializes the static variable sockaddr.
1150 + */
1151 +static int
1152 +ipc_create_socket(const char *filename)
1153 +{
1154 + char *normal_filename;
1155 + char *parent;
1156 + const size_t addr_size = sizeof(struct sockaddr_un);
1157 + const int sock_type = SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC;
1158 +
1159 + normalizepath(filename, &normal_filename);
1160 +
1161 + // In case socket file exists
1162 + unlink(normal_filename);
1163 +
1164 + // For portability clear the addr structure, since some implementations have
1165 + // nonstandard fields in the structure
1166 + memset(&sockaddr, 0, addr_size);
1167 +
1168 + parentdir(normal_filename, &parent);
1169 + // Create parent directories
1170 + mkdirp(parent);
1171 + free(parent);
1172 +
1173 + sockaddr.sun_family = AF_LOCAL;
1174 + strcpy(sockaddr.sun_path, normal_filename);
1175 + free(normal_filename);
1176 +
1177 + sock_fd = socket(AF_LOCAL, sock_type, 0);
1178 + if (sock_fd == -1) {
1179 + fputs("Failed to create socket\n", stderr);
1180 + return -1;
1181 + }
1182 +
1183 + DEBUG("Created socket at %s\n", sockaddr.sun_path);
1184 +
1185 + if (bind(sock_fd, (const struct sockaddr *)&sockaddr, addr_size) == -1) {
1186 + fputs("Failed to bind socket\n", stderr);
1187 + return -1;
1188 + }
1189 +
1190 + DEBUG("Socket binded\n");
1191 +
1192 + if (listen(sock_fd, IPC_SOCKET_BACKLOG) < 0) {
1193 + fputs("Failed to listen for connections on socket\n", stderr);
1194 + return -1;
1195 + }
1196 +
1197 + DEBUG("Now listening for connections on socket\n");
1198 +
1199 + return sock_fd;
1200 +}
1201 +
1202 +/**
1203 + * Internal function used to receive IPC messages from a given file descriptor.
1204 + *
1205 + * Returns -1 on error reading (could be EAGAIN or EINTR)
1206 + * Returns -2 if EOF before header could be read
1207 + * Returns -3 if invalid IPC header
1208 + * Returns -4 if message length exceeds MAX_MESSAGE_SIZE
1209 + */
1210 +static int
1211 +ipc_recv_message(int fd, uint8_t *msg_type, uint32_t *reply_size,
1212 + uint8_t **reply)
1213 +{
1214 + uint32_t read_bytes = 0;
1215 + const int32_t to_read = sizeof(dwm_ipc_header_t);
1216 + char header[to_read];
1217 + char *walk = header;
1218 +
1219 + // Try to read header
1220 + while (read_bytes < to_read) {
1221 + const ssize_t n = read(fd, header + read_bytes, to_read - read_bytes);
1222 +
1223 + if (n == 0) {
1224 + if (read_bytes == 0) {
1225 + fprintf(stderr, "Unexpectedly reached EOF while reading header.");
1226 + fprintf(stderr,
1227 + "Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n",
1228 + read_bytes, to_read);
1229 + return -2;
1230 + } else {
1231 + fprintf(stderr, "Unexpectedly reached EOF while reading header.");
1232 + fprintf(stderr,
1233 + "Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n",
1234 + read_bytes, to_read);
1235 + return -3;
1236 + }
1237 + } else if (n == -1) {
1238 + // errno will still be set
1239 + return -1;
1240 + }
1241 +
1242 + read_bytes += n;
1243 + }
1244 +
1245 + // Check if magic string in header matches
1246 + if (memcmp(walk, IPC_MAGIC, IPC_MAGIC_LEN) != 0) {
1247 + fprintf(stderr, "Invalid magic string. Got '%.*s', expected '%s'\n",
1248 + IPC_MAGIC_LEN, walk, IPC_MAGIC);
1249 + return -3;
1250 + }
1251 +
1252 + walk += IPC_MAGIC_LEN;
1253 +
1254 + // Extract reply size
1255 + memcpy(reply_size, walk, sizeof(uint32_t));
1256 + walk += sizeof(uint32_t);
1257 +
1258 + if (*reply_size > MAX_MESSAGE_SIZE) {
1259 + fprintf(stderr, "Message too long: %" PRIu32 " bytes. ", *reply_size);
1260 + fprintf(stderr, "Maximum message size is: %d\n", MAX_MESSAGE_SIZE);
1261 + return -4;
1262 + }
1263 +
1264 + // Extract message type
1265 + memcpy(msg_type, walk, sizeof(uint8_t));
1266 + walk += sizeof(uint8_t);
1267 +
1268 + if (*reply_size > 0)
1269 + (*reply) = malloc(*reply_size);
1270 + else
1271 + return 0;
1272 +
1273 + read_bytes = 0;
1274 + while (read_bytes < *reply_size) {
1275 + const ssize_t n = read(fd, *reply + read_bytes, *reply_size - read_bytes);
1276 +
1277 + if (n == 0) {
1278 + fprintf(stderr, "Unexpectedly reached EOF while reading payload.");
1279 + fprintf(stderr, "Read %" PRIu32 " bytes, expected %" PRIu32 " bytes.\n",
1280 + read_bytes, *reply_size);
1281 + free(*reply);
1282 + return -2;
1283 + } else if (n == -1) {
1284 + // TODO: Should we return and wait for another epoll event?
1285 + // This would require saving the partial read in some way.
1286 + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
1287 +
1288 + free(*reply);
1289 + return -1;
1290 + }
1291 +
1292 + read_bytes += n;
1293 + }
1294 +
1295 + return 0;
1296 +}
1297 +
1298 +/**
1299 + * Internal function used to write a buffer to a file descriptor
1300 + *
1301 + * Returns number of bytes written if successful write
1302 + * Returns 0 if no bytes were written due to EAGAIN or EWOULDBLOCK
1303 + * Returns -1 on unknown error trying to write, errno will carry over from
1304 + * write() call
1305 + */
1306 +static ssize_t
1307 +ipc_write_message(int fd, const void *buf, size_t count)
1308 +{
1309 + size_t written = 0;
1310 +
1311 + while (written < count) {
1312 + const ssize_t n = write(fd, (uint8_t *)buf + written, count - written);
1313 +
1314 + if (n == -1) {
1315 + if (errno == EAGAIN || errno == EWOULDBLOCK)
1316 + return written;
1317 + else if (errno == EINTR)
1318 + continue;
1319 + else
1320 + return n;
1321 + }
1322 +
1323 + written += n;
1324 + DEBUG("Wrote %zu/%zu to client at fd %d\n", written, count, fd);
1325 + }
1326 +
1327 + return written;
1328 +}
1329 +
1330 +/**
1331 + * Initialization for generic event message. This is used to allocate the yajl
1332 + * handle, set yajl options, and in the future any other initialization that
1333 + * should occur for event messages.
1334 + */
1335 +static void
1336 +ipc_event_init_message(yajl_gen *gen)
1337 +{
1338 + *gen = yajl_gen_alloc(NULL);
1339 + yajl_gen_config(*gen, yajl_gen_beautify, 1);
1340 +}
1341 +
1342 +/**
1343 + * Prepares buffers of IPC subscribers of specified event using buffer from yajl
1344 + * handle.
1345 + */
1346 +static void
1347 +ipc_event_prepare_send_message(yajl_gen gen, IPCEvent event)
1348 +{
1349 + const unsigned char *buffer;
1350 + size_t len = 0;
1351 +
1352 + yajl_gen_get_buf(gen, &buffer, &len);
1353 + len++; // For null char
1354 +
1355 + for (IPCClient *c = ipc_clients; c; c = c->next) {
1356 + if (c->subscriptions & event) {
1357 + DEBUG("Sending selected client change event to fd %d\n", c->fd);
1358 + ipc_prepare_send_message(c, IPC_TYPE_EVENT, len, (char *)buffer);
1359 + }
1360 + }
1361 +
1362 + // Not documented, but this frees temp_buffer
1363 + yajl_gen_free(gen);
1364 +}
1365 +
1366 +/**
1367 + * Initialization for generic reply message. This is used to allocate the yajl
1368 + * handle, set yajl options, and in the future any other initialization that
1369 + * should occur for reply messages.
1370 + */
1371 +static void
1372 +ipc_reply_init_message(yajl_gen *gen)
1373 +{
1374 + *gen = yajl_gen_alloc(NULL);
1375 + yajl_gen_config(*gen, yajl_gen_beautify, 1);
1376 +}
1377 +
1378 +/**
1379 + * Prepares the IPC client's buffer with a message using the buffer of the yajl
1380 + * handle.
1381 + */
1382 +static void
1383 +ipc_reply_prepare_send_message(yajl_gen gen, IPCClient *c,
1384 + IPCMessageType msg_type)
1385 +{
1386 + const unsigned char *buffer;
1387 + size_t len = 0;
1388 +
1389 + yajl_gen_get_buf(gen, &buffer, &len);
1390 + len++; // For null char
1391 +
1392 + ipc_prepare_send_message(c, msg_type, len, (const char *)buffer);
1393 +
1394 + // Not documented, but this frees temp_buffer
1395 + yajl_gen_free(gen);
1396 +}
1397 +
1398 +/**
1399 + * Find the IPCCommand with the specified name
1400 + *
1401 + * Returns 0 if a command with the specified name was found
1402 + * Returns -1 if a command with the specified name could not be found
1403 + */
1404 +static int
1405 +ipc_get_ipc_command(const char *name, IPCCommand *ipc_command)
1406 +{
1407 + for (int i = 0; i < ipc_commands_len; i++) {
1408 + if (strcmp(ipc_commands[i].name, name) == 0) {
1409 + *ipc_command = ipc_commands[i];
1410 + return 0;
1411 + }
1412 + }
1413 +
1414 + return -1;
1415 +}
1416 +
1417 +/**
1418 + * Parse a IPC_TYPE_RUN_COMMAND message from a client. This function extracts
1419 + * the arguments, argument count, argument types, and command name and returns
1420 + * the parsed information as an IPCParsedCommand. If this function returns
1421 + * successfully, the parsed_command must be freed using
1422 + * ipc_free_parsed_command_members.
1423 + *
1424 + * Returns 0 if the message was successfully parsed
1425 + * Returns -1 otherwise
1426 + */
1427 +static int
1428 +ipc_parse_run_command(char *msg, IPCParsedCommand *parsed_command)
1429 +{
1430 + char error_buffer[1000];
1431 + yajl_val parent = yajl_tree_parse(msg, error_buffer, 1000);
1432 +
1433 + if (parent == NULL) {
1434 + fputs("Failed to parse command from client\n", stderr);
1435 + fprintf(stderr, "%s\n", error_buffer);
1436 + fprintf(stderr, "Tried to parse: %s\n", msg);
1437 + return -1;
1438 + }
1439 +
1440 + // Format:
1441 + // {
1442 + // "command": "<command name>"
1443 + // "args": [ "arg1", "arg2", ... ]
1444 + // }
1445 + const char *command_path[] = {"command", 0};
1446 + yajl_val command_val = yajl_tree_get(parent, command_path, yajl_t_string);
1447 +
1448 + if (command_val == NULL) {
1449 + fputs("No command key found in client message\n", stderr);
1450 + yajl_tree_free(parent);
1451 + return -1;
1452 + }
1453 +
1454 + const char *command_name = YAJL_GET_STRING(command_val);
1455 + size_t command_name_len = strlen(command_name);
1456 + parsed_command->name = (char *)malloc((command_name_len + 1) * sizeof(char));
1457 + strcpy(parsed_command->name, command_name);
1458 +
1459 + DEBUG("Received command: %s\n", parsed_command->name);
1460 +
1461 + const char *args_path[] = {"args", 0};
1462 + yajl_val args_val = yajl_tree_get(parent, args_path, yajl_t_array);
1463 +
1464 + if (args_val == NULL) {
1465 + fputs("No args key found in client message\n", stderr);
1466 + yajl_tree_free(parent);
1467 + return -1;
1468 + }
1469 +
1470 + unsigned int *argc = &parsed_command->argc;
1471 + Arg **args = &parsed_command->args;
1472 + ArgType **arg_types = &parsed_command->arg_types;
1473 +
1474 + *argc = args_val->u.array.len;
1475 +
1476 + // If no arguments are specified, make a dummy argument to pass to the
1477 + // function. This is just the way dwm's void(Arg*) functions are setup.
1478 + if (*argc == 0) {
1479 + *args = (Arg *)malloc(sizeof(Arg));
1480 + *arg_types = (ArgType *)malloc(sizeof(ArgType));
1481 + (*arg_types)[0] = ARG_TYPE_NONE;
1482 + (*args)[0].i = 0;
1483 + (*argc)++;
1484 + } else if (*argc > 0) {
1485 + *args = (Arg *)calloc(*argc, sizeof(Arg));
1486 + *arg_types = (ArgType *)malloc(*argc * sizeof(ArgType));
1487 +
1488 + for (int i = 0; i < *argc; i++) {
1489 + yajl_val arg_val = args_val->u.array.values[i];
1490 +
1491 + if (YAJL_IS_NUMBER(arg_val)) {
1492 + if (YAJL_IS_INTEGER(arg_val)) {
1493 + // Any values below 0 must be a signed int
1494 + if (YAJL_GET_INTEGER(arg_val) < 0) {
1495 + (*args)[i].i = YAJL_GET_INTEGER(arg_val);
1496 + (*arg_types)[i] = ARG_TYPE_SINT;
1497 + DEBUG("i=%ld\n", (*args)[i].i);
1498 + // Any values above 0 should be an unsigned int
1499 + } else if (YAJL_GET_INTEGER(arg_val) >= 0) {
1500 + (*args)[i].ui = YAJL_GET_INTEGER(arg_val);
1501 + (*arg_types)[i] = ARG_TYPE_UINT;
1502 + DEBUG("ui=%ld\n", (*args)[i].i);
1503 + }
1504 + // If the number is not an integer, it must be a float
1505 + } else {
1506 + (*args)[i].f = (float)YAJL_GET_DOUBLE(arg_val);
1507 + (*arg_types)[i] = ARG_TYPE_FLOAT;
1508 + DEBUG("f=%f\n", (*args)[i].f);
1509 + // If argument is not a number, it must be a string
1510 + }
1511 + } else if (YAJL_IS_STRING(arg_val)) {
1512 + char *arg_s = YAJL_GET_STRING(arg_val);
1513 + size_t arg_s_size = (strlen(arg_s) + 1) * sizeof(char);
1514 + (*args)[i].v = (char *)malloc(arg_s_size);
1515 + (*arg_types)[i] = ARG_TYPE_STR;
1516 + strcpy((char *)(*args)[i].v, arg_s);
1517 + }
1518 + }
1519 + }
1520 +
1521 + yajl_tree_free(parent);
1522 +
1523 + return 0;
1524 +}
1525 +
1526 +/**
1527 + * Free the members of a IPCParsedCommand struct
1528 + */
1529 +static void
1530 +ipc_free_parsed_command_members(IPCParsedCommand *command)
1531 +{
1532 + for (int i = 0; i < command->argc; i++) {
1533 + if (command->arg_types[i] == ARG_TYPE_STR) free((void *)command->args[i].v);
1534 + }
1535 + free(command->args);
1536 + free(command->arg_types);
1537 + free(command->name);
1538 +}
1539 +
1540 +/**
1541 + * Check if the given arguments are the correct length and type. Also do any
1542 + * casting to correct the types.
1543 + *
1544 + * Returns 0 if the arguments were the correct length and types
1545 + * Returns -1 if the argument count doesn't match
1546 + * Returns -2 if the argument types don't match
1547 + */
1548 +static int
1549 +ipc_validate_run_command(IPCParsedCommand *parsed, const IPCCommand actual)
1550 +{
1551 + if (actual.argc != parsed->argc) return -1;
1552 +
1553 + for (int i = 0; i < parsed->argc; i++) {
1554 + ArgType ptype = parsed->arg_types[i];
1555 + ArgType atype = actual.arg_types[i];
1556 +
1557 + if (ptype != atype) {
1558 + if (ptype == ARG_TYPE_UINT && atype == ARG_TYPE_PTR)
1559 + // If this argument is supposed to be a void pointer, cast it
1560 + parsed->args[i].v = (void *)parsed->args[i].ui;
1561 + else if (ptype == ARG_TYPE_UINT && atype == ARG_TYPE_SINT)
1562 + // If this argument is supposed to be a signed int, cast it
1563 + parsed->args[i].i = parsed->args[i].ui;
1564 + else
1565 + return -2;
1566 + }
1567 + }
1568 +
1569 + return 0;
1570 +}
1571 +
1572 +/**
1573 + * Convert event name to their IPCEvent equivalent enum value
1574 + *
1575 + * Returns 0 if a valid event name was given
1576 + * Returns -1 otherwise
1577 + */
1578 +static int
1579 +ipc_event_stoi(const char *subscription, IPCEvent *event)
1580 +{
1581 + if (strcmp(subscription, "tag_change_event") == 0)
1582 + *event = IPC_EVENT_TAG_CHANGE;
1583 + else if (strcmp(subscription, "client_focus_change_event") == 0)
1584 + *event = IPC_EVENT_CLIENT_FOCUS_CHANGE;
1585 + else if (strcmp(subscription, "layout_change_event") == 0)
1586 + *event = IPC_EVENT_LAYOUT_CHANGE;
1587 + else if (strcmp(subscription, "monitor_focus_change_event") == 0)
1588 + *event = IPC_EVENT_MONITOR_FOCUS_CHANGE;
1589 + else if (strcmp(subscription, "focused_title_change_event") == 0)
1590 + *event = IPC_EVENT_FOCUSED_TITLE_CHANGE;
1591 + else if (strcmp(subscription, "focused_state_change_event") == 0)
1592 + *event = IPC_EVENT_FOCUSED_STATE_CHANGE;
1593 + else
1594 + return -1;
1595 + return 0;
1596 +}
1597 +
1598 +/**
1599 + * Parse a IPC_TYPE_SUBSCRIBE message from a client. This function extracts the
1600 + * event name and the subscription action from the message.
1601 + *
1602 + * Returns 0 if message was successfully parsed
1603 + * Returns -1 otherwise
1604 + */
1605 +static int
1606 +ipc_parse_subscribe(const char *msg, IPCSubscriptionAction *subscribe,
1607 + IPCEvent *event)
1608 +{
1609 + char error_buffer[100];
1610 + yajl_val parent = yajl_tree_parse((char *)msg, error_buffer, 100);
1611 +
1612 + if (parent == NULL) {
1613 + fputs("Failed to parse command from client\n", stderr);
1614 + fprintf(stderr, "%s\n", error_buffer);
1615 + return -1;
1616 + }
1617 +
1618 + // Format:
1619 + // {
1620 + // "event": "<event name>"
1621 + // "action": "<subscribe|unsubscribe>"
1622 + // }
1623 + const char *event_path[] = {"event", 0};
1624 + yajl_val event_val = yajl_tree_get(parent, event_path, yajl_t_string);
1625 +
1626 + if (event_val == NULL) {
1627 + fputs("No 'event' key found in client message\n", stderr);
1628 + return -1;
1629 + }
1630 +
1631 + const char *event_str = YAJL_GET_STRING(event_val);
1632 + DEBUG("Received event: %s\n", event_str);
1633 +
1634 + if (ipc_event_stoi(event_str, event) < 0) return -1;
1635 +
1636 + const char *action_path[] = {"action", 0};
1637 + yajl_val action_val = yajl_tree_get(parent, action_path, yajl_t_string);
1638 +
1639 + if (action_val == NULL) {
1640 + fputs("No 'action' key found in client message\n", stderr);
1641 + return -1;
1642 + }
1643 +
1644 + const char *action = YAJL_GET_STRING(action_val);
1645 +
1646 + if (strcmp(action, "subscribe") == 0)
1647 + *subscribe = IPC_ACTION_SUBSCRIBE;
1648 + else if (strcmp(action, "unsubscribe") == 0)
1649 + *subscribe = IPC_ACTION_UNSUBSCRIBE;
1650 + else {
1651 + fputs("Invalid action specified for subscription\n", stderr);
1652 + return -1;
1653 + }
1654 +
1655 + yajl_tree_free(parent);
1656 +
1657 + return 0;
1658 +}
1659 +
1660 +/**
1661 + * Parse an IPC_TYPE_GET_DWM_CLIENT message from a client. This function
1662 + * extracts the window id from the message.
1663 + *
1664 + * Returns 0 if message was successfully parsed
1665 + * Returns -1 otherwise
1666 + */
1667 +static int
1668 +ipc_parse_get_dwm_client(const char *msg, Window *win)
1669 +{
1670 + char error_buffer[100];
1671 +
1672 + yajl_val parent = yajl_tree_parse(msg, error_buffer, 100);
1673 +
1674 + if (parent == NULL) {
1675 + fputs("Failed to parse message from client\n", stderr);
1676 + fprintf(stderr, "%s\n", error_buffer);
1677 + return -1;
1678 + }
1679 +
1680 + // Format:
1681 + // {
1682 + // "client_window_id": <client window id>
1683 + // }
1684 + const char *win_path[] = {"client_window_id", 0};
1685 + yajl_val win_val = yajl_tree_get(parent, win_path, yajl_t_number);
1686 +
1687 + if (win_val == NULL) {
1688 + fputs("No client window id found in client message\n", stderr);
1689 + return -1;
1690 + }
1691 +
1692 + *win = YAJL_GET_INTEGER(win_val);
1693 +
1694 + yajl_tree_free(parent);
1695 +
1696 + return 0;
1697 +}
1698 +
1699 +/**
1700 + * Called when an IPC_TYPE_RUN_COMMAND message is received from a client. This
1701 + * function parses, executes the given command, and prepares a reply message to
1702 + * the client indicating success/failure.
1703 + *
1704 + * NOTE: There is currently no check for argument validity beyond the number of
1705 + * arguments given and types of arguments. There is also no way to check if the
1706 + * function succeeded based on dwm's void(const Arg*) function types. Pointer
1707 + * arguments can cause crashes if they are not validated in the function itself.
1708 + *
1709 + * Returns 0 if message was successfully parsed
1710 + * Returns -1 on failure parsing message
1711 + */
1712 +static int
1713 +ipc_run_command(IPCClient *ipc_client, char *msg)
1714 +{
1715 + IPCParsedCommand parsed_command;
1716 + IPCCommand ipc_command;
1717 +
1718 + // Initialize struct
1719 + memset(&parsed_command, 0, sizeof(IPCParsedCommand));
1720 +
1721 + if (ipc_parse_run_command(msg, &parsed_command) < 0) {
1722 + ipc_prepare_reply_failure(ipc_client, IPC_TYPE_RUN_COMMAND,
1723 + "Failed to parse run command");
1724 + return -1;
1725 + }
1726 +
1727 + if (ipc_get_ipc_command(parsed_command.name, &ipc_command) < 0) {
1728 + ipc_prepare_reply_failure(ipc_client, IPC_TYPE_RUN_COMMAND,
1729 + "Command %s not found", parsed_command.name);
1730 + ipc_free_parsed_command_members(&parsed_command);
1731 + return -1;
1732 + }
1733 +
1734 + int res = ipc_validate_run_command(&parsed_command, ipc_command);
1735 + if (res < 0) {
1736 + if (res == -1)
1737 + ipc_prepare_reply_failure(ipc_client, IPC_TYPE_RUN_COMMAND,
1738 + "%u arguments provided, %u expected",
1739 + parsed_command.argc, ipc_command.argc);
1740 + else if (res == -2)
1741 + ipc_prepare_reply_failure(ipc_client, IPC_TYPE_RUN_COMMAND,
1742 + "Type mismatch");
1743 + ipc_free_parsed_command_members(&parsed_command);
1744 + return -1;
1745 + }
1746 +
1747 + if (parsed_command.argc == 1)
1748 + ipc_command.func.single_param(parsed_command.args);
1749 + else if (parsed_command.argc > 1)
1750 + ipc_command.func.array_param(parsed_command.args, parsed_command.argc);
1751 +
1752 + DEBUG("Called function for command %s\n", parsed_command.name);
1753 +
1754 + ipc_free_parsed_command_members(&parsed_command);
1755 +
1756 + ipc_prepare_reply_success(ipc_client, IPC_TYPE_RUN_COMMAND);
1757 + return 0;
1758 +}
1759 +
1760 +/**
1761 + * Called when an IPC_TYPE_GET_MONITORS message is received from a client. It
1762 + * prepares a reply with the properties of all of the monitors in JSON.
1763 + */
1764 +static void
1765 +ipc_get_monitors(IPCClient *c, Monitor *mons, Monitor *selmon)
1766 +{
1767 + yajl_gen gen;
1768 + ipc_reply_init_message(&gen);
1769 + dump_monitors(gen, mons, selmon);
1770 +
1771 + ipc_reply_prepare_send_message(gen, c, IPC_TYPE_GET_MONITORS);
1772 +}
1773 +
1774 +/**
1775 + * Called when an IPC_TYPE_GET_TAGS message is received from a client. It
1776 + * prepares a reply with info about all the tags in JSON.
1777 + */
1778 +static void
1779 +ipc_get_tags(IPCClient *c, const char *tags[], const int tags_len)
1780 +{
1781 + yajl_gen gen;
1782 + ipc_reply_init_message(&gen);
1783 +
1784 + dump_tags(gen, tags, tags_len);
1785 +
1786 + ipc_reply_prepare_send_message(gen, c, IPC_TYPE_GET_TAGS);
1787 +}
1788 +
1789 +/**
1790 + * Called when an IPC_TYPE_GET_LAYOUTS message is received from a client. It
1791 + * prepares a reply with a JSON array of available layouts
1792 + */
1793 +static void
1794 +ipc_get_layouts(IPCClient *c, const Layout layouts[], const int layouts_len)
1795 +{
1796 + yajl_gen gen;
1797 + ipc_reply_init_message(&gen);
1798 +
1799 + dump_layouts(gen, layouts, layouts_len);
1800 +
1801 + ipc_reply_prepare_send_message(gen, c, IPC_TYPE_GET_LAYOUTS);
1802 +}
1803 +
1804 +/**
1805 + * Called when an IPC_TYPE_GET_DWM_CLIENT message is received from a client. It
1806 + * prepares a JSON reply with the properties of the client with the specified
1807 + * window XID.
1808 + *
1809 + * Returns 0 if the message was successfully parsed and if the client with the
1810 + * specified window XID was found
1811 + * Returns -1 if the message could not be parsed
1812 + */
1813 +static int
1814 +ipc_get_dwm_client(IPCClient *ipc_client, const char *msg, const Monitor *mons)
1815 +{
1816 + Window win;
1817 +
1818 + if (ipc_parse_get_dwm_client(msg, &win) < 0) return -1;
1819 +
1820 + // Find client with specified window XID
1821 + for (const Monitor *m = mons; m; m = m->next)
1822 + for (Client *c = m->clients; c; c = c->next)
1823 + if (c->win == win) {
1824 + yajl_gen gen;
1825 + ipc_reply_init_message(&gen);
1826 +
1827 + dump_client(gen, c);
1828 +
1829 + ipc_reply_prepare_send_message(gen, ipc_client,
1830 + IPC_TYPE_GET_DWM_CLIENT);
1831 +
1832 + return 0;
1833 + }
1834 +
1835 + ipc_prepare_reply_failure(ipc_client, IPC_TYPE_GET_DWM_CLIENT,
1836 + "Client with window id %d not found", win);
1837 + return -1;
1838 +}
1839 +
1840 +/**
1841 + * Called when an IPC_TYPE_SUBSCRIBE message is received from a client. It
1842 + * subscribes/unsubscribes the client from the specified event and replies with
1843 + * the result.
1844 + *
1845 + * Returns 0 if the message was successfully parsed.
1846 + * Returns -1 if the message could not be parsed
1847 + */
1848 +static int
1849 +ipc_subscribe(IPCClient *c, const char *msg)
1850 +{
1851 + IPCSubscriptionAction action = IPC_ACTION_SUBSCRIBE;
1852 + IPCEvent event = 0;
1853 +
1854 + if (ipc_parse_subscribe(msg, &action, &event)) {
1855 + ipc_prepare_reply_failure(c, IPC_TYPE_SUBSCRIBE, "Event does not exist");
1856 + return -1;
1857 + }
1858 +
1859 + if (action == IPC_ACTION_SUBSCRIBE) {
1860 + DEBUG("Subscribing client on fd %d to %d\n", c->fd, event);
1861 + c->subscriptions |= event;
1862 + } else if (action == IPC_ACTION_UNSUBSCRIBE) {
1863 + DEBUG("Unsubscribing client on fd %d to %d\n", c->fd, event);
1864 + c->subscriptions ^= event;
1865 + } else {
1866 + ipc_prepare_reply_failure(c, IPC_TYPE_SUBSCRIBE,
1867 + "Invalid subscription action");
1868 + return -1;
1869 + }
1870 +
1871 + ipc_prepare_reply_success(c, IPC_TYPE_SUBSCRIBE);
1872 + return 0;
1873 +}
1874 +
1875 +int
1876 +ipc_init(const char *socket_path, const int p_epoll_fd, IPCCommand commands[],
1877 + const int commands_len)
1878 +{
1879 + // Initialize struct to 0
1880 + memset(&sock_epoll_event, 0, sizeof(sock_epoll_event));
1881 +
1882 + int socket_fd = ipc_create_socket(socket_path);
1883 + if (socket_fd < 0) return -1;
1884 +
1885 + ipc_commands = commands;
1886 + ipc_commands_len = commands_len;
1887 +
1888 + epoll_fd = p_epoll_fd;
1889 +
1890 + // Wake up to incoming connection requests
1891 + sock_epoll_event.data.fd = socket_fd;
1892 + sock_epoll_event.events = EPOLLIN;
1893 + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &sock_epoll_event)) {
1894 + fputs("Failed to add sock file descriptor to epoll", stderr);
1895 + return -1;
1896 + }
1897 +
1898 + return socket_fd;
1899 +}
1900 +
1901 +void
1902 +ipc_cleanup()
1903 +{
1904 + IPCClient *c = ipc_clients;
1905 + // Free clients and their buffers
1906 + while (c) {
1907 + ipc_drop_client(c);
1908 + c = ipc_clients;
1909 + }
1910 +
1911 + // Stop waking up for socket events
1912 + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock_fd, &sock_epoll_event);
1913 +
1914 + // Uninitialize all static variables
1915 + epoll_fd = -1;
1916 + sock_fd = -1;
1917 + ipc_commands = NULL;
1918 + ipc_commands_len = 0;
1919 + memset(&sock_epoll_event, 0, sizeof(struct epoll_event));
1920 + memset(&sockaddr, 0, sizeof(struct sockaddr_un));
1921 +
1922 + // Delete socket
1923 + unlink(sockaddr.sun_path);
1924 +
1925 + shutdown(sock_fd, SHUT_RDWR);
1926 + close(sock_fd);
1927 +}
1928 +
1929 +int
1930 +ipc_get_sock_fd()
1931 +{
1932 + return sock_fd;
1933 +}
1934 +
1935 +IPCClient *
1936 +ipc_get_client(int fd)
1937 +{
1938 + return ipc_list_get_client(ipc_clients, fd);
1939 +}
1940 +
1941 +int
1942 +ipc_is_client_registered(int fd)
1943 +{
1944 + return (ipc_get_client(fd) != NULL);
1945 +}
1946 +
1947 +int
1948 +ipc_accept_client()
1949 +{
1950 + int fd = -1;
1951 +
1952 + struct sockaddr_un client_addr;
1953 + socklen_t len = 0;
1954 +
1955 + // For portability clear the addr structure, since some implementations
1956 + // have nonstandard fields in the structure
1957 + memset(&client_addr, 0, sizeof(struct sockaddr_un));
1958 +
1959 + fd = accept(sock_fd, (struct sockaddr *)&client_addr, &len);
1960 + if (fd < 0 && errno != EINTR) {
1961 + fputs("Failed to accept IPC connection from client", stderr);
1962 + return -1;
1963 + }
1964 +
1965 + if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) {
1966 + shutdown(fd, SHUT_RDWR);
1967 + close(fd);
1968 + fputs("Failed to set flags on new client fd", stderr);
1969 + }
1970 +
1971 + IPCClient *nc = ipc_client_new(fd);
1972 + if (nc == NULL) return -1;
1973 +
1974 + // Wake up to messages from this client
1975 + nc->event.data.fd = fd;
1976 + nc->event.events = EPOLLIN | EPOLLHUP;
1977 + epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &nc->event);
1978 +
1979 + ipc_list_add_client(&ipc_clients, nc);
1980 +
1981 + DEBUG("%s%d\n", "New client at fd: ", fd);
1982 +
1983 + return fd;
1984 +}
1985 +
1986 +int
1987 +ipc_drop_client(IPCClient *c)
1988 +{
1989 + int fd = c->fd;
1990 + shutdown(fd, SHUT_RDWR);
1991 + int res = close(fd);
1992 +
1993 + if (res == 0) {
1994 + struct epoll_event ev;
1995 +
1996 + // Stop waking up to messages from this client
1997 + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ev);
1998 + ipc_list_remove_client(&ipc_clients, c);
1999 +
2000 + free(c->buffer);
2001 + free(c);
2002 +
2003 + DEBUG("Successfully removed client on fd %d\n", fd);
2004 + } else if (res < 0 && res != EINTR) {
2005 + fprintf(stderr, "Failed to close fd %d\n", fd);
2006 + }
2007 +
2008 + return res;
2009 +}
2010 +
2011 +int
2012 +ipc_read_client(IPCClient *c, IPCMessageType *msg_type, uint32_t *msg_size,
2013 + char **msg)
2014 +{
2015 + int fd = c->fd;
2016 + int ret =
2017 + ipc_recv_message(fd, (uint8_t *)msg_type, msg_size, (uint8_t **)msg);
2018 +
2019 + if (ret < 0) {
2020 + // This will happen if these errors occur while reading header
2021 + if (ret == -1 &&
2022 + (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK))
2023 + return -2;
2024 +
2025 + fprintf(stderr, "Error reading message: dropping client at fd %d\n", fd);
2026 + ipc_drop_client(c);
2027 +
2028 + return -1;
2029 + }
2030 +
2031 + // Make sure receive message is null terminated to avoid parsing issues
2032 + if (*msg_size > 0) {
2033 + size_t len = *msg_size;
2034 + nullterminate(msg, &len);
2035 + *msg_size = len;
2036 + }
2037 +
2038 + DEBUG("[fd %d] ", fd);
2039 + if (*msg_size > 0)
2040 + DEBUG("Received message: '%.*s' ", *msg_size, *msg);
2041 + else
2042 + DEBUG("Received empty message ");
2043 + DEBUG("Message type: %" PRIu8 " ", (uint8_t)*msg_type);
2044 + DEBUG("Message size: %" PRIu32 "\n", *msg_size);
2045 +
2046 + return 0;
2047 +}
2048 +
2049 +ssize_t
2050 +ipc_write_client(IPCClient *c)
2051 +{
2052 + const ssize_t n = ipc_write_message(c->fd, c->buffer, c->buffer_size);
2053 +
2054 + if (n < 0) return n;
2055 +
2056 + // TODO: Deal with client timeouts
2057 +
2058 + if (n == c->buffer_size) {
2059 + c->buffer_size = 0;
2060 + free(c->buffer);
2061 + // No dangling pointers!
2062 + c->buffer = NULL;
2063 + // Stop waking up when client is ready to receive messages
2064 + if (c->event.events & EPOLLOUT) {
2065 + c->event.events -= EPOLLOUT;
2066 + epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, &c->event);
2067 + }
2068 + return n;
2069 + }
2070 +
2071 + // Shift unwritten buffer to beginning of buffer and reallocate
2072 + c->buffer_size -= n;
2073 + memmove(c->buffer, c->buffer + n, c->buffer_size);
2074 + c->buffer = (char *)realloc(c->buffer, c->buffer_size);
2075 +
2076 + return n;
2077 +}
2078 +
2079 +void
2080 +ipc_prepare_send_message(IPCClient *c, const IPCMessageType msg_type,
2081 + const uint32_t msg_size, const char *msg)
2082 +{
2083 + dwm_ipc_header_t header = {
2084 + .magic = IPC_MAGIC_ARR, .type = msg_type, .size = msg_size};
2085 +
2086 + uint32_t header_size = sizeof(dwm_ipc_header_t);
2087 + uint32_t packet_size = header_size + msg_size;
2088 +
2089 + if (c->buffer == NULL)
2090 + c->buffer = (char *)malloc(c->buffer_size + packet_size);
2091 + else
2092 + c->buffer = (char *)realloc(c->buffer, c->buffer_size + packet_size);
2093 +
2094 + // Copy header to end of client buffer
2095 + memcpy(c->buffer + c->buffer_size, &header, header_size);
2096 + c->buffer_size += header_size;
2097 +
2098 + // Copy message to end of client buffer
2099 + memcpy(c->buffer + c->buffer_size, msg, msg_size);
2100 + c->buffer_size += msg_size;
2101 +
2102 + // Wake up when client is ready to receive messages
2103 + c->event.events |= EPOLLOUT;
2104 + epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, &c->event);
2105 +}
2106 +
2107 +void
2108 +ipc_prepare_reply_failure(IPCClient *c, IPCMessageType msg_type,
2109 + const char *format, ...)
2110 +{
2111 + yajl_gen gen;
2112 + va_list args;
2113 +
2114 + // Get output size
2115 + va_start(args, format);
2116 + size_t len = vsnprintf(NULL, 0, format, args);
2117 + va_end(args);
2118 + char *buffer = (char *)malloc((len + 1) * sizeof(char));
2119 +
2120 + ipc_reply_init_message(&gen);
2121 +
2122 + va_start(args, format);
2123 + vsnprintf(buffer, len + 1, format, args);
2124 + va_end(args);
2125 + dump_error_message(gen, buffer);
2126 +
2127 + ipc_reply_prepare_send_message(gen, c, msg_type);
2128 + fprintf(stderr, "[fd %d] Error: %s\n", c->fd, buffer);
2129 +
2130 + free(buffer);
2131 +}
2132 +
2133 +void
2134 +ipc_prepare_reply_success(IPCClient *c, IPCMessageType msg_type)
2135 +{
2136 + const char *success_msg = "{\"result\":\"success\"}";
2137 + const size_t msg_len = strlen(success_msg) + 1; // +1 for null char
2138 +
2139 + ipc_prepare_send_message(c, msg_type, msg_len, success_msg);
2140 +}
2141 +
2142 +void
2143 +ipc_tag_change_event(int mon_num, TagState old_state, TagState new_state)
2144 +{
2145 + yajl_gen gen;
2146 + ipc_event_init_message(&gen);
2147 + dump_tag_event(gen, mon_num, old_state, new_state);
2148 + ipc_event_prepare_send_message(gen, IPC_EVENT_TAG_CHANGE);
2149 +}
2150 +
2151 +void
2152 +ipc_client_focus_change_event(int mon_num, Client *old_client,
2153 + Client *new_client)
2154 +{
2155 + yajl_gen gen;
2156 + ipc_event_init_message(&gen);
2157 + dump_client_focus_change_event(gen, old_client, new_client, mon_num);
2158 + ipc_event_prepare_send_message(gen, IPC_EVENT_CLIENT_FOCUS_CHANGE);
2159 +}
2160 +
2161 +void
2162 +ipc_layout_change_event(const int mon_num, const char *old_symbol,
2163 + const Layout *old_layout, const char *new_symbol,
2164 + const Layout *new_layout)
2165 +{
2166 + yajl_gen gen;
2167 + ipc_event_init_message(&gen);
2168 + dump_layout_change_event(gen, mon_num, old_symbol, old_layout, new_symbol,
2169 + new_layout);
2170 + ipc_event_prepare_send_message(gen, IPC_EVENT_LAYOUT_CHANGE);
2171 +}
2172 +
2173 +void
2174 +ipc_monitor_focus_change_event(const int last_mon_num, const int new_mon_num)
2175 +{
2176 + yajl_gen gen;
2177 + ipc_event_init_message(&gen);
2178 + dump_monitor_focus_change_event(gen, last_mon_num, new_mon_num);
2179 + ipc_event_prepare_send_message(gen, IPC_EVENT_MONITOR_FOCUS_CHANGE);
2180 +}
2181 +
2182 +void
2183 +ipc_focused_title_change_event(const int mon_num, const Window client_id,
2184 + const char *old_name, const char *new_name)
2185 +{
2186 + yajl_gen gen;
2187 + ipc_event_init_message(&gen);
2188 + dump_focused_title_change_event(gen, mon_num, client_id, old_name, new_name);
2189 + ipc_event_prepare_send_message(gen, IPC_EVENT_FOCUSED_TITLE_CHANGE);
2190 +}
2191 +
2192 +void
2193 +ipc_focused_state_change_event(const int mon_num, const Window client_id,
2194 + const ClientState *old_state,
2195 + const ClientState *new_state)
2196 +{
2197 + yajl_gen gen;
2198 + ipc_event_init_message(&gen);
2199 + dump_focused_state_change_event(gen, mon_num, client_id, old_state,
2200 + new_state);
2201 + ipc_event_prepare_send_message(gen, IPC_EVENT_FOCUSED_STATE_CHANGE);
2202 +}
2203 +
2204 +void
2205 +ipc_send_events(Monitor *mons, Monitor **lastselmon, Monitor *selmon)
2206 +{
2207 + for (Monitor *m = mons; m; m = m->next) {
2208 + unsigned int urg = 0, occ = 0, tagset = 0;
2209 +
2210 + for (Client *c = m->clients; c; c = c->next) {
2211 + occ |= c->tags;
2212 +
2213 + if (c->isurgent) urg |= c->tags;
2214 + }
2215 + tagset = m->tagset[m->seltags];
2216 +
2217 + TagState new_state = {.selected = tagset, .occupied = occ, .urgent = urg};
2218 +
2219 + if (memcmp(&m->tagstate, &new_state, sizeof(TagState)) != 0) {
2220 + ipc_tag_change_event(m->num, m->tagstate, new_state);
2221 + m->tagstate = new_state;
2222 + }
2223 +
2224 + if (m->lastsel != m->sel) {
2225 + ipc_client_focus_change_event(m->num, m->lastsel, m->sel);
2226 + m->lastsel = m->sel;
2227 + }
2228 +
2229 + if (strcmp(m->ltsymbol, m->lastltsymbol) != 0 ||
2230 + m->lastlt != m->lt[m->sellt]) {
2231 + ipc_layout_change_event(m->num, m->lastltsymbol, m->lastlt, m->ltsymbol,
2232 + m->lt[m->sellt]);
2233 + strcpy(m->lastltsymbol, m->ltsymbol);
2234 + m->lastlt = m->lt[m->sellt];
2235 + }
2236 +
2237 + if (*lastselmon != selmon) {
2238 + if (*lastselmon != NULL)
2239 + ipc_monitor_focus_change_event((*lastselmon)->num, selmon->num);
2240 + *lastselmon = selmon;
2241 + }
2242 +
2243 + Client *sel = m->sel;
2244 + if (!sel) continue;
2245 + ClientState *o = &m->sel->prevstate;
2246 + ClientState n = {.oldstate = sel->oldstate,
2247 + .isfixed = sel->isfixed,
2248 + .isfloating = sel->isfloating,
2249 + .isfullscreen = sel->isfullscreen,
2250 + .isurgent = sel->isurgent,
2251 + .neverfocus = sel->neverfocus};
2252 + if (memcmp(o, &n, sizeof(ClientState)) != 0) {
2253 + ipc_focused_state_change_event(m->num, m->sel->win, o, &n);
2254 + *o = n;
2255 + }
2256 + }
2257 +}
2258 +
2259 +int
2260 +ipc_handle_client_epoll_event(struct epoll_event *ev, Monitor *mons,
2261 + Monitor **lastselmon, Monitor *selmon,
2262 + const char *tags[], const int tags_len,
2263 + const Layout *layouts, const int layouts_len)
2264 +{
2265 + int fd = ev->data.fd;
2266 + IPCClient *c = ipc_get_client(fd);
2267 +
2268 + if (ev->events & EPOLLHUP) {
2269 + DEBUG("EPOLLHUP received from client at fd %d\n", fd);
2270 + ipc_drop_client(c);
2271 + } else if (ev->events & EPOLLOUT) {
2272 + DEBUG("Sending message to client at fd %d...\n", fd);
2273 + if (c->buffer_size) ipc_write_client(c);
2274 + } else if (ev->events & EPOLLIN) {
2275 + IPCMessageType msg_type = 0;
2276 + uint32_t msg_size = 0;
2277 + char *msg = NULL;
2278 +
2279 + DEBUG("Received message from fd %d\n", fd);
2280 + if (ipc_read_client(c, &msg_type, &msg_size, &msg) < 0) return -1;
2281 +
2282 + if (msg_type == IPC_TYPE_GET_MONITORS)
2283 + ipc_get_monitors(c, mons, selmon);
2284 + else if (msg_type == IPC_TYPE_GET_TAGS)
2285 + ipc_get_tags(c, tags, tags_len);
2286 + else if (msg_type == IPC_TYPE_GET_LAYOUTS)
2287 + ipc_get_layouts(c, layouts, layouts_len);
2288 + else if (msg_type == IPC_TYPE_RUN_COMMAND) {
2289 + if (ipc_run_command(c, msg) < 0) return -1;
2290 + ipc_send_events(mons, lastselmon, selmon);
2291 + } else if (msg_type == IPC_TYPE_GET_DWM_CLIENT) {
2292 + if (ipc_get_dwm_client(c, msg, mons) < 0) return -1;
2293 + } else if (msg_type == IPC_TYPE_SUBSCRIBE) {
2294 + if (ipc_subscribe(c, msg) < 0) return -1;
2295 + } else {
2296 + fprintf(stderr, "Invalid message type received from fd %d", fd);
2297 + ipc_prepare_reply_failure(c, msg_type, "Invalid message type: %d",
2298 + msg_type);
2299 + }
2300 + free(msg);
2301 + } else {
2302 + fprintf(stderr, "Epoll event returned %d from fd %d\n", ev->events, fd);
2303 + return -1;
2304 + }
2305 +
2306 + return 0;
2307 +}
2308 +
2309 +int
2310 +ipc_handle_socket_epoll_event(struct epoll_event *ev)
2311 +{
2312 + if (!(ev->events & EPOLLIN)) return -1;
2313 +
2314 + // EPOLLIN means incoming client connection request
2315 + fputs("Received EPOLLIN event on socket\n", stderr);
2316 + int new_fd = ipc_accept_client();
2317 +
2318 + return new_fd;
2319 +}
2320 diff --git a/ipc.h b/ipc.h
2321 new file mode 100644
2322 index 0000000..e3b5bba
2323 --- /dev/null
2324 +++ b/ipc.h
2325 @@ -0,0 +1,320 @@
2326 +#ifndef IPC_H_
2327 +#define IPC_H_
2328 +
2329 +#include <stdint.h>
2330 +#include <sys/epoll.h>
2331 +#include <yajl/yajl_gen.h>
2332 +
2333 +#include "IPCClient.h"
2334 +
2335 +// clang-format off
2336 +#define IPC_MAGIC "DWM-IPC"
2337 +#define IPC_MAGIC_ARR { 'D', 'W', 'M', '-', 'I', 'P', 'C'}
2338 +#define IPC_MAGIC_LEN 7 // Not including null char
2339 +
2340 +#define IPCCOMMAND(FUNC, ARGC, TYPES) \
2341 + { #FUNC, {FUNC }, ARGC, (ArgType[ARGC])TYPES }
2342 +// clang-format on
2343 +
2344 +typedef enum IPCMessageType {
2345 + IPC_TYPE_RUN_COMMAND = 0,
2346 + IPC_TYPE_GET_MONITORS = 1,
2347 + IPC_TYPE_GET_TAGS = 2,
2348 + IPC_TYPE_GET_LAYOUTS = 3,
2349 + IPC_TYPE_GET_DWM_CLIENT = 4,
2350 + IPC_TYPE_SUBSCRIBE = 5,
2351 + IPC_TYPE_EVENT = 6
2352 +} IPCMessageType;
2353 +
2354 +typedef enum IPCEvent {
2355 + IPC_EVENT_TAG_CHANGE = 1 << 0,
2356 + IPC_EVENT_CLIENT_FOCUS_CHANGE = 1 << 1,
2357 + IPC_EVENT_LAYOUT_CHANGE = 1 << 2,
2358 + IPC_EVENT_MONITOR_FOCUS_CHANGE = 1 << 3,
2359 + IPC_EVENT_FOCUSED_TITLE_CHANGE = 1 << 4,
2360 + IPC_EVENT_FOCUSED_STATE_CHANGE = 1 << 5
2361 +} IPCEvent;
2362 +
2363 +typedef enum IPCSubscriptionAction {
2364 + IPC_ACTION_UNSUBSCRIBE = 0,
2365 + IPC_ACTION_SUBSCRIBE = 1
2366 +} IPCSubscriptionAction;
2367 +
2368 +/**
2369 + * Every IPC packet starts with this structure
2370 + */
2371 +typedef struct dwm_ipc_header {
2372 + uint8_t magic[IPC_MAGIC_LEN];
2373 + uint32_t size;
2374 + uint8_t type;
2375 +} __attribute((packed)) dwm_ipc_header_t;
2376 +
2377 +typedef enum ArgType {
2378 + ARG_TYPE_NONE = 0,
2379 + ARG_TYPE_UINT = 1,
2380 + ARG_TYPE_SINT = 2,
2381 + ARG_TYPE_FLOAT = 3,
2382 + ARG_TYPE_PTR = 4,
2383 + ARG_TYPE_STR = 5
2384 +} ArgType;
2385 +
2386 +/**
2387 + * An IPCCommand function can have either of these function signatures
2388 + */
2389 +typedef union ArgFunction {
2390 + void (*single_param)(const Arg *);
2391 + void (*array_param)(const Arg *, int);
2392 +} ArgFunction;
2393 +
2394 +typedef struct IPCCommand {
2395 + char *name;
2396 + ArgFunction func;
2397 + unsigned int argc;
2398 + ArgType *arg_types;
2399 +} IPCCommand;
2400 +
2401 +typedef struct IPCParsedCommand {
2402 + char *name;
2403 + Arg *args;
2404 + ArgType *arg_types;
2405 + unsigned int argc;
2406 +} IPCParsedCommand;
2407 +
2408 +/**
2409 + * Initialize the IPC socket and the IPC module
2410 + *
2411 + * @param socket_path Path to create the socket at
2412 + * @param epoll_fd File descriptor for epoll
2413 + * @param commands Address of IPCCommands array defined in config.h
2414 + * @param commands_len Length of commands[] array
2415 + *
2416 + * @return int The file descriptor of the socket if it was successfully created,
2417 + * -1 otherwise
2418 + */
2419 +int ipc_init(const char *socket_path, const int p_epoll_fd,
2420 + IPCCommand commands[], const int commands_len);
2421 +
2422 +/**
2423 + * Uninitialize the socket and module. Free allocated memory and restore static
2424 + * variables to their state before ipc_init
2425 + */
2426 +void ipc_cleanup();
2427 +
2428 +/**
2429 + * Get the file descriptor of the IPC socket
2430 + *
2431 + * @return int File descriptor of IPC socket, -1 if socket not created.
2432 + */
2433 +int ipc_get_sock_fd();
2434 +
2435 +/**
2436 + * Get address to IPCClient with specified file descriptor
2437 + *
2438 + * @param fd File descriptor of IPC Client
2439 + *
2440 + * @return Address to IPCClient with specified file descriptor, -1 otherwise
2441 + */
2442 +IPCClient *ipc_get_client(int fd);
2443 +
2444 +/**
2445 + * Check if an IPC client exists with the specified file descriptor
2446 + *
2447 + * @param fd File descriptor
2448 + *
2449 + * @return int 1 if client exists, 0 otherwise
2450 + */
2451 +int ipc_is_client_registered(int fd);
2452 +
2453 +/**
2454 + * Disconnect an IPCClient from the socket and remove the client from the list
2455 + * of known connected clients
2456 + *
2457 + * @param c Address of IPCClient
2458 + *
2459 + * @return 0 if the client's file descriptor was closed successfully, the
2460 + * result of executing close() on the file descriptor otherwise.
2461 + */
2462 +int ipc_drop_client(IPCClient *c);
2463 +
2464 +/**
2465 + * Accept an IPC Client requesting to connect to the socket and add it to the
2466 + * list of clients
2467 + *
2468 + * @return File descriptor of new client, -1 on error
2469 + */
2470 +int ipc_accept_client();
2471 +
2472 +/**
2473 + * Read an incoming message from an accepted IPC client
2474 + *
2475 + * @param c Address of IPCClient
2476 + * @param msg_type Address to IPCMessageType variable which will be assigned
2477 + * the message type of the received message
2478 + * @param msg_size Address to uint32_t variable which will be assigned the size
2479 + * of the received message
2480 + * @param msg Address to char* variable which will be assigned the address of
2481 + * the received message. This must be freed using free().
2482 + *
2483 + * @return 0 on success, -1 on error reading message, -2 if reading the message
2484 + * resulted in EAGAIN, EINTR, or EWOULDBLOCK.
2485 + */
2486 +int ipc_read_client(IPCClient *c, IPCMessageType *msg_type, uint32_t *msg_size,
2487 + char **msg);
2488 +
2489 +/**
2490 + * Write any pending buffer of the client to the client's socket
2491 + *
2492 + * @param c Client whose buffer to write
2493 + *
2494 + * @return Number of bytes written >= 0, -1 otherwise. errno will still be set
2495 + * from the write operation.
2496 + */
2497 +ssize_t ipc_write_client(IPCClient *c);
2498 +
2499 +/**
2500 + * Prepare a message in the specified client's buffer.
2501 + *
2502 + * @param c Client to prepare message for
2503 + * @param msg_type Type of message to prepare
2504 + * @param msg_size Size of the message in bytes. Should not exceed
2505 + * MAX_MESSAGE_SIZE
2506 + * @param msg Message to prepare (not including header). This pointer can be
2507 + * freed after the function invocation.
2508 + */
2509 +void ipc_prepare_send_message(IPCClient *c, const IPCMessageType msg_type,
2510 + const uint32_t msg_size, const char *msg);
2511 +
2512 +/**
2513 + * Prepare an error message in the specified client's buffer
2514 + *
2515 + * @param c Client to prepare message for
2516 + * @param msg_type Type of message
2517 + * @param format Format string following vsprintf
2518 + * @param ... Arguments for format string
2519 + */
2520 +void ipc_prepare_reply_failure(IPCClient *c, IPCMessageType msg_type,
2521 + const char *format, ...);
2522 +
2523 +/**
2524 + * Prepare a success message in the specified client's buffer
2525 + *
2526 + * @param c Client to prepare message for
2527 + * @param msg_type Type of message
2528 + */
2529 +void ipc_prepare_reply_success(IPCClient *c, IPCMessageType msg_type);
2530 +
2531 +/**
2532 + * Send a tag_change_event to all subscribers. Should be called only when there
2533 + * has been a tag state change.
2534 + *
2535 + * @param mon_num The index of the monitor (Monitor.num property)
2536 + * @param old_state The old tag state
2537 + * @param new_state The new (now current) tag state
2538 + */
2539 +void ipc_tag_change_event(const int mon_num, TagState old_state,
2540 + TagState new_state);
2541 +
2542 +/**
2543 + * Send a client_focus_change_event to all subscribers. Should be called only
2544 + * when the client focus changes.
2545 + *
2546 + * @param mon_num The index of the monitor (Monitor.num property)
2547 + * @param old_client The old DWM client selection (Monitor.oldsel)
2548 + * @param new_client The new (now current) DWM client selection
2549 + */
2550 +void ipc_client_focus_change_event(const int mon_num, Client *old_client,
2551 + Client *new_client);
2552 +
2553 +/**
2554 + * Send a layout_change_event to all subscribers. Should be called only
2555 + * when there has been a layout change.
2556 + *
2557 + * @param mon_num The index of the monitor (Monitor.num property)
2558 + * @param old_symbol The old layout symbol
2559 + * @param old_layout Address to the old Layout
2560 + * @param new_symbol The new (now current) layout symbol
2561 + * @param new_layout Address to the new Layout
2562 + */
2563 +void ipc_layout_change_event(const int mon_num, const char *old_symbol,
2564 + const Layout *old_layout, const char *new_symbol,
2565 + const Layout *new_layout);
2566 +
2567 +/**
2568 + * Send a monitor_focus_change_event to all subscribers. Should be called only
2569 + * when the monitor focus changes.
2570 + *
2571 + * @param last_mon_num The index of the previously selected monitor
2572 + * @param new_mon_num The index of the newly selected monitor
2573 + */
2574 +void ipc_monitor_focus_change_event(const int last_mon_num,
2575 + const int new_mon_num);
2576 +
2577 +/**
2578 + * Send a focused_title_change_event to all subscribers. Should only be called
2579 + * if a selected client has a title change.
2580 + *
2581 + * @param mon_num Index of the client's monitor
2582 + * @param client_id Window XID of client
2583 + * @param old_name Old name of the client window
2584 + * @param new_name New name of the client window
2585 + */
2586 +void ipc_focused_title_change_event(const int mon_num, const Window client_id,
2587 + const char *old_name, const char *new_name);
2588 +
2589 +/**
2590 + * Send a focused_state_change_event to all subscribers. Should only be called
2591 + * if a selected client has a state change.
2592 + *
2593 + * @param mon_num Index of the client's monitor
2594 + * @param client_id Window XID of client
2595 + * @param old_state Old state of the client
2596 + * @param new_state New state of the client
2597 + */
2598 +void ipc_focused_state_change_event(const int mon_num, const Window client_id,
2599 + const ClientState *old_state,
2600 + const ClientState *new_state);
2601 +/**
2602 + * Check to see if an event has occured and call the *_change_event functions
2603 + * accordingly
2604 + *
2605 + * @param mons Address of Monitor pointing to start of linked list
2606 + * @param lastselmon Address of pointer to previously selected monitor
2607 + * @param selmon Address of selected Monitor
2608 + */
2609 +void ipc_send_events(Monitor *mons, Monitor **lastselmon, Monitor *selmon);
2610 +
2611 +/**
2612 + * Handle an epoll event caused by a registered IPC client. Read, process, and
2613 + * handle any received messages from clients. Write pending buffer to client if
2614 + * the client is ready to receive messages. Drop clients that have sent an
2615 + * EPOLLHUP.
2616 + *
2617 + * @param ev Associated epoll event returned by epoll_wait
2618 + * @param mons Address of Monitor pointing to start of linked list
2619 + * @param selmon Address of selected Monitor
2620 + * @param lastselmon Address of pointer to previously selected monitor
2621 + * @param tags Array of tag names
2622 + * @param tags_len Length of tags array
2623 + * @param layouts Array of available layouts
2624 + * @param layouts_len Length of layouts array
2625 + *
2626 + * @return 0 if event was successfully handled, -1 on any error receiving
2627 + * or handling incoming messages or unhandled epoll event.
2628 + */
2629 +int ipc_handle_client_epoll_event(struct epoll_event *ev, Monitor *mons,
2630 + Monitor **lastselmon, Monitor *selmon,
2631 + const char *tags[], const int tags_len,
2632 + const Layout *layouts, const int layouts_len);
2633 +
2634 +/**
2635 + * Handle an epoll event caused by the IPC socket. This function only handles an
2636 + * EPOLLIN event indicating a new client requesting to connect to the socket.
2637 + *
2638 + * @param ev Associated epoll event returned by epoll_wait
2639 + *
2640 + * @return 0, if the event was successfully handled, -1 if not an EPOLLIN event
2641 + * or if a new IPC client connection request could not be accepted.
2642 + */
2643 +int ipc_handle_socket_epoll_event(struct epoll_event *ev);
2644 +
2645 +#endif /* IPC_H_ */
2646 diff --git a/util.c b/util.c
2647 index fe044fc..dca4794 100644
2648 --- a/util.c
2649 +++ b/util.c
2650 @@ -3,6 +3,8 @@
2651 #include <stdio.h>
2652 #include <stdlib.h>
2653 #include <string.h>
2654 +#include <errno.h>
2655 +#include <sys/stat.h>
2656
2657 #include "util.h"
2658
2659 @@ -33,3 +35,136 @@ die(const char *fmt, ...) {
2660
2661 exit(1);
2662 }
2663 +
2664 +int
2665 +normalizepath(const char *path, char **normal)
2666 +{
2667 + size_t len = strlen(path);
2668 + *normal = (char *)malloc((len + 1) * sizeof(char));
2669 + const char *walk = path;
2670 + const char *match;
2671 + size_t newlen = 0;
2672 +
2673 + while ((match = strchr(walk, '/'))) {
2674 + // Copy everything between match and walk
2675 + strncpy(*normal + newlen, walk, match - walk);
2676 + newlen += match - walk;
2677 + walk += match - walk;
2678 +
2679 + // Skip all repeating slashes
2680 + while (*walk == '/')
2681 + walk++;
2682 +
2683 + // If not last character in path
2684 + if (walk != path + len)
2685 + (*normal)[newlen++] = '/';
2686 + }
2687 +
2688 + (*normal)[newlen++] = '\0';
2689 +
2690 + // Copy remaining path
2691 + strcat(*normal, walk);
2692 + newlen += strlen(walk);
2693 +
2694 + *normal = (char *)realloc(*normal, newlen * sizeof(char));
2695 +
2696 + return 0;
2697 +}
2698 +
2699 +int
2700 +parentdir(const char *path, char **parent)
2701 +{
2702 + char *normal;
2703 + char *walk;
2704 +
2705 + normalizepath(path, &normal);
2706 +
2707 + // Pointer to last '/'
2708 + if (!(walk = strrchr(normal, '/'))) {
2709 + free(normal);
2710 + return -1;
2711 + }
2712 +
2713 + // Get path up to last '/'
2714 + size_t len = walk - normal;
2715 + *parent = (char *)malloc((len + 1) * sizeof(char));
2716 +
2717 + // Copy path up to last '/'
2718 + strncpy(*parent, normal, len);
2719 + // Add null char
2720 + (*parent)[len] = '\0';
2721 +
2722 + free(normal);
2723 +
2724 + return 0;
2725 +}
2726 +
2727 +int
2728 +mkdirp(const char *path)
2729 +{
2730 + char *normal;
2731 + char *walk;
2732 + size_t normallen;
2733 +
2734 + normalizepath(path, &normal);
2735 + normallen = strlen(normal);
2736 + walk = normal;
2737 +
2738 + while (walk < normal + normallen + 1) {
2739 + // Get length from walk to next /
2740 + size_t n = strcspn(walk, "/");
2741 +
2742 + // Skip path /
2743 + if (n == 0) {
2744 + walk++;
2745 + continue;
2746 + }
2747 +
2748 + // Length of current path segment
2749 + size_t curpathlen = walk - normal + n;
2750 + char curpath[curpathlen + 1];
2751 + struct stat s;
2752 +
2753 + // Copy path segment to stat
2754 + strncpy(curpath, normal, curpathlen);
2755 + strcpy(curpath + curpathlen, "");
2756 + int res = stat(curpath, &s);
2757 +
2758 + if (res < 0) {
2759 + if (errno == ENOENT) {
2760 + DEBUG("Making directory %s\n", curpath);
2761 + if (mkdir(curpath, 0700) < 0) {
2762 + fprintf(stderr, "Failed to make directory %s\n", curpath);
2763 + perror("");
2764 + free(normal);
2765 + return -1;
2766 + }
2767 + } else {
2768 + fprintf(stderr, "Error statting directory %s\n", curpath);
2769 + perror("");
2770 + free(normal);
2771 + return -1;
2772 + }
2773 + }
2774 +
2775 + // Continue to next path segment
2776 + walk += n;
2777 + }
2778 +
2779 + free(normal);
2780 +
2781 + return 0;
2782 +}
2783 +
2784 +int
2785 +nullterminate(char **str, size_t *len)
2786 +{
2787 + if ((*str)[*len - 1] == '\0')
2788 + return 0;
2789 +
2790 + (*len)++;
2791 + *str = (char*)realloc(*str, *len * sizeof(char));
2792 + (*str)[*len - 1] = '\0';
2793 +
2794 + return 0;
2795 +}
2796 diff --git a/util.h b/util.h
2797 index f633b51..73a238e 100644
2798 --- a/util.h
2799 +++ b/util.h
2800 @@ -4,5 +4,15 @@
2801 #define MIN(A, B) ((A) < (B) ? (A) : (B))
2802 #define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
2803
2804 +#ifdef _DEBUG
2805 +#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
2806 +#else
2807 +#define DEBUG(...)
2808 +#endif
2809 +
2810 void die(const char *fmt, ...);
2811 void *ecalloc(size_t nmemb, size_t size);
2812 +int normalizepath(const char *path, char **normal);
2813 +int mkdirp(const char *path);
2814 +int parentdir(const char *path, char **parent);
2815 +int nullterminate(char **str, size_t *len);
2816 diff --git a/yajl_dumps.c b/yajl_dumps.c
2817 new file mode 100644
2818 index 0000000..8bf9688
2819 --- /dev/null
2820 +++ b/yajl_dumps.c
2821 @@ -0,0 +1,351 @@
2822 +#include "yajl_dumps.h"
2823 +
2824 +#include <stdint.h>
2825 +
2826 +int
2827 +dump_tag(yajl_gen gen, const char *name, const int tag_mask)
2828 +{
2829 + // clang-format off
2830 + YMAP(
2831 + YSTR("bit_mask"); YINT(tag_mask);
2832 + YSTR("name"); YSTR(name);
2833 + )
2834 + // clang-format on
2835 +
2836 + return 0;
2837 +}
2838 +
2839 +int
2840 +dump_tags(yajl_gen gen, const char *tags[], int tags_len)
2841 +{
2842 + // clang-format off
2843 + YARR(
2844 + for (int i = 0; i < tags_len; i++)
2845 + dump_tag(gen, tags[i], 1 << i);
2846 + )
2847 + // clang-format on
2848 +
2849 + return 0;
2850 +}
2851 +
2852 +int
2853 +dump_client(yajl_gen gen, Client *c)
2854 +{
2855 + // clang-format off
2856 + YMAP(
2857 + YSTR("name"); YSTR(c->name);
2858 + YSTR("tags"); YINT(c->tags);
2859 + YSTR("window_id"); YINT(c->win);
2860 + YSTR("monitor_number"); YINT(c->mon->num);
2861 +
2862 + YSTR("geometry"); YMAP(
2863 + YSTR("current"); YMAP (
2864 + YSTR("x"); YINT(c->x);
2865 + YSTR("y"); YINT(c->y);
2866 + YSTR("width"); YINT(c->w);
2867 + YSTR("height"); YINT(c->h);
2868 + )
2869 + YSTR("old"); YMAP(
2870 + YSTR("x"); YINT(c->oldx);
2871 + YSTR("y"); YINT(c->oldy);
2872 + YSTR("width"); YINT(c->oldw);
2873 + YSTR("height"); YINT(c->oldh);
2874 + )
2875 + )
2876 +
2877 + YSTR("size_hints"); YMAP(
2878 + YSTR("base"); YMAP(
2879 + YSTR("width"); YINT(c->basew);
2880 + YSTR("height"); YINT(c->baseh);
2881 + )
2882 + YSTR("step"); YMAP(
2883 + YSTR("width"); YINT(c->incw);
2884 + YSTR("height"); YINT(c->inch);
2885 + )
2886 + YSTR("max"); YMAP(
2887 + YSTR("width"); YINT(c->maxw);
2888 + YSTR("height"); YINT(c->maxh);
2889 + )
2890 + YSTR("min"); YMAP(
2891 + YSTR("width"); YINT(c->minw);
2892 + YSTR("height"); YINT(c->minh);
2893 + )
2894 + YSTR("aspect_ratio"); YMAP(
2895 + YSTR("min"); YDOUBLE(c->mina);
2896 + YSTR("max"); YDOUBLE(c->maxa);
2897 + )
2898 + )
2899 +
2900 + YSTR("border_width"); YMAP(
2901 + YSTR("current"); YINT(c->bw);
2902 + YSTR("old"); YINT(c->oldbw);
2903 + )
2904 +
2905 + YSTR("states"); YMAP(
2906 + YSTR("is_fixed"); YBOOL(c->isfixed);
2907 + YSTR("is_floating"); YBOOL(c->isfloating);
2908 + YSTR("is_urgent"); YBOOL(c->isurgent);
2909 + YSTR("never_focus"); YBOOL(c->neverfocus);
2910 + YSTR("old_state"); YBOOL(c->oldstate);
2911 + YSTR("is_fullscreen"); YBOOL(c->isfullscreen);
2912 + )
2913 + )
2914 + // clang-format on
2915 +
2916 + return 0;
2917 +}
2918 +
2919 +int
2920 +dump_monitor(yajl_gen gen, Monitor *mon, int is_selected)
2921 +{
2922 + // clang-format off
2923 + YMAP(
2924 + YSTR("master_factor"); YDOUBLE(mon->mfact);
2925 + YSTR("num_master"); YINT(mon->nmaster);
2926 + YSTR("num"); YINT(mon->num);
2927 + YSTR("is_selected"); YBOOL(is_selected);
2928 +
2929 + YSTR("monitor_geometry"); YMAP(
2930 + YSTR("x"); YINT(mon->mx);
2931 + YSTR("y"); YINT(mon->my);
2932 + YSTR("width"); YINT(mon->mw);
2933 + YSTR("height"); YINT(mon->mh);
2934 + )
2935 +
2936 + YSTR("window_geometry"); YMAP(
2937 + YSTR("x"); YINT(mon->wx);
2938 + YSTR("y"); YINT(mon->wy);
2939 + YSTR("width"); YINT(mon->ww);
2940 + YSTR("height"); YINT(mon->wh);
2941 + )
2942 +
2943 + YSTR("tagset"); YMAP(
2944 + YSTR("current"); YINT(mon->tagset[mon->seltags]);
2945 + YSTR("old"); YINT(mon->tagset[mon->seltags ^ 1]);
2946 + )
2947 +
2948 + YSTR("tag_state"); dump_tag_state(gen, mon->tagstate);
2949 +
2950 + YSTR("clients"); YMAP(
2951 + YSTR("selected"); YINT(mon->sel ? mon->sel->win : 0);
2952 + YSTR("stack"); YARR(
2953 + for (Client* c = mon->stack; c; c = c->snext)
2954 + YINT(c->win);
2955 + )
2956 + YSTR("all"); YARR(
2957 + for (Client* c = mon->clients; c; c = c->next)
2958 + YINT(c->win);
2959 + )
2960 + )
2961 +
2962 + YSTR("layout"); YMAP(
2963 + YSTR("symbol"); YMAP(
2964 + YSTR("current"); YSTR(mon->ltsymbol);
2965 + YSTR("old"); YSTR(mon->lastltsymbol);
2966 + )
2967 + YSTR("address"); YMAP(
2968 + YSTR("current"); YINT((uintptr_t)mon->lt[mon->sellt]);
2969 + YSTR("old"); YINT((uintptr_t)mon->lt[mon->sellt ^ 1]);
2970 + )
2971 + )
2972 +
2973 + YSTR("bar"); YMAP(
2974 + YSTR("y"); YINT(mon->by);
2975 + YSTR("is_shown"); YBOOL(mon->showbar);
2976 + YSTR("is_top"); YBOOL(mon->topbar);
2977 + YSTR("window_id"); YINT(mon->barwin);
2978 + )
2979 + )
2980 + // clang-format on
2981 +
2982 + return 0;
2983 +}
2984 +
2985 +int
2986 +dump_monitors(yajl_gen gen, Monitor *mons, Monitor *selmon)
2987 +{
2988 + // clang-format off
2989 + YARR(
2990 + for (Monitor *mon = mons; mon; mon = mon->next) {
2991 + if (mon == selmon)
2992 + dump_monitor(gen, mon, 1);
2993 + else
2994 + dump_monitor(gen, mon, 0);
2995 + }
2996 + )
2997 + // clang-format on
2998 +
2999 + return 0;
3000 +}
3001 +
3002 +int
3003 +dump_layouts(yajl_gen gen, const Layout layouts[], const int layouts_len)
3004 +{
3005 + // clang-format off
3006 + YARR(
3007 + for (int i = 0; i < layouts_len; i++) {
3008 + YMAP(
3009 + // Check for a NULL pointer. The cycle layouts patch adds an entry at
3010 + // the end of the layouts array with a NULL pointer for the symbol
3011 + YSTR("symbol"); YSTR((layouts[i].symbol ? layouts[i].symbol : ""));
3012 + YSTR("address"); YINT((uintptr_t)(layouts + i));
3013 + )
3014 + }
3015 + )
3016 + // clang-format on
3017 +
3018 + return 0;
3019 +}
3020 +
3021 +int
3022 +dump_tag_state(yajl_gen gen, TagState state)
3023 +{
3024 + // clang-format off
3025 + YMAP(
3026 + YSTR("selected"); YINT(state.selected);
3027 + YSTR("occupied"); YINT(state.occupied);
3028 + YSTR("urgent"); YINT(state.urgent);
3029 + )
3030 + // clang-format on
3031 +
3032 + return 0;
3033 +}
3034 +
3035 +int
3036 +dump_tag_event(yajl_gen gen, int mon_num, TagState old_state,
3037 + TagState new_state)
3038 +{
3039 + // clang-format off
3040 + YMAP(
3041 + YSTR("tag_change_event"); YMAP(
3042 + YSTR("monitor_number"); YINT(mon_num);
3043 + YSTR("old_state"); dump_tag_state(gen, old_state);
3044 + YSTR("new_state"); dump_tag_state(gen, new_state);
3045 + )
3046 + )
3047 + // clang-format on
3048 +
3049 + return 0;
3050 +}
3051 +
3052 +int
3053 +dump_client_focus_change_event(yajl_gen gen, Client *old_client,
3054 + Client *new_client, int mon_num)
3055 +{
3056 + // clang-format off
3057 + YMAP(
3058 + YSTR("client_focus_change_event"); YMAP(
3059 + YSTR("monitor_number"); YINT(mon_num);
3060 + YSTR("old_win_id"); old_client == NULL ? YNULL() : YINT(old_client->win);
3061 + YSTR("new_win_id"); new_client == NULL ? YNULL() : YINT(new_client->win);
3062 + )
3063 + )
3064 + // clang-format on
3065 +
3066 + return 0;
3067 +}
3068 +
3069 +int
3070 +dump_layout_change_event(yajl_gen gen, const int mon_num,
3071 + const char *old_symbol, const Layout *old_layout,
3072 + const char *new_symbol, const Layout *new_layout)
3073 +{
3074 + // clang-format off
3075 + YMAP(
3076 + YSTR("layout_change_event"); YMAP(
3077 + YSTR("monitor_number"); YINT(mon_num);
3078 + YSTR("old_symbol"); YSTR(old_symbol);
3079 + YSTR("old_address"); YINT((uintptr_t)old_layout);
3080 + YSTR("new_symbol"); YSTR(new_symbol);
3081 + YSTR("new_address"); YINT((uintptr_t)new_layout);
3082 + )
3083 + )
3084 + // clang-format on
3085 +
3086 + return 0;
3087 +}
3088 +
3089 +int
3090 +dump_monitor_focus_change_event(yajl_gen gen, const int last_mon_num,
3091 + const int new_mon_num)
3092 +{
3093 + // clang-format off
3094 + YMAP(
3095 + YSTR("monitor_focus_change_event"); YMAP(
3096 + YSTR("old_monitor_number"); YINT(last_mon_num);
3097 + YSTR("new_monitor_number"); YINT(new_mon_num);
3098 + )
3099 + )
3100 + // clang-format on
3101 +
3102 + return 0;
3103 +}
3104 +
3105 +int
3106 +dump_focused_title_change_event(yajl_gen gen, const int mon_num,
3107 + const Window client_id, const char *old_name,
3108 + const char *new_name)
3109 +{
3110 + // clang-format off
3111 + YMAP(
3112 + YSTR("focused_title_change_event"); YMAP(
3113 + YSTR("monitor_number"); YINT(mon_num);
3114 + YSTR("client_window_id"); YINT(client_id);
3115 + YSTR("old_name"); YSTR(old_name);
3116 + YSTR("new_name"); YSTR(new_name);
3117 + )
3118 + )
3119 + // clang-format on
3120 +
3121 + return 0;
3122 +}
3123 +
3124 +int
3125 +dump_client_state(yajl_gen gen, const ClientState *state)
3126 +{
3127 + // clang-format off
3128 + YMAP(
3129 + YSTR("old_state"); YBOOL(state->oldstate);
3130 + YSTR("is_fixed"); YBOOL(state->isfixed);
3131 + YSTR("is_floating"); YBOOL(state->isfloating);
3132 + YSTR("is_fullscreen"); YBOOL(state->isfullscreen);
3133 + YSTR("is_urgent"); YBOOL(state->isurgent);
3134 + YSTR("never_focus"); YBOOL(state->neverfocus);
3135 + )
3136 + // clang-format on
3137 +
3138 + return 0;
3139 +}
3140 +
3141 +int
3142 +dump_focused_state_change_event(yajl_gen gen, const int mon_num,
3143 + const Window client_id,
3144 + const ClientState *old_state,
3145 + const ClientState *new_state)
3146 +{
3147 + // clang-format off
3148 + YMAP(
3149 + YSTR("focused_state_change_event"); YMAP(
3150 + YSTR("monitor_number"); YINT(mon_num);
3151 + YSTR("client_window_id"); YINT(client_id);
3152 + YSTR("old_state"); dump_client_state(gen, old_state);
3153 + YSTR("new_state"); dump_client_state(gen, new_state);
3154 + )
3155 + )
3156 + // clang-format on
3157 +
3158 + return 0;
3159 +}
3160 +
3161 +int
3162 +dump_error_message(yajl_gen gen, const char *reason)
3163 +{
3164 + // clang-format off
3165 + YMAP(
3166 + YSTR("result"); YSTR("error");
3167 + YSTR("reason"); YSTR(reason);
3168 + )
3169 + // clang-format on
3170 +
3171 + return 0;
3172 +}
3173 diff --git a/yajl_dumps.h b/yajl_dumps.h
3174 new file mode 100644
3175 index 0000000..ee9948e
3176 --- /dev/null
3177 +++ b/yajl_dumps.h
3178 @@ -0,0 +1,65 @@
3179 +#ifndef YAJL_DUMPS_H_
3180 +#define YAJL_DUMPS_H_
3181 +
3182 +#include <string.h>
3183 +#include <yajl/yajl_gen.h>
3184 +
3185 +#define YSTR(str) yajl_gen_string(gen, (unsigned char *)str, strlen(str))
3186 +#define YINT(num) yajl_gen_integer(gen, num)
3187 +#define YDOUBLE(num) yajl_gen_double(gen, num)
3188 +#define YBOOL(v) yajl_gen_bool(gen, v)
3189 +#define YNULL() yajl_gen_null(gen)
3190 +#define YARR(body) \
3191 + { \
3192 + yajl_gen_array_open(gen); \
3193 + body; \
3194 + yajl_gen_array_close(gen); \
3195 + }
3196 +#define YMAP(body) \
3197 + { \
3198 + yajl_gen_map_open(gen); \
3199 + body; \
3200 + yajl_gen_map_close(gen); \
3201 + }
3202 +
3203 +int dump_tag(yajl_gen gen, const char *name, const int tag_mask);
3204 +
3205 +int dump_tags(yajl_gen gen, const char *tags[], int tags_len);
3206 +
3207 +int dump_client(yajl_gen gen, Client *c);
3208 +
3209 +int dump_monitor(yajl_gen gen, Monitor *mon, int is_selected);
3210 +
3211 +int dump_monitors(yajl_gen gen, Monitor *mons, Monitor *selmon);
3212 +
3213 +int dump_layouts(yajl_gen gen, const Layout layouts[], const int layouts_len);
3214 +
3215 +int dump_tag_state(yajl_gen gen, TagState state);
3216 +
3217 +int dump_tag_event(yajl_gen gen, int mon_num, TagState old_state,
3218 + TagState new_state);
3219 +
3220 +int dump_client_focus_change_event(yajl_gen gen, Client *old_client,
3221 + Client *new_client, int mon_num);
3222 +
3223 +int dump_layout_change_event(yajl_gen gen, const int mon_num,
3224 + const char *old_symbol, const Layout *old_layout,
3225 + const char *new_symbol, const Layout *new_layout);
3226 +
3227 +int dump_monitor_focus_change_event(yajl_gen gen, const int last_mon_num,
3228 + const int new_mon_num);
3229 +
3230 +int dump_focused_title_change_event(yajl_gen gen, const int mon_num,
3231 + const Window client_id,
3232 + const char *old_name, const char *new_name);
3233 +
3234 +int dump_client_state(yajl_gen gen, const ClientState *state);
3235 +
3236 +int dump_focused_state_change_event(yajl_gen gen, const int mon_num,
3237 + const Window client_id,
3238 + const ClientState *old_state,
3239 + const ClientState *new_state);
3240 +
3241 +int dump_error_message(yajl_gen gen, const char *reason);
3242 +
3243 +#endif // YAJL_DUMPS_H_
3244 --
3245 2.29.2
3246