668 lines
15 KiB
C
668 lines
15 KiB
C
#include <sys/types.h>
|
|
#include <sys/utsname.h>
|
|
#include <sys/stat.h>
|
|
#include <dirent.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <fnmatch.h>
|
|
#include <limits.h>
|
|
#include <getopt.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#ifdef ENABLE_SYSTEMD
|
|
# include <systemd/sd-bus.h>
|
|
#endif
|
|
|
|
static char *path = "/sys/class";
|
|
static char *classes[] = { "backlight", "leds", NULL };
|
|
|
|
static char *run_dir = "/tmp/brightnessctl";
|
|
|
|
struct value;
|
|
struct device;
|
|
|
|
enum operation;
|
|
|
|
static void fail(char *, ...);
|
|
static void usage(void);
|
|
#define cat_with(...) _cat_with(__VA_ARGS__, NULL)
|
|
static char *_cat_with(char, ...);
|
|
static char *dir_child(char *, char*);
|
|
static char *device_path(struct device *);
|
|
static char *class_path(char *);
|
|
static unsigned int calc_value(struct device *, struct value *);
|
|
static int apply_operation(struct device *, enum operation, struct value *);
|
|
static bool parse_value(struct value *, char *);
|
|
static bool do_write_device(struct device *);
|
|
static bool read_device(struct device *, char *, char *);
|
|
static int read_class(struct device **, char *);
|
|
static int read_devices(struct device **);
|
|
static void print_device(struct device *);
|
|
static void list_devices(struct device **);
|
|
static struct device *find_device(struct device **, char *);
|
|
static bool save_device_data(struct device *);
|
|
static bool restore_device_data(struct device *);
|
|
static bool ensure_dir(char *);
|
|
static bool ensure_dev_dir(struct device *);
|
|
#define ensure_run_dir() ensure_dir(run_dir)
|
|
|
|
#ifdef ENABLE_SYSTEMD
|
|
static bool logind_set_brightness(struct device *);
|
|
#endif
|
|
|
|
struct device {
|
|
char *class;
|
|
char *id;
|
|
unsigned int curr_brightness;
|
|
unsigned int max_brightness;
|
|
};
|
|
|
|
enum value_type { ABSOLUTE, RELATIVE };
|
|
enum delta_type { DIRECT, DELTA };
|
|
enum sign { PLUS, MINUS };
|
|
|
|
struct value {
|
|
unsigned long val;
|
|
enum value_type v_type;
|
|
enum delta_type d_type;
|
|
enum sign sign;
|
|
};
|
|
|
|
enum operation { INFO, GET, MAX, SET };
|
|
|
|
struct params {
|
|
char *class;
|
|
char *device;
|
|
struct value val;
|
|
long min;
|
|
enum operation operation;
|
|
bool quiet;
|
|
bool list;
|
|
bool pretend;
|
|
bool mach;
|
|
bool save;
|
|
bool restore;
|
|
float exponent;
|
|
};
|
|
|
|
static struct params p;
|
|
|
|
static const struct option options[] = {
|
|
{"class", required_argument, NULL, 'c'},
|
|
{"device", required_argument, NULL, 'd'},
|
|
{"help", no_argument, NULL, 'h'},
|
|
{"list", no_argument, NULL, 'l'},
|
|
{"machine-readable", no_argument, NULL, 'm'},
|
|
{"min-value", optional_argument, NULL, 'n'},
|
|
{"exponent", optional_argument, NULL, 'e'},
|
|
{"quiet", no_argument, NULL, 'q'},
|
|
{"pretend", no_argument, NULL, 'p'},
|
|
{"restore", no_argument, NULL, 'r'},
|
|
{"save", no_argument, NULL, 's'},
|
|
{"version", no_argument, NULL, 'V'},
|
|
{NULL,}
|
|
};
|
|
|
|
static bool (*write_device)(struct device *) = do_write_device;
|
|
|
|
int main(int argc, char **argv) {
|
|
struct device *devs[255];
|
|
struct device *dev;
|
|
struct utsname name;
|
|
char *dev_name, *file_path, *sys_run_dir;
|
|
int n, c, phelp = 0;
|
|
if (uname(&name))
|
|
fail("Unable to determine current OS. Exiting!\n");
|
|
if (strcmp(name.sysname, "Linux"))
|
|
fail("This program only supports Linux.\n");
|
|
p.exponent = 1;
|
|
while (1) {
|
|
if ((c = getopt_long(argc, argv, "lqpmn::e::srhVc:d:", options, NULL)) < 0)
|
|
break;
|
|
switch (c) {
|
|
case 'l':
|
|
p.list = true;
|
|
break;
|
|
case 'q':
|
|
p.quiet = true;
|
|
break;
|
|
case 'p':
|
|
p.pretend = true;
|
|
break;
|
|
case 's':
|
|
p.save = true;
|
|
break;
|
|
case 'r':
|
|
p.restore = true;
|
|
break;
|
|
case 'm':
|
|
p.mach = true;
|
|
break;
|
|
case 'n':
|
|
if (optarg)
|
|
p.min = atol(optarg);
|
|
else
|
|
p.min = 1;
|
|
break;
|
|
case 'e':
|
|
if (optarg)
|
|
p.exponent = atof(optarg);
|
|
else
|
|
p.exponent = 4;
|
|
break;
|
|
case 'h':
|
|
usage();
|
|
exit(EXIT_SUCCESS);
|
|
case 'c':
|
|
p.class = strdup(optarg);
|
|
break;
|
|
case 'd':
|
|
p.device = strdup(optarg);
|
|
break;
|
|
case 'V':
|
|
printf("%s\n", VERSION);
|
|
exit(0);
|
|
break;
|
|
default:
|
|
phelp++;
|
|
}
|
|
}
|
|
if (phelp) {
|
|
usage();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
if (p.class) {
|
|
if (!(n = read_class(devs, p.class)))
|
|
fail("Failed to read any devices of class '%s'.\n", p.class);
|
|
} else {
|
|
if (!(n = read_devices(devs)))
|
|
fail("Failed to read any devices.\n");
|
|
}
|
|
devs[n] = NULL;
|
|
if (p.list) {
|
|
list_devices(devs);
|
|
return 0;
|
|
}
|
|
dev_name = p.device;
|
|
if (!dev_name)
|
|
dev_name = devs[0]->id;
|
|
if (argc == 0)
|
|
p.operation = INFO;
|
|
else switch (argv[0][0]) {
|
|
case 'm':
|
|
p.operation = MAX; break;
|
|
case 's':
|
|
p.operation = SET; break;
|
|
case 'g':
|
|
p.operation = GET; break;
|
|
default:
|
|
case 'i':
|
|
p.operation = INFO; break;
|
|
}
|
|
argc--;
|
|
argv++;
|
|
if (p.operation == SET && argc == 0)
|
|
fail("You need to provide a value to set.\n");
|
|
if (p.operation == SET && !parse_value(&p.val, argv[0]))
|
|
fail("Invalid value given");
|
|
if (!(dev = find_device(devs, dev_name)))
|
|
fail("Device '%s' not found.\n", dev_name);
|
|
if ((p.operation == SET || p.restore) && !p.pretend && geteuid()) {
|
|
errno = 0;
|
|
file_path = cat_with('/', path, dev->class, dev->id, "brightness");
|
|
if (access(file_path, W_OK)) {
|
|
#ifdef ENABLE_SYSTEMD
|
|
write_device = logind_set_brightness;
|
|
#else
|
|
perror("Can't modify brightness");
|
|
fail("\nYou should run this program with root privileges.\n"
|
|
"Alternatively, get write permissions for device files.\n");
|
|
#endif
|
|
}
|
|
free(file_path);
|
|
}
|
|
if ((sys_run_dir = getenv("XDG_RUNTIME_DIR")))
|
|
run_dir = dir_child(sys_run_dir, "brightnessctl");
|
|
if (p.save)
|
|
if (!save_device_data(dev))
|
|
fprintf(stderr, "Could not save data for device '%s'.\n", dev->id);
|
|
if (p.restore) {
|
|
if (restore_device_data(dev))
|
|
write_device(dev);
|
|
}
|
|
return apply_operation(dev, p.operation, &p.val);
|
|
}
|
|
|
|
int apply_operation(struct device *dev, enum operation operation, struct value *val) {
|
|
switch (operation) {
|
|
case INFO:
|
|
print_device(dev);
|
|
return 0;
|
|
case GET:
|
|
fprintf(stdout, "%u\n", dev->curr_brightness);
|
|
return 0;
|
|
case MAX:
|
|
fprintf(stdout, "%u\n", dev->max_brightness);
|
|
return 0;
|
|
case SET:
|
|
dev->curr_brightness = calc_value(dev, val);
|
|
if (!p.pretend)
|
|
if (!write_device(dev))
|
|
goto fail;
|
|
if (!p.quiet) {
|
|
if (!p.mach)
|
|
fprintf(stdout, "Updated device '%s':\n", dev->id);
|
|
print_device(dev);
|
|
}
|
|
return 0;
|
|
/* FALLTHRU */
|
|
fail:
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
bool parse_value(struct value *val, char *str) {
|
|
long n;
|
|
char c;
|
|
char *buf;
|
|
errno = 0;
|
|
val->v_type = ABSOLUTE;
|
|
val->d_type = DIRECT;
|
|
val->sign = PLUS;
|
|
if (!str || !*str)
|
|
return false;
|
|
if (*str == '+' || *str == '-') {
|
|
val->sign = *str == '+' ? PLUS : MINUS;
|
|
val->d_type = DELTA;
|
|
str++;
|
|
}
|
|
n = strtol(str, &buf, 10);
|
|
if (errno || buf == str)
|
|
return false;
|
|
val->val = labs(n) % LONG_MAX;
|
|
while ((c = *(buf++))) switch(c) {
|
|
case '+':
|
|
val->sign = PLUS;
|
|
val->d_type = DELTA;
|
|
break;
|
|
case '-':
|
|
val->sign = MINUS;
|
|
val->d_type = DELTA;
|
|
break;
|
|
case '%':
|
|
val->v_type = RELATIVE;
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
struct device *find_device(struct device **devs, char *name) {
|
|
struct device *dev;
|
|
while ((dev = *(devs++)))
|
|
if (!fnmatch(name, dev->id, 0))
|
|
return dev;
|
|
return NULL;
|
|
}
|
|
|
|
void list_devices(struct device **devs) {
|
|
struct device *dev;
|
|
if (!p.mach)
|
|
fprintf(stdout, "Available devices:\n");
|
|
while ((dev = *(devs++)))
|
|
print_device(dev);
|
|
}
|
|
|
|
float val_to_percent(float val, struct device *d, bool rnd) {
|
|
if (val < 0)
|
|
return 0;
|
|
float ret = powf(val / d->max_brightness, 1.0f / p.exponent) * 100;
|
|
return rnd ? roundf(ret) : ret;
|
|
}
|
|
|
|
unsigned long percent_to_val(float percent, struct device *d) {
|
|
return roundf(powf(percent / 100, p.exponent) * d->max_brightness);
|
|
}
|
|
|
|
void print_device(struct device *dev) {
|
|
char *format = p.mach ? "%s,%s,%d,%d%%,%d\n" :
|
|
"Device '%s' of class '%s':\n\tCurrent brightness: %d (%d%%)\n\tMax brightness: %d\n\n";
|
|
fprintf(stdout, format,
|
|
dev->id, dev->class,
|
|
dev->curr_brightness,
|
|
(int) val_to_percent(dev->curr_brightness, dev, true),
|
|
dev->max_brightness);
|
|
}
|
|
|
|
unsigned int calc_value(struct device *d, struct value *val) {
|
|
long new = d->curr_brightness;
|
|
if (val->d_type == DIRECT) {
|
|
new = val->v_type == ABSOLUTE ? val->val : percent_to_val(val->val, d);
|
|
goto apply;
|
|
}
|
|
long mod = val->val;
|
|
if (val->sign == MINUS)
|
|
mod *= -1;
|
|
if (val->v_type == RELATIVE) {
|
|
mod = percent_to_val(val_to_percent(d->curr_brightness, d, false) + mod, d) - d->curr_brightness;
|
|
if (val->val != 0 && mod == 0)
|
|
mod = val->sign == PLUS ? 1 : -1;
|
|
}
|
|
new += mod;
|
|
apply:
|
|
if (new < p.min)
|
|
new = p.min;
|
|
if (new < 0)
|
|
new = 0;
|
|
if (new > d->max_brightness)
|
|
new = d->max_brightness;
|
|
return new;
|
|
}
|
|
|
|
#ifdef ENABLE_SYSTEMD
|
|
|
|
bool logind_set_brightness(struct device *d) {
|
|
sd_bus *bus = NULL;
|
|
int r = sd_bus_default_system(&bus);
|
|
if (r < 0) {
|
|
fprintf(stderr, "Can't connect to system bus: %s\n", strerror(-r));
|
|
return false;
|
|
}
|
|
|
|
r = sd_bus_call_method(bus,
|
|
"org.freedesktop.login1",
|
|
"/org/freedesktop/login1/session/auto",
|
|
"org.freedesktop.login1.Session",
|
|
"SetBrightness",
|
|
NULL,
|
|
NULL,
|
|
"ssu",
|
|
d->class,
|
|
d->id,
|
|
d->curr_brightness);
|
|
if (r < 0)
|
|
fprintf(stderr, "Failed to set brightness: %s\n", strerror(-r));
|
|
|
|
sd_bus_unref(bus);
|
|
|
|
return r >= 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
bool do_write_device(struct device *d) {
|
|
FILE *f;
|
|
char c[16];
|
|
size_t s = sprintf(c, "%u", d->curr_brightness);
|
|
errno = 0;
|
|
if (s <= 0) {
|
|
errno = EINVAL;
|
|
goto fail;
|
|
}
|
|
if ((f = fopen(dir_child(device_path(d), "brightness"), "w"))) {
|
|
if (fwrite(c, 1, s, f) < s)
|
|
goto close;
|
|
} else
|
|
goto fail;
|
|
errno = 0;
|
|
close:
|
|
fclose(f);
|
|
fail:
|
|
if (errno)
|
|
perror("Error writing device");
|
|
return !errno;
|
|
}
|
|
|
|
bool read_device(struct device *d, char *class, char *id) {
|
|
DIR *dirp;
|
|
FILE *f;
|
|
char *dev_path = NULL;
|
|
char *ent_path;
|
|
int error = 0;
|
|
struct dirent *ent;
|
|
bool cur;
|
|
d->class = strdup(class);
|
|
d->id = strdup(id);
|
|
dev_path = device_path(d);
|
|
if (!(dirp = opendir(dev_path)))
|
|
goto dfail;
|
|
while ((ent = readdir(dirp))) {
|
|
if (!strcmp(ent->d_name, ".") && !strcmp(ent->d_name, ".."))
|
|
continue;
|
|
if ((cur = !strcmp(ent->d_name, "brightness")) ||
|
|
!strcmp(ent->d_name, "max_brightness")) {
|
|
if (!(f = fopen(ent_path = dir_child(dev_path, ent->d_name), "r")))
|
|
goto fail;
|
|
clearerr(f);
|
|
if (fscanf(f, "%u", cur ? &d->curr_brightness : &d->max_brightness) == EOF) {
|
|
fprintf(stderr, "End-of-file reading %s of device '%s'.",
|
|
cur ? "brightness" : "max brightness", d->id);
|
|
error++;
|
|
} else if (ferror(f)) {
|
|
fprintf(stderr, "Error reading %s of device '%s': %s.",
|
|
cur ? "brightness" : "max brightness", d->id, strerror(errno));
|
|
error++;
|
|
}
|
|
fclose(f);
|
|
free(ent_path);
|
|
ent_path = NULL;
|
|
}
|
|
}
|
|
errno = 0;
|
|
fail:
|
|
closedir(dirp);
|
|
dfail:
|
|
free(dev_path);
|
|
free(ent_path);
|
|
if (errno) {
|
|
perror("Error reading device");
|
|
error++;
|
|
}
|
|
return !error;
|
|
}
|
|
|
|
int read_class(struct device **devs, char *class) {
|
|
DIR *dirp;
|
|
struct dirent *ent;
|
|
struct device *dev;
|
|
char *c_path;
|
|
int cnt = 0;
|
|
dirp = opendir(c_path = class_path(class));
|
|
if (!dirp)
|
|
return 0;
|
|
while ((ent = readdir(dirp))) {
|
|
if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
|
|
continue;
|
|
dev = malloc(sizeof(struct device));
|
|
if (!read_device(dev, class, ent->d_name)) {
|
|
free(dev);
|
|
continue;
|
|
}
|
|
devs[cnt++] = dev;
|
|
}
|
|
closedir(dirp);
|
|
free(c_path);
|
|
return cnt;
|
|
}
|
|
|
|
int read_devices(struct device **devs) {
|
|
size_t n = 0;
|
|
char *class;
|
|
int cnt = 0;
|
|
while ((class = classes[n++]))
|
|
cnt += read_class(devs + cnt, class);
|
|
return cnt;
|
|
}
|
|
|
|
bool save_device_data(struct device *dev) {
|
|
char c[16];
|
|
size_t s = sprintf(c, "%u", dev->curr_brightness);
|
|
char *d_path = cat_with('/', run_dir, dev->class, dev->id);
|
|
FILE *fp;
|
|
mode_t old = 0;
|
|
int error = 0;
|
|
errno = 0;
|
|
if (s <= 0) {
|
|
fprintf(stderr, "Error converting device data.");
|
|
error++;
|
|
goto fail;
|
|
}
|
|
if (!ensure_dev_dir(dev))
|
|
goto fail;
|
|
old = umask(0);
|
|
fp = fopen(d_path, "w");
|
|
umask(old);
|
|
if (!fp)
|
|
goto fail;
|
|
if (fwrite(c, 1, s, fp) < s) {
|
|
fprintf(stderr, "Error writing to '%s'.\n", d_path);
|
|
error++;
|
|
}
|
|
fclose(fp);
|
|
fail:
|
|
free(d_path);
|
|
if (errno) {
|
|
perror("Error saving device data");
|
|
error++;
|
|
}
|
|
return !error;
|
|
}
|
|
|
|
bool restore_device_data(struct device *dev) {
|
|
char buf[16];
|
|
char *filename = cat_with('/', run_dir, dev->class, dev->id);
|
|
char *end;
|
|
FILE *fp;
|
|
memset(buf, 0, 16);
|
|
errno = 0;
|
|
if (!(fp = fopen(filename, "r")))
|
|
goto fail;
|
|
if (!fread(buf, 1, 15, fp))
|
|
goto rfail;
|
|
dev->curr_brightness = strtol(buf, &end, 10);
|
|
if (end == buf)
|
|
errno = EINVAL;
|
|
rfail:
|
|
fclose(fp);
|
|
fail:
|
|
free(filename);
|
|
if (errno) {
|
|
perror("Error restoring device data");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ensure_dir(char *dir) {
|
|
struct stat sb;
|
|
if (stat(dir, &sb)) {
|
|
if (errno != ENOENT)
|
|
return false;
|
|
errno = 0;
|
|
if (mkdir(dir, 0777)) {
|
|
return false;
|
|
}
|
|
if (stat(dir, &sb))
|
|
return false;
|
|
}
|
|
if (!S_ISDIR(sb.st_mode)) {
|
|
errno = ENOTDIR;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ensure_dev_dir(struct device *dev) {
|
|
char *cpath;
|
|
bool ret;
|
|
if (!ensure_run_dir())
|
|
return false;
|
|
cpath = dir_child(run_dir, dev->class);
|
|
ret = ensure_dir(cpath);
|
|
free(cpath);
|
|
return ret;
|
|
}
|
|
|
|
char *_cat_with(char c, ...) {
|
|
size_t size = 32;
|
|
size_t length = 0;
|
|
char *buf = calloc(1, size + 1);
|
|
char *curr;
|
|
char split[2] = {c, '\0'};
|
|
va_list va;
|
|
va_start(va, c);
|
|
curr = va_arg(va, char *);
|
|
while (curr) {
|
|
length += strlen(curr);
|
|
while (length + 2 > size)
|
|
buf = realloc(buf, size *= 2);
|
|
strcat(buf, curr);
|
|
if ((curr = va_arg(va, char*))) {
|
|
length++;
|
|
strcat(buf, split);
|
|
}
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
char *dir_child(char *parent, char *child) {
|
|
return cat_with('/', parent, child);
|
|
}
|
|
|
|
char *device_path(struct device *dev) {
|
|
return cat_with('/', path, dev->class, dev->id);
|
|
}
|
|
|
|
char *class_path(char *class) {
|
|
return dir_child(path, class);
|
|
}
|
|
|
|
void fail(char *err_msg, ...) {
|
|
va_list va;
|
|
va_start(va, err_msg);
|
|
vfprintf(stderr, err_msg, va);
|
|
va_end(va);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
void usage() {
|
|
fprintf(stderr, "brightnessctl %s - read and control device brightness.\n\n", VERSION);
|
|
fprintf(stderr,
|
|
"Usage: brightnessctl [options] [operation] [value]\n\
|
|
\n\
|
|
Options:\n\
|
|
-l, --list\t\t\tlist devices with available brightness controls.\n\
|
|
-q, --quiet\t\t\tsuppress output.\n\
|
|
-p, --pretend\t\t\tdo not perform write operations.\n\
|
|
-m, --machine-readable\tproduce machine-readable output.\n\
|
|
-n, --min-value\t\tset minimum brightness, defaults to 1.\n\
|
|
-e, --exponent[=K]\t\tchanges percentage curve to exponential.\n\
|
|
-s, --save\t\t\tsave previous state in a temporary file.\n\
|
|
-r, --restore\t\t\trestore previous saved state.\n\
|
|
-h, --help\t\t\tprint this help.\n\
|
|
-d, --device=DEVICE\t\tspecify device name (can be a wildcard).\n\
|
|
-c, --class=CLASS\t\tspecify device class.\n\
|
|
-V, --version\t\t\tprint version and exit.\n\
|
|
\n\
|
|
Operations:\n\
|
|
i, info\t\t\tget device info.\n\
|
|
g, get\t\t\tget current brightness of the device.\n\
|
|
m, max\t\t\tget maximum brightness of the device.\n\
|
|
s, set VALUE\t\t\tset brightness of the device.\n\
|
|
\n\
|
|
Valid values:\n\
|
|
specific value\t\tExample: 500\n\
|
|
percentage value\t\tExample: 50%%\n\
|
|
specific delta\t\tExample: 50- or +10\n\
|
|
percentage delta\t\tExample: 50%%- or +10%%\n\
|
|
\n");
|
|
}
|
|
|