Add IPC patch, add as an option. Will likely simplify this soon but for

now, edit config,mk and enable it in toggle.h. It is enabled by default
but not compatible with BSD so comment the lines if you use that.
This commit is contained in:
speedie 2022-08-26 11:41:11 +02:00
parent dbc9b278d5
commit a258afba18
14 changed files with 2992 additions and 13 deletions

View file

@ -1,4 +1,4 @@
# speespeedwm // minimal X window manager designed for productivity and aesthetics. # speedwm // minimal X window manager designed for productivity and aesthetics.
# See LICENSE file for copyright and license details. # See LICENSE file for copyright and license details.
include config.mk include config.mk
@ -6,7 +6,11 @@ include config.mk
SRC = drw.c speedwm.c util.c SRC = drw.c speedwm.c util.c
OBJ = ${SRC:.c=.o} OBJ = ${SRC:.c=.o}
ifdef USEIPC
all: options speedwm speedwm-msg
else
all: options speedwm all: options speedwm
endif
options: options:
@echo speedwm build options: @echo speedwm build options:
@ -20,7 +24,12 @@ options:
${OBJ}: config.mk ${OBJ}: config.mk
speedwm: ${OBJ} speedwm: ${OBJ}
${CC} -o $@ ${OBJ} ${LDFLAGS} ; rm -f drw.o speedwm.o util.o ${CC} -o $@ ${OBJ} ${LDFLAGS} ; rm -f *.o
ifdef USEIPC
speedwm-msg: speedwm-msg.o
${CC} -o $@ $< ${LDFLAGS} ; rm -f speedwm-msg.o
endif
clean: clean:
rm -f speedwm ${OBJ} speedwm-spde-${VERSION}.tar.gz && echo "Cleaned!" rm -f speedwm ${OBJ} speedwm-spde-${VERSION}.tar.gz && echo "Cleaned!"
@ -31,12 +40,14 @@ dist: clean
tar -cf speedwm-spde-${VERSION}.tar speedwm-spde-${VERSION} tar -cf speedwm-spde-${VERSION}.tar speedwm-spde-${VERSION}
gzip speedwm-spde-${VERSION}.tar gzip speedwm-spde-${VERSION}.tar
rm -rf speedwm-spde-${VERSION} speedwm rm -rf speedwm-spde-${VERSION} speedwm
rm -rf speedwm-spde-${VERSION} speedwm-msg
install: all install: all
touch drw.o speedwm.o util.o touch drw.o speedwm.o util.o
mkdir -p ${DESTDIR}${PREFIX}/bin mkdir -p ${DESTDIR}${PREFIX}/bin
cp -f speedwm ${DESTDIR}${PREFIX}/bin cp -f speedwm speedwm-msg ${DESTDIR}${PREFIX}/bin
chmod 755 ${DESTDIR}${PREFIX}/bin/speedwm chmod 755 ${DESTDIR}${PREFIX}/bin/speedwm
[ -f speedwm-msg ] && chmod 755 ${DESTDIR}${PREFIX}/bin/speedwm-msg || :
cp -f docs/bindlist ${DESTDIR}${PREFIX}/share/speedwm-bindlist cp -f docs/bindlist ${DESTDIR}${PREFIX}/share/speedwm-bindlist
cp -f docs/deplist ${DESTDIR}${PREFIX}/share/speedwm-deplist cp -f docs/deplist ${DESTDIR}${PREFIX}/share/speedwm-deplist
cp -f docs/about ${DESTDIR}${PREFIX}/share/speedwm-about cp -f docs/about ${DESTDIR}${PREFIX}/share/speedwm-about
@ -68,7 +79,7 @@ install: all
./scripts/speedwm-compatcheck ./scripts/speedwm-compatcheck
./scripts/speedwm-dm ./scripts/speedwm-dm
[ -f speedwm.png ] && cp -f speedwm.png ${DESTDIR}${PREFIX}/share/pixmaps/speedwm.png || : [ -f speedwm.png ] && cp -f speedwm.png ${DESTDIR}${PREFIX}/share/pixmaps/speedwm.png || :
rm -f drw.o speedwm.o util.o speedwm ; echo "Installed speedwm to ${DESTDIR}${PREFIX}/bin" rm -f drw.o speedwm.o util.o speedwm speedwm-msg ; echo "Installed speedwm to ${DESTDIR}${PREFIX}/bin"
uninstall: uninstall:
rm -f ${DESTDIR}${PREFIX}/bin/speedwm\ rm -f ${DESTDIR}${PREFIX}/bin/speedwm\

View file

@ -6,6 +6,13 @@
# If you use OpenBSD, uncomment this line. (remove the # at the start of the line below) # If you use OpenBSD, uncomment this line. (remove the # at the start of the line below)
#FREETYPEINC = ${X11INC}/freetype2 #FREETYPEINC = ${X11INC}/freetype2
# IPC
# Whether or not to compile in IPC support.
# If you use OpenBSD or FreeBSD, this is not supported. In that case set this to false and set USEIPC in options.h to 0.
USEIPC = false
YAJLLIBS = -lyajl # Comment if you use IPC
YAJLINC = /usr/include/yajl # Comment if you use IPC
# Solaris support # Solaris support
# If you use Solaris, uncomment this line. (remove the # at the start of the line below) # If you use Solaris, uncomment this line. (remove the # at the start of the line below)
#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" #CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\"
@ -48,8 +55,8 @@ FREETYPELIBS = -lfontconfig -lXft
FREETYPEINC = /usr/include/freetype2 FREETYPEINC = /usr/include/freetype2
# includes and libs # includes and libs
INCS = -I${X11INC} -I${FREETYPEINC} INCS = -I${X11INC} -I${FREETYPEINC} -I${YAJLINC}
LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lXrender -lImlib2 -lX11-xcb -lxcb -lxcb-res -lXext LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lXrender -lImlib2 -lX11-xcb -lxcb -lxcb-res -lXext ${YAJLLIBS}
# flags # flags
CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}

16
ipccmd.h Normal file
View file

@ -0,0 +1,16 @@
static const char *ipcsockpath = "/tmp/speedwm.sock";
static IPCCommand ipccommands[] = {
IPCCOMMAND( view, 1, {ARG_TYPE_UINT} ),
IPCCOMMAND( tagmon, 1, {ARG_TYPE_UINT} ),
IPCCOMMAND( focusmon, 1, {ARG_TYPE_SINT} ),
IPCCOMMAND( focusstackvis, 1, {ARG_TYPE_SINT} ),
IPCCOMMAND( zoom, 1, {ARG_TYPE_NONE} ),
IPCCOMMAND( incnmaster, 1, {ARG_TYPE_SINT} ),
IPCCOMMAND( killclient, 1, {ARG_TYPE_SINT} ),
IPCCOMMAND( togglefloating, 1, {ARG_TYPE_NONE} ),
IPCCOMMAND( setmfact, 1, {ARG_TYPE_FLOAT} ),
IPCCOMMAND( setlayoutsafe, 1, {ARG_TYPE_PTR} ),
IPCCOMMAND( quit, 1, {ARG_TYPE_NONE} )
};

548
speedwm-msg.c Normal file
View file

