Reorganize code and remove hack for empty block outputs
This commit is contained in:
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);
|
||||
}
|
||||
Reference in New Issue
Block a user