17 Jan 2008

More Linux fun: screen brightness

I like to use a GNU/Linux machine than a Windows machine for at least two reasons:
  • GNU/Linux user environment (especially KDE) is customizable.
  • You get to see how things are implemented, which is kinda fun.
What’s the pain point in using a GNU/Linux machine? Upgrades are scary. New version of the OS may not support your hardware. It takes a while for Linux machines to support new hardware.

In this post, I will tell you how an OS upgrade broke a functionality on my machine and how I fixed it (which was a fun experience).

Update 1: zerosk8 says in a comment that the command xbacklight can be used to control screen brightness from the command line.  On Ubuntu machines, run sudo apt-get install xbacklight to install the command on your machine.  Thank you zerosk8!  (Near the end of this post I am discussing how I set up keyboard shortcuts for adjusting brightness.  This part might be useful even if you use xbacklight command.)

Update 2: The technique described in this post worked on my Lenovo laptop (with an ATI graphics card).  It didn’t, however, work on my Samsung N150 laptop (which has an Intel graphics card).  Check this blog post of mine to see what works on the Samsung machine.

Original post:
My upgrade to Kubuntu Gutsy broke the functionality of Fn+Home and Fn+End keys on my ThinkPad. (If you use Sony Vaio, look at Daniel’s comment. Thanks Daniel for the information!) These keys are used to control the brightness of the LCD screen. When I was Googling around to find a solution, I learned that the LCD screen driver provides a file system-like interface for controlling it. The file /sys/class/backlight/acpi_video0/brightness acts as the interface for adjusting screen brightness. So I wrote the following shell script to adjust screen brightness:
#!/bin/sh
#
# Adjust LCD brightness of ThinkPad laptops.

CTRL_FILE=/sys/class/backlight/acpi_video0/brightness

usage() {
  echo >&2 Usage: lcd-brightness [value]
}

case $# in
0)
  cat $CTRL_FILE
  ;;
1)
  echo $1 >$CTRL_FILE
  ;;
*)
  usage
esac
The code feels right when looking at it. Let’s try it out.
$ ./lcd-brightness
80
$ ./lcd-brightness 70
./lcd-brightness: line 16: /sys/class/backlight/acpi_video0/brightness: Permission denied
$ sudo ./lcd-brightness  # sudo runs the command as root.
80
$ sudo ./lcd-brightness 70  # This worked -- screen brightness changed.
So this problem looks like a permission issue. Just to make sure:
$ ls -l /sys/class/backlight/acpi_video0/brightness
-rw-r--r-- 1 root root 4.0K Jan 17 16:47 /sys/class/backlight/acpi_video0/brightness
So only root can change the screen brightness. But I don’t want to sudo all the time to adjust screen brightness. I said to myself, “I know how to handle this.” And I installed my script like this:
$ sudo cp ./lcd-brightness /usr/local/bin/
$ sudo chmod 755 /usr/local/bin/lcd-brightness
$ sudo chmod +s /usr/local/bin/lcd-brightness
$ ls -l /usr/local/bin/lcd-brightness
-rwsr-sr-x 1 root root 7.3K Jan 17 08:21 /usr/local/bin/lcd-brightness
Time to test this newly installed script.
$ type lcd-brightness
lcd-brightness is /usr/local/bin/lcd-brightness
$ lcd-brightness
70
$ lcd-brightness 80
/usr/local/bin/lcd-brightness: line 16: /sys/class/backlight/acpi_video0/brightness: Permission denied
# Huh?  Let’s try again as root?
$ sudo lcd-brightness 80   # This changes the brightness
Looks like setuid bit is not working as I expected. Some more Googling around gave me the answer. Wikipedia article on setuid says: “Due to the increased likelihood of security flaws, many operating systems ignore the setuid attribute when applied to executable shell scripts.“ That explains everything. So my next option is to write it as a C program. I translated this shell script into C as follows:
/*
 * Change/query the brightness of LCD screen.
 */

#include <stdio.h>

void usage()
{
  fprintf(stderr, "Usage: lcd-brightness [value]\n");
}

int main(int argc, char *argv[])
{
  FILE *fp;
  int bright = 0;
  const char *kFileName =
      "/sys/class/backlight/acpi_video0/brightness";

  switch (argc) {
    case 1:
      fp = fopen(kFileName, "r");
      fscanf(fp, "%d", &bright);
      printf("%d\n", bright);
      break;
    case 2:
      fp = fopen(kFileName, "w");
      bright = atoi(argv[1]);
      fprintf(fp, "%d\n", bright);
      break;
    default:
      usage();
      return -1;
  }

  fclose(fp);
  return 0;
}
Compiling this file and installing the binary like earlier does the job. Cool :-)

