/* 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; again: if (max_brightness) { if (adjustment >= max_brightness - brightness) brightness = max_brightness; else brightness += adjustment; } else { if (brightness < adjustment) brightness = 0; else brightness -= adjustment; } len = sprintf(buf, "%ju\n", brightness); writefile(path, buf, (size_t)len); if (readintfile(path, &result)) { if (result == *brightnessp && brightness != max_brightness) goto again; *brightnessp = result; /* TODO publish update on bus */ } } 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 (;;) { /* TODO add support for adjusting with the rat */ 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 = ereallocn(devs, ndevs + 1, sizeof(*devs), 0); 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; }