Since we have chronic light sensitivity as part of our ME/CFS, I decided to investigate whether there is any software way to make the screen of our phone running SailfishOS consistently darker without disabling automatic brightness control.
Unfortunately the minimum allowed by the “Brightness Base Level” slider under Settings → Look and Feel → Display is still someway too high after all, so there is no user-friendly way to lower it further, but there did turn out to be a hackey way to force a lower brightness response by editing system files which in turn requires enabling root access if you don’t already have that…
Changing the system brightness profile
Note that some of these steps may need to be repeated after a system upgrade.
Getting root access
- Enable “Developer Mode” under Settings → System → Developer tools
- Set an SSH password, this will be also your root password
- Either:
- Also enable SSH by checking “Remote connection”, then running
ssh defaultuser@<ADDRESS>(where<ADDRESS>is the “WLAN IP address” listed) on your computer’s terminal window and entering the set password (recommended if you have SSH on a computer as using the “Terminal” app is pretty annoying) - Close settings and launch the “Terminal” from the app drawer
- Enter
devel-suon the computer with SSH logged in or in the “Terminal” app - Type in the password set in step 2
- Make sure
nanois installed by typing inpkcon install nanoand pressing enter
There is also an official help guide with pictures for this at https://docs.sailfishos.org/Support/Help_Articles/Enabling_Developer_Mode/
Replacing brightness profiles
System brightness profiles are stored at /etc/mce/20als-defaults.ini on the
device (ALS = ambient light sensor). Since you probably already selected the
lowest profile in system settings and its still too bright, we have to update
the Ambient Light level to screen brightness mapping to be even less sensitive.
- Run
nano /etc/mce/20als-defaults.inito edit the MCE Ambient Light Sensor (ALS) preset/defaults file. - Use the arrow keys to navigate to the end of the
[BrightnessDisplay]section (just above[BrightnessLed]). - Remove all the existing lines starting with
LimitsProfile…andLevelsProfile…by going to the start of the line and repeatedly pressingCtrl+K(cut line) -
Copy and paste the following lines containing the updated profiles:
LimitsProfile0=1;2;4;6;11;19;34;61;109;195;350;511;608;724;862;1026;1221;1454;1731;2060;2453 LevelsProfile0=1;1;1;1;1;1;1;1;1;1;1;10;20;30;40;50;60;70;80;90;100 LimitsProfile1=1;2;4;6;11;19;34;61;109;195;350;490;584;695;827;985;1172;1396;1662;1978;2355 LevelsProfile1=1;1;1;1;1;2;2;2;3;3;4;14;24;33;43;52;62;72;81;91;100 LimitsProfile2=1;2;4;6;11;19;34;61;109;195;350;471;560;667;794;945;1125;1340;1595;1899;2261 LevelsProfile2=1;1;2;2;3;4;5;5;6;7;8;18;27;36;45;54;64;73;82;91;100 LimitsProfile3=1;2;4;6;11;19;34;61;109;195;350;452;538;640;762;908;1080;1286;1531;1823;2170 LevelsProfile3=1;2;3;4;5;6;7;8;9;10;12;21;30;39;48;56;65;74;83;92;100 LimitsProfile4=1;2;4;6;11;19;34;61;109;195;350;434;516;615;732;871;1037;1235;1470;1750;2083 LevelsProfile4=1;2;4;5;7;8;10;11;13;14;16;25;33;42;50;58;67;75;84;92;100 LimitsProfile5=1;2;4;6;11;19;34;61;109;195;350;416;496;590;703;836;996;1185;1411;1680;2000 LevelsProfile5=1;3;5;7;9;10;12;14;16;18;20;28;36;44;52;60;68;76;84;92;100 LimitsProfile6=1;2;4;6;11;19;34;61;109;195;350;400;476;567;674;803;956;1138;1355;1613;1920 LevelsProfile6=2;4;6;8;10;13;15;17;19;21;24;32;40;47;55;62;70;78;85;93;100 LimitsProfile7=1;2;4;6;11;19;34;61;109;195;350;384;457;544;648;771;918;1092;1301;1548;1843 LevelsProfile7=2;5;7;10;12;15;17;20;22;25;28;36;43;50;57;64;72;79;86;93;100 LimitsProfile8=1;2;4;6;11;19;34;61;109;195;350;368;439;522;622;740;881;1049;1249;1486;1769 LevelsProfile8=2;5;8;11;14;17;20;23;26;29;32;39;46;53;60;66;73;80;87;94;100 LimitsProfile9=1;2;4;6;11;19;34;61;109;195;350;354;421;501;597;710;846;1007;1199;1427;1699 LevelsProfile9=3;6;9;13;16;19;22;26;29;32;36;43;49;56;62;68;75;81;88;94;100 LimitsProfile10=1;2;4;6;11;19;34;61;109;195;350;340;404;481;573;682;812;967;1151;1370;1631 LevelsProfile10=3;7;10;14;18;21;25;29;32;36;40;46;52;58;64;70;76;82;88;94;100 LimitsProfile11=1;2;4;6;11;19;34;61;109;195;350;326;388;462;550;655;779;928;1105;1315;1566 LevelsProfile11=4;8;12;16;20;24;28;32;36;40;44;50;56;61;67;72;78;84;89;95;100 LimitsProfile12=1;2;4;6;11;19;34;61;109;195;350;313;373;443;528;629;748;891;1060;1262;1503 LevelsProfile12=4;8;13;17;21;26;30;34;39;43;48;54;59;64;69;74;80;85;90;95;100 LimitsProfile13=1;2;4;6;11;19;34;61;109;195;350;300;358;426;507;603;718;855;1018;1212;1443 LevelsProfile13=4;9;14;18;23;28;33;37;42;47;52;57;62;67;72;76;81;86;91;96;100 LimitsProfile14=1;2;4;6;11;19;34;61;109;195;350;288;343;409;487;579;690;821;977;1163;1385 LevelsProfile14=5;10;15;20;25;30;35;40;45;50;56;61;65;70;74;78;83;87;92;96;100 LimitsProfile15=1;2;4;6;11;19;34;61;109;195;350;277;330;392;467;556;662;788;938;1117;1330 LevelsProfile15=5;10;16;21;27;32;38;43;49;54;59;64;68;72;76;80;84;88;92;96;100 LimitsProfile16=1;2;4;6;11;19;34;61;109;195;350;266;316;377;448;534;636;757;901;1072;1276 LevelsProfile16=5;11;17;23;29;34;40;46;52;58;64;68;72;75;79;82;86;90;93;97;100 LimitsProfile17=1;2;4;6;11;19;34;61;109;195;350;255;304;362;430;512;610;726;865;1029;1225 LevelsProfile17=6;12;18;24;30;37;43;49;55;61;68;72;75;78;81;84;88;91;94;97;100 LimitsProfile18=1;2;4;6;11;19;34;61;109;195;350;245;292;347;413;492;586;697;830;988;1176 LevelsProfile18=6;13;19;26;32;39;45;52;58;65;72;75;78;81;84;86;89;92;95;98;100 LimitsProfile19=1;2;4;6;11;19;34;61;109;195;350;235;280;333;397;472;562;669;797;949;1129 LevelsProfile19=6;13;20;27;34;41;48;55;62;69;76;79;81;84;86;88;91;93;96;98;100 LimitsProfile20=1;2;4;6;11;19;34;61;109;195;350;226;269;320;381;453;540;643;765;911;1084 LevelsProfile20=7;14;21;29;36;43;50;58;65;72;80;82;84;86;88;90;92;94;96;98;100 -
Press Ctrl+
X, thenY, then Enter to save and close the editor. - Enter
systemctl restart mceor just restart your device to load the new values.
This replaces the default profiles with ones that starting from about the same maximum brightness curves will go continously lower with all settings below about 24% in setting selecting curves that were previously unreachable.
Background
How I found about this
TL;DR: Some educated guesswork, intimiate Linux system knowledge and a little help from the MCE source code.
To find out which SailfishOS system component was responsible for managing
the backlight, I knew I had find which program writes to /sys/class/backlight/<DEVNAME>
(the Linux kernel interface for controlling backlight devices). So after looking
up how to find our which process writes to a file, I compiled
fanotify-example
on the device using gcc (GNU C Compiler), then ran it as
./fanotify-example /sys/class/backlight/panel0-backlight. This revealed
that the brightness value of the main panel is controlled by /usr/sbin/mce (MCE).
Knowing that it is customary for Linux system services to have configuration
files in /etc, I then checked the directory /etc/mce and found a file named
20als-defaults.ini in there, which I correctly guessed would stand for
Ambient Light Sensor defaults.
Given that that file is both documented and rather obvious I than started
looking for a way to select a different display backlight profile. For this I
played around a bit with the mce command and found a debug logging switch
that revealed that there are apparently some “GConf” values influencing its
operation in addition to the files at /etc/mce and that it reads the file
/var/lib/mce/builtin-gconf.values to find these values. The name GConf indeed
turned out related to the old GNOME 2.x configuration system, but GConf isn’t
actually used anymore. It’s just that MCE contains a GConf compatible
configuration parser, which made assume that nothing in the system apparently
ever writes that format anymore – this turned out to be a wrong assumption
as that file is actually rewritten by system settings when you update any
system-wide values.
Since I assumed that that file is shipped by SailfishOS during installation,
I looked for information on what it does and learned from the manpage at
https://github.com/sailfishos/mce/blob/91198feec76f1069ed7b38f519e9a0f7852ed358/man/mce.8#L89
(that isn’t actually shipped in SailfishOS itself) that the “GConf key”
/system/osso/dsm/display/display_brightness controls the current brightness
profile used, which let me to believe I just need to change that value
to use a different brightness curve – while one can indeed change that file
to load a different curve, one can also just do so in system settings.
Finally, I had to figure out how to generate dimmer curves since even the default 0 curve is still too bright. This involved playing around with the existing values in a Python shell and trying different binary operator on them to hopefully try and spot the pattern. Plotting the existing curves was quite helpful for this once I decided to finally do that, since you can see discontinuities, trends and repetitions much better than when just starting at the numbers.
How I generated the extra curve values
Based on analysis I wrote the following Python code to generate some additional dimmer curves, but latter realized that it was impossible to load them:
limprf = lambda max: f"1;2;4;6;11;19;34;61;109;195;350;{';'.join(str(round(max * 0.84**p)) for p in range(9, -1, -1))}"
lvlprf = lambda mid: f"{';'.join(str(max(floor(mid / 11 * i), 1)) for i in range(1, 11 + 1))};{';'.join(str(ceil(((100 - mid) / 10 * i) + mid)) for i in range(1, 10 + 1))}"
def gen_curves(n):
for i in range(1, n + 1):
print()
print(f"LimitsProfile{20 + i}={limprf(2000 / (0.966 ** i))}")
print(f"LevelsProfile{20 + i}={lvlprf(20 - (3 * i))}")
gen_curves(9)
Once they didn’t load, I begrudinginly dug through the available source code and found that the system is actually limited to 21 curves (0–20), so instead I generated some modified curves (as showcased in the main part of the article) that start lower and end at the same brightness level:
limprf = lambda max: f"1;2;4;6;11;19;34;61;109;195;350;{';'.join(str(round(max * 0.84**p)) for p in range(9, -1, -1))}"
lvlprf = lambda mid: f"{';'.join(str(max(floor(mid / 11 * i), 1)) for i in range(1, 11 + 1))};{';'.join(str(ceil(((100 - mid) / 10 * i) + mid)) for i in range(1, 10 + 1))}"
for i in range(0, 20 + 1):
print()
print(f"LimitsProfile{i}={limprf(2000 * (0.96 ** (i - 5)))}")
print(f"LevelsProfile{i}={lvlprf(20 + (4 * (i - 5)))}")
In any case, these just extrapolates the same trends found in the pre-existing curves, so I cannot really explain why exactly those values, but apparently they work. Oh well. 🤷🏽