In case you didn’t know exactly how to install this program, or too lazy to type commands, use these commands:
$ gcc lcd-brightness.c
$ sudo cp ./a.out /usr/local/bin/lcd-brightness
$ sudo chmod +s /usr/local/bin/lcd-brightness
$ ls -l /usr/local/bin/lcd-brightness
-rwsr-sr-x 1 root root 7.3K Jun  7 21:47 /usr/local/bin/lcd-brightness
But this is not the end of story. Later I found a usability problem with this program. I use this program only when I want to either increase or decrease the screen brightness. That is a two-step process — first I have to run the program and find the screen brightness. Then run it again with appropriate brightness argument. This is not an optimal way to use a program like this. So I decided to write two more shell scripts on top of lcd-brightness command. Here are those scripts:
$ cat increase-lcd-brightness
#/bin/bash
#
# Increase LCD brightness by 10.
# Assumes that lcd-brightness command is on $PATH.
lcd-brightness $(expr $(lcd-brightness) + 10)
$ cat decrease-lcd-brightness
#/bin/bash
#
# Decrease LCD brightness by 10.
# Assumes that lcd-brightness command is on $PATH.
lcd-brightness $(expr $(lcd-brightness) - 10)
The scripts are very simple, aren’t they? But there’s still another usability issue: I have to switch to a terminal to invoke these commands, which might be a distraction when I am coding or reading some document. Correct approach would be to use shortcut keys for this. So I went ahead and defined two new global shortcut keys on my machine. On KDE it’s done from Control Center. I opened the corresponding applet by selecting K Menu > System Settings > Accessibility > Input Actions. Added a new action called “Increase LCD Brightness” with type as “Keyboard Shortcut -> Command/URL (simple)”.
Chose the keyboard shortcut Ctrl+Alt+=.
For command, I gave the path to increase-lcd-brightness script.
Likewise, I assigned Ctrl+Alt+- to decrease-lcd-brightness script. And that’s about it. Now I can use shortcut keys for adjusting screen brightness. Now you understand when I say “Linux is customizable,” don’t you? ;-)

