Recently Written · git

ink-dim

Ink Dim: tap to set the frontlight of a Viwoods AiPaper reader below the lowest system level. Tap again to go back.

git clone https://github.com/equwal/ink-dim

Log | Files | Refs


app/src/main/java/dev/equwal/inkdim/Dim.kt (3572 bytes)

1 // SPDX-License-Identifier: GPL-3.0-or-later
2 // Copyright (c) 2026 equwal
3 package dev.equwal.inkdim
4 
5 /**
6  * The decision and the shell commands. No Android types, so it is all testable.
7  *
8  * Measured on the Viwoods reader: the frontlight is an ordinary backlight LED.
9  * Its driver accepts 1 as the lowest lit value, but the framework will not go
10  * below 5. Ask for 4 through any official route and it snaps to zero. A write
11  * straight to the LED node gets under that floor, and the value then holds,
12  * even across sleep, until the system brightness is next set.
13  *
14  * The node belongs to `system`, so a plain shell user cannot write it. These
15  * readers ship a userdebug build, where the shell user may run `su 0`. So each
16  * write tries the plain write first and falls back to `su 0`.
17  */
18 object Dim {
19 
20     /** What the caller asked for. */
21     enum class Action { TOGGLE, ON, OFF }
22 
23     /** The light nodes to try, in order. The first one that exists wins. */
24     val nodes = listOf(
25         "/sys/class/leds/lcd-backlight/brightness",
26         "/sys/class/backlight/panel0-backlight/brightness"
27     )
28 
29     /** The value to hold the light at. The lowest value the driver still lights. */
30     const val ON_VALUE = 1
31 
32     /**
33      * The state the light is in now.
34      *
35      * The node wins, because the user can change the brightness in the system
36      * at any time. [remembered] is the fallback when the node could not be read.
37      */
38     fun isOn(nodeValue: Int?, remembered: Boolean): Boolean =
39         if (nodeValue == null) remembered else nodeValue <= ON_VALUE
40 
41     /** The state to go to. */
42     fun next(nodeValue: Int?, remembered: Boolean, action: Action): Boolean = when (action) {
43         Action.ON -> true
44         Action.OFF -> false
45         Action.TOGGLE -> !isOn(nodeValue, remembered)
46     }
47 
48     // ---- shell commands ----------------------------------------------------
49 
50     /**
51      * Writes [value] to the first node that exists. A plain write first, then
52      * through `su 0`, then a read back to prove the value took.
53      *
54      * Exit 0 the light holds [value], 1 the write was refused, 2 there is no
55      * node on this device.
56      */
57     fun writeCommand(nodes: List<String>, value: Int): String {
58         val list = nodes.joinToString(" ") { "'$it'" }
59         return "for n in $list; do [ -e \$n ] || continue; " +
60             "echo $value > \$n 2>/dev/null || su 0 sh -c \"echo $value > \$n\"; " +
61             "[ \"\$(cat \$n)\" = \"$value\" ] && exit 0; exit 1; done; exit 2"
62     }
63 
64     /**
65      * Prints the value of the first node that exists. Exit 1 means this device
66      * has no light node that this app knows.
67      */
68     fun readCommand(nodes: List<String>): String {
69         val list = nodes.joinToString(" ") { "'$it'" }
70         return "for n in $list; do [ -e \$n ] || continue; cat \$n; exit 0; done; exit 1"
71     }
72 
73     /**
74      * Gives the light back to the system.
75      *
76      * The system brightness goes up by one and straight back. That makes the
77      * framework write the node again, with the value the user chose. So "off"
78      * always lands on the brightness the system holds, whatever it is.
79      */
80     fun restoreCommand(): String =
81         "v=\$(settings get system screen_brightness); " +
82             "settings put system screen_brightness \$((v+1)); " +
83             "settings put system screen_brightness \$v"
84 
85     /** The value the node holds, or null. Never throws. */
86     fun parseRead(output: String): Int? =
87         output.lineSequence().map { it.trim() }.firstNotNullOfOrNull { it.toIntOrNull() }
88 }