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