commit c64aac0fcf7f17b3dcb5d6abd12c050f0975fc06
parent e10be19558a92637133680c6501832befc8bec6f
Author: Jaywalker <justin@justinandyadi.com>
Date: Tue, 19 Mar 2024 13:52:39 -0500
Added new patch for tabbed: unix-socket-control
This patch allows tabbed to be controlled by a UNIX socket, much like bspwm is
controlled by a UNIX socket. This allows a user to use sxhkd for all keybinds
needed to interact with tabbed.
Diffstat:
2 files changed, 417 insertions(+), 0 deletions(-)
diff --git a/tools.suckless.org/tabbed/patches/unix-socket-control/index.md b/tools.suckless.org/tabbed/patches/unix-socket-control/index.md
@@ -0,0 +1,31 @@
+UNIX Socket Control
+================================
+
+Description
+-----------
+This patch removes the built-in keybinds and instead replaces them with a UNIX socket and protocol for input.
+The idea is essentially the same as how bspwm uses bspc for controlling the windows and sxkhd to bind the bspc
+commands to keys.
+
+The UNIX socket path is `/tmp/tabbed_$WINID-socket` where `WINID` is the tabbed window id in decimal format.
+The protocol uses the same names and arguments as the internal functions separated by `\0`.
+
+For example, to select the next tab, you would use: `printf "%s\0%s\0" "rotate" "1" | nc -U "/tmp/tabbed_$WINID-socket"`
+
+I have also created two scripts to be used in conjunction with this patch: `tabc` and `bspctab`.
+The `tabc` script allows more easy access to common actions like changing tabs, moving tabs, etc.
+For example, the above command to select the next tab using `tabc` would be simply: `tabc tabnext "$WINID"`
+
+The `bspctab` uses `tabc` to making using tabs as seamless as possible in bspwm. The intent was for tabs in bspwm
+to feel much like using tabs in i3 as that was the only feature I missed when switching to bspwm.
+Details on how to implement `bspctab` in your `sxhkdrc` can be found along side the scripts in my repo listed below.
+
+[Helper Scripts Repo](https://github.com/Jaywalker/tabbed-unix-socket-scripts)
+
+Download
+--------
+* [tabbed-unix-socket-control-20240319.diff](tabbed-unix-socket-control-20240319.diff)
+
+Authors
+-------
+* Justin Williams
diff --git a/tools.suckless.org/tabbed/patches/unix-socket-control/tabbed-unix-socket-control-20240319.diff b/tools.suckless.org/tabbed/patches/unix-socket-control/tabbed-unix-socket-control-20240319.diff
@@ -0,0 +1,386 @@
+diff --git a/Makefile b/Makefile
+index 54ba350..049a086 100644
+--- a/Makefile
++++ b/Makefile
+@@ -10,7 +10,7 @@ DOCPREFIX = ${PREFIX}/share/doc/${NAME}
+
+ # use system flags.
+ TABBED_CFLAGS = -I/usr/X11R6/include -I/usr/include/freetype2 ${CFLAGS}
+-TABBED_LDFLAGS = -L/usr/X11R6/lib -lX11 -lfontconfig -lXft ${LDFLAGS}
++TABBED_LDFLAGS = -L/usr/X11R6/lib -lX11 -lfontconfig -lXft -lpthread ${LDFLAGS}
+ TABBED_CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=700L
+
+ # OpenBSD (uncomment)
+diff --git a/config.def.h b/config.def.h
+index 51bb13d..2c0f3fd 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -32,35 +32,3 @@ static Bool npisrelative = False;
+ p, winid, NULL \
+ } \
+ }
+-
+-#define MODKEY ControlMask
+-static const Key keys[] = {
+- /* modifier key function argument */
+- { MODKEY|ShiftMask, XK_Return, focusonce, { 0 } },
+- { MODKEY|ShiftMask, XK_Return, spawn, { 0 } },
+-
+- { MODKEY|ShiftMask, XK_l, rotate, { .i = +1 } },
+- { MODKEY|ShiftMask, XK_h, rotate, { .i = -1 } },
+- { MODKEY|ShiftMask, XK_j, movetab, { .i = -1 } },
+- { MODKEY|ShiftMask, XK_k, movetab, { .i = +1 } },
+- { MODKEY, XK_Tab, rotate, { .i = 0 } },
+-
+- { MODKEY, XK_grave, spawn, SETPROP("_TABBED_SELECT_TAB") },
+- { MODKEY, XK_1, move, { .i = 0 } },
+- { MODKEY, XK_2, move, { .i = 1 } },
+- { MODKEY, XK_3, move, { .i = 2 } },
+- { MODKEY, XK_4, move, { .i = 3 } },
+- { MODKEY, XK_5, move, { .i = 4 } },
+- { MODKEY, XK_6, move, { .i = 5 } },
+- { MODKEY, XK_7, move, { .i = 6 } },
+- { MODKEY, XK_8, move, { .i = 7 } },
+- { MODKEY, XK_9, move, { .i = 8 } },
+- { MODKEY, XK_0, move, { .i = 9 } },
+-
+- { MODKEY, XK_q, killclient, { 0 } },
+-
+- { MODKEY, XK_u, focusurgent, { 0 } },
+- { MODKEY|ShiftMask, XK_u, toggle, { .v = (void*) &urgentswitch } },
+-
+- { 0, XK_F11, fullscreen, { 0 } },
+-};
+diff --git a/tabbed.c b/tabbed.c
+index 81be5e4..47de28b 100644
+--- a/tabbed.c
++++ b/tabbed.c
+@@ -2,8 +2,12 @@
+ * See LICENSE file for copyright and license details.
+ */
+
++#include <sys/socket.h>
++#include <sys/un.h>
+ #include <sys/wait.h>
++#include <fcntl.h>
+ #include <locale.h>
++#include <pthread.h>
+ #include <signal.h>
+ #include <stdarg.h>
+ #include <stdio.h>
+@@ -47,6 +51,10 @@
+ #define LENGTH(x) (sizeof((x)) / sizeof(*(x)))
+ #define CLEANMASK(mask) (mask & ~(numlockmask | LockMask))
+ #define TEXTW(x) (textnw(x, strlen(x)) + dc.font.height)
++#define streq(s1, s2) (strcmp((s1), (s2)) == 0)
++
++/* UNIX Socket settings */
++#define SOCKET_PATH_TPL "/tmp/tabbed_%s-socket"
+
+ enum { ColFG, ColBG, ColLast }; /* color */
+ enum { WMProtocols, WMDelete, WMName, WMState, WMFullscreen,
+@@ -57,13 +65,6 @@ typedef union {
+ const void *v;
+ } Arg;
+
+-typedef struct {
+- unsigned int mod;
+- KeySym keysym;
+- void (*func)(const Arg *);
+- const Arg arg;
+-} Key;
+-
+ typedef struct {
+ int x, y, w, h;
+ XftColor norm[ColLast];
+@@ -111,6 +112,7 @@ static int getclient(Window w);
+ static XftColor getcolor(const char *colstr);
+ static int getfirsttab(void);
+ static Bool gettextprop(Window w, Atom atom, char *text, unsigned int size);
++static void handle_message(char *msg, int msg_len, FILE *rsp, int sock_fd);
+ static void initfont(const char *fontstr);
+ static Bool isprotodel(int c);
+ static void keypress(const XEvent *e);
+@@ -119,6 +121,7 @@ static void manage(Window win);
+ static void maprequest(const XEvent *e);
+ static void move(const Arg *arg);
+ static void movetab(const Arg *arg);
++static void process_message(char **args, int num, FILE *rsp, int sock_fd);
+ static void propertynotify(const XEvent *e);
+ static void resize(int c, int w, int h);
+ static void rotate(const Arg *arg);
+@@ -148,7 +151,6 @@ static void (*handler[LASTEvent]) (const XEvent *) = {
+ [DestroyNotify] = destroynotify,
+ [Expose] = expose,
+ [FocusIn] = focusin,
+- [KeyPress] = keypress,
+ [MapRequest] = maprequest,
+ [PropertyNotify] = propertynotify,
+ };
+@@ -169,6 +171,8 @@ static char winid[64];
+ static char **cmd;
+ static char *wmname = "tabbed";
+ static const char *geometry;
++static int sock_fd = -1;
++static char socket_path[256];
+
+ char *argv0;
+
+@@ -228,6 +232,9 @@ cleanup(void)
+ XDestroyWindow(dpy, win);
+ XSync(dpy, False);
+ free(cmd);
++
++ close(sock_fd);
++ unlink(socket_path);
+ }
+
+ void
+@@ -657,22 +664,6 @@ isprotodel(int c)
+ return ret;
+ }
+
+-void
+-keypress(const XEvent *e)
+-{
+- const XKeyEvent *ev = &e->xkey;
+- unsigned int i;
+- KeySym keysym;
+-
+- keysym = XkbKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0, 0);
+- for (i = 0; i < LENGTH(keys); i++) {
+- if (keysym == keys[i].keysym &&
+- CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) &&
+- keys[i].func)
+- keys[i].func(&(keys[i].arg));
+- }
+-}
+-
+ void
+ killclient(const Arg *arg)
+ {
+@@ -713,16 +704,6 @@ manage(Window w)
+ StructureNotifyMask | EnterWindowMask);
+ XSync(dpy, False);
+
+- for (i = 0; i < LENGTH(keys); i++) {
+- if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) {
+- for (j = 0; j < LENGTH(modifiers); j++) {
+- XGrabKey(dpy, code, keys[i].mod |
+- modifiers[j], w, True,
+- GrabModeAsync, GrabModeAsync);
+- }
+- }
+- }
+-
+ c = ecalloc(1, sizeof *c);
+ c->win = w;
+
+@@ -919,6 +900,152 @@ rotate(const Arg *arg)
+ }
+ }
+
++void
++handle_message(char *msg, int msg_len, FILE *rsp, int sock_fd)
++{
++ int cap = 8;
++ int num = 0;
++ char **args = calloc(cap, sizeof(char *));
++
++ if (args == NULL) {
++ perror("Handle message: calloc");
++ return;
++ }
++
++ for (int i = 0, j = 0; i < msg_len; i++) {
++ if (msg[i] == 0) {
++ args[num++] = msg + j;
++ j = i + 1;
++ }
++ if (num >= cap) {
++ cap *= 2;
++ char **new = realloc(args, cap * sizeof(char *));
++ if (new == NULL) {
++ free(args);
++ perror("Handle message: realloc");
++ return;
++ } else {
++ args = new;
++ }
++ }
++ }
++
++ if (num < 1) {
++ free(args);
++ perror("No arguments given.");
++ return;
++ }
++
++ char **args_orig = args;
++ process_message(args, num, rsp, sock_fd);
++ free(args_orig);
++}
++
++void
++process_message(char **args, int num, FILE *rsp, int sock_fd)
++{
++ char buf[256];
++ Arg arg;
++ if (streq("rotate", *args)) {
++ if (num == 2) {
++ args++;
++ arg.i = atoi(*args);
++ rotate(&arg);
++ } else {
++ arg.i = 1;
++ rotate(&arg);
++ }
++ } else if (streq("movetab", *args)) {
++ if (num == 2) {
++ args++;
++ arg.i = atoi(*args);
++ movetab(&arg);
++ }
++ } else if (streq("move", *args)) {
++ if (num == 2) {
++ args++;
++ arg.i = atoi(*args);
++ move(&arg);
++ }
++ } else if (streq("toggle", *args)) {
++ arg.v = (void*)&urgentswitch;
++ toggle(&arg);
++ } else if (streq("killclient", *args)) {
++ arg.i = 0;
++ killclient(&arg);
++ } else if (streq("focusonce", *args)) {
++ arg.i = 0;
++ focusonce(&arg);
++ } else if (streq("focusurgent", *args)) {
++ arg.i = 0;
++ focusurgent(&arg);
++ } else if (streq("spawn", *args)) {
++ arg.i = 0;
++ spawn(&arg);
++ } else if (streq("fullscreen", *args)) {
++ arg.i = 0;
++ fullscreen(&arg);
++ } else if (streq("isfirst", *args)) {
++ if (sel == 0) {
++ write(sock_fd, "1", 1);
++ } else {
++ write(sock_fd, "0", 1);
++ }
++ } else if (streq("islast", *args)) {
++ if (nclients > 0 && sel == (nclients-1)) {
++ write(sock_fd, "1", 1);
++ } else {
++ write(sock_fd, "0", 1);
++ }
++ } else if (streq("isempty", *args)) {
++ if (nclients == 0) {
++ write(sock_fd, "1", 1);
++ } else {
++ write(sock_fd, "0", 1);
++ }
++ } else if (streq("totaltabs", *args)) {
++ if (nclients > 0) {
++ snprintf(buf, sizeof(buf), "%d", nclients);
++ write(sock_fd, buf, strlen(buf));
++ } else {
++ write(sock_fd, "0", 1);
++ }
++ } else if (streq("tabnumber", *args)) {
++ snprintf(buf, sizeof(buf), "%d", sel);
++ write(sock_fd, buf, strlen(buf));
++ }
++ fflush(rsp);
++ fclose(rsp);
++}
++
++void *
++socket_run(void *vargp)
++{
++ fd_set descriptors;
++ int cli_fd, n;
++ char msg[BUFSIZ] = {0};
++
++ while (running) {
++ FD_ZERO(&descriptors);
++ FD_SET(sock_fd, &descriptors);
++
++ if (FD_ISSET(sock_fd, &descriptors)) {
++ cli_fd = accept(sock_fd, NULL, 0);
++ if (cli_fd > 0 && (n = recv(cli_fd, msg, sizeof(msg)-1, 0)) > 0) {
++ msg[n] = '\0';
++ FILE *rsp = fdopen(cli_fd, "w");
++ if (rsp != NULL) {
++ handle_message(msg, n, rsp, cli_fd);
++ } else {
++ fprintf(stderr, "warn: Can't open the client socket as file.\n");
++ }
++ close(cli_fd);
++ }
++ }
++ }
++ return NULL;
++}
++
+ void
+ run(void)
+ {
+@@ -930,6 +1057,9 @@ run(void)
+ if (doinitspawn == True)
+ spawn(NULL);
+
++ pthread_t thread_id;
++ pthread_create(&thread_id, NULL, socket_run, NULL);
++
+ while (running) {
+ XNextEvent(dpy, &ev);
+ if (handler[ev.type])
+@@ -1083,6 +1213,35 @@ setup(void)
+
+ nextfocus = foreground;
+ focus(-1);
++
++ /* Setup UNIX socket */
++ struct sockaddr_un sock_address;
++ if (sock_fd == -1) {
++ snprintf(socket_path, sizeof(socket_path), SOCKET_PATH_TPL, winid);
++
++ sock_address.sun_family = AF_UNIX;
++ if (snprintf(sock_address.sun_path, sizeof(sock_address.sun_path), "%s", socket_path) < 0) {
++ fprintf(stderr, "Couldn't write the socket path.\n");
++ }
++
++ sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
++
++ if (sock_fd == -1) {
++ fprintf(stderr, "Couldn't create the socket.\n");
++ }
++
++ unlink(socket_path);
++
++ if (bind(sock_fd, (struct sockaddr *) &sock_address, sizeof(sock_address)) == -1) {
++ fprintf(stderr, "Couldn't bind a name to the socket.\n");
++ }
++
++ if (listen(sock_fd, SOMAXCONN) == -1) {
++ fprintf(stderr, "Couldn't listen to the socket.\n");
++ }
++ }
++
++ fcntl(sock_fd, F_SETFD, FD_CLOEXEC | fcntl(sock_fd, F_GETFD));
+ }
+
+ void
+@@ -1286,6 +1445,8 @@ main(int argc, char *argv[])
+ int replace = 0;
+ char *pstr;
+
++ XInitThreads();
++
+ ARGBEGIN {
+ case 'c':
+ closelastclient = True;