/** * Brightness control daemon * Copyright (c) 2006-2007, Phillip Berndt * * 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 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Compile using: * gcc -lX11 -lXss bright.c * */ #define RELEASE "0.4_beta4" /* #define DEBUG */ /* Includes {{{ */ #include <unistd.h> #include <signal.h> #include <string.h> #include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/extensions/scrnsaver.h> #include <dirent.h> #include <stdio.h> #include <stdlib.h> #include <glob.h> #include <fcntl.h> #include <unistd.h> #include <sys/select.h> #include <pthread.h> #include <signal.h> #include <sys/stat.h> #include <sys/wait.h> #include <sys/types.h> #include <pwd.h> #include <time.h> #include <regex.h> #include <sys/inotify.h> /* }}} */ /* Overall program settings {{{ */ #ifdef DEBUG char verbose = 1; #else char verbose = 0; #endif char daemonize = 0; char force = 0; int waitSeconds = 3; int darkBright = 0; int maxBright = 0; char isDark = 0; char savedLevel = 0; char beforeFade = 0; time_t lastFade = 0; char b_class[21]; char actualBrightnessFile[255]; char brightnessFile[255]; char b_fifo[255]; int bfifoFd = 0; FILE *brightnessFd; regex_t eventSourceFilter; Display *display = NULL; /* Required prototypes */ void signalHandlerAlarm(int sig); /* }}} */ /* * Write an error to stderr and exit */ void error(char *str) { /* {{{ */ if(daemonize == 1) { return; } fputs(str, stderr); fputs("\n", stderr); #ifdef DEBUG #include <errno.h> printf("libc says: %s\n", strerror(errno)); #endif /* Close display */ if(display) { XCloseDisplay(display); } /* Quit */ exit(1); } /* }}} */ /* * Write an debug message to the console */ inline void info(char *str) { /*{{{*/ if(verbose == 1 && daemonize == 0) { printf("%s\n", str); } } /*}}}*/ /* * Function to get the brightness of the pc */ int getBrightness() { /*{{{*/ char line[255]; FILE *brightness = fopen(actualBrightnessFile, "r"); if(!brightness) { error("Failed to open /sys/class/backlight/*/actual_brightness. Do you have the " "ACPI extensions for the class supplied using -c in your kernel?"); } fgets(line, 255, brightness); int retVal = atoi(line); fclose(brightness); return retVal; } /*}}}*/ /* * Function to set the brightness of the * display * * Must be done stepwise! */ void setBrightness(int level) { /*{{{*/ char output[255]; int i, plevel; int saved; saved = alarm(0); signal(SIGALRM, SIG_IGN); lastFade = time(NULL); plevel = getBrightness(); for(i=plevel; ; i += (plevel<level ? 1 : -1)) { sprintf(output, "%i\n", i); if(fputs(output, brightnessFd) == EOF) { error("Failed to update brightness."); } fflush(brightnessFd); if(i == level) { break; } /* We had to disable signal handler and alarm because of this: */ usleep(200); } signal(SIGALRM, signalHandlerAlarm); alarm(saved); } /*}}}*/ /* * Check whether the PC is on AC */ int isOnAC() /*{{{*/ { char line[255]; FILE *ac = fopen("/proc/acpi/ac_adapter/AC/state", "r"); if(!ac) { /* Ignore this */ return 0; } fgets(line, 255, ac); fclose(ac); return strstr(line, "on-line") != NULL; } /*}}}*/ /* * Signal handler; just quit */ void signalHandlerQuit(int signal) { /*{{{*/ if(display != NULL) { info("Closing display"); XCloseDisplay(display); } if(bfifoFd != 0) { /* This may or may not work */ close(bfifoFd); if(unlink(b_fifo) != 0) { info("Failed to remove FIFO - this does only work when you didn't drop privileges"); } } error("Received signal. Exiting..."); } /*}}}*/ /* * Get IDLE time * Taken from GAJIM source, src/common/idle.c * * Modified to return -1 when the screen saver is deactivated */ int getIdleTime() { /*{{{*/ static XScreenSaverInfo *mit_info = NULL; int idle_time, event_base, error_base; if (XScreenSaverQueryExtension(display, &event_base, &error_base)) { if (mit_info == NULL) { mit_info = XScreenSaverAllocInfo(); } XScreenSaverQueryInfo(display, RootWindow(display, 0), mit_info); if(mit_info->state == 3) { /* ScreenSaverDisabled */ idle_time = -1; } else { idle_time = (mit_info->idle) / 1000; } } else { idle_time = 0; } return idle_time; } /*}}}*/ /* * Print a help message and exit */ void printHelp() /*{{{*/ { printf("brightd %s\nA X11-daemon for iBook-like brightness management\nCopyright (c) 2006-2007, Phillip Berndt\n\nOptions:\n", RELEASE); printf(" -v Be verbose\n"); printf(" -d Daemonize\n"); printf(" -u n Drop privileges to this user (Defaults to nobody)\n"); printf(" -w n Wait n seconds before reducing brightness (Defaults to 3)\n"); printf(" -b n The brightness setting for the dark screen (Defaults zu 0)\n"); printf(" -f Reduce brightness even if on the highest brightness level\n"); printf(" Specify twice to also do so when on AC\n"); printf(" -e n Filter event sources using regexp n (on /dev/input/by-path\n"); printf(" -c n Set the backlight class to use (defaults to the first\n"); printf(" subnode of /sys/class/backlight)\n"); printf(" -x Don't query X11 Xss extension\n"); printf(" -r n Create a FIFO, into which acpid may write the new level when the user\n"); printf(" changed display brightness\n"); printf("\n"); exit(0); } /*}}}*/ /* * Load default brightness class into b_class */ void loadDefaultClass() /*{{{*/ { struct dirent *dirEntry; DIR *backlightDir = opendir("/sys/class/backlight"); if(backlightDir) { while(1) { dirEntry = readdir(backlightDir); if(!dirEntry) { break; } if(dirEntry->d_name[0] == '.') { continue; } strcpy(b_class, dirEntry->d_name); if(verbose == 1) { printf("Using brightness class %s\n", b_class); } closedir(backlightDir); return; } closedir(backlightDir); } strcpy(b_class, "none"); } /*}}}*/ /* * Event source filter */ inline char isEventFileValid(char *file) { /* {{{ */ if(*((char*)&eventSourceFilter) == 0) { return 1; } else { return regexec(&eventSourceFilter, file, 0, NULL, 0) == 0; } } /* }}} */ /** * Application logic code {{{ */ void signalHandlerAlarm(int sig) { /*{{{*/ /* Reenable the handler */ signal(sig, signalHandlerAlarm); if(isDark == 0) { /* Check if fading is okay */ if(isOnAC() && force < 2) { info("Would fade, but on AC"); alarm(waitSeconds); return; } if(getBrightness() == maxBright && force < 1) { info("Would fade, but maximum brightness level selected"); alarm(waitSeconds); return; } if(display != NULL && getIdleTime() < waitSeconds) { info("Would fade, but screensaver denies to do so"); alarm(waitSeconds); return; } /* Fade */ info("Fading to dark"); beforeFade = getBrightness(); savedLevel = darkBright; setBrightness(darkBright); isDark = 1; } return; } /*}}}*/ void handleReceivedEvent() { /*{{{*/ /* Reset alarm */ alarm(waitSeconds); /* Fade back to light */ if(isDark == 1) { if(verbose == 1 && daemonize == 0) { printf("Fading to light (level %d)\n", beforeFade); } savedLevel = beforeFade; setBrightness(beforeFade); isDark = 0; } } /*}}}*/ void userChangedBrightness(int toLevel) { /*{{{*/ if(verbose == 1 && daemonize == 0) { printf("User changed brightness level to %d\n", toLevel); } /* Reset alarm */ alarm(waitSeconds); if(isDark == 1 && toLevel > darkBright) { /* Fade to max immediately */ info("Maximum lightness, because user increased brightness"); setBrightness(maxBright); savedLevel = maxBright; isDark = 0; } if(toLevel < darkBright) { /* Deny this */ info("Sorry, you can't lower brightness below darkBright"); setBrightness(darkBright); savedLevel = darkBright; } savedLevel = toLevel; } /*}}}*/ /* }}} */ /* * The main program */ int main(int argc, char *argv[]) { /*{{{*/ int defaultBrightness = 0; int oldBrightness = 0; int newBrightness = 0; int highFd = 0; int noXCode = 0; int eventReceived; int inotifyFdX11; int inotifyFdEvents; int i; int fd; int filesCount; char option; char user[25]; char buf[255]; char buf2[255]; struct passwd *userStruct; glob_t *events; fd_set rfds; fd_set openfds; struct inotify_event *inotifyEvent; #ifdef DEBUG printf("DEBUG BUILD\n"); #endif /* Load settings {{{ */ strcpy(user, "nobody"); loadDefaultClass(); opterr = 0; if(argc == 0) { printHelp(); } regcomp(&eventSourceFilter, ".*event.*", REG_EXTENDED | REG_NOSUB); while((option = getopt(argc, argv, "vdfxe:w:b:c:r:u:")) > 0) { switch(option) { case 'v': /* Verbose */ verbose = 1; break; case 'w': /* Wait n seconds before fading */ waitSeconds = atoi(optarg); if(waitSeconds <= 0) { printHelp(); } break; case 'd': /* Daemonize */ daemonize = 1; break; case 'e': /* Event source filter */ /* Compile regex */ i = regcomp(&eventSourceFilter, optarg, REG_EXTENDED | REG_NOSUB); if(i != 0) { regerror(i, &eventSourceFilter, buf, 255); printf("Error: %s\n", buf); error("Regex compilation failed"); } break; case 'b': /* Darkest setting */ darkBright = atoi(optarg); if(darkBright < 0 || darkBright > 5) { printHelp(); } break; case 'f': /* Force fading */ force++; break; case 'u': /* User */ if(strlen(optarg) > 20) { error("The user name should not be longer than 20 characters."); } strcpy(user, optarg); break; case 'c': /* Brightness class */ if(strlen(optarg) > 20) { error("The class should not be longer than 20 characters."); } strcpy(b_class, optarg); break; case 'x': /* Deactivate X-Code */ noXCode = 1; break; case 'r': /* Brightness FIFO */ if(strlen(optarg) > 250) { error("The filename should not be longer than 250 characters."); } strcpy(b_fifo, optarg); bfifoFd = 1; break; default: printHelp(); } } /* }}} */ /* Load some sysfs and default settings stuff {{{ */ /* Get the user ID */ userStruct = getpwnam(user); if(userStruct == NULL) { error("Failed to get the UID for the specified user"); } /* Set files in sysfs to use */ sprintf(brightnessFile, "/sys/class/backlight/%s/brightness", b_class); brightnessFd = fopen(brightnessFile, "w"); if(!brightnessFd) { printf("Failed to open %s for writing.\n", brightnessFile); error( "- Do you have the correct ACPI extensions in your kernel?\n" "- Do you have permissions to write to that file?"); } /* Load maximum brightness; abuse some not yet initialised variables to do so ;) */ sprintf(actualBrightnessFile, "/sys/class/backlight/%s/max_brightness", b_class); maxBright = getBrightness(); if(verbose == 1 && daemonize == 0) { printf("Maximum brightness is %d\n", maxBright); } sprintf(actualBrightnessFile, "/sys/class/backlight/%s/actual_brightness", b_class); /* Load brightness */ defaultBrightness = newBrightness = oldBrightness = getBrightness(); /* Open all event files */ FD_ZERO(&openfds); events = (glob_t *)malloc(sizeof(glob_t)); if(glob("/dev/input/by-path/*", 0, NULL, events) != 0) { printf("Failed to list event devices in /dev/input/by-path/\n"); exit(1); } filesCount = events->gl_pathc; for(i=0; i<filesCount; i++) { if(!isEventFileValid(events->gl_pathv[i])) { continue; } fd = open(events->gl_pathv[i], O_RDONLY | O_NONBLOCK); #ifdef DEBUG printf("%d: %s\n", fd, events->gl_pathv[i]); #endif if(fd == -1) { printf("Failed to open %s\n", events->gl_pathv[i]); error("You have to be root to run this"); } if(fd > highFd) { highFd = fd; } FD_SET(fd, &openfds); } globfree(events); /* Create inotify mapping for that directory */ inotifyFdEvents = inotify_init(); if(inotifyFdEvents != -1) { if(inotify_add_watch(inotifyFdEvents, "/dev/input/by-path/", IN_CREATE) == -1) { close(inotifyFdEvents); inotifyFdEvents = -1; } if(inotifyFdEvents > highFd) { highFd = inotifyFdEvents; } } /* Open FIFO */ if(bfifoFd == 1) { info("Creating and opening FIFO"); unlink(b_fifo); if(mkfifo(b_fifo, 0777) != 0) { error("Failed to create FIFO"); } bfifoFd = open(b_fifo, O_RDONLY | O_NONBLOCK); #ifdef DEBUG printf("%d: Fifo\n", bfifoFd); #endif if(bfifoFd == -1) { error("Failed to open FIFO for reading"); } if(bfifoFd > highFd) { highFd = bfifoFd; } } /* }}} */ /* Signal handlers, daemonize and X-Server opening {{{ */ /* Create signal handler for closing the display */ signal(SIGINT, signalHandlerQuit); signal(SIGHUP, signalHandlerQuit); signal(SIGALRM, signalHandlerAlarm); /* Daemonize */ #ifndef DEBUG if(daemonize == 1) { daemon(0, 0); } #endif /* Wait for an X-Server to appear */ if(noXCode == 0) { inotifyFdX11 = inotify_init(); if(inotifyFdX11 != -1) { if(inotify_add_watch(inotifyFdX11, "/tmp/.X11-unix/", IN_CREATE) == -1) { close(inotifyFdX11); inotifyFdX11 = -1; } } while(1) { /* Try opening $DISPLAY first */ if((display = XOpenDisplay(NULL)) != NULL) { info("Opened X11 Display from ENV{DISPLAY}"); } else { /* Failed to open $DISPLAY - wait for X11 to appear */ if(inotifyFdX11 != -1) { info("Waiting for an X-Server to appear(!)"); read(inotifyFdX11, buf, sizeof(struct inotify_event) * 2); inotifyEvent = (struct inotify_event *)buf; strncpy(buf, inotifyEvent->name, 3); buf[0] = ':'; buf[2] = 0; if(verbose == 1 && daemonize == 0) { printf("Found X-Server '%s'; I will try to open a connection to it every 5 seconds\n", buf); } } else { info("inotify on /tmp/.X11-unix failed - polling for ':0'"); strcpy(buf, ":0"); } display = XOpenDisplay(buf); while(display == NULL) { sleep(5); display = XOpenDisplay(buf); } } info("Opened a connection"); if(fork() == 0) { break; } waitpid(0, NULL, 0); info("Connection lost."); sleep(5); } } /* Drop privileges */ setegid(userStruct->pw_gid); setgid(userStruct->pw_gid); seteuid(userStruct->pw_uid); setuid(userStruct->pw_uid); /* }}} */ /* Wait for input */ alarm(waitSeconds); while(1) { eventReceived = 0; rfds = openfds; /* Add inotify and FIFO to watch list */ if(inotifyFdEvents != 0) { FD_SET(inotifyFdEvents, &rfds); } if(bfifoFd != 0) { FD_SET(bfifoFd, &rfds); } /* Select filepointers */ while(select(highFd + 1, &rfds, NULL, NULL, NULL) == -1) { #ifdef DEBUG info("DEBUG: Select failed."); #endif } /* Check for event fd */ for(fd=0; fd<sizeof(fd_set) * 8; fd++) { if(FD_ISSET(fd, &openfds) && FD_ISSET(fd, &rfds)) { #ifdef DEBUG printf("DEBUG: fd %d fired\n", fd); #endif if(read(fd, buf, 250) == -1) { #ifdef DEBUG printf("DEBUG: fd %d closed\n", fd); #endif close(fd); FD_CLR(fd, &openfds); if(fd == highFd) { i = fd; while(i > 0 && (!FD_ISSET(--i, &openfds)) && (i != bfifoFd)); highFd = i + 1; } } else { eventReceived = 1; } } } /* Event received */ if(eventReceived) { handleReceivedEvent(); } /* FIFO action */ if(bfifoFd != 0 && FD_ISSET(bfifoFd, &rfds)) { memset(buf, 0, 5); if(read(bfifoFd, buf, 2) != -1) { if(strlen(buf) > 0) { #ifdef DEBUG printf("Fifo said: %s\n", buf); #endif i = atoi(buf); if(savedLevel != i && lastFade + 1 < time(NULL)) { userChangedBrightness(i); } } else { /* Reopen FIFO */ close(bfifoFd); bfifoFd = open(b_fifo, O_RDONLY | O_NONBLOCK); #ifdef DEBUG info("Reopening FIFO"); #endif if(bfifoFd == -1) { error("Failed to open FIFO for reading"); } if(bfifoFd > highFd) { highFd = bfifoFd; } } } } /* Inotify on /dev/input */ if(inotifyFdEvents != -1 && FD_ISSET(inotifyFdEvents, &rfds)) { memset(buf, 0, sizeof(struct inotify_event) + 51); read(inotifyFdEvents, buf, sizeof(struct inotify_event) + 50); inotifyEvent = (struct inotify_event *)buf; if(inotifyEvent->len > 0 && strstr(inotifyEvent->name, "event") != NULL) { sprintf(buf2, "/dev/input/by-path/%s", inotifyEvent->name); if(!isEventFileValid(buf2)) { continue; } fd = open(buf2, O_RDONLY | O_NONBLOCK); #ifdef DEBUG printf("%d: %s\n", fd, buf2); #endif if(fd == -1) { if(verbose == 1 && daemonize == 0) { printf("Failed to open %s\n", buf2); } } else { if(fd > highFd) { highFd = fd; } FD_SET(fd, &openfds); } } } } } /*}}}*/ /* vim:ft=c:fileencoding=iso-8859-1:foldmethod=marker **/