@ -0,0 +1,548 @@
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <yajl/yajl_gen.h>
#define IPC_MAGIC "DWM-IPC"
// clang-format off
#define IPC_MAGIC_ARR { 'D', 'W', 'M', '-', 'I', 'P', 'C' }
// clang-format on
#define IPC_MAGIC_LEN 7 // Not including null char
#define IPC_EVENT_TAG_CHANGE "tag_change_event"
#define IPC_EVENT_CLIENT_FOCUS_CHANGE "client_focus_change_event"
#define IPC_EVENT_LAYOUT_CHANGE "layout_change_event"
#define IPC_EVENT_MONITOR_FOCUS_CHANGE "monitor_focus_change_event"
#define IPC_EVENT_FOCUSED_TITLE_CHANGE "focused_title_change_event"
#define IPC_EVENT_FOCUSED_STATE_CHANGE "focused_state_change_event"
#define YSTR(str) yajl_gen_string(gen, (unsigned char *)str, strlen(str))
#define YINT(num) yajl_gen_integer(gen, num)
#define YDOUBLE(num) yajl_gen_double(gen, num)
#define YBOOL(v) yajl_gen_bool(gen, v)
#define YNULL() yajl_gen_null(gen)
#define YARR(body) \
{ \
yajl_gen_array_open(gen); \
body; \
yajl_gen_array_close(gen); \
}
#define YMAP(body) \
{ \
yajl_gen_map_open(gen); \
body; \
yajl_gen_map_close(gen); \
}
typedef unsigned long Window;
const char *DEFAULT_SOCKET_PATH = "/tmp/speedwm.sock";
static int sock_fd = -1;
static unsigned int ignore_reply = 0;
typedef enum IPCMessageType {
IPC_TYPE_RUN_COMMAND = 0,
IPC_TYPE_GET_MONITORS = 1,
IPC_TYPE_GET_TAGS = 2,
IPC_TYPE_GET_LAYOUTS = 3,
IPC_TYPE_GET_DWM_CLIENT = 4,
IPC_TYPE_SUBSCRIBE = 5,
IPC_TYPE_EVENT = 6
} IPCMessageType;
// Every IPC message must begin with this
typedef struct speedwm_ipc_header {
uint8_t magic[IPC_MAGIC_LEN];
uint32_t size;
uint8_t type;
} __attribute((packed)) speedwm_ipc_header_t;
static int
recv_message(uint8_t *msg_type, uint32_t *reply_size, uint8_t **reply)
{
uint32_t read_bytes = 0;
const int32_t to_read = sizeof(speedwm_ipc_header_t);
char header[to_read];
char *walk = header;
// Try to read header
while (read_bytes < to_read) {
ssize_t n = read(sock_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) {
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);
// Extract message type
memcpy(msg_type, walk, sizeof(uint8_t));
walk += sizeof(uint8_t);
(*reply) = malloc(*reply_size);
// Extract payload
read_bytes = 0;
while (read_bytes < *reply_size) {
ssize_t n = read(sock_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) {
if (errno == EINTR || errno == EAGAIN) continue;
free(*reply);
return -1;
}
read_bytes += n;
}
return 0;
}
static int
read_socket(IPCMessageType *msg_type, uint32_t *msg_size, char **msg)
{
int ret = -1;
while (ret != 0) {
ret = recv_message((uint8_t *)msg_type, msg_size, (uint8_t **)msg);
if (ret < 0) {
// Try again (non-fatal error)
if (ret == -1 && (errno == EINTR || errno == EAGAIN)) continue;
fprintf(stderr, "Error receiving response from socket. ");
fprintf(stderr, "The connection might have been lost.\n");
exit(2);
}
}
return 0;
}
static ssize_t
write_socket(const void *buf, size_t count)
{
size_t written = 0;
while (written < count) {
const ssize_t n =
write(sock_fd, ((uint8_t *)buf) + written, count - written);
if (n == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
continue;
else
return n;
}
written += n;
}
return written;
}
static void
connect_to_socket()
{
struct sockaddr_un addr;
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
// Initialize struct to 0
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, DEFAULT_SOCKET_PATH);
connect(sock, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un));
sock_fd = sock;
}
static int
send_message(IPCMessageType msg_type, uint32_t msg_size, uint8_t *msg)
{
speedwm_ipc_header_t header = {
.magic = IPC_MAGIC_ARR, .size = msg_size, .type = msg_type};
size_t header_size = sizeof(speedwm_ipc_header_t);
size_t total_size = header_size + msg_size;
uint8_t buffer[total_size];
// Copy header to buffer
memcpy(buffer, &header, header_size);
// Copy message to buffer
memcpy(buffer + header_size, msg, header.size);
write_socket(buffer, total_size);
return 0;
}
static int
is_float(const char *s)
{
size_t len = strlen(s);
int is_dot_used = 0;
int is_minus_used = 0;
// Floats can only have one decimal point in between or digits
// Optionally, floats can also be below zero (negative)
for (int i = 0; i < len; i++) {
if (isdigit(s[i]))
continue;
else if (!is_dot_used && s[i] == '.' && i != 0 && i != len - 1) {
is_dot_used = 1;
continue;
} else if (!is_minus_used && s[i] == '-' && i == 0) {
is_minus_used = 1;
continue;
} else
return 0;
}
return 1;
}
static int
is_unsigned_int(const char *s)
{
size_t len = strlen(s);
// Unsigned int can only have digits
for (int i = 0; i < len; i++) {
if (isdigit(s[i]))
continue;
else
return 0;
}
return 1;
}
static int
is_signed_int(const char *s)
{
size_t len = strlen(s);
// Signed int can only have digits and a negative sign at the start
for (int i = 0; i < len; i++) {
if (isdigit(s[i]))
continue;
else if (i == 0 && s[i] == '-') {
continue;
} else
return 0;
}
return 1;
}
static void
flush_socket_reply()
{
IPCMessageType reply_type;
uint32_t reply_size;
char *reply;
read_socket(&reply_type, &reply_size, &reply);
free(reply);
}
static void
print_socket_reply()
{
IPCMessageType reply_type;
uint32_t reply_size;
char *reply;
read_socket(&reply_type, &reply_size, &reply);
printf("%.*s\n", reply_size, reply);
fflush(stdout);
free(reply);
}
static int
run_command(const char *name, char *args[], int argc)
{
const unsigned char *msg;
size_t msg_size;
yajl_gen gen = yajl_gen_alloc(NULL);
// Message format:
// {
// "command": "<name>",
// "args": [ ... ]
// }
// clang-format off
YMAP(
YSTR("command"); YSTR(name);
YSTR("args"); YARR(
for (int i = 0; i < argc; i++) {
if (is_signed_int(args[i])) {
long long num = atoll(args[i]);
YINT(num);
} else if (is_float(args[i])) {
float num = atof(args[i]);
YDOUBLE(num);
} else {
YSTR(args[i]);
}
}
)
)
// clang-format on
yajl_gen_get_buf(gen, &msg, &msg_size);
send_message(IPC_TYPE_RUN_COMMAND, msg_size, (uint8_t *)msg);
if (!ignore_reply)
print_socket_reply();
else
flush_socket_reply();
yajl_gen_free(gen);
return 0;
}
static int
get_monitors()
{
send_message(IPC_TYPE_GET_MONITORS, 1, (uint8_t *)"");
print_socket_reply();
return 0;
}
static int
get_tags()
{
send_message(IPC_TYPE_GET_TAGS, 1, (uint8_t *)"");
print_socket_reply();
return 0;
}
static int
get_layouts()
{
send_message(IPC_TYPE_GET_LAYOUTS, 1, (uint8_t *)"");
print_socket_reply();
return 0;
}
static int
get_speedwm_client(Window win)
{
const unsigned char *msg;
size_t msg_size;
yajl_gen gen = yajl_gen_alloc(NULL);
// Message format:
// {
// "client_window_id": "<win>"
// }
// clang-format off
YMAP(
YSTR("client_window_id"); YINT(win);
)
// clang-format on
yajl_gen_get_buf(gen, &msg, &msg_size);
send_message(IPC_TYPE_GET_DWM_CLIENT, msg_size, (uint8_t *)msg);
print_socket_reply();
yajl_gen_free(gen);
return 0;
}
static int
subscribe(const char *event)
{
const unsigned char *msg;
size_t msg_size;
yajl_gen gen = yajl_gen_alloc(NULL);
// Message format:
// {
// "event": "<event>",
// "action": "subscribe"
// }
// clang-format off
YMAP(
YSTR("event"); YSTR(event);
YSTR("action"); YSTR("subscribe");
)
// clang-format on
yajl_gen_get_buf(gen, &msg, &msg_size);
send_message(IPC_TYPE_SUBSCRIBE, msg_size, (uint8_t *)msg);
if (!ignore_reply)
print_socket_reply();
else
flush_socket_reply();
yajl_gen_free(gen);
return 0;
}
static void
usage_error(const char *prog_name, const char *format, ...)
{
va_list args;
va_start(args, format);
fprintf(stderr, "Error: ");
vfprintf(stderr, format, args);
fprintf(stderr, "\nusage: %s <command> [...]\n", prog_name);
fprintf(stderr, "Try '%s help'\n", prog_name);
va_end(args);
exit(1);
}
static void
print_usage(const char *name)
{
printf("usage: %s [options] <command> [...]\n", name);
puts("");
puts("Commands:");
puts(" run_command <name> [args...] Run an IPC command");
puts("");
puts(" get_monitors Get monitor properties");
puts("");
puts(" get_tags Get list of tags");
puts("");
puts(" get_layouts Get list of layouts");
puts("");
puts(" get_speedwm_client <window_id> Get speedwm client proprties");
puts("");
puts(" subscribe [events...] Subscribe to specified events");
puts(" Options: " IPC_EVENT_TAG_CHANGE ",");
puts(" " IPC_EVENT_LAYOUT_CHANGE ",");
puts(" " IPC_EVENT_CLIENT_FOCUS_CHANGE ",");
puts(" " IPC_EVENT_MONITOR_FOCUS_CHANGE ",");
puts(" " IPC_EVENT_FOCUSED_TITLE_CHANGE ",");
puts(" " IPC_EVENT_FOCUSED_STATE_CHANGE);
puts("");
puts(" help Display this message");
puts("");
puts("Options:");
puts(" --ignore-reply Don't print reply messages from");
puts(" run_command and subscribe.");
puts("");
}
int
main(int argc, char *argv[])
{
const char *prog_name = argv[0];
connect_to_socket();
if (sock_fd == -1) {
fprintf(stderr, "Failed to connect to socket\n");
return 1;
}
int i = 1;
if (i < argc && strcmp(argv[i], "--ignore-reply") == 0) {
ignore_reply = 1;
i++;
}
if (i >= argc) usage_error(prog_name, "Expected an argument, got none");
if (strcmp(argv[i], "help") == 0)
print_usage(prog_name);
else if (strcmp(argv[i], "run_command") == 0) {
if (++i >= argc) usage_error(prog_name, "No command specified");
// Command name
char *command = argv[i];
// Command arguments are everything after command name
char **command_args = argv + ++i;
// Number of command arguments
int command_argc = argc - i;
run_command(command, command_args, command_argc);
} else if (strcmp(argv[i], "get_monitors") == 0) {
get_monitors();
} else if (strcmp(argv[i], "get_tags") == 0) {
get_tags();
} else if (strcmp(argv[i], "get_layouts") == 0) {
get_layouts();
} else if (strcmp(argv[i], "get_speedwm_client") == 0) {
if (++i < argc) {
if (is_unsigned_int(argv[i])) {
Window win = atol(argv[i]);
get_speedwm_client(win);
} else
usage_error(prog_name, "Expected unsigned integer argument");
} else
usage_error(prog_name, "Expected the window id");
} else if (strcmp(argv[i], "subscribe") == 0) {
if (++i < argc) {
for (int j = i; j < argc; j++) subscribe(argv[j]);
} else
usage_error(prog_name, "Expected event name");
// Keep listening for events forever
while (1) {
print_socket_reply();
}
} else
usage_error(prog_name, "Invalid argument '%s'", argv[i]);
return 0;
}

