flak rss random

xautobacklight

Some newer laptops adjust the screen brightness according to ambient light in the room. This is fairly annoying in most cases, because what I really care about is the relative brightness of the screen contents. White web pages are too bright in a dark room. Fortunately, there’s a tool, Lumen, which can adjust the backlight based on actual brightness. Unfortunately, it’s for somebody else’s computer.

In order to write xautobacklight we need to do about three things. We need to measure the screen brightness (and consequently detect changes). We need to adjust the backlight to a comfortable level. And, as a bonus, we need to fiddle with the contrast.

The first part is easy. XGetImage on the root window. There is some code in xwd that makes this appear really complicated, because it handles nightmare scenarios like overlay planes of multiple bit depths. Let us say no more about that. We’re going to call XGetImage once, get a nice 32 bit RGBA bitmap equal to the screen size, and add up the pixels. We’ll weight blue and green a little higher.

        image = XGetImage(dpy, root, 0, 0, w, h, AllPlanes, ZPixmap);
        size = image->bytes_per_line * image->height;
        for (int i = 0; i < h; i++) { 
                unsigned char *line = image->data + i * image->bytes_per_line;
                for (int j = 0; j < w; j++) {
                        unsigned char *pix = line + j * image->bits_per_pixel / 8;
                        unsigned char r = pix[0];
                        unsigned char g = pix[1];
                        unsigned char b = pix[2];
                        unsigned char a = pix[3];

                        brightness += r + b * 1.2 + g * 1.5;
                }
        }
        XDestroyImage(image);

Don’t forget to call XDestroyImage. The man page for XGetImage doesn’t mention it, but it’s kinda important.

Detecting screen changes is somewhat trickier. We could be lazy and just poll, but that’s lame. We can also watch for various X events that are correlated with switching between xterms and browsers. Turns out if you use dwm like you should, this is super easy. Switching between desktops will unmap and map all the windows on the old and new screens. More importantly for our purposes, it will assign focus to the root window while the transition is in progress. It’s possible to watch for focus changes on the whole window tree, but then we’d get lots of spurious events switching between xterms. It’s also possible to watch for the map and unmap events, but again, we get a multitude of events for a single desktop switch.

        XSelectInput(dpy, root, FocusChangeMask);

Unfortunately, we can’t use XSelectInput to listen for button presses in other windows, so we miss clicks in the browser. If you navigate from dark pages to light pages, we won’t notice. Meh.

We’ll bypass X to set the brightness. It’s much simpler to do this with wscons.

        struct wsdisplay_param param;
        memset(&param, 0, sizeof(param));
#if 0
        param.param = WSDISPLAYIO_PARAM_BACKLIGHT;
#else
        param.param = WSDISPLAYIO_PARAM_BRIGHTNESS;
#endif
        ioctl(consfd, WSDISPLAYIO_GETPARAM, &param);
        param.curval = param.min + param.max * percent / 100;
        ioctl(consfd, WSDISPLAYIO_SETPARAM, &param);

There is a backlight property, but on most machines we actually want the brightness control. (The inteldrm driver calls it SDVO_CMD_GET_BRIGHTNESS and such. If you still have a zaurus, it uses backlight.) There’s also a way to do this with X, but it involves magic atoms.

One of the problems with reducing brightness is that text can become harder to read because we’ve also reduced effective contrast. Fortunately, we already know how to adjust the gamma so let’s try something along those lines.

We can increase contrast by compressing the gamma curve, then splitting it into two disjoint parts. Pull all the dark colors down and make them darker. Push all the light colors up and make them lighter.

                for (int i = 0; i < size; i++) {
                        double g = 65535.0 * i / size * cutoff;
                        if (i > size * cutoff)
                                g += 65535.0 * (1 - cutoff);
                        g *= scale;
                        crtc_gamma->red[i] = g;
                        crtc_gamma->green[i] = g;
                        crtc_gamma->blue[i] = g;
                }
                XRRSetCrtcGamma(dpy, crtcxid, crtc_gamma);

When we reduce screen brightness, we’ll run this code with a cutoff of 0.5. This would work great except oh my god no it doesn’t everything is terrible. Turns out some fancy pants designers make web pages where the text is colored rgb(130, 130, 130), falling into the lighter half of the curve and therefore getting pushed even lighter. Oh, well, I didn’t want to read your page anyway.

All together:

        XSelectInput(dpy, root, FocusChangeMask);
        while (1) {
                XNextEvent(dpy, &ev);
                brightness = getbrightness(dpy, screen, root);
                if (brightness > 400)
                        dim = 1;
                else
                        dim = 0;
                if (dim != olddim) {
                        setbrightness(dim ? 30 : 60);
                        adjustgamma(dpy, root, dim ? 0.5 : 1.0);
                }
                olddim = dim;
        }

xautobacklight.c. Error checking is reserved for people who make mistakes.

One may also consider xdimmer.

Posted 09 Aug 2016 17:33 by tedu Updated: 24 Dec 2023 17:19
Tagged: openbsd project x11