diff options
Diffstat (limited to 'dwmblocks')
| -rw-r--r-- | dwmblocks/.clang-format | 8 | ||||
| -rw-r--r-- | dwmblocks/.clang-tidy | 30 | ||||
| -rw-r--r-- | dwmblocks/.clangd | 5 | ||||
| -rw-r--r-- | dwmblocks/.github/FUNDING.yml | 4 | ||||
| -rw-r--r-- | dwmblocks/.gitignore | 3 | ||||
| -rw-r--r-- | dwmblocks/Makefile | 70 | ||||
| -rw-r--r-- | dwmblocks/README.md | 180 | ||||
| -rw-r--r-- | dwmblocks/config.def.h | 100 | ||||
| -rw-r--r-- | dwmblocks/dwmblocks.c | 296 | ||||
| -rw-r--r-- | dwmblocks/include/block.h | 29 | ||||
| -rw-r--r-- | dwmblocks/include/cli.h | 12 | ||||
| -rw-r--r-- | dwmblocks/include/main.h | 16 | ||||
| -rw-r--r-- | dwmblocks/include/signal-handler.h | 33 | ||||
| -rw-r--r-- | dwmblocks/include/status.h | 31 | ||||
| -rw-r--r-- | dwmblocks/include/timer.h | 21 | ||||
| -rw-r--r-- | dwmblocks/include/util.h | 28 | ||||
| -rw-r--r-- | dwmblocks/include/watcher.h | 28 | ||||
| -rw-r--r-- | dwmblocks/include/x11.h | 13 | ||||
| -rw-r--r-- | dwmblocks/src/block.c | 147 | ||||
| -rw-r--r-- | dwmblocks/src/cli.c | 33 | ||||
| -rw-r--r-- | dwmblocks/src/main.c | 168 | ||||
| -rw-r--r-- | dwmblocks/src/signal-handler.c | 124 | ||||
| -rw-r--r-- | dwmblocks/src/status.c | 78 | ||||
| -rw-r--r-- | dwmblocks/src/timer.c | 72 | ||||
| -rw-r--r-- | dwmblocks/src/util.c | 49 | ||||
| -rw-r--r-- | dwmblocks/src/watcher.c | 69 | ||||
| -rw-r--r-- | dwmblocks/src/x11.c | 44 |
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 + -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; +} |