192
speedwm.c
View file

@ -15,6 +15,10 @@
#include <stdint.h> #include <stdint.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include "toggle.h"
#if USEIPC
#include <sys/epoll.h>
#endif
#include <X11/cursorfont.h> #include <X11/cursorfont.h>
#include <X11/keysym.h> #include <X11/keysym.h>
#include <X11/Xatom.h> #include <X11/Xatom.h>
@ -91,9 +95,23 @@ enum { DWMTags, DWMLast };
enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle,
ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ ClkClientWin, ClkRootWin, ClkLast }; /* clicks */
#if USEIPC
typedef struct TagState TagState;
struct TagState {
int selected;
int occupied;
int urgent;
};
typedef struct ClientState ClientState;
struct ClientState {
int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen;
};
#endif
typedef union { typedef union {
int i; long i;
unsigned int ui; unsigned long ui;
float f; float f;
const void *v; const void *v;
} Arg; } Arg;
@ -129,6 +147,9 @@ struct Client {
Client *swallowing; Client *swallowing;
Monitor *mon; Monitor *mon;
Window win; Window win;
#if USEIPC
ClientState prevstate;
#endif
}; };
typedef struct { typedef struct {
@ -153,6 +174,9 @@ typedef struct {
typedef struct Pertag Pertag; typedef struct Pertag Pertag;
struct Monitor { struct Monitor {
char ltsymbol[16]; char ltsymbol[16];
#if USEIPC
char lastltsymbol[16];
#endif
float mfact; float mfact;
int nmaster; int nmaster;
int num; int num;
@ -169,6 +193,9 @@ struct Monitor {
unsigned int seltags; unsigned int seltags;
unsigned int sellt; unsigned int sellt;
unsigned int tagset[2]; unsigned int tagset[2];
#if USEIPC
TagState tagstate;
#endif
int previewshow; int previewshow;
int showbar; int showbar;
int barposition; int barposition;
@ -176,6 +203,9 @@ struct Monitor {
int mouseactivated; int mouseactivated;
Client *clients; Client *clients;
Client *sel; Client *sel;
#if USEIPC
Client *lastsel;
#endif
Client *stack; Client *stack;
Monitor *next; Monitor *next;
Window barwin; Window barwin;
@ -183,6 +213,9 @@ struct Monitor {
Window tagwin; Window tagwin;
Pixmap *tagmap; Pixmap *tagmap;
const Layout *lt[2]; const Layout *lt[2];
#if USEIPC
const Layout *lastlt;
#endif
Pertag *pertag; Pertag *pertag;
}; };
@ -290,6 +323,9 @@ static void grabbuttons(Client *c, int focused);
static void hide(const Arg *arg); static void hide(const Arg *arg);
static void hidewin(Client *c); static void hidewin(Client *c);
static void incnmaster(const Arg *arg); static void incnmaster(const Arg *arg);
#if USEIPC
static int handlexevent(struct epoll_event *ev);
#endif
static void keypress(XEvent *e); static void keypress(XEvent *e);
static int fake_signal(void); static int fake_signal(void);
static void killclient(const Arg *arg); static void killclient(const Arg *arg);
@ -344,9 +380,15 @@ static void incrogaps(const Arg *arg);
static void togglegaps(const Arg *arg); static void togglegaps(const Arg *arg);
static void defaultgaps(const Arg *arg); static void defaultgaps(const Arg *arg);
static void setlayout(const Arg *arg); static void setlayout(const Arg *arg);
#if USEIPC
static void setlayoutsafe(const Arg *arg);
#endif
static void setcfact(const Arg *arg); static void setcfact(const Arg *arg);
static void setmfact(const Arg *arg); static void setmfact(const Arg *arg);
static void setup(void); static void setup(void);
#if USEIPC
static void setupepoll(void);
#endif
static void seturgent(Client *c, int urg); static void seturgent(Client *c, int urg);
static void show(const Arg *arg); static void show(const Arg *arg);
static void showwin(Client *c); static void showwin(Client *c);
@ -445,6 +487,10 @@ static void (*handler[LASTEvent]) (XEvent *) = {
[UnmapNotify] = unmapnotify [UnmapNotify] = unmapnotify
}; };
static Atom wmatom[WMLast], netatom[NetLast], dwmatom[DWMLast], motifatom; static Atom wmatom[WMLast], netatom[NetLast], dwmatom[DWMLast], motifatom;
#if USEIPC
static int epoll_fd;
static int dpy_fd;
#endif
static int running = 1; static int running = 1;
static int restart = 0; static int restart = 0;
static Cur *cursor[CurLast]; static Cur *cursor[CurLast];
@ -453,6 +499,9 @@ static Clr **tagscheme;
static Display *dpy; static Display *dpy;
static Drw *drw; static Drw *drw;
static Monitor *mons, *selmon; static Monitor *mons, *selmon;
#if USEIPC
static Monitor *lastselmon; /* IPC */
#endif
static Window root, wmcheckwin; static Window root, wmcheckwin;
@ -470,6 +519,10 @@ static Colormap cmap;
static xcb_connection_t *xcon; static xcb_connection_t *xcon;
#define FORCE_VSPLIT forcevsplit #define FORCE_VSPLIT forcevsplit
#if USEIPC
#include "toggle/ipc.c"
#endif
/* configuration, allows nested code to access above variables */ /* configuration, allows nested code to access above variables */
#include <X11/XF86keysym.h> /* Enable multimedia button support */ #include <X11/XF86keysym.h> /* Enable multimedia button support */
#include "options.h" /* Include options */ #include "options.h" /* Include options */
@ -481,6 +534,12 @@ static xcb_connection_t *xcon;
#include "xresources.h" /* Include .Xresources/Pywal support */ #include "xresources.h" /* Include .Xresources/Pywal support */
#include "keybinds.h" /* Include keybinds */ #include "keybinds.h" /* Include keybinds */
#if USEIPC
#include "ipccmd.h" /* Include IPC */
#include "toggle/ipc-cli.c"
#include "toggle/ipc-yajl.c"
#endif
static const char *statuscmd[] = { shell, "-c", NULL, NULL }; static const char *statuscmd[] = { shell, "-c", NULL, NULL };
#include "mouse.h" /* Include mouse support */ #include "mouse.h" /* Include mouse support */
@ -1045,6 +1104,14 @@ cleanup(void)
XSync(dpy, False); XSync(dpy, False);
XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
XDeleteProperty(dpy, root, netatom[NetActiveWindow]); XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
#if USEIPC
ipc_cleanup();
if (close(epoll_fd) < 0) {
fprintf(stderr, "Failed to close epoll file descriptor\n");
}
#endif
} }
void void
@ -2104,6 +2171,27 @@ hidewin(Client *c) {
XUngrabServer(dpy); XUngrabServer(dpy);
} }
#if USEIPC
int
handlexevent(struct epoll_event *ev)
{
if (ev->events & EPOLLIN) {
XEvent ev;
while (running && XPending(dpy)) {
XNextEvent(dpy, &ev);
if (handler[ev.type]) {
handler[ev.type](&ev); /* call handler */
ipc_send_events(mons, &lastselmon, selmon);
}
}
} else if (ev-> events & EPOLLHUP) {
return -1;
}
return 0;
}
#endif
void void
incnmaster(const Arg *arg) incnmaster(const Arg *arg)
{ {
@ -3423,12 +3511,44 @@ restack(Monitor *m)
void void
run(void) run(void)
{ {
XEvent ev; #if USEIPC
/* main event loop */ int event_count = 0;
const int MAX_EVENTS = 10;
struct epoll_event events[MAX_EVENTS];
#endif
XSync(dpy, False); XSync(dpy, False);
while (running && !XNextEvent(dpy, &ev))
if (handler[ev.type]) #if USEIPC
handler[ev.type](&ev); /* call handler */ /* main event loop */
while (running) {
event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
for (int i = 0; i < event_count; i++) {
int event_fd = events[i].data.fd;
DEBUG("Got event from fd %d\n", event_fd);
if (event_fd == dpy_fd) {
// -1 means EPOLLHUP
if (handlexevent(events + i) == -1)
return;
} else if (event_fd == ipc_get_sock_fd()) {
ipc_handle_socket_epoll_event(events + i);
} else if (ipc_is_client_registered(event_fd)){
if (ipc_handle_client_epoll_event(events + i, mons, &lastselmon, selmon,
LENGTH(tags), layouts, LENGTH(layouts)) < 0) {
fprintf(stderr, "Error handling IPC event on fd %d\n", event_fd);
}
} else {
fprintf(stderr, "Got event from unknown fd %d, ptr %p, u32 %d, u64 %lu",
event_fd, events[i].data.ptr, events[i].data.u32,
events[i].data.u64);
fprintf(stderr, " with events %d\n", events[i].events);
return;
}
}
}
#endif
} }
void spawnscratch(const Arg *arg) void spawnscratch(const Arg *arg)
@ -3747,6 +3867,20 @@ setcfact(const Arg *arg) {
arrange(selmon); arrange(selmon);
} }
#if USEIPC
void
setlayoutsafe(const Arg *arg)
{
const Layout *ltptr = (Layout *)arg->v;
if (ltptr == 0)
setlayout(arg);
for (int i = 0; i < LENGTH(layouts); i++) {
if (ltptr == &layouts[i])
setlayout(arg);
}
}
#endif
/* arg > 1.0 will set mfact absolutely */ /* arg > 1.0 will set mfact absolutely */
void void
setmfact(const Arg *arg) setmfact(const Arg *arg)
@ -3877,6 +4011,9 @@ setup(void)
XSelectInput(dpy, root, wa.event_mask); XSelectInput(dpy, root, wa.event_mask);
grabkeys(); grabkeys();
focus(NULL); focus(NULL);
#if USEIPC
setupepoll();
#endif
spawnbar(); spawnbar();
} }
@ -3886,6 +4023,36 @@ setviewport(void){
XChangeProperty(dpy, root, netatom[NetDesktopViewport], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)data, 2); XChangeProperty(dpy, root, netatom[NetDesktopViewport], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)data, 2);
} }
#if USEIPC
void
setupepoll(void)
{
epoll_fd = epoll_create1(0);
dpy_fd = ConnectionNumber(dpy);
struct epoll_event dpy_event;
// Initialize struct to 0
memset(&dpy_event, 0, sizeof(dpy_event));
DEBUG("Display socket is fd %d\n", dpy_fd);
if (epoll_fd == -1) {
fputs("Failed to create epoll file descriptor", stderr);
}
dpy_event.events = EPOLLIN;
dpy_event.data.fd = dpy_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, dpy_fd, &dpy_event)) {
fputs("Failed to add display file descriptor to epoll", stderr);
close(epoll_fd);
exit(1);
}
if (ipc_init(ipcsockpath, epoll_fd, ipccommands, LENGTH(ipccommands)) < 0) {
fputs("Failed to initialize IPC\n", stderr);
}
}
#endif
void void
seturgent(Client *c, int urg) seturgent(Client *c, int urg)
@ -4670,11 +4837,22 @@ updaterules(Client *c)
void void
updatetitle(Client *c) updatetitle(Client *c)
{ {
#if USEIPC
char oldname[sizeof(c->name)];
strcpy(oldname, c->name);
#endif
if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name)) if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name))
gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name); gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name);
if (c->name[0] == '\0') if (c->name[0] == '\0')
strcpy(c->name, broken); strcpy(c->name, broken);
#if USEIPC
for (Monitor *m = mons; m; m = m->next) {
if (m->sel == c && strcmp(oldname, c->name) != 0)
ipc_focused_title_change_event(m->num, c->win, oldname, c->name);
}
#endif
} }
void void

