That screen really is too bright sometimes

Warning

The contents of this post are based on a fundamental mistake of the mce configuration structure!

For a better way that actually works (although not as well explained), see:
https://forum.sailfishos.org/t/adjust-minimum-brightness-sony-10-ii-4-4-068/12959/13

I’ll write an updated post with better explanation later!

Original contents

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 SettingsLook and FeelDisplay 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

  1. Enable “Developer Mode” under SettingsSystemDeveloper tools
  2. Set an SSH password, this will be also your root password
  3. Either:
  4. 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)
  5. Close settings and launch the “Terminal” from the app drawer
  6. Enter devel-su on the computer with SSH logged in or in the “Terminal” app
  7. Type in the password set in step 2
  8. Make sure nano is installed by typing in pkcon install nano and pressing enter

There is also an official help guide with pictures for this at https://docs.sailfishos.org/Support/Help_Articles/Enabling_Developer_Mode/

Changing the brightness profile

  1. Run nano /var/lib/mce/builtin-gconf.values to edit the configuration of the MCE system service.
  2. Use the arrow keys to find the line that says /system/osso/dsm/display/display_brightness=….
  3. Change the number after the equals sign (=): Default on Sony Xperia 10 II is 1, lowest is by default 0, highest is 20.
  4. Press Ctrl+X, then Y, then Enter to save and close the editor.
  5. Enter systemctl restart mce to restart MCE and apply the changes.

Adding lower brightness profiles

If you did the above and changed the profile from 1 to 0, you’ll notice there isn’t much difference and 0 is the lowest possible value by default. To get significantly lower values we therefore need to add our own profiles!

  1. Run nano /etc/mce/10als-defaults.ini to edit the MCE Adaptive Light Sensor (ALS) preset/defaults file.
  2. Use the arrow keys to navigate to the end of the [BrightnessDisplay] section (just above [BrightnessLed]).
  3. Copy and paste the following lines containing extra profiles:

    LimitsProfile21=1;2;4;6;11;19;34;61;109;195;350;431;513;611;727;866;1031;1227;1461;1739;2070
    LevelsProfile21=1;3;4;6;7;9;10;12;13;15;17;26;34;42;51;59;67;76;84;92;100
    
    LimitsProfile22=1;2;4;6;11;19;34;61;109;195;350;446;531;632;753;896;1067;1270;1512;1800;2143
    LevelsProfile22=1;2;3;5;6;7;8;10;11;12;14;23;32;40;49;57;66;75;83;92;100
    
    LimitsProfile23=1;2;4;6;11;19;34;61;109;195;350;462;550;655;779;928;1105;1315;1566;1864;2219
    LevelsProfile23=1;2;3;4;5;6;7;8;9;10;11;20;29;38;47;56;65;74;83;92;100
    
    LimitsProfile24=1;2;4;6;11;19;34;61;109;195;350;478;569;678;807;961;1144;1361;1621;1929;2297
    LevelsProfile24=1;1;2;2;3;4;5;5;6;7;8;18;27;36;45;54;64;73;82;91;100
    
    LimitsProfile25=1;2;4;6;11;19;34;61;109;195;350;495;589;702;835;994;1184;1409;1678;1997;2378
    LevelsProfile25=1;1;1;1;2;2;3;3;4;4;5;15;24;34;43;53;62;72;81;91;100
    
    LimitsProfile26=1;2;4;6;11;19;34;61;109;195;350;512;610;726;865;1029;1225;1459;1737;2068;2461
    LevelsProfile26=1;1;1;1;1;1;1;1;1;1;2;12;22;32;42;51;61;71;81;91;100
    
    LimitsProfile27=1;2;4;6;11;19;34;61;109;195;350;531;632;752;895;1066;1269;1510;1798;2140;2548
    LevelsProfile27=1;1;1;1;1;1;1;1;1;1;1;10;20;30;40;50;60;70;80;90;100
    
    LimitsProfile28=1;2;4;6;11;19;34;61;109;195;350;549;654;778;927;1103;1313;1563;1861;2216;2638
    LevelsProfile28=1;1;1;1;1;1;1;1;1;1;1;7;17;28;38;48;59;69;80;90;100
    
    LimitsProfile29=1;2;4;6;11;19;34;61;109;195;350;569;677;806;959;1142;1359;1618;1927;2294;2730
    LevelsProfile29=1;1;1;1;1;1;1;1;1;1;1;4;15;26;36;47;58;68;79;90;100
    
  4. Press Ctrl+X, then Y, then Enter to save and close the editor.

This adds profiles 21 to 29 which each being successively darker than profile 0, meaning that the order of profiles from darkest to brightest is now:
29, 28, 27, 26, 25, 24, 23, 22, 21, 0, 1, 2, 3, 4, 5, 6, …, 18, 19, 20
(Just think of the leading digit 2 as a minus sign for values ≥ 21. 😉)

Notably though, the minimum brightness is always 1% (unless you change the “Brightness Base Level” slider under SettingsLook and FeelDisplay to a higher value) and MCE – and hence SailfishOS – will never set a value lower than that!

After adding the extra profiles you can use them like explained under Changing the brightness profile.

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, with nothing in the system apparently ever writing that format anymore.

After some more digging and trial-and-error, I 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. Since there is not tool to update these values on the system (the mentioned mcetool doesn’t exist and neither does any GConf CLI), I concluded I should just edit that file I found and it’d be fine.

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 do that:

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)

This 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. 🤷🏽