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