4
toggle.h Normal file
View file

@ -0,0 +1,4 @@
/* Toggle patches
* This header allows you to enable/disable patches that can break compability with certain OSes or configurations.
*/
#define USEIPC 1 /* Whether or not to use IPC. If you set this to 1, set USEIPC to true in config.mk and comment the YAJLLIBS and YAJLINC lines in config.mk. Not compatible with BSDs so for those, set this to 0. */

66
toggle/ipc-cli.c Normal file
View file

@ -0,0 +1,66 @@
#include "ipc-cli.h"
#include <string.h>
#include <sys/epoll.h>
#include "../util.h"
IPCClient *
ipc_client_new(int fd)
{
IPCClient *c = (IPCClient *)malloc(sizeof(IPCClient));
if (c == NULL) return NULL;
// Initialize struct
memset(&c->event, 0, sizeof(struct epoll_event));
c->buffer_size = 0;
c->buffer = NULL;
c->fd = fd;
c->event.data.fd = fd;
c->next = NULL;
c->prev = NULL;
c->subscriptions = 0;
return c;
}
void
ipc_list_add_client(IPCClientList *list, IPCClient *nc)
{
DEBUG("Adding client with fd %d to list\n", nc->fd);
if (*list == NULL) {
// List is empty, point list at first client
*list = nc;
} else {
IPCClient *c;
// Go to last client in list
for (c = *list; c && c->next; c = c->next)
;
c->next = nc;
nc->prev = c;
}
}
void
ipc_list_remove_client(IPCClientList *list, IPCClient *c)
{
IPCClient *cprev = c->prev;
IPCClient *cnext = c->next;
if (cprev != NULL) cprev->next = c->next;
if (cnext != NULL) cnext->prev = c->prev;
if (c == *list) *list = c->next;
}
IPCClient *
ipc_list_get_client(IPCClientList list, int fd)
{
for (IPCClient *c = list; c; c = c->next) {
if (c->fd == fd) return c;
}
return NULL;
}

