From 968ceac7f34bc084c59a7e7d470a5b99a7d0ee77 Mon Sep 17 00:00:00 2001
From: Utkarsh Verma <utkarshverma@protonmail.com>
Date: Sat, 17 Apr 2021 15:47:40 +0530
Subject: [PATCH] Execute blocks asynchronously

---
 README.md |   9 +--
 config.h  |  15 +++--
 main.c    | 178 +++++++++++++++++++++++++-----------------------------
 3 files changed, 99 insertions(+), 103 deletions(-)

diff --git a/README.md b/README.md
index 20c2503..6e01143 100644
--- a/README.md
+++ b/README.md
@@ -5,8 +5,9 @@ A modular statusbar for `dwm` written in C. You may think of it as `i3blocks`, b
 - Modular
 - Lightweight
 - Suckless
-- Modules are clickable
-- Each module can be externally triggered to update itself
+- Blocks are clickable
+- Blocks are loaded asynchronously
+- Each block can be externally triggered to update itself
 - Compatible with `i3blocks` scripts
 
 > Apart from these features, this build of `dwmblocks` is more optimized and fixes the scroll issue due to which the statusbar flickers on scrolling.
@@ -68,7 +69,7 @@ Update signal | Signal to be used for triggering the block. Must be a positive i
 
 The syntax for defining a block is:
 ```c
-static const Block blocks[] = {
+static Block blocks[] = {
     ...
     {"date", 1800, 1},
     ...
@@ -80,7 +81,7 @@ Apart from that you can also modify the block delimiters and width of each block
 // Maximum length of each block, expressed in number of characters.
 #define CMDLENGTH 50
 
-#define delimiter " "
+#define DELIMITER " "
 ```
 
 ## Signalling changes
diff --git a/config.h b/config.h
index 16c61e9..a7c854d 100644
--- a/config.h
+++ b/config.h
@@ -1,7 +1,15 @@
 #define CMDLENGTH 50
-#define delimiter "  "
+#define DELIMITER "  "
 
