LibreShift / red-moon

Android screen filter app for night time phone use.
GNU General Public License v3.0
658 stars 82 forks source link

Implement root mode #150

Closed notjuliee closed 3 years ago

notjuliee commented 7 years ago

Steps (added by smichel17):

Original post (unmodified) below:


I have decompiled f.lux, and figured out how it sets screen colors.

service call SurfaceFlinger 1015 i32 1 i32 %d i32 %d i32 %d i32 0 i32 %d i32 %d i32 %d i32 0 i32 %d i32 %d i32 %d i32 0 i32 0 i32 0 i32 0 i32 %d > /dev/null as root adjusts the android compositor. I haven't quite figured out what the numbers are, but it shouldn't be too much work to guess those.

service call SurfaceFlinger 1015 i32 0 > /dev/null as root resets the screen colors. Screen colors also reset after reboot, so it's easy to fix if you screw it up ;P

I'll work on figuring out the exact numbers, but I am unfamiliar with your codebase and won't be able to help implement it into Red Moon.

Edit: Here's all the info I've gotten so far.

raatmarien commented 7 years ago

This is awesome, it seems to work on my phone! I called it with all zeroes and it turned my screen black :+1:

I found this in the source of SurfaceFlinger: https://android.googlesource.com/platform/frameworks/native/+/master/services/surfaceflinger/SurfaceFlinger.cpp#3262

It seems to be the code for this service call and should be quite helpful to figure out what everything means. I didn't have the time to look at it closely, but the comment mentions that it accepts a color matrix. This also explains why so many of the arguments are zero.

So thanks a lot for sharing this, it looks very promising! As soon as I find time I'm gonna play around with this some more.

raatmarien commented 7 years ago

More info on color matrices https://developer.android.com/reference/android/graphics/ColorMatrix.html

With this it should be possible to construct any color change.

raatmarien commented 7 years ago

I tried your standard values and it works! It alters the colors slightly on my phone. Here is the command for convenience (since I spend some time filling in the values):

service call SurfaceFlinger 1015 i32 1 i32 1060000000 i32 -1200000000 i32 -1200000000 i32 0 i32 1065000000 i32 1065000000 i32 -1500000000 i32 0 i32 1000000000 i32 -1200000000 i32 1065000000 i32 0 i32 0 i32 0 i32 0 i32 1065000000

For anyone who wants to try it, running it in Termux with su works for me. Do it at your own risk though :smile:.

Edit:

I think the reason that all the numbers seem arbitrary (and large and so), is that the call doesn't actually read i32 for the all but the first values. It reads floats, which should be preceded by f in the service call. These big numbers are probably just the integer representation of IEEE floats.

The previous call converted to floats is:

service call SurfaceFlinger 1015 i32 1 f 0.68 f -5.94743e-05 f -5.94743e-05 f 0 f 0.978947 f 0.978947 f -1.05344e-15 f 0 f 0.00472379 f -5.94743e-05 f 0.978947 f 0 f 0 f 0 f 0 f 0.978947

And gives the same output. These numbers make a lot more sense to me. Also note that some of these numbers are very close to zero and one and should probably just be replaced with that.

service call SurfaceFlinger 1015 i32 1 f 0.68 f 0 f 0 f 0 f 1 f 1 f 0 f 0 f 0 f 0 f 1 f 0 f 0 f 0 f 0 f 1

Laid out as a matrix:

service call SurfaceFlinger 1015 i32 1
f 0.68 f 0 f 0 f 0 
f 1 f 1 f 0 f 0
f 0 f 0 f 1 f 0
f 0 f 0 f 0 f 1
raatmarien commented 7 years ago

This matrix gives us the power to change the color however we want. To modulate the color we can just use a diagonal matrix like this (without the enters of course, I added those to show the matrix layout more clearly):

service call SurfaceFlinger 1015 i32 1
f $r f 0 f 0 f 0
f 0 f $g f 0 f 0
f 0 f 0 f $b f 0
f 0 f 0 f 0 f 1

Where we replace $r, $g and $b with the color values of the filter as floats between 0.0 and 1.0.

If you want to test it out, here is one which tunes green and blue down to 80% and leaves red as it is:

