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