aboutsummaryrefslogblamecommitdiffstats
path: root/adjbacklight.c
blob: 6fa006649ccc9231ebb00d5cfe4242bbbcec45a4 (plain) (tree)



































































































































































































































                                                                                                                






































































































































































                                                                                                                                 




                            
                         

                              
                                      
                       



                                  


















                                          

                                                                                 

                                                                                         
                            

                           

                                                                      
                                  

         

                                                                                     
                







                                                                                            
                         
                                                                                             
                 




                                                                                             














                                                          
                                 












                                                                                             
/* 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);
	}
}


static void
check_permissions(void)
{
	long int ngroups_max;
	gid_t *groups;
	int ngroups;
	struct group *videogrp = getgrnam("video");

	if (!getuid())
		return;

	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));
		exit(1);
	} else if (videogrp) {
		ngroups_max = sysconf(_SC_NGROUPS_MAX) + 1;
		if (ngroups_max < 0 || ngroups_max > INT_MAX - 1) {
			fprintf(stderr, "%s: sysconf _SC_NGROUPS_MAX: %s\n", argv0, strerror(errno));
			exit(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));
			exit(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);
			exit(1);
		}
	}
}


static int
parse_set_argument(const char *str, char *set_prefix, double *set_value, const char **set_suffix)
{
	*set_prefix = '=';

	if (*str == '+' || *str == '-') {
		*set_prefix = *str;
		str++;
	}
	if (!isdigit(*str) && *str != '.')
		return -1;
	errno = 0;
	*set_value = strtod(str, (void *)set_suffix);
	if (errno || *set_value < 0) {
		fprintf(stderr, "%s: strtod %s: %s\n", argv0, str, strerror(errno));
		exit(1);
	}
	if (**set_suffix && strcmp(*set_suffix, "%") && strcmp(*set_suffix, "%%"))
		usage();
	if (*set_prefix == '-') {
		*set_value = -*set_value;
		*set_prefix = '+';
	}
	return 0;
}


static int
init_terminal(pid_t *pid, struct termios *saved_stty)
{
	struct termios stty;
	struct winsize win;
	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));
		exit(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));
		exit(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));
		exit(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);
		return -1;
	}

	return 0;
}


static int
update(int argc, char *argv[], int all, int get, char *set, int set_prefix, double set_value, const char *set_suffix)
{
	DIR *dir;
	struct dirent *ent;
	int any = argc;

	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);
	}

	return any;
}


int
main(int argc, char *argv[])
{
	int all = 0;
	int get = 0;
	char *set = NULL;
	char set_prefix = '=';
	double set_value = 0;
	const char *set_suffix = NULL;
	pid_t pid = -1;
	struct termios saved_stty;
	int isinteractive = 0;
	size_t size = 0;
	ssize_t len;

	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 && parse_set_argument(set, &set_prefix, &set_value, &set_suffix))
		usage();

	/* Check permissions (important because the program is installed with set-uid) */
	check_permissions();

	if (!get && !set) {
		isinteractive = isatty(STDIN_FILENO);
		if (isinteractive && init_terminal(&pid, &saved_stty))
			goto done;
	}

	if (get || set || isinteractive) {
		update(argc, argv, all, get, set, set_prefix, set_value, set_suffix);
	} else {
		while ((len = getline(&set, &size, stdin)) > 0) {
			if (len && set[len - 1] == '\n')
				set[--len] = '\0';
			if (!len)
				continue;
			if (parse_set_argument(set, &set_prefix, &set_value, &set_suffix)) {
				fprintf(stderr, "%s: invalid input: %s\n", argv0, set);
				return 0;
			}
			update(argc, argv, all, get, set, set_prefix, set_value, set_suffix);
		}
		if (ferror(stdin)) {
			fprintf(stderr, "%s: getline <stdin>: %s\n", argv0, strerror(errno));
			return 1;
		}
		free(set);
	}

	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 (isinteractive && 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;
}