summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--LICENSE15
-rw-r--r--Makefile54
-rw-r--r--backlight.c507
-rw-r--r--common.c2
-rw-r--r--common.h131
-rw-r--r--config.mk8
-rw-r--r--stopwatch.c230
8 files changed, 952 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c68ad4a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+*\#*
+*~
+*.o
+*.su
+*.bin
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c44b2d9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2021 Mattias Andrée <maandree@kth.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8aa59db
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,54 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+HDR =\
+ common.h
+
+BIN_ =\
+ backlight\
+ stopwatch
+
+OBJ =\
+ common.o\
+ $(BIN_:=.o)
+
+BIN = $(BIN_:=.bin)
+MAN1 = $(BIN_:=.1)
+
+
+all: $(BIN)
+$(OBJ): $(@:.o=.c) $(HDR)
+$(BIN): $(@:.bin=.o) common.o
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS)
+
+.o.bin:
+ $(CC) -o $@ $< common.o $(LDFLAGS)
+
+install: $(BIN)
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
+ mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1"
+ for f in $(BIN_); do\
+ ! test -d "$(DESTDIR)$(PREFIX)/bin/$$f" &&\
+ cp -- "$$f.bin" "$(DESTDIR)$(PREFIX)/bin/$$f" || exit 1;\
+ done
+ cp -- $(MAN1) "$(DESTDIR)$(MANPREFIX)/man1"
+
+post-install:
+ chown -- '0:$(VIDEO_GROUP)' "$(DESTDIR)$(PREFIX)/bin/backlight"
+ chmod -- 4754 "$(DESTDIR)$(PREFIX)/bin/backlight"
+
+uninstall:
+ -cd -- "$(DESTDIR)$(PREFIX)/bin" && rm -f -- $(BIN_)
+ -cd -- "$(DESTDIR)$(MANPREFIX)/man1" && rm -f $(MAN1)
+
+clean:
+ -rm -rf -- *.o *.su *.bin
+
+.SUFFIXES:
+.SUFFIXES: .bin .o .c
+
+.PHONY: all install post-install uninstall clean
diff --git a/backlight.c b/backlight.c
new file mode 100644
index 0000000..633f04c
--- /dev/null
+++ b/backlight.c
@@ -0,0 +1,507 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+USAGE("[name] ...");
+
+
+static volatile sig_atomic_t caught_sigterm = 0;
+static volatile sig_atomic_t caught_sigwinch = 1;
+static volatile sig_atomic_t caught_sigio = 0;
+
+static int info = 0;
+static char path[4096];
+
+
+
+static void
+sigterm(int signo)
+{
+ caught_sigterm = 1;
+ (void) signo;
+}
+
+static void
+sigwinch(int signo)
+{
+ caught_sigwinch = 1;
+ (void) signo;
+}
+
+static void
+sigio(int signo)
+{
+ caught_sigio = 1;
+ (void) signo;
+}
+
+
+static void
+writefile(const char *path, char *buf, size_t size)
+{
+ int fd;
+ size_t off = 0;
+ ssize_t r;
+
+ fd = open(path, O_WRONLY);
+ if (fd < 0) {
+ weprintf("write %s:", path);
+ return;
+ }
+
+ for (;;) {
+ r = write(fd, &buf[off], size - off);
+ if (r <= 0) {
+ if (!r)
+ break;
+ close(fd);
+ weprintf("write %s:", path);
+ return;
+ }
+ off += (size_t)r;
+ }
+
+ close(fd);
+}
+
+
+static int
+readstrfile(const char *path, char *buf, size_t size)
+{
+ int fd;
+ size_t off = 0, i, j;
+ ssize_t r;
+ char c;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return 0;
+
+ for (;;) {
+ r = read(fd, &buf[off], size - sizeof("…") - off);
+ if (r <= 0) {
+ if (!r)
+ break;
+ close(fd);
+ return 0;
+ }
+ off += (size_t)r;
+ }
+
+ off -= (off && buf[off - 1] == '\n');
+
+ if (off == size - sizeof("…")) {
+ for (i = 0; i < off;) {
+ if ((buf[i] & 0xC0) == 0xC0) {
+ j = i;
+ c = buf[i];
+ do {
+ c <<= 1;
+ i++;
+ } while ((c & 0x80) && i < off && (buf[i] & 0xC0) == 0x80);
+ if (c & 0x80) {
+ off = j;
+ break;
+ }
+ } else {
+ i += 1;
+ }
+ }
+ memcpy(&buf[off], "…", sizeof("…"));
+ } else {
+ buf[off] = '\0';
+ }
+
+ close(fd);
+ return 1;
+}
+
+static int
+readintfile(const char *path, uintmax_t *valuep)
+{
+ char buf[256], *end;
+ int r;
+ r = readstrfile(path, buf, sizeof(buf));
+ if (r <= 0)
+ return r;
+ if (!isdigit(*buf))
+ return 0;
+ errno = 0;
+ *valuep = strtoumax(buf, &end, 10);
+ if (errno || *end)
+ return 0;
+ return 1;
+}
+
+
+static void
+adjust_fine(const char *path, uintmax_t max_brightness, uintmax_t *brightnessp, uintmax_t adjustment)
+{
+ char buf[sizeof(uintmax_t) * 3 + 2];
+ uintmax_t brightness = *brightnessp, result;
+ int len;
+
+fprintf(stderr, "START\n");
+again:
+fprintf(stderr, "adjusting: %ju, %ju, %ju, %ju\n", brightness, *brightnessp, max_brightness, adjustment);
+
+ if (max_brightness) {
+ if (adjustment >= max_brightness - brightness)
+ brightness = max_brightness;
+ else
+ brightness += adjustment;
+ } else {
+ if (brightness < adjustment)
+ brightness = 0;
+ else
+ brightness -= adjustment;
+ }
+
+fprintf(stderr, "setting: %ju, %s\n", brightness, path);
+ len = sprintf(buf, "%ju\n", brightness);
+ writefile(path, buf, (size_t)len);
+
+ if (readintfile(path, &result)) {
+fprintf(stderr, "result: %ju, %ju, %ju, %ju\n", result, brightness, *brightnessp, max_brightness);
+ if (result == *brightnessp && brightness != max_brightness)
+ goto again;
+ *brightnessp = result;
+ }
+fprintf(stderr, "END: %ju\n", *brightnessp);
+}
+
+
+static int
+show_backlights(int tfd, char **devs, size_t ndevs)
+{
+ struct winsize winsize;
+ char buf[256], c, prev_c = 0, *p, *colour, line[(sizeof("─") - 1) * 64 + 1];
+ int width = 0, height = 0, left = 0, namelen = 0, first = 1, len, digits;
+ int rearm = 1, redraw = 0, reload = 1, i;
+ int have_max = 0, have_current = 0, vis_brightness_at;
+ uintmax_t max_brightness = 0, brightness = 0;
+ double percent;
+ size_t index = 0, k;
+ ssize_t r;
+
+ for (k = 0, p = line; k < 64; k++)
+ p = stpcpy(p, "—");
+
+ while (!caught_sigterm) {
+ if (caught_sigwinch) {
+ if (ioctl(STDOUT_FILENO, (unsigned long)TIOCGWINSZ, &winsize) < 0) {
+ if (errno == EINTR)
+ continue;
+ weprintf("ioctl <stdout> TIOCGWINSZ:");
+ return -1;
+ }
+ caught_sigwinch = 0;
+ width = winsize.ws_col;
+ height = winsize.ws_row;
+ if (height > 2)
+ height -= 2;
+ else
+ height = 0;
+ if (width > 10) {
+ width /= 5;
+ if (width < 10)
+ width = 10;
+ }
+ left = (winsize.ws_col - width) / 2;
+ redraw = 1;
+ }
+ if (first) {
+ first = 0;
+ } else if (caught_sigio) {
+ caught_sigio = 0;
+ for (;;) {
+ r = read(STDIN_FILENO, &c, 1);
+ if (r <= 0) {
+ if (!r)
+ return 0;
+ if (errno == EAGAIN)
+ break;
+ if (errno == EINTR)
+ continue;
+ weprintf("read <stdin>:");
+ return -1;
+ }
+ if (c == 'r') {
+ rearm = 1;
+ } else if (c == 'L' - '@') {
+ redraw = 1;
+ } else if (c == 'i') {
+ info ^= 1;
+ redraw = 1;
+ stpcpy(p, "/brightness");
+ } else if (c == 'q') {
+ return 0;
+ } else if (c == 'C') {
+ if (ndevs > 1) {
+ redraw = 1;
+ index = (index + 1) % ndevs;
+ reload = 1;
+ have_current = 0;
+ }
+ } else if (c == 'D') {
+ if (ndevs > 1) {
+ redraw = 1;
+ if (!index)
+ index = ndevs;
+ index -= 1;
+ reload = 1;
+ have_current = 0;
+ }
+ } else if (c == 'A' && have_max) {
+ if (!have_current) {
+ stpcpy(p, "/brightness");
+ have_current = readintfile(path, &brightness);
+ }
+ if (have_current && brightness < max_brightness) {
+ redraw = 1;
+ adjust_fine(path, max_brightness, &brightness, 1);
+ }
+ } else if (c == 'B' && have_max) {
+ if (!have_current) {
+ stpcpy(p, "/brightness");
+ have_current = readintfile(path, &brightness);
+ }
+ if (have_current && brightness) {
+ redraw = 1;
+ adjust_fine(path, 0, &brightness, 1);
+ }
+ } else if (c == '~' && prev_c == '5' && have_max) {
+ if (!have_current) {
+ stpcpy(p, "/brightness");
+ have_current = readintfile(path, &brightness);
+ }
+ if (have_current && brightness < max_brightness) {
+ redraw = 1;
+ adjust_fine(path, max_brightness, &brightness, max_brightness / 10);
+ }
+ } else if (c == '~' && prev_c == '6' && have_max) {
+ if (!have_current) {
+ stpcpy(p, "/brightness");
+ have_current = readintfile(path, &brightness);
+ }
+ if (have_current && brightness) {
+ redraw = 1;
+ adjust_fine(path, 0, &brightness, max_brightness / 10);
+ }
+ }
+ prev_c = c;
+ }
+ }
+
+ /* Need instead of a sleep at the and to remove artifacts when there is a lot of input */
+ if (rearm) {
+ if (timerfd_settime(tfd, 0, &(struct itimerspec){{0, 100000000L}, {0, 100000000L}}, NULL)) {
+ weprintf("timerfd_settime <timerfd> 0 {0.1s 0.1s} NULL:");
+ return -1;
+ }
+ rearm = 0;
+ redraw = 0;
+ } else if (redraw) {
+ redraw = 0;
+ } else {
+ if (read(tfd, &(int64_t){0}, sizeof(int64_t)) < 0) {
+ if (errno == EINTR)
+ continue;
+ weprintf("read <timerfd> &(int64_t) sizeof(int64_t):");
+ return -1;
+ }
+ }
+
+ if (reload) {
+ reload = 0;
+ namelen = strlen(devs[index]);
+ p = stpcpy(stpcpy(path, "/sys/class/backlight/"), devs[index]);
+ stpcpy(p, "/max_brightness");
+ have_max = readintfile(path, &max_brightness);
+ stpcpy(p, "/brightness");
+ }
+
+ if (info) {
+ printf("\033[H\033[1m%s\033[m\033[K\n\033[K", devs[index]);
+ stpcpy(p, "/max_brightness");
+ if (readstrfile(path, buf, sizeof(buf)))
+ printf("\n\033[1mMaximum brightness:\033[m %s\033[K", buf);
+ stpcpy(p, "/brightness");
+ if (readstrfile(path, buf, sizeof(buf)))
+ printf("\n\033[1mBrightness:\033[m %s\033[K", buf);
+ stpcpy(p, "/actual_brightness");
+ if (readstrfile(path, buf, sizeof(buf)))
+ printf("\n\033[1mActual brightness:\033[m %s\033[K", buf);
+ stpcpy(p, "/bl_power");
+ if (readstrfile(path, buf, sizeof(buf)))
+ printf("\n\033[1mBacklight power:\033[m %s\033[K", buf);
+ stpcpy(p, "/scale");
+ if (readstrfile(path, buf, sizeof(buf)))
+ printf("\n\033[1mScale:\033[m %s\033[K", buf);
+ stpcpy(p, "/type");
+ if (readstrfile(path, buf, sizeof(buf)))
+ printf("\n\033[1mType:\033[m %s\033[K", buf);
+ printf("\033[J");
+ fflush(stdout);
+ continue;
+ }
+
+ have_current = readintfile(path, &brightness);
+
+ printf("\033[H%*.s\033[1m%s\033[m\033[K\n", (winsize.ws_col - namelen) / 2, "", devs[index]);
+ if (!have_max || !max_brightness) {
+ len = (int)(sizeof("Maximum brightness is unavailable") - 1);
+ printf("%*.s%s\033[K\n", (winsize.ws_col - len) / 2, "", "Maximum brightness is unavailable");
+ } else if (!have_current) {
+ len = (int)(sizeof("Current brightness is unavailable") - 1);
+ printf("%*.s%s\033[K\n", (winsize.ws_col - len) / 2, "", "Current brightness is unavailable");
+ } else {
+ if (width > 2 && height > 12) {
+ printf("%*.s┌", left, "");
+ for (i = 2; i + 64 < width; i += 64)
+ printf("%s", line);
+ if (i < width)
+ printf("%.*s", (width - i) * ((int)sizeof("─") - 1), line);
+ printf("┐\033[K\n");
+
+ vis_brightness_at = (int)((brightness * (uintmax_t)(height - 2) * 2 + 1) / (max_brightness * 2));
+ vis_brightness_at = 1 + height - 2 - vis_brightness_at;
+ for (i = 2; i < height; i++) {
+ colour = (i >= vis_brightness_at) ? "7" : "";
+ printf("%*.s│\033[%sm%*.s\033[m│\033[K\n", left, "", colour, width - 2, "");
+ }
+
+ printf("%*.s└", left, "");
+ for (i = 2; i + 64 < width; i += 64)
+ printf("%s", line);
+ if (i < width)
+ printf("%.*s", (width - i) * ((int)sizeof("─") - 1), line);
+ printf("┘\033[K\n");
+ }
+ digits = max_brightness >= 10000 ? 2 : max_brightness >= 1000 ? 1 : 0;
+ percent = 100. * (double)brightness / (double)max_brightness;
+ len = snprintf(NULL, 0, "%.*lf%% (%ju of %ju)", digits, percent, brightness, max_brightness);
+ printf("%*.s%.*lf%% (%ju of %ju)\033[K", (winsize.ws_col - len) / 2, "",
+ digits, percent, brightness, max_brightness);
+ }
+
+ printf("\033[J");
+ fflush(stdout);
+ }
+
+ return 0;
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ int old_sig = 0, old_flags = -1, tcset = 0, ret, tfd;
+ struct f_owner_ex old_owner, new_owner;
+ struct termios stty, saved_stty;
+ struct sigaction sigact;
+ char **devs = NULL, *p;
+ size_t ndevs = 0;
+ struct dirent *f;
+ DIR *dir;
+
+ ARGBEGIN {
+ case 'i':
+ info = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (!argc) {
+ dir = opendir("/sys/class/backlight");
+ if (!dir)
+ eprintf("opendir /sys/class/backlight:");
+ while ((errno = 0, f = readdir(dir))) {
+ if (f->d_name[0] == '.')
+ continue;
+ if (strlen(f->d_name) + 256 > sizeof(path))
+ continue;
+ p = stpcpy(stpcpy(path, "/sys/class/backlight/"), f->d_name);
+ stpcpy(p, "/max_brightness");
+ if (access(path, F_OK))
+ continue;
+ stpcpy(p, "/brightness");
+ if (access(path, F_OK))
+ continue;
+ devs = erealloc2(devs, ndevs + 1, sizeof(*devs));
+ devs[ndevs] = strdup(f->d_name);
+ if (!devs[ndevs++])
+ eprintf("strdup:");
+ }
+ closedir(dir);
+ if (errno)
+ eprintf("readdir /sys/class/backlight:");
+ if (!ndevs)
+ eprintf("no backlight devices found\n");
+ } else {
+ devs = argv;
+ ndevs = (size_t)argc;
+ }
+
+ memset(&sigact, 0, sizeof(sigact));
+
+ sigact.sa_handler = sigterm;
+ sigaction(SIGTERM, &sigact, NULL);
+ sigaction(SIGQUIT, &sigact, NULL);
+ sigaction(SIGINT, &sigact, NULL);
+
+ sigact.sa_handler = sigwinch;
+ sigaction(SIGWINCH, &sigact, NULL);
+
+ sigact.sa_handler = sigio;
+ sigaction(SIGIO, &sigact, NULL);
+ sigaction(SIGURG, &sigact, NULL);
+
+ tfd = timerfd_create(CLOCK_MONOTONIC, 0);
+ if (tfd < 0)
+ eprintf("timerfd_create CLOCK_MONOTONIC 0:");
+
+ if (fcntl(STDIN_FILENO, F_GETOWN_EX, &old_owner))
+ eprintf("fcntl <stdin> F_GETOWN_EX:");
+ memset(&new_owner, 0, sizeof(new_owner));
+ new_owner.type = F_OWNER_PID;
+ new_owner.pid = getpid();
+ if (fcntl(STDIN_FILENO, F_SETOWN_EX, &new_owner))
+ eprintf("fcntl <stdin> F_SETOWN_EX {.type=F_OWNER_PID, .pid=<getpid()>}:");
+ old_flags = fcntl(STDIN_FILENO, F_GETFL);
+ if (old_flags != -1)
+ fcntl(STDIN_FILENO, F_SETFL, old_flags | FASYNC | O_NONBLOCK);
+ fcntl(STDIN_FILENO, F_GETSIG, &old_sig);
+ if (old_sig)
+ fcntl(STDIN_FILENO, F_SETSIG, 0);
+
+ if (!tcgetattr(STDIN_FILENO, &stty)) {
+ saved_stty = stty;
+ stty.c_lflag &= (tcflag_t)~(ECHO | ICANON);
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &stty);
+ tcset = 1;
+ }
+
+ fprintf(stdout, "\033[?1049h\033[?25l");
+
+ ret = -show_backlights(tfd, devs, ndevs);
+
+ fprintf(stdout, "\033[?25h\n\033[?1049l");
+ fflush(stdout);
+
+ fcntl(STDIN_FILENO, F_SETOWN_EX, &old_owner);
+ if (old_flags != -1)
+ fcntl(STDIN_FILENO, F_SETFL, old_flags);
+ if (old_sig)
+ fcntl(STDIN_FILENO, F_SETSIG, old_sig);
+ if (tcset)
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_stty);
+ close(tfd);
+ if (!argc) {
+ while (ndevs--)
+ free(devs[ndevs]);
+ free(devs);
+ }
+
+ return ret;
+}
diff --git a/common.c b/common.c
new file mode 100644
index 0000000..72692ba
--- /dev/null
+++ b/common.c
@@ -0,0 +1,2 @@
+/* See LICENSE file for copyright and license details. */
+char *argv0;
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..0ee870c
--- /dev/null
+++ b/common.h
@@ -0,0 +1,131 @@
+/* See LICENSE file for copyright and license details. */
+#if !defined(_XOPEN_SOURCE)
+# define _XOPEN_SOURCE 700
+#elif (_XOPEN_SOURCE + 0 < 700)
+# undef _XOPEN_SOURCE
+# define _XOPEN_SOURCE 700
+#endif
+#ifndef _BSD_SOURCE
+# define _BSD_SOURCE
+#endif
+#ifndef _DEFAULT_SOURCE
+# define _DEFAULT_SOURCE
+#endif
+
+#include <libcore.h>
+#include <libcore-arg.h>
+#include <libcore-aux.h>
+
+#define malloc2 libcore_malloc2
+#define malloc3 libcore_malloc3
+#define calloc3 libcore_calloc3
+#define realloc2 libcore_realloc2
+#define realloc3 libcore_realloc3
+#define enmalloc libcore_enmalloc
+#define enmalloc2 libcore_enmalloc2
+#define enmalloc3 libcore_enmalloc3
+#define encalloc libcore_encalloc
+#define encalloc3 libcore_encalloc3
+#define enrealloc libcore_enrealloc
+#define enrealloc2 libcore_enrealloc2
+#define enrealloc3 libcore_enrealloc3
+#define emalloc(N1) enmalloc(1, (N1))
+#define emalloc2(N1, N2) enmalloc2(1, (N1), (N2));
+#define emalloc3(N1, N2, N3) enmalloc3(1, (N1), (N2), (N3));
+#define ecalloc(N1, N2) encalloc(1, (N1), (N2));
+#define ecalloc3(N1, N2, N3) encalloc3(1, (N1), (N2), (N3));
+#define erealloc(PTR, N1) enrealloc(1, (PTR), (N1));
+#define erealloc2(PTR, N1, N2) enrealloc2(1, (PTR), (N1), (N2));
+#define erealloc3(PTR, N1, N2, N3) enrealloc3(1, (PTR), (N1), (N2), (N3));
+
+#define eprintf libcore_eprintf
+#define enprintf libcore_enprintf
+#define weprintf libcore_weprintf
+
+#define fshut libcore_fshut
+#define efshut libcore_efshut
+#define enfshut libcore_enfshut
+
+#define toji libcore_toji
+#define toju libcore_toju
+#define tozi libcore_tozi
+#define tozu libcore_tozu
+#define tolli libcore_tolli
+#define tollu libcore_tollu
+#define toli libcore_toli
+#define tolu libcore_tolu
+#define toi libcore_toi
+#define tou libcore_tou
+#define entoji_flag libcore_entoji_flag
+#define entoju_flag libcore_entoju_flag
+#define entozi_flag libcore_entozi_flag
+#define entozu_flag libcore_entozu_flag
+#define entolli_flag libcore_entolli_flag
+#define entollu_flag libcore_entollu_flag
+#define entoli_flag libcore_entoli_flag
+#define entolu_flag libcore_entolu_flag
+#define entoi_flag libcore_entoi_flag
+#define entou_flag libcore_entou_flag
+#define entoji_arg libcore_entoji_arg
+#define entoju_arg libcore_entoju_arg
+#define entozi_arg libcore_entozi_arg
+#define entozu_arg libcore_entozu_arg
+#define entolli_arg libcore_entolli_arg
+#define entollu_arg libcore_entollu_arg
+#define entoli_arg libcore_entoli_arg
+#define entolu_arg libcore_entolu_arg
+#define entoi_arg libcore_entoi_arg
+#define entou_arg libcore_entou_arg
+#define etoji_flag(...) libcore_etoji_flag(__VA_ARGS__)
+#define etoju_flag(...) libcore_etoju_flag(__VA_ARGS__)
+#define etozi_flag(...) libcore_etozi_flag(__VA_ARGS__)
+#define etozu_flag(...) libcore_etozu_flag(__VA_ARGS__)
+#define etolli_flag(...) libcore_etolli_flag(__VA_ARGS__)
+#define etollu_flag(...) libcore_etollu_flag(__VA_ARGS__)
+#define etoli_flag(...) libcore_etoli_flag(__VA_ARGS__)
+#define etolu_flag(...) libcore_etolu_flag(__VA_ARGS__)
+#define etoi_flag(...) libcore_etoi_flag(__VA_ARGS__)
+#define etou_flag(...) libcore_etou_flag(__VA_ARGS__)
+#define etoji_arg(...) libcore_etoji_arg(__VA_ARGS__)
+#define etoju_arg(...) libcore_etoju_arg(__VA_ARGS__)
+#define etozi_arg(...) libcore_etozi_arg(__VA_ARGS__)
+#define etozu_arg(...) libcore_etozu_arg(__VA_ARGS__)
+#define etolli_arg(...) libcore_etolli_arg(__VA_ARGS__)
+#define etollu_arg(...) libcore_etollu_arg(__VA_ARGS__)
+#define etoli_arg(...) libcore_etoli_arg(__VA_ARGS__)
+#define etolu_arg(...) libcore_etolu_arg(__VA_ARGS__)
+#define etoi_arg(...) libcore_etoi_arg(__VA_ARGS__)
+#define etou_arg(...) libcore_etou_arg(__VA_ARGS__)
+#define tof libcore_tof
+#define tolf libcore_tolf
+#define tollf libcore_tollf
+#define entof_flag libcore_entof_flag
+#define entof_arg libcore_entof_arg
+#define entolf_flag libcore_entolf_flag
+#define entolf_arg libcore_entolf_arg
+#define entollf_flag libcore_entollf_flag
+#define entollf_arg libcore_entollf_arg
+#define etof_flag(...) libcore_etof_flag(__VA_ARGS__)
+#define etof_arg(...) libcore_etof_arg(__VA_ARGS__)
+#define etolf_flag(...) libcore_etolf_flag(__VA_ARGS__)
+#define etolf_arg(...) libcore_etolf_arg(__VA_ARGS__)
+#define etollf_flag(...) libcore_etollf_flag(__VA_ARGS__)
+#define etollf_arg(...) libcore_etollf_arg(__VA_ARGS__)
+
+
+static inline long int
+estrtol(const char *s, int base)
+{
+ int saved_errno = errno;
+ long long int ret;
+ char *end;
+ errno = 0;
+ ret = strtol(s, &end, base);
+ if (errno || *end) {
+ if (!errno)
+ errno = EINVAL;
+ eprintf("strtol %s:", s);
+ }
+ errno = saved_errno;
+ return ret;
+}
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..108056d
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,8 @@
+PREFIX = /usr/local
+MANPREFIX = $(PREFIX)/share/man
+
+VIDEO_GROUP = video
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS = -std=c99 -Wall -Wextra -O2 -g $(CPPFLAGS)
+LDFLAGS = -lcore
diff --git a/stopwatch.c b/stopwatch.c
new file mode 100644
index 0000000..19aba4f
--- /dev/null
+++ b/stopwatch.c
@@ -0,0 +1,230 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+USAGE("[-2]");
+
+static volatile sig_atomic_t caught_sigterm = 0;
+static volatile sig_atomic_t caught_sigio = 0;
+static struct itimerspec orig_time;
+static int quadsize = 0;
+
+static void
+sigterm(int signo)
+{
+ caught_sigterm = 1;
+ (void) signo;
+}
+
+static void
+sigio(int signo)
+{
+ caught_sigio = 1;
+ (void) signo;
+}
+
+static int
+display_stopwatch(int timerfd)
+{
+ uint64_t overrun = 0;
+ uintmax_t total_overrun = 0, h, m, s, d, first_overrun = 0;
+ int paused = 0, have_first = 0;
+ struct itimerspec old_time, zero_time;
+ ssize_t r;
+ char c;
+
+ memset(&zero_time, 0, sizeof(zero_time));
+
+ while (!caught_sigterm) {
+ if (caught_sigio) {
+ caught_sigio = 0;
+ for (;;) {
+ r = read(STDIN_FILENO, &c, 1);
+ if (r <= 0) {
+ if (!r)
+ goto out;
+ if (errno == EAGAIN)
+ break;
+ if (errno == EINTR)
+ continue;
+ goto fail;
+ }
+ if (c == 'q') {
+ goto out;
+ } else if (c == ' ') {
+ paused ^= 1;
+ if (paused) {
+ if (timerfd_settime(timerfd, 0, &zero_time, &old_time))
+ goto fail;
+ } else {
+ if (timerfd_settime(timerfd, 0, &old_time, NULL))
+ goto fail;
+ }
+ } else if (c == '\n') {
+ if (timerfd_settime(timerfd, 0, &orig_time, NULL))
+ goto fail;
+ d = total_overrun % 10;
+ s = total_overrun / 10 % 60;
+ m = total_overrun / 10 / 60 % 60;
+ h = total_overrun / 10 / 60 / 60;
+ if (quadsize)
+ printf("\033#5");
+ printf("%ju:%02ju:%02ju.%ju", h, m, s, d);
+ if (!have_first) {
+ have_first = 1;
+ first_overrun = total_overrun;
+ } else if (total_overrun > first_overrun) {
+ total_overrun = total_overrun - first_overrun;
+ d = total_overrun % 10;
+ s = total_overrun / 10 % 60;
+ m = total_overrun / 10 / 60 % 60;
+ h = total_overrun / 10 / 60 / 60;
+ printf(" \033[31m(+%ju:%02ju:%02ju.%ju)\033[m", h, m, s, d);
+ } else if (total_overrun < first_overrun) {
+ total_overrun = first_overrun - total_overrun;
+ d = total_overrun % 10;
+ s = total_overrun / 10 % 60;
+ m = total_overrun / 10 / 60 % 60;
+ h = total_overrun / 10 / 60 / 60;
+ printf(" \033[34m(-%ju:%02ju:%02ju.%ju)\033[m", h, m, s, d);
+ }
+ printf("\033[K\n");
+ total_overrun = 0;
+ }
+ }
+ }
+
+ d = total_overrun % 10;
+ s = total_overrun / 10 % 60;
+ m = total_overrun / 10 / 60 % 60;
+ h = total_overrun / 10 / 60 / 60;
+
+ if (!paused) {
+ if (quadsize) {
+ printf("\033""7\033#3%ju:%02ju:%02ju.%ju\033[K\n", h, m, s, d);
+ printf("\033#4%ju:%02ju:%02ju.%ju\033[K\033""8", h, m, s, d);
+ } else {
+ printf("\033""7%ju:%02ju:%02ju.%ju\033[K\033""8", h, m, s, d);
+ }
+ fflush(stdout);
+ } else {
+ if (quadsize) {
+ printf("\033""7\033#3\033[33m%ju:%02ju:%02ju.%ju (paused)\033[m\033[K\n", h, m, s, d);
+ printf("\033#4\033[33m%ju:%02ju:%02ju.%ju (paused)\033[m\033[K\033""8", h, m, s, d);
+ } else {
+ printf("\033""7\033[33m%ju:%02ju:%02ju.%ju (paused)\033[m\033[K\033""8", h, m, s, d);
+ }
+ fflush(stdout);
+ pause();
+ continue;
+ }
+
+ if (read(timerfd, &overrun, sizeof(overrun)) < 0) {
+ if (errno != EINTR)
+ goto fail;
+ } else {
+ total_overrun += (uintmax_t)overrun;
+ }
+ }
+
+out:
+ return 0;
+
+fail:
+ return -1;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int timerfd = -1, old_flags = -1, tcset = 0, old_sig = 0;
+ struct sigaction sigact;
+ int64_t owner_set = 0;
+ struct f_owner_ex old_owner, new_owner;
+ struct termios stty, saved_stty;
+
+ ARGBEGIN {
+ case '2':
+ quadsize = 1;
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc)
+ usage();
+
+ printf("\033[?25l\033[m");
+
+ orig_time.it_interval.tv_sec = 0;
+ orig_time.it_interval.tv_nsec = 100000000L;
+ orig_time.it_value.tv_sec = 0;
+ orig_time.it_value.tv_nsec = 100000000L;
+ timerfd = timerfd_create(CLOCK_BOOTTIME, 0);
+ if (timerfd < 0)
+ goto fail;
+ if (timerfd_settime(timerfd, 0, &orig_time, NULL))
+ goto fail;
+
+ memset(&sigact, 0, sizeof(sigact));
+
+ sigact.sa_handler = sigterm;
+ sigaction(SIGTERM, &sigact, NULL);
+ sigaction(SIGQUIT, &sigact, NULL);
+ sigaction(SIGINT, &sigact, NULL);
+
+ sigact.sa_handler = sigio;
+ sigaction(SIGIO, &sigact, NULL);
+ sigaction(SIGURG, &sigact, NULL);
+
+ if (fcntl(STDIN_FILENO, F_GETOWN_EX, &old_owner))
+ goto fail;
+ memset(&new_owner, 0, sizeof(new_owner));
+ new_owner.type = F_OWNER_PID;
+ new_owner.pid = getpid();
+ if (fcntl(STDIN_FILENO, F_SETOWN_EX, &new_owner))
+ goto fail;
+ owner_set = 1;
+ old_flags = fcntl(STDIN_FILENO, F_GETFL);
+ fcntl(STDIN_FILENO, F_SETFL, old_flags | FASYNC | O_NONBLOCK);
+ fcntl(STDIN_FILENO, F_GETSIG, &old_sig);
+ if (old_sig)
+ fcntl(STDIN_FILENO, F_SETSIG, 0);
+
+ if (!tcgetattr(STDIN_FILENO, &stty)) {
+ saved_stty = stty;
+ stty.c_lflag &= (tcflag_t)~(ECHO | ICANON);
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &stty);
+ tcset = 1;
+ }
+
+ if (display_stopwatch(timerfd))
+ goto fail;
+
+ if (quadsize)
+ printf("\n\n\033#5\033[?25h");
+ else
+ printf("\033[?25h\n");
+ fflush(stdout);
+ fcntl(STDIN_FILENO, F_SETOWN_EX, &old_owner);
+ fcntl(STDIN_FILENO, F_SETFL, old_flags);
+ fcntl(STDIN_FILENO, F_SETSIG, old_sig);
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_stty);
+ close(timerfd);
+ return 0;
+
+fail:
+ printf("\033[?25h\n");
+ perror(argv0 ? argv0 : "stopwatch");
+ fflush(stdout);
+ if (owner_set)
+ fcntl(STDIN_FILENO, F_SETOWN_EX, &old_owner);
+ if (old_flags != -1)
+ fcntl(STDIN_FILENO, F_SETFL, old_flags);
+ if (old_sig)
+ fcntl(STDIN_FILENO, F_SETSIG, old_sig);
+ if (tcset)
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_stty);
+ if (timerfd >= 0)
+ close(timerfd);
+ return 1;
+}