61
toggle/ipc-cli.h Normal file
View file

@ -0,0 +1,61 @@
#ifndef IPC_CLIENT_H_
#define IPC_CLIENT_H_
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
typedef struct IPCClient IPCClient;
/**
* This structure contains the details of an IPC Client and pointers for a
* linked list
*/
struct IPCClient {
int fd;
int subscriptions;
char *buffer;
uint32_t buffer_size;
struct epoll_event event;
IPCClient *next;
IPCClient *prev;
};
typedef IPCClient *IPCClientList;
/**
* Allocate memory for new IPCClient with the specified file descriptor and
* initialize struct.
*
* @param fd File descriptor of IPC client
*
* @return Address to allocated IPCClient struct
*/
IPCClient *ipc_client_new(int fd);
/**
* Add an IPC Client to the specified list
*
* @param list Address of the list to add the client to
* @param nc Address of the IPCClient
*/
void ipc_list_add_client(IPCClientList *list, IPCClient *nc);
/**
* Remove an IPCClient from the specified list
*
* @param list Address of the list to remove the client from
* @param c Address of the IPCClient
*/
void ipc_list_remove_client(IPCClientList *list, IPCClient *c);
/**
* Get an IPCClient from the specified IPCClient list
*
* @param list List to remove the client from
* @param fd File descriptor of the IPCClient
*/
IPCClient *ipc_list_get_client(IPCClientList list, int fd);
#endif // IPC_CLIENT_H_

350
toggle/ipc-yajl.c Normal file
View file

