-
-
Notifications
You must be signed in to change notification settings - Fork 29
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
[Feature] als.none.manual #127
base: main
Are you sure you want to change the base?
Conversation
Thanks! I'll have a look at the code in the coming days. My main request is that you actually use this in a few days, and tell share your experience here - does it work perfectly, are you sometimes struggling with it, do you wish it to behave differently but can't exactly nail it, etc? I'd really love us to achieve something that you (and hopefully others) will actually want to use, not something that you will abandon after a week because it doesn't really solve the main pain point... |
Yeah. I will keep you posted. A thing I noticed right away is that an absolute brightness reduction, say 50 (out of a total max of 255 for my machine), was too much when the brightness was already near 50 or below. So maybe the reduction should be a percentage of the current brightness value? Like when it is the same brightness of 50, it will now apply 50% reduction instead of an absolute 50 (which makes it zero, bad) resulting in 25 absolute brightness. Do you have any idea how brightness intensity is perceived by human eyes so that the configuration can be made to be the most natural? |
Looks like "Weber-Fechner Law" is one good model for this, see e.g. here on just on wiki |
Nice! I guess reductions being in percentages do a good enough job of decreasing the brightness by more when it is high and by less when it is low, right? Edit: What I feared was that the human perception might be uneven across the brightness spectrum. Like it wouldn't be a clean law like this; some ranges behave completely differently from the ones before and after. Thankfully that doesn't seem to be the case. |
I suppose you are thinking about percentages of percentages? So e.g. on my laptop Try, let's see how it feels :) This at least sounds simpler than implementing some kind of power law logarithmic function, we can go that route if percentages don't achieve great results :) |
Yes, that's correct, and more simply, just I will test, I think this will be enough. Worst case a custom curve is also an option. |
…ductions. Reduce jitter when manually changing brightness.
problematic with `brightnessctl --save set` and `brightnessctl --restore` ran from hypridle
[als.none]
[als.none.manual]
5 = 0
7 = 0
10 = 0
15 = 1
20 = 3
25 = 6
30 = 10
35 = 13
40 = 15
45 = 17
50 = 20
60 = 25
65 = 30
70 = 35
75 = 40
80 = 45
85 = 50
90 = 55
100 = 60 The current config I am using for real-life testing, after the latest commit. @maximbaz What is the upper bound for the computed luma value? I assumed it was 255 but I could only get it upto ~92 I think while displaying a solid white picture that spanned the whole screen. |
It's 0-100 like a percentage, but there could be a bug, try here, do you get 100 on white with all controls hidden? https://deadpixeltest.org Make sure you don't run some tools like dimming screen or enabling opacity of inactive windows or something like that, that could potentially affect whiteness of white. |
Getting 93 on that link too. With no controls, no black bars around the white, full window opacity, etc. |
If you have time and desire, let's debug this in a separate issue not to take focus away from your interesting contribution here :) I pushed Back to this PR, I'll go through the code once we get close to the behavior that you are satisfied with. I think it's awesome that you are continuing to experiment, and I feel like we are definitely getting somewhere. Here are some thoughts, semi-organized 😅
What do you think about all this, am I missing something perhaps? |
For now I am okay with 93 as highest luma because it is easy to work around it in the config for this PR. But if it turns out that the luma calculations are wrong in other ways too then I will definitely look at it. I created a new issue for tracking here: #129
I actually laughed out loud on this for a full minute. Nice find! I came up with it only semi-randomly.
Not sure if I understand correctly what you are saying but going back and forth between dark and bright windows the brightness value switches back and forth between the same exact two values. I monitored it like this:
Yup. |
Was your only concern inconsistent values moving back and forth? |
I feel like I had a different impression of how this is supposed to work, could you help me understand on an example?
And so it looks like after this simple trip we returned to the exact conditions on screen (luma But now that I'm re-reading your earlier messages and looking at the code, it looks like the code reduces brightness on every that event? So from |
A variable is keeping track of "pre-reduction" brightness such that at all times, the following holds true:
This is done by:
Using the previously posted config as example where
|
I seeee...! Many thanks for explaining - this is clearly a totally different algorithm that I imagined you were developing 😅 I honestly like the idea, your step-by-step scenario helped me a lot to understand exactly why it makes sense 😁 How's it going so far in the last couple of days, are you so far satisfied with how it behaves throughout the day? I'm also curious how often you find yourself adjusting the brightness? I have now many thoughts, now that I finally am on board with your idea 😅 Maybe just thoughts out loud that we don't have to act upon, but if you have some ideas I'd appreciate your feedback.
|
Thanks for the admiration.
Working perfectly. Haven't even needed to fine tune the config further. My laptop stays in the same room with windows closed (winter here in Pakistan, basically constant ambience) so have needed to adjust brightness manually much lesser than before. But as this is based on the premise that manual adjustments will be explicitly relied on by wluma, this isn't problematic anyway, I see the point of your question though 😊.
Yup, semi-randomly reduced the values bit by bit.
Maybe, but what is the logical explanation?
Will need a solid explanation first if all the config is going to be reduced to one or two constants, I am not sure what that is at this point. Even then, this gives exact control to the user in an easy to understand way, just set what reduction for which luma. We can always give magic defaults though which the user can tweak manually if they require. On second thought, I suspect even a simple linear plot will be just as effective, even an inverted curve for that matter, which is what I think you mean. Still, I am not sure, maybe depends on the exact monitor, common luma values in certain workflows etc. and even then might vary person to person. My point is the learning counterpart to this manual thing allows very nuanced predictions, this is in line with that. I mean, the user can always set a simple config like this to start and go from there, just like I did: [als.none.manual]
20 = 5
60 = 10
80 = 20 I think we need a number of willing testers with varying setups to get the best answer unless we can come up with a good explanation. Otherwise the best course of action for us in my opinion is to let users do as they desire. Ahhh, this is a never ending debate if we go down this rabbit hole. I am thinking KDE vs. Gnome.
So, some manual input / holding the hands for helping wluma learn?
Nice idea, can be discussed, can you make a new Github issue/discussion for this? |
Simply that you want whiter screen contents to reduce brightness more than darker screen contents?
Well they can simply be the parameters for the polynomial, say up to cubic one 😁 And we can provide a way to find the parameters, e.g. something like this: https://observablehq.com/@grahampullan/interactive-curve-fitting What's potentially interesting with this is that it becomes smooth, for every single luma value we can get a corresponding reduction point, so we no longer have ranges of values, and so it will be potentially less noticeable when wluma reduces or increases brightness. Not saying we have to do anything now, I'm just expressing some considerations 😁 |
Would you like to try how a smooth adjustment would feel like? I'm thinking to just hardcoding the curve that chatgpt fit on your data, it should feel like as if you extended your config for all 100 luma values. I suppose something like this?
|
In terms of code layout, we should go away from using "als" term here. I think what we should get is rename In terms of config, every Would you like to try to adjust layout? Just to say this explicitly, don't worry if you don't - the important part here is to try different ideas and find a solution we are happy with, I can shuffle the final code around afterwards, no problem 😁 |
I realize now that with polynomials you can describe anything that is possible with the current config. So my previous argument isn't useful. Curves are a superset of current config.
I think current config is easier to understand and write than any curve specified by equations which are pretty abstract to grasp right away just looking at the parameters?
No dependency on anything like this too. It would be another thing if this type of GUI was built into wluma but that is pretty out of scope.
It feels pretty smooth right now in usage. Room for more smoothness too currently with current config. Curves might be a bit more expensive to compute multiple times a second? I guess not by much? |
Regarding code layout and config keywords etc., I don't have any strong opinion on that stuff so we can do anything that feels good to you there. No issues. In fact the more I think about it, the more I feel that configuring with curves is alright too other than the points I already mentioned so we can do whatever you as the maintainer feel is the best for the project. I just want to thank you for the engaging discussions. It was really fun. I find your great attention to detail and UX very inspiring. |
And forgot to add, per display config for manual mode is a must, yes. But maybe predictor / choosing manual or auto can be global? What do you say? |
I think you had good points regarding curve, I think it's nice that it can be concise and smooth, but you are definitely right that it's much harder to read and understand and debug and make slight adjustments. Regarding the smoothness, I believe the existing predictor in
I think it will be simply easier to have predictor's-additional-data (such as points in this case) together with defining what predictor to use, rather than having two different places (global predictor + per-output link to global predictor with its additional data). Plus potentially you do want to use different predictors, e.g. the one in "main" for keyboard, if you e.g. want your keyboard to be tied to |
Yeah, this too.
That's a good idea.
Yeah you are right. I guess we are at a point now where we can decide what's left to merge this PR. Can you write up what are the final adjustments needed and we can divide the work between us or either one of us can do it all by themselves? What do you say? |
|
I can't reproduce the weirdness, whether I hold the button or press-release quickly multiple times. Could it be that you didn't give wluma access to raw driver? (See here). Could you maybe explain in more details what you see and how this looks like? From what I could experience during testing, this works amazingly! |
Odd, I can't reproduce it right now. Never experienced it on My key-repeat rate is/was always 60 times a second, maybe that caused it only at that time? If you as well can't experience anything weird now with 60+ times a second repeat rate and the code looks fine to you, then I guess it is okay to not worry about it if it doesn't happen again in the coming days (I didn't use my laptop since my previous comment). |
So besides this, only writing a few tests remains? Anything else? What about some extra timeout? Ref. #127 (comment) |
So first of all I want to say that you've already done the most important part of the contribution - I dare say, we landed on an implementation that is better than perhaps either of us initially envisioned, it is very pleasant to use, and a nice overall addition to wluma! Thank you for sticking for this :) In terms of what we do next, my first and foremost goal is to ensure that the implementation we have is useful to you, so while working on the final things, if you find ways to reproduce weird behavior or find other issues, identifying and fixing those is the most important. Other than that, one or few tests would be nice to add, just to seal the idea of this algorithm, i.e. to protect it from future me 😂 Not super necessary by any means. And I'd like to have ALS as an input, just because it changes format of config and potentially adds interesting use-cases with no compromises on your use-case. I'll do some code cleanup afterwards, so dont worry e.g. about that extra timeout. I'm very flexible on how to proceed - we can continue working inside this PR (e.g. to reduce friction, since both of us can just push), we can open new PRs that merge into this PR, we can merge this one into main and open new PRs on top of main. What do you feel most comfortable with? I should also ask, do you have capacity and/or desire to add those changes, or would you rather be done with this? I'm happy to merge this whenever you want :) |
Yeah.
Thanks, and I thank you for all your efforts too, wouldn't have been possible without.
Actually, I realize I had inadvertently made a small change (not anything useful) only my local copy of the code before testing just now. Sorry for that. The bug is still there once I reverted this change. So, can you try reproducing once again while something like this command is running so that you can see the exact values for more info: # for fish shell:
while true; set x (notify-send --replace-id=$x --print-id Current\ Brightness:\ (brightnessctl get)); sleep 0.01; end Bug description: after the cooldown period, the first brightness change (increase or decrease) makes the brightness change to what value you attempted to set it to, then instantly reverts back to where you started, then finally settles to what you attempted to set it to initially after a time period that seems to be directly proportional to: const COOLDOWN_STEPS: u8 = 15; // this value, multiply this by roughly 100ms I think that's all, everything else works great.
We can make it a non-breaking change, for example like this: (which I think we should do anyway) [[output.backlight]]
name = "eDP-2"
path = "/sys/class/backlight/intel_backlight"
capturer = "wayland"
[output.backlight.predictor.manual]
# if this key is present, use it, and ignore any thresholds.* or refuse to start, can be discussed which
thresholds = { 5 = 0, 30 = 30, 60 = 60 }
# if thersholds.* is present, use them as you described earlier
thresholds.dark = { 5 = 0, 30 = 30, 60 = 60 }
thresholds.work = { 5 = 0, 30 = 3, 60 = 6 }
Let's merge this one into main after tests and timeout bug, and open new PRs on top of |
OK! I'll try to reproduce the bug tomorrow, unless you beat me to it with a fix 😁
Other parts of code already use the fact that ALS threshold is called "none" when using Besides the bug to fix, I'll merge when you tell me you are happy with this PR being merged 👍 |
I think I didn't understand what you just said. How are the two Line 8 in db77d27
thresholds key under [output.backlight.predictor.manual] possibly being another table (with keys night , dark , dim , and so on)? We can just error out calling it a bad config when [als.none] is used alongside output.backlight.predictor.manual.thresholds.{night,dark,dim,normal,bright,outdoors} .
I am sure I am missing something. I mean, either it will be like this: (current) [als.iio]
path = "/sys/bus/iio/devices"
thresholds = { 0 = "night", 20 = "dark", 80 = "dim", 250 = "normal", 500 = "bright", 800 = "outdoors" }
[[output.backlight]]
name = "eDP-2"
path = "/sys/class/backlight/intel_backlight"
capturer = "wayland"
[output.backlight.predictor.manual]
thresholds = { 5 = 0, 30 = 30, 60 = 60 } or like this: (non-breaking future change) [als.iio]
path = "/sys/bus/iio/devices"
thresholds = { 0 = "night", 20 = "dark", 80 = "dim", 250 = "normal", 500 = "bright", 800 = "outdoors" }
[[output.backlight]]
name = "eDP-2"
path = "/sys/class/backlight/intel_backlight"
capturer = "wayland"
[output.backlight.predictor.manual]
thresholds.dark = { 5 = 0, 30 = 30, 60 = 60 }
thresholds.work = { 5 = 0, 30 = 3, 60 = 6 } And we can refuse to run on this: (or prefer one of [als.iio]
path = "/sys/bus/iio/devices"
thresholds = { 0 = "night", 20 = "dark", 80 = "dim", 250 = "normal", 500 = "bright", 800 = "outdoors" } # this is never an issue
[[output.backlight]]
name = "eDP-2"
path = "/sys/class/backlight/intel_backlight"
capturer = "wayland"
[output.backlight.predictor.manual]
thresholds = { 5 = 0, 30 = 30, 60 = 60 }
thresholds.dark = { 5 = 0, 30 = 30, 60 = 60 }
thresholds.work = { 5 = 0, 30 = 3, 60 = 6 } I am genuinely curious to know what you mean. Thanks. |
There's no smart purpose, this is purely due to laziness to maintain advanced config validation logic ( 😅 ), I want the syntax to simply be In wluma there is no concept of "not having an ALS provider", it always is there, only that in IIO and webcam a user is free to choose their preferred names for And so this is then a totally valid config that we don't need to error out on - it just picks the correct threshold based on which ALS provider is enabled and what threshold that particular provider reports. # [als.none]
[als.iio]
path = "/sys/bus/iio/devices"
thresholds = { 0 = "night", 20 = "dark", 80 = "dim", 250 = "normal", 500 = "bright", 800 = "outdoors" }
[[output.backlight]]
name = "eDP-2"
path = "/sys/class/backlight/intel_backlight"
capturer = "wayland"
[output.backlight.predictor.manual]
thresholds.none = { 5 = 0, 30 = 30, 60 = 60 }
thresholds.dark = { 5 = 0, 30 = 30, 60 = 60 }
thresholds.work = { 5 = 0, 30 = 3, 60 = 6 } |
I saw that on startup this controller instantly goes into cooldown mode, I pushed a small change to prevent that.
Nice bug you caught! It is in fact not specific to your branch, and is even reproducible on It is simply a race condition - we periodically predict a good brightness value and sent the prediction further to another thread, but it's not atomic operation, the predicted value isn't instantly being applied. So when you manually change brightness, we stop sending new predictions during the cooldown period, but the last prediction that we sent might have not been applied yet, so it is being applied a millisecond later after you did your manual adjustment. I think nobody has noticed that before simply because people usually press button several times, so even if wluma undid the first keypress, starting from the second keypress wluma no longer conflicts with you. You probably also discovered this, as you wrote that the first brightness change gives the weird behavior, whereas the consequent brightness changes within cooldown period no longer bug out. I can think of some ways to reduce the likelihood of this happening, but I don't think we can ever totally eliminate this, simply because of the nature of this being a shared resource, where we cannot take an exclusive lock to do atomic change. In any case let's not try to fix it in this PR :) |
I see. But no worries, this is a totally genuine reason too if you feel it will be complex to do. Thanks for explaining. And even though I might not be the one to implement this, I say that let's wait at least a few weeks before anyone works on it. It is fine if it will be a breaking change because we can keep this whole PR undocumented behavior for now until we have a final decision on |
Thanks for the fix.
Ah. Then it is out of scope for this PR indeed. I guess I will push some tests now and then we merge 🥳. |
Cool! Why would you like to wait though with adding als support? There is a number of users that run latest code from |
My thinking is that we might change our minds on:
And merging this PR doesn't break anyone's existing config as all new config introduced here is optional with defaults being the old stuff. |
ah I see - at least my mind is already made up, in my eyes this achieves a generic solution, easily composable and opens up more possibilities, I'll go with this unless we discover new arguments before we merge this one. |
The premise of als support for the new predictor is that it is useful to essentially specify different "curves" based on different als readings. But I think that one single curve is enough for every use case. Let's say my curve is a straight line (which actually works pretty well), I will be happy with the same straight line whether the als says it is a dark room or a bright sunny day. The same reduction percentages based on current luma will work fine in either case. I think what we want is maybe something like this: like manually changing the brightness adjusts |
Basically this manual predictor excludes luma from the learning process (making it manually specified). |
I'm not quite sure how we can transform als value into a pre-reduction brightness adjustment, especially without user input expressing that desire...? Example with multiple curves is very simple though - at work I want bright browser screen to reduce brightness at most by 10%, while in the evening I want 80% reduction. |
Well when you put it in those magic words it is somehow making sense to me suddenly 😆. You said the same exact thing with config before but I couldn't understand it then. Sorry for that. Okay then, I am renaming it to |
Awesome 😁😁 |
[als.iio]
path = "/sys/bus/iio/devices"
thresholds = { 0 = "night", 20 = "dark", 80 = "dim", 250 = "normal", 500 = "bright", 800 = "outdoors" } What is "night", "dark", "dim", "normal", "bright", and "outdoors" used for in Edit: Nevermind, I read it in the readme. So it is only used for giving learning data point fields some name? |
Yeah, to give ranges of ALS values some human-friendly name, and then additionally to associate training data with the named range (also in part because real ALS is logarithmic, so jump from 0 to 10 feels a lot more than from 10 to 100, so it's hard to use it purely as a coefficient). So when we need to predict a value, we only use training data available for this one particular range only, the current one. In other words, it's again basically different curves for different ALS, the only difference is that with "manual" predictor we define curves in advance, and in "smart" predictor you build the curves as you continue to use wluma and adjust values from time to time, every time you adjust values you are basically giving an additional point to one specific curve, making it that more precise. |
Currently hard coded it to always use |
Ready for final review. |
So I decided to add the table that maps luma ranges to brightness reduction values under
als.none
. It made sense to think ofals.none
as also having a manual mode as well that doesn't rely on learning at all but is more, you know... manual. But underneath it just switches to a different predictor whenals.none.manual
isn't empty.I am configuring it like this for testing:
How this table is interpreted:
There is no sanity check on the new config right now.
Changing the brightness really quickly also has a bit of weirdness. Though I am not sure how to solve that or why that happens.
Also, I am a total beginner with rust so please don't hesitate to suggest any improvements as this probably does have room for improvement.
Resolves: #126