You are not logged in.

#1 2016-02-08 16:36:29

multiplex'd
Member
Registered: 2015-08-30
Posts: 7

brightlight - change the screen backlight brightness on Linux

brightlight is a program that reads and sets the backlight brightness of integrated displays like those found on laptops. I wrote brightlight because after I installed Arch on my laptop (it came with Windows 8), the backlight keys no longer worked. After a little Google-fu and some poking I wrote a bash script to do the job:

#!/bin/sh

if [[ $UID -ne 0 ]] ; then
    echo Insufficient privelidges.
    exit 1
fi

if [[ "$1" == "" ]] ; then
    echo No arguments given
    exit 1
fi

if [[ "$1" == "get" ]] ; then
    rawvalue=$(cat /sys/class/backlight/intel_backlight/brightness)
    percentage=$(( ($rawvalue * 100) / 7812 ))
    echo $percentage
    exit 0
elif [[ "$1" == "set" ]] ; then
    brightness=$(( ($2 * 7812) / 100 ))
    echo $brightness > /sys/class/backlight/intel_backlight/brightness
else
    echo Unrecognised argument
fi

exit 0

but I felt that it was a little inflexible, and I wanted a solution that others could use (and an excuse to learn C).

The code is hosted on GitHub. This is my first project, so comments and suggestions are welcome.

Offline

#2 2016-02-08 16:42:55

ewaller
Administrator
From: Pasadena, CA
Registered: 2009-07-13
Posts: 19,769

Re: brightlight - change the screen backlight brightness on Linux

multiplex'd wrote:

...
(and an excuse to learn C).
...

Huh?   hmm


Edit:  BTW, I did write one in C for the reasons you stated.  If you want, I can share it.
Edit2:  As a critique, you hard coded the max brightness.  That number changes from machine to machine.  You would be better served to read from max_brightness to discover the appropriate value for the machine on which the script is running.
Edit3.   Also, I note that you are checking for root.  Okay.  One of the nice things about doing it in C is that you can set the SUID bit to run the program as the person who owns the program as opposed to the person who is running the program.  For security, this is prohibited on Bash scripts.  Were it in C, owned by root,  with the SUID bit set, a normal user could set the brightness.

Last edited by ewaller (2016-02-08 16:51:24)


Nothing is too wonderful to be true, if it be consistent with the laws of nature -- Michael Faraday
Sometimes it is the people no one can imagine anything of who do the things no one can imagine. -- Alan Turing
---
How to Ask Questions the Smart Way

Offline

#3 2016-02-08 17:22:58

multiplex'd
Member
Registered: 2015-08-30
Posts: 7

Re: brightlight - change the screen backlight brightness on Linux

Apologies if I didn't make myself clear: the bash script I posted above is the original script I was using before I decided to write brightlight. Brightlight itself is written in C and is hosted in the GitHub repository I linked to above. I posted the bash script as an example as how changing the backlight could be done.

Thanks for pointing out the hardcoded values and the suid bit. The check for uid=0 was a hack to get writing to the backlight brightness file to work and the hardcoded value is there because I wrote the script quickly and I wasn't too concerned with portability at that point. As for reading from max_brightness, brightlight does actually do this in order to be portable.

Thanks for the comment.

Offline

#4 2016-02-08 18:10:59

ewaller
Administrator
From: Pasadena, CA
Registered: 2009-07-13
Posts: 19,769

Re: brightlight - change the screen backlight brightness on Linux

Got it big_smile

Okay, I will share my code with you.  Your code looks pretty good -- especially as it was a learning exercise. You did spend a fair amount of effort to reinvent the wheel.  Take a look at your command line parser and compare it to mine which uses argp.  As you learn C, you may want to spend a couple days reading through the GNU C libraries to make your life a little easier.

