diff --git a/TODO b/TODO index 7c300f0..9a8fa8a 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,4 @@ -Individual device control +#Individual device control List devices Query backlight percentage (get) allow fade in and fade out diff --git a/makefile b/makefile index 70b1a3a..7d6cb2e 100644 --- a/makefile +++ b/makefile @@ -2,7 +2,7 @@ all: rexbacklight rexbacklight: src/rexbacklight.c - gcc -std=c11 -g -Wall -Wextra -pedantic src/rexbacklight.c -o rexbacklight + gcc -std=c11 -DDEBUG_REXBACKLIGHT -g -Wall -Wextra -pedantic src/rexbacklight.c -o rexbacklight # strip --strip-all rexbacklight .PHONY: install diff --git a/src/rexbacklight.c b/src/rexbacklight.c index 29b63b9..45d60ee 100644 --- a/src/rexbacklight.c +++ b/src/rexbacklight.c @@ -24,8 +24,6 @@ #include #include -#define OP_INC 1 -#define OP_DEC 2 #define OP_SET 4 #define OP_GET 128 #define OP_NONE 0 @@ -41,8 +39,40 @@ #define CHECK_OPTION(opt, arg) (!strcmp(opt##_LONG_OPT, arg) || !strcmp(opt##_SHORT_OPT, arg)) +#define RETVAL_INVALID_FILE -1 +#define RETVAL_INVALID_DIR -2 +#define RETVAL_UNRECOGNIZED_OPTION -3 +#define RETVAL_MISSING_OPTION -4 +#define RETVAL_INVALID_DEVICE -5 +#define RETVAL_SUCCESS EXIT_SUCCESS + +struct string_array{ + char** list; + int size; +}; + +struct arg_values{ + struct arg_values* next; + + //Specific device to control + //NULL means all devices + const char* device; + + //What value to put in the backlight file + char* delta; + + //How many seconds to transition + int fade_duration; + + unsigned char operation; +}; + + + //This is where backlight devices can be found in sysfs static const char* backlight_dir = "/sys/class/backlight/"; +static const char* backlight_file = "brightness"; +static const char* max_backlight_file = "max_brightness"; //Print out usage message and GPL message if not caused by an error void usage(int exit_val){ @@ -56,7 +86,7 @@ void usage(int exit_val){ fprintf(stderr, " max\n"); fprintf(stderr, " min\n"); - if(!exit_val){ + if(exit_val == RETVAL_SUCCESS){ fprintf(stderr, "\nrexbacklight Copyright (C) 2018 rexy712\n"); fprintf(stderr, "This program comes with ABSOLUTELY NO WARRANTY.\n"); fprintf(stderr, "This is free software, and you are welcome to redistribute it\n"); @@ -67,79 +97,116 @@ void usage(int exit_val){ exit(exit_val); } -struct string_array{ - char** list; - int size; -}; +void free_cmd_args(struct arg_values* a){ + if(!a->next) + return; + free_cmd_args(a->next); + free(a->next); +} -//Get a list of backlight devices in sysfs -struct string_array get_backlight_sources(void){ +#define CHECK_NEXT_ARG(rval) \ + do{ \ + if(i == argc - 1){ \ + fprintf(stderr, "Missing argument to '%s'\n\n", argv[i]); \ + free_cmd_args(&ret); \ + usage(rval); \ + } \ + }while(0) +#define UNRECOGNIZED_OPTION(rval) \ + do{ \ + fprintf(stderr, "Unrecognized command line option '%s'\n\n", argv[i]); \ + free_cmd_args(&ret); \ + usage(rval); \ + }while(0); - DIR* fd; - struct dirent* dir; +//Convert command line arguments to flags +struct arg_values process_cmd_args(int argc, char** argv){ + struct arg_values ret = {0}; + struct arg_values* curr = &ret; - fd = opendir(backlight_dir); - //If the dir can't be opened, return no backlight devices - if(!fd){ - fprintf(stderr, "Unable to open '%s' for reading!\n", backlight_dir); - return (struct string_array){.list = NULL, .size = 0}; - } + //Skip argv[0] + for(int i = 1;i < argc;i++){ - int i, list_size = 8; - char** list = malloc(sizeof(const char*) * list_size); + //Check for switches + if(CHECK_OPTION(GET, argv[i])){ + curr->operation |= OP_GET; + continue; - for(i = 0;(dir = readdir(fd));){ - - //Resize list if more than 8 backlight devices exist (not likely) - if(i > list_size){ - char** tmp = malloc(sizeof(const char*) * (list_size += 8)); - for(int j = 0;j < i;j++){ - tmp[j] = list[j]; - } - free(list); - list = tmp; - } - - //Don't include "." or ".." in directory list - if(!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, "..")){ + }else if(CHECK_OPTION(FADE, argv[i])){ + CHECK_NEXT_ARG(-5); + curr->fade_duration = strtol(argv[++i], NULL, 0); continue; } - list[i] = malloc(strlen(dir->d_name) + 1); - strcpy(list[i], dir->d_name); - i++; + + else if(CHECK_OPTION(DEVICE, argv[i])){ + CHECK_NEXT_ARG(-5); + curr->next = calloc(1, sizeof(struct arg_values)); + curr = curr->next; + curr->device = argv[++i]; + continue; + } + else if(CHECK_OPTION(HELP, argv[i])){ + free_cmd_args(&ret); + usage(RETVAL_SUCCESS); + } + + else if(!strcmp(argv[i], "max")){ + curr->operation = OP_SET; + curr->delta = argv[i]; + } + else if(!strcmp(argv[i], "min")){ + curr->operation = OP_SET; + curr->delta = argv[i]; + } + else if(!strcmp(argv[i], "off")){ + curr->operation = OP_SET; + curr->delta = argv[i]; + } + + //If we get a '-' followed by not a number, it's not a known option + else if(argv[i][1] < '0' || argv[i][1] > '9'){ + UNRECOGNIZED_OPTION(RETVAL_UNRECOGNIZED_OPTION); + } + + //If we get a '-' followed by a number, it's a decrement request + else{ + curr->operation = OP_SET; + curr->delta = argv[i]; + } } - closedir(fd); + //If there isn't an operation defined in the global context and there is no specified device, there's nothing to do + if(!ret.operation){ + if(!ret.next){ + fprintf(stderr, "No operation requested!\n\n"); + usage(RETVAL_MISSING_OPTION); + } + //if there is a device specified, make sure each one has a requested operation + for(curr = ret.next;curr;curr = curr->next){ + if(!curr->operation){ + fprintf(stderr, "No operation requested for device '%s'!\n\n", curr->device); + free_cmd_args(&ret); + usage(RETVAL_MISSING_OPTION); + } + } + //If there is a globally defined operation, apply it to the devices with no operation request + }else{ + for(curr = ret.next;curr;curr = curr->next){ + if(curr->operation == OP_NONE){ + curr->operation = ret.operation; + if(!curr->delta) + curr->delta = ret.delta; - return (struct string_array){.list = list, .size = i}; -} - -//Delete the list of backlight devices -void free_string_array(struct string_array* s){ - for(int i = 0;i < s->size;i++) - free(s->list[i]); - free(s->list); -} - -//Read a float from a file -float get_brightness(const char* file){ - //Static buffer because it shouldn't be able to exceed 127 chars long - char buff[127]; - FILE* bright = fopen(file, "r"); - if(!bright){ - fprintf(stderr, "Unable to open brightness file \"%s\"!\n", file); - return 0; + } + } } - if(!fgets(buff, sizeof(buff), bright)){ - fprintf(stderr, "Unable to read file \"%s\"!\n", file); - fclose(bright); - return 0; - } - fclose(bright); - return atof(buff); + return ret; } +#undef CHECK_OPTION +#undef UNRECOGNIZED_OPTION -int process_arg(char* arg, float min, float current, float max){ +//Process an operation +int process_op(char* arg, float min, float current, float max){ //Amount to inc/dec int delta = delta = max * atof(arg + 1) / 100.0; @@ -180,180 +247,169 @@ int process_arg(char* arg, float min, float current, float max){ //Set to maximum }else if(!strcmp("max", arg)){ return max; - //Unknown option - }else{ - usage(-1); } return current; } -struct arg_values{ - struct arg_values* next; +//Delete the list of backlight devices +void free_string_array(struct string_array* s){ + for(int i = 0;i < s->size;i++) + free(s->list[i]); + free(s->list); +} - //Specific device to control - //NULL means all devices - const char* device; +//Get a list of backlight devices in sysfs +struct string_array get_backlight_sources(void){ - //What value to put in the backlight file - int delta; + DIR* fd; + struct dirent* dir; - //How many seconds to transition - int fade_duration; + fd = opendir(backlight_dir); + //If the dir can't be opened, return no backlight devices + if(!fd){ + fprintf(stderr, "Unable to open '%s' for reading!\n", backlight_dir); + return (struct string_array){.list = NULL, .size = 0}; + } - unsigned char operation; -}; + int i, list_size = 8; + char** list = malloc(sizeof(const char*) * list_size); -void free_cmd_args(struct arg_values* a){ - if(!a->next) + for(i = 0;(dir = readdir(fd));){ + + //Don't include "." or ".." in directory list + if(!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, "..")){ + continue; + } + + //Resize list if more than 8 backlight devices exist (not likely) + if(i > list_size){ + char** tmp = malloc(sizeof(const char*) * (list_size += 8)); + for(int j = 0;j < i;j++){ + tmp[j] = list[j]; + } + free(list); + list = tmp; + } + + list[i] = malloc(strlen(dir->d_name) + 1); + strcpy(list[i], dir->d_name); + i++; + } + + closedir(fd); + + return (struct string_array){.list = list, .size = i}; +} + + +//Read a float from a file +float get_brightness(const char* file){ + //Static buffer because it shouldn't be able to exceed 127 chars long + char buff[127]; + FILE* bright = fopen(file, "r"); + if(!bright){ + fprintf(stderr, "Unable to open brightness file \"%s\"!\n", file); + return 0; + } + if(!fgets(buff, sizeof(buff), bright)){ + fprintf(stderr, "Unable to read file \"%s\"!\n", file); + fclose(bright); + return 0; + } + fclose(bright); + return atof(buff); +} + +//Write value to backlight files +void write_delta(char* delta){ + int out = process_op(delta, 0, get_brightness(backlight_file), get_brightness(max_backlight_file)); + FILE* bright = fopen(backlight_file, "w+"); + if(!bright){ + fprintf(stderr, "Unable to open brightness file \"%s%s\" for writing!\n", backlight_dir, backlight_file); return; - free_cmd_args(a->next); - free(a->next); -} - -#define CHECK_NEXT_ARG(rval) \ - do{ \ - if(i == argc - 1){ \ - fprintf(stderr, "Missing argument to '%s'\n\n", argv[i]); \ - free_cmd_args(&ret); \ - usage(rval); \ - } \ - }while(0) -#define UNRECOGNIZED_OPTION(rval) \ - do{ \ - fprintf(stderr, "Unrecognized command line option '%s'\n\n", argv[i]); \ - free_cmd_args(&ret); \ - usage(rval); \ - }while(0); - -struct arg_values process_cmd_args(int argc, char** argv){ - struct arg_values ret = {0}; - struct arg_values* curr = &ret; - - //Skip argv[0] - for(int i = 1;i < argc;i++){ - - //Check for switches - if(CHECK_OPTION(GET, argv[i])){ - curr->operation |= OP_GET; - continue; - - }else if(CHECK_OPTION(FADE, argv[i])){ - CHECK_NEXT_ARG(-5); - curr->fade_duration = strtol(argv[++i], NULL, 0); - continue; - } - - else if(CHECK_OPTION(DEVICE, argv[i])){ - CHECK_NEXT_ARG(-5); - curr->next = calloc(1, sizeof(struct arg_values)); - curr = curr->next; - curr->device = argv[++i]; - continue; - } - else if(CHECK_OPTION(HELP, argv[i])){ - free_cmd_args(&ret); - usage(0); - } - - //If we get a '-' followed by not a number, it's not a known option - else if(argv[i][1] < '0' || argv[i][1] > '9'){ - UNRECOGNIZED_OPTION(-4); - } - - //If we get a '-' followed by a number, it's a decrement request - else{ - switch(argv[i][0]){ - case '=': - curr->operation = OP_SET; - break; - case '-': - curr->operation = OP_DEC; - break; - case '+': - curr->operation = OP_INC; - break; - default: - UNRECOGNIZED_OPTION(-4); - } - curr->delta = atof(&argv[i][1]); - } } - - //If there isn't an operation defined in the global context and there is no specified device, there's nothing to do - if(!ret.operation){ - if(!ret.next){ - fprintf(stderr, "No operation requested!\n\n"); - usage(-1); - } - //if there is a device specified, make sure each one has a requested operation - for(curr = ret.next;curr;curr = curr->next){ - if(!curr->operation){ - fprintf(stderr, "No operation requested for device '%s'!\n\n", curr->device); - usage(-1); - } - } - //If there is a globally defined operation, apply it to the devices with no operation request - }else{ - for(curr = ret.next;curr;curr = curr->next){ - if(!curr->operation) - curr->operation = ret.operation; - } - } - return ret; + fprintf(bright, "%d", out); + fclose(bright); } -#undef CHECK_OPTION -#undef UNRECOGNIZED_OPTION //argv[1] shall be [+-=]|min|max|off int main(int argc, char** argv){ - const char* backlight_file = "brightness"; - const char* max_backlight_file = "max_brightness"; - struct arg_values args = process_cmd_args(argc, argv); + struct arg_values args; //A linked list of devices and the requested settings. + struct string_array backlight_names; //List of all backlight devices in sysfs. - //Acquire a list of backlight devices in sysfs - struct string_array backlight_names = get_backlight_sources(); + args = process_cmd_args(argc, argv); + backlight_names = get_backlight_sources(); + +//Macro for easy memory cleaning +#define CLEANUP() do{free_string_array(&backlight_names);free_cmd_args(&args);}while(0) + + + + //If there are no backlights, we can't do anything if(backlight_names.size == 0){ fprintf(stderr, "No backlights devices found!\n"); - return -1; + CLEANUP(); + return RETVAL_INVALID_DEVICE; + } + + //Make sure all explicit devices actually exist + for(struct arg_values* curr = args.next;curr;curr = curr->next){ + for(int i = 0;i < backlight_names.size;i++){ + if(!strcmp(curr->device, backlight_names.list[i])){ + goto continue_outer; + } + } + fprintf(stderr, "No such device '%s'\n", curr->device); + CLEANUP(); + return RETVAL_INVALID_DEVICE; + continue_outer:; } //save our starting directory so we can change to each backlight directory (makes logic easier) char* starting_dir = getcwd(NULL, 0); +//Redefine CLEANUP to include freeing of the starting_dir string +#undef CLEANUP +#define CLEANUP() do{free_string_array(&backlight_names);free_cmd_args(&args);free(starting_dir);}while(0) + + //Change to the base directory for all sysfs backlights if(chdir(backlight_dir)){ fprintf(stderr, "Unable to read backlight sysfs directory!\n"); - return -2; + CLEANUP(); + return RETVAL_INVALID_DIR; } - //Set all backlight sources to the requested percentage - for(int i = 0;i < backlight_names.size;i++){ - if(chdir(backlight_names.list[i])){ - fprintf(stderr, "Unable to open backlight directory \"%s%s\"!\n", backlight_dir, backlight_names.list[i]); - continue; + //If args.next is not NULL, then 1+ devices were specified. Only apply change to those + if(args.next){ + for(struct arg_values* curr = args.next;curr;curr = curr->next){ + if(chdir(curr->device)){ + fprintf(stderr, "Unable to open backlight directory \"%s%s\"!\n", backlight_dir, curr->device); + continue; + } + write_delta(curr->delta); } - - //Write value to backlight files - int out = process_arg(argv[1], 0, get_brightness(backlight_file), get_brightness(max_backlight_file)); - FILE* bright = fopen(backlight_file, "w+"); - if(!bright){ - fprintf(stderr, "Unable to open brightness file \"%s%s\" for writing!\n", backlight_dir, backlight_file); - continue; + //Otherise, apply delta to all backlights + }else{ + for(int i = 0;i < backlight_names.size;i++){ + if(chdir(backlight_names.list[i])){ + fprintf(stderr, "Unable to open backlight directory \"%s%s\"!\n", backlight_dir, backlight_names.list[i]); + continue; + } + write_delta(args.delta); } - fprintf(bright, "%d", out); - - fclose(bright); } - free_string_array(&backlight_names); - free_cmd_args(&args); //Return to start directory if(chdir(starting_dir)){ - free(starting_dir); + CLEANUP(); fprintf(stderr, "Could not return to starting directory!\nWas the directory moved/deleted?\n"); - if(chdir(getenv("HOME")) || chdir("/")){ - return -5; + if(!chdir(getenv("HOME")) || !chdir("/")){ + return RETVAL_INVALID_DIR; + }else{ + return RETVAL_INVALID_DIR; } } - free(starting_dir); - return 0; + CLEANUP(); + return RETVAL_SUCCESS; } +#undef CLEANUP