@ -0,0 +1,350 @@
#include "ipc-yajl.h"
#include <stdint.h>
int
dump_tag(yajl_gen gen, const char *name, const int tag_mask)
{
// clang-format off
YMAP(
YSTR("bit_mask"); YINT(tag_mask);
YSTR("name"); YSTR(name);
)
// clang-format on
return 0;
}
int
dump_tags(yajl_gen gen, int tags_len)
{
// clang-format off
YARR(
for (int i = 0; i < tags_len; i++)
dump_tag(gen, tags[i], 1 << i);
)
// clang-format on
return 0;
}
int
dump_client(yajl_gen gen, Client *c)
{
// clang-format off
YMAP(
YSTR("name"); YSTR(c->name);
YSTR("tags"); YINT(c->tags);
YSTR("window_id"); YINT(c->win);
YSTR("monitor_number"); YINT(c->mon->num);
YSTR("geometry"); YMAP(
YSTR("current"); YMAP (
YSTR("x"); YINT(c->x);
YSTR("y"); YINT(c->y);
YSTR("width"); YINT(c->w);
YSTR("height"); YINT(c->h);
)
YSTR("old"); YMAP(
YSTR("x"); YINT(c->oldx);
YSTR("y"); YINT(c->oldy);
YSTR("width"); YINT(c->oldw);
YSTR("height"); YINT(c->oldh);
)
)
YSTR("size_hints"); YMAP(
YSTR("base"); YMAP(
YSTR("width"); YINT(c->basew);
YSTR("height"); YINT(c->baseh);
)
YSTR("step"); YMAP(
YSTR("width"); YINT(c->incw);
YSTR("height"); YINT(c->inch);
)
YSTR("max"); YMAP(
YSTR("width"); YINT(c->maxw);
YSTR("height"); YINT(c->maxh);
)
YSTR("min"); YMAP(
YSTR("width"); YINT(c->minw);
YSTR("height"); YINT(c->minh);
)
YSTR("aspect_ratio"); YMAP(
YSTR("min"); YDOUBLE(c->mina);
YSTR("max"); YDOUBLE(c->maxa);
)
)
YSTR("border_width"); YMAP(
YSTR("current"); YINT(c->bw);
YSTR("old"); YINT(c->oldbw);
)
YSTR("states"); YMAP(
YSTR("is_fixed"); YBOOL(c->isfixed);
YSTR("is_floating"); YBOOL(c->isfloating);
YSTR("is_urgent"); YBOOL(c->isurgent);
YSTR("never_focus"); YBOOL(c->neverfocus);
YSTR("old_state"); YBOOL(c->oldstate);
YSTR("is_fullscreen"); YBOOL(c->isfullscreen);
)
)
// clang-format on
return 0;
}
int
dump_monitor(yajl_gen gen, Monitor *mon, int is_selected)
{
// clang-format off
YMAP(
YSTR("master_factor"); YDOUBLE(mon->mfact);
YSTR("num_master"); YINT(mon->nmaster);
YSTR("num"); YINT(mon->num);
YSTR("is_selected"); YBOOL(is_selected);
YSTR("monitor_geometry"); YMAP(
YSTR("x"); YINT(mon->mx);
YSTR("y"); YINT(mon->my);
YSTR("width"); YINT(mon->mw);
YSTR("height"); YINT(mon->mh);
)
YSTR("window_geometry"); YMAP(
YSTR("x"); YINT(mon->wx);
YSTR("y"); YINT(mon->wy);
YSTR("width"); YINT(mon->ww);
YSTR("height"); YINT(mon->wh);
)
YSTR("tagset"); YMAP(
YSTR("current"); YINT(mon->tagset[mon->seltags]);
YSTR("old"); YINT(mon->tagset[mon->seltags ^ 1]);
)
YSTR("tag_state"); dump_tag_state(gen, mon->tagstate);
YSTR("clients"); YMAP(
YSTR("selected"); YINT(mon->sel ? mon->sel->win : 0);
YSTR("stack"); YARR(
for (Client* c = mon->stack; c; c = c->snext)
YINT(c->win);
)
YSTR("all"); YARR(
for (Client* c = mon->clients; c; c = c->next)
YINT(c->win);
)
)
YSTR("layout"); YMAP(
YSTR("symbol"); YMAP(
YSTR("current"); YSTR(mon->ltsymbol);
YSTR("old"); YSTR(mon->lastltsymbol);
)
YSTR("address"); YMAP(
YSTR("current"); YINT((uintptr_t)mon->lt[mon->sellt]);
YSTR("old"); YINT((uintptr_t)mon->lt[mon->sellt ^ 1]);
)
)
YSTR("bar"); YMAP(
YSTR("y"); YINT(mon->by);
YSTR("is_shown"); YBOOL(mon->showbar);
YSTR("is_top"); YBOOL(mon->barposition);
YSTR("window_id"); YINT(mon->barwin);
)
)
// clang-format on
return 0;
}
int
dump_monitors(yajl_gen gen, Monitor *mons, Monitor *selmon)
{
// clang-format off
YARR(
for (Monitor *mon = mons; mon; mon = mon->next) {
if (mon == selmon)
dump_monitor(gen, mon, 1);
else
dump_monitor(gen, mon, 0);
}
)
// clang-format on
return 0;
}
int
dump_layouts(yajl_gen gen, const Layout layouts[], const int layouts_len)
{
// clang-format off
YARR(
for (int i = 0; i < layouts_len; i++) {
YMAP(
// Check for a NULL pointer. The cycle layouts patch adds an entry at
// the end of the layouts array with a NULL pointer for the symbol
YSTR("symbol"); YSTR((layouts[i].symbol ? layouts[i].symbol : ""));
YSTR("address"); YINT((uintptr_t)(layouts + i));
)
}
)
// clang-format on
return 0;
}
int
dump_tag_state(yajl_gen gen, TagState state)
{
// clang-format off
YMAP(
YSTR("selected"); YINT(state.selected);
YSTR("occupied"); YINT(state.occupied);
YSTR("urgent"); YINT(state.urgent);
)
// clang-format on
return 0;
}
int
dump_tag_event(yajl_gen gen, int mon_num, TagState old_state,
TagState new_state)
{
// clang-format off
YMAP(
YSTR("tag_change_event"); YMAP(
YSTR("monitor_number"); YINT(mon_num);
YSTR("old_state"); dump_tag_state(gen, old_state);
YSTR("new_state"); dump_tag_state(gen, new_state);
)
)
// clang-format on
return 0;
}
int
dump_client_focus_change_event(yajl_gen gen, Client *old_client,
Client *new_client, int mon_num)
{
// clang-format off
YMAP(
YSTR("client_focus_change_event"); YMAP(
YSTR("monitor_number"); YINT(mon_num);
YSTR("old_win_id"); old_client == NULL ? YNULL() : YINT(old_client->win);
YSTR("new_win_id"); new_client == NULL ? YNULL() : YINT(new_client->win);
)
)
// clang-format on
return 0;
}
int
dump_layout_change_event(yajl_gen gen, const int mon_num,
const char *old_symbol, const Layout *old_layout,
const char *new_symbol, const Layout *new_layout)
{
// clang-format off
YMAP(
YSTR("layout_change_event"); YMAP(
YSTR("monitor_number"); YINT(mon_num);
YSTR("old_symbol"); YSTR(old_symbol);
YSTR("old_address"); YINT((uintptr_t)old_layout);
YSTR("new_symbol"); YSTR(new_symbol);
YSTR("new_address"); YINT((uintptr_t)new_layout);
)
)
// clang-format on
return 0;
}
int
dump_monitor_focus_change_event(yajl_gen gen, const int last_mon_num,
const int new_mon_num)
{
// clang-format off
YMAP(
YSTR("monitor_focus_change_event"); YMAP(
YSTR("old_monitor_number"); YINT(last_mon_num);
YSTR("new_monitor_number"); YINT(new_mon_num);
)
)
// clang-format on
return 0;
}
int
dump_focused_title_change_event(yajl_gen gen, const int mon_num,
const Window client_id, const char *old_name,
const char *new_name)
{
// clang-format off
YMAP(
YSTR("focused_title_change_event"); YMAP(
YSTR("monitor_number"); YINT(mon_num);
YSTR("client_window_id"); YINT(client_id);
YSTR("old_name"); YSTR(old_name);
YSTR("new_name"); YSTR(new_name);
)
)
// clang-format on
return 0;
}
int
dump_client_state(yajl_gen gen, const ClientState *state)
{
// clang-format off
YMAP(
YSTR("old_state"); YBOOL(state->oldstate);
YSTR("is_fixed"); YBOOL(state->isfixed);
YSTR("is_floating"); YBOOL(state->isfloating);
YSTR("is_fullscreen"); YBOOL(state->isfullscreen);
YSTR("is_urgent"); YBOOL(state->isurgent);
YSTR("never_focus"); YBOOL(state->neverfocus);
)
// clang-format on
return 0;
}
int
dump_focused_state_change_event(yajl_gen gen, const int mon_num,
const Window client_id,
const ClientState *old_state,
const ClientState *new_state)
{
// clang-format off
YMAP(
YSTR("focused_state_change_event"); YMAP(
YSTR("monitor_number"); YINT(mon_num);
YSTR("client_window_id"); YINT(client_id);
YSTR("old_state"); dump_client_state(gen, old_state);
YSTR("new_state"); dump_client_state(gen, new_state);
)
)
// clang-format on
return 0;
}
int
dump_error_message(yajl_gen gen, const char *reason)
{
// clang-format off
YMAP(
YSTR("result"); YSTR("error");
YSTR("reason"); YSTR(reason);
)
// clang-format on
return 0;
}

65
toggle/ipc-yajl.h Normal file
View file