/**
 *
 * @mainpage Backlight
 *
 * @section Introduction
 * This program is a command line program that allows the brightness level of
 * an Intel video system to be read, set, incremented or decremented.
 *
 * It will read and write @a /sys/class/backlight/intel_backlight/brightness
 * to establish the current backlight brightness and can write to that file to change
 * the brightness.
 *
 * To establish the maximum allowed brightness, it will read
 * @a /sys/class/backlight/intel_backlight/max_brightness
 *
 * @author Eric Waller
 *
 * @date September 2015
 *
 * @copyright
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @section Usage
 * backlight [OPTION...]
 * Option          | Description
 * --------------- | --------------
 *   -d, --dec=INT | Decrement
 *   -i, --inc=INT | Increment
 *   -s, --set=INT | Set
 *   -v, --verbose | Produce verbose output
 *   -?, --help    | Give this help list
 *       --usage   | Give a short usage message
 *   -V, --version | Print program version
 *
 * The program will report the maximum permitted brightness and the
 * brightness setting when the program exits
 */

#include <stdlib.h>
#include <stdio.h>
#include <argp.h>

/**
 * Stores the values of the program options that are passed
 * in from the command line.  The values initially set to invalid values by
 * main and are updated as the command line parameters are parsed.  It is
 * only permissible to specify one of inc, dec, or set.
 */
typedef struct {
  int verbose; /**< If set, be verbose */
  int inc;  /**< Value by which to increment the brightness */
  int dec;  /**< Value by which to decrement the brightness */
  int set;  /**< Value by which to set the brightness */
} ProgramArguments;

static ProgramArguments arguments;

///  Define the acceptable command line options

static struct argp_option options[] =
{
  {"verbose", 'v', 0, 0, "Produce verbose output"},  /**< The verbose command line flag has no argument */
  {"inc", 'i', "INT",0,"Increment"},                 ///< increment command line flag requires an integer argument
  {"dec", 'd', "INT",0,"Decrement"},                 ///< decrement command line flag requires an integer argument
  {"set", 's', "INT",0,"Set"},                       ///< decrement command line flag requires an integer argument
  {0}
};

const char *argp_program_version = "backlight 0.1";
const char *argp_program_bug_address = "<ewwaller+code@gmail.com>";
static char doc[] =
  "backlight -- Read, set, increment, or decrement the backlight on Intel graphics based displays";

static char args_doc[] = "";
static error_t parse_opt (int key, char *arg, struct argp_state *state);

static struct argp argp = { options, parse_opt, args_doc, doc };


int
ReadSysFile(char *theFileName)
{
  /**  Read a file from @a /sys and interpret the data on the first line of
   *   the "file" as an integer expresed as a ascii decimal string
   *
   *   @param[in] *theFileName A zero terminated string containing the name and
   *                           path of the sys file
   *   @return the integer value read from the file.  -1 indicates failure.
   */

  char* readBuffer = NULL;
  long unsigned int  bufferSize = 0;

  FILE *theFile = fopen(theFileName,"r");
  if (!theFile)
    {
      fprintf(stderr,"\nCould not open the file %s\n",theFileName);
      return -1;
    }

  getline(&readBuffer, &bufferSize, theFile);
  if (readBuffer)
    {
      int theIntValue=atoi(readBuffer);
      free(readBuffer);
      readBuffer=NULL;
      bufferSize=0;
      fclose(theFile);
      return (theIntValue);
    }
  fclose(theFile);
  return -1;
}

int
WriteSysFile(char *theFileName, int theValue)
{
  /**
   *   Write a file from /sys an interpret the data on the first line of the "file" as an integer expresed as a ascii decimal string
   *
   *   @param[in] *theFileName A pointer to a zero terminated string containing the
   *                           name and path of the sys file
   *    @param[in] theValue    The value to be written to the file
   *    @return                0 or positive integer is success;  negative integer is failure
  */

  FILE *theFile = fopen(theFileName,"w");
  if (!theFile)
    {
      fprintf(stderr,"\nCould not open the file %s\n",theFileName);
      return -1;
    }
  int returnValue;
  returnValue = fprintf(theFile,"%i\n",theValue);
  fclose(theFile);
  return returnValue;
}

