Add lights support for Nook Glowlight 4 Plus (bnrv1300)#592
Conversation
| // Nook Glowlight 4 (4/4e/4plus) | ||
| (MANUFACTURER == "barnesandnoble") | ||
| && (MODEL == "bnrv1000" || MODEL == "bnrv1100" || MODEL == "bnrv1300") | ||
| // Nook Glowlight 4 Plus |
There was a problem hiding this comment.
That does after 4, not before. ;-)
Starting a new process every time setWarmth is set using su -c could cause freezes in the slider that makes several rapid calls. One should test. |
8aa086f to
493d770
Compare
|
I asked Claude about issues using "su -c" and it decided to change the code such that: I don't know if that's an improvement. |
39b3250 to
94e0ddc
Compare
|
I'm not sure if you meant to force push this branch, but those last two commits about the wifi seem to have stowawayed themselves. |
|
Oh man, I've definitely given Claude too much power! I'm sorry I'm messing this up. Do you think we can get the basic Glowlight 4 Plus changes added in? I'll have to ask Claude how to separate the wifi stuff from the basic Glowlight 4 Plus color and warmth functionality. |
|
You can simply revert the last two commits ( |
I don’t think it’s an improvement. I asked GemMindGPT to analyze it from your perspective/worldview: Claude’s proposal solves the wrong bottleneck by introducing a much more fragile IPC architecture into a latency-sensitive path. The original concern — spawning The issue is not simply “persistent shell bad.” The issue is that the implementation turns a deterministic one-shot command execution into a long-lived stateful IPC channel without implementing the machinery required to safely manage that channel. 1. The implementation is not actually managing the shellThe code opens an interactive That is incorrect. Once you keep a shell open, you inherit all the responsibilities of stream lifecycle management:
None of that exists in the proposed implementation. The shell is effectively being used as an unmanaged daemon with a write-only pipe attached to it. That is a serious architectural smell, especially inside Android where process lifetime is controlled externally by the framework. 2. The deadlock risk is real — even if rareYour point about pipe buffering is correct and important. The implementation never drains:
Even if the current Pipe buffers are finite. Once full, the child process blocks on write. When the shell blocks internally, subsequent writes from the app can also block. Since this code is likely triggered from UI-adjacent execution paths, this creates the possibility of UI stalls or ANRs. The important nuance is:
That is exactly the kind of latent bug that survives development and appears later as “random freezes.” 3. The error handling is objectively brokenThis is probably the strongest criticism. Using That means: suWriter!!.println(cmd)does not reliably indicate success. If the shell dies, the pipe breaks, or writes fail, So the application can enter a false-success state where:
This is worse than a visible failure because it destroys state consistency. The implementation effectively assumes: but That alone is enough reason to reject the implementation in production code. 4.
|
|
Dang! Your AI just schooled my AI! Claude was impressed (I was, too, but I only understood maybe 50% of it). Claude decided to: ▎ Replaced the persistent root shell with a simple Runtime.exec(arrayOf("su", "-c", "echo $warmth > $COLOR_FILE")) per call, matching the pattern already |
|
Instead of writing directly to /sys/class, you can look at the messages in the topics below showing how the original driver was developed. Maybe you can find a miracle method that works. |
|
Claude seems to still be changing this PR even though I instructed it that we must troubleshoot problems locally, first. I'm sorry about that. |
|
So, following @hugleo's suggestion to look for other methods of changing the warmth setting, I just gave Claude full access to my Nook and had it decompile the system apps to see how the warmth was changed. |
That's kind of scary. Anyway, that means COLOR_FILE and setWarmthViaSu still need to be cleaned up, doesn't it? |
|
Another thing. This way, if you change the setting outside of KOReader, you can get the consistent value from the Android system. |
It is a bit scary! But I told Claude to just pull files from the device to decompile them, not start changing important things on the device. We'll see if it complies or does a complete rm -rf /. Claude wanted to keep them as a backup, but I think it's okay to get rid of them. It should be noted that we are using com.nook.partner to do the warmth settings, but it is the same app that takes control of the device to force it into the nook ecosystem and act as the default launcher, so we have to do some setup tasks to disable some of it's intents. I'll work on getting rid of those root-requiring commands |
These systems are very reticent to break existing API contracts, even if the "existing" contract is one they implemented five minutes ago. Which makes sense for some use cases but not for others. Anyway, you don't need any Claude to remove a few lines. ;-) |
4f1eecd to
4e5347e
Compare
|
Sorry for the messy history on this branch — I've force-pushed a clean rewrite with 2 commits. Anyway, I'm hoping this addresses some of the issues that were raised previously and can be eventually merged! Claude's comments:
Also bundled a small fix in the second commit: |
|
|
||
| // Cache root view for EPD calls from NativeThread (window.decorView must not be | ||
| // accessed from a non-UI thread; pre-cache it here on the main thread instead). | ||
| epdRootView = view ?: window.decorView.findViewById(android.R.id.content) |
There was a problem hiding this comment.
Is a new NativeSurfaceView with a fresh epdRootView created on rotation and such? That caching somewhat worries me (and seems rather unrelated to lights support ;-).
There was a problem hiding this comment.
I believe it is once again apparent that I have no idea what I'm doing. I can't even begin to parse the EPD stuff.
Claude says:
Good point on both counts.
On staleness: I've added a refresh in
surfaceCreatedsoepdRootViewis updated whenever the surface is (re)created, rather than relying solely on theonCreateassignment:override fun surfaceCreated(holder: SurfaceHolder) { super.surfaceCreated(holder) epdRootView = view ?: window.decorView.rootView drawSplashScreen(holder) }On it being unrelated to lights: agreed — I've split the
MainActivitychanges out into a separate PR (#596) so this one stays focused on the Nook GL4+ device support.
f36ad70 to
b7978df
Compare
| override fun getPlatform(): String = "freescale" | ||
| override fun getMode(): String = "full-only" | ||
|
|
||
| override fun getWaveformFull(): Int = EMPEROR_EINK_NO_MERGE + EMPEROR_EINK_GC16_MODE |
There was a problem hiding this comment.
Mind that Codacy complains about the formatting. Otherwise fine by me.
There was a problem hiding this comment.
Yay!!!
Claude believes the formatting is fixed:
Fixed — removed the alignment spaces from the companion object and the waveform overrides.
| } | ||
|
|
||
| override fun getPlatform(): String = "freescale" | ||
| override fun getMode(): String = "full-only" |
There was a problem hiding this comment.
It should be "all" if the partial modes you implemented are working in the KOReader GUI. Otherwise, keep it as "full-only".
|
Since it's a new driver, it can be added to the EInk test (https://github.com/koreader/android-luajit-launcher/blob/master/app/src/main/java/org/koreader/launcher/TestActivity.kt). And if ADB properties need to be set to allow proper writing for the light driver, please add a section to the wiki explaining that, similar to this example: https://github.com/koreader/koreader/wiki/Android-tips-and-tricks#onyx-devices With these changes, is everything working as expected? |
Split bnrv1300 out of NOOK_GL4 into a dedicated NOOK_GL4PLUS device ID. NookGL4plusController: - Brightness via Settings.System.SCREEN_BRIGHTNESS (requires "Modify system settings" special app permission; same approach as TolinoNtxController) - Warmth via com.nook.partner GlowLightService — no root required. Sends action_set_color_temperature (0–100 scale); the service rescales ÷10 for lm3630a hardware and calls PowerManager.setFrontlightBrightnessColor() under its own DEVICE_POWER privilege. - getWarmth() reads screen_brightness_color from Settings.System so the value stays consistent with changes made outside KOReader. - Hardware range: brightness 0–100, warmth 0–10. EPD: NOOK_GL4PLUS uses the same NGL4EPDController as NOOK_GL4. Waveform control via force_update_mode (AllWinner sysfs node) requires a Magisk module to grant write access — not appropriate for upstream. See https://github.com/backcountrymountains/nook-gl4plus-research for details. Fixes koreader/koreader#14574 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
efb1c6c to
f4a7184
Compare
The GL4 Plus (bnrv1300) uses an AllWinner "Emperor" platform that is architecturally distinct from the GL4/4e (Freescale NTX). Split it into its own DeviceInfo ID and wire it to a dedicated EPD controller. NookEmperorEPDController uses ViewRootImpl.setRefreshMode(int) via reflection — a B&N-specific addition to their Android 8.1 framework. The call routes through SurfaceFlinger → HWC (hwcomposer.virgo.so) → hwc_set_layer_refresh_mode → layer->refreshMode → /dev/disp ioctl. Because HWC reads layer->refreshMode during composition, the waveform is applied to the correct (incoming) buffer, not the currently-displayed one. GC16 (mode 0x4) confirmed working on device: kernel dmesg shows mode=0x200004 on full-page refreshes. No root or Magisk module required. Fallback order: 1. ViewRootImpl.setRefreshMode (B&N Android 8.1) 2. SurfaceControl.setRefreshMode via mSurfaceControl (AOSP Android 10+) 3. sysfs force_update_mode write (requires Magisk epd_gc16 module) Note: this PR overlaps with koreader#592 on DeviceInfo.kt (same bnrv1300 split) and on EPDFactory.kt (koreader#592 adds NOOK_GL4PLUS to the NGL4 group; this PR gives it a dedicated controller instead). Should be merged after koreader#592 or koreader#592's EPDFactory change should be dropped in favour of this one. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
So, after parsing the above comment about the EPD controller, I discovered that all Claude had done was rediscover all of the previous work on the Nook Glowlight 4/4e After being crestfallen, going through much despair and struggle to find workarounds and travel down many dead ends, Claude and I were able to make an EPD controller that added functionality beyond what had previously been implemented. It was a true hero's journey (joking). I asked Claude to document some of it here and here. Claude decided that it was best to split this PR (I'm so sorry it has gone through so many iterations) with the EPD controller PR. We added some text in the description of this PR WRT what could be put in the wiki. We don't have write access to that and it seemed that another PR was another headache, but we can add it to the wiki however you see fit. Claude says:
|
The GL4 Plus (bnrv1300) uses an AllWinner "Emperor" platform that is architecturally distinct from the GL4/4e (Freescale NTX). Split it into its own DeviceInfo ID and wire it to a dedicated EPD controller. NookEmperorEPDController uses ViewRootImpl.setRefreshMode(int) via reflection — a B&N-specific addition to their Android 8.1 framework. The call routes through SurfaceFlinger → HWC (hwcomposer.virgo.so) → hwc_set_layer_refresh_mode → layer->refreshMode → /dev/disp ioctl. Because HWC reads layer->refreshMode during composition, the waveform is applied to the correct (incoming) buffer, not the currently-displayed one. GC16 (mode 0x4) confirmed working on device: kernel dmesg shows mode=0x200004 on full-page refreshes. No root or Magisk module required. Fallback order: 1. ViewRootImpl.setRefreshMode (B&N Android 8.1) 2. SurfaceControl.setRefreshMode via mSurfaceControl (AOSP Android 10+) 3. sysfs force_update_mode write (requires Magisk epd_gc16 module) Note: this PR overlaps with koreader#592 on DeviceInfo.kt (same bnrv1300 split) and on EPDFactory.kt (koreader#592 adds NOOK_GL4PLUS to the NGL4 group; this PR gives it a dedicated controller instead). Should be merged after koreader#592 or Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The GL4 Plus (bnrv1300) uses an AllWinner "Emperor" platform that is architecturally distinct from the GL4/4e (Freescale NTX). Split it into its own DeviceInfo ID and wire it to a dedicated EPD controller. NookEmperorEPDController uses ViewRootImpl.setRefreshMode(int) via reflection — a B&N-specific addition to their Android 8.1 framework. The call routes through SurfaceFlinger → HWC (hwcomposer.virgo.so) → hwc_set_layer_refresh_mode → layer->refreshMode → /dev/disp ioctl. Because HWC reads layer->refreshMode during composition, the waveform is applied to the correct (incoming) buffer, not the currently-displayed one. GC16 (mode 0x4) confirmed working on device: kernel dmesg shows mode=0x200004 on full-page refreshes. No root or Magisk module required. Fallback order: 1. ViewRootImpl.setRefreshMode (B&N Android 8.1) 2. SurfaceControl.setRefreshMode via mSurfaceControl (AOSP Android 10+) 3. sysfs force_update_mode write (requires Magisk epd_gc16 module) Note: this PR overlaps with koreader#592 on DeviceInfo.kt (same bnrv1300 split) and on EPDFactory.kt (koreader#592 adds NOOK_GL4PLUS to the NGL4 group; this PR gives it a dedicated controller instead). Should be merged after koreader#592 or Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split bnrv1300 out of the NOOK_GL4 device ID into a new NOOK_GL4PLUS ID with a dedicated
NookGL4plusController.Brightness is set via
Settings.System.SCREEN_BRIGHTNESS— consistent with other Android e-reader controllers (e.g. TolinoNtxController, which uses the same lm3630a hardware). On e-ink devices the frontlight is driven by dedicated hardware that only responds to the system brightness setting, notwindow.attributes.screenBrightness. This permission is reset on fresh install.Warmth is set via the Nook's own
com.nook.partner.service.GlowLightService— no root required. The controller sends astartServiceintent with actionaction_set_color_temperatureand extraextra_color_temperature(0–100 scale). The service (a priv-app holdingDEVICE_POWER) internally rescales by ÷10 for the bnrv1300 hardware and callsPowerManager.setFrontlightBrightnessColor()under its own privilege, bypassing both the SELinux restriction on sysfs writes and theDEVICE_POWERrequirement for direct binder calls. Hardware range is 0–10. Note: the Nook's native display settings show 0=warm and 10=cool — KOReader's warmth slider uses the opposite convention (0=cool, 10=warm), so the controller passes the value through without inversion and lets KOReader's UI labeling handle user-facing direction.EPD waveform control (GC16 rootless, no Magisk module required) is in companion PR #597.
Setup
Brightness — "Modify system settings" permission
Required after each fresh install. No root needed.
Via ADB:
adb shell appops set org.koreader.launcher WRITE_SETTINGS allowVia the system UI: long-press the KOReader icon → App Info → Advanced → Modify system settings → Allow.
Warmth —
com.nook.partnermust be enabledWarmth depends on
GlowLightServiceinsidecom.nook.partner. The package must not be disabled wholesale. If warmth is unresponsive:adb shell pm enable com.nook.partnerIf you previously disabled
com.nook.partnerto remove the B&N launcher or block OTA updates, re-enable the package and then selectively re-disable the components you don't want —GlowLightServiceis independent of those components and is unaffected:Do not disable
com.nook.partner.service.GlowLightServiceitself.Test plan
Fixes koreader/koreader#14574
Lua-side changes: koreader/koreader#15561
This change is