mirror of
https://github.com/mintycube/dwmblocks.git
synced 2024-10-22 14:05:47 +02:00
Rewrite the entire code-base
The following changes are focused upon: - Modularity - Doing away with globals - No heap allocations - Better command line interface - Switch from Xlib to XCB - More verbose type definitions - Implement a single-file config by utilising X-macros
This commit is contained in:
parent
2773129533
commit
bc84d094cd
@ -1,6 +1,8 @@
|
|||||||
BasedOnStyle: Google
|
BasedOnStyle: Google
|
||||||
IndentWidth: 4
|
IndentWidth: 4
|
||||||
|
InsertBraces: true
|
||||||
ColumnLimit: 79
|
ColumnLimit: 79
|
||||||
AlignArrayOfStructures: Left
|
AlignConsecutiveMacros: Consecutive
|
||||||
AlignConsecutiveMacros: true
|
|
||||||
AllowShortFunctionsOnASingleLine: None
|
AllowShortFunctionsOnASingleLine: None
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AllowShortIfStatementsOnASingleLine: Never
|
||||||
|
11
.clangd
11
.clangd
@ -1,6 +1,5 @@
|
|||||||
CompileFlags:
|
Diagnostics:
|
||||||
Add:
|
UnusedIncludes: Strict
|
||||||
- "-I."
|
MissingIncludes: Strict
|
||||||
- "-I./inc"
|
Includes:
|
||||||
- "-I.."
|
IgnoreHeader: bits/getopt_core.h
|
||||||
- "-I../inc"
|
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,3 @@
|
|||||||
build/
|
build/
|
||||||
.cache/
|
.cache/
|
||||||
dwmblocks
|
compile_commands.json
|
||||||
|
30
Makefile
30
Makefile
@ -3,18 +3,20 @@
|
|||||||
BIN := dwmblocks
|
BIN := dwmblocks
|
||||||
BUILD_DIR := build
|
BUILD_DIR := build
|
||||||
SRC_DIR := src
|
SRC_DIR := src
|
||||||
INC_DIR := inc
|
INC_DIR := include
|
||||||
|
|
||||||
VERBOSE := 0
|
VERBOSE := 0
|
||||||
|
LIBS := xcb-atom
|
||||||
|
|
||||||
PREFIX := /usr/local
|
PREFIX := /usr/local
|
||||||
CFLAGS := -Wall -Wextra -Ofast -I. -I$(INC_DIR)
|
CFLAGS := -Ofast -I. -I$(INC_DIR)
|
||||||
CFLAGS += -Wall -Wextra -Wno-missing-field-initializers
|
CFLAGS += -DBINARY=\"$(BIN)\" -D_POSIX_C_SOURCE=200809L
|
||||||
LDLIBS := -lX11
|
CFLAGS += -Wall -Wpedantic -Wextra -Wswitch-enum
|
||||||
|
CFLAGS += $(shell pkg-config --cflags $(LIBS))
|
||||||
|
LDLIBS := $(shell pkg-config --libs $(LIBS))
|
||||||
|
|
||||||
VPATH := $(SRC_DIR)
|
SRCS := $(wildcard $(SRC_DIR)/*.c)
|
||||||
OBJS := $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(wildcard $(SRC_DIR)/*.c))
|
OBJS := $(subst $(SRC_DIR)/,$(BUILD_DIR)/,$(SRCS:.c=.o))
|
||||||
OBJS += $(patsubst %.c,$(BUILD_DIR)/%.o,$(wildcard *.c))
|
|
||||||
|
|
||||||
INSTALL_DIR := $(DESTDIR)$(PREFIX)/bin
|
INSTALL_DIR := $(DESTDIR)$(PREFIX)/bin
|
||||||
|
|
||||||
@ -26,17 +28,15 @@ endif
|
|||||||
|
|
||||||
all: $(BUILD_DIR)/$(BIN)
|
all: $(BUILD_DIR)/$(BIN)
|
||||||
|
|
||||||
$(BUILD_DIR)/$(BIN): $(OBJS)
|
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c config.h
|
||||||
$(PRINTF) "LD" $@
|
$Qmkdir -p $(@D)
|
||||||
$Q$(LINK.o) $^ $(LDLIBS) -o $@
|
|
||||||
|
|
||||||
$(BUILD_DIR)/%.o: %.c config.h | $(BUILD_DIR)
|
|
||||||
$(PRINTF) "CC" $@
|
$(PRINTF) "CC" $@
|
||||||
$Q$(COMPILE.c) -o $@ $<
|
$Q$(COMPILE.c) -o $@ $<
|
||||||
|
|
||||||
$(BUILD_DIR):
|
|
||||||
$(PRINTF) "MKDIR" $@
|
$(BUILD_DIR)/$(BIN): $(OBJS)
|
||||||
$Qmkdir -p $@
|
$(PRINTF) "LD" $@
|
||||||
|
$Q$(LINK.o) $^ $(LDLIBS) -o $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$(PRINTF) "CLEAN" $(BUILD_DIR)
|
$(PRINTF) "CLEAN" $(BUILD_DIR)
|
||||||
|
27
README.md
27
README.md
@ -85,15 +85,14 @@ dwmblocks &
|
|||||||
|
|
||||||
### Modifying the blocks
|
### Modifying the blocks
|
||||||
|
|
||||||
You can define your status bar blocks in `config.c`:
|
You can define your status bar blocks in `config.h`:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
Block blocks[] = {
|
#define BLOCKS(X) \
|
||||||
...
|
...
|
||||||
{"volume", 0, 5},
|
X("volume", 0, 5), \
|
||||||
{"date", 1800, 1},
|
X("date", 1800, 1), \
|
||||||
...
|
...
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Each block has the following properties:
|
Each block has the following properties:
|
||||||
@ -107,17 +106,21 @@ Each block has the following properties:
|
|||||||
Apart from defining the blocks, features can be toggled through `config.h`:
|
Apart from defining the blocks, features can be toggled through `config.h`:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
// Maximum possible length of output from block, expressed in number of characters.
|
// String used to delimit block outputs in the status.
|
||||||
#define CMDLENGTH 50
|
|
||||||
|
|
||||||
// The status bar's delimiter that appears in between each block.
|
|
||||||
#define DELIMITER " "
|
#define DELIMITER " "
|
||||||
|
|
||||||
// Adds a leading delimiter to the status bar, useful for powerline.
|
// Maximum number of Unicode characters that a block can output.
|
||||||
#define LEADING_DELIMITER 1
|
#define MAX_BLOCK_OUTPUT_LENGTH 45
|
||||||
|
|
||||||
// Enable clickability for blocks. See the "Clickable blocks" section below.
|
|
||||||
|
// Control whether blocks are clickable.
|
||||||
#define CLICKABLE_BLOCKS 1
|
#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
|
### Signalling changes
|
||||||
|
19
config.c
19
config.c
@ -1,19 +0,0 @@
|
|||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include "block.h"
|
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
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},
|
|
||||||
};
|
|
||||||
|
|
||||||
const unsigned short blockCount = LEN(blocks);
|
|
31
config.h
31
config.h
@ -1,6 +1,29 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define CLICKABLE_BLOCKS 1 // Enable clickability for blocks
|
// String used to delimit block outputs in the status.
|
||||||
#define CMDLENGTH 45 // Trim block output to this length
|
#define DELIMITER " "
|
||||||
#define DELIMITER " " // Delimiter string used to separate blocks
|
|
||||||
#define LEADING_DELIMITER 0 // Whether a leading separator should be used
|
// 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
|
||||||
|
|
||||||
|
// Define blocks for the status feed as X(cmd, interval, signal).
|
||||||
|
#define BLOCKS(X) \
|
||||||
|
X("sb-mail", 600, 1) \
|
||||||
|
X("sb-music", 0, 2) \
|
||||||
|
X("sb-disk", 1800, 3) \
|
||||||
|
X("sb-memory", 10, 4) \
|
||||||
|
X("sb-loadavg", 5, 5) \
|
||||||
|
X("sb-mic", 0, 6) \
|
||||||
|
X("sb-record", 0, 7) \
|
||||||
|
X("sb-volume", 0, 8) \
|
||||||
|
X("sb-battery", 5, 9) \
|
||||||
|
X("sb-date", 1, 10)
|
||||||
|
15
inc/bar.h
15
inc/bar.h
@ -1,15 +0,0 @@
|
|||||||
#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 *);
|
|
17
inc/block.h
17
inc/block.h
@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#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 *);
|
|
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#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);
|
|
@ -1,5 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
int setupX();
|
|
||||||
int closeX();
|
|
||||||
void setXRootName(char *);
|
|
24
include/block.h
Normal file
24
include/block.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <bits/stdint-uintn.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
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;
|
||||||
|
|
||||||
|
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);
|
||||||
|
bool block_must_run(const block *const block, const unsigned int time);
|
10
include/cli.h
Normal file
10
include/cli.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool is_debug_mode;
|
||||||
|
} cli_arguments;
|
||||||
|
|
||||||
|
int cli_init(cli_arguments* const args, const char* const argv[],
|
||||||
|
const int argc);
|
14
include/main.h
Normal file
14
include/main.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#include "block.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
// Utilise C's adjacent string concatenation to count the number of blocks.
|
||||||
|
#define X(...) "."
|
||||||
|
extern block blocks[LEN(BLOCKS(X)) - 1];
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
#define REFRESH_SIGNAL SIGUSR1
|
22
include/signal-handler.h
Normal file
22
include/signal-handler.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <bits/types/sigset_t.h>
|
||||||
|
|
||||||
|
#include "timer.h"
|
||||||
|
|
||||||
|
typedef sigset_t signal_set;
|
||||||
|
typedef int (*signal_refresh_callback)(void);
|
||||||
|
typedef int (*signal_timer_callback)(timer* const timer);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int fd;
|
||||||
|
const signal_refresh_callback refresh_callback;
|
||||||
|
const signal_timer_callback timer_callback;
|
||||||
|
} signal_handler;
|
||||||
|
|
||||||
|
signal_handler signal_handler_new(
|
||||||
|
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);
|
25
include/status.h
Normal file
25
include/status.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "block.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "x11.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
#define STATUS_LENGTH \
|
||||||
|
((LEN(blocks) * (MEMBER_LENGTH(block, output) - 1) + CLICKABLE_BLOCKS) + \
|
||||||
|
(LEN(blocks) - 1 + LEADING_DELIMITER + TRAILING_DELIMITER) * \
|
||||||
|
(LEN(DELIMITER) - 1) + \
|
||||||
|
1)
|
||||||
|
char current[STATUS_LENGTH];
|
||||||
|
char previous[STATUS_LENGTH];
|
||||||
|
#undef STATUS_LENGTH
|
||||||
|
} status;
|
||||||
|
|
||||||
|
status status_new(void);
|
||||||
|
bool status_update(status* const status);
|
||||||
|
int status_write(const status* const status, const bool is_debug_mode,
|
||||||
|
x11_connection* const connection);
|
14
include/timer.h
Normal file
14
include/timer.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#define TIMER_SIGNAL SIGALRM
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned int time;
|
||||||
|
const unsigned int tick;
|
||||||
|
const unsigned int reset_value;
|
||||||
|
} timer;
|
||||||
|
|
||||||
|
timer timer_new(void);
|
||||||
|
int timer_arm(timer *const timer);
|
22
include/util.h
Normal file
22
include/util.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||||
|
#define LEN(arr) (sizeof(arr) / sizeof(arr[0]))
|
||||||
|
#define BIT(n) (1 << (n))
|
||||||
|
#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member)
|
||||||
|
#define MEMBER_LENGTH(type, member) \
|
||||||
|
(MEMBER_SIZE(type, member) / MEMBER_SIZE(type, member[0]))
|
||||||
|
|
||||||
|
#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);
|
22
include/watcher.h
Normal file
22
include/watcher.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <sys/poll.h>
|
||||||
|
|
||||||
|
#include "main.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SIGNAL_FD = LEN(blocks),
|
||||||
|
WATCHER_FD_COUNT,
|
||||||
|
} watcher_fd_index;
|
||||||
|
|
||||||
|
typedef struct pollfd watcher_fd;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
watcher_fd fds[WATCHER_FD_COUNT];
|
||||||
|
} watcher;
|
||||||
|
|
||||||
|
int watcher_init(watcher *const watcher, const int signal_fd);
|
||||||
|
int watcher_poll(watcher *const watcher, const int timeout_ms);
|
||||||
|
bool watcher_fd_is_readable(const watcher_fd *const watcher_fd);
|
10
include/x11.h
Normal file
10
include/x11.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#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);
|
62
src/bar.c
62
src/bar.c
@ -1,62 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
169
src/block.c
169
src/block.c
@ -1,72 +1,147 @@
|
|||||||
#include "block.h"
|
#include "block.h"
|
||||||
|
|
||||||
#define _GNU_SOURCE
|
#include <bits/stdint-uintn.h>
|
||||||
|
#include <bits/types/FILE.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
static int execLock = 0;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
void execBlock(const Block *block, const char *button) {
|
block->fork_pid = -1;
|
||||||
unsigned short i = block - blocks;
|
|
||||||
|
|
||||||
// Ensure only one child process exists per block at an instance
|
return 0;
|
||||||
if (execLock & 1 << i) return;
|
}
|
||||||
// Lock execution of block until current instance finishes execution
|
|
||||||
execLock |= 1 << i;
|
|
||||||
|
|
||||||
if (fork() == 0) {
|
int block_deinit(block *const block) {
|
||||||
close(block->pipe[0]);
|
int status = close(block->pipe[READ_END]);
|
||||||
dup2(block->pipe[1], STDOUT_FILENO);
|
status |= close(block->pipe[WRITE_END]);
|
||||||
close(block->pipe[1]);
|
if (status != 0) {
|
||||||
|
(void)fprintf(stderr, "error: could not close \"%s\" block's pipe\n",
|
||||||
|
block->command);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (button) setenv("BLOCK_BUTTON", button, 1);
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
FILE *file = popen(block->command, "r");
|
int block_execute(block *const block, const uint8_t button) {
|
||||||
if (!file) {
|
// Ensure only one child process exists per block at an instance.
|
||||||
printf("\n");
|
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);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buffer will hold both '\n' and '\0'
|
FILE *const file = popen(block->command, "r");
|
||||||
char buffer[LEN(block->output) + 1];
|
if (file == NULL) {
|
||||||
if (fgets(buffer, LEN(buffer), file) == NULL) {
|
(void)write(write_fd, &null, sizeof(null));
|
||||||
// Send an empty line in case of no output
|
exit(EXIT_FAILURE);
|
||||||
printf("\n");
|
}
|
||||||
|
|
||||||
|
// 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);
|
exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
pclose(file);
|
|
||||||
|
|
||||||
// Trim to the max possible UTF-8 capacity
|
return 0;
|
||||||
trimUTF8(buffer, LEN(buffer));
|
|
||||||
|
|
||||||
printf("%s\n", buffer);
|
|
||||||
exit(EXIT_SUCCESS);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void execBlocks(unsigned int time) {
|
int block_update(block *const block) {
|
||||||
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)];
|
char buffer[LEN(block->output)];
|
||||||
int bytesRead = read(block->pipe[0], buffer, LEN(buffer));
|
|
||||||
|
|
||||||
// String from pipe will always end with '\n'
|
const ssize_t bytes_read =
|
||||||
buffer[bytesRead - 1] = '\0';
|
read(block->pipe[READ_END], buffer, LEN(buffer));
|
||||||
|
if (bytes_read == -1) {
|
||||||
strcpy(block->output, buffer);
|
(void)fprintf(stderr,
|
||||||
|
"error: could not fetch output of \"%s\" block\n",
|
||||||
// Remove execution lock for the current block
|
block->command);
|
||||||
execLock &= ~(1 << (block - blocks));
|
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)strcpy(block->output, buffer);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool block_must_run(const block *const block, const unsigned int time) {
|
||||||
|
if (time == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block->interval == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return time % block->interval == 0;
|
||||||
}
|
}
|
||||||
|
30
src/cli.c
Normal file
30
src/cli.c
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#include "cli.h"
|
||||||
|
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int cli_init(cli_arguments *const args, const char *const argv[],
|
||||||
|
const int argc) {
|
||||||
|
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 'h':
|
||||||
|
// fall through
|
||||||
|
case '?':
|
||||||
|
(void)fprintf(stderr, "error: unknown option `-%c'\n", optopt);
|
||||||
|
// fall through
|
||||||
|
default:
|
||||||
|
(void)fprintf(stderr, "usage: %s [-d]\n", BINARY);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
311
src/main.c
311
src/main.c
@ -1,157 +1,180 @@
|
|||||||
#define _GNU_SOURCE
|
#include "main.h"
|
||||||
#include <signal.h>
|
|
||||||
#include <stdio.h>
|
#include <stdbool.h>
|
||||||
#include <string.h>
|
#include <stddef.h>
|
||||||
#include <sys/epoll.h>
|
|
||||||
#include <sys/signalfd.h>
|
|
||||||
|
|
||||||
#include "bar.h"
|
|
||||||
#include "block.h"
|
#include "block.h"
|
||||||
|
#include "cli.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "signal-handler.h"
|
||||||
|
#include "status.h"
|
||||||
|
#include "timer.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include "watcher.h"
|
||||||
#include "x11.h"
|
#include "x11.h"
|
||||||
|
|
||||||
static unsigned short statusContinue = 1;
|
#define BLOCK(cmd, period, sig) \
|
||||||
unsigned short debugMode = 0;
|
{ \
|
||||||
static int epollFD, signalFD;
|
.command = cmd, \
|
||||||
static unsigned int timerTick = 0, maxInterval = 1;
|
.interval = period, \
|
||||||
|
.signal = sig, \
|
||||||
|
},
|
||||||
|
|
||||||
void signalHandler() {
|
block blocks[] = {BLOCKS(BLOCK)};
|
||||||
struct signalfd_siginfo info;
|
#undef BLOCK
|
||||||
read(signalFD, &info, sizeof(info));
|
|
||||||
unsigned int signal = info.ssi_signo;
|
|
||||||
|
|
||||||
static unsigned int timer = 0;
|
static int init_blocks(void) {
|
||||||
switch (signal) {
|
for (unsigned short i = 0; i < LEN(blocks); ++i) {
|
||||||
case SIGALRM:
|
block *const block = &blocks[i];
|
||||||
// Schedule the next timer event and execute blocks
|
if (block_init(block) != 0) {
|
||||||
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, "%s\n", "dwmblocks: Failed to open display");
|
|
||||||
return 1;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int deinit_blocks(void) {
|
||||||
|
for (unsigned short i = 0; i < LEN(blocks); ++i) {
|
||||||
|
block *const block = &blocks[i];
|
||||||
|
if (block_deinit(block) != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int execute_blocks(const unsigned int time) {
|
||||||
|
for (unsigned short i = 0; i < LEN(blocks); ++i) {
|
||||||
|
block *const block = &blocks[i];
|
||||||
|
if (!block_must_run(block, time)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block_execute(&blocks[i], 0) != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int trigger_event(timer *const timer) {
|
||||||
|
if (execute_blocks(timer->time) != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timer_arm(timer) != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int refresh_callback(void) {
|
||||||
|
if (execute_blocks(0) != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int event_loop(const bool is_debug_mode,
|
||||||
|
x11_connection *const connection,
|
||||||
|
signal_handler *const signal_handler) {
|
||||||
|
timer timer = timer_new();
|
||||||
|
|
||||||
|
// Kickstart the event loop with an initial execution.
|
||||||
|
if (trigger_event(&timer) != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
watcher watcher;
|
||||||
|
if (watcher_init(&watcher, signal_handler->fd) != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
status status = status_new();
|
||||||
|
bool is_alive = true;
|
||||||
|
while (is_alive) {
|
||||||
|
const int event_count = watcher_poll(&watcher, -1);
|
||||||
|
if (event_count == -1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (unsigned short j = 0; j < WATCHER_FD_COUNT; ++j) {
|
||||||
|
if (i == event_count) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const watcher_fd *const watcher_fd = &watcher.fds[j];
|
||||||
|
if (!watcher_fd_is_readable(watcher_fd)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
++i;
|
||||||
|
|
||||||
|
if (j == SIGNAL_FD) {
|
||||||
|
is_alive = signal_handler_process(signal_handler, &timer) == 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
block *const block = &blocks[j];
|
||||||
|
(void)block_update(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool has_status_changed = status_update(&status);
|
||||||
|
if (has_status_changed) {
|
||||||
|
if (status_write(&status, is_debug_mode, connection) != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(const int argc, const char *const argv[]) {
|
||||||
|
cli_arguments cli_args;
|
||||||
|
if (cli_init(&cli_args, argv, argc) != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
x11_connection *const connection = x11_connection_open();
|
||||||
|
if (connection == NULL) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int status = 0;
|
||||||
|
if (init_blocks() != 0) {
|
||||||
|
status = 1;
|
||||||
|
goto x11_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
signal_handler signal_handler =
|
||||||
|
signal_handler_new(refresh_callback, trigger_event);
|
||||||
|
if (signal_handler_init(&signal_handler) != 0) {
|
||||||
|
status = 1;
|
||||||
|
goto deinit_blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event_loop(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() != 0) {
|
||||||
|
status = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
x11_close:
|
||||||
|
x11_connection_close(connection);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
119
src/signal-handler.c
Normal file
119
src/signal-handler.c
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
#include "signal-handler.h"
|
||||||
|
|
||||||
|
#include <bits/stdint-uintn.h>
|
||||||
|
#include <bits/types/sigset_t.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/signalfd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "block.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
typedef struct signalfd_siginfo signal_info;
|
||||||
|
|
||||||
|
signal_handler signal_handler_new(
|
||||||
|
const signal_refresh_callback refresh_callback,
|
||||||
|
const signal_timer_callback timer_callback) {
|
||||||
|
signal_handler handler = {
|
||||||
|
.refresh_callback = refresh_callback,
|
||||||
|
.timer_callback = timer_callback,
|
||||||
|
};
|
||||||
|
|
||||||
|
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 < LEN(blocks); ++i) {
|
||||||
|
const block *const block = &blocks[i];
|
||||||
|
if (blocks->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 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(timer) != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
case REFRESH_SIGNAL:
|
||||||
|
if (handler->refresh_callback() != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
case SIGTERM:
|
||||||
|
// fall through
|
||||||
|
case SIGINT:
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned short i = 0; i < LEN(blocks); ++i) {
|
||||||
|
block *const block = blocks + i;
|
||||||
|
if (block->signal == signal - SIGRTMIN) {
|
||||||
|
const uint8_t button = (uint8_t)info.ssi_int;
|
||||||
|
block_execute(block, button);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
74
src/status.c
Normal file
74
src/status.c
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#include "status.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "block.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "main.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(void) {
|
||||||
|
status status = {
|
||||||
|
.current = {[0] = '\0'},
|
||||||
|
.previous = {[0] = '\0'},
|
||||||
|
};
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool status_update(status *const status) {
|
||||||
|
(void)strcpy(status->previous, status->current);
|
||||||
|
status->current[0] = '\0';
|
||||||
|
|
||||||
|
for (unsigned short i = 0; i < LEN(blocks); ++i) {
|
||||||
|
const block *const block = &blocks[i];
|
||||||
|
|
||||||
|
if (strlen(block->output) > 0) {
|
||||||
|
#if LEADING_DELIMITER
|
||||||
|
(void)strcat(status->current, DELIMITER);
|
||||||
|
#else
|
||||||
|
if (status->current[0] != '\0') {
|
||||||
|
(void)strcat(status->current, DELIMITER);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CLICKABLE_BLOCKS
|
||||||
|
if (block->signal > 0) {
|
||||||
|
const char signal[] = {(char)block->signal, '\0'};
|
||||||
|
(void)strcat(status->current, signal);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
(void)strcat(status->current, block->output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if TRAILING_DELIMITER
|
||||||
|
if (status->current[0] != '\0') {
|
||||||
|
(void)strcat(status->current, 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;
|
||||||
|
}
|
56
src/timer.c
Normal file
56
src/timer.c
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#include "timer.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "block.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
static unsigned int compute_tick(void) {
|
||||||
|
unsigned int tick = 0;
|
||||||
|
|
||||||
|
for (unsigned short i = 0; i < LEN(blocks); ++i) {
|
||||||
|
const block *const block = &blocks[i];
|
||||||
|
tick = gcd(block->interval, tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int compute_reset_value(void) {
|
||||||
|
unsigned int reset_value = 1;
|
||||||
|
|
||||||
|
for (unsigned short i = 0; i < LEN(blocks); ++i) {
|
||||||
|
const block *const block = &blocks[i];
|
||||||
|
reset_value = MAX(block->interval, reset_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reset_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
timer timer_new(void) {
|
||||||
|
timer timer = {
|
||||||
|
.time = 0,
|
||||||
|
.tick = compute_tick(),
|
||||||
|
.reset_value = compute_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 + 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
55
src/util.c
55
src/util.c
@ -1,41 +1,50 @@
|
|||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <stdio.h>
|
||||||
|
#define UTF8_MULTIBYTE_BIT BIT(7)
|
||||||
|
|
||||||
int gcd(int a, int b) {
|
unsigned int gcd(unsigned int a, unsigned int b) {
|
||||||
int temp;
|
|
||||||
while (b > 0) {
|
while (b > 0) {
|
||||||
temp = a % b;
|
const unsigned int temp = a % b;
|
||||||
a = b;
|
a = b;
|
||||||
b = temp;
|
b = temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
void closePipe(int pipe[2]) {
|
size_t truncate_utf8_string(char* const buffer, const size_t size,
|
||||||
close(pipe[0]);
|
const size_t char_limit) {
|
||||||
close(pipe[1]);
|
size_t char_count = 0;
|
||||||
|
size_t i = 0;
|
||||||
|
while (char_count < char_limit) {
|
||||||
|
char ch = buffer[i];
|
||||||
|
if (ch == '\0') {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
void trimUTF8(char* buffer, unsigned int size) {
|
unsigned short skip = 1;
|
||||||
int length = (size - 1) / 4;
|
|
||||||
int count = 0, j = 0;
|
// Multibyte unicode character
|
||||||
char ch = buffer[j];
|
if ((ch & UTF8_MULTIBYTE_BIT) != 0) {
|
||||||
while (ch != '\0' && ch != '\n' && count < length) {
|
// Skip continuation bytes.
|
||||||
// Skip continuation bytes, if any
|
|
||||||
int skip = 1;
|
|
||||||
while ((ch & 0xc0) > 0x80) {
|
|
||||||
ch <<= 1;
|
ch <<= 1;
|
||||||
skip++;
|
while ((ch & UTF8_MULTIBYTE_BIT) != 0) {
|
||||||
|
ch <<= 1;
|
||||||
|
++skip;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
j += skip;
|
// Avoid buffer overflow.
|
||||||
ch = buffer[j];
|
if (i + skip >= size) {
|
||||||
count++;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trim trailing newline and spaces
|
++char_count;
|
||||||
buffer[j] = ' ';
|
i += skip;
|
||||||
while (j >= 0 && buffer[j] == ' ') j--;
|
}
|
||||||
buffer[j + 1] = '\0';
|
|
||||||
|
buffer[i] = '\0';
|
||||||
|
|
||||||
|
return i + 1;
|
||||||
}
|
}
|
||||||
|
53
src/watcher.c
Normal file
53
src/watcher.c
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#include "watcher.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/poll.h>
|
||||||
|
|
||||||
|
#include "main.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
int watcher_init(watcher* const watcher, const int signal_fd) {
|
||||||
|
if (signal_fd == -1) {
|
||||||
|
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 < LEN(blocks); ++i) {
|
||||||
|
const int block_fd = blocks[i].pipe[READ_END];
|
||||||
|
if (block_fd == -1) {
|
||||||
|
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) {
|
||||||
|
const 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return event_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool watcher_fd_is_readable(const watcher_fd* const watcher_fd) {
|
||||||
|
return (watcher_fd->revents & POLLIN) != 0;
|
||||||
|
}
|
51
src/x11.c
51
src/x11.c
@ -1,25 +1,44 @@
|
|||||||
#include "x11.h"
|
#include "x11.h"
|
||||||
|
|
||||||
#include <X11/Xlib.h>
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
#include <xcb/xproto.h>
|
||||||
|
|
||||||
static Display *display;
|
x11_connection *x11_connection_open(void) {
|
||||||
static Window rootWindow;
|
xcb_connection_t *const connection = xcb_connect(NULL, NULL);
|
||||||
|
if (xcb_connection_has_error(connection)) {
|
||||||
|
(void)fprintf(stderr, "error: could not connect to the X server\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
int setupX() {
|
return connection;
|
||||||
display = XOpenDisplay(NULL);
|
}
|
||||||
if (!display) {
|
|
||||||
|
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 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
rootWindow = DefaultRootWindow(display);
|
|
||||||
return 0;
|
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