diff options
| -rw-r--r-- | .gitignore | 5 | ||||
| -rw-r--r-- | LICENSE | 15 | ||||
| -rw-r--r-- | Makefile | 54 | ||||
| -rw-r--r-- | backlight.c | 507 | ||||
| -rw-r--r-- | common.c | 2 | ||||
| -rw-r--r-- | common.h | 131 | ||||
| -rw-r--r-- | config.mk | 8 | ||||
| -rw-r--r-- | stopwatch.c | 230 |
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 @@ -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; +} |
