Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement root mode #150

Closed
4 of 10 tasks
notjuliee opened this issue Jun 15, 2017 · 17 comments · Fixed by #287
Closed
4 of 10 tasks

Implement root mode #150

notjuliee opened this issue Jun 15, 2017 · 17 comments · Fixed by #287

Comments

@notjuliee
Copy link
Member

notjuliee commented Jun 15, 2017

Steps (added by smichel17):

  • Finish refactor
  • Make fresh root-mode branch based on master, hard-coded to use root mode
  • Merge @joonatoona's listener to this branch
  • Figure out how to set up the android NDK so the listener works (and set it up)
  • Integrate @raatmarien's multiplicative filter calculations
    • Improve these calculations (?) or, do that later
  • Decide on gui
    • add a root mode checkbox in the menu (like dark theme)
    • Request root instead of asking for other permissions
  • People interested in testing run locally for a while to find bugs
  • Release!

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
Copy link
Member

raatmarien commented Jun 16, 2017

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

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
Copy link
Member

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
Copy link
Member

raatmarien commented Jun 16, 2017

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 😄.

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
Copy link
Member

raatmarien commented Jun 16, 2017

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).

@smichel17 smichel17 mentioned this issue Jun 16, 2017
@mirh
Copy link

mirh commented Jun 16, 2017

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

@raatmarien
Copy link
Member

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 😄.

raatmarien added a commit that referenced this issue Jun 16, 2017
See #150

It will request root after turning on the filter, which is quite
inconvenient, because you then have to turn of the filter through the
notification to grant the permission (a transparent filter is still
being drawn). It is also *very* slow so be patient when trying it. But
it seems to work (on my phone at least)!
@raatmarien
Copy link
Member

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
Copy link
Member

smichel17 commented Jun 17, 2017

[todos moved to top post]

@notjuliee
Copy link
Member Author

@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
Copy link
Member

smichel17 commented Aug 14, 2017

@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,

  • percent Is an int between 0 and 100, which represents the progress in a long-running transition (Progressively red-en and dim backlight according to sunset #33, not the half-second transition). 0 means the screen is not modified.
  • durationMax says how much time the transition is allowed to take. Overlay uses this to force an instant on for the preview; in root mode you can ignore this and always transition instantly for performance.
  • onFinish() is the continuation, and should be called when the transition is complete. This way, the service won't stop itself before the fade out is complete.
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 not that happy with the name setProgress; I'll change it if I think of something better.
  • The second needs to store some measure of progress internally, but the first just follows instructions. Less state -> fewer possiblities to get out of sync.
  • On the other hand, the second (or something like it) could allow better control over when we open/close a root shell, which has performance implications (Significantly Speed Up Root Mode #162).

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
Copy link
Member Author

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
Copy link
Member

smichel17 commented Aug 14, 2017

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
Copy link
Member Author

notjuliee commented Aug 14, 2017

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
Copy link
Member

smichel17 commented Aug 14, 2017

I use the Riot app on my phone.

@smichel17 smichel17 added this to the later milestone Aug 27, 2017
@smichel17 smichel17 removed this from the planned milestone Jul 9, 2018
@mirh
Copy link

mirh commented Nov 30, 2018

@smichel17
Copy link
Member

Whoa, cool.

@smichel17
Copy link
Member

Anyone following this issue who wants to test ⇒ #287

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.

4 participants