#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
+#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const char errmsg[] = ERRMSG;
static const int errlen = sizeof(ERRMSG) - 1;
-char *argv0;
+char *argv0 = NULL;
+int running = 1;
/* TODO: Convert slot list to slot array. */
typedef struct Slot Slot;
struct Config {
double interval;
char * separator;
+ char expiry_character;
Slot * slots;
int slot_count;
- int total_width;
- int output_to_x_root_window;
+ int buf_width;
+ int to_x_root;
};
enum read_status {
FAILURE
};
+char *
+buf_create(Config *cfg)
+{
+ int seplen;
+ char *buf;
+ Slot *s;
+
+ buf = calloc(1, cfg->buf_width + 1);
+ if (buf == NULL)
+ khlib_fatal(
+ "[memory] Failed to allocate buffer of %d bytes",
+ cfg->buf_width
+ );
+ memset(buf, ' ', cfg->buf_width);
+ buf[cfg->buf_width] = '\0';
+ seplen = strlen(cfg->separator);
+ /* Set the separators */
+ for (s = cfg->slots; s; s = s->next) {
+ /* Skip the first, left-most */
+ if (s->out_pos_lo) {
+ /* Copying only seplen ensures we omit the '\0' byte. */
+ strncpy(
+ buf + (s->out_pos_lo - seplen),
+ cfg->separator,
+ seplen
+ );
+ }
+ }
+ return buf;
+}
+
Slot *
slots_rev(Slot *old)
{
}
void
-slot_print(Slot *s)
+slot_log(Slot *s)
{
khlib_info("Slot "
"{"
}
void
-slots_print(Slot *head)
+slots_log(Slot *head)
{
- for (Slot *s = head; s; s = s->next) {
- slot_print(s);
+ Slot *s = head;
+
+ for (; s; s = s->next) {
+ slot_log(s);
}
}
void
-slot_expire(Slot *s, struct timespec t, char *buf)
+slots_assert_fifos_exist(Slot *s)
+{
+ struct stat st;
+ int errors = 0;
+
+ for (; s; s = s->next) {
+ if (lstat(s->in_fifo, &st) < 0) {
+ khlib_error(
+ "Cannot stat \"%s\". Error: %s\n",
+ s->in_fifo,
+ strerror(errno)
+ );
+ errors++;
+ continue;
+ }
+ if (!(st.st_mode & S_IFIFO)) {
+ khlib_error("\"%s\" is not a FIFO\n", s->in_fifo);
+ errors++;
+ continue;
+ }
+ }
+ if (errors)
+ khlib_fatal(
+ "Encountered errors with given file paths. See log.\n"
+ );
+}
+
+void
+slot_close(Slot *s)
+{
+ close(s->in_fd);
+ s->in_fd = -1;
+ s->out_pos_cur = s->out_pos_lo;
+}
+
+void
+slots_close(Slot *s)
+{
+ for (; s; s = s->next)
+ if (s->in_fd > -1)
+ slot_close(s);
+}
+
+
+void
+slot_expire(Slot *s, struct timespec t, char expiry_character, char *buf)
{
struct timespec td;
timespecsub(&t, &(s->in_last_read), &td);
if (timespeccmp(&td, &(s->out_ttl), >=)) {
- /* TODO: Maybe configurable expiry character. */
- memset(buf + s->out_pos_lo, '_', s->out_width);
+ memset(
+ buf + s->out_pos_lo,
+ expiry_character,
+ s->out_width
+ );
khlib_warn("Slot expired: \"%s\"\n", s->in_fifo);
}
}
}
enum read_status
-slot_read(Slot *s, struct timespec t, char *buf)
+slot_read(Slot *s, char *buf)
{
char c; /* Character read. */
int r; /* Remaining unused positions in buffer range. */
r = (s->out_pos_hi - s->out_pos_cur) + 1;
if (r > 0)
memset(buf + s->out_pos_cur, ' ', r);
- s->out_pos_cur = s->out_pos_lo;
- s->in_last_read = t;
return END_OF_MESSAGE;
} else {
if (s->out_pos_cur <= s->out_pos_hi)
buf[s->out_pos_cur++] = c;
- /* Drop beyond available range. */
- /*
- * TODO Define max after which we stop reading.
- * To ensure that a rogue large message
- * doesn't trap us here.
- */
+ else
+ /*
+ * Force EOM beyond available range.
+ * To ensure that a rogue large message
+ * doesn't trap us here needlessly
+ * long.
+ */
+ return END_OF_MESSAGE;
}
break;
default:
switch (errno) {
case EINTR:
khlib_error(
- "pselect temp failure: %d, errno: %d, msg: %s\n",
+ "pselect interrupted: %d, errno: %d, msg: %s\n",
ready,
errno,
strerror(errno)
continue;
if (FD_ISSET(s->in_fd, &fds)) {
khlib_debug("reading: %s\n", s->in_fifo);
- switch (slot_read(s, t, buf)) {
+ switch (slot_read(s, buf)) {
/*
* ### MESSAGE LOSS ###
* is introduced by closing at EOM in addition
case END_OF_MESSAGE:
case END_OF_FILE:
case FAILURE:
- close(s->in_fd);
- s->in_fd = -1;
+ slot_close(s);
+ s->in_last_read = t;
ready--;
break;
case RETRY:
assert(0);
}
} else {
- slot_expire(s, t, buf);
+ slot_expire(s, t, cfg->expiry_character, buf);
}
}
} while (ready);
}
void
-config_print(Config *cfg)
+config_log(Config *cfg)
{
khlib_info(
"Config "
" interval = %f,"
" separator = %s,"
" slot_count = %d,"
- " total_width = %d,"
+ " buf_width = %d,"
" slots = ..."
" }\n",
cfg->interval,
cfg->separator,
cfg->slot_count,
- cfg->total_width
+ cfg->buf_width
);
- slots_print(cfg->slots);
+ slots_log(cfg->slots);
+}
+
+void
+config_stretch_for_separators(Config *cfg)
+{
+ int seplen = strlen(cfg->separator);
+ int prefix = 0;
+ int nslots = 0;
+ Slot *s = cfg->slots;
+
+ while (s) {
+ s->out_pos_lo += prefix;
+ s->out_pos_hi += prefix;
+ s->out_pos_cur = s->out_pos_lo;
+ prefix += seplen;
+ nslots++;
+ s = s->next;
+ }
+ cfg->buf_width += (seplen * (nslots - 1));
}
int
" | -s SEPARATOR\n"
" | -x (* Output to X root window *)\n"
" | -l LOG_LEVEL\n"
+ " | -e EXPIRY_CHARACTER\n"
" SEPARATOR = string\n"
" INTERVAL = float (* (positive) number of seconds *)\n"
" LOG_LEVEL = int (* %d through %d *)\n"
+ " EXPIRY_CHARACTER = string "
+ "(* Character with which to fill the slot upon expiration. *)\n"
"\n",
argv0,
Nothing,
opts_parse_any(cfg, argc, argv, i);
}
+void
+parse_opts_opt_e(Config *cfg, int argc, char *argv[], int i)
+{
+ if (i >= argc)
+ usage("Option -e parameter is missing.\n");
+ cfg->expiry_character = argv[i++][0];
+ opts_parse_any(cfg, argc, argv, i);
+}
+
void
parse_opts_opt(Config *cfg, int argc, char *argv[], int i)
{
parse_opts_opt_s(cfg, argc, argv, ++i);
break;
case 'x':
- cfg->output_to_x_root_window = 1;
+ cfg->to_x_root = 1;
opts_parse_any(cfg, argc, argv, ++i);
break;
case 'l':
/* TODO: Generic set_int */
parse_opts_opt_l(cfg, argc, argv, ++i);
break;
+ case 'e':
+ /* TODO: Generic set_str */
+ parse_opts_opt_e(cfg, argc, argv, ++i);
+ break;
default :
usage("Option \"%s\" is invalid\n", argv[i]);
}
s->out_width = atoi(w);
s->out_ttl = khlib_timespec_of_float(atof(t));
s->in_last_read = in_last_read;
- s->out_pos_lo = cfg->total_width;
+ s->out_pos_lo = cfg->buf_width;
s->out_pos_cur = s->out_pos_lo;
s->out_pos_hi = s->out_pos_lo + s->out_width - 1;
s->next = cfg->slots;
cfg->slots = s;
- cfg->total_width += s->out_width;
+ cfg->buf_width += s->out_width;
cfg->slot_count++;
} else {
khlib_fatal("[memory] Allocation failure.");
{
opts_parse_any(cfg, argc, argv, 1);
cfg->slots = slots_rev(cfg->slots);
+ config_log(cfg);
+ if (cfg->slots == NULL)
+ usage("No slot specs were given!\n");
}
-int
-main(int argc, char *argv[])
+void
+loop(Config *cfg, char *buf, Display *d)
{
- Config cfg = {
- .interval = 1.0,
- .separator = "|",
- .slots = NULL,
- .slot_count = 0,
- .total_width = 0,
- .output_to_x_root_window = 0,
- };
-
- int width = 0;
- int nslots = 0;
- int seplen = 0;
- int prefix = 0;
- int errors = 0;
- char *buf;
- Display *d = NULL;
- struct stat st;
struct timespec
t0, /* time stamp. before reading slots */
t1, /* time stamp. after reading slots */
ti, /* time interval desired (t1 - t0) */
td, /* time interval measured (t1 - t0) */
tc; /* time interval correction (ti - td) when td < ti */
- Slot *s;
-
- argv0 = argv[0];
-
- opts_parse(&cfg, argc, argv);
- khlib_debug("argv0 = %s\n", argv0);
- config_print(&cfg);
-
- ti = khlib_timespec_of_float(cfg.interval);
-
- if (cfg.slots == NULL)
- usage("No slot specs were given!\n");
-
- /* 1st pass to check file existence and type */
- for (s = cfg.slots; s; s = s->next) {
- if (lstat(s->in_fifo, &st) < 0) {
- khlib_error(
- "Cannot stat \"%s\". Error: %s\n",
- s->in_fifo,
- strerror(errno)
- );
- errors++;
- continue;
- }
- if (!(st.st_mode & S_IFIFO)) {
- khlib_error("\"%s\" is not a FIFO\n", s->in_fifo);
- errors++;
- continue;
- }
- }
- if (errors)
- khlib_fatal(
- "Encountered errors with given file paths. See log.\n"
- );
-
- width = cfg.total_width;
- seplen = strlen(cfg.separator);
- /* 2nd pass to make space for separators */
- for (s = cfg.slots; s; s = s->next) {
- s->out_pos_lo += prefix;
- s->out_pos_hi += prefix;
- s->out_pos_cur = s->out_pos_lo;
- prefix += seplen;
- nslots++;
- }
- width += (seplen * (nslots - 1));
- buf = calloc(1, width + 1);
- if (buf == NULL)
- khlib_fatal(
- "[memory] Failed to allocate buffer of %d bytes",
- width
- );
- memset(buf, ' ', width);
- buf[width] = '\0';
- /* 3rd pass to set the separators */
- for (s = cfg.slots; s; s = s->next) {
- if (s->out_pos_lo) { /* Skip the first, left-most */
- /* Copying only seplen ensures we omit the '\0' byte. */
- strncpy(
- buf + (s->out_pos_lo - seplen),
- cfg.separator,
- seplen
- );
- }
- }
-
- if (cfg.output_to_x_root_window && !(d = XOpenDisplay(NULL)))
- khlib_fatal("XOpenDisplay failed with: %p\n", d);
- /* TODO: Handle signals */
- for (;;) {
+ ti = khlib_timespec_of_float(cfg->interval);
+ while (running) {
clock_gettime(CLOCK_MONOTONIC, &t0); // FIXME: check errors
- slots_read(&cfg, &ti, buf);
- if (cfg.output_to_x_root_window) {
+ slots_read(cfg, &ti, buf);
+ if (cfg->to_x_root) {
if (XStoreName(d, DefaultRootWindow(d), buf) < 0)
khlib_fatal("XStoreName failed.\n");
XFlush(d);
khlib_sleep(&tc);
}
}
+}
+
+void
+terminate(int s)
+{
+ khlib_debug("terminating due to signal %d\n", s);
+ running = 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ argv0 = argv[0];
+
+ Config cfg = {
+ .interval = 1.0,
+ .separator = "|",
+ .expiry_character = '_',
+ .slots = NULL,
+ .slot_count = 0,
+ .buf_width = 0,
+ .to_x_root = 0,
+ };
+ char *buf;
+ Display *d = NULL;
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = terminate;
+ sigaction(SIGTERM, &sa, NULL);
+ sigaction(SIGINT , &sa, NULL);
+
+ opts_parse(&cfg, argc, argv);
+ slots_assert_fifos_exist(cfg.slots);
+ config_stretch_for_separators(&cfg);
+ buf = buf_create(&cfg);
+ if (cfg.to_x_root && !(d = XOpenDisplay(NULL)))
+ khlib_fatal("XOpenDisplay failed with: %p\n", d);
+ loop(&cfg, buf, d);
+ slots_close(cfg.slots);
return EXIT_SUCCESS;
}