-const Block blocks[] = {
+typedef struct {
+	char* command;
+	unsigned int interval;
+	unsigned int signal;
+	char output[CMDLENGTH];
+	int pipe[2];
+} Block;
+
+static Block blocks[] = {
 	{"sb-mail", 1800, 1},
 	{"sb-music", 0, 2},
 	{"sb-disk", 1800, 4},
@@ -10,5 +18,4 @@ const Block blocks[] = {
 	{"sb-volume", 0, 5},
 	{"sb-battery", 5, 6},
 	{"sb-date", 20, 7},
-	{"sb-network", 5, 8},
-};
+	{"sb-network", 5, 8}};
diff --git a/main.c b/main.c
index 3d4b961..aa7cbd5 100644
--- a/main.c
+++ b/main.c
@@ -1,4 +1,6 @@
 #include <X11/Xlib.h>
+#include <fcntl.h>
+#include <limits.h>
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -6,27 +8,17 @@
 #include <time.h>
 #include <unistd.h>
 
-#define len(arr) (sizeof(arr) / sizeof(arr[0]))
-
-typedef struct {
-	char *command;
-	unsigned int interval;
-	unsigned int signal;
-} Block;
 #include "config.h"
 
-void debug();
-void setRoot();
+#define LEN(arr) (sizeof(arr) / sizeof(arr[0]))
+#define MIN(x, y) (x > y ? y : x)
 
 static Display *dpy;
 static int screen;
 static Window root;
-static char commands[len(blocks)][256];
-static char statusbar[len(blocks)][CMDLENGTH];
-static char statusStr[2][len(blocks) * CMDLENGTH +
-						 (len(delimiter) - 1) * (len(blocks) - 1) + 1];
+static char statusBar[2][LEN(blocks) * (CMDLENGTH + LEN(DELIMITER) - 1) + 1];
 static int statusContinue = 1;
-static void (*writeStatus)() = setRoot;
+void (*writeStatus)();
 
 int gcd(int a, int b) {
 	int temp;
@@ -44,55 +36,45 @@ void replace(char *str, char old, char new) {
 			*ch = new;
 }
 
-// Open process *command and store output in *output
-void getCommand(const Block *block, char *output) {
-	// Leave signal in output for dwm
-	if (block->signal) {
-		output[0] = block->signal;
-		output++;
-	}
+void getCommand(int i, const char *button) {
+	Block *block = blocks + i;
 
-	char *cmd = block->command;
-	FILE *cmdFile = popen(cmd, "r");
-	if (!cmdFile) return;
+	if (fork() == 0) {
+		dup2(block->pipe[1], STDOUT_FILENO);
+		close(block->pipe[0]);
+		close(block->pipe[1]);
 
-	int fd = fileno(cmdFile);
-	if (!read(fd, output, CMDLENGTH - 2))
-		output[0] = '\0';
-	else {
-		output[CMDLENGTH - 2] = '\0';
-		replace(output, '\n', '\0');
+		// Temporary hack
+		char cmd[1024];
+		sprintf(cmd, "echo \"_$(%s)\"", block->command);
+
+		char *command[] = {"/bin/sh", "-c", cmd, NULL};
+		if (button) setenv("BLOCK_BUTTON", button, 1);
+		setsid();
+		execvp(command[0], command);
 	}
-	pclose(cmdFile);
 }
 
 void getCommands(int time) {
-	const Block *current;
-	for (int i = 0; i < len(blocks); i++) {
-		current = blocks + i;
-		if (time == -1 || (current->interval != 0 && time % current->interval == 0))
-			getCommand(current, statusbar[i]);
-	}
+	for (int i = 0; i < LEN(blocks); i++)
+		if (time == 0 || (blocks[i].interval != 0 && time % blocks[i].interval == 0))
+			getCommand(i, NULL);
 }
 
-void getSignalCommands(int signal) {
-	const Block *current;
-	for (int i = 0; i < len(blocks); i++) {
-		current = blocks + i;
-		if (current->signal == signal)
-			getCommand(current, statusbar[i]);
-	}
+void getSignalCommand(int signal) {
+	for (int i = 0; i < LEN(blocks); i++)
+		if (blocks[i].signal == signal)
+			getCommand(i, NULL);
 }
 
 int getStatus(char *new, char *old) {
 	strcpy(old, new);
 	new[0] = '\0';
-	strcat(new, delimiter);
-	for (int i = 0; i < len(blocks); i++) {
-		strcat(new, statusbar[i]);
-		if (strlen(statusbar[i]) > (blocks[i].signal != 0) &&
-			i != len(blocks) - 1)
-			strcat(new, delimiter);
+	for (int i = 0; i < LEN(blocks); i++) {
+		Block *block = blocks + i;
+		if (strlen(block->output) > (block->signal != 0))
+			strcat(new, DELIMITER);
+		strcat(new, block->output);
 	}
 	new[strlen(new)] = '\0';
 	return strcmp(new, old);
@@ -100,63 +82,67 @@ int getStatus(char *new, char *old) {
 
 void debug() {
 	// Only write out if text has changed
-	if (!getStatus(statusStr[0], statusStr[1])) return;
+	if (!getStatus(statusBar[0], statusBar[1])) return;
 
-	write(STDOUT_FILENO, statusStr[0], strlen(statusStr[0]));
+	write(STDOUT_FILENO, statusBar[0], strlen(statusBar[0]));
 	write(STDOUT_FILENO, "\n", 1);
 }
 
 void setRoot() {
 	// Only set root if text has changed
-	if (!getStatus(statusStr[0], statusStr[1])) return;
+	if (!getStatus(statusBar[0], statusBar[1])) return;
 
 	Display *d = XOpenDisplay(NULL);
 	if (d) dpy = d;
 	screen = DefaultScreen(dpy);
 	root = RootWindow(dpy, screen);
-	XStoreName(dpy, root, statusStr[0]);
+	XStoreName(dpy, root, statusBar[0]);
 	XCloseDisplay(dpy);
 }
 
 void buttonHandler(int sig, siginfo_t *si, void *ucontext) {
-	char button[2] = {'0' + si->si_value.sival_int & 0xff, '\0'};
 	sig = si->si_value.sival_int >> 8;
-	if (fork() == 0) {
-		const Block *current;
-		char *shCommand = NULL;
-		for (int i = 0; i < len(blocks); i++) {
-			current = blocks + i;
-			if (current->signal == sig) {
-				shCommand = commands[i];
-				break;
-			}
-		}
 
-		if (shCommand) {
-			char *command[] = {"/bin/sh", "-c", shCommand, NULL};
-			setenv("BLOCK_BUTTON", button, 1);
-			setsid();
-			execvp(command[0], command);
-		}
-		exit(EXIT_SUCCESS);
-	}
+	int i = 0;
+	while (blocks[i].signal != sig) i++;
+	const char button[2] = {'0' + si->si_value.sival_int & 0xff, '\0'};
+	getCommand(i, button);
 }
 
-void signalHandler(int signum) {
-	getSignalCommands(signum - SIGRTMIN);
+void signalHandler(int signal) { getSignalCommand(signal - SIGRTMIN); }
+
+void termHandler(int signal) {
+	statusContinue = 0;
+	exit(EXIT_SUCCESS);
+}
+
+void childHandler() {
+	for (int i = 0; i < LEN(blocks); i++) {
+		Block *block = blocks + i;
+
+		int bytesToRead = CMDLENGTH;
+		char *output = block->output;
+		if (block->signal) output++, bytesToRead--;
+
+		char placebo;
+		if (read(block->pipe[0], &placebo, 1) == 1) {
+			char buffer[PIPE_BUF];
+			read(block->pipe[0], buffer, PIPE_BUF);
+			replace(buffer, '\n', '\0');
+			strncpy(output, buffer, bytesToRead);
+			break;
+		}
+	}
 	writeStatus();
 }
 
-void termHandler(int sigNum) {
-	statusContinue = 0;
-	exit(0);
-}
-
 void setupSignals() {
-	struct sigaction sa;
+	signal(SIGTERM, termHandler);
+	signal(SIGINT, termHandler);
 
 	// Handle block update signals
-	for (int i = 0; i < len(blocks); i++) {
+	struct sigaction sa;
+	for (int i = 0; i < LEN(blocks); i++) {
 		if (blocks[i].signal > 0) {
 			signal(SIGRTMIN + blocks[i].signal, signalHandler);
 			sigaddset(&sa.sa_mask, SIGRTMIN + blocks[i].signal);
@@ -167,20 +153,20 @@ void setupSignals() {
 	sa.sa_sigaction = buttonHandler;
 	sa.sa_flags = SA_SIGINFO;
 	sigaction(SIGUSR1, &sa, NULL);
+
+	// Handle exit of forks
 	struct sigaction sigchld_action = {
-		.sa_handler = SIG_DFL,
+		.sa_handler = childHandler,
 		.sa_flags = SA_NOCLDWAIT,
 	};
 	sigaction(SIGCHLD, &sigchld_action, NULL);
 }
 
 void statusLoop() {
-	setupSignals();
-	getCommands(-1);
-	writeStatus();
+	getCommands(0);
 
 	unsigned int sleepInterval = -1;
-	for (int i = 0; i < len(blocks); i++)
+	for (int i = 0; i < LEN(blocks); i++)
 		if (blocks[i].interval)
 			sleepInterval = gcd(blocks[i].interval, sleepInterval);
 
@@ -194,7 +180,6 @@ void statusLoop() {
 
 		// Write to status after sleeping
 		getCommands(i);
-		writeStatus();
 
 		// After sleep, reset timer and update counter
 		toSleep = sleepTime;
@@ -203,15 +188,18 @@ void statusLoop() {
 }
 
 int main(int argc, char **argv) {
-	const int processID = getpid();
-	for (int i = 0; i < len(blocks); i++)
-		sprintf(commands[i], "%s && kill -%d %d", blocks[i].command,
-				SIGRTMIN + blocks[i].signal, processID);
-
+	writeStatus = setRoot;
 	for (int i = 0; i < argc; i++)
 		if (!strcmp("-d", argv[i])) writeStatus = debug;
 
-	signal(SIGTERM, termHandler);
-	signal(SIGINT, termHandler);
+	for (int i = 0; i < LEN(blocks); i++) {
+		Block *block = blocks + i;
+		pipe(block->pipe);
+		fcntl(block->pipe[0], F_SETFL, O_NONBLOCK);
+
+		if (block->signal) block->output[0] = block->signal;
+	}
+
+	setupSignals();
 	statusLoop();
 }