summaryrefslogtreecommitdiff
path: root/dwmblocks
diff options
context:
space:
mode:
authorTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2025-03-08 15:21:28 +0900
committerTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2025-03-08 15:21:28 +0900
commit4437d5b3c3eea76f6e2b0fd4a2ba21c02a098aeb (patch)
treee8dcb20bf144aacf88f93b012dccacdeb08015cd /dwmblocks
parentc2b06f0d5795a789f4ddab459179ff89aedfee98 (diff)
updates
Diffstat (limited to 'dwmblocks')
-rw-r--r--dwmblocks/.clang-format8
-rw-r--r--dwmblocks/.clang-tidy30
-rw-r--r--dwmblocks/.clangd5
-rw-r--r--dwmblocks/.github/FUNDING.yml4
-rw-r--r--dwmblocks/.gitignore3
-rw-r--r--dwmblocks/Makefile70
-rw-r--r--dwmblocks/README.md180
-rw-r--r--dwmblocks/config.def.h100
-rw-r--r--dwmblocks/dwmblocks.c296
-rw-r--r--dwmblocks/include/block.h29
-rw-r--r--dwmblocks/include/cli.h12
-rw-r--r--dwmblocks/include/main.h16
-rw-r--r--dwmblocks/include/signal-handler.h33
-rw-r--r--dwmblocks/include/status.h31
-rw-r--r--dwmblocks/include/timer.h21
-rw-r--r--dwmblocks/include/util.h28
-rw-r--r--dwmblocks/include/watcher.h28
-rw-r--r--dwmblocks/include/x11.h13
-rw-r--r--dwmblocks/src/block.c147
-rw-r--r--dwmblocks/src/cli.c33
-rw-r--r--dwmblocks/src/main.c168
-rw-r--r--dwmblocks/src/signal-handler.c124
-rw-r--r--dwmblocks/src/status.c78
-rw-r--r--dwmblocks/src/timer.c72
-rw-r--r--dwmblocks/src/util.c49
-rw-r--r--dwmblocks/src/watcher.c69
-rw-r--r--dwmblocks/src/x11.c44
27 files changed, 1294 insertions, 397 deletions
diff --git a/dwmblocks/.clang-format b/dwmblocks/.clang-format
new file mode 100644
index 0000000..299675f
--- /dev/null
+++ b/dwmblocks/.clang-format
@@ -0,0 +1,8 @@
+BasedOnStyle: Google
+IndentWidth: 4
+InsertBraces: true
+ColumnLimit: 79
+AlignConsecutiveMacros: Consecutive
+AllowShortFunctionsOnASingleLine: None
+AllowShortLoopsOnASingleLine: false
+AllowShortIfStatementsOnASingleLine: Never
diff --git a/dwmblocks/.clang-tidy b/dwmblocks/.clang-tidy
new file mode 100644
index 0000000..17ce268
--- /dev/null
+++ b/dwmblocks/.clang-tidy
@@ -0,0 +1,30 @@
+Checks: |
+ -*,
+ abseil-*,
+ bugprone-*,
+ clang-analyzer-*,
+ misc-*,
+ modernize-*,
+ performance-*,
+ portability-*,
+ readability-*,
+ llvm-*,
+ -bugprone-easily-swappable-parameters,
+ -readability-avoid-const-params-in-decls,
+ -readability-identifier-length
+
+CheckOptions:
+ - key: readability-inconsistent-declaration-parameter-name.Strict
+ value: true
+ - key: readability-identifier-naming.StructCase
+ value: lower_case
+ - key: readability-identifier-naming.FunctionCase
+ value: lower_case
+ - key: readability-identifier-naming.VariableCase
+ value: lower_case
+ - key: readability-identifier-naming.EnumConstantCase
+ value: UPPER_CASE
+ - key: readability-identifier-naming.MacroDefinitionCase
+ value: UPPER_CASE
+ - key: readability-function-cognitive-complexity.Threshold
+ value: 15
diff --git a/dwmblocks/.clangd b/dwmblocks/.clangd
new file mode 100644
index 0000000..d5ba524
--- /dev/null
+++ b/dwmblocks/.clangd
@@ -0,0 +1,5 @@
+Diagnostics:
+ UnusedIncludes: Strict
+ MissingIncludes: Strict
+ Includes:
+ IgnoreHeader: bits/getopt_core.h
diff --git a/dwmblocks/.github/FUNDING.yml b/dwmblocks/.github/FUNDING.yml
new file mode 100644
index 0000000..c7fd590
--- /dev/null
+++ b/dwmblocks/.github/FUNDING.yml
@@ -0,0 +1,4 @@
+patreon: UtkarshVerma
+ko_fi: UtkarshVerma
+liberapay: UtkarshVerma
+custom: https://paypal.me/UtkarshVermaI
diff --git a/dwmblocks/.gitignore b/dwmblocks/.gitignore
new file mode 100644
index 0000000..902f3c7
--- /dev/null
+++ b/dwmblocks/.gitignore
@@ -0,0 +1,3 @@
+build/
+.cache/
+compile_commands.json
diff --git a/dwmblocks/Makefile b/dwmblocks/Makefile
index 159a71a..3e07f93 100644
--- a/dwmblocks/Makefile
+++ b/dwmblocks/Makefile
@@ -1,36 +1,60 @@
-PREFIX := /usr/local
-CC := cc
-CFLAGS := -pedantic -Wall -Wno-deprecated-declarations -Os
-LDFLAGS := -lX11
+.POSIX:
-# FreeBSD (uncomment)
-#LDFLAGS += -L/usr/local/lib -I/usr/local/include
-# # OpenBSD (uncomment)
-#LDFLAGS += -L/usr/X11R6/lib -I/usr/X11R6/include
+BIN := dwmblocks
+BUILD_DIR := build
+SRC_DIR := src
+INC_DIR := include
-all: options dwmblocks
+DEBUG := 0
+VERBOSE := 0
+LIBS := xcb-atom
-options:
- @echo dwmblocks build options:
- @echo "CFLAGS = ${CFLAGS}"
- @echo "LDFLAGS = ${LDFLAGS}"
- @echo "CC = ${CC}"
+PREFIX := /usr/local
+CFLAGS := -Ofast -I. -I$(INC_DIR) -std=c99
+CFLAGS += -DBINARY=\"$(BIN)\" -D_POSIX_C_SOURCE=200809L
+CFLAGS += -Wall -Wpedantic -Wextra -Wswitch-enum
+CFLAGS += $(shell pkg-config --cflags $(LIBS))
+LDLIBS := $(shell pkg-config --libs $(LIBS))
-dwmblocks: dwmblocks.c config.def.h config.h
- ${CC} -o dwmblocks dwmblocks.c ${CFLAGS} ${LDFLAGS}
+SRCS := $(wildcard $(SRC_DIR)/*.c)
+OBJS := $(subst $(SRC_DIR)/,$(BUILD_DIR)/,$(SRCS:.c=.o))
+
+INSTALL_DIR := $(DESTDIR)$(PREFIX)/bin
+
+# Prettify output
+PRINTF := @printf "%-8s %s\n"
+ifeq ($(VERBOSE), 0)
+ Q := @
+endif
+
+ifeq ($(DEBUG), 1)
+ CFLAGS += -g
+endif
+
+all: $(BUILD_DIR)/$(BIN)
+
+$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c config.def.h config.h
+ $Qmkdir -p $(@D)
+ $(PRINTF) "CC" $@
+ $Q$(COMPILE.c) -o $@ $<
+
+$(BUILD_DIR)/$(BIN): $(OBJS)
+ $(PRINTF) "LD" $@
+ $Q$(LINK.o) $^ $(LDLIBS) -o $@
config.h:
cp config.def.h $@
clean:
- rm -f *.o *.gch dwmblocks
+ $(PRINTF) "CLEAN" $(BUILD_DIR)
+ $Q$(RM) $(BUILD_DIR)/*
-install: dwmblocks
- mkdir -p ${DESTDIR}${PREFIX}/bin
- cp -f dwmblocks ${DESTDIR}${PREFIX}/bin
- chmod 755 ${DESTDIR}${PREFIX}/bin/dwmblocks
+install: $(BUILD_DIR)/$(BIN)
+ $(PRINTF) "INSTALL" $(INSTALL_DIR)/$(BIN)
+ $Qinstall -D -m 755 $< $(INSTALL_DIR)/$(BIN)
uninstall:
- rm -f ${DESTDIR}${PREFIX}/bin/dwmblocks
+ $(PRINTF) "RM" $(INSTALL_DIR)/$(BIN)
+ $Q$(RM) $(INSTALL_DIR)/$(BIN)
-.PHONY: all options clean install uninstall
+.PHONY: all clean install uninstall
diff --git a/dwmblocks/README.md b/dwmblocks/README.md
index 7d21e30..1b389f6 100644
--- a/dwmblocks/README.md
+++ b/dwmblocks/README.md
@@ -1,44 +1,164 @@
-# dwmblocks
+# dwmblocks-async
-Modular status bar for dwm written in c.
+A [`dwm`](https://dwm.suckless.org) status bar that has a modular, async
+design, so it is always responsive. Imagine `i3blocks`, but for `dwm`.
-# Modifying blocks
+![A lean config of dwmblocks-async.](preview.png)
-The statusbar is made from text output from commandline programs. Blocks are
-added and removed by editing the config.h file.
+## Features
-# Luke's build
+- [Modular](#modifying-the-blocks)
+- Lightweight
+- [Suckless](https://suckless.org/philosophy)
+- Blocks:
+ - [Clickable](#clickable-blocks)
+ - Loaded asynchronously
+ - [Updates can be externally triggered](#signalling-changes)
+- Compatible with `i3blocks` scripts
-I have dwmblocks read my preexisting scripts
-[here in my dotfiles repo](https://github.com/LukeSmithxyz/voidrice/tree/master/.local/bin/statusbar).
-So if you want my build out of the box, download those and put them in your
-`$PATH`. I do this to avoid redundancy in LARBS, both i3 and dwm use the same
-statusbar scripts.
+> Additionally, this build of `dwmblocks` is more optimized and fixes the
+> flickering of the status bar when scrolling.
-# Signaling changes
+## Why `dwmblocks`?
-Most statusbars constantly rerun every script every several seconds to update.
-This is an option here, but a superior choice is giving your module a signal
-that you can signal to it to update on a relevant event, rather than having it
-rerun idly.
+In `dwm`, you have to set the status bar through an infinite loop, like so:
-For example, the audio module has the update signal 10 by default. Thus,
-running `pkill -RTMIN+10 dwmblocks` will update it.
+```sh
+while :; do
+ xsetroot -name "$(date)"
+ sleep 30
+done
+```
-You can also run `kill -44 $(pidof dwmblocks)` which will have the same effect,
-but is faster. Just add 34 to your typical signal number.
+This is inefficient when running multiple commands that need to be updated at
+different frequencies. For example, to display an unread mail count and a clock
+in the status bar:
-My volume module *never* updates on its own, instead I have this command run
-along side my volume shortcuts in dwm to only update it when relevant.
+```sh
+while :; do
+ xsetroot -name "$(mailCount) $(date)"
+ sleep 60
+done
+```
-Note that all modules must have different signal numbers.
+Both are executed at the same rate, which is wasteful. Ideally, the mail
+counter would be updated every thirty minutes, since there's a limit to the
+number of requests I can make using Gmail's APIs (as a free user).
-# Clickable modules
+`dwmblocks` allows you to divide the status bar into multiple blocks, each of
+which can be updated at its own interval. This effectively addresses the
+previous issue, because the commands in a block are only executed once within
+that time frame.
-Like i3blocks, this build allows you to build in additional actions into your
-scripts in response to click events. See the above linked scripts for examples
-of this using the `$BLOCK_BUTTON` variable.
+## Why `dwmblocks-async`?
-For this feature to work, you need the appropriate patch in dwm as well. See
-[here](https://dwm.suckless.org/patches/statuscmd/).
-Credit for those patches goes to Daniel Bylinka (daniel.bylinka@gmail.com).
+The magic of `dwmblocks-async` is in the `async` part. Since vanilla
+`dwmblocks` executes the commands of each block sequentially, it leads to
+annoying freezes. In cases where one block takes several seconds to execute,
+like in the mail and date blocks example from above, the delay is clearly
+visible. Fire up a new instance of `dwmblocks` and you'll see!
+
+With `dwmblocks-async`, the computer executes each block asynchronously
+(simultaneously).
+
+## Installation
+
+Clone this repository, modify `config.h` appropriately, then compile the
+program:
+
+```sh
+git clone https://github.com/UtkarshVerma/dwmblocks-async.git
+cd dwmblocks-async
+vi config.h
+sudo make install
+```
+
+## Usage
+
+To set `dwmblocks-async` as your status bar, you need to run it as a background
+process on startup. One way is to add the following to `~/.xinitrc`:
+
+```sh
+# The binary of `dwmblocks-async` is named `dwmblocks`
+dwmblocks &
+```
+
+### Modifying the blocks
+
+You can define your status bar blocks in `config.h`:
+
+```c
+#define BLOCKS(X) \
+ ...
+ X(" ", "wpctl get-volume @DEFAULT_AUDIO_SINK@ | cut -d' ' -f2", 0, 5) \
+ X("󰥔 ", "date '+%H:%M:%S'", 1, 1) \
+ ...
+```
+
+Each block has the following properties:
+
+| Property | Description |
+| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Icon | An icon you wish to prepend to your block output. |
+| Command | The command you wish to execute in your block. |
+| Update interval | Time in seconds, after which you want the block to update. If `0`, the block will never be updated. |
+| Update signal | Signal to be used for triggering the block. Must be a positive integer. If `0`, a signal won't be set up for the block and it will be unclickable. |
+
+Apart from defining the blocks, features can be toggled through `config.h`:
+
+```c
+// String used to delimit block outputs in the status.
+#define DELIMITER " "
+
+// Maximum number of Unicode characters that a block can output.
+#define MAX_BLOCK_OUTPUT_LENGTH 45
+
+// Control whether blocks are clickable.
+#define CLICKABLE_BLOCKS 1
+
+// Control whether a leading delimiter should be prepended to the status.
+#define LEADING_DELIMITER 0
+
+// Control whether a trailing delimiter should be appended to the status.
+#define TRAILING_DELIMITER 0
+```
+
+### Signalling changes
+
+Most status bars constantly rerun all scripts every few seconds. This is an
+option here, but a superior choice is to give your block a signal through which
+you can indicate it to update on relevant event, rather than have it rerun
+idly.
+
+For example, the volume block has the update signal `5` by default. I run
+`kill -39 $(pidof dwmblocks)` alongside my volume shortcuts in `dwm` to only
+update it when relevant. Just add `34` to your signal number! You could also
+run `pkill -RTMIN+5 dwmblocks`, but it's slower.
+
+To refresh all the blocks, run `kill -10 $(pidof dwmblocks)` or
+`pkill -SIGUSR1 dwmblocks`.
+
+> All blocks must have different signal numbers!
+
+### Clickable blocks
+
+Like `i3blocks`, this build allows you to build in additional actions into your
+scripts in response to click events. You can check out
+[my status bar scripts](https://github.com/UtkarshVerma/dotfiles/tree/main/.local/bin/statusbar)
+as references for using the `$BLOCK_BUTTON` variable.
+
+To use this feature, define the `CLICKABLE_BLOCKS` feature macro in your
+`config.h`:
+
+```c
+#define CLICKABLE_BLOCKS 1
+```
+
+Apart from that, you need `dwm` to be patched with
+[statuscmd](https://dwm.suckless.org/patches/statuscmd/).
+
+## Credits
+
+This work would not have been possible without
+[Luke's build of dwmblocks](https://github.com/LukeSmithxyz/dwmblocks) and
+[Daniel Bylinka's statuscmd patch](https://dwm.suckless.org/patches/statuscmd/).
diff --git a/dwmblocks/config.def.h b/dwmblocks/config.def.h
index 7054f95..5cb2015 100644
--- a/dwmblocks/config.def.h
+++ b/dwmblocks/config.def.h
@@ -1,49 +1,53 @@
-// Modify this file to change what commands output to your statusbar, and recompile using the make command.
-static const Block blocks[] = {
- /*Icon*/ /*Command*/ /*Update Interval*/ /*Update Signal (1-31)*/
- {"", "sb-music", 0, 23}, // Music (far left)
- {"", "cat /tmp/recordingicon 2>/dev/null", 0, 24}, // Recording
- {"", "sb-torrent", 20, 22}, // Torrent
- {"", "sb-queues", 10, 21}, // Queues
- {"", "sb-mailbox", 300, 20}, // Mailbox
- {"", "sb-news", 0, 19}, // News
- {"", "sb-repos", 60, 18}, // Git repositories
- {"", "sb-tasks", 60, 17}, // Tasks
- {"", "sb-packages", 0, 16}, // Packages
- {"", "sb-forecast", 10800, 15}, // Weather
- /* {"", "sb-price xmr-btc \"Monero to Bitcoin\" 🔒 30", 9000, 31}, */
- /* {"", "sb-price xmr Monero 🔒 29", 9000, 30}, */
- /* {"", "sb-price bnb Binance 🫧 28", 9000, 29}, */
- /* {"", "sb-price xrp XRP 🪓 27", 9000, 28}, */
- /* {"", "sb-price usdt Tether ⛺ 26", 9000, 27}, */
- /* {"", "sb-price eth Ethereum 🍸 25", 9000, 26}, */
- /* {"", "sb-price btc Bitcoin 💰 24", 9000, 25}, */
- /* {"", "sb-nettraf", 1, 14}, // Network */
- {"", "sb-cpu", 60, 13}, // CPU
- {"", "sb-memory", 60, 12}, // Memory
- {"", "sb-disk", 10800, 11}, // Disk
- {"", "sb-keyboard", 0, 10}, // Inputs
- {"", "sb-bghitness", 0, 9}, // Background Lightness
- {"", "sb-brightness", 0, 8}, // Brightness
- {"", "sb-internet", 5, 7}, // Internet
- /* {"", "sb-iplocate", 0, 6}, // ip */
- {"", "sb-volume", 0, 5}, // Volume
- {"", "sb-battery", 5, 4}, // Battery
- {"", "sb-clock", 60, 3}, // Clock
- {"", "sb-ecrypt", 0, 2}, // Ecrypt (rightmost)
- {"", "sb-help-icon", 0, 1}, // Help Icon (rightmost)
-};
-
-// Sets delimiter between status commands. NULL character ('\0') means no delimiter.
-static char *delim = " ";
-
-// Have dwmblocks automatically recompile and run when you edit this file in
-// vim with the following line in your vimrc/init.vim:
-
-// autocmd BufWritePost ~/.local/src/suckless/dwmblocks/config.def.h !cd ~/.local/src/suckless/dwmblocks/; sudo make install && { killall -q dwmblocks;setsid -f dwmblocks }
-
-// Although, pkill is slightly slower than the command kill, which can
-// make a big difference if we are making semi-frequent changes in a script.
-// To signal with kill, we must send the value plus 34. Just remember 34.
-// So, 10 + 34 = 44, so we use this command: kill -44 $(pidof dwmblocks)
+#ifndef CONFIG_H
+#define CONFIG_H
+
+// String used to delimit block outputs in the status.
+#define DELIMITER " "
+
+// Maximum number of Unicode characters that a block can output.
+#define MAX_BLOCK_OUTPUT_LENGTH 50
+
+// Control whether blocks are clickable.
+#define CLICKABLE_BLOCKS 1
+
+// Control whether a leading delimiter should be prepended to the status.
+#define LEADING_DELIMITER 0
+
+// Control whether a trailing delimiter should be appended to the status.
+#define TRAILING_DELIMITER 0
+
+// Define blocks for the status feed as X(icon, cmd, interval, signal).
+#define BLOCKS(X)\
+ X("", "sb-music", 0, 23) \
+ X("", "sb-torrent", 0, 22) \
+ X("", "sb-queues", 10, 21) \
+ X("", "sb-mailbox", 300, 20) \
+ X("", "sb-news", 0, 19) \
+ X("", "sb-repos", 60, 18) \
+ X("", "sb-tasks", 60, 17) \
+ X("", "sb-packages", 0, 16) \
+ X("", "sb-forecast", 10800, 15) \
+ X("", "sb-cpu", 60, 13) \
+ X("", "sb-memory", 60, 12) \
+ X("", "sb-disk", 10800, 11) \
+ X("", "sb-keyboard", 0, 10) \
+ X("", "sb-bghitness", 0, 9) \
+ X("", "sb-brightness", 0, 8) \
+ X("", "sb-internet", 5, 7) \
+ X("", "sb-volume", 0, 5) \
+ X("", "sb-battery", 5, 4) \
+ X("", "sb-clock", 60, 3) \
+ X("", "sb-ecrypt", 0, 2) \
+ X("", "sb-help-icon", 0, 1)
+
+ /* X("", "sb-price xmr-btc \"Monero to Bitcoin\" 🔒 30", 9000, 31) \ */
+ /* X("", "sb-price xmr Monero 🔒 29", 9000, 30) \ */
+ /* X("", "sb-price bnb Binance 🫧 28", 9000, 29) \ */
+ /* X("", "sb-price xrp XRP 🪓 27", 9000, 28) \ */
+ /* X("", "sb-price usdt Tether ⛺ 26", 9000, 27) \ */
+ /* X("", "sb-price eth Ethereum 🍸 25", 9000, 26) \ */
+ /* X("", "sb-price btc Bitcoin 💰 24", 9000, 25) \ */
+ /* X("", "sb-iplocate", 0, 6) \ */
+
+#endif // CONFIG_H
diff --git a/dwmblocks/dwmblocks.c b/dwmblocks/dwmblocks.c
deleted file mode 100644
index 1bd87f0..0000000
--- a/dwmblocks/dwmblocks.c
+++ /dev/null
@@ -1,296 +0,0 @@
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <time.h>
-#include <signal.h>
-#include <errno.h>
-#include <X11/Xlib.h>
-#include <sys/signalfd.h>
-#include <poll.h>
-#define LENGTH(X) (sizeof(X) / sizeof (X[0]))
-#define CMDLENGTH 100
-
-typedef struct {
- char* icon;
- char* command;
- unsigned int interval;
- unsigned int signal;
-} Block;
-void sighandler();
-void buttonhandler(int ssi_int);
-void replace(char *str, char old, char new);
-void remove_all(char *str, char to_remove);
-void getcmds(int time);
-void getsigcmds(int signal);
-void setupsignals();
-int getstatus(char *str, char *last);
-void setroot();
-void statusloop();
-void termhandler(int signum);
-
-
-#include "config.h"
-
-static Display *dpy;
-static int screen;
-static Window root;
-static char statusbar[LENGTH(blocks)][CMDLENGTH] = {0};
-static char statusstr[2][256];
-static int statusContinue = 1;
-static int signalFD;
-static int timerInterval = -1;
-static void (*writestatus) () = setroot;
-
-void replace(char *str, char old, char new)
-{
- for(char * c = str; *c; c++)
- if(*c == old)
- *c = new;
-}
-
-// the previous function looked nice but unfortunately it didnt work if to_remove was in any position other than the last character
-// theres probably still a better way of doing this
-void remove_all(char *str, char to_remove) {
- char *read = str;
- char *write = str;
- while (*read) {
- if (*read != to_remove) {
- *write++ = *read;
- }
- ++read;
- }
- *write = '\0';
-}
-
-int gcd(int a, int b)
-{
- int temp;
- while (b > 0){
- temp = a % b;
-
- a = b;
- b = temp;
- }
- return a;
-}
-
-
-//opens process *cmd and stores output in *output
-void getcmd(const Block *block, char *output)
-{
- if (block->signal)
- {
- output[0] = block->signal;
- output++;
- }
- char *cmd = block->command;
- FILE *cmdf = popen(cmd,"r");
- if (!cmdf){
- //printf("failed to run: %s, %d\n", block->command, errno);
- return;
- }
- char tmpstr[CMDLENGTH] = "";
- // TODO decide whether its better to use the last value till next time or just keep trying while the error was the interrupt
- // this keeps trying to read if it got nothing and the error was an interrupt
- // could also just read to a separate buffer and not move the data over if interrupted
- // this way will take longer trying to complete 1 thing but will get it done
- // the other way will move on to keep going with everything and the part that failed to read will be wrong till its updated again
- // either way you have to save the data to a temp buffer because when it fails it writes nothing and then then it gets displayed before this finishes
- char * s;
- int e;
- do {
- errno = 0;
- s = fgets(tmpstr, CMDLENGTH-(strlen(delim)+1), cmdf);
- e = errno;
- } while (!s && e == EINTR);
- pclose(cmdf);
- int i = strlen(block->icon);
- strcpy(output, block->icon);
- strcpy(output+i, tmpstr);
- remove_all(output, '\n');
- i = strlen(output);
- if ((i > 0 && block != &blocks[LENGTH(blocks) - 1])){
- strcat(output, delim);
- }
- i+=strlen(delim);
- output[i++] = '\0';
-}
-
-void getcmds(int time)
-{
- const Block* current;
- for(int i = 0; i < LENGTH(blocks); i++)
- {
- current = blocks + i;
- if ((current->interval != 0 && time % current->interval == 0) || time == -1){
- getcmd(current,statusbar[i]);
- }
- }
-}
-
-void getsigcmds(int signal)
-{
- const Block *current;
- for (int i = 0; i < LENGTH(blocks); i++)
- {
- current = blocks + i;
- if (current->signal == signal){
- getcmd(current,statusbar[i]);
- }
- }
-}
-
-void setupsignals()
-{
- sigset_t signals;
- sigemptyset(&signals);
- sigaddset(&signals, SIGALRM); // Timer events
- sigaddset(&signals, SIGUSR1); // Button events
- // All signals assigned to blocks
- for (size_t i = 0; i < LENGTH(blocks); i++)
- if (blocks[i].signal > 0)
- sigaddset(&signals, SIGRTMIN + blocks[i].signal);
- // Create signal file descriptor for pooling
- signalFD = signalfd(-1, &signals, 0);
- // Block all real-time signals
- for (int i = SIGRTMIN; i <= SIGRTMAX; i++) sigaddset(&signals, i);
- sigprocmask(SIG_BLOCK, &signals, NULL);
- // Do not transform children into zombies
- struct sigaction sigchld_action = {
- .sa_handler = SIG_DFL,
- .sa_flags = SA_NOCLDWAIT
- };
- sigaction(SIGCHLD, &sigchld_action, NULL);
-
-}
-
-int getstatus(char *str, char *last)
-{
- strcpy(last, str);
- str[0] = '\0';
- for(int i = 0; i < LENGTH(blocks); i++) {
- strcat(str, statusbar[i]);
- if (i == LENGTH(blocks) - 1)
- strcat(str, " ");
- }
- str[strlen(str)-1] = '\0';
- return strcmp(str, last);//0 if they are the same
-}
-
-void setroot()
-{
- if (!getstatus(statusstr[0], statusstr[1]))//Only set root if text has changed.
- return;
- Display *d = XOpenDisplay(NULL);
- if (d) {
- dpy = d;
- }
- screen = DefaultScreen(dpy);
- root = RootWindow(dpy, screen);
- XStoreName(dpy, root, statusstr[0]);
- XCloseDisplay(dpy);
-}
-
-void pstdout()
-{
- if (!getstatus(statusstr[0], statusstr[1]))//Only write out if text has changed.
- return;
- printf("%s\n",statusstr[0]);
- fflush(stdout);
-}
-
-
-void statusloop()
-{
- setupsignals();
- // first figure out the default wait interval by finding the
- // greatest common denominator of the intervals
- for(int i = 0; i < LENGTH(blocks); i++){
- if(blocks[i].interval){
- timerInterval = gcd(blocks[i].interval, timerInterval);
- }
- }
- getcmds(-1); // Fist time run all commands
- raise(SIGALRM); // Schedule first timer event
- int ret;
- struct pollfd pfd[] = {{.fd = signalFD, .events = POLLIN}};
- while (statusContinue) {
- // Wait for new signal
- ret = poll(pfd, sizeof(pfd) / sizeof(pfd[0]), -1);
- if (ret < 0 || !(pfd[0].revents & POLLIN)) break;
- sighandler(); // Handle signal
- }
-}
-
-void sighandler()
-{
- static int time = 0;
- struct signalfd_siginfo si;
- int ret = read(signalFD, &si, sizeof(si));
- if (ret < 0) return;
- int signal = si.ssi_signo;
- switch (signal) {
- case SIGALRM:
- // Execute blocks and schedule the next timer event
- getcmds(time);
- alarm(timerInterval);
- time += timerInterval;
- break;
- case SIGUSR1:
- // Handle buttons
- buttonhandler(si.ssi_int);
- return;
- default:
- // Execute the block that has the given signal
- getsigcmds(signal - SIGRTMIN);
- break;
- }
- writestatus();
-}
-
-void buttonhandler(int ssi_int)
-{
- char button[2] = {('0' + ssi_int) & 0xff, '\0'};
- pid_t process_id = getpid();
- int sig = ssi_int >> 8;
- if (fork() == 0)
- {
- const Block *current;
- for (int i = 0; i < LENGTH(blocks); i++)
- {
- current = blocks + i;
- if (current->signal == sig)
- break;
- }
- char shcmd[1024];
- sprintf(shcmd,"%s && kill -%d %d",current->command, current->signal+34,process_id);
- char *command[] = { "/bin/sh", "-c", shcmd, NULL };
- setenv("BLOCK_BUTTON", button, 1);
- setsid();
- execvp(command[0], command);
- exit(EXIT_SUCCESS);
- }
-}
-
-
-void termhandler(int signum)
-{
- statusContinue = 0;
-}
-
-int main(int argc, char** argv)
-{
- for(int i = 0; i < argc; i++)
- {
- if (!strcmp("-d",argv[i]))
- delim = argv[++i];
- else if(!strcmp("-p",argv[i]))
- writestatus = pstdout;
- }
- signal(SIGTERM, termhandler);
- signal(SIGINT, termhandler);
- statusloop();
- close(signalFD);
-}
-
diff --git a/dwmblocks/include/block.h b/dwmblocks/include/block.h
new file mode 100644
index 0000000..c4f8d54
--- /dev/null
+++ b/dwmblocks/include/block.h
@@ -0,0 +1,29 @@
+#ifndef BLOCK_H
+#define BLOCK_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "config.h"
+#include "util.h"
+
+typedef struct {
+ const char *const icon;
+ const char *const command;
+ const unsigned int interval;
+ const int signal;
+
+ int pipe[PIPE_FD_COUNT];
+ char output[MAX_BLOCK_OUTPUT_LENGTH * UTF8_MAX_BYTE_COUNT + 1];
+ pid_t fork_pid;
+} block;
+
+block block_new(const char *const icon, const char *const command,
+ const unsigned int interval, const int signal);
+int block_init(block *const block);
+int block_deinit(block *const block);
+int block_execute(block *const block, const uint8_t button);
+int block_update(block *const block);
+
+#endif // BLOCK_H
diff --git a/dwmblocks/include/cli.h b/dwmblocks/include/cli.h
new file mode 100644
index 0000000..2f93f62
--- /dev/null
+++ b/dwmblocks/include/cli.h
@@ -0,0 +1,12 @@
+#ifndef CLI_H
+#define CLI_H
+
+#include <stdbool.h>
+
+typedef struct {
+ bool is_debug_mode;
+} cli_arguments;
+
+cli_arguments cli_parse_arguments(const char* const argv[], const int argc);
+
+#endif // CLI_H
diff --git a/dwmblocks/include/main.h b/dwmblocks/include/main.h
new file mode 100644
index 0000000..b37a6b1
--- /dev/null
+++ b/dwmblocks/include/main.h
@@ -0,0 +1,16 @@
+#ifndef MAIN_H
+#define MAIN_H
+
+#include <signal.h>
+
+#include "config.h"
+#include "util.h"
+
+#define REFRESH_SIGNAL SIGUSR1
+
+// Utilise C's adjacent string concatenation to count the number of blocks.
+#define X(...) "."
+enum { BLOCK_COUNT = LEN(BLOCKS(X)) - 1 };
+#undef X
+
+#endif // MAIN_H
diff --git a/dwmblocks/include/signal-handler.h b/dwmblocks/include/signal-handler.h
new file mode 100644
index 0000000..da2d471
--- /dev/null
+++ b/dwmblocks/include/signal-handler.h
@@ -0,0 +1,33 @@
+#ifndef SIGNAL_HANDLER_H
+#define SIGNAL_HANDLER_H
+
+#include <signal.h>
+
+#include "block.h"
+#include "timer.h"
+
+typedef sigset_t signal_set;
+typedef int (*signal_refresh_callback)(block* const blocks,
+ const unsigned short block_count);
+typedef int (*signal_timer_callback)(block* const blocks,
+ const unsigned short block_code,
+ timer* const timer);
+
+typedef struct {
+ int fd;
+ const signal_refresh_callback refresh_callback;
+ const signal_timer_callback timer_callback;
+
+ block* const blocks;
+ const unsigned short block_count;
+} signal_handler;
+
+signal_handler signal_handler_new(
+ block* const blocks, const unsigned short block_count,
+ const signal_refresh_callback refresh_callback,
+ const signal_timer_callback timer_callback);
+int signal_handler_init(signal_handler* const handler);
+int signal_handler_deinit(signal_handler* const handler);
+int signal_handler_process(signal_handler* const handler, timer* const timer);
+
+#endif // SIGNAL_HANDLER_H
diff --git a/dwmblocks/include/status.h b/dwmblocks/include/status.h
new file mode 100644
index 0000000..48fb3d8
--- /dev/null
+++ b/dwmblocks/include/status.h
@@ -0,0 +1,31 @@
+#ifndef STATUS_H
+#define STATUS_H
+
+#include <stdbool.h>
+
+#include "block.h"
+#include "config.h"
+#include "main.h"
+#include "util.h"
+#include "x11.h"
+
+typedef struct {
+#define STATUS_LENGTH \
+ ((BLOCK_COUNT * (MEMBER_LENGTH(block, output) - 1) + CLICKABLE_BLOCKS) + \
+ (BLOCK_COUNT - 1 + LEADING_DELIMITER + TRAILING_DELIMITER) * \
+ (LEN(DELIMITER) - 1) + \
+ 1)
+ char current[STATUS_LENGTH];
+ char previous[STATUS_LENGTH];
+#undef STATUS_LENGTH
+
+ const block* const blocks;
+ const unsigned short block_count;
+} status;
+
+status status_new(const block* const blocks, const unsigned short block_count);
+bool status_update(status* const status);
+int status_write(const status* const status, const bool is_debug_mode,
+ x11_connection* const connection);
+
+#endif // STATUS_H
diff --git a/dwmblocks/include/timer.h b/dwmblocks/include/timer.h
new file mode 100644
index 0000000..1ec7f75
--- /dev/null
+++ b/dwmblocks/include/timer.h
@@ -0,0 +1,21 @@
+#ifndef TIMER_H
+#define TIMER_H
+
+#include <signal.h>
+#include <stdbool.h>
+
+#include "block.h"
+
+#define TIMER_SIGNAL SIGALRM
+
+typedef struct {
+ unsigned int time;
+ const unsigned int tick;
+ const unsigned int reset_value;
+} timer;
+
+timer timer_new(const block *const blocks, const unsigned short block_count);
+int timer_arm(timer *const timer);
+bool timer_must_run_block(const timer *const timer, const block *const block);
+
+#endif // TIMER_H
diff --git a/dwmblocks/include/util.h b/dwmblocks/include/util.h
new file mode 100644
index 0000000..a3bdcce
--- /dev/null
+++ b/dwmblocks/include/util.h
@@ -0,0 +1,28 @@
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <stddef.h>
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define LEN(arr) (sizeof(arr) / sizeof((arr)[0]))
+#define BIT(n) (1 << (n))
+
+// NOLINTBEGIN(bugprone-macro-parentheses)
+#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member)
+#define MEMBER_LENGTH(type, member) \
+ (MEMBER_SIZE(type, member) / MEMBER_SIZE(type, member[0]))
+// NOLINTEND(bugprone-macro-parentheses)
+
+#define UTF8_MAX_BYTE_COUNT 4
+
+enum pipe_fd_index {
+ READ_END,
+ WRITE_END,
+ PIPE_FD_COUNT,
+};
+
+unsigned int gcd(unsigned int a, unsigned int b);
+size_t truncate_utf8_string(char* const buffer, const size_t size,
+ const size_t char_limit);
+
+#endif // UTIL_H
diff --git a/dwmblocks/include/watcher.h b/dwmblocks/include/watcher.h
new file mode 100644
index 0000000..ff31809
--- /dev/null
+++ b/dwmblocks/include/watcher.h
@@ -0,0 +1,28 @@
+#ifndef WATCHER_H
+#define WATCHER_H
+
+#include <poll.h>
+#include <stdbool.h>
+
+#include "block.h"
+#include "main.h"
+
+enum watcher_fd_index {
+ SIGNAL_FD = BLOCK_COUNT,
+ WATCHER_FD_COUNT,
+};
+
+typedef struct pollfd watcher_fd;
+
+typedef struct {
+ watcher_fd fds[WATCHER_FD_COUNT];
+ unsigned short active_blocks[BLOCK_COUNT];
+ unsigned short active_block_count;
+ bool got_signal;
+} watcher;
+
+int watcher_init(watcher *const watcher, const block *const blocks,
+ const unsigned short block_count, const int signal_fd);
+int watcher_poll(watcher *const watcher, const int timeout_ms);
+
+#endif // WATCHER_H
diff --git a/dwmblocks/include/x11.h b/dwmblocks/include/x11.h
new file mode 100644
index 0000000..6faaced
--- /dev/null
+++ b/dwmblocks/include/x11.h
@@ -0,0 +1,13 @@
+#ifndef X11_H
+#define X11_H
+
+#include <xcb/xcb.h>
+
+typedef xcb_connection_t x11_connection;
+
+x11_connection* x11_connection_open(void);
+void x11_connection_close(x11_connection* const connection);
+int x11_set_root_name(x11_connection* const connection,
+ const char* const name);
+
+#endif // X11_H
diff --git a/dwmblocks/src/block.c b/dwmblocks/src/block.c
new file mode 100644
index 0000000..a6c919d
--- /dev/null
+++ b/dwmblocks/src/block.c
@@ -0,0 +1,147 @@
+#include "block.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "util.h"
+
+block block_new(const char *const icon, const char *const command,
+ const unsigned int interval, const int signal) {
+ block block = {
+ .icon = icon,
+ .command = command,
+ .interval = interval,
+ .signal = signal,
+
+ .output = {[0] = '\0'},
+ .fork_pid = -1,
+ };
+
+ return block;
+}
+
+int block_init(block *const block) {
+ if (pipe(block->pipe) != 0) {
+ (void)fprintf(stderr,
+ "error: could not create a pipe for \"%s\" block\n",
+ block->command);
+ return 1;
+ }
+
+ return 0;
+}
+
+int block_deinit(block *const block) {
+ int status = close(block->pipe[READ_END]);
+ status |= close(block->pipe[WRITE_END]);
+ if (status != 0) {
+ (void)fprintf(stderr, "error: could not close \"%s\" block's pipe\n",
+ block->command);
+ return 1;
+ }
+
+ return 0;
+}
+
+int block_execute(block *const block, const uint8_t button) {
+ // Ensure only one child process exists per block at an instance.
+ if (block->fork_pid != -1) {
+ return 0;
+ }
+
+ block->fork_pid = fork();
+ if (block->fork_pid == -1) {
+ (void)fprintf(
+ stderr, "error: could not create a subprocess for \"%s\" block\n",
+ block->command);
+ return 1;
+ }
+
+ if (block->fork_pid == 0) {
+ const int write_fd = block->pipe[WRITE_END];
+ int status = close(block->pipe[READ_END]);
+
+ if (button != 0) {
+ char button_str[4];
+ (void)snprintf(button_str, LEN(button_str), "%hhu", button);
+ status |= setenv("BLOCK_BUTTON", button_str, 1);
+ }
+
+ const char null = '\0';
+ if (status != 0) {
+ (void)write(write_fd, &null, sizeof(null));
+ exit(EXIT_FAILURE);
+ }
+
+ FILE *const file = popen(block->command, "r");
+ if (file == NULL) {
+ (void)write(write_fd, &null, sizeof(null));
+ exit(EXIT_FAILURE);
+ }
+
+ // Ensure null-termination since fgets() will leave buffer untouched on
+ // no output.
+ char buffer[LEN(block->output)] = {[0] = null};
+ (void)fgets(buffer, LEN(buffer), file);
+
+ // Remove trailing newlines.
+ const size_t length = strcspn(buffer, "\n");
+ buffer[length] = null;
+
+ // Exit if command execution failed or if file could not be closed.
+ if (pclose(file) != 0) {
+ (void)write(write_fd, &null, sizeof(null));
+ exit(EXIT_FAILURE);
+ }
+
+ const size_t output_size =
+ truncate_utf8_string(buffer, LEN(buffer), MAX_BLOCK_OUTPUT_LENGTH);
+ (void)write(write_fd, buffer, output_size);
+
+ exit(EXIT_SUCCESS);
+ }
+
+ return 0;
+}
+
+int block_update(block *const block) {
+ char buffer[LEN(block->output)];
+
+ const ssize_t bytes_read =
+ read(block->pipe[READ_END], buffer, LEN(buffer));
+ if (bytes_read == -1) {
+ (void)fprintf(stderr,
+ "error: could not fetch output of \"%s\" block\n",
+ block->command);
+ return 2;
+ }
+
+ // Collect exit-status of the subprocess to avoid zombification.
+ int fork_status = 0;
+ if (waitpid(block->fork_pid, &fork_status, 0) == -1) {
+ (void)fprintf(stderr,
+ "error: could not obtain exit status for \"%s\" block\n",
+ block->command);
+ return 2;
+ }
+ block->fork_pid = -1;
+
+ if (fork_status != 0) {
+ (void)fprintf(stderr,
+ "error: \"%s\" block exited with non-zero status\n",
+ block->command);
+ return 1;
+ }
+
+ (void)strncpy(block->output, buffer, LEN(buffer));
+
+ return 0;
+}
diff --git a/dwmblocks/src/cli.c b/dwmblocks/src/cli.c
new file mode 100644
index 0000000..b1849ec
--- /dev/null
+++ b/dwmblocks/src/cli.c
@@ -0,0 +1,33 @@
+#include "cli.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+cli_arguments cli_parse_arguments(const char *const argv[], const int argc) {
+ errno = 0;
+ cli_arguments args = {
+ .is_debug_mode = false,
+ };
+
+ int opt = -1;
+ opterr = 0; // Suppress getopt's built-in invalid opt message
+ while ((opt = getopt(argc, (char *const *)argv, "dh")) != -1) {
+ switch (opt) {
+ case 'd':
+ args.is_debug_mode = true;
+ break;
+ case '?':
+ (void)fprintf(stderr, "error: unknown option `-%c'\n", optopt);
+ // fall through
+ case 'h':
+ // fall through
+ default:
+ (void)fprintf(stderr, "usage: %s [-d]\n", BINARY);
+ errno = 1;
+ }
+ }
+
+ return args;
+}
diff --git a/dwmblocks/src/main.c b/dwmblocks/src/main.c
new file mode 100644
index 0000000..747b075
--- /dev/null
+++ b/dwmblocks/src/main.c
@@ -0,0 +1,168 @@
+#include "main.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "block.h"
+#include "cli.h"
+#include "config.h"
+#include "signal-handler.h"
+#include "status.h"
+#include "timer.h"
+#include "util.h"
+#include "watcher.h"
+#include "x11.h"
+
+static int init_blocks(block *const blocks, const unsigned short block_count) {
+ for (unsigned short i = 0; i < block_count; ++i) {
+ block *const block = &blocks[i];
+ if (block_init(block) != 0) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int deinit_blocks(block *const blocks,
+ const unsigned short block_count) {
+ for (unsigned short i = 0; i < block_count; ++i) {
+ block *const block = &blocks[i];
+ if (block_deinit(block) != 0) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int execute_blocks(block *const blocks,
+ const unsigned short block_count,
+ const timer *const timer) {
+ for (unsigned short i = 0; i < block_count; ++i) {
+ block *const block = &blocks[i];
+ if (!timer_must_run_block(timer, block)) {
+ continue;
+ }
+
+ if (block_execute(&blocks[i], 0) != 0) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int trigger_event(block *const blocks, const unsigned short block_count,
+ timer *const timer) {
+ if (execute_blocks(blocks, block_count, timer) != 0) {
+ return 1;
+ }
+
+ if (timer_arm(timer) != 0) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static int refresh_callback(block *const blocks,
+ const unsigned short block_count) {
+ if (execute_blocks(blocks, block_count, NULL) != 0) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static int event_loop(block *const blocks, const unsigned short block_count,
+ const bool is_debug_mode,
+ x11_connection *const connection,
+ signal_handler *const signal_handler) {
+ timer timer = timer_new(blocks, block_count);
+
+ // Kickstart the event loop with an initial execution.
+ if (trigger_event(blocks, block_count, &timer) != 0) {
+ return 1;
+ }
+
+ watcher watcher;
+ if (watcher_init(&watcher, blocks, block_count, signal_handler->fd) != 0) {
+ return 1;
+ }
+
+ status status = status_new(blocks, block_count);
+ bool is_alive = true;
+ while (is_alive) {
+ if (watcher_poll(&watcher, -1) != 0) {
+ return 1;
+ }
+
+ if (watcher.got_signal) {
+ is_alive = signal_handler_process(signal_handler, &timer) == 0;
+ }
+
+ for (unsigned short i = 0; i < watcher.active_block_count; ++i) {
+ (void)block_update(&blocks[watcher.active_blocks[i]]);
+ }
+
+ const bool has_status_changed = status_update(&status);
+ if (has_status_changed &&
+ status_write(&status, is_debug_mode, connection) != 0) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int main(const int argc, const char *const argv[]) {
+ const cli_arguments cli_args = cli_parse_arguments(argv, argc);
+ if (errno != 0) {
+ return 1;
+ }
+
+ x11_connection *const connection = x11_connection_open();
+ if (connection == NULL) {
+ return 1;
+ }
+
+#define BLOCK(icon, command, interval, signal) \
+ block_new(icon, command, interval, signal),
+ block blocks[BLOCK_COUNT] = {BLOCKS(BLOCK)};
+#undef BLOCK
+ const unsigned short block_count = LEN(blocks);
+
+ int status = 0;
+ if (init_blocks(blocks, block_count) != 0) {
+ status = 1;
+ goto x11_close;
+ }
+
+ signal_handler signal_handler = signal_handler_new(
+ blocks, block_count, refresh_callback, trigger_event);
+ if (signal_handler_init(&signal_handler) != 0) {
+ status = 1;
+ goto deinit_blocks;
+ }
+
+ if (event_loop(blocks, block_count, cli_args.is_debug_mode, connection,
+ &signal_handler) != 0) {
+ status = 1;
+ }
+
+ if (signal_handler_deinit(&signal_handler) != 0) {
+ status = 1;
+ }
+
+deinit_blocks:
+ if (deinit_blocks(blocks, block_count) != 0) {
+ status = 1;
+ }
+
+x11_close:
+ x11_connection_close(connection);
+
+ return status;
+}
diff --git a/dwmblocks/src/signal-handler.c b/dwmblocks/src/signal-handler.c
new file mode 100644
index 0000000..d816dcd
--- /dev/null
+++ b/dwmblocks/src/signal-handler.c
@@ -0,0 +1,124 @@
+#include "signal-handler.h"
+
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/signalfd.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "block.h"
+#include "main.h"
+#include "timer.h"
+
+typedef struct signalfd_siginfo signal_info;
+
+signal_handler signal_handler_new(
+ block *const blocks, const unsigned short block_count,
+ const signal_refresh_callback refresh_callback,
+ const signal_timer_callback timer_callback) {
+ signal_handler handler = {
+ .refresh_callback = refresh_callback,
+ .timer_callback = timer_callback,
+
+ .blocks = blocks,
+ .block_count = block_count,
+ };
+
+ return handler;
+}
+
+int signal_handler_init(signal_handler *const handler) {
+ signal_set set;
+ (void)sigemptyset(&set);
+
+ // Handle user-generated signal for refreshing the status.
+ (void)sigaddset(&set, REFRESH_SIGNAL);
+
+ // Handle SIGALRM generated by the timer.
+ (void)sigaddset(&set, TIMER_SIGNAL);
+
+ // Handle termination signals.
+ (void)sigaddset(&set, SIGINT);
+ (void)sigaddset(&set, SIGTERM);
+
+ for (unsigned short i = 0; i < handler->block_count; ++i) {
+ const block *const block = &handler->blocks[i];
+ if (block->signal > 0) {
+ if (sigaddset(&set, SIGRTMIN + block->signal) != 0) {
+ (void)fprintf(
+ stderr,
+ "error: invalid or unsupported signal specified for "
+ "\"%s\" block\n",
+ block->command);
+ return 1;
+ }
+ }
+ }
+
+ // Create a signal file descriptor for epoll to watch.
+ handler->fd = signalfd(-1, &set, 0);
+ if (handler->fd == -1) {
+ (void)fprintf(stderr,
+ "error: could not create file descriptor for signals\n");
+ return 1;
+ }
+
+ // Block all realtime and handled signals.
+ for (int i = SIGRTMIN; i <= SIGRTMAX; ++i) {
+ (void)sigaddset(&set, i);
+ }
+ (void)sigprocmask(SIG_BLOCK, &set, NULL);
+
+ return 0;
+}
+
+int signal_handler_deinit(signal_handler *const handler) {
+ if (close(handler->fd) != 0) {
+ (void)fprintf(stderr,
+ "error: could not close signal file descriptor\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+int signal_handler_process(signal_handler *const handler, timer *const timer) {
+ signal_info info;
+ const ssize_t bytes_read = read(handler->fd, &info, sizeof(info));
+ if (bytes_read == -1) {
+ (void)fprintf(stderr, "error: could not read info of incoming signal");
+ return 1;
+ }
+
+ const int signal = (int)info.ssi_signo;
+ switch (signal) {
+ case TIMER_SIGNAL:
+ if (handler->timer_callback(handler->blocks, handler->block_count,
+ timer) != 0) {
+ return 1;
+ }
+ return 0;
+ case REFRESH_SIGNAL:
+ if (handler->refresh_callback(handler->blocks,
+ handler->block_count) != 0) {
+ return 1;
+ }
+ return 0;
+ case SIGTERM:
+ // fall through
+ case SIGINT:
+ return 1;
+ }
+
+ for (unsigned short i = 0; i < handler->block_count; ++i) {
+ block *const block = &handler->blocks[i];
+ if (block->signal == signal - SIGRTMIN) {
+ const uint8_t button = (uint8_t)info.ssi_int;
+ block_execute(block, button);
+ break;
+ }
+ }
+
+ return 0;
+}
diff --git a/dwmblocks/src/status.c b/dwmblocks/src/status.c
new file mode 100644
index 0000000..cf0911a
--- /dev/null
+++ b/dwmblocks/src/status.c
@@ -0,0 +1,78 @@
+#include "status.h"
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "block.h"
+#include "config.h"
+#include "util.h"
+#include "x11.h"
+
+static bool has_status_changed(const status *const status) {
+ return strcmp(status->current, status->previous) != 0;
+}
+
+status status_new(const block *const blocks,
+ const unsigned short block_count) {
+ status status = {
+ .current = {[0] = '\0'},
+ .previous = {[0] = '\0'},
+
+ .blocks = blocks,
+ .block_count = block_count,
+ };
+
+ return status;
+}
+
+bool status_update(status *const status) {
+ (void)strncpy(status->previous, status->current, LEN(status->current));
+ status->current[0] = '\0';
+
+ for (unsigned short i = 0; i < status->block_count; ++i) {
+ const block *const block = &status->blocks[i];
+
+ if (strlen(block->output) > 0) {
+#if LEADING_DELIMITER
+ (void)strncat(status->current, DELIMITER, LEN(DELIMITER));
+#else
+ if (status->current[0] != '\0') {
+ (void)strncat(status->current, DELIMITER, LEN(DELIMITER));
+ }
+#endif
+
+#if CLICKABLE_BLOCKS
+ if (block->signal > 0) {
+ const char signal[] = {(char)block->signal, '\0'};
+ (void)strncat(status->current, signal, LEN(signal));
+ }
+#endif
+
+ (void)strncat(status->current, block->icon, LEN(block->output));
+ (void)strncat(status->current, block->output, LEN(block->output));
+ }
+ }
+
+#if TRAILING_DELIMITER
+ if (status->current[0] != '\0') {
+ (void)strncat(status->current, DELIMITER, LEN(DELIMITER));
+ }
+#endif
+
+ return has_status_changed(status);
+}
+
+int status_write(const status *const status, const bool is_debug_mode,
+ x11_connection *const connection) {
+ if (is_debug_mode) {
+ (void)printf("%s\n", status->current);
+ return 0;
+ }
+
+ if (x11_set_root_name(connection, status->current) != 0) {
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/dwmblocks/src/timer.c b/dwmblocks/src/timer.c
new file mode 100644
index 0000000..2ee555b
--- /dev/null
+++ b/dwmblocks/src/timer.c
@@ -0,0 +1,72 @@
+#include "timer.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "block.h"
+#include "util.h"
+
+static unsigned int compute_tick(const block *const blocks,
+ const unsigned short block_count) {
+ unsigned int tick = 0;
+
+ for (unsigned short i = 0; i < block_count; ++i) {
+ const block *const block = &blocks[i];
+ tick = gcd(block->interval, tick);
+ }
+
+ return tick;
+}
+
+static unsigned int compute_reset_value(const block *const blocks,
+ const unsigned short block_count) {
+ unsigned int reset_value = 1;
+
+ for (unsigned short i = 0; i < block_count; ++i) {
+ const block *const block = &blocks[i];
+ reset_value = MAX(block->interval, reset_value);
+ }
+
+ return reset_value;
+}
+
+timer timer_new(const block *const blocks, const unsigned short block_count) {
+ const unsigned int reset_value = compute_reset_value(blocks, block_count);
+
+ timer timer = {
+ .time = reset_value, // Initial value to execute all blocks.
+ .tick = compute_tick(blocks, block_count),
+ .reset_value = reset_value,
+ };
+
+ return timer;
+}
+
+int timer_arm(timer *const timer) {
+ errno = 0;
+ (void)alarm(timer->tick);
+
+ if (errno != 0) {
+ (void)fprintf(stderr, "error: could not arm timer\n");
+ return 1;
+ }
+
+ // Wrap `time` to the interval [1, reset_value].
+ timer->time = (timer->time + timer->tick) % timer->reset_value;
+
+ return 0;
+}
+
+bool timer_must_run_block(const timer *const timer, const block *const block) {
+ if (timer == NULL || timer->time == timer->reset_value) {
+ return true;
+ }
+
+ if (block->interval == 0) {
+ return false;
+ }
+
+ return timer->time % block->interval == 0;
+}
diff --git a/dwmblocks/src/util.c b/dwmblocks/src/util.c
new file mode 100644
index 0000000..10485db
--- /dev/null
+++ b/dwmblocks/src/util.c
@@ -0,0 +1,49 @@
+#include "util.h"
+
+#define UTF8_MULTIBYTE_BIT BIT(7)
+
+unsigned int gcd(unsigned int a, unsigned int b) {
+ while (b > 0) {
+ const unsigned int temp = a % b;
+ a = b;
+ b = temp;
+ }
+
+ return a;
+}
+
+size_t truncate_utf8_string(char* const buffer, const size_t size,
+ const size_t char_limit) {
+ size_t char_count = 0;
+ size_t i = 0;
+ while (char_count < char_limit) {
+ char ch = buffer[i];
+ if (ch == '\0') {
+ break;
+ }
+
+ unsigned short skip = 1;
+
+ // Multibyte unicode character.
+ if ((ch & UTF8_MULTIBYTE_BIT) != 0) {
+ // Skip continuation bytes.
+ ch <<= 1;
+ while ((ch & UTF8_MULTIBYTE_BIT) != 0) {
+ ch <<= 1;
+ ++skip;
+ }
+ }
+
+ // Avoid buffer overflow.
+ if (i + skip >= size) {
+ break;
+ }
+
+ ++char_count;
+ i += skip;
+ }
+
+ buffer[i] = '\0';
+
+ return i + 1;
+}
diff --git a/dwmblocks/src/watcher.c b/dwmblocks/src/watcher.c
new file mode 100644
index 0000000..71b6c52
--- /dev/null
+++ b/dwmblocks/src/watcher.c
@@ -0,0 +1,69 @@
+#include "watcher.h"
+
+#include <errno.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "block.h"
+#include "util.h"
+
+static bool watcher_fd_is_readable(const watcher_fd* const watcher_fd) {
+ return (watcher_fd->revents & POLLIN) != 0;
+}
+
+int watcher_init(watcher* const watcher, const block* const blocks,
+ const unsigned short block_count, const int signal_fd) {
+ if (signal_fd == -1) {
+ (void)fprintf(
+ stderr,
+ "error: invalid signal file descriptor passed to watcher\n");
+ return 1;
+ }
+
+ watcher_fd* const fd = &watcher->fds[SIGNAL_FD];
+ fd->fd = signal_fd;
+ fd->events = POLLIN;
+
+ for (unsigned short i = 0; i < block_count; ++i) {
+ const int block_fd = blocks[i].pipe[READ_END];
+ if (block_fd == -1) {
+ (void)fprintf(
+ stderr,
+ "error: invalid block file descriptors passed to watcher\n");
+ return 1;
+ }
+
+ watcher_fd* const fd = &watcher->fds[i];
+ fd->fd = block_fd;
+ fd->events = POLLIN;
+ }
+
+ return 0;
+}
+
+int watcher_poll(watcher* watcher, const int timeout_ms) {
+ int event_count = poll(watcher->fds, LEN(watcher->fds), timeout_ms);
+
+ // Don't return non-zero status for signal interruptions.
+ if (event_count == -1 && errno != EINTR) {
+ (void)fprintf(stderr, "error: watcher could not poll blocks\n");
+ return 1;
+ }
+
+ watcher->got_signal = watcher_fd_is_readable(&watcher->fds[SIGNAL_FD]);
+
+ watcher->active_block_count = event_count - (int)watcher->got_signal;
+ unsigned short i = 0;
+ unsigned short j = 0;
+ while (i < event_count && j < LEN(watcher->active_blocks)) {
+ if (watcher_fd_is_readable(&watcher->fds[j])) {
+ watcher->active_blocks[i] = j;
+ ++i;
+ }
+
+ ++j;
+ }
+
+ return 0;
+}
diff --git a/dwmblocks/src/x11.c b/dwmblocks/src/x11.c
new file mode 100644
index 0000000..7a310e9
--- /dev/null
+++ b/dwmblocks/src/x11.c
@@ -0,0 +1,44 @@
+#include "x11.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <xcb/xcb.h>
+#include <xcb/xproto.h>
+
+x11_connection *x11_connection_open(void) {
+ xcb_connection_t *const connection = xcb_connect(NULL, NULL);
+ if (xcb_connection_has_error(connection)) {
+ (void)fprintf(stderr, "error: could not connect to X server\n");
+ return NULL;
+ }
+
+ return connection;
+}
+
+void x11_connection_close(xcb_connection_t *const connection) {
+ xcb_disconnect(connection);
+}
+
+int x11_set_root_name(x11_connection *const connection, const char *name) {
+ xcb_screen_t *const screen =
+ xcb_setup_roots_iterator(xcb_get_setup(connection)).data;
+ const xcb_window_t root_window = screen->root;
+
+ const unsigned short name_format = 8;
+ const xcb_void_cookie_t cookie = xcb_change_property(
+ connection, XCB_PROP_MODE_REPLACE, root_window, XCB_ATOM_WM_NAME,
+ XCB_ATOM_STRING, name_format, strlen(name), name);
+
+ xcb_generic_error_t *error = xcb_request_check(connection, cookie);
+ if (error != NULL) {
+ (void)fprintf(stderr, "error: could not set X root name\n");
+ return 1;
+ }
+
+ if (xcb_flush(connection) <= 0) {
+ (void)fprintf(stderr, "error: could not flush X output buffer\n");
+ return 1;
+ }
+
+ return 0;
+}