23 comments:

  1. I was having trouble with my LCD until I read your page. Thanks for the incredibly clear and detailed explanation. I will now experiment with more customizations in Linux!

    ReplyDelete
  2. Thank you very much. I did the same thing with my Sony Vaio, with the following changes:

    1) The path is /sys/class/backlight/sony/brightness

    2) The brightness steps from 1 to 7 by increments of 1, so I modified the shell scripts to use 1 instead of 10.

    3) I use gnome, so I set shortcut keys using gconf-editor

    Thanks for pointing the way!

    ReplyDelete
  3. I have been frustrated for a while trying to get screen brightness to work in my Dell Inspiron e1705. Your blog inspired me, and after locating the brightness file (for me it was /proc/acpi/video/VID/LCD/brightness), I wrote a simple script in C++ that had to look at the current value and modify it accordingly (my brightness file starts at 12 and goes in increments of 12 up to 84, then jumps to 100), assigned shortcuts, and voila! I couldn't be happier. Thanks for the idea!

    ReplyDelete
  4. This post helped me immensely. Thanks a ton for your code examples. I'm on a Gateway ML6721, which has irregular brightness settings.(12,25,37,50,62,75,87, and 100)

    I built a pair of scripts to replace yours to use these variables. If there's anyone out there who would like such things, just send me an email or reply to this. I'll also upload the "lcd-brightness" all compiled, if that's all right with the author.

    ReplyDelete
  5. Thanks a lot for the insight.

    I compiled the C program on my system. To change brightness level, it will only work sudo'ed, becaue the file it has to modify requires root privileges. How did you achieve making the C program work for a regular user (without sudo)?

    Thanks in advance.

    Daniel

    ReplyDelete
  6. Ok, after installing the C program like you instructed, it worked.

    Only thing is that to compile I had to include stdlib.h

    and that's because of the atoi() function.

    Thanks again.

    ReplyDelete
  7. "Only thing is that to compile I had to include stdlib.h"

    Ah, good catch. I didn't have all warnings enabled; so I didn't see the warning when I compiled. 'gcc -Wall' shows me the warning about atoi. Thanks Daniel!

    ReplyDelete
  8. To avoid the prickly setuid business you can take advantage of the fact that the acpid daemon runs as root. Just create an event file at /etc/acpi/events/Fn-PgUp that contains the following:

    event=ibm/hotkey HKEY 00000080 00001012
    action=/etc/acpi/Fn-PgUp.sh

    And have your Fn-PgUp.sh change the brighness.

    When you hit Fn-PgUp the acpi event "HKEY 00000080 00001012" is generated which is picked up and acted upon by acpid.

    ReplyDelete
  9. There is very nice application in Ubuntu, which is connected to these values in /sys filesystem and which show nice "progress bar" when user change its brightness.

    Does someone know its name? I'd like to install that on my Debian. If you know, please let me know on my e-mail address, thanks.

    ReplyDelete
  10. OK, it's name is gnome-power-manager... if everyone would like to know. Very nice app :)

    ReplyDelete
  11. Hi Manki, Guys

    I have the same problem, but with Redhat 2.6.9, what confuses me now is that I couldn't find this brightness file in my system, I already tried sys/class/... but nothing there!, I also tried proc/acpi/... path; have read this in a comment posted above, but still nothing, any suggestion to solve this!:(
    I only need to know the path of the brightness file in Redhat filesystem.

    Suhib

    ReplyDelete
  12. Hi Suhib,

    Is "/proc/acpi/video/VGA/LCD/brightness" what you're looking for? (I don't have a RedHat machine; a quick Google search seems to suggest this path. Hope this helps.)

    ReplyDelete
  13. I see, but the problem is I don't have the video directory in "/proc/acpi/.." path!!?

    ReplyDelete
  14. @Suhib Rawshdeh:

    It sounds very simple, but have you tried a "locate brightness"? It might just return what you're looking for. (If you haven't used locate before, be sure to run "sudo updatedb" first.

    ReplyDelete
  15. I think my problem is that I'm using an older version of redhat which didn't support acpi.

    Thanks any way guys,

    ReplyDelete
  16. Hi!!! mankikannan.blogspot.com is one of the best innovative websites of its kind. I enjoy reading it every day. I will be back.

    ReplyDelete
  17. @Anonymous: whoa, you've made my day. Thank You!

    ReplyDelete
  18. I consider, that you are mistaken. I can defend the position. Write to me in PM.

    ReplyDelete
  19. I was looking for a way to change the LCD brightness on startup with my script, and google showed me this post.

    First, I tryed to modify /sys/class/backlight/acpi_video0/brightness, but it was so annoying to sudo echa time.

    Finally, just discovered an app for CLI that lets you change the brightness in a very easy way (and no need of superuser permissions!).

    The app is: xbacklight
    Using it: xbacklight -set number

    number, an integer between 0 and 100

    ;)

    ReplyDelete
  20. Awesome post dude!
    It just works perfectly! :D
    You have no idea how badly I have been asking for something like this for ages! :D
    Thanks a ton. :D

    ReplyDelete
  21. Thanks Royzz. Thanks zerosk8. :)

    ReplyDelete
  22. Thanks, it work's great.
    You can modify your C source code to:


    /*
    * Change/query the brightness of LCD screen.
    */

    #include

    int main(int argc, char *argv[])
    {
    FILE *fp;
    int bright = 0;
    const char *kFileName = "/sys/class/backlight/dell_backlight/brightness";

    fp = fopen(kFileName, "r");
    fscanf(fp, "%d", &bright);
    fclose(fp);

    switch (argc) {
    case 1:
    printf("%d\n", bright);
    break;
    case 2:
    fp = fopen(kFileName, "w");
    bright += atoi(argv[1]);
    fprintf(fp, "%d\n", bright);
    fclose(fp);
    break;
    default:
    fprintf(stderr, "Usage: lcd-brightness [value]\n");
    return -1;
    }

    return 0;
    }


    Now, you don't have to create 2 more scripts to increase/decrease - just type:
    lcd-brigthness +[number] - to increase by number
    lcd-brigthness -[number] - to decrease by number
    lcd-brigthness - to print the current number

    (Of course correct the path to the brightness file so it will match yours and make sure to set the write chmode)

    ReplyDelete
  23. I`m with Lubuntu (kernel 3.0.x) on Vostro 3350 w hybrid GFX(ATI+Sandy). So i have: /sys/class/backlight/acpi_video1/brightness - for SandyBr. GFX and
    /sys/class/backlight/acpi_video0/brightness - for ATI
    and /sys/class/backlight/intel_backlight/brightness - which does nothing.
    Since I didn`t manage to setup GFX switching properly(yet), I use acpi_call module to disable ATI on startup to save power & fan noise.
    I checked the scripts - it works, when i change acpi_video0 to acpi_video1, thanks - it gives me idea how it works, but xbacklight utility mentioned above is the general solution for me.

    ReplyDelete