diff options
Diffstat (limited to 'adjbacklight.c')
-rw-r--r-- | adjbacklight.c | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/adjbacklight.c b/adjbacklight.c new file mode 100644 index 0000000..9fe68c1 --- /dev/null +++ b/adjbacklight.c @@ -0,0 +1,425 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <alloca.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <grp.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "arg.h" + + +#ifndef BACKLIGHT_DIR +# define BACKLIGHT_DIR "/sys/class/backlight" +#endif + + + +char *argv0; +static double brightness = 0; +static int nbrightness = 0; +static int cols = 80; +static char *space; +static char *line; + + + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-g | -s [+|-]level[%%|%%%%]] [-a | device ...]\n", argv0); + exit(1); +} + + +static long int +readfile(const char *path) +{ + int fd; + char buf[64]; + ssize_t i, n; + long int value = 0; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + + while ((n = read(fd, buf, sizeof(buf))) > 0) { + for (i = 0; i < n; i++) { + if (isdigit(buf[i])) + value = value * 10 + (buf[i] - '0'); + else if (buf[i] != '\n') + goto out; + } + } + +out: + close(fd); + return n ? -1 : value; +} + + +static double +getbrightness(const char *device, long int *out_cur, long int *out_max) +{ + size_t n = sizeof("//max_brightness") - 1; + char *path; + long int max, cur; + + n += strlen(BACKLIGHT_DIR); + n += strlen(device); + path = alloca(n + 1); + + if (sprintf(path, "%s/%s/max_brightness", BACKLIGHT_DIR, device) != (int)n) + abort(); + max = readfile(path); + if (max < 1) + return -1; + if (out_max) + *out_max = max; + + if (sprintf(path, "%s/%s/brightness", BACKLIGHT_DIR, device) != (int)n - 4) + abort(); + cur = readfile(path); + if (cur < 0) + return -1; + if (out_cur) + *out_cur = cur; + + return (double)cur / (double)max; +} + + +static int +setbrightness(const char *device, long int value) +{ + size_t p = 0, n = sizeof("//brightness") - 1; + char *path, buf[128]; + ssize_t r = 0; + int fd; + + n += strlen(BACKLIGHT_DIR); + n += strlen(device); + path = alloca(n + 1); + + if (sprintf(path, "%s/%s/brightness", BACKLIGHT_DIR, device) != (int)n) + abort(); + if (sprintf(buf, "%li\n", value) < 0) + abort(); + + fd = open(path, O_WRONLY); + if (fd < 0) + return -1; + + for (n = strlen(buf); p < n; p += (size_t)r) + if ((r = write(fd, &buf[p], n - p)) < 0) + break; + + close(fd); + return r < 0 ? -1 : 0; +} + + +static int +adjbrightness(const char *device, double pcur, long int cur, long int max, double adj, int inc, const char *suf) +{ + switch (strlen(suf)) { + case 2: + pcur = adj * pcur / 100 + pcur * inc; + cur = (long int)(pcur * (double)max + 0.5); + break; + case 1: + pcur = adj / 100 + pcur * inc; + cur = (long int)(pcur * (double)max + 0.5); + break; + case 0: + cur = (long int)adj + cur * inc; + break; + default: + abort(); + } + + cur = cur < 0 ? 0 : cur < max ? cur : max; + return setbrightness(device, cur); +} + + +static void +bars(long int max, long int init, long int cur) +{ + long int mid = (long int)((double)cur * (double)(cols - 2) / (double)max + 0.5); + + printf("\033[%iD\033[6A", cols); + printf("\033[2K┌%s┐\n", line); + space[mid] = '\0'; + printf("\033[2K│\033[47m%s\033[49m%s│\n", space, &space[mid + 1]); + space[mid] = ' '; + printf("\033[2K└%s┘\n", line); + printf("\033[2KMaximum brightness: %li\n", max); + printf("\033[2KInitial brightness: %li\n", init); + printf("\033[2KCurrent brightness: %li\n", cur); + + fflush(stdout); +} + + +static void +interactive(const char *device, long int cur, long int max) +{ + long int step, init; + int c; + + step = max / 200; + step = step ? step : 1; + init = cur; + + printf("\n\n\n\n\n\n"); + bars(max, init, cur); + while ((c = getchar()) != -1) { + switch (c) { + case 'q': + case '\n': + case 4: + printf("\n"); + return; + case 'A': + case 'C': + cur += step << 1; + /* fall through */ + case 'B': + case 'D': + cur -= step; + cur = cur < 0 ? 0 : cur < max ? cur : max; + adjbrightness(device, 0, 0, max, (double)cur, 0, ""); + bars(max, init, cur); + } + } +} + + +static void +handle_device(const char *device, int get, int set, double adj, int inc, const char *suf) +{ + double value; + long int cur, max; + + value = getbrightness(device, &cur, &max); + + if (get) { + if (value >= 0) { + brightness += value; + nbrightness++; + } + } else if (set) { + adjbrightness(device, value, cur, max, adj, inc, suf); + } else { + interactive(device, cur, max); + } +} + + +int +main(int argc, char *argv[]) +{ + int all = 0; + int get = 0; + char* set = NULL; + char set_prefix = '='; + double set_value = 0; + char* set_suffix = NULL; + struct winsize win; + struct termios saved_stty; + struct termios stty; + pid_t pid = -1; + DIR *dir; + struct dirent *ent; + int any = 0; + + ARGBEGIN { + case 'a': + all = 1; + break; + case 'g': + get = 1; + break; + case 's': + set = EARGF(usage()); + break; + default: + usage(); + } ARGEND; + + if ((get && set) || (argc && all)) + usage(); + + /* Parse -s argument */ + if (set) { + if (*set == '+' || *set == '-') { + set_prefix = *set; + set++; + } + if (!isdigit(*set) && *set != '.') + usage(); + errno = 0; + set_value = strtod(set, &set_suffix); + if (errno || set_value < 0) { + fprintf(stderr, "%s: strtod %s: %s\n", argv0, set, strerror(errno)); + return 1; + } + if (*set_suffix && strcmp(set_suffix, "%") && strcmp(set_suffix, "%%")) + usage(); + if (set_prefix == '-') { + set_value = -set_value; + set_prefix = '+'; + } + } + + /* Check permissions (important because the program is installed with set-uid) */ + if (getuid()) { + struct group *videogrp = getgrnam("video"); + if (!videogrp && errno && errno != ENOENT && errno != ESRCH && errno != EPERM) { + /* Note, glibc sets errno to EIO if the group does not exist, + * this is the not the specified behavour by either POSIX or + * glibc, and it would be a security issue to treat it as OK. + * Additionally, EBADF is not treated as OK. */ + fprintf(stderr, "%s: getgrnam video: %s\n", argv0, strerror(errno)); + return 1; + } else if (videogrp) { + long ngroups_max = sysconf(_SC_NGROUPS_MAX) + 1; + gid_t *groups; + int ngroups; + if (ngroups_max < 0 || ngroups_max > INT_MAX - 1) { + fprintf(stderr, "%s: sysconf _SC_NGROUPS_MAX: %s\n", argv0, strerror(errno)); + return 1; + } + groups = alloca((size_t)ngroups_max * sizeof(*groups)); + ngroups = getgroups((int)ngroups_max, groups); + if (ngroups < 0) { + fprintf(stderr, "%s: getgroups: %s\n", argv0, strerror(errno)); + return 1; + } + while (ngroups--) + if (groups[ngroups] == videogrp->gr_gid) + break; + if (ngroups < 0) { + fprintf(stderr, "%s: only root and members of the group 'video' may run this command\n", argv0); + return 1; + } + } + } + + if (!get && !set) { + int i; + printf("\n\n"); + printf("If the program is abnormally aborted the may be some residual\n"); + printf("effects on the terminal. the following commands should reset it:\n"); + printf("\n"); + printf(" stty sane\n"); + printf(" printf '\\ec'\n"); + printf("\n"); + printf("\n\n\n\n"); + + /* Get the size of the terminal */ + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == -1) + fprintf(stderr, "%s: ioctl <stdout> TIOCGWINSZ: %s\n", argv0, strerror(errno)); + else + cols = win.ws_col; + + line = malloc((size_t)cols * 3); + space = malloc((size_t)cols); + if (!line || !space) { + fprintf(stderr, "%s: malloc: %s\n", argv0, strerror(ENOMEM)); + return 1; + } + for (i = 0; i < cols; i++) { + line[i * 3 + 0] = (char)(0xE2); + line[i * 3 + 1] = (char)(0x94); + line[i * 3 + 2] = (char)(0x80); + } + memset(space, ' ', (size_t)cols); + space[cols - 1] = '\0'; + line[(cols - 2) * 3] = '\0'; + + /* stty -icanon -echo */ + if (tcgetattr(STDIN_FILENO, &stty)) { + fprintf(stderr, "%s: tcgetattr <stdin>: %s\n", argv0, strerror(errno)); + return 1; + } + saved_stty = stty; + stty.c_lflag &= (tcflag_t)~(ICANON | ECHO); + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &stty)) { + fprintf(stderr, "%s: tcsetattr <stdin>: %s\n", argv0, strerror(errno)); + return 1; + } + + /* Hide cursor */ + printf("%s", "\033[?25l"); + fflush(stdout); + + /* Fork to diminish risk of unclean exit */ + pid = fork(); + if (pid == (pid_t)-1) { + fprintf(stderr, "%s: fork: %s\n", argv0, strerror(errno)); + } else if (pid) { + waitpid(pid, NULL, 0); + goto done; + } + } + + if (argc) { + for (; *argv; argv++) { + if (strchr(*argv, '/')) + *argv = strrchr(*argv, '/'); + handle_device(*argv, get, !!set, set_value, set_prefix == '+', set_suffix); + } + } else { + if ((dir = opendir(BACKLIGHT_DIR))) { + while ((ent = readdir(dir))) { + if (all || strstr(ent->d_name, "acpi_video") == ent->d_name) { + if (*ent->d_name && *ent->d_name != '.') { + handle_device(ent->d_name, get, !!set, set_value, set_prefix == '+', set_suffix); + any = 1; + } + } + } + closedir(dir); + } + if (!any) + fprintf(stderr, "%s: cannot find any backlight devices\n", argv0); + } + + if (get) { + if (nbrightness) { + brightness *= 100; + brightness /= (double)nbrightness; + printf("%.2lf%%\n", brightness); + fflush(stdout); + } else { + printf("%s\n", "100.00%"); + fflush(stdout); + } + } + +done: + if (!get && !set && pid) { + /* Show cursor */ + printf("%s", "\033[?25h"); + fflush(stdout); + + /* `stty icanon echo` */ + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_stty)) { + fprintf(stderr, "%s: tcsetattr <stdin>: %s\n", argv0, strerror(errno)); + return 1; + } + } + + return 0; +} |