Skip to content

Add monochrome mode - #350

Closed
coolpx wants to merge 2 commits into
LibreShift:masterfrom
coolpx:feature/monochrome-mode
Closed

coolpx wants to merge 2 commits into
LibreShift:masterfrom
coolpx:feature/monochrome-mode

Conversation

@coolpx

@coolpx coolpx commented Nov 23, 2025

Copy link
Copy Markdown

This has been a wishlist feature for me for a while, so I decided to add it 😊

This preserves contrast when using a very low color temperature (close to 500K). It's rooted-only since it can't be replicated without SurfaceFlinger.

Tested on my Pixel 8 Pro running Android 16.

Simulated screenshots (you can see the largest difference in the download links, they're nearly invisible on low brightness without this):
enabled
disabled

Introduces a new monochrome setting that applies a high-quality
grayscale filter before applying color temperature. This allows users
with very low color temperatures (500K) to still distinguish blue
objects as different shades of gray before the warm tint is applied.

Implementation:
- Requires root mode (uses SurfaceFlinger color matrix transformation)
- Applies luminosity-based grayscale (0.299R + 0.587G + 0.114B)
- Automatically hidden when root mode is disabled
- Added UI checkbox with descriptive summary and root requirement notice
- Backward compatible (defaults to false)

Changes:
- Added monochrome boolean field to Profile data class with JSON support
- Implemented color matrix transformation in SurfaceFlinger
- Added monochrome preference to Config with profile activation
- Added FilterFragment logic to show/hide based on root mode availability
- Added UI strings and preference resources

The feature preserves contrast with extreme color temperatures while
maintaining the benefits of blue light reduction.
The grgit 4.1.0 dependency was not available in Maven repositories,
causing build failures. Updated to 4.1.1 which is available and
compatible with the existing Gradle and Kotlin versions.

Also added gradlePluginPortal() repository to buildscript to ensure
proper dependency resolution.

@smichel17 smichel17 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks really nice! Just a few small suggestions.

}

var monochrome by BooleanPreference(R.string.pref_key_monochrome, false) {
activeProfile.run { if (it != monochrome) activateProfile(copy(monochrome = it)) }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure this works, since it matches the other ones, but ugh, I was so hyperfixated on making code concise (specifically as measured by lines of code) when I wrote this.

Today I'd write it more like this (assuming I have correctly inferred it; I did this in GitHub webui):

Suggested change
activeProfile.run { if (it != monochrome) activateProfile(copy(monochrome = it)) }
if (it != monochrome) {
activateProfile(activeProfile.copy(monochrome = it))
}

No need to change this, unless you want to change the others too, which you are more than welcome to.

Comment on lines +33 to 34
val monochrome: Boolean = false)
: Event, Comparable<Profile> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does Kotlin support trailing commas now?

Suggested change
val monochrome: Boolean = false)
: Event, Comparable<Profile> {
val monochrome: Boolean = false,
) : Event, Comparable<Profile> {

Shell.su(call).exec()
val profile = activeProfile

if (profile.monochrome) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of duplication here, relative to the unique code. Here's how I'd suggest handling it:

  • Move the r, g, and b declarations before the if. Maybe they'll be calculated differently in the future, but for now they're the same, so no need to prematurely split code paths.
  • Extract a common call function which takes the matrix part of the string, builds and logs the command, and does the SU call. Each of the branches would now end with this.

pref.summary = if (Config.useRoot) {
getString(R.string.pref_summary_monochrome)
} else {
getString(R.string.pref_summary_monochrome_requires_root)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're hiding the pref when root is disabled, then no need for the "requires root" description, right?

@satmandu

satmandu commented Apr 1, 2026

Copy link
Copy Markdown

Is there any way to do this without being rooted?

As per https://www.neuroai.science/p/blue-light-filters-dont-work the proper way to avoid messing up circadian rhythms would be to eliminate ALL non-red light, so a monochrome red mode would be perfect to accomplist this.

Fixes: #353

@satmandu

satmandu commented Apr 1, 2026

Copy link
Copy Markdown

Also maybe Shizuku could help with this as per #342

@coolpx

coolpx commented Sep 13, 2026

Copy link
Copy Markdown
Author

Closing this because I used AI for the feature implementation and my thoughts on the ethics of "contributing" that kind of thing to open-source projects have changed dramatically.

@coolpx coolpx closed this Sep 13, 2026
@smichel17

Copy link
Copy Markdown
Member

It's nice to see people changing their minds, thank you for that. So much of the internet seems incapable of it, so it's nice to experience.

My personal stance on coding agents is fairly similar to the SFC's recommendations on the topic, with one significant deviation: I have zero tolerance for llm-generated prose (including documentation and code comments!). I find LLM use actively hinders humans from coming to a shared understanding — as if you fed your original words through a machine translation to a different language and back to English.

For this particular PR, given human effort has already been invested (my review), I would be fine if you or someone else wants to address the comments and take it to completion. (although I'm quite busy these days, so I don't promise any timeline on my next review)

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants