From 71461330a7d343a5bbdd6c2c96d4652f44852030 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Thu, 25 Mar 2021 13:20:15 +0100 Subject: First commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- .gitignore | 5 + LICENSE | 15 ++ Makefile | 54 +++++++ backlight.c | 507 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ common.c | 2 + common.h | 131 ++++++++++++++++ config.mk | 8 + stopwatch.c | 230 +++++++++++++++++++++++++++ 8 files changed, 952 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 backlight.c create mode 100644 common.c create mode 100644 common.h create mode 100644 config.mk create mode 100644 stopwatch.c 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 + +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 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 :"); + 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 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 &(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 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 F_SETOWN_EX {.type=F_OWNER_PID, .pid=}:"); + 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 +#include +#include + +#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; +} -- cgit v1.2.3-70-g09d2