dwm/ipc.c
mihirlad55 4a230d362a [PATCH] Add IPC support through a unix socket
This patch currently supports the following requests:
* Run custom commands with arguments (similar to key bind functions)
* Get monitor properties
* Get all available layouts
* Get available tags
* Get client properties
* Subscribe to tag change, client focus change, and layout change,
  monitor focus change, focused title change, and client state change
  events

This patch includes a dwm-msg cli program that supports all of the
above requests for easy integration into shell scripts.

The messages are sent in a JSON format to promote integration to
increase scriptability in languages like Python/JavaScript.

The patch requires YAJL for JSON parsing and a system with epoll
support. Portability is planned to be increased in the future.

This patch is best applied after all other patches to avoid merge
conflicts.

For more info on the IPC implementation and how to send/receive
messages, documentation can be found at
https://github.com/mihirlad55/dwm-ipc
2023-08-04 21:33:07 +02:00

1203 lines
33 KiB
C

#include "ipc.h"
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <yajl/yajl_gen.h>
#include <yajl/yajl_tree.h>
#include "util.h"
#include "yajl_dumps.h"
static struct sockaddr_un sockaddr;
static struct epoll_event sock_epoll_event;
static IPCClientList ipc_clients = NULL;
static int epoll_fd = -1;
static int sock_fd = -1;
static IPCCommand *ipc_commands;
static unsigned int ipc_commands_len;
// Max size is 1 MB
static const uint32_t MAX_MESSAGE_SIZE = 1000000;
static const int IPC_SOCKET_BACKLOG = 5;
/**
* Create IPC socket at specified path and return file descriptor to socket.
* This initializes the static variable sockaddr.
*/
static int
ipc_create_socket(const char *filename)
{
char *normal_filename;
char *parent;
const size_t addr_size = sizeof(struct sockaddr_un);
const int sock_type = SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC;
normalizepath(filename, &normal_filename);
// In case socket file exists
unlink(normal_filename);
// For portability clear the addr structure, since some implementations have
// nonstandard fields in the structure
memset(&sockaddr, 0, addr_size);
parentdir(normal_filename, &parent);
// Create parent directories
mkdirp(parent);
free(parent);
sockaddr.sun_family = AF_LOCAL;
strcpy(sockaddr.sun_path, normal_filename);
free(normal_filename);
sock_fd = socket(AF_LOCAL, sock_type, 0);
if (sock_fd == -1) {
fputs("Failed to create socket\n", stderr);
return -1;
}
DEBUG("Created socket at %s\n", sockaddr.sun_path);
if (bind(sock_fd, (const struct sockaddr *)&sockaddr, addr_size) == -1) {
fputs("Failed to bind socket\n", stderr);
return -1;
}
DEBUG("Socket binded\n");
if (listen(sock_fd, IPC_SOCKET_BACKLOG) < 0) {
fputs("Failed to listen for connections on socket\n", stderr);
return -1;
}
DEBUG("Now listening for connections on socket\n");
return sock_fd;
}
/**
* Internal function used to receive IPC messages from a given file descriptor.
*
* Returns -1 on error reading (could be EAGAIN or EINTR)
* Returns -2 if EOF before header could be read
* Returns -3 if invalid IPC header
* Returns -4 if message length exceeds MAX_MESSAGE_SIZE
*/
static int
ipc_recv_message(int fd, uint8_t *msg_type, uint32_t *reply_size,
uint8_t **reply)
{
uint32_t read_bytes = 0;
const int32_t to_read = sizeof(dwm_ipc_header_t);
char header[to_read];
char *walk = header;
// Try to read header
while (read_bytes < to_read) {
const ssize_t n = read(fd, header + read_bytes, to_read - read_bytes);
if (n == 0) {
if (read_bytes == 0) {
fprintf(stderr, "Unexpectedly reached EOF while reading header.");
fprintf(stderr,
"Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n",
read_bytes, to_read);
return -2;
} else {
fprintf(stderr, "Unexpectedly reached EOF while reading header.");
fprintf(stderr,
"Read %" PRIu32 " bytes, expected %" PRIu32 " total bytes.\n",
read_bytes, to_read);
return -3;
}
} else if (n == -1) {
// errno will still be set
return -1;
}
read_bytes += n;
}
// Check if magic string in header matches
if (memcmp(walk, IPC_MAGIC, IPC_MAGIC_LEN) != 0) {
fprintf(stderr, "Invalid magic string. Got '%.*s', expected '%s'\n",
IPC_MAGIC_LEN, walk, IPC_MAGIC);
return -3;
}
walk += IPC_MAGIC_LEN;
// Extract reply size
memcpy(reply_size, walk, sizeof(uint32_t));
walk += sizeof(uint32_t);
if (*reply_size > MAX_MESSAGE_SIZE) {
fprintf(stderr, "Message too long: %" PRIu32 " bytes. ", *reply_size);
fprintf(stderr, "Maximum message size is: %d\n", MAX_MESSAGE_SIZE);
return -4;
}
// Extract message type
memcpy(msg_type, walk, sizeof(uint8_t));
walk += sizeof(uint8_t);
if (*reply_size > 0)
(*reply) = malloc(*reply_size);
else
return 0;
read_bytes = 0;
while (read_bytes < *reply_size) {
const ssize_t n = read(fd, *reply + read_bytes, *reply_size - read_bytes);
if (n == 0) {
fprintf(stderr, "Unexpectedly reached EOF while reading payload.");
fprintf(stderr, "Read %" PRIu32 " bytes, expected %" PRIu32 " bytes.\n",
read_bytes, *reply_size);
free(*reply);
return -2;
} else if (n == -1) {
// TODO: Should we return and wait for another epoll event?
// This would require saving the partial read in some way.
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
free(*reply);
return -1;
}
read_bytes += n;
}
return 0;
}
/**
* Internal function used to write a buffer to a file descriptor
*
* Returns number of bytes written if successful write
* Returns 0 if no bytes were written due to EAGAIN or EWOULDBLOCK
* Returns -1 on unknown error trying to write, errno will carry over from
* write() call
*/
static ssize_t
ipc_write_message(int fd, const void *buf, size_t count)
{
size_t written = 0;
while (written < count) {
const ssize_t n = write(fd, (uint8_t *)buf + written, count - written);
if (n == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return written;
else if (errno == EINTR)
continue;
else
return n;
}
written += n;
DEBUG("Wrote %zu/%zu to client at fd %d\n", written, count, fd);
}
return written;
}
/**
* Initialization for generic event message. This is used to allocate the yajl
* handle, set yajl options, and in the future any other initialization that
* should occur for event messages.
*/
static void
ipc_event_init_message(yajl_gen *gen)
{
*gen = yajl_gen_alloc(NULL);
yajl_gen_config(*gen, yajl_gen_beautify, 1);
}
/**
* Prepares buffers of IPC subscribers of specified event using buffer from yajl
* handle.
*/
static void
ipc_event_prepare_send_message(yajl_gen gen, IPCEvent event)
{
const unsigned char *buffer;
size_t len = 0;
yajl_gen_get_buf(gen, &buffer, &len);
len++; // For null char
for (IPCClient *c = ipc_clients; c; c = c->next) {
if (c->subscriptions & event) {
DEBUG("Sending selected client change event to fd %d\n", c->fd);
ipc_prepare_send_message(c, IPC_TYPE_EVENT, len, (char *)buffer);
}
}
// Not documented, but this frees temp_buffer
yajl_gen_free(gen);
}
/**
* Initialization for generic reply message. This is used to allocate the yajl
* handle, set yajl options, and in the future any other initialization that
* should occur for reply messages.
*/
static void
ipc_reply_init_message(yajl_gen *gen)
{
*gen = yajl_gen_alloc(NULL);
yajl_gen_config(*gen, yajl_gen_beautify, 1);
}
/**
* Prepares the IPC client's buffer with a message using the buffer of the yajl
* handle.
*/
static void
ipc_reply_prepare_send_message(yajl_gen gen, IPCClient *c,
IPCMessageType msg_type)
{
const unsigned char *buffer;
size_t len = 0;
yajl_gen_get_buf(gen, &buffer, &len);
len++; // For null char
ipc_prepare_send_message(c, msg_type, len, (const char *)buffer);
// Not documented, but this frees temp_buffer
yajl_gen_free(gen);
}
/**
* Find the IPCCommand with the specified name
*
* Returns 0 if a command with the specified name was found
* Returns -1 if a command with the specified name could not be found
*/
static int
ipc_get_ipc_command(const char *name, IPCCommand *ipc_command)
{
for (int i = 0; i < ipc_commands_len; i++) {
if (strcmp(ipc_commands[i].name, name) == 0) {
*ipc_command = ipc_commands[i];
return 0;
}
}
return -1;
}
/**
* Parse a IPC_TYPE_RUN_COMMAND message from a client. This function extracts
* the arguments, argument count, argument types, and command name and returns
* the parsed information as an IPCParsedCommand. If this function returns
* successfully, the parsed_command must be freed using
* ipc_free_parsed_command_members.
*
* Returns 0 if the message was successfully parsed
* Returns -1 otherwise
*/
static int
ipc_parse_run_command(char *msg, IPCParsedCommand *parsed_command)
{
char error_buffer[1000];
yajl_val parent = yajl_tree_parse(msg, error_buffer, 1000);
if (parent == NULL) {
fputs("Failed to parse command from client\n", stderr);
fprintf(stderr, "%s\n", error_buffer);
fprintf(stderr, "Tried to parse: %s\n", msg);
return -1;
}
// Format:
// {
// "command": "<command name>"
// "args": [ "arg1", "arg2", ... ]
// }
const char *command_path[] = {"command", 0};
yajl_val command_val = yajl_tree_get(parent, command_path, yajl_t_string);
if (command_val == NULL) {
fputs("No command key found in client message\n", stderr);
yajl_tree_free(parent);
return -1;
}
const char *command_name = YAJL_GET_STRING(command_val);
size_t command_name_len = strlen(command_name);
parsed_command->name = (char *)malloc((command_name_len + 1) * sizeof(char));
strcpy(parsed_command->name, command_name);
DEBUG("Received command: %s\n", parsed_command->name);
const char *args_path[] = {"args", 0};
yajl_val args_val = yajl_tree_get(parent, args_path, yajl_t_array);
if (args_val == NULL) {
fputs("No args key found in client message\n", stderr);
yajl_tree_free(parent);
return -1;
}
unsigned int *argc = &parsed_command->argc;
Arg **args = &parsed_command->args;
ArgType **arg_types = &parsed_command->arg_types;
*argc = args_val->u.array.len;
// If no arguments are specified, make a dummy argument to pass to the
// function. This is just the way dwm's void(Arg*) functions are setup.
if (*argc == 0) {
*args = (Arg *)malloc(sizeof(Arg));
*arg_types = (ArgType *)malloc(sizeof(ArgType));
(*arg_types)[0] = ARG_TYPE_NONE;
(*args)[0].i = 0;
(*argc)++;
} else if (*argc > 0) {
*args = (Arg *)calloc(*argc, sizeof(Arg));
*arg_types = (ArgType *)malloc(*argc * sizeof(ArgType));
for (int i = 0; i < *argc; i++) {
yajl_val arg_val = args_val->u.array.values[i];
if (YAJL_IS_NUMBER(arg_val)) {
if (YAJL_IS_INTEGER(arg_val)) {
// Any values below 0 must be a signed int
if (YAJL_GET_INTEGER(arg_val) < 0) {
(*args)[i].i = YAJL_GET_INTEGER(arg_val);
(*arg_types)[i] = ARG_TYPE_SINT;
DEBUG("i=%ld\n", (*args)[i].i);
// Any values above 0 should be an unsigned int
} else if (YAJL_GET_INTEGER(arg_val) >= 0) {
(*args)[i].ui = YAJL_GET_INTEGER(arg_val);
(*arg_types)[i] = ARG_TYPE_UINT;
DEBUG("ui=%ld\n", (*args)[i].i);
}
// If the number is not an integer, it must be a float
} else {
(*args)[i].f = (float)YAJL_GET_DOUBLE(arg_val);
(*arg_types)[i] = ARG_TYPE_FLOAT;
DEBUG("f=%f\n", (*args)[i].f);
// If argument is not a number, it must be a string
}
} else if (YAJL_IS_STRING(arg_val)) {
char *arg_s = YAJL_GET_STRING(arg_val);
size_t arg_s_size = (strlen(arg_s) + 1) * sizeof(char);
(*args)[i].v = (char *)malloc(arg_s_size);
(*arg_types)[i] = ARG_TYPE_STR;
strcpy((char *)(*args)[i].v, arg_s);
}
}
}
yajl_tree_free(parent);
return 0;
}
/**
* Free the members of a IPCParsedCommand struct
*/
static void
ipc_free_parsed_command_members(IPCParsedCommand *command)
{
for (int i = 0; i < command->argc; i++) {
if (command->arg_types[i] == ARG_TYPE_STR) free((void *)command->args[i].v);
}
free(command->args);
free(command->arg_types);
free(command->name);
}
/**
* Check if the given arguments are the correct length and type. Also do any
* casting to correct the types.
*
* Returns 0 if the arguments were the correct length and types
* Returns -1 if the argument count doesn't match
* Returns -2 if the argument types don't match
*/
static int
ipc_validate_run_command(IPCParsedCommand *parsed, const IPCCommand actual)
{
if (actual.argc != parsed->argc) return -1;
for (int i = 0; i < parsed->argc; i++) {
ArgType ptype = parsed->arg_types[i];
ArgType atype = actual.arg_types[i];
if (ptype != atype) {
if (ptype == ARG_TYPE_UINT && atype == ARG_TYPE_PTR)
// If this argument is supposed to be a void pointer, cast it
parsed->args[i].v = (void *)parsed->args[i].ui;
else if (ptype == ARG_TYPE_UINT && atype == ARG_TYPE_SINT)
// If this argument is supposed to be a signed int, cast it
parsed->args[i].i = parsed->args[i].ui;
else
return -2;
}
}
return 0;
}
/**
* Convert event name to their IPCEvent equivalent enum value
*
* Returns 0 if a valid event name was given
* Returns -1 otherwise
*/
static int
ipc_event_stoi(const char *subscription, IPCEvent *event)
{
if (strcmp(subscription, "tag_change_event") == 0)
*event = IPC_EVENT_TAG_CHANGE;
else if (strcmp(subscription, "client_focus_change_event") == 0)
*event = IPC_EVENT_CLIENT_FOCUS_CHANGE;
else if (strcmp(subscription, "layout_change_event") == 0)
*event = IPC_EVENT_LAYOUT_CHANGE;
else if (strcmp(subscription, "monitor_focus_change_event") == 0)
*event = IPC_EVENT_MONITOR_FOCUS_CHANGE;
else if (strcmp(subscription, "focused_title_change_event") == 0)
*event = IPC_EVENT_FOCUSED_TITLE_CHANGE;
else if (strcmp(subscription, "focused_state_change_event") == 0)
*event = IPC_EVENT_FOCUSED_STATE_CHANGE;
else
return -1;
return 0;
}
/**
* Parse a IPC_TYPE_SUBSCRIBE message from a client. This function extracts the
* event name and the subscription action from the message.
*
* Returns 0 if message was successfully parsed
* Returns -1 otherwise
*/
static int
ipc_parse_subscribe(const char *msg, IPCSubscriptionAction *subscribe,
IPCEvent *event)
{
char error_buffer[100];
yajl_val parent = yajl_tree_parse((char *)msg, error_buffer, 100);
if (parent == NULL) {
fputs("Failed to parse command from client\n", stderr);
fprintf(stderr, "%s\n", error_buffer);
return -1;
}
// Format:
// {
// "event": "<event name>"
// "action": "<subscribe|unsubscribe>"
// }
const char *event_path[] = {"event", 0};
yajl_val event_val = yajl_tree_get(parent, event_path, yajl_t_string);
if (event_val == NULL) {
fputs("No 'event' key found in client message\n", stderr);
return -1;
}
const char *event_str = YAJL_GET_STRING(event_val);
DEBUG("Received event: %s\n", event_str);
if (ipc_event_stoi(event_str, event) < 0) return -1;
const char *action_path[] = {"action", 0};
yajl_val action_val = yajl_tree_get(parent, action_path, yajl_t_string);
if (action_val == NULL) {
fputs("No 'action' key found in client message\n", stderr);
return -1;
}
const char *action = YAJL_GET_STRING(action_val);
if (strcmp(action, "subscribe") == 0)
*subscribe = IPC_ACTION_SUBSCRIBE;
else if (strcmp(action, "unsubscribe") == 0)
*subscribe = IPC_ACTION_UNSUBSCRIBE;
else {
fputs("Invalid action specified for subscription\n", stderr);
return -1;
}
yajl_tree_free(parent);
return 0;
}
/**
* Parse an IPC_TYPE_GET_DWM_CLIENT message from a client. This function
* extracts the window id from the message.
*
* Returns 0 if message was successfully parsed
* Returns -1 otherwise
*/
static int
ipc_parse_get_dwm_client(const char *msg, Window *win)
{
char error_buffer[100];
yajl_val parent = yajl_tree_parse(msg, error_buffer, 100);
if (parent == NULL) {
fputs("Failed to parse message from client\n", stderr);
fprintf(stderr, "%s\n", error_buffer);
return -1;
}
// Format:
// {
// "client_window_id": <client window id>
// }
const char *win_path[] = {"client_window_id", 0};
yajl_val win_val = yajl_tree_get(parent, win_path, yajl_t_number);
if (win_val == NULL) {
fputs("No client window id found in client message\n", stderr);
return -1;
}
*win = YAJL_GET_INTEGER(win_val);
yajl_tree_free(parent);
return 0;
}
/**
* Called when an IPC_TYPE_RUN_COMMAND message is received from a client. This
* function parses, executes the given command, and prepares a reply message to
* the client indicating success/failure.
*
* NOTE: There is currently no check for argument validity beyond the number of
* arguments given and types of arguments. There is also no way to check if the
* function succeeded based on dwm's void(const Arg*) function types. Pointer
* arguments can cause crashes if they are not validated in the function itself.
*
* Returns 0 if message was successfully parsed
* Returns -1 on failure parsing message
*/
static int
ipc_run_command(IPCClient *ipc_client, char *msg)
{
IPCParsedCommand parsed_command;
IPCCommand ipc_command;
// Initialize struct
memset(&parsed_command, 0, sizeof(IPCParsedCommand));
if (ipc_parse_run_command(msg, &parsed_command) < 0) {
ipc_prepare_reply_failure(ipc_client, IPC_TYPE_RUN_COMMAND,
"Failed to parse run command");
return -1;
}
if (ipc_get_ipc_command(parsed_command.name, &ipc_command) < 0) {
ipc_prepare_reply_failure(ipc_client, IPC_TYPE_RUN_COMMAND,
"Command %s not found", parsed_command.name);
ipc_free_parsed_command_members(&parsed_command);
return -1;
}
int res = ipc_validate_run_command(&parsed_command, ipc_command);
if (res < 0) {
if (res == -1)
ipc_prepare_reply_failure(ipc_client, IPC_TYPE_RUN_COMMAND,
"%u arguments provided, %u expected",
parsed_command.argc, ipc_command.argc);
else if (res == -2)
ipc_prepare_reply_failure(ipc_client, IPC_TYPE_RUN_COMMAND,
"Type mismatch");
ipc_free_parsed_command_members(&parsed_command);
return -1;
}
if (parsed_command.argc == 1)
ipc_command.func.single_param(parsed_command.args);
else if (parsed_command.argc > 1)
ipc_command.func.array_param(parsed_command.args, parsed_command.argc);
DEBUG("Called function for command %s\n", parsed_command.name);
ipc_free_parsed_command_members(&parsed_command);
ipc_prepare_reply_success(ipc_client, IPC_TYPE_RUN_COMMAND);
return 0;
}
/**
* Called when an IPC_TYPE_GET_MONITORS message is received from a client. It
* prepares a reply with the properties of all of the monitors in JSON.
*/
static void
ipc_get_monitors(IPCClient *c, Monitor *mons, Monitor *selmon)
{
yajl_gen gen;
ipc_reply_init_message(&gen);
dump_monitors(gen, mons, selmon);
ipc_reply_prepare_send_message(gen, c, IPC_TYPE_GET_MONITORS);
}
/**
* Called when an IPC_TYPE_GET_TAGS message is received from a client. It
* prepares a reply with info about all the tags in JSON.
*/
static void
ipc_get_tags(IPCClient *c, const char *tags[], const int tags_len)
{
yajl_gen gen;
ipc_reply_init_message(&gen);
dump_tags(gen, tags, tags_len);
ipc_reply_prepare_send_message(gen, c, IPC_TYPE_GET_TAGS);
}
/**
* Called when an IPC_TYPE_GET_LAYOUTS message is received from a client. It
* prepares a reply with a JSON array of available layouts
*/
static void
ipc_get_layouts(IPCClient *c, const Layout layouts[], const int layouts_len)
{
yajl_gen gen;
ipc_reply_init_message(&gen);
dump_layouts(gen, layouts, layouts_len);
ipc_reply_prepare_send_message(gen, c, IPC_TYPE_GET_LAYOUTS);
}
/**
* Called when an IPC_TYPE_GET_DWM_CLIENT message is received from a client. It
* prepares a JSON reply with the properties of the client with the specified
* window XID.
*
* Returns 0 if the message was successfully parsed and if the client with the
* specified window XID was found
* Returns -1 if the message could not be parsed
*/
static int
ipc_get_dwm_client(IPCClient *ipc_client, const char *msg, const Monitor *mons)
{
Window win;
if (ipc_parse_get_dwm_client(msg, &win) < 0) return -1;
// Find client with specified window XID
for (const Monitor *m = mons; m; m = m->next)
for (Client *c = m->clients; c; c = c->next)
if (c->win == win) {
yajl_gen gen;
ipc_reply_init_message(&gen);
dump_client(gen, c);
ipc_reply_prepare_send_message(gen, ipc_client,
IPC_TYPE_GET_DWM_CLIENT);
return 0;
}
ipc_prepare_reply_failure(ipc_client, IPC_TYPE_GET_DWM_CLIENT,
"Client with window id %d not found", win);
return -1;
}
/**
* Called when an IPC_TYPE_SUBSCRIBE message is received from a client. It
* subscribes/unsubscribes the client from the specified event and replies with
* the result.
*
* Returns 0 if the message was successfully parsed.
* Returns -1 if the message could not be parsed
*/
static int
ipc_subscribe(IPCClient *c, const char *msg)
{
IPCSubscriptionAction action = IPC_ACTION_SUBSCRIBE;
IPCEvent event = 0;
if (ipc_parse_subscribe(msg, &action, &event)) {
ipc_prepare_reply_failure(c, IPC_TYPE_SUBSCRIBE, "Event does not exist");
return -1;
}
if (action == IPC_ACTION_SUBSCRIBE) {
DEBUG("Subscribing client on fd %d to %d\n", c->fd, event);
c->subscriptions |= event;
} else if (action == IPC_ACTION_UNSUBSCRIBE) {
DEBUG("Unsubscribing client on fd %d to %d\n", c->fd, event);
c->subscriptions ^= event;
} else {
ipc_prepare_reply_failure(c, IPC_TYPE_SUBSCRIBE,
"Invalid subscription action");
return -1;
}
ipc_prepare_reply_success(c, IPC_TYPE_SUBSCRIBE);
return 0;
}
int
ipc_init(const char *socket_path, const int p_epoll_fd, IPCCommand commands[],
const int commands_len)
{
// Initialize struct to 0
memset(&sock_epoll_event, 0, sizeof(sock_epoll_event));
int socket_fd = ipc_create_socket(socket_path);
if (socket_fd < 0) return -1;
ipc_commands = commands;
ipc_commands_len = commands_len;
epoll_fd = p_epoll_fd;
// Wake up to incoming connection requests
sock_epoll_event.data.fd = socket_fd;
sock_epoll_event.events = EPOLLIN;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &sock_epoll_event)) {
fputs("Failed to add sock file descriptor to epoll", stderr);
return -1;
}
return socket_fd;
}
void
ipc_cleanup()
{
IPCClient *c = ipc_clients;
// Free clients and their buffers
while (c) {
ipc_drop_client(c);
c = ipc_clients;
}
// Stop waking up for socket events
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock_fd, &sock_epoll_event);
// Uninitialize all static variables
epoll_fd = -1;
sock_fd = -1;
ipc_commands = NULL;
ipc_commands_len = 0;
memset(&sock_epoll_event, 0, sizeof(struct epoll_event));
memset(&sockaddr, 0, sizeof(struct sockaddr_un));
// Delete socket
unlink(sockaddr.sun_path);
shutdown(sock_fd, SHUT_RDWR);
close(sock_fd);
}
int
ipc_get_sock_fd()
{
return sock_fd;
}
IPCClient *
ipc_get_client(int fd)
{
return ipc_list_get_client(ipc_clients, fd);
}
int
ipc_is_client_registered(int fd)
{
return (ipc_get_client(fd) != NULL);
}
int
ipc_accept_client()
{
int fd = -1;
struct sockaddr_un client_addr;
socklen_t len = 0;
// For portability clear the addr structure, since some implementations
// have nonstandard fields in the structure
memset(&client_addr, 0, sizeof(struct sockaddr_un));
fd = accept(sock_fd, (struct sockaddr *)&client_addr, &len);
if (fd < 0 && errno != EINTR) {
fputs("Failed to accept IPC connection from client", stderr);
return -1;
}
if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) {
shutdown(fd, SHUT_RDWR);
close(fd);
fputs("Failed to set flags on new client fd", stderr);
}
IPCClient *nc = ipc_client_new(fd);
if (nc == NULL) return -1;
// Wake up to messages from this client
nc->event.data.fd = fd;
nc->event.events = EPOLLIN | EPOLLHUP;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &nc->event);
ipc_list_add_client(&ipc_clients, nc);
DEBUG("%s%d\n", "New client at fd: ", fd);
return fd;
}
int
ipc_drop_client(IPCClient *c)
{
int fd = c->fd;
shutdown(fd, SHUT_RDWR);
int res = close(fd);
if (res == 0) {
struct epoll_event ev;
// Stop waking up to messages from this client
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ev);
ipc_list_remove_client(&ipc_clients, c);
free(c->buffer);
free(c);
DEBUG("Successfully removed client on fd %d\n", fd);
} else if (res < 0 && res != EINTR) {
fprintf(stderr, "Failed to close fd %d\n", fd);
}
return res;
}
int
ipc_read_client(IPCClient *c, IPCMessageType *msg_type, uint32_t *msg_size,
char **msg)
{
int fd = c->fd;
int ret =
ipc_recv_message(fd, (uint8_t *)msg_type, msg_size, (uint8_t **)msg);
if (ret < 0) {
// This will happen if these errors occur while reading header
if (ret == -1 &&
(errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK))
return -2;
fprintf(stderr, "Error reading message: dropping client at fd %d\n", fd);
ipc_drop_client(c);
return -1;
}
// Make sure receive message is null terminated to avoid parsing issues
if (*msg_size > 0) {
size_t len = *msg_size;
nullterminate(msg, &len);
*msg_size = len;
}
DEBUG("[fd %d] ", fd);
if (*msg_size > 0)
DEBUG("Received message: '%.*s' ", *msg_size, *msg);
else
DEBUG("Received empty message ");
DEBUG("Message type: %" PRIu8 " ", (uint8_t)*msg_type);
DEBUG("Message size: %" PRIu32 "\n", *msg_size);
return 0;
}
ssize_t
ipc_write_client(IPCClient *c)
{
const ssize_t n = ipc_write_message(c->fd, c->buffer, c->buffer_size);
if (n < 0) return n;
// TODO: Deal with client timeouts
if (n == c->buffer_size) {
c->buffer_size = 0;
free(c->buffer);
// No dangling pointers!
c->buffer = NULL;
// Stop waking up when client is ready to receive messages
if (c->event.events & EPOLLOUT) {
c->event.events -= EPOLLOUT;
epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, &c->event);
}
return n;
}
// Shift unwritten buffer to beginning of buffer and reallocate
c->buffer_size -= n;
memmove(c->buffer, c->buffer + n, c->buffer_size);
c->buffer = (char *)realloc(c->buffer, c->buffer_size);
return n;
}
void
ipc_prepare_send_message(IPCClient *c, const IPCMessageType msg_type,
const uint32_t msg_size, const char *msg)
{
dwm_ipc_header_t header = {
.magic = IPC_MAGIC_ARR, .type = msg_type, .size = msg_size};
uint32_t header_size = sizeof(dwm_ipc_header_t);
uint32_t packet_size = header_size + msg_size;
if (c->buffer == NULL)
c->buffer = (char *)malloc(c->buffer_size + packet_size);
else
c->buffer = (char *)realloc(c->buffer, c->buffer_size + packet_size);
// Copy header to end of client buffer
memcpy(c->buffer + c->buffer_size, &header, header_size);
c->buffer_size += header_size;
// Copy message to end of client buffer
memcpy(c->buffer + c->buffer_size, msg, msg_size);
c->buffer_size += msg_size;
// Wake up when client is ready to receive messages
c->event.events |= EPOLLOUT;
epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, &c->event);
}
void
ipc_prepare_reply_failure(IPCClient *c, IPCMessageType msg_type,
const char *format, ...)
{
yajl_gen gen;
va_list args;
// Get output size
va_start(args, format);
size_t len = vsnprintf(NULL, 0, format, args);
va_end(args);
char *buffer = (char *)malloc((len + 1) * sizeof(char));
ipc_reply_init_message(&gen);
va_start(args, format);
vsnprintf(buffer, len + 1, format, args);
va_end(args);
dump_error_message(gen, buffer);
ipc_reply_prepare_send_message(gen, c, msg_type);
fprintf(stderr, "[fd %d] Error: %s\n", c->fd, buffer);
free(buffer);
}
void
ipc_prepare_reply_success(IPCClient *c, IPCMessageType msg_type)
{
const char *success_msg = "{\"result\":\"success\"}";
const size_t msg_len = strlen(success_msg) + 1; // +1 for null char
ipc_prepare_send_message(c, msg_type, msg_len, success_msg);
}
void
ipc_tag_change_event(int mon_num, TagState old_state, TagState new_state)
{
yajl_gen gen;
ipc_event_init_message(&gen);
dump_tag_event(gen, mon_num, old_state, new_state);
ipc_event_prepare_send_message(gen, IPC_EVENT_TAG_CHANGE);
}
void
ipc_client_focus_change_event(int mon_num, Client *old_client,
Client *new_client)
{
yajl_gen gen;
ipc_event_init_message(&gen);
dump_client_focus_change_event(gen, old_client, new_client, mon_num);
ipc_event_prepare_send_message(gen, IPC_EVENT_CLIENT_FOCUS_CHANGE);
}
void
ipc_layout_change_event(const int mon_num, const char *old_symbol,
const Layout *old_layout, const char *new_symbol,
const Layout *new_layout)
{
yajl_gen gen;
ipc_event_init_message(&gen);
dump_layout_change_event(gen, mon_num, old_symbol, old_layout, new_symbol,
new_layout);
ipc_event_prepare_send_message(gen, IPC_EVENT_LAYOUT_CHANGE);
}
void
ipc_monitor_focus_change_event(const int last_mon_num, const int new_mon_num)
{
yajl_gen gen;
ipc_event_init_message(&gen);
dump_monitor_focus_change_event(gen, last_mon_num, new_mon_num);
ipc_event_prepare_send_message(gen, IPC_EVENT_MONITOR_FOCUS_CHANGE);
}
void
ipc_focused_title_change_event(const int mon_num, const Window client_id,
const char *old_name, const char *new_name)
{
yajl_gen gen;
ipc_event_init_message(&gen);
dump_focused_title_change_event(gen, mon_num, client_id, old_name, new_name);
ipc_event_prepare_send_message(gen, IPC_EVENT_FOCUSED_TITLE_CHANGE);
}
void
ipc_focused_state_change_event(const int mon_num, const Window client_id,
const ClientState *old_state,
const ClientState *new_state)
{
yajl_gen gen;
ipc_event_init_message(&gen);
dump_focused_state_change_event(gen, mon_num, client_id, old_state,
new_state);
ipc_event_prepare_send_message(gen, IPC_EVENT_FOCUSED_STATE_CHANGE);
}
void
ipc_send_events(Monitor *mons, Monitor **lastselmon, Monitor *selmon)
{
for (Monitor *m = mons; m; m = m->next) {
unsigned int urg = 0, occ = 0, tagset = 0;
for (Client *c = m->clients; c; c = c->next) {
occ |= c->tags;
if (c->isurgent) urg |= c->tags;
}
tagset = m->tagset[m->seltags];
TagState new_state = {.selected = tagset, .occupied = occ, .urgent = urg};
if (memcmp(&m->tagstate, &new_state, sizeof(TagState)) != 0) {
ipc_tag_change_event(m->num, m->tagstate, new_state);
m->tagstate = new_state;
}
if (m->lastsel != m->sel) {
ipc_client_focus_change_event(m->num, m->lastsel, m->sel);
m->lastsel = m->sel;
}
if (strcmp(m->ltsymbol, m->lastltsymbol) != 0 ||
m->lastlt != m->lt[m->sellt]) {
ipc_layout_change_event(m->num, m->lastltsymbol, m->lastlt, m->ltsymbol,
m->lt[m->sellt]);
strcpy(m->lastltsymbol, m->ltsymbol);
m->lastlt = m->lt[m->sellt];
}
if (*lastselmon != selmon) {
if (*lastselmon != NULL)
ipc_monitor_focus_change_event((*lastselmon)->num, selmon->num);
*lastselmon = selmon;
}
Client *sel = m->sel;
if (!sel) continue;
ClientState *o = &m->sel->prevstate;
ClientState n = {.oldstate = sel->oldstate,
.isfixed = sel->isfixed,
.isfloating = sel->isfloating,
.isfullscreen = sel->isfullscreen,
.isurgent = sel->isurgent,
.neverfocus = sel->neverfocus};
if (memcmp(o, &n, sizeof(ClientState)) != 0) {
ipc_focused_state_change_event(m->num, m->sel->win, o, &n);
*o = n;
}
}
}
int
ipc_handle_client_epoll_event(struct epoll_event *ev, Monitor *mons,
Monitor **lastselmon, Monitor *selmon,
const char *tags[], const int tags_len,
const Layout *layouts, const int layouts_len)
{
int fd = ev->data.fd;
IPCClient *c = ipc_get_client(fd);
if (ev->events & EPOLLHUP) {
DEBUG("EPOLLHUP received from client at fd %d\n", fd);
ipc_drop_client(c);
} else if (ev->events & EPOLLOUT) {
DEBUG("Sending message to client at fd %d...\n", fd);
if (c->buffer_size) ipc_write_client(c);
} else if (ev->events & EPOLLIN) {
IPCMessageType msg_type = 0;
uint32_t msg_size = 0;
char *msg = NULL;
DEBUG("Received message from fd %d\n", fd);
if (ipc_read_client(c, &msg_type, &msg_size, &msg) < 0) return -1;
if (msg_type == IPC_TYPE_GET_MONITORS)
ipc_get_monitors(c, mons, selmon);
else if (msg_type == IPC_TYPE_GET_TAGS)
ipc_get_tags(c, tags, tags_len);
else if (msg_type == IPC_TYPE_GET_LAYOUTS)
ipc_get_layouts(c, layouts, layouts_len);
else if (msg_type == IPC_TYPE_RUN_COMMAND) {
if (ipc_run_command(c, msg) < 0) return -1;
ipc_send_events(mons, lastselmon, selmon);
} else if (msg_type == IPC_TYPE_GET_DWM_CLIENT) {
if (ipc_get_dwm_client(c, msg, mons) < 0) return -1;
} else if (msg_type == IPC_TYPE_SUBSCRIBE) {
if (ipc_subscribe(c, msg) < 0) return -1;
} else {
fprintf(stderr, "Invalid message type received from fd %d", fd);
ipc_prepare_reply_failure(c, msg_type, "Invalid message type: %d",
msg_type);
}
free(msg);
} else {
fprintf(stderr, "Epoll event returned %d from fd %d\n", ev->events, fd);
return -1;
}
return 0;
}
int
ipc_handle_socket_epoll_event(struct epoll_event *ev)
{
if (!(ev->events & EPOLLIN)) return -1;
// EPOLLIN means incoming client connection request
fputs("Received EPOLLIN event on socket\n", stderr);
int new_fd = ipc_accept_client();
return new_fd;
}