From e152101f4cd7ff22df9f02380d52d6a9526f5864 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 13 Aug 2026 16:53:57 -0600 Subject: [PATCH 1/2] Nook GL4 Plus: make warmth survive B&N's colour temperature service GlowLightService also owns B&N's Color Temperature Management, which re-applies a warmth value on every SCREEN_ON -- after the framework has restored screen_brightness_color, so it wins over anything we set. Its mode is persisted only in the service's own shared prefs, and getCTMMode() defaults to -1 (CTM_MODE_DISABLE) when that key is missing, which is what an unclean shutdown leaves behind. In that mode setupCTM() forces COLD_LIGHT -- 0 on Emperor -- and action_set_color_temperature does not persist either, because saveColorTemperature() routes to a ScheduleMode whose save is a no-op with null sunrise/sunset. Warmth therefore resets to cold on every unlock, and no amount of moving the slider makes it stick. Assert CTM_MODE_MANUAL once per process, immediately before the first warmth write. In manual mode the same warmth intent writes manual_color_temperature, so the value we set is what gets re-applied at the next unlock. The mode intent must always be followed by a warmth write, since asserting the mode makes the service re-apply its own stored value -- hence the first call skips the redundant-write early return. Verified on a BNRV1300. With ctm_mode absent, the service logs setupCTM:-1 and drives the light to colour 0 on every SCREEN_ON. After this change the first warmth write logs "CTM mode set to manual", the service logs setupCTM:0, and the value survives both a screen cycle and a reboot. Co-Authored-By: Claude Opus 5 --- .../device/lights/NookGL4plusController.kt | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/koreader/launcher/device/lights/NookGL4plusController.kt b/app/src/main/java/org/koreader/launcher/device/lights/NookGL4plusController.kt index 261d2631c..4f32ce123 100644 --- a/app/src/main/java/org/koreader/launcher/device/lights/NookGL4plusController.kt +++ b/app/src/main/java/org/koreader/launcher/device/lights/NookGL4plusController.kt @@ -14,6 +14,14 @@ import org.koreader.launcher.device.LightsInterface * Sends action_set_color_temperature (0-100 scale); the service rescales to 0-10 for * the lm3630a_led hardware and calls PowerManager.setFrontlightBrightnessColor() using * its own DEVICE_POWER privilege. + * + * The same service owns B&N's Color Temperature Management (CTM), which re-applies a + * warmth value on every SCREEN_ON. Its mode lives only in the service's shared prefs + * (ctm_preference.xml, key ctm_mode) and defaults to -1 (CTM_MODE_DISABLE) when that + * key is missing -- which is what an unclean shutdown leaves behind. In mode -1 the + * service forces COLD_LIGHT (0) after every unlock and drops our stored value, so + * warmth silently resets to cold. Mode 0 (CTM_MODE_MANUAL) instead re-applies the + * value we set, so we assert it once per process before the first warmth write. * see https://github.com/koreader/koreader/issues/14574 */ class NookGL4plusController : LightsInterface { @@ -27,9 +35,13 @@ class NookGL4plusController : LightsInterface { private const val GLOWLIGHT_SERVICE = "com.nook.partner.service.GlowLightService" private const val ACTION_SET_COLOR_TEMPERATURE = "action_set_color_temperature" private const val EXTRA_COLOR_TEMPERATURE = "extra_color_temperature" + private const val ACTION_SET_CTM_MODE = "action_set_ctm_mode" + private const val EXTRA_CTM_MODE = "extra_ctm_mode" + private const val CTM_MODE_MANUAL = 0 } @Volatile private var currentWarmth: Int = MIN + @Volatile private var ctmModeAsserted: Boolean = false override fun getPlatform(): String = "nook" override fun hasFallback(): Boolean = false @@ -76,11 +88,37 @@ class NookGL4plusController : LightsInterface { Log.w(TAG, "warmth value out of range: $warmth") return } - if (warmth == getWarmth(activity)) return + // Skip redundant writes -- but never on the first call of the process: asserting + // CTM mode makes the service re-apply its own stored value, so it must always be + // followed by a warmth write that seeds that value with ours. + if (ctmModeAsserted && warmth == getWarmth(activity)) return Log.v(TAG, "Setting warmth to $warmth of $WARMTH_MAX") + assertManualCtmMode(activity) setWarmthViaService(activity, warmth) } + /* Put B&N's Color Temperature Management into manual mode, so that the value written + * by setWarmthViaService() survives the next SCREEN_ON instead of being forced to cold. + * GlowLightService is an IntentService, so this is handled before the warmth intent + * that follows it. Idempotent, and a no-op cost after the first call. */ + private fun assertManualCtmMode(activity: Activity) { + if (ctmModeAsserted) return + try { + val intent = Intent(ACTION_SET_CTM_MODE).apply { + component = ComponentName(GLOWLIGHT_PACKAGE, GLOWLIGHT_SERVICE) + putExtra(EXTRA_CTM_MODE, CTM_MODE_MANUAL) + } + if (activity.startService(intent) != null) { + ctmModeAsserted = true + Log.v(TAG, "CTM mode set to manual") + } else { + Log.w(TAG, "GlowLightService unavailable, cannot set CTM mode") + } + } catch (e: Exception) { + Log.w(TAG, "setting CTM mode failed: $e") + } + } + private fun setWarmthViaService(activity: Activity, warmth: Int): Boolean { return try { val intent = Intent(ACTION_SET_COLOR_TEMPERATURE).apply { From 2cfaffe37f5d8446f24af1e8df0438d5acc7fdbf Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 5 Sep 2026 08:34:37 -0600 Subject: [PATCH 2/2] Nook GL4 Plus: shorten added comments Per review: the rationale is in the commit message and the PR, it does not need restating in the source. Co-Authored-By: Claude Opus 5 --- .../device/lights/NookGL4plusController.kt | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/koreader/launcher/device/lights/NookGL4plusController.kt b/app/src/main/java/org/koreader/launcher/device/lights/NookGL4plusController.kt index 4f32ce123..592b54115 100644 --- a/app/src/main/java/org/koreader/launcher/device/lights/NookGL4plusController.kt +++ b/app/src/main/java/org/koreader/launcher/device/lights/NookGL4plusController.kt @@ -14,14 +14,8 @@ import org.koreader.launcher.device.LightsInterface * Sends action_set_color_temperature (0-100 scale); the service rescales to 0-10 for * the lm3630a_led hardware and calls PowerManager.setFrontlightBrightnessColor() using * its own DEVICE_POWER privilege. - * - * The same service owns B&N's Color Temperature Management (CTM), which re-applies a - * warmth value on every SCREEN_ON. Its mode lives only in the service's shared prefs - * (ctm_preference.xml, key ctm_mode) and defaults to -1 (CTM_MODE_DISABLE) when that - * key is missing -- which is what an unclean shutdown leaves behind. In mode -1 the - * service forces COLD_LIGHT (0) after every unlock and drops our stored value, so - * warmth silently resets to cold. Mode 0 (CTM_MODE_MANUAL) instead re-applies the - * value we set, so we assert it once per process before the first warmth write. + * The service also re-applies warmth on every SCREEN_ON, forcing it to cold unless its + * CTM mode is manual, so we assert that mode before the first warmth write. * see https://github.com/koreader/koreader/issues/14574 */ class NookGL4plusController : LightsInterface { @@ -88,19 +82,15 @@ class NookGL4plusController : LightsInterface { Log.w(TAG, "warmth value out of range: $warmth") return } - // Skip redundant writes -- but never on the first call of the process: asserting - // CTM mode makes the service re-apply its own stored value, so it must always be - // followed by a warmth write that seeds that value with ours. + // Never skip the first write: it seeds the value the service re-applies. if (ctmModeAsserted && warmth == getWarmth(activity)) return Log.v(TAG, "Setting warmth to $warmth of $WARMTH_MAX") assertManualCtmMode(activity) setWarmthViaService(activity, warmth) } - /* Put B&N's Color Temperature Management into manual mode, so that the value written - * by setWarmthViaService() survives the next SCREEN_ON instead of being forced to cold. - * GlowLightService is an IntentService, so this is handled before the warmth intent - * that follows it. Idempotent, and a no-op cost after the first call. */ + /* GlowLightService is an IntentService, so this is handled before the warmth intent + * that follows it. */ private fun assertManualCtmMode(activity: Activity) { if (ctmModeAsserted) return try {