int
parseIntArgument(char *arg)
{
  /**
   *  Convert a null terminated string of decimal digits to an integer.  Any non-decimal
   *  digits in the string will result in a failure.
   *
   *  @param[in] arg A pointer to null terminated string of decimal digits
   *  @return A positive or zero integer represented by the string.
   *  @warning An error condition will cause the program to exit with an error.
   */

  char *endptr, *str;
  long val;
  errno = 0;    /* To distinguish success/failure after call */
  val = strtol(arg, &endptr, 10);
  if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
      || (errno != 0 && val == 0))
    {
      perror("strtol");
      exit(EXIT_FAILURE);
    }

  if (endptr == str) {
    fprintf(stderr, "No digits were found\n");
    exit(EXIT_FAILURE);
  }
  if (*endptr)
    {+
      printf ("Non digit in decimal value\n");
      exit(EXIT_FAILURE);
    }
  /* If we got here, strtol() successfully parsed a number */
  return (int)val;
}

void
TooManyOptions(void)
{
  /**
   * A simple helper function that prints an error message and exits
   * @warning Calling this function causes the program to exit with an error.
   */

  printf("Increment, Decrement and Set are mutually exclusive options\n");
  exit(EXIT_FAILURE);
}

static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
  /**
   *  Process the command line arguments and options.  Collect all
   *  the options and remember their state. This function is called one
   *  time for each key.  As the keys are passed in, check to ensure they
   *  do not conflict with each other.  When there are no more keys,
   *  ensure there are no additional parameters that were passed into the
   *  program -- this one does not want any.
 x  *
   *  @param[in]     key    An integer that represents a char value of one
   *                        of options passed from the command line.
   *  @param[in,out] *arg   A pointer to a null terminated string that is the argument of the
   *                        option represented by key.
   *  @param[in]     *state
   */

  ProgramArguments* argumentPtr = state->input;

  switch (key)
    {
    case 'v':
      argumentPtr->verbose = 1;
      break;
    case 'i':
      if ((arguments.dec != -1) || (arguments.set != -1))
        TooManyOptions();
      arguments.inc=parseIntArgument(arg);
      break;
    case 'd':
      if ((arguments.inc != -1) || (arguments.set != -1))
        TooManyOptions();
      arguments.dec=parseIntArgument(arg);
      break;
    case 's':
      if ((arguments.dec != -1) || (arguments.inc != -1))
        TooManyOptions();
      arguments.set=parseIntArgument(arg);
      break;
    case ARGP_KEY_NO_ARGS:
      /* If there are no Arguments, that is good.  We don't want any */
      break;
    case ARGP_KEY_ARG:
      /* I am not expecting any arguments that are not options. */
      argp_usage (state);
      break;
    default:
      return ARGP_ERR_UNKNOWN;
    }
  return 0;
}

int
main (int argc, char** argv)
{
  /**
   * This is the main function.
   * line.  It will determine the maximum brightness permitted, and the current brightness,
   * and will parse the parameters passed in on the command and determine their validity.  If
   * they are valid, and they call for a change in the brightness setting, it will write to the
   * appropriate system file to cause the brightness to change.
   *
   *  @param[in]  argc    An integer that represents the number of command line parameters.
   *  @param[in]  **argv  A pointer to an array of pointers to null terminated strings that store
   *                      the parameters on the command line.
   *  @return             An integer that represents the exit value of the program.  0 means success.
   */

  arguments.verbose = 0;
  arguments.set = -1;
  arguments.inc = -1;
  arguments.dec = -1;

  int max_brightness = 0;
  int brightness =0;
  max_brightness = ReadSysFile("/sys/class/backlight/intel_backlight/max_brightness");
  if (max_brightness < 0)
    exit(EXIT_FAILURE);
  brightness = ReadSysFile("/sys/class/backlight/intel_backlight/brightness");
  if (brightness < 0)
    exit(EXIT_FAILURE);
  argp_parse (&argp, argc, argv, 0, 0, &arguments);
  if (arguments.inc >= 0 ) brightness += arguments.inc;
  if (arguments.dec >= 0 ) brightness -= arguments.dec;
  if (arguments.set >= 0 ) brightness  = arguments.set;
  if (brightness<0) brightness = 0;
  if (brightness>max_brightness) brightness = max_brightness;
  if ((arguments.inc >= 0) || (arguments.dec >= 0) || (arguments.set >= 0))
    if (WriteSysFile("/sys/class/backlight/intel_backlight/brightness",brightness) < 0)
      printf("Unable to set brightness.  Check permissions");
  printf("Max Brightness = %i\n",max_brightness);
  printf("Current Brightness = %i\n",brightness);
  if (arguments.verbose)
    {
      printf("Increment:%i; Decrement:%i, Set:%i\n",arguments.inc,arguments.dec,arguments.set);
    }
}