service call SurfaceFlinger 1015 i32 1 f 1 f 0 f 0 f 0 f 0 f 0.8 f 0 f 0 f 0 f 0 f 0.8 f 0 f 0 f 0 f 0 f 1

Edit:

Notice that the matrix should be delivered transposed (column after column, instead of row after row).

mirh commented 7 years ago

Wow, bravo. Is this what cf.lumen also uses or not?

raatmarien commented 7 years ago

I'm not sure, someone would have to take a look under the hood of CF.Lumen to see. I do remember though that CF.Lumen seemed to sometimes replace the standard SurfaceFlinger with a modified version, but it is possible that this was just a fix for inconsistencies or something. Anyways, pure speculation on my part :smile:.

raatmarien commented 7 years ago

I've pushed a proof of concept of integrating this with Red Moon to the branch root-mode. It is not usable at all yet, but it does show the Red Moon UI using this service call to alter the screen colors.

smichel17 commented 7 years ago

[todos moved to top post]

notjuliee commented 7 years ago

@smichel17 you shouldn't need overlay to dim hw buttons, at least on my phone you can set the brightness through /sys/devices/soc.0/leds-qpnp-15/leds/button-backlight/brightness

smichel17 commented 7 years ago

@raatmarien @joonatoona I'm going on vacation tomorrow for about two weeks; I may or may not have regular internet access or time to code. I was trying to finish refactoring for root mode, but that's unfortunately not going to happen in time. I've almost finalized the interface, and am interested in feedback. Here's two options. In both,

interface Filter {
    fun setProgress(percent: Int, durationMax: Int, onFinish: () -> Unit)
}

interface Filter {
    fun turnOn (durationMax: Int, onFinish: () -> Unit = {})
    fun turnOff(durationMax: Int, onFinish: () -> Unit = {})
    fun setProgress(percent: Int)
}

In the first interface, when percent is set to zero, we need to remove the view*, stop app monitoring*, and restore the brightness if it was lowered. When percent is set to a non-zero number, all those things should be turned on. In the second, those don't happen until a turnOn or turnOff call. *overlay mode only

I'm leaning towards the first interface, and we can change it later if we need to optimize. If you agree, feel free to go ahead with implementation.

notjuliee commented 7 years ago

If implemented correctly, root mode should be faster than the overlay based system.

You definitely should NOT open a new root shell for each frame. I spent a ton of time trying to speed up OpenShift, a background listener is the only way to make the speed semi reasonable.

I feel like transitions should be handled by a background listener. I'm working on once in C, I'll share once that's ready.

I tried putting the transitions directly into the background script, and just sent the start, stop, and time through the app, and it was (at least how I measured it) a bit faster than your current overay-based system.

smichel17 commented 7 years ago

I kind of figured that the RootFilter (or whatever you call the class that implements the interface above) would open a root shell when it is initialized (which happens when the service is started), and keep the shell around for as long as the service is running.

Typing that out makes me realize I don't know that much about how root shells work, except that they're expensive to open. Are they stored as an object you can pass commands to? Or is everything executed in that shell until it's closed? If so, what're the boundaries of 'everything'? In that thread? In the process/task? Do you need to close it manually or is it okay to just stop the service and let the garbage collector do its job?

Basically, I'd like to understand the trade-offs (if any) involved in keeping a root shell open, so the interface between the service and the filter makes the most sense.

edit: also, feel free to ping me in matrix for a faster response (even if it's just "hey, check github"). I should be up for a couple more hours.

notjuliee commented 7 years ago

I'm not an expert by any stretch of the imagination, I just scre w around until it does what I want it to.

As far as I can tell, no command executes until you close the pipe. When you close the pipe, java can't directly interact with it.

But closing the pipe doesn't kill the process, so I communicate with the bash script using named pipes.

edit: Don't have a matrix client on my phone, I'll look into that.

smichel17 commented 7 years ago

I use the Riot app on my phone.

mirh commented 5 years ago

https://github.com/Chainfire/inject-hook-cflumen

smichel17 commented 5 years ago

Whoa, cool.

smichel17 commented 3 years ago

Anyone following this issue who wants to test ⇒ #287