mirror of
https://github.com/mintycube/dwmblocks.git
synced 2024-10-22 14:05:47 +02:00
Reorganize code and remove hack for empty block outputs
This commit is contained in:
parent
f6a5fa6480
commit
8dd9bc6a7d
@ -1,2 +1,3 @@
|
||||
BasedOnStyle: Google
|
||||
IndentWidth: 4
|
||||
ColumnLimit: 79
|
||||
|
6
.clangd
Normal file
6
.clangd
Normal file
@ -0,0 +1,6 @@
|
||||
CompileFlags:
|
||||
Add:
|
||||
- "-I."
|
||||
- "-I./inc"
|
||||
- "-I.."
|
||||
- "-I../inc"
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
*.o
|
||||
dwmblocks
|
||||
build/
|
||||
.cache/
|
||||
dwmblocks
|
||||
|
37
Makefile
37
Makefile
@ -1,21 +1,36 @@
|
||||
.POSIX:
|
||||
|
||||
PREFIX = /usr/local
|
||||
CFLAGS = -Ofast
|
||||
LDLIBS = -lX11
|
||||
BUILD_DIR := build
|
||||
SRC_DIR := src
|
||||
INC_DIR := inc
|
||||
|
||||
BIN = dwmblocks
|
||||
PREFIX := /usr/local
|
||||
CFLAGS := -Wall -Ofast -I. -I$(INC_DIR)
|
||||
LDLIBS := -lX11
|
||||
|
||||
$(BIN): main.o
|
||||
$(CC) $^ -o $@ $(LDLIBS)
|
||||
BIN := dwmblocks
|
||||
VPATH := $(SRC_DIR)
|
||||
OBJS := $(subst $(SRC_DIR)/,$(BUILD_DIR)/,$(subst .c,.o,$(wildcard $(SRC_DIR)/*.c)))
|
||||
OBJS += $(addprefix $(BUILD_DIR)/,$(subst .c,.o,$(wildcard *.c)))
|
||||
|
||||
all: $(BUILD_DIR)/$(BIN)
|
||||
|
||||
$(BUILD_DIR)/$(BIN): $(OBJS)
|
||||
$(LINK.o) $^ $(LDLIBS) -o $@
|
||||
|
||||
$(BUILD_DIR)/%.o: %.c config.h | $(BUILD_DIR)
|
||||
$(COMPILE.c) -o $@ $<
|
||||
|
||||
$(BUILD_DIR):
|
||||
mkdir -p $(BUILD_DIR)
|
||||
|
||||
clean:
|
||||
$(RM) *.o $(BIN)
|
||||
$(RM) -r $(BUILD_DIR)
|
||||
|
||||
install: $(BIN)
|
||||
install -D -m 755 $(BIN) $(DESTDIR)$(PREFIX)/bin/$(BIN)
|
||||
install: $(BUILD_DIR)/$(BIN)
|
||||
install -D -m 755 $< $(DESTDIR)/$(PREFIX)/bin/$(BIN)
|
||||
|
||||
uninstall:
|
||||
$(RM) $(DESTDIR)$(PREFIX)/bin/$(BIN)
|
||||
$(RM) $(DESTDIR)/$(PREFIX)/bin/$(BIN)
|
||||
|
||||
.PHONY: clean install uninstall
|
||||
.PHONY: all clean install uninstall
|
||||
|
104
README.md
104
README.md
@ -1,21 +1,26 @@
|
||||
# dwmblocks-async
|
||||
A [`dwm`](https://dwm.suckless.org) status bar that has a modular, async design, so it is always responsive. Imagine `i3blocks`, but for `dwm`.
|
||||
|
||||
A [`dwm`](https://dwm.suckless.org) status bar that has a modular, async
|
||||
design, so it is always responsive. Imagine `i3blocks`, but for `dwm`.
|
||||
|
||||
![A lean config of dwmblocks-async.](preview.png)
|
||||
|
||||
## Features
|
||||
|
||||
- [Modular](#modifying-the-blocks)
|
||||
- Lightweight
|
||||
- [Suckless](https://suckless.org/philosophy)
|
||||
- Blocks:
|
||||
- [Clickable](#clickable-blocks)
|
||||
- Loaded asynchronously
|
||||
- [Updates can be externally triggered](#signalling-changes)
|
||||
- [Clickable](#clickable-blocks)
|
||||
- Loaded asynchronously
|
||||
- [Updates can be externally triggered](#signalling-changes)
|
||||
- Compatible with `i3blocks` scripts
|
||||
|
||||
> Additionally, this build of `dwmblocks` is more optimized and fixes the flickering of the status bar when scrolling.
|
||||
> Additionally, this build of `dwmblocks` is more optimized and fixes the
|
||||
> flickering of the status bar when scrolling.
|
||||
|
||||
## Why `dwmblocks`?
|
||||
|
||||
In `dwm`, you have to set the status bar through an infinite loop, like so:
|
||||
|
||||
```sh
|
||||
@ -25,7 +30,9 @@ while :; do
|
||||
done
|
||||
```
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
```sh
|
||||
while :; do
|
||||
@ -34,17 +41,30 @@ while :; do
|
||||
done
|
||||
```
|
||||
|
||||
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).
|
||||
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).
|
||||
|
||||
`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.
|
||||
`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.
|
||||
|
||||
## Why `dwmblocks-async`?
|
||||
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).
|
||||
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:
|
||||
|
||||
Clone this repository, modify `config.h` appropriately, then compile the
|
||||
program:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/UtkarshVerma/dwmblocks-async.git
|
||||
@ -54,7 +74,9 @@ 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`:
|
||||
|
||||
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`
|
||||
@ -62,26 +84,27 @@ dwmblocks &
|
||||
```
|
||||
|
||||
### Modifying the blocks
|
||||
You can define your status bar blocks in `config.h`:
|
||||
|
||||
You can define your status bar blocks in `config.c`:
|
||||
|
||||
```c
|
||||
const Block blocks[] = {
|
||||
Block blocks[] = {
|
||||
...
|
||||
BLOCK("volume", 0, 5),
|
||||
BLOCK("date", 1800, 1),
|
||||
{"volume", 0, 5},
|
||||
{"date", 1800, 1},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Each block has the following properties:
|
||||
|
||||
Property|Description
|
||||
-|-
|
||||
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.
|
||||
| Property | Description |
|
||||
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 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. |
|
||||
|
||||
Additional parameters can be modified:
|
||||
Apart from defining the blocks, features can be toggled through `config.h`:
|
||||
|
||||
```c
|
||||
// Maximum possible length of output from block, expressed in number of characters.
|
||||
@ -91,31 +114,48 @@ Additional parameters can be modified:
|
||||
#define DELIMITER " "
|
||||
|
||||
// Adds a leading delimiter to the status bar, useful for powerline.
|
||||
#define LEADING_DELIMITER
|
||||
#define LEADING_DELIMITER 1
|
||||
|
||||
// Enable clickability for blocks. See the "Clickable blocks" section below.
|
||||
#define CLICKABLE_BLOCKS
|
||||
#define CLICKABLE_BLOCKS 1
|
||||
```
|
||||
|
||||
### 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.
|
||||
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.
|
||||
|
||||
To refresh all the blocks, run `kill -10 $(pidof dwmblocks)` or `pkill -SIGUSR1 dwmblocks`.
|
||||
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`:
|
||||
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
|
||||
#define CLICKABLE_BLOCKS 1
|
||||
```
|
||||
|
||||
Apart from that, you need `dwm` to be patched with [statuscmd](https://dwm.suckless.org/patches/statuscmd/).
|
||||
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/).
|
||||
|
||||
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/).
|
||||
|
21
config.c
Normal file
21
config.c
Normal file
@ -0,0 +1,21 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "block.h"
|
||||
#include "util.h"
|
||||
|
||||
// clang-format off
|
||||
Block blocks[] = {
|
||||
{"sb-mail", 600 , 1 },
|
||||
{"sb-music", 0, 2 },
|
||||
{"sb-disk", 1800, 3 },
|
||||
{"sb-memory", 10, 4 },
|
||||
{"sb-loadavg", 5, 5 },
|
||||
{"sb-mic", 0, 6 },
|
||||
{"sb-record", 0, 7 },
|
||||
{"sb-volume", 0, 8 },
|
||||
{"sb-battery", 5, 9 },
|
||||
{"sb-date", 1, 10},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
const unsigned short blockCount = LEN(blocks);
|
20
config.h
20
config.h
@ -1,16 +1,6 @@
|
||||
#define CMDLENGTH 45
|
||||
#define DELIMITER " "
|
||||
#define CLICKABLE_BLOCKS
|
||||
#pragma once
|
||||
|
||||
const Block blocks[] = {
|
||||
BLOCK("sb-mail", 1800, 17),
|
||||
BLOCK("sb-music", 0, 18),
|
||||
BLOCK("sb-disk", 1800, 19),
|
||||
BLOCK("sb-memory", 10, 20),
|
||||
BLOCK("sb-loadavg", 5, 21),
|
||||
BLOCK("sb-mic", 0, 26),
|
||||
BLOCK("sb-record", 0, 27),
|
||||
BLOCK("sb-volume", 0, 22),
|
||||
BLOCK("sb-battery", 5, 23),
|
||||
BLOCK("sb-date", 1, 24)
|
||||
};
|
||||
#define CLICKABLE_BLOCKS 1 // Enable clickability for blocks
|
||||
#define CMDLENGTH 45 // Number of characters that each block will output
|
||||
#define DELIMITER " " // Delimiter string used to separate blocks
|
||||
#define LEADING_DELIMITER 0 // Whether a leading separator should be used
|
||||
|
15
inc/bar.h
Normal file
15
inc/bar.h
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include "block.h"
|
||||
#include "config.h"
|
||||
#include "util.h"
|
||||
|
||||
typedef struct {
|
||||
char *current;
|
||||
char *previous;
|
||||
} BarStatus;
|
||||
|
||||
extern unsigned short debugMode;
|
||||
|
||||
void initStatus(BarStatus *);
|
||||
void freeStatus(BarStatus *);
|
||||
void writeStatus(BarStatus *);
|
19
inc/block.h
Normal file
19
inc/block.h
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "config.h"
|
||||
|
||||
typedef struct {
|
||||
const char *command;
|
||||
const unsigned int interval;
|
||||
const unsigned int signal;
|
||||
int pipe[2];
|
||||
char output[CMDLENGTH * 4 + 1];
|
||||
} Block;
|
||||
|
||||
extern Block blocks[];
|
||||
extern const unsigned short blockCount;
|
||||
|
||||
void execBlock(const Block *, const char *);
|
||||
void execBlocks(unsigned int);
|
||||
void updateBlock(Block *);
|
9
inc/util.h
Normal file
9
inc/util.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include <unistd.h>
|
||||
|
||||
#define LEN(arr) (sizeof(arr) / sizeof(arr[0]))
|
||||
#define MAX(a, b) (a > b ? a : b)
|
||||
|
||||
int gcd(int, int);
|
||||
void closePipe(int[2]);
|
||||
void trimUTF8(char*, unsigned int);
|
5
inc/x11.h
Normal file
5
inc/x11.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
int setupX();
|
||||
int closeX();
|
||||
void setXRootName(char *);
|
302
main.c
302
main.c
@ -1,302 +0,0 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <X11/Xlib.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/signalfd.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define LEN(arr) (sizeof(arr) / sizeof(arr[0]))
|
||||
#define MAX(a, b) (a > b ? a : b)
|
||||
#define BLOCK(cmd, interval, signal) \
|
||||
{ "echo \"$(" cmd ")\"", interval, signal }
|
||||
|
||||
typedef const struct {
|
||||
const char *command;
|
||||
const unsigned int interval;
|
||||
const unsigned int signal;
|
||||
} Block;
|
||||
#include "config.h"
|
||||
|
||||
#ifdef CLICKABLE_BLOCKS
|
||||
#undef CLICKABLE_BLOCKS
|
||||
#define CLICKABLE_BLOCKS 1
|
||||
#else
|
||||
#define CLICKABLE_BLOCKS 0
|
||||
#endif
|
||||
|
||||
#ifdef LEADING_DELIMITER
|
||||
#undef LEADING_DELIMITER
|
||||
#define LEADING_DELIMITER 1
|
||||
#else
|
||||
#define LEADING_DELIMITER 0
|
||||
#endif
|
||||
|
||||
static Display *dpy;
|
||||
static Window root;
|
||||
static unsigned short statusContinue = 1;
|
||||
static struct epoll_event event;
|
||||
static int pipes[LEN(blocks)][2];
|
||||
static int timer = 0, timerTick = 0, maxInterval = 1;
|
||||
static int signalFD;
|
||||
static int epollFD;
|
||||
static int execLock = 0;
|
||||
|
||||
// Longest UTF-8 character is 4 bytes long
|
||||
static char outputs[LEN(blocks)][CMDLENGTH * 4 + 1 + CLICKABLE_BLOCKS];
|
||||
static char
|
||||
statusBar[2]
|
||||
[LEN(blocks) * (LEN(outputs[0]) - 1) +
|
||||
(LEN(blocks) - 1 + LEADING_DELIMITER) * (LEN(DELIMITER) - 1) + 1];
|
||||
|
||||
void (*writeStatus)();
|
||||
|
||||
int gcd(int a, int b) {
|
||||
int temp;
|
||||
while (b > 0) {
|
||||
temp = a % b;
|
||||
a = b;
|
||||
b = temp;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
void closePipe(int *pipe) {
|
||||
close(pipe[0]);
|
||||
close(pipe[1]);
|
||||
}
|
||||
|
||||
void execBlock(int i, const char *button) {
|
||||
// Ensure only one child process exists per block at an instance
|
||||
if (execLock & 1 << i) return;
|
||||
// Lock execution of block until current instance finishes execution
|
||||
execLock |= 1 << i;
|
||||
|
||||
if (fork() == 0) {
|
||||
close(pipes[i][0]);
|
||||
dup2(pipes[i][1], STDOUT_FILENO);
|
||||
close(pipes[i][1]);
|
||||
|
||||
if (button) setenv("BLOCK_BUTTON", button, 1);
|
||||
execl("/bin/sh", "sh", "-c", blocks[i].command, (char *)NULL);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
void execBlocks(unsigned int time) {
|
||||
for (int i = 0; i < LEN(blocks); i++)
|
||||
if (time == 0 ||
|
||||
(blocks[i].interval != 0 && time % blocks[i].interval == 0))
|
||||
execBlock(i, NULL);
|
||||
}
|
||||
|
||||
int getStatus(char *new, char *old) {
|
||||
strcpy(old, new);
|
||||
new[0] = '\0';
|
||||
|
||||
for (int i = 0; i < LEN(blocks); i++) {
|
||||
#if LEADING_DELIMITER
|
||||
if (strlen(outputs[i]))
|
||||
#else
|
||||
if (strlen(new) && strlen(outputs[i]))
|
||||
#endif
|
||||
strcat(new, DELIMITER);
|
||||
strcat(new, outputs[i]);
|
||||
}
|
||||
return strcmp(new, old);
|
||||
}
|
||||
|
||||
void updateBlock(int i) {
|
||||
char *output = outputs[i];
|
||||
char buffer[LEN(outputs[0]) - CLICKABLE_BLOCKS];
|
||||
int bytesRead = read(pipes[i][0], buffer, LEN(buffer));
|
||||
|
||||
// Trim UTF-8 string to desired length
|
||||
int count = 0, j = 0;
|
||||
while (buffer[j] != '\n' && count < CMDLENGTH) {
|
||||
count++;
|
||||
|
||||
// Skip continuation bytes, if any
|
||||
char ch = buffer[j];
|
||||
int skip = 1;
|
||||
while ((ch & 0xc0) > 0x80) ch <<= 1, skip++;
|
||||
j += skip;
|
||||
}
|
||||
|
||||
// Cache last character and replace it with a trailing space
|
||||
char ch = buffer[j];
|
||||
buffer[j] = ' ';
|
||||
|
||||
// Trim trailing spaces
|
||||
while (j >= 0 && buffer[j] == ' ') j--;
|
||||
buffer[j + 1] = 0;
|
||||
|
||||
// Clear the pipe
|
||||
if (bytesRead == LEN(buffer)) {
|
||||
while (ch != '\n' && read(pipes[i][0], &ch, 1) == 1)
|
||||
;
|
||||
}
|
||||
|
||||
#if CLICKABLE_BLOCKS
|
||||
if (bytesRead > 1 && blocks[i].signal > 0) {
|
||||
output[0] = blocks[i].signal;
|
||||
output++;
|
||||
}
|
||||
#endif
|
||||
|
||||
strcpy(output, buffer);
|
||||
|
||||
// Remove execution lock for the current block
|
||||
execLock &= ~(1 << i);
|
||||
}
|
||||
|
||||
void debug() {
|
||||
// Only write out if text has changed
|
||||
if (!getStatus(statusBar[0], statusBar[1])) return;
|
||||
|
||||
write(STDOUT_FILENO, statusBar[0], strlen(statusBar[0]));
|
||||
write(STDOUT_FILENO, "\n", 1);
|
||||
}
|
||||
|
||||
int setupX() {
|
||||
dpy = XOpenDisplay(NULL);
|
||||
if (!dpy) return 1;
|
||||
|
||||
root = DefaultRootWindow(dpy);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setRoot() {
|
||||
// Only set root if text has changed
|
||||
if (!getStatus(statusBar[0], statusBar[1])) return;
|
||||
|
||||
XStoreName(dpy, root, statusBar[0]);
|
||||
XFlush(dpy);
|
||||
}
|
||||
|
||||
void signalHandler() {
|
||||
struct signalfd_siginfo info;
|
||||
read(signalFD, &info, sizeof(info));
|
||||
unsigned int signal = info.ssi_signo;
|
||||
|
||||
switch (signal) {
|
||||
case SIGALRM:
|
||||
// Schedule the next timer event and execute blocks
|
||||
alarm(timerTick);
|
||||
execBlocks(timer);
|
||||
|
||||
// Wrap `timer` to the interval [1, `maxInterval`]
|
||||
timer = (timer + timerTick - 1) % maxInterval + 1;
|
||||
return;
|
||||
case SIGUSR1:
|
||||
// Update all blocks on receiving SIGUSR1
|
||||
execBlocks(0);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int j = 0; j < LEN(blocks); j++) {
|
||||
if (blocks[j].signal == signal - SIGRTMIN) {
|
||||
char button[4]; // value can't be more than 255;
|
||||
sprintf(button, "%d", info.ssi_int & 0xff);
|
||||
execBlock(j, button);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void termHandler() { statusContinue = 0; }
|
||||
|
||||
void setupSignals() {
|
||||
sigset_t handledSignals;
|
||||
sigemptyset(&handledSignals);
|
||||
sigaddset(&handledSignals, SIGUSR1);
|
||||
sigaddset(&handledSignals, SIGALRM);
|
||||
|
||||
// Append all block signals to `handledSignals`
|
||||
for (int i = 0; i < LEN(blocks); i++)
|
||||
if (blocks[i].signal > 0)
|
||||
sigaddset(&handledSignals, SIGRTMIN + blocks[i].signal);
|
||||
|
||||
// Create a signal file descriptor for epoll to watch
|
||||
signalFD = signalfd(-1, &handledSignals, 0);
|
||||
event.data.u32 = LEN(blocks);
|
||||
epoll_ctl(epollFD, EPOLL_CTL_ADD, signalFD, &event);
|
||||
|
||||
// Block all realtime and handled signals
|
||||
for (int i = SIGRTMIN; i <= SIGRTMAX; i++) sigaddset(&handledSignals, i);
|
||||
sigprocmask(SIG_BLOCK, &handledSignals, NULL);
|
||||
|
||||
// Handle termination signals
|
||||
signal(SIGINT, termHandler);
|
||||
signal(SIGTERM, termHandler);
|
||||
|
||||
// Avoid zombie subprocesses
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = SIG_DFL;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_NOCLDWAIT;
|
||||
sigaction(SIGCHLD, &sa, 0);
|
||||
}
|
||||
|
||||
void statusLoop() {
|
||||
// Update all blocks initially
|
||||
raise(SIGALRM);
|
||||
|
||||
struct epoll_event events[LEN(blocks) + 1];
|
||||
while (statusContinue) {
|
||||
int eventCount = epoll_wait(epollFD, events, LEN(events), -1);
|
||||
for (int i = 0; i < eventCount; i++) {
|
||||
unsigned short id = events[i].data.u32;
|
||||
if (id < LEN(blocks))
|
||||
updateBlock(id);
|
||||
else
|
||||
signalHandler();
|
||||
}
|
||||
|
||||
if (eventCount != -1) writeStatus();
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
epollFD = epoll_create(LEN(blocks));
|
||||
event.events = EPOLLIN;
|
||||
|
||||
for (int i = 0; i < LEN(blocks); i++) {
|
||||
// Append each block's pipe to `epollFD`
|
||||
pipe(pipes[i]);
|
||||
event.data.u32 = i;
|
||||
epoll_ctl(epollFD, EPOLL_CTL_ADD, pipes[i][0], &event);
|
||||
|
||||
// Calculate the max interval and tick size for the timer
|
||||
if (blocks[i].interval) {
|
||||
maxInterval = MAX(blocks[i].interval, maxInterval);
|
||||
timerTick = gcd(blocks[i].interval, timerTick);
|
||||
}
|
||||
}
|
||||
|
||||
setupSignals();
|
||||
}
|
||||
|
||||
int main(const int argc, const char *argv[]) {
|
||||
if (setupX()) {
|
||||
fprintf(stderr, "dwmblocks: Failed to open display\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
writeStatus = setRoot;
|
||||
for (int i = 0; i < argc; i++)
|
||||
if (!strcmp("-d", argv[i])) writeStatus = debug;
|
||||
|
||||
init();
|
||||
statusLoop();
|
||||
|
||||
XCloseDisplay(dpy);
|
||||
close(epollFD);
|
||||
close(signalFD);
|
||||
for (int i = 0; i < LEN(pipes); i++) closePipe(pipes[i]);
|
||||
|
||||
return 0;
|
||||
}
|
62
src/bar.c
Normal file
62
src/bar.c
Normal file
@ -0,0 +1,62 @@
|
||||
#include "bar.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "block.h"
|
||||
#include "x11.h"
|
||||
|
||||
void initStatus(BarStatus *status) {
|
||||
const unsigned int statusLength =
|
||||
(blockCount * (LEN(blocks[0].output) - 1)) +
|
||||
(blockCount - 1 + LEADING_DELIMITER) * (LEN(DELIMITER) - 1);
|
||||
|
||||
status->current = (char *)malloc(statusLength);
|
||||
status->previous = (char *)malloc(statusLength);
|
||||
status->current[0] = '\0';
|
||||
status->previous[0] = '\0';
|
||||
}
|
||||
|
||||
void freeStatus(BarStatus *status) {
|
||||
free(status->current);
|
||||
free(status->previous);
|
||||
}
|
||||
|
||||
int updateStatus(BarStatus *status) {
|
||||
strcpy(status->previous, status->current);
|
||||
status->current[0] = '\0';
|
||||
|
||||
for (int i = 0; i < blockCount; i++) {
|
||||
Block *block = blocks + i;
|
||||
|
||||
if (strlen(block->output)) {
|
||||
#if LEADING_DELIMITER
|
||||
strcat(status->current, DELIMITER);
|
||||
#else
|
||||
if (status->current[0]) strcat(status->current, DELIMITER);
|
||||
#endif
|
||||
|
||||
#if CLICKABLE_BLOCKS
|
||||
if (!debugMode && block->signal) {
|
||||
char signal[] = {block->signal, '\0'};
|
||||
strcat(status->current, signal);
|
||||
}
|
||||
#endif
|
||||
|
||||
strcat(status->current, block->output);
|
||||
}
|
||||
}
|
||||
return strcmp(status->current, status->previous);
|
||||
}
|
||||
|
||||
void writeStatus(BarStatus *status) {
|
||||
// Only write out if status has changed
|
||||
if (!updateStatus(status)) return;
|
||||
|
||||
if (debugMode) {
|
||||
printf("%s\n", status->current);
|
||||
return;
|
||||
}
|
||||
setXRootName(status->current);
|
||||
}
|
70
src/block.c
Normal file
70
src/block.c
Normal file
@ -0,0 +1,70 @@
|
||||
#include "block.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
static int execLock = 0;
|
||||
|
||||
void execBlock(const Block *block, const char *button) {
|
||||
unsigned short i = block - blocks;
|
||||
|
||||
// Ensure only one child process exists per block at an instance
|
||||
if (execLock & 1 << i) return;
|
||||
// Lock execution of block until current instance finishes execution
|
||||
execLock |= 1 << i;
|
||||
|
||||
if (fork() == 0) {
|
||||
close(block->pipe[0]);
|
||||
dup2(block->pipe[1], STDOUT_FILENO);
|
||||
close(block->pipe[1]);
|
||||
|
||||
if (button) setenv("BLOCK_BUTTON", button, 1);
|
||||
|
||||
FILE *file = popen(block->command, "r");
|
||||
if (!file) {
|
||||
printf("\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Buffer will hold both '\n' and '\0'
|
||||
char buffer[LEN(block->output) + 1];
|
||||
if (fgets(buffer, LEN(buffer), file) == NULL) {
|
||||
// Send an empty line in case of no output
|
||||
printf("\n");
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
pclose(file);
|
||||
|
||||
// Trim to the max possible UTF-8 capacity
|
||||
trimUTF8(buffer, LEN(buffer));
|
||||
|
||||
printf("%s\n", buffer);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
void execBlocks(unsigned int time) {
|
||||
for (int i = 0; i < blockCount; i++) {
|
||||
const Block *block = blocks + i;
|
||||
if (time == 0 ||
|
||||
(block->interval != 0 && time % block->interval == 0)) {
|
||||
execBlock(block, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateBlock(Block *block) {
|
||||
char buffer[LEN(block->output)];
|
||||
int bytesRead = read(block->pipe[0], buffer, LEN(buffer));
|
||||
|
||||
// String from pipe will always end with '\n'
|
||||
buffer[bytesRead - 1] = '\0';
|
||||
|
||||
strcpy(block->output, buffer);
|
||||
|
||||
// Remove execution lock for the current block
|
||||
execLock &= ~(1 << (block - blocks));
|
||||
}
|
155
src/main.c
Normal file
155
src/main.c
Normal file
@ -0,0 +1,155 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/signalfd.h>
|
||||
|
||||
#include "bar.h"
|
||||
#include "block.h"
|
||||
#include "util.h"
|
||||
#include "x11.h"
|
||||
|
||||
static unsigned short statusContinue = 1;
|
||||
unsigned short debugMode = 0;
|
||||
static int epollFD, signalFD;
|
||||
static int timerTick = 0, maxInterval = 1;
|
||||
|
||||
void signalHandler() {
|
||||
struct signalfd_siginfo info;
|
||||
read(signalFD, &info, sizeof(info));
|
||||
unsigned int signal = info.ssi_signo;
|
||||
|
||||
static unsigned int timer = 0;
|
||||
switch (signal) {
|
||||
case SIGALRM:
|
||||
// Schedule the next timer event and execute blocks
|
||||
alarm(timerTick);
|
||||
execBlocks(timer);
|
||||
|
||||
// Wrap `timer` to the interval [1, `maxInterval`]
|
||||
timer = (timer + timerTick - 1) % maxInterval + 1;
|
||||
return;
|
||||
case SIGUSR1:
|
||||
// Update all blocks on receiving SIGUSR1
|
||||
execBlocks(0);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int j = 0; j < blockCount; j++) {
|
||||
const Block *block = blocks + j;
|
||||
if (block->signal == signal - SIGRTMIN) {
|
||||
char button[4]; // value can't be more than 255;
|
||||
sprintf(button, "%d", info.ssi_int & 0xff);
|
||||
execBlock(block, button);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void termHandler() { statusContinue = 0; }
|
||||
|
||||
void setupSignals() {
|
||||
sigset_t handledSignals;
|
||||
sigemptyset(&handledSignals);
|
||||
sigaddset(&handledSignals, SIGUSR1);
|
||||
sigaddset(&handledSignals, SIGALRM);
|
||||
|
||||
// Append all block signals to `handledSignals`
|
||||
for (int i = 0; i < blockCount; i++)
|
||||
if (blocks[i].signal > 0)
|
||||
sigaddset(&handledSignals, SIGRTMIN + blocks[i].signal);
|
||||
|
||||
// Create a signal file descriptor for epoll to watch
|
||||
signalFD = signalfd(-1, &handledSignals, 0);
|
||||
|
||||
// Block all realtime and handled signals
|
||||
for (int i = SIGRTMIN; i <= SIGRTMAX; i++) sigaddset(&handledSignals, i);
|
||||
sigprocmask(SIG_BLOCK, &handledSignals, NULL);
|
||||
|
||||
// Handle termination signals
|
||||
signal(SIGINT, termHandler);
|
||||
signal(SIGTERM, termHandler);
|
||||
|
||||
// Avoid zombie subprocesses
|
||||
struct sigaction signalAction;
|
||||
signalAction.sa_handler = SIG_DFL;
|
||||
sigemptyset(&signalAction.sa_mask);
|
||||
signalAction.sa_flags = SA_NOCLDWAIT;
|
||||
sigaction(SIGCHLD, &signalAction, 0);
|
||||
}
|
||||
|
||||
void statusLoop() {
|
||||
// Update all blocks initially
|
||||
raise(SIGALRM);
|
||||
|
||||
BarStatus status;
|
||||
initStatus(&status);
|
||||
struct epoll_event events[blockCount + 1];
|
||||
while (statusContinue) {
|
||||
int eventCount = epoll_wait(epollFD, events, LEN(events), 100);
|
||||
for (int i = 0; i < eventCount; i++) {
|
||||
unsigned short id = events[i].data.u32;
|
||||
if (id < blockCount) {
|
||||
updateBlock(blocks + id);
|
||||
} else {
|
||||
signalHandler();
|
||||
}
|
||||
}
|
||||
|
||||
if (eventCount != -1) writeStatus(&status);
|
||||
}
|
||||
freeStatus(&status);
|
||||
}
|
||||
|
||||
void init() {
|
||||
epollFD = epoll_create(blockCount);
|
||||
struct epoll_event event = {
|
||||
.events = EPOLLIN,
|
||||
};
|
||||
|
||||
for (int i = 0; i < blockCount; i++) {
|
||||
// Append each block's pipe's read end to `epollFD`
|
||||
pipe(blocks[i].pipe);
|
||||
event.data.u32 = i;
|
||||
epoll_ctl(epollFD, EPOLL_CTL_ADD, blocks[i].pipe[0], &event);
|
||||
|
||||
// Calculate the max interval and tick size for the timer
|
||||
if (blocks[i].interval) {
|
||||
maxInterval = MAX(blocks[i].interval, maxInterval);
|
||||
timerTick = gcd(blocks[i].interval, timerTick);
|
||||
}
|
||||
}
|
||||
|
||||
setupSignals();
|
||||
|
||||
// Watch signal file descriptor as well
|
||||
event.data.u32 = blockCount;
|
||||
epoll_ctl(epollFD, EPOLL_CTL_ADD, signalFD, &event);
|
||||
}
|
||||
|
||||
int main(const int argc, const char *argv[]) {
|
||||
if (setupX()) {
|
||||
fprintf(stderr, "dwmblocks: Failed to open display\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < argc; i++) {
|
||||
if (!strcmp("-d", argv[i])) {
|
||||
debugMode = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
statusLoop();
|
||||
|
||||
if (closeX())
|
||||
fprintf(stderr, "%s\n", "dwmblocks: Failed to close display");
|
||||
|
||||
close(epollFD);
|
||||
close(signalFD);
|
||||
for (int i = 0; i < blockCount; i++) closePipe(blocks[i].pipe);
|
||||
|
||||
return 0;
|
||||
}
|
39
src/util.c
Normal file
39
src/util.c
Normal file
@ -0,0 +1,39 @@
|
||||
#include "util.h"
|
||||
|
||||
int gcd(int a, int b) {
|
||||
int temp;
|
||||
while (b > 0) {
|
||||
temp = a % b;
|
||||
a = b;
|
||||
b = temp;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
void closePipe(int pipe[2]) {
|
||||
close(pipe[0]);
|
||||
close(pipe[1]);
|
||||
}
|
||||
|
||||
void trimUTF8(char* buffer, unsigned int size) {
|
||||
int length = (size - 1) / 4;
|
||||
int count = 0, j = 0;
|
||||
char ch = buffer[j];
|
||||
while (ch != '\0' && ch != '\n' && count < length) {
|
||||
// Skip continuation bytes, if any
|
||||
int skip = 1;
|
||||
while ((ch & 0xc0) > 0x80) {
|
||||
ch <<= 1;
|
||||
skip++;
|
||||
}
|
||||
|
||||
j += skip;
|
||||
ch = buffer[j];
|
||||
count++;
|
||||
}
|
||||
|
||||
// Trim trailing newline and spaces
|
||||
buffer[j] = ' ';
|
||||
while (j >= 0 && buffer[j] == ' ') j--;
|
||||
buffer[j + 1] = '\0';
|
||||
}
|
23
src/x11.c
Normal file
23
src/x11.c
Normal file
@ -0,0 +1,23 @@
|
||||
#include "x11.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
static Display *display;
|
||||
static Window rootWindow;
|
||||
|
||||
int setupX() {
|
||||
display = XOpenDisplay(NULL);
|
||||
if (!display) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
rootWindow = DefaultRootWindow(display);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int closeX() { return XCloseDisplay(display); }
|
||||
|
||||
void setXRootName(char *str) {
|
||||
XStoreName(display, rootWindow, str);
|
||||
XFlush(display);
|
||||
}
|
Loading…
Reference in New Issue
Block a user