Edit:  Also, note how similar are your functions get_max_brightness() and get_current_brightness().  Mayhaps you could come up with a way of writing a generalized function that does both?  Pass in a variable -- either a pointer to a file name, or maybe an integer that specifies which operation you want.

Edit2:  Hint:  If you put main at the end, and are careful in the order write your functions, you do not need to prototype the functions at the beginning of the program.   When I code, I try not to use prototypes in the .c files; I only prototype functions in .h files, and then, only those I intend to share.

Last edited by ewaller (2016-02-08 18:37:52)


Nothing is too wonderful to be true, if it be consistent with the laws of nature -- Michael Faraday
Sometimes it is the people no one can imagine anything of who do the things no one can imagine. -- Alan Turing
---
How to Ask Questions the Smart Way

Offline

#5 2016-02-08 19:11:23

multiplex'd
Member
Registered: 2015-08-30
Posts: 7

Re: brightlight - change the screen backlight brightness on Linux

Yes, my command line parser is quite cumbersome. I designed it that way because I hadn't yet discovered the getopt() family of functions (nor argp) when I wrote the parser, and I was trying to understand how the parser in thttpd worked at the time. I am planning on going back and rewriting the parser to use getopt() and tidying the validation functions for version 2.

As for the similar functions, I agree that a single generalised function would be a good idea; I like the idea of using an integer as a flag for selecting operations. I think a lot of the functions could be generalised and unified, which would definitely clean up the program's structure.

I like to have main() as close to the top of the source code as possible so I don't have to scroll too much in order to read it so I prototype the other functions first. However, that's a matter of preference and coding style.

Offline

#6 2016-02-23 04:34:28

Haikarainen
Member
Registered: 2012-09-04
Posts: 93

Re: brightlight - change the screen backlight brightness on Linux

Lol, you have taken the same path as I did several years ago (Bash script called lightscript, then a C application called light).

https://github.com/haikarainen/light

https://aur.archlinux.org/packages/light-git

https://aur.archlinux.org/packages/light

Btw, dont hardcode the controller. It's different from system to system, and some systems have several different (with several different precision levels).

Last edited by Haikarainen (2016-02-23 04:36:45)

Offline

#7 2016-02-25 17:59:49

multiplex'd
Member
Registered: 2015-08-30
Posts: 7

Re: brightlight - change the screen backlight brightness on Linux

Well there's definitely more than one way to do it then smile

When I get the chance, I'll have a look through the code for light - I might learn a thing or two.

As for hard-coding the controller, I made a point of making the path a #define at the top of the source code so it could be easily changed. If the target system has more than one backlight whose brightness can be changed, my approach would either be to use brightlight's -f flag or compile two copies of brightlight, each of which has a different default path defined. I also used integer arithmetic for calculating percentages because at my level of coding skill integers are easier to parse than floating point types, however I'm sure I could work out how to do that given enough time.

I would also note that I'm not making the assumption that the backlight controllers live at /sys/class/backlight/ - if you were using (for example) GoboLinux the controllers would be located at /System/Kernel/Objects/class/backlight/.

I'll be quite interested to see the differences in approach between light and brightlight when I get round to reading the code.

Offline

Board footer

Powered by FluxBB