@ -0,0 +1,65 @@
#ifndef YAJL_DUMPS_H_
#define YAJL_DUMPS_H_
#include <string.h>
#include <yajl/yajl_gen.h>
#define YSTR(str) yajl_gen_string(gen, (unsigned char *)str, strlen(str))
#define YINT(num) yajl_gen_integer(gen, num)
#define YDOUBLE(num) yajl_gen_double(gen, num)
#define YBOOL(v) yajl_gen_bool(gen, v)
#define YNULL() yajl_gen_null(gen)
#define YARR(body) \
{ \
yajl_gen_array_open(gen); \
body; \
yajl_gen_array_close(gen); \
}
#define YMAP(body) \
{ \
yajl_gen_map_open(gen); \
body; \
yajl_gen_map_close(gen); \
}
int dump_tag(yajl_gen gen, const char *name, const int tag_mask);
int dump_tags(yajl_gen gen, int tags_len);
int dump_client(yajl_gen gen, Client *c);
int dump_monitor(yajl_gen gen, Monitor *mon, int is_selected);
int dump_monitors(yajl_gen gen, Monitor *mons, Monitor *selmon);
int dump_layouts(yajl_gen gen, const Layout layouts[], const int layouts_len);
int dump_tag_state(yajl_gen gen, TagState state);
int dump_tag_event(yajl_gen gen, int mon_num, TagState old_state,
TagState new_state);
int dump_client_focus_change_event(yajl_gen gen, Client *old_client,
Client *new_client, int mon_num);
int dump_layout_change_event(yajl_gen gen, const int mon_num,
const char *old_symbol, const Layout *old_layout,
const char *new_symbol, const Layout *new_layout);
int dump_monitor_focus_change_event(yajl_gen gen, const int last_mon_num,
const int new_mon_num);
int dump_focused_title_change_event(yajl_gen gen, const int mon_num,
const Window client_id,
const char *old_name, const char *new_name);
int dump_client_state(yajl_gen gen, const ClientState *state);
int dump_focused_state_change_event(yajl_gen gen, const int mon_num,
const Window client_id,
const ClientState *old_state,
const ClientState *new_state);
int dump_error_message(yajl_gen gen, const char *reason);
#endif // YAJL_DUMPS_H_

1201
toggle/ipc.c Normal file

File diff suppressed because it is too large Load diff

318
toggle/ipc.h Normal file
View file

@ -0,0 +1,318 @@
#ifndef IPC_H_
#define IPC_H_
#include <stdint.h>
#include <sys/epoll.h>
#include <yajl/yajl_gen.h>
#include "ipc-cli.h"
// clang-format off
#define IPC_MAGIC "DWM-IPC"
#define IPC_MAGIC_ARR { 'D', 'W', 'M', '-', 'I', 'P', 'C'}
#define IPC_MAGIC_LEN 7 // Not including null char
#define IPCCOMMAND(FUNC, ARGC, TYPES) \
{ #FUNC, {FUNC }, ARGC, (ArgType[ARGC])TYPES }
// clang-format on
typedef enum IPCMessageType {
IPC_TYPE_RUN_COMMAND = 0,
IPC_TYPE_GET_MONITORS = 1,
IPC_TYPE_GET_TAGS = 2,
IPC_TYPE_GET_LAYOUTS = 3,
IPC_TYPE_GET_DWM_CLIENT = 4,
IPC_TYPE_SUBSCRIBE = 5,
IPC_TYPE_EVENT = 6
} IPCMessageType;
typedef enum IPCEvent {
IPC_EVENT_TAG_CHANGE = 1 << 0,
IPC_EVENT_CLIENT_FOCUS_CHANGE = 1 << 1,
IPC_EVENT_LAYOUT_CHANGE = 1 << 2,
IPC_EVENT_MONITOR_FOCUS_CHANGE = 1 << 3,
IPC_EVENT_FOCUSED_TITLE_CHANGE = 1 << 4,
IPC_EVENT_FOCUSED_STATE_CHANGE = 1 << 5
} IPCEvent;
typedef enum IPCSubscriptionAction {
IPC_ACTION_UNSUBSCRIBE = 0,
IPC_ACTION_SUBSCRIBE = 1
} IPCSubscriptionAction;
/**
* Every IPC packet starts with this structure
*/
typedef struct dwm_ipc_header {
uint8_t magic[IPC_MAGIC_LEN];
uint32_t size;
uint8_t type;
} __attribute((packed)) dwm_ipc_header_t;
typedef enum ArgType {
ARG_TYPE_NONE = 0,
ARG_TYPE_UINT = 1,
ARG_TYPE_SINT = 2,
ARG_TYPE_FLOAT = 3,
ARG_TYPE_PTR = 4,
ARG_TYPE_STR = 5
} ArgType;
/**
* An IPCCommand function can have either of these function signatures
*/
typedef union ArgFunction {
void (*single_param)(const Arg *);
void (*array_param)(const Arg *, int);
} ArgFunction;
typedef struct IPCCommand {
char *name;
ArgFunction func;
unsigned int argc;
ArgType *arg_types;
} IPCCommand;
typedef struct IPCParsedCommand {
char *name;
Arg *args;
ArgType *arg_types;
unsigned int argc;
} IPCParsedCommand;
/**
* Initialize the IPC socket and the IPC module
*
* @param socket_path Path to create the socket at
* @param epoll_fd File descriptor for epoll
* @param commands Address of IPCCommands array defined in config.h
* @param commands_len Length of commands[] array
*
* @return int The file descriptor of the socket if it was successfully created,
* -1 otherwise
*/
int ipc_init(const char *socket_path, const int p_epoll_fd,
IPCCommand commands[], const int commands_len);
/**
* Uninitialize the socket and module. Free allocated memory and restore static
* variables to their state before ipc_init
*/
void ipc_cleanup();
/**
* Get the file descriptor of the IPC socket
*
* @return int File descriptor of IPC socket, -1 if socket not created.
*/
int ipc_get_sock_fd();
/**
* Get address to IPCClient with specified file descriptor
*
* @param fd File descriptor of IPC Client
*
* @return Address to IPCClient with specified file descriptor, -1 otherwise
*/
IPCClient *ipc_get_client(int fd);
/**
* Check if an IPC client exists with the specified file descriptor
*
* @param fd File descriptor
*
* @return int 1 if client exists, 0 otherwise
*/
int ipc_is_client_registered(int fd);
/**
* Disconnect an IPCClient from the socket and remove the client from the list
* of known connected clients
*
* @param c Address of IPCClient
*
* @return 0 if the client's file descriptor was closed successfully, the
* result of executing close() on the file descriptor otherwise.
*/
int ipc_drop_client(IPCClient *c);
/**
* Accept an IPC Client requesting to connect to the socket and add it to the
* list of clients
*
* @return File descriptor of new client, -1 on error
*/
int ipc_accept_client();
/**
* Read an incoming message from an accepted IPC client
*
* @param c Address of IPCClient
* @param msg_type Address to IPCMessageType variable which will be assigned
* the message type of the received message
* @param msg_size Address to uint32_t variable which will be assigned the size
* of the received message
* @param msg Address to char* variable which will be assigned the address of
* the received message. This must be freed using free().
*
* @return 0 on success, -1 on error reading message, -2 if reading the message
* resulted in EAGAIN, EINTR, or EWOULDBLOCK.
*/
int ipc_read_client(IPCClient *c, IPCMessageType *msg_type, uint32_t *msg_size,
char **msg);
/**
* Write any pending buffer of the client to the client's socket
*
* @param c Client whose buffer to write
*
* @return Number of bytes written >= 0, -1 otherwise. errno will still be set
* from the write operation.
*/
ssize_t ipc_write_client(IPCClient *c);
/**
* Prepare a message in the specified client's buffer.
*
* @param c Client to prepare message for
* @param msg_type Type of message to prepare
* @param msg_size Size of the message in bytes. Should not exceed
* MAX_MESSAGE_SIZE
* @param msg Message to prepare (not including header). This pointer can be
* freed after the function invocation.
*/
void ipc_prepare_send_message(IPCClient *c, const IPCMessageType msg_type,
const uint32_t msg_size, const char *msg);
/**
* Prepare an error message in the specified client's buffer
*
* @param c Client to prepare message for
* @param msg_type Type of message
* @param format Format string following vsprintf
* @param ... Arguments for format string
*/
void ipc_prepare_reply_failure(IPCClient *c, IPCMessageType msg_type,
const char *format, ...);
/**
* Prepare a success message in the specified client's buffer
*
* @param c Client to prepare message for
* @param msg_type Type of message
*/
void ipc_prepare_reply_success(IPCClient *c, IPCMessageType msg_type);
/**
* Send a tag_change_event to all subscribers. Should be called only when there
* has been a tag state change.
*
* @param mon_num The index of the monitor (Monitor.num property)
* @param old_state The old tag state
* @param new_state The new (now current) tag state
*/
void ipc_tag_change_event(const int mon_num, TagState old_state,
TagState new_state);
/**
* Send a client_focus_change_event to all subscribers. Should be called only
* when the client focus changes.
*
* @param mon_num The index of the monitor (Monitor.num property)
* @param old_client The old DWM client selection (Monitor.oldsel)
* @param new_client The new (now current) DWM client selection
*/
void ipc_client_focus_change_event(const int mon_num, Client *old_client,
Client *new_client);
/**
* Send a layout_change_event to all subscribers. Should be called only
* when there has been a layout change.
*
* @param mon_num The index of the monitor (Monitor.num property)
* @param old_symbol The old layout symbol
* @param old_layout Address to the old Layout
* @param new_symbol The new (now current) layout symbol
* @param new_layout Address to the new Layout
*/
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);
/**
* Send a monitor_focus_change_event to all subscribers. Should be called only
* when the monitor focus changes.
*
* @param last_mon_num The index of the previously selected monitor
* @param new_mon_num The index of the newly selected monitor
*/
void ipc_monitor_focus_change_event(const int last_mon_num,
const int new_mon_num);
/**
* Send a focused_title_change_event to all subscribers. Should only be called
* if a selected client has a title change.
*
* @param mon_num Index of the client's monitor
* @param client_id Window XID of client
* @param old_name Old name of the client window
* @param new_name New name of the client window
*/
void ipc_focused_title_change_event(const int mon_num, const Window client_id,
const char *old_name, const char *new_name);
/**
* Send a focused_state_change_event to all subscribers. Should only be called
* if a selected client has a state change.
*
* @param mon_num Index of the client's monitor
* @param client_id Window XID of client
* @param old_state Old state of the client
* @param new_state New state of the client
*/
void ipc_focused_state_change_event(const int mon_num, const Window client_id,
const ClientState *old_state,
const ClientState *new_state);
/**
* Check to see if an event has occured and call the *_change_event functions
* accordingly
*
* @param mons Address of Monitor pointing to start of linked list
* @param lastselmon Address of pointer to previously selected monitor
* @param selmon Address of selected Monitor
*/
void ipc_send_events(Monitor *mons, Monitor **lastselmon, Monitor *selmon);
/**
* Handle an epoll event caused by a registered IPC client. Read, process, and
* handle any received messages from clients. Write pending buffer to client if
* the client is ready to receive messages. Drop clients that have sent an
* EPOLLHUP.
*
* @param ev Associated epoll event returned by epoll_wait
* @param mons Address of Monitor pointing to start of linked list
* @param selmon Address of selected Monitor
* @param lastselmon Address of pointer to previously selected monitor
* @param tags Array of tag names
* @param tags_len Length of tags array
* @param layouts Array of available layouts
* @param layouts_len Length of layouts array
*
* @return 0 if event was successfully handled, -1 on any error receiving
* or handling incoming messages or unhandled epoll event.
*/
int ipc_handle_client_epoll_event(struct epoll_event *ev, Monitor *mons,
Monitor **lastselmon, Monitor *selmon, const int tags_len,
const Layout *layouts, const int layouts_len);
/**
* Handle an epoll event caused by the IPC socket. This function only handles an
* EPOLLIN event indicating a new client requesting to connect to the socket.
*
* @param ev Associated epoll event returned by epoll_wait
*
* @return 0, if the event was successfully handled, -1 if not an EPOLLIN event
* or if a new IPC client connection request could not be accepted.
*/
int ipc_handle_socket_epoll_event(struct epoll_event *ev);
#endif /* IPC_H_ */

