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