2021-10-13 03:07:55 +02:00
|
|
|
#define _GNU_SOURCE
|
2021-03-20 10:52:45 +01:00
|
|
|
#include <X11/Xlib.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
2021-10-14 22:28:20 +02:00
|
|
|
#include <sys/epoll.h>
|
|
|
|
#include <sys/signalfd.h>
|
2021-03-20 10:52:45 +01:00
|
|
|
#include <time.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
2021-10-12 16:53:42 +02:00
|
|
|
#define LEN(arr) (sizeof(arr) / sizeof(arr[0]))
|
2022-01-16 12:00:39 +01:00
|
|
|
#define MAX(a, b) (a > b ? a : b)
|
2022-01-03 06:04:20 +01:00
|
|
|
#define BLOCK(cmd, interval, signal) \
|
|
|
|
{ "echo \"$(" cmd ")\"", interval, signal }
|
2022-01-16 18:18:47 +01:00
|
|
|
|
2022-01-03 06:04:20 +01:00
|
|
|
typedef const struct {
|
2021-10-14 22:28:20 +02:00
|
|
|
const char* command;
|
2021-10-12 06:36:31 +02:00
|
|
|
const unsigned int interval;
|
|
|
|
const unsigned int signal;
|
|
|
|
} Block;
|
2021-03-20 10:52:45 +01:00
|
|
|
#include "config.h"
|
|
|
|
|
2021-10-17 03:58:15 +02:00
|
|
|
#ifdef CLICKABLE_BLOCKS
|
|
|
|
#undef CLICKABLE_BLOCKS
|
|
|
|
#define CLICKABLE_BLOCKS 1
|
|
|
|
#else
|
|
|
|
#define CLICKABLE_BLOCKS 0
|
|
|
|
#endif
|
|
|
|
|
2022-01-03 06:04:20 +01:00
|
|
|
#ifdef LEADING_DELIMITER
|
|
|
|
#undef LEADING_DELIMITER
|
|
|
|
#define LEADING_DELIMITER 1
|
|
|
|
#else
|
|
|
|
#define LEADING_DELIMITER 0
|
|
|
|
#endif
|
|
|
|
|
2021-10-14 22:28:20 +02:00
|
|
|
static Display* dpy;
|
2021-03-20 10:52:45 +01:00
|
|
|
static int screen;
|
|
|
|
static Window root;
|
2021-10-14 22:28:20 +02:00
|
|
|
static unsigned short int statusContinue = 1;
|
2021-10-17 03:58:15 +02:00
|
|
|
static char outputs[LEN(blocks)][CMDLENGTH + 1 + CLICKABLE_BLOCKS];
|
2022-01-03 06:04:20 +01:00
|
|
|
static char statusBar[2][LEN(blocks) * (LEN(outputs[0]) - 1) + (LEN(blocks) - 1 + LEADING_DELIMITER) * (LEN(DELIMITER) - 1) + 1];
|
2021-10-14 22:28:20 +02:00
|
|
|
static struct epoll_event event, events[LEN(blocks) + 2];
|
|
|
|
static int pipes[LEN(blocks)][2];
|
2021-10-16 06:02:18 +02:00
|
|
|
static int timerPipe[2];
|
2021-10-14 22:28:20 +02:00
|
|
|
static int signalFD;
|
|
|
|
static int epollFD;
|
2022-01-16 18:18:47 +01:00
|
|
|
static unsigned int execLock = 0;
|
2021-04-17 12:17:40 +02:00
|
|
|
void (*writeStatus)();
|
2021-04-13 07:35:21 +02:00
|
|
|
|
|
|
|
int gcd(int a, int b) {
|
|
|
|
int temp;
|
|
|
|
while (b > 0) {
|
|
|
|
temp = a % b;
|
|
|
|
a = b;
|
|
|
|
b = temp;
|
2021-03-20 10:52:45 +01:00
|
|
|
}
|
2021-04-13 07:35:21 +02:00
|
|
|
return a;
|
2021-03-20 10:52:45 +01:00
|
|
|
}
|
|
|
|
|
2021-10-16 06:02:18 +02:00
|
|
|
void closePipe(int* pipe) {
|
|
|
|
close(pipe[0]);
|
|
|
|
close(pipe[1]);
|
|
|
|
}
|
|
|
|
|
2021-10-14 22:28:20 +02:00
|
|
|
void execBlock(int i, const char* button) {
|
2022-01-16 18:18:47 +01:00
|
|
|
// 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;
|
|
|
|
|
2021-04-17 12:17:40 +02:00
|
|
|
if (fork() == 0) {
|
2021-10-14 22:28:20 +02:00
|
|
|
close(pipes[i][0]);
|
2021-10-16 06:02:18 +02:00
|
|
|
dup2(pipes[i][1], STDOUT_FILENO);
|
2021-10-12 06:36:31 +02:00
|
|
|
|
|
|
|
if (button)
|
|
|
|
setenv("BLOCK_BUTTON", button, 1);
|
2022-01-16 18:18:47 +01:00
|
|
|
execl("/bin/sh", "sh", "-c", blocks[i].command, (char*)NULL);
|
2021-03-20 10:52:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-16 12:00:39 +01:00
|
|
|
void execBlocks(unsigned int time) {
|
2021-04-17 12:17:40 +02:00
|
|
|
for (int i = 0; i < LEN(blocks); i++)
|
|
|
|
if (time == 0 || (blocks[i].interval != 0 && time % blocks[i].interval == 0))
|
2021-10-14 22:28:20 +02:00
|
|
|
execBlock(i, NULL);
|
2021-04-13 07:35:21 +02:00
|
|
|
}
|
|
|
|
|
2021-10-14 22:28:20 +02:00
|
|
|
int getStatus(char* new, char* old) {
|
2021-03-20 10:52:45 +01:00
|
|
|
strcpy(old, new);
|
2021-10-14 22:28:20 +02:00
|
|
|
new[0] = '\0';
|
2021-10-13 17:58:49 +02:00
|
|
|
|
2021-04-17 12:17:40 +02:00
|
|
|
for (int i = 0; i < LEN(blocks); i++) {
|
2022-01-03 06:04:20 +01:00
|
|
|
#if LEADING_DELIMITER
|
2021-10-15 11:56:08 +02:00
|
|
|
if (strlen(outputs[i]))
|
2021-10-14 22:28:20 +02:00
|
|
|
#else
|
2021-10-15 11:56:08 +02:00
|
|
|
if (strlen(new) && strlen(outputs[i]))
|
2021-10-14 22:28:20 +02:00
|
|
|
#endif
|
2021-04-17 12:17:40 +02:00
|
|
|
strcat(new, DELIMITER);
|
2021-10-12 06:36:31 +02:00
|
|
|
strcat(new, outputs[i]);
|
2021-03-20 10:52:45 +01:00
|
|
|
}
|
|
|
|
return strcmp(new, old);
|
|
|
|
}
|
|
|
|
|
2021-10-14 22:28:20 +02:00
|
|
|
void updateBlock(int i) {
|
2021-10-15 11:56:08 +02:00
|
|
|
char* output = outputs[i];
|
2022-01-03 06:04:20 +01:00
|
|
|
char buffer[LEN(outputs[0]) - CLICKABLE_BLOCKS];
|
2021-10-14 22:28:20 +02:00
|
|
|
int bytesRead = read(pipes[i][0], buffer, LEN(buffer));
|
2021-12-30 06:14:06 +01:00
|
|
|
|
|
|
|
// Trim UTF-8 characters properly
|
|
|
|
int j = bytesRead - 1;
|
|
|
|
while ((buffer[j] & 0b11000000) == 0x80)
|
|
|
|
j--;
|
2021-12-30 06:33:42 +01:00
|
|
|
buffer[j] = ' ';
|
|
|
|
|
|
|
|
// Trim trailing spaces
|
|
|
|
while (buffer[j] == ' ')
|
|
|
|
j--;
|
|
|
|
buffer[j + 1] = '\0';
|
2021-10-14 22:28:20 +02:00
|
|
|
|
|
|
|
if (bytesRead == LEN(buffer)) {
|
2021-10-15 11:56:08 +02:00
|
|
|
// Clear the pipe
|
2021-10-14 22:28:20 +02:00
|
|
|
char ch;
|
|
|
|
while (read(pipes[i][0], &ch, 1) == 1 && ch != '\n')
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
2021-10-17 03:58:15 +02:00
|
|
|
#if CLICKABLE_BLOCKS
|
2022-01-16 18:18:47 +01:00
|
|
|
if (bytesRead > 1 && blocks[i].signal > 0) {
|
2021-10-15 11:30:43 +02:00
|
|
|
output[0] = blocks[i].signal;
|
|
|
|
output++;
|
|
|
|
}
|
2021-10-17 03:58:15 +02:00
|
|
|
#endif
|
2021-10-14 22:28:20 +02:00
|
|
|
|
2021-10-17 03:58:15 +02:00
|
|
|
strcpy(output, buffer);
|
2022-01-16 18:18:47 +01:00
|
|
|
|
|
|
|
// Remove execution lock for the current block
|
|
|
|
execLock &= ~(1 << i);
|
2021-10-14 22:28:20 +02:00
|
|
|
}
|
|
|
|
|
2021-04-13 07:35:21 +02:00
|
|
|
void debug() {
|
|
|
|
// Only write out if text has changed
|
2021-10-12 06:36:31 +02:00
|
|
|
if (!getStatus(statusBar[0], statusBar[1]))
|
|
|
|
return;
|
2021-04-13 07:35:21 +02:00
|
|
|
|
2021-04-17 12:17:40 +02:00
|
|
|
write(STDOUT_FILENO, statusBar[0], strlen(statusBar[0]));
|
2021-04-13 07:35:21 +02:00
|
|
|
write(STDOUT_FILENO, "\n", 1);
|
|
|
|
}
|
|
|
|
|
2021-03-20 10:52:45 +01:00
|
|
|
void setRoot() {
|
2021-03-22 06:42:52 +01:00
|
|
|
// Only set root if text has changed
|
2021-10-12 06:36:31 +02:00
|
|
|
if (!getStatus(statusBar[0], statusBar[1]))
|
|
|
|
return;
|
2021-03-20 10:52:45 +01:00
|
|
|
|
2021-10-14 22:28:20 +02:00
|
|
|
Display* d = XOpenDisplay(NULL);
|
2021-10-12 06:36:31 +02:00
|
|
|
if (d)
|
|
|
|
dpy = d;
|
2021-03-20 10:52:45 +01:00
|
|
|
screen = DefaultScreen(dpy);
|
|
|
|
root = RootWindow(dpy, screen);
|
2021-04-17 12:17:40 +02:00
|
|
|
XStoreName(dpy, root, statusBar[0]);
|
2021-03-20 10:52:45 +01:00
|
|
|
XCloseDisplay(dpy);
|
|
|
|
}
|
|
|
|
|
2021-10-14 22:28:20 +02:00
|
|
|
void signalHandler() {
|
|
|
|
struct signalfd_siginfo info;
|
|
|
|
read(signalFD, &info, sizeof(info));
|
2021-03-20 10:52:45 +01:00
|
|
|
|
2021-10-14 22:28:20 +02:00
|
|
|
for (int j = 0; j < LEN(blocks); j++) {
|
|
|
|
if (blocks[j].signal == info.ssi_signo - SIGRTMIN) {
|
|
|
|
char button[] = {'0' + info.ssi_int & 0xff, 0};
|
|
|
|
execBlock(j, button);
|
|
|
|
break;
|
|
|
|
}
|
2021-10-12 06:36:31 +02:00
|
|
|
}
|
2021-10-14 22:28:20 +02:00
|
|
|
}
|
2021-10-14 11:13:07 +02:00
|
|
|
|
2021-10-14 22:28:20 +02:00
|
|
|
void termHandler() {
|
|
|
|
statusContinue = 0;
|
2021-03-20 10:52:45 +01:00
|
|
|
}
|
|
|
|
|
2021-04-13 07:35:21 +02:00
|
|
|
void setupSignals() {
|
2021-04-17 12:17:40 +02:00
|
|
|
signal(SIGTERM, termHandler);
|
|
|
|
signal(SIGINT, termHandler);
|
2021-04-13 07:35:21 +02:00
|
|
|
|
2021-10-14 22:28:20 +02:00
|
|
|
// Avoid zombie subprocesses
|
2021-04-17 12:17:40 +02:00
|
|
|
struct sigaction sa;
|
2021-10-14 22:28:20 +02:00
|
|
|
sa.sa_handler = SIG_DFL;
|
|
|
|
sigemptyset(&sa.sa_mask);
|
|
|
|
sa.sa_flags = SA_NOCLDWAIT;
|
|
|
|
sigaction(SIGCHLD, &sa, 0);
|
|
|
|
|
|
|
|
// Handle block update signals
|
|
|
|
sigset_t sigset;
|
|
|
|
sigemptyset(&sigset);
|
|
|
|
for (int i = 0; i < LEN(blocks); i++)
|
2021-08-31 13:48:19 +02:00
|
|
|
if (blocks[i].signal > 0)
|
2021-10-14 22:28:20 +02:00
|
|
|
sigaddset(&sigset, SIGRTMIN + blocks[i].signal);
|
|
|
|
|
|
|
|
signalFD = signalfd(-1, &sigset, 0);
|
|
|
|
sigprocmask(SIG_BLOCK, &sigset, NULL);
|
|
|
|
event.data.u32 = LEN(blocks) + 1;
|
|
|
|
epoll_ctl(epollFD, EPOLL_CTL_ADD, signalFD, &event);
|
|
|
|
}
|
|
|
|
|
2021-04-13 07:35:21 +02:00
|
|
|
void statusLoop() {
|
2022-01-16 18:18:47 +01:00
|
|
|
while (statusContinue) {
|
2022-01-17 08:40:43 +01:00
|
|
|
int eventCount = epoll_wait(epollFD, events, LEN(events), -1);
|
2021-10-14 22:28:20 +02:00
|
|
|
for (int i = 0; i < eventCount; i++) {
|
|
|
|
unsigned int id = events[i].data.u32;
|
|
|
|
|
|
|
|
if (id == LEN(blocks)) {
|
2022-01-16 18:18:47 +01:00
|
|
|
unsigned int j = 0;
|
2021-10-16 06:02:18 +02:00
|
|
|
read(timerPipe[0], &j, sizeof(j));
|
2021-10-14 22:28:20 +02:00
|
|
|
execBlocks(j);
|
|
|
|
} else if (id < LEN(blocks)) {
|
|
|
|
updateBlock(events[i].data.u32);
|
|
|
|
} else {
|
|
|
|
signalHandler();
|
|
|
|
}
|
|
|
|
}
|
2022-01-16 18:18:47 +01:00
|
|
|
if (eventCount != -1)
|
2021-10-14 22:28:20 +02:00
|
|
|
writeStatus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void timerLoop() {
|
2021-10-16 06:02:18 +02:00
|
|
|
close(timerPipe[0]);
|
2021-04-13 07:35:21 +02:00
|
|
|
|
2022-01-16 12:00:39 +01:00
|
|
|
unsigned int sleepInterval = 0;
|
|
|
|
unsigned int maxInterval = 0;
|
2021-04-17 12:17:40 +02:00
|
|
|
for (int i = 0; i < LEN(blocks); i++)
|
2022-01-16 12:00:39 +01:00
|
|
|
if (blocks[i].interval) {
|
|
|
|
maxInterval = MAX(blocks[i].interval, maxInterval);
|
2021-04-13 07:35:21 +02:00
|
|
|
sleepInterval = gcd(blocks[i].interval, sleepInterval);
|
2022-01-16 12:00:39 +01:00
|
|
|
}
|
2021-04-13 07:35:21 +02:00
|
|
|
|
2022-01-16 12:00:39 +01:00
|
|
|
unsigned int i = 0;
|
2021-04-13 07:35:21 +02:00
|
|
|
struct timespec sleepTime = {sleepInterval, 0};
|
|
|
|
struct timespec toSleep = sleepTime;
|
|
|
|
|
2021-10-14 22:28:20 +02:00
|
|
|
while (1) {
|
|
|
|
// Notify parent to update blocks
|
2021-10-16 06:02:18 +02:00
|
|
|
write(timerPipe[1], &i, sizeof(i));
|
2021-04-13 07:35:21 +02:00
|
|
|
|
2022-01-16 18:18:47 +01:00
|
|
|
// Wrap `i` to the interval [1, `maxInterval`]
|
2022-01-16 12:00:39 +01:00
|
|
|
i = (i + sleepInterval - 1) % maxInterval + 1;
|
2022-01-16 18:18:47 +01:00
|
|
|
|
|
|
|
// Sleep for `sleepTime` even on being interrupted
|
|
|
|
while (nanosleep(&toSleep, &toSleep) == -1)
|
|
|
|
;
|
|
|
|
toSleep = sleepTime;
|
2021-04-13 07:35:21 +02:00
|
|
|
}
|
2021-10-16 06:02:18 +02:00
|
|
|
|
|
|
|
close(timerPipe[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void init() {
|
|
|
|
epollFD = epoll_create(LEN(blocks) + 1);
|
|
|
|
event.events = EPOLLIN;
|
|
|
|
|
|
|
|
for (int i = 0; i < LEN(blocks); i++) {
|
|
|
|
pipe(pipes[i]);
|
|
|
|
event.data.u32 = i;
|
|
|
|
epoll_ctl(epollFD, EPOLL_CTL_ADD, pipes[i][0], &event);
|
|
|
|
}
|
|
|
|
|
|
|
|
pipe(timerPipe);
|
|
|
|
event.data.u32 = LEN(blocks);
|
|
|
|
epoll_ctl(epollFD, EPOLL_CTL_ADD, timerPipe[0], &event);
|
|
|
|
|
|
|
|
setupSignals();
|
2021-04-13 07:35:21 +02:00
|
|
|
}
|
|
|
|
|
2021-10-14 22:28:20 +02:00
|
|
|
int main(const int argc, const char* argv[]) {
|
2021-04-17 12:17:40 +02:00
|
|
|
writeStatus = setRoot;
|
2021-03-20 10:52:45 +01:00
|
|
|
for (int i = 0; i < argc; i++)
|
2021-10-12 06:36:31 +02:00
|
|
|
if (!strcmp("-d", argv[i]))
|
|
|
|
writeStatus = debug;
|
2021-04-17 12:17:40 +02:00
|
|
|
|
2021-10-14 22:28:20 +02:00
|
|
|
init();
|
|
|
|
|
2022-01-17 08:40:43 +01:00
|
|
|
// Ensure that `timerLoop()` only runs in the fork
|
|
|
|
if (fork() == 0)
|
2022-01-16 12:00:39 +01:00
|
|
|
timerLoop();
|
2022-01-17 08:40:43 +01:00
|
|
|
else
|
|
|
|
statusLoop();
|
2021-10-14 22:28:20 +02:00
|
|
|
|
|
|
|
close(epollFD);
|
2021-10-16 06:02:18 +02:00
|
|
|
close(signalFD);
|
|
|
|
closePipe(timerPipe);
|
|
|
|
for (int i = 0; i < LEN(pipes); i++)
|
|
|
|
closePipe(pipes[i]);
|
2021-10-14 22:28:20 +02:00
|
|
|
return 0;
|
2021-03-20 10:52:45 +01:00
|
|
|
}
|