140
util.c
View file

@ -3,6 +3,11 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "toggle.h"
#ifdef USEIPC
#include <errno.h>
#include <sys/stat.h>
#endif
#include "util.h" #include "util.h"
@ -33,3 +38,138 @@ die(const char *fmt, ...) {
exit(1); exit(1);
} }
#if USEIPC
int
normalizepath(const char *path, char **normal)
{
size_t len = strlen(path);
*normal = (char *)malloc((len + 1) * sizeof(char));
const char *walk = path;
const char *match;
size_t newlen = 0;
while ((match = strchr(walk, '/'))) {
// Copy everything between match and walk
strncpy(*normal + newlen, walk, match - walk);
newlen += match - walk;
walk += match - walk;
// Skip all repeating slashes
while (*walk == '/')
walk++;
// If not last character in path
if (walk != path + len)
(*normal)[newlen++] = '/';
}
(*normal)[newlen++] = '\0';
// Copy remaining path
strcat(*normal, walk);
newlen += strlen(walk);
*normal = (char *)realloc(*normal, newlen * sizeof(char));
return 0;
}
int
parentdir(const char *path, char **parent)
{
char *normal;
char *walk;
normalizepath(path, &normal);
// Pointer to last '/'
if (!(walk = strrchr(normal, '/'))) {
free(normal);
return -1;
}
// Get path up to last '/'
size_t len = walk - normal;
*parent = (char *)malloc((len + 1) * sizeof(char));
// Copy path up to last '/'
strncpy(*parent, normal, len);
// Add null char
(*parent)[len] = '\0';
free(normal);
return 0;
}
int
mkdirp(const char *path)
{
char *normal;
char *walk;
size_t normallen;
normalizepath(path, &normal);
normallen = strlen(normal);
walk = normal;
while (walk < normal + normallen + 1) {
// Get length from walk to next /
size_t n = strcspn(walk, "/");
// Skip path /
if (n == 0) {
walk++;
continue;
}
// Length of current path segment
size_t curpathlen = walk - normal + n;
char curpath[curpathlen + 1];
struct stat s;
// Copy path segment to stat
strncpy(curpath, normal, curpathlen);
strcpy(curpath + curpathlen, "");
int res = stat(curpath, &s);
if (res < 0) {
if (errno == ENOENT) {
DEBUG("Making directory %s\n", curpath);
if (mkdir(curpath, 0700) < 0) {
fprintf(stderr, "Failed to make directory %s\n", curpath);
perror("");
free(normal);
return -1;
}
} else {
fprintf(stderr, "Error statting directory %s\n", curpath);
perror("");
free(normal);
return -1;
}
}
// Continue to next path segment
walk += n;
}
free(normal);
return 0;
}
int
nullterminate(char **str, size_t *len)
{
if ((*str)[*len - 1] == '\0')
return 0;
(*len)++;
*str = (char*)realloc(*str, *len * sizeof(char));
(*str)[*len - 1] = '\0';
return 0;
}
#endif

14
util.h
View file

@ -4,5 +4,19 @@
#define MIN(A, B) ((A) < (B) ? (A) : (B)) #define MIN(A, B) ((A) < (B) ? (A) : (B))
#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) #define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
#ifdef USEIPC
#ifdef _DEBUG
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
#else
#define DEBUG(...)
#endif
#endif
void die(const char *fmt, ...); void die(const char *fmt, ...);
void *ecalloc(size_t nmemb, size_t size); void *ecalloc(size_t nmemb, size_t size);
#ifdef USEIPC
int normalizepath(const char *path, char **normal);
int mkdirp(const char *path);
int parentdir(const char *path, char **parent);
int nullterminate(char **str, size_t *len);
#endif