├── Makefile ├── xdimmer.1 ├── README.md └── xdimmer.c /Makefile: -------------------------------------------------------------------------------- 1 | # vim:ts=8 2 | 3 | CC ?= cc 4 | CFLAGS ?= -O2 5 | CFLAGS += -Wall -Wunused -Wmissing-prototypes -Wstrict-prototypes -Wunused 6 | 7 | PREFIX ?= /usr/local 8 | BINDIR ?= $(DESTDIR)$(PREFIX)/bin 9 | MANDIR ?= $(DESTDIR)$(PREFIX)/man/man1 10 | 11 | INSTALL_PROGRAM ?= install -s 12 | INSTALL_DATA ?= install 13 | 14 | X11BASE ?= /usr/X11R6 15 | INCLUDES?= -I$(X11BASE)/include 16 | LDPATH ?= -L$(X11BASE)/lib 17 | LIBS += -lX11 -lXrandr -lXext -lXi -lm 18 | 19 | PROG = xdimmer 20 | OBJS = xdimmer.o 21 | 22 | all: $(PROG) 23 | 24 | $(PROG): $(OBJS) 25 | $(CC) $(OBJS) $(LDPATH) $(LIBS) -o $@ 26 | 27 | $(OBJS): *.c 28 | $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ 29 | 30 | README.md: xdimmer.1 31 | mandoc -T markdown xdimmer.1 > README.md 32 | 33 | install: all 34 | mkdir -p $(BINDIR) 35 | $(INSTALL_PROGRAM) $(PROG) $(BINDIR) 36 | mkdir -p $(MANDIR) 37 | $(INSTALL_DATA) -m 644 xdimmer.1 $(MANDIR)/xdimmer.1 38 | 39 | clean: 40 | rm -f $(PROG) $(OBJS) 41 | 42 | .PHONY: all install clean 43 | -------------------------------------------------------------------------------- /xdimmer.1: -------------------------------------------------------------------------------- 1 | .Dd $Mdocdate: January 18 2024$ 2 | .Dt XDIMMER 1 3 | .Os 4 | .Sh NAME 5 | .Nm xdimmer 6 | .Nd dim the screen and/or the keyboard backlight when idle or when ambient 7 | light changes 8 | .Sh SYNOPSIS 9 | .Nm 10 | .Op Fl a 11 | .Op Fl b Ar brighten steps 12 | .Op Fl d 13 | .Op Fl K 14 | .Op Fl k 15 | .Op Fl n 16 | .Op Fl p Ar percent 17 | .Op Fl s Ar dim steps 18 | .Op Fl t Ar timeout 19 | .Sh DESCRIPTION 20 | .Nm 21 | waits 22 | .Ar timeout 23 | number of seconds and if no keyboard or mouse input is detected, the screen 24 | backlight is dimmed to 25 | .Ar percent 26 | in 27 | .Ar dim steps 28 | steps, unless the 29 | .Ar -n 30 | option was specified. 31 | Once keyboard or mouse input is detected, the screen backlight is restored 32 | to its previous brightness value in 33 | .Ar brighten steps 34 | steps. 35 | .Pp 36 | On OpenBSD, if the 37 | .Ar -k 38 | option is used, the keyboard backlight is also dimmed to zero and restored 39 | upon movement via the 40 | .Xr wscons 4 41 | interface. 42 | .Pp 43 | Also on OpenBSD, if the 44 | .Ar -a 45 | option is used and an ambient light sensor device is located via 46 | .Xr sysctl 8 , 47 | screen backlight (and keyboard backlight if 48 | .Ar -k 49 | is used) is dimmed or brightened based on lux readings. 50 | .Sh OPTIONS 51 | .Bl -tag -width Ds 52 | .It Fl a 53 | Change backlights according to ambient light sensor lux readings. 54 | Currently only supported on OpenBSD. 55 | .It Fl b Ar steps 56 | Number of steps to take while restoring backlight. 57 | The default is 58 | .Dv 5 59 | steps. 60 | .It Fl d 61 | Print debugging messages to stdout. 62 | .It Fl K 63 | Only listen for keyboard input when resetting the idle timer. 64 | .It Fl k 65 | Affect the keyboard backlight as well as the screen backlight. 66 | Currently only supported on OpenBSD. 67 | .It Fl n 68 | Do not adjust the screen backlight when idle. 69 | .It Fl p Ar percent 70 | Absolute brightness value to which the backlight is dimmed. 71 | The default is 72 | .Dv 10 73 | percent. 74 | .It Fl s Ar steps 75 | Number of steps to take while decrementing backlight. 76 | The default is 77 | .Dv 20 78 | steps. 79 | .It Fl t Ar timeout 80 | Number of seconds to wait without receiving input before dimming. 81 | The default is 82 | .Dv 120 83 | seconds. 84 | .Sh SIGNALS 85 | .Bl -tag -width "SIGUSR1" -compact 86 | .It Dv SIGINT 87 | .Nm 88 | will exit, attempting to brighten the screen and/or keyboard before 89 | exiting if they are currently in a dimmed state. 90 | .Pp 91 | .It Dv SIGUSR1 92 | .Nm 93 | will immediately dim the screen and/or keyboard (depending on 94 | .Ar -k 95 | and 96 | .Ar -n 97 | options). 98 | .Pp 99 | .It Dv SIGUSR2 100 | .Nm 101 | will immediately brighten the screen and/or keyboard if they are 102 | in a dimmed state. 103 | .Sh AUTHORS 104 | .Nm 105 | was written by 106 | .An joshua stein Aq Mt jcs@jcs.org . 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | XDIMMER(1) - General Commands Manual 2 | 3 | # NAME 4 | 5 | **xdimmer** - dim the screen and/or the keyboard backlight when idle or when ambient 6 | light changes 7 | 8 | # SYNOPSIS 9 | 10 | **xdimmer** 11 | \[**-a**] 12 | \[**-b** *brighten steps*] 13 | \[**-d**] 14 | \[**-k**] 15 | \[**-n**] 16 | \[**-p** *percent*] 17 | \[**-s** *dim steps*] 18 | \[**-t** *timeout*] 19 | 20 | # DESCRIPTION 21 | 22 | **xdimmer** 23 | waits 24 | *timeout* 25 | number of seconds and if no keyboard or mouse input is detected, the screen 26 | backlight is dimmed to 27 | *percent* 28 | in 29 | *dim steps* 30 | steps, unless the 31 | *-n* 32 | option was specified. 33 | Once keyboard or mouse input is detected, the screen backlight is restored 34 | to its previous brightness value in 35 | *brighten steps* 36 | steps. 37 | 38 | On OpenBSD, if the 39 | *-k* 40 | option is used, the keyboard backlight is also dimmed to zero and restored 41 | upon movement via the 42 | wscons(4) 43 | interface. 44 | 45 | Also on OpenBSD, if the 46 | *-a* 47 | option is used and an ambient light sensor device is located via 48 | sysctl(8), 49 | screen backlight (and keyboard backlight if 50 | *-k* 51 | is used) is dimmed or brightened based on lux readings. 52 | 53 | # OPTIONS 54 | 55 | **-a** 56 | 57 | > Change backlights according to ambient light sensor lux readings. 58 | > Currently only supported on OpenBSD. 59 | 60 | **-b** *steps* 61 | 62 | > Number of steps to take while restoring backlight. 63 | > The default is 64 | > `5` 65 | > steps. 66 | 67 | **-d** 68 | 69 | > Print debugging messages to stdout. 70 | 71 | **-k** 72 | 73 | > Affect the keyboard backlight as well as the screen backlight. 74 | > Currently only supported on OpenBSD. 75 | 76 | **-n** 77 | 78 | > Do not adjust the screen backlight when idle. 79 | 80 | **-p** *percent* 81 | 82 | > Absolute brightness value to which the backlight is dimmed. 83 | > The default is 84 | > `10` 85 | > percent. 86 | 87 | **-s** *steps* 88 | 89 | > Number of steps to take while decrementing backlight. 90 | > The default is 91 | > `20` 92 | > steps. 93 | 94 | **-t** *timeout* 95 | 96 | > Number of seconds to wait without receiving input before dimming. 97 | > The default is 98 | > `120` 99 | > seconds. 100 | 101 | # SIGNALS 102 | 103 | `SIGINT` 104 | 105 | > **xdimmer** 106 | > will exit, attempting to brighten the screen and/or keyboard before 107 | > exiting if they are currently in a dimmed state. 108 | 109 | `SIGUSR1` 110 | 111 | > **xdimmer** 112 | > will immediately dim the screen and/or keyboard (depending on 113 | > *-k* 114 | > and 115 | > *-n* 116 | > options). 117 | 118 | `SIGUSR2` 119 | 120 | > **xdimmer** 121 | > will immediately brighten the screen and/or keyboard if they are 122 | > in a dimmed state. 123 | 124 | # AUTHORS 125 | 126 | **xdimmer** 127 | was written by 128 | joshua stein <[jcs@jcs.org](mailto:jcs@jcs.org)>. 129 | 130 | OpenBSD 6.6 - August 27, 2019 131 | -------------------------------------------------------------------------------- /xdimmer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * xdimmer 3 | * Copyright (c) 2013-2019 joshua stein 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 3. The name of the author may not be used to endorse or promote products 15 | * derived from this software without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #ifdef __linux__ 38 | #include 39 | #else 40 | #include 41 | #endif 42 | #include 43 | #include 44 | #ifdef __linux__ 45 | #include 46 | #else 47 | #include 48 | #endif 49 | 50 | #ifdef __OpenBSD__ 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #endif 58 | 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | 67 | #define DEFAULT_DIM_TIMEOUT 120 68 | #define DEFAULT_DIM_PERCENTAGE 10 69 | 70 | #define DEFAULT_DIM_STEPS 20 71 | #define DEFAULT_BRIGHTEN_STEPS 5 72 | 73 | enum { 74 | OP_GET, 75 | OP_SET, 76 | }; 77 | 78 | enum { 79 | MSG_EXIT = 1, 80 | MSG_DIM, 81 | MSG_BRIGHTEN, 82 | }; 83 | 84 | #ifdef __OpenBSD__ 85 | static const struct als_setting { 86 | char *label; 87 | int min_lux; 88 | int backlight; 89 | int kbd_backlight; 90 | } als_settings[] = { 91 | /* scene min lux screen kbd */ 92 | { "pitch black", 0, 20, 80 }, 93 | { "very dark", 11, 30, 70 }, 94 | { "dark indoors", 51, 40, 60 }, 95 | { "dim indoors", 201, 50, 50 }, 96 | { "normal indoors", 401, 60, 40 }, 97 | { "bright indoors", 1001, 70, 30 }, 98 | { "dim outdoors", 5001, 80, 20 }, 99 | { "cloudy outdoors", 10001, 90, 10 }, 100 | { "sunlight", 30001, 100, 0 }, 101 | }; 102 | #endif 103 | 104 | void xloop(void); 105 | void set_alarm(XSyncAlarm *, XSyncTestType); 106 | void bail(int); 107 | void sigusr1(int); 108 | void sigusr2(int); 109 | void stepper(float, float, int, int); 110 | float backlight_op(int, float); 111 | float kbd_backlight_op(int, float); 112 | int als_find_sensor(void); 113 | void als_fetch(void); 114 | void usage(void); 115 | int XPeekEventOrTimeout(Display *, XEvent *, unsigned int); 116 | int pipemsg[2]; 117 | 118 | extern char *__progname; 119 | 120 | /* options */ 121 | static int dim_kbd = 0; 122 | static int dimmed = 0; 123 | static int dim_screen = 1; 124 | static int use_als = 0; 125 | static int kbd_idle_only = 0; 126 | 127 | /* ALS reading */ 128 | static float als = -1; 129 | 130 | /* backlight reading and target while dimming/undimming */ 131 | static float backlight = -1; 132 | static float kbd_backlight = -1; 133 | 134 | static int dim_timeout = DEFAULT_DIM_TIMEOUT; 135 | static int dim_pct = DEFAULT_DIM_PERCENTAGE; 136 | static int dim_steps = DEFAULT_DIM_STEPS; 137 | static int brighten_steps = DEFAULT_BRIGHTEN_STEPS; 138 | 139 | static Atom backlight_a = 0; 140 | static XSyncCounter idler_counter = 0; 141 | static int exiting = 0; 142 | static int force_dim = 0; 143 | static int force_brighten = 0; 144 | static int debug = 0; 145 | #define DPRINTF(x) { if (debug) { printf x; } }; 146 | 147 | #ifdef __OpenBSD__ 148 | static int wsconsdfd = 0; 149 | static int wsconskfd = 0; 150 | int alsmib[5] = { CTL_HW, HW_SENSORS, 0, 0, 0 }; 151 | #endif 152 | 153 | static Display *dpy; 154 | 155 | int 156 | main(int argc, char *argv[]) 157 | { 158 | int ch; 159 | 160 | while ((ch = getopt(argc, argv, "ab:dkKnp:s:t:")) != -1) { 161 | const char *errstr; 162 | 163 | switch (ch) { 164 | case 'a': 165 | #ifndef __OpenBSD__ 166 | errx(1, "ambient light sensors not supported on this " 167 | "platform"); 168 | #endif 169 | use_als = 1; 170 | break; 171 | case 'b': 172 | brighten_steps = strtonum(optarg, 1, 100, &errstr); 173 | if (errstr) 174 | errx(2, "brighten steps: %s", errstr); 175 | break; 176 | case 'd': 177 | debug = 1; 178 | break; 179 | case 'k': 180 | #ifndef __OpenBSD__ 181 | errx(1, "keyboard backlight not supported on this " 182 | "platform"); 183 | #endif 184 | dim_kbd = 1; 185 | break; 186 | case 'K': 187 | kbd_idle_only = 1; 188 | break; 189 | case 'n': 190 | dim_screen = 0; 191 | break; 192 | case 'p': 193 | dim_pct = strtonum(optarg, 1, 100, &errstr); 194 | if (errstr) 195 | errx(2, "dim percentage: %s", errstr); 196 | break; 197 | case 's': 198 | dim_steps = strtonum(optarg, 1, 500, &errstr); 199 | if (errstr) 200 | errx(2, "dim steps: %s", errstr); 201 | break; 202 | case 't': 203 | dim_timeout = strtonum(optarg, 1, INT_MAX, &errstr); 204 | if (errstr) 205 | errx(2, "dim timeout: %s", errstr); 206 | break; 207 | default: 208 | usage(); 209 | } 210 | } 211 | argc -= optind; 212 | argv += optind; 213 | 214 | if (!dim_screen && !dim_kbd && !use_als) 215 | errx(1, "not dimming screen or keyboard, nothing to do"); 216 | 217 | if (!(dpy = XOpenDisplay(NULL))) 218 | errx(1, "can't open display %s", XDisplayName(NULL)); 219 | 220 | if (dim_screen || use_als) { 221 | backlight_a = XInternAtom(dpy, RR_PROPERTY_BACKLIGHT, True); 222 | if (backlight_a == None) { 223 | #ifdef __OpenBSD__ 224 | /* see if wscons display.brightness is available */ 225 | if (!(wsconsdfd = open("/dev/ttyC0", O_WRONLY)) || 226 | backlight_op(OP_GET, 0) < 0) 227 | #endif 228 | errx(1, "no backlight control"); 229 | } 230 | } 231 | 232 | #ifdef __OpenBSD__ 233 | if (dim_kbd) 234 | if (!(wsconskfd = open("/dev/wskbd0", O_WRONLY)) || 235 | kbd_backlight_op(OP_GET, 0) < 0) 236 | errx(1, "no keyboard backlight control"); 237 | 238 | if (use_als && !als_find_sensor()) 239 | errx(1, "can't find ambient light sensor"); 240 | #endif 241 | 242 | if (dim_screen) 243 | DPRINTF(("dimming screen to %d%% in %d secs\n", dim_pct, 244 | dim_timeout)); 245 | if (dim_kbd) 246 | DPRINTF(("dimming keyboard backlight in %d secs\n", 247 | dim_timeout)); 248 | if (use_als) 249 | DPRINTF(("automatically updating brightness from ALS\n")); 250 | if (kbd_idle_only) 251 | DPRINTF(("only watching idle state of keyboard\n")); 252 | 253 | signal(SIGINT, bail); 254 | signal(SIGTERM, bail); 255 | signal(SIGUSR1, sigusr1); 256 | signal(SIGUSR2, sigusr2); 257 | 258 | /* setup a pipe to wait for messages from signal handlers */ 259 | pipe(pipemsg); 260 | 261 | xloop(); 262 | 263 | return 0; 264 | } 265 | 266 | void 267 | xloop(void) 268 | { 269 | XSyncSystemCounter *counters; 270 | XSyncAlarm idle_alarm = None; 271 | XSyncAlarm reset_alarm = None; 272 | XIDeviceInfo *xinfo; 273 | char masdname[25]; 274 | int sync_event, error; 275 | int major, minor, ncounters, ndevices; 276 | int i, j; 277 | 278 | if (XSyncQueryExtension(dpy, &sync_event, &error) != True) 279 | errx(1, "no sync extension available"); 280 | 281 | XSyncInitialize(dpy, &major, &minor); 282 | 283 | if (kbd_idle_only) { 284 | xinfo = XIQueryDevice(dpy, XIAllDevices, &ndevices); 285 | masdname[0] = '\0'; 286 | for (j = 0; j < ndevices; j++) { 287 | if (xinfo[j].use != XIMasterKeyboard) 288 | continue; 289 | 290 | snprintf(masdname, sizeof(masdname), 291 | "DEVICEIDLETIME %d", xinfo[j].deviceid); 292 | break; 293 | } 294 | if (masdname[0] == '\0') 295 | errx(1, "no xinput master keyboard device found"); 296 | } 297 | 298 | counters = XSyncListSystemCounters(dpy, &ncounters); 299 | for (i = 0; i < ncounters; i++) { 300 | if (kbd_idle_only) { 301 | if (strcmp(counters[i].name, masdname) == 0) { 302 | DPRINTF(("using idle time of %s\n", 303 | counters[i].name)); 304 | idler_counter = counters[i].counter; 305 | break; 306 | } 307 | } else { 308 | if (strcmp(counters[i].name, "IDLETIME") == 0) { 309 | idler_counter = counters[i].counter; 310 | break; 311 | } 312 | } 313 | } 314 | XSyncFreeSystemCounterList(counters); 315 | if (kbd_idle_only) 316 | XIFreeDeviceInfo(xinfo); 317 | 318 | if (!idler_counter) 319 | errx(1, "no idle counter"); 320 | 321 | /* 322 | * fire an XSyncAlarmNotifyEvent when IDLETIME counter reaches 323 | * dim_timeout seconds 324 | */ 325 | set_alarm(&idle_alarm, XSyncPositiveComparison); 326 | 327 | for (;;) { 328 | XEvent e; 329 | XSyncAlarmNotifyEvent *alarm_e; 330 | int do_dim = 0, do_brighten = 0; 331 | 332 | DPRINTF(("waiting for next event\n")); 333 | 334 | /* if we're checking an als, only wait 1 second for x event */ 335 | if (XPeekEventOrTimeout(dpy, &e, (use_als ? 1000 : 0)) == 0) { 336 | if (use_als && !dimmed) 337 | als_fetch(); 338 | continue; 339 | } 340 | 341 | if (exiting) 342 | break; 343 | 344 | if (force_dim) { 345 | do_dim = force_dim; 346 | } else if (force_brighten) { 347 | do_brighten = force_brighten; 348 | } else { 349 | XNextEvent(dpy, &e); 350 | 351 | if (!dim_screen && !dim_kbd) 352 | continue; 353 | 354 | if (e.type != (sync_event + XSyncAlarmNotify)) { 355 | DPRINTF(("got event of type %d\n", e.type)); 356 | continue; 357 | } 358 | 359 | alarm_e = (XSyncAlarmNotifyEvent *)&e; 360 | 361 | if (alarm_e->alarm == idle_alarm) { 362 | DPRINTF(("idle counter reached %dms, dimming\n", 363 | XSyncValueLow32(alarm_e->counter_value))); 364 | do_dim = 1; 365 | } else if (alarm_e->alarm == reset_alarm) { 366 | DPRINTF(("idle counter reset, brightening\n")); 367 | do_brighten = 1; 368 | } 369 | } 370 | 371 | if (do_dim && !dimmed) { 372 | set_alarm(&reset_alarm, XSyncNegativeTransition); 373 | 374 | if (dim_screen) 375 | backlight = backlight_op(OP_GET, 0); 376 | if (dim_kbd) 377 | kbd_backlight = kbd_backlight_op(OP_GET, 0); 378 | 379 | stepper(dim_pct, 0, force_dim ? 1 : dim_steps, 1); 380 | dimmed = 1; 381 | } else if (do_brighten && dimmed) { 382 | if (use_als) 383 | als_fetch(); 384 | 385 | set_alarm(&idle_alarm, XSyncPositiveComparison); 386 | 387 | stepper(backlight, kbd_backlight, 388 | force_brighten ? 1 : brighten_steps, 0); 389 | dimmed = 0; 390 | } 391 | 392 | force_dim = force_brighten = 0; 393 | } 394 | 395 | if (dimmed) { 396 | DPRINTF(("restoring backlight to %f / %f before exiting\n", 397 | backlight, kbd_backlight)); 398 | stepper(backlight, kbd_backlight, brighten_steps, 0); 399 | } 400 | } 401 | 402 | void 403 | set_alarm(XSyncAlarm *alarm, XSyncTestType test) 404 | { 405 | XSyncAlarmAttributes attr; 406 | XSyncValue value; 407 | unsigned int flags; 408 | int64_t cur_idle; 409 | 410 | XSyncQueryCounter(dpy, idler_counter, &value); 411 | cur_idle = ((int64_t)XSyncValueHigh32(value) << 32) | 412 | XSyncValueLow32(value); 413 | DPRINTF(("cur idle %lld\n", cur_idle)); 414 | 415 | attr.trigger.counter = idler_counter; 416 | attr.trigger.test_type = test; 417 | attr.trigger.value_type = XSyncRelative; 418 | XSyncIntsToValue(&attr.trigger.wait_value, dim_timeout * 1000, 419 | ((uint64_t)(dim_timeout * 1000) >> 32)); 420 | XSyncIntToValue(&attr.delta, 0); 421 | 422 | flags = XSyncCACounter | XSyncCATestType | XSyncCAValue | XSyncCADelta; 423 | 424 | if (*alarm) 425 | XSyncDestroyAlarm(dpy, *alarm); 426 | 427 | *alarm = XSyncCreateAlarm(dpy, flags, &attr); 428 | } 429 | 430 | void 431 | stepper(float new_backlight, float new_kbd_backlight, int steps, int inter) 432 | { 433 | float tbacklight, tkbd_backlight; 434 | float step_inc = 0, kbd_step_inc = 0; 435 | int j; 436 | 437 | if (dim_screen || use_als) { 438 | tbacklight = backlight_op(OP_GET, 0); 439 | if (((int)new_backlight != (int)tbacklight)) 440 | step_inc = (new_backlight - tbacklight) / steps; 441 | } 442 | 443 | if (dim_kbd) { 444 | tkbd_backlight = kbd_backlight_op(OP_GET, 0); 445 | if ((int)new_kbd_backlight != (int)tkbd_backlight) 446 | kbd_step_inc = (new_kbd_backlight - tkbd_backlight) / 447 | steps; 448 | } 449 | 450 | if (!step_inc && !kbd_step_inc) 451 | return; 452 | 453 | if (dim_screen || use_als) 454 | DPRINTF(("stepping from %0.2f to %0.2f in increments of %f " 455 | "(%d step%s)\n", tbacklight, new_backlight, step_inc, steps, 456 | (steps == 1 ? "" : "s"))); 457 | 458 | if (dim_kbd) 459 | DPRINTF(("stepping keyboard from %0.2f to %0.2f in increments " 460 | "of %f (%d step%s)\n", tkbd_backlight, new_kbd_backlight, 461 | kbd_step_inc, steps, (steps == 1 ? "" : "s"))); 462 | 463 | /* discard any stale alarm events */ 464 | XSync(dpy, True); 465 | 466 | for (j = 1; j <= steps; j++) { 467 | XEvent e; 468 | 469 | if (dim_screen || use_als) { 470 | if (j == steps) 471 | tbacklight = new_backlight; 472 | else 473 | tbacklight += step_inc; 474 | 475 | backlight_op(OP_SET, tbacklight); 476 | } 477 | 478 | if (dim_kbd) { 479 | if (j == steps) 480 | tkbd_backlight = new_kbd_backlight; 481 | else 482 | tkbd_backlight += kbd_step_inc; 483 | 484 | kbd_backlight_op(OP_SET, tkbd_backlight); 485 | } 486 | 487 | if (inter && XPeekEventOrTimeout(dpy, &e, 1) != 0) { 488 | DPRINTF(("%s: X event of type %d while stepping, " 489 | "breaking early\n", __func__, e.type)); 490 | return; 491 | } 492 | } 493 | } 494 | 495 | float 496 | backlight_op(int op, float new_backlight) 497 | { 498 | Atom actual_type; 499 | int actual_format; 500 | unsigned long nitems; 501 | unsigned long bytes_after; 502 | unsigned char *prop; 503 | XRRPropertyInfo *info; 504 | long value, to; 505 | float min, max; 506 | int i; 507 | 508 | float cur_backlight = -1.0; 509 | 510 | if (backlight_a == None) { 511 | #ifdef __OpenBSD__ 512 | struct wsdisplay_param param; 513 | 514 | if (op == OP_SET) 515 | DPRINTF(("%s (wscons): set %f\n", __func__, 516 | new_backlight)); 517 | 518 | param.param = WSDISPLAYIO_PARAM_BRIGHTNESS; 519 | if (ioctl(wsconsdfd, WSDISPLAYIO_GETPARAM, ¶m) < 0) 520 | err(1, "WSDISPLAYIO_GETPARAM failed"); 521 | 522 | if (op == OP_SET) { 523 | param.curval = (float)(param.max - param.min) * 524 | (new_backlight / 100.0); 525 | 526 | if (param.curval > param.max) 527 | param.curval = param.max; 528 | else if (param.curval < param.min) 529 | param.curval = param.min; 530 | 531 | if (ioctl(wsconsdfd, WSDISPLAYIO_SETPARAM, ¶m) < 0) 532 | err(1, "WSDISPLAYIO_SETPARAM failed"); 533 | } 534 | 535 | cur_backlight = ((float)param.curval / 536 | (float)(param.max - param.min)) * 100; 537 | #endif 538 | } else { 539 | if (op == OP_SET) 540 | DPRINTF(("%s (xrandr): set %f\n", __func__, 541 | new_backlight)); 542 | 543 | XRRScreenResources *screen_res = 544 | XRRGetScreenResourcesCurrent(dpy, DefaultRootWindow(dpy)); 545 | if (!screen_res) 546 | errx(1, "no screen resources"); 547 | 548 | for (i = 0; i < screen_res->noutput; i++) { 549 | RROutput output = screen_res->outputs[i]; 550 | 551 | if (XRRGetOutputProperty(dpy, output, backlight_a, 552 | 0, 4, False, False, None, &actual_type, 553 | &actual_format, &nitems, &bytes_after, 554 | &prop) != Success) 555 | continue; 556 | 557 | if (actual_type != XA_INTEGER || nitems != 1 || 558 | actual_format != 32) { 559 | XFree (prop); 560 | continue; 561 | } 562 | 563 | value = *((long *) prop); 564 | XFree(prop); 565 | 566 | info = XRRQueryOutputProperty(dpy, output, backlight_a); 567 | if (!info) 568 | continue; 569 | 570 | if (!info->range || info->num_values != 2) { 571 | XFree(info); 572 | continue; 573 | } 574 | 575 | min = info->values[0]; 576 | max = info->values[1]; 577 | XFree(info); 578 | 579 | /* convert into a percentage */ 580 | cur_backlight = ((value - min) * 100) / (max - min); 581 | 582 | if (op == OP_SET) { 583 | to = min + ((new_backlight * 584 | (max - min)) / 100); 585 | if (to < min) 586 | to = min; 587 | if (to > max) 588 | to = max; 589 | 590 | XRRChangeOutputProperty(dpy, output, 591 | backlight_a, XA_INTEGER, 32, 592 | PropModeReplace, 593 | (unsigned char *)&to, 1); 594 | XSync(dpy, False); 595 | } 596 | else 597 | /* just return the first screen's backlight */ 598 | break; 599 | } 600 | 601 | XRRFreeScreenResources(screen_res); 602 | } 603 | 604 | if (op == OP_GET) 605 | DPRINTF(("%s (xrandr): %f\n", __func__, cur_backlight)); 606 | 607 | return cur_backlight; 608 | } 609 | 610 | float 611 | kbd_backlight_op(int op, float new_backlight) 612 | { 613 | #ifdef __OpenBSD__ 614 | struct wskbd_backlight param; 615 | 616 | if (ioctl(wsconskfd, WSKBDIO_GETBACKLIGHT, ¶m) < 0) 617 | err(1, "WSKBDIO_GETBACKLIGHT failed"); 618 | 619 | if (op == OP_SET) { 620 | DPRINTF(("%s: %f\n", __func__, new_backlight)); 621 | 622 | param.curval = (float)(param.max - param.min) * 623 | (new_backlight / 100.0); 624 | 625 | if (param.curval > param.max) 626 | param.curval = param.max; 627 | else if (param.curval < param.min) 628 | param.curval = param.min; 629 | 630 | if (ioctl(wsconskfd, WSKBDIO_SETBACKLIGHT, ¶m) < 0) 631 | err(1, "WSKBDIO_SETBACKLIGHT failed"); 632 | } 633 | 634 | return ((float)param.curval / (float)(param.max - param.min)) * 100; 635 | #else 636 | return 0; 637 | #endif 638 | } 639 | 640 | int 641 | als_find_sensor(void) 642 | { 643 | #ifdef __OpenBSD__ 644 | struct sensordev sensordev; 645 | struct sensor sensor; 646 | size_t sdlen, slen; 647 | int dev, numt; 648 | 649 | sdlen = sizeof(sensordev); 650 | slen = sizeof(sensor); 651 | 652 | for (dev = 0; ; dev++) { 653 | alsmib[2] = dev; 654 | 655 | if (sysctl(alsmib, 3, &sensordev, &sdlen, NULL, 0) == -1) { 656 | if (errno == ENXIO) 657 | continue; 658 | else if (errno == ENOENT) 659 | break; 660 | 661 | return 0; 662 | } 663 | 664 | if (strstr(sensordev.xname, "acpials") == NULL && 665 | strstr(sensordev.xname, "asmc") == NULL) 666 | continue; 667 | 668 | alsmib[3] = SENSOR_LUX; 669 | 670 | for (numt = 0; numt < 1; numt++) { 671 | alsmib[4] = numt; 672 | if (sysctl(alsmib, 5, &sensor, &slen, NULL, 0) == -1) { 673 | if (errno != ENOENT) { 674 | warn("sysctl"); 675 | continue; 676 | } 677 | } 678 | 679 | DPRINTF(("using als sensor %s\n", sensordev.xname)); 680 | 681 | return 1; 682 | } 683 | } 684 | #endif 685 | 686 | return 0; 687 | } 688 | 689 | void 690 | als_fetch(void) 691 | { 692 | #ifdef __OpenBSD__ 693 | struct sensordev sensordev; 694 | struct sensor sensor; 695 | size_t sdlen; 696 | float lux, tbacklight = backlight, tkbd_backlight = kbd_backlight; 697 | int i; 698 | 699 | sdlen = sizeof(sensordev); 700 | 701 | if (sysctl(alsmib, 5, &sensor, &sdlen, NULL, 0) == -1) { 702 | warn("sysctl"); 703 | return; 704 | } 705 | 706 | lux = sensor.value / 1000000.0; 707 | 708 | if ((int)als < 0) { 709 | als = lux; 710 | } else if (abs((int)lux - (int)als) < 10) { 711 | als = lux; 712 | return; 713 | } else { 714 | DPRINTF(("als lux change %f -> %f, screen: %f, kbd: %f\n", als, 715 | lux, backlight, kbd_backlight)); 716 | } 717 | 718 | for (i = (sizeof(als_settings) / sizeof(struct als_setting)) - 1; 719 | i >= 0; i--) { 720 | struct als_setting as = als_settings[i]; 721 | 722 | if (lux < as.min_lux) 723 | continue; 724 | 725 | DPRINTF(("using lux profile %s\n", as.label)); 726 | 727 | if (dim_kbd && ((int)round(kbd_backlight) != as.kbd_backlight)) { 728 | DPRINTF(("als: adjusting keyboard backlight from %d%% " 729 | "to %d%%\n", (int)round(kbd_backlight), 730 | as.kbd_backlight)); 731 | tkbd_backlight = as.kbd_backlight; 732 | } 733 | 734 | if ((int)round(backlight) != as.backlight) { 735 | DPRINTF(("als: adjusting screen backlight from %d%% " 736 | "to %d%%\n", (int)round(backlight), as.backlight)); 737 | tbacklight = as.backlight; 738 | } 739 | 740 | if ((int)round(kbd_backlight) != tkbd_backlight || 741 | (int)round(backlight) != tbacklight) 742 | stepper(tbacklight, tkbd_backlight, dim_steps, 0); 743 | 744 | /* become our new normal */ 745 | backlight = tbacklight; 746 | kbd_backlight = tkbd_backlight; 747 | 748 | setproctitle("%s", as.label); 749 | 750 | break; 751 | } 752 | 753 | als = lux; 754 | #endif 755 | } 756 | 757 | void 758 | usage(void) 759 | { 760 | fprintf(stderr, "usage: %s [-adkn] [-b brighten steps] [-p dim pct] " 761 | "[-s dim steps] [-t timeout secs]\n", __progname); 762 | exit(1); 763 | } 764 | 765 | void 766 | bail(int sig) 767 | { 768 | int msg = MSG_EXIT; 769 | 770 | if (exiting) 771 | exit(0); 772 | 773 | /* 774 | * Doing X ops inside a signal handler causes an infinite loop in 775 | * _XReply/xcb, so we can't properly brighten and exit ourselves. 776 | * write to the pipe setup earlier so our event loop will see it upon 777 | * polling. 778 | */ 779 | DPRINTF(("got signal %d, trying to exit\n", sig)); 780 | write(pipemsg[1], &msg, 1); 781 | exiting = 1; 782 | } 783 | 784 | void 785 | sigusr1(int sig) 786 | { 787 | int msg = MSG_DIM; 788 | 789 | DPRINTF(("got signal %d, forcing dim\n", sig)); 790 | write(pipemsg[1], &msg, 1); 791 | } 792 | 793 | void 794 | sigusr2(int sig) 795 | { 796 | int msg = MSG_BRIGHTEN; 797 | 798 | DPRINTF(("got signal %d, forcing brighten\n", sig)); 799 | write(pipemsg[1], &msg, 1); 800 | } 801 | 802 | int 803 | XPeekEventOrTimeout(Display *dpy, XEvent *e, unsigned int msecs) 804 | { 805 | struct pollfd pfd[2]; 806 | int msg = 0; 807 | 808 | while (!XPending(dpy)) { 809 | memset(&pfd, 0, sizeof(pfd)); 810 | pfd[0].fd = ConnectionNumber(dpy); 811 | pfd[0].events = POLLIN; 812 | pfd[1].fd = pipemsg[0]; 813 | pfd[1].events = POLLIN; 814 | 815 | switch (poll(pfd, 2, msecs == 0 ? INFTIM : msecs)) { 816 | case -1: 817 | /* signal, maybe exit handler, we'll loop again */ 818 | DPRINTF(("poll returned -1 for errno %d\n", errno)); 819 | break; 820 | case 0: 821 | /* timed out */ 822 | return 0; 823 | default: 824 | if (pfd[1].revents) { 825 | read(pipemsg[0], &msg, 1); 826 | switch (msg) { 827 | case MSG_EXIT: 828 | DPRINTF(("%s: got pipe message: exit\n", 829 | __func__)); 830 | exiting = 1; 831 | break; 832 | case MSG_DIM: 833 | DPRINTF(("%s: got pipe message: dim\n", 834 | __func__)); 835 | force_dim = 1; 836 | break; 837 | case MSG_BRIGHTEN: 838 | DPRINTF(("%s: got pipe message: " 839 | "brighten\n", __func__)); 840 | force_brighten = 1; 841 | break; 842 | default: 843 | DPRINTF(("%s: junk on msg pipe: 0x%x\n", 844 | __func__, msg)); 845 | } 846 | return 1; 847 | } else if (pfd[0].revents) { 848 | DPRINTF(("%s: got X event\n", __func__)); 849 | XPeekEvent(dpy, e); 850 | return 1; 851 | } 852 | } 853 | } 854 | 855 | XPeekEvent(dpy, e); 856 | return 1; 857 | } 858 | --------------------------------------------------------------------------------