AutoWarmth: add option to toggle/set frontlight on day/night (#9337)

reviewable/pr9382/r1
zwim 2 years ago committed by GitHub
parent 460d42293a
commit 9d71333ade
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -15,6 +15,7 @@ local time = require("ui/time")
local util = require("util")
local _ = require("gettext")
local C_ = _.pgettext
local Powerd = Device.powerd
local T = FFIUtil.template
local DEFAULT_AUTODIM_STARTTIME_M = 5
@ -43,10 +44,10 @@ function AutoDim:init()
end
function AutoDim:addToMainMenu(menu_items)
menu_items.autodim = self:getAutodimMenu()
menu_items.autodim = self:getAutoDimMenu()
end
function AutoDim:getAutodimMenu()
function AutoDim:getAutoDimMenu()
return {
text = _("Automatic dimmer"),
checked_func = function() return self.autodim_starttime_m > 0 end,
@ -158,7 +159,14 @@ end
function AutoDim:_schedule_autodim_task(seconds)
UIManager:unschedule(self.autodim_task)
if self.autodim_starttime_m < 0 then
if self.onResume then
self:clearEventHandlers()
end
return
else
if not self.onResume then
self:setEventHandlers()
end
end
seconds = seconds or self.autodim_starttime_m * 60
@ -171,7 +179,7 @@ end
function AutoDim:restoreFrontlight()
if self.autodim_save_fl then
Device.powerd:setIntensity(self.autodim_save_fl)
Powerd:setIntensity(self.autodim_save_fl)
self:updateFooter(true)
self.autodim_save_fl = nil
end
@ -185,7 +193,7 @@ function AutoDim:onInputEvent()
self:_schedule_autodim_task()
end
function AutoDim:onSuspend()
function AutoDim:_onSuspend()
self:_unschedule_autodim_task()
if self.isCurrentlyDimming then
self:_unschedule_ramp_task()
@ -193,7 +201,7 @@ function AutoDim:onSuspend()
end
end
function AutoDim:onResume()
function AutoDim:_onResume()
self.last_action_time = UIManager:getElapsedTimeSinceBoot()
if self.trap_widget then
UIManager:scheduleIn(1, function()
@ -206,12 +214,12 @@ function AutoDim:onResume()
self:_schedule_autodim_task()
end
function AutoDim:onEnterStandby()
function AutoDim:_onEnterStandby()
self:_unschedule_autodim_task()
-- don't unschedule ramp task, as this is done in onLeaveStandby if necessary
end
function AutoDim:onLeaveStandby()
function AutoDim:_onLeaveStandby()
if self.isCurrentlyDimming then
if self.last_ramp_scheduling_time then
-- we are during the ramp down
@ -232,6 +240,36 @@ function AutoDim:onLeaveStandby()
end
end
function AutoDim:setEventHandlers()
self.onResume = self._onResume
self.onSuspend = self._onSuspend
self.onEnterStandby = self._onEnterStandby
self.onLeaveStandby = self._onLeaveStandby
end
function AutoDim:clearEventHandlers()
self.onResume = nil
self.onSuspend = nil
self.onEnterStandby = nil
self.onLeaveStandby = nil
end
function AutoDim:onFrontlightTurnedOff()
-- This might be happening through autowarmth during a ramp down.
-- Set original intensity, but don't turn fl on actually.
self.isCurrentlyDimming = false
self.last_ramp_scheduling_time = nil
UIManager:unschedule(self.ramp_task)
Powerd.fl_intensity = self.autodim_save_fl or Powerd.fl_intensity
self.autodim_save_fl = nil
if self.trap_widget then
UIManager:close(self.trap_widget) -- don't swallow input events from now
self.trap_widget = nil
end
self:_schedule_autodim_task() -- reschedule
end
function AutoDim:updateFooter(clear)
-- update footer only if it is not covered by another widget
if self.top_widget_before_dim == "ReaderUI" or
@ -247,12 +285,14 @@ end
function AutoDim:autodim_task()
if self.isCurrentlyDimming then return end
if Powerd:isFrontlightOff() then
self:_schedule_autodim_task()
end
local now = UIManager:getElapsedTimeSinceBoot()
local idle_duration = now - self.last_action_time
local check_delay = time.s(self.autodim_starttime_m * 60) - idle_duration
if check_delay <= 0 then
self.autodim_save_fl = self.autodim_save_fl or Device.powerd:frontlightIntensity()
self.autodim_save_fl = self.autodim_save_fl or Powerd:frontlightIntensity()
self.autodim_end_fl = math.floor(self.autodim_save_fl * self.autodim_fraction / 100 + 0.5)
-- Clamp `self.autodim_end_fl` to 1 if `self.autodim_fraction` ~= 0
if self.autodim_fraction ~= 0 and self.autodim_end_fl == 0 then
@ -281,7 +321,8 @@ function AutoDim:autodim_task()
self:ramp_task() -- which schedules itself
-- Don't schedule `autodim_task` here, as this is done in `trap_widget.dismiss_callback` or in `onResume`
else
self:_schedule_autodim_task(time.s(self.autodim_starttime_m * 60))
self.autodim_save_fl = nil
self:_schedule_autodim_task()
end
else
self:_schedule_autodim_task(time.to_s(check_delay))
@ -290,23 +331,20 @@ end
function AutoDim:ramp_task()
self.isCurrentlyDimming = true -- this will disable rescheduling of the `autodim_task`
local fl_level = Device.powerd:frontlightIntensity()
local fl_level = Powerd:frontlightIntensity()
if fl_level > self.autodim_end_fl then
Device.powerd:setIntensity(fl_level - 1)
fl_level = fl_level - 1
Powerd:setIntensity(fl_level)
self.ramp_event_countdown = self.ramp_event_countdown - 1
if self.ramp_event_countdown <= 0 then
-- Update footer on every self.ramp_event_countdown call
self:updateFooter()
self.ramp_event_countdown = self.ramp_event_countdown_startvalue
self.last_ramp_scheduling_time = nil
end
self:_schedule_ramp_task(self.autodim_step_time_s) -- Reschedule only if ramp is not finished
-- `isCurrentlyDimming` stays true, to flag we have a dimmed FL.
end
if fl_level == self.autodim_end_fl and self.ramp_event_countdown_startvalue > 0 then
-- Update footer at the end of the ramp.
self:updateFooter()
self.last_ramp_scheduling_time = nil
end
end
function AutoDim:_schedule_ramp_task(next_time_s)

@ -24,6 +24,7 @@ local WidgetContainer = require("ui/widget/container/widgetcontainer")
local logger = require("logger")
local _ = require("gettext")
local C_ = _.pgettext
local Powerd = Device.powerd
local T = FFIUtil.template
local Screen = require("device").screen
local util = require("util")
@ -35,7 +36,7 @@ local activate_closer_midnight = 4
local midnight_index = 11
local device_max_warmth = Device:hasNaturalLight() and Device.powerd.fl_warmth_max or 100
local device_max_warmth = Device:hasNaturalLight() and Powerd.fl_warmth_max or 100
local device_warmth_fit_scale = device_max_warmth / 100
local function frac(x)
@ -44,8 +45,9 @@ end
local AutoWarmth = WidgetContainer:new{
name = "autowarmth",
sched_times = {},
sched_times_s = {},
sched_warmths = {},
fl_turned_off = nil -- true/false if autowarmth has toggled the frontlight
}
-- get timezone offset in hours (including dst)
@ -70,6 +72,7 @@ function AutoWarmth:init()
or {0.0, 5.5, 6.0, 6.5, 7.0, 13.0, 21.5, 22.0, 22.5, 23.0, 24.0}
self.warmth = G_reader_settings:readSetting("autowarmth_warmth")
or { 90, 90, 80, 60, 20, 20, 20, 60, 80, 90, 90}
self.fl_off_during_day = G_reader_settings:saveSetting("autowarmth_fl_off_during_day")
-- schedule recalculation shortly afer midnight
self:scheduleMidnightUpdate()
@ -119,53 +122,49 @@ function AutoWarmth:onAutoWarmthMode()
end
function AutoWarmth:leavePowerSavingState(from_resume)
if self.activate == 0 then return end
logger.dbg("AutoWarmth: onResume/onLeaveStandby")
local resume_date = os.date("*t")
-- check if resume and suspend are done on the same day
if resume_date.day == SunTime.date.day and resume_date.month == SunTime.date.month
and resume_date.year == SunTime.date.year then
local now = SunTime:getTimeInSec(resume_date)
self:scheduleNextWarmthChange(now, self.sched_warmth_index, from_resume)
-- reschedule 5sec after midnight
UIManager:scheduleIn(24*3600 + 5 - now, self.scheduleMidnightUpdate, self)
local now_s = SunTime:getTimeInSec(resume_date)
self:scheduleNextWarmthChange(now_s, self.sched_warmth_index, from_resume)
-- Reschedule 1sec after midnight
UIManager:scheduleIn(24*3600 + 1 - now_s, self.scheduleMidnightUpdate, self)
else
self:scheduleMidnightUpdate(from_resume) -- resume is on the other day, do all calcs again
end
end
function AutoWarmth:onResume()
function AutoWarmth:_onResume()
self:leavePowerSavingState(true)
end
function AutoWarmth:onLeaveStandby()
function AutoWarmth:_onLeaveStandby()
self:leavePowerSavingState(false)
end
-- wrapper for unscheduling, so that only our setWarmth gets unscheduled
function AutoWarmth.setWarmth(val, force)
if val then
if val > 100 then
DeviceListener:onSetNightMode(true)
else
DeviceListener:onSetNightMode(false)
end
if Device:hasNaturalLight() then
val = math.min(val, 100)
Device.powerd:setWarmth(val, force)
end
end
end
function AutoWarmth:onSuspend()
if self.activate == 0 then return end
function AutoWarmth:_onSuspend()
UIManager:unschedule(self.scheduleMidnightUpdate)
UIManager:unschedule(self.setWarmth)
end
AutoWarmth.onEnterStandby = AutoWarmth.onSuspend
AutoWarmth._onEnterStandby = AutoWarmth._onSuspend
function AutoWarmth:setEventHandlers()
self.onResume = self._onResume
self.onSuspend = self._onSuspend
self.onEnterStandby = self._onEnterStandby
self.onLeaveStandby = self._onLeaveStandby
end
function AutoWarmth:clearEventHandlers()
self.onResume = nil
self.onSuspend = nil
self.onEnterStandby = nil
self.onLeaveStandby = nil
end
-- from_resume ... true if called from onResume
function AutoWarmth:scheduleMidnightUpdate(from_resume)
@ -177,32 +176,33 @@ function AutoWarmth:scheduleMidnightUpdate(from_resume)
SunTime:setPosition(self.location, self.latitude, self.longitude, self.timezone, self.altitude, true)
SunTime:setAdvanced()
SunTime:setDate() -- today
SunTime:calculateTimes()
SunTime:calculateTimes() -- calculates times in hours
self.sched_times = {}
self.sched_times_s = {}
self.sched_warmths = {}
self.fl_turned_off = nil
local function prepareSchedule(times, index1, index2)
local time1 = times[index1]
if not time1 then return end
local function prepareSchedule(times_h, index1, index2)
local time1_h = times_h[index1]
if not time1_h then return end
local time = SunTime:getTimeInSec(time1)
table.insert(self.sched_times, time)
table.insert(self.sched_warmths, self.warmth[index1])
local time1_s = SunTime:getTimeInSec(time1_h)
self.sched_times_s[#self.sched_times_s + 1] = time1_s
self.sched_warmths[#self.sched_warmths + 1] = self.warmth[index1]
local time2 = times[index2]
if not time2 then return end -- to near to the pole
local time2_h = times_h[index2]
if not time2_h then return end -- to near to the pole
local warmth_diff = math.min(self.warmth[index2], 100) - math.min(self.warmth[index1], 100)
if warmth_diff ~= 0 then
local time_diff = SunTime:getTimeInSec(time2) - time
local delta_t = time_diff / math.abs(warmth_diff) -- can be inf, no problem
local time_diff_s = SunTime:getTimeInSec(time2_h) - time1_s
if warmth_diff ~= 0 and time_diff_s > 0 then
local delta_t = time_diff_s / math.abs(warmth_diff) -- cannot be inf, no problem
local delta_w = warmth_diff > 0 and 1 or -1
for i = 1, math.abs(warmth_diff)-1 do
for i = 1, math.abs(warmth_diff) - 1 do
local next_warmth = math.min(self.warmth[index1], 100) + delta_w * i
-- only apply warmth for steps the hardware has (e.g. Tolino has 0-10 hw steps
-- which map to warmth 0, 10, 20, 30 ... 100)
if frac(next_warmth * device_warmth_fit_scale) == 0 then
table.insert(self.sched_times, time + delta_t * i)
table.insert(self.sched_times_s, time1_s + delta_t * i)
table.insert(self.sched_warmths, math.floor(math.min(self.warmth[index1], 100) + delta_w * i))
end
end
@ -210,75 +210,80 @@ function AutoWarmth:scheduleMidnightUpdate(from_resume)
end
if self.activate == activate_sun then
self.current_times = {unpack(SunTime.times, 1, midnight_index)}
self.current_times_h = {unpack(SunTime.times, 1, midnight_index)}
elseif self.activate == activate_schedule then
self.current_times = {unpack(self.scheduler_times, 1, midnight_index)}
self.current_times_h = {unpack(self.scheduler_times, 1, midnight_index)}
else
self.current_times = {unpack(SunTime.times, 1, midnight_index)}
self.current_times_h = {unpack(SunTime.times, 1, midnight_index)}
if self.activate == activate_closer_noon then
for i = 1, midnight_index do
if not self.current_times[i] then
self.current_times[i] = self.scheduler_times[i]
if not self.current_times_h[i] then
self.current_times_h[i] = self.scheduler_times[i]
elseif self.scheduler_times[i] and
math.abs(self.current_times[i]%24 - 12) > math.abs(self.scheduler_times[i]%24 - 12) then
self.current_times[i] = self.scheduler_times[i]
math.abs(self.current_times_h[i]%24 - 12) > math.abs(self.scheduler_times[i]%24 - 12) then
self.current_times_h[i] = self.scheduler_times[i]
end
end
else -- activate_closer_midnight
for i = 1, midnight_index do
if not self.current_times[i] then
self.current_times[i] = self.scheduler_times[i]
if not self.current_times_h[i] then
self.current_times_h[i] = self.scheduler_times[i]
elseif self.scheduler_times[i] and
math.abs(self.current_times[i]%24 - 12) < math.abs(self.scheduler_times[i]%24 - 12) then
self.current_times[i] = self.scheduler_times[i]
math.abs(self.current_times_h[i]%24 - 12) < math.abs(self.scheduler_times[i]%24 - 12) then
self.current_times_h[i] = self.scheduler_times[i]
end
end
end
end
if self.easy_mode then
self.current_times[1] = nil
self.current_times[2] = nil
self.current_times[3] = nil
self.current_times[6] = nil
self.current_times[9] = nil
self.current_times[10] = nil
self.current_times[11] = nil
self.current_times_h[1] = nil
self.current_times_h[2] = nil
self.current_times_h[3] = nil
self.current_times_h[6] = nil
self.current_times_h[9] = nil
self.current_times_h[10] = nil
self.current_times_h[11] = nil
end
-- here are dragons
local i = 1
-- find first valid entry
while not self.current_times[i] and i <= midnight_index do
while not self.current_times_h[i] and i <= midnight_index do
i = i + 1
end
local next
while i <= midnight_index do
next = i + 1
-- find next valid entry
while not self.current_times[next] and next <= midnight_index do
while not self.current_times_h[next] and next <= midnight_index do
next = next + 1
end
prepareSchedule(self.current_times, i, next)
prepareSchedule(self.current_times_h, i, next)
i = next
end
local now = SunTime:getTimeInSec()
local now_s = SunTime:getTimeInSec()
-- Reschedule 1sec after midnight
UIManager:scheduleIn(24*3600 + 1 - now_s, self.scheduleMidnightUpdate, self)
-- reschedule 5sec after midnight
UIManager:scheduleIn(24*3600 + 5 - now, self.scheduleMidnightUpdate, self)
-- and schedule the first warmth change
self:scheduleNextWarmthChange(now, 1, from_resume)
-- set event handlers
if self.activate ~= 0 then
-- Schedule the first warmth change
self:scheduleNextWarmthChange(now_s, 1, from_resume)
self:setEventHandlers()
else
self:clearEventHandlers()
end
end
-- schedules the next warmth change
-- search_pos ... start searching from that index
-- from_resume ... true if first call after resume
function AutoWarmth:scheduleNextWarmthChange(time, search_pos, from_resume)
logger.dbg("AutoWarmth: scheduleWarmthChange")
if UIManager:unschedule(AutoWarmth.setWarmth) then
logger.err("AutoWarmth: abnormal unschedule, time changed?")
end
function AutoWarmth:scheduleNextWarmthChange(time_s, search_pos, from_resume)
logger.dbg("AutoWarmth: scheduleNextWarmthChange")
UIManager:unschedule(AutoWarmth.setWarmth)
if self.activate == 0 or #self.sched_warmths == 0 or search_pos > #self.sched_warmths then
return
@ -299,10 +304,13 @@ function AutoWarmth:scheduleNextWarmthChange(time, search_pos, from_resume)
local actual_warmth = self.sched_warmths[self.sched_warmth_index or #self.sched_warmths]
local next_warmth = actual_warmth
for i = self.sched_warmth_index, #self.sched_warmths do
if self.sched_times[i] <= time then
if self.sched_times_s[i] <= time_s then
actual_warmth = self.sched_warmths[i] or actual_warmth
if self.sched_times[i] <= time + delay_s then
next_warmth = self.sched_warmths[i] or next_warmth
local j = i
while self.sched_times_s[j] <= time_s + delay_s do
-- Most times only one iteration through this loop
next_warmth = self.sched_warmths[j] or next_warmth
j = j + 1
end
else
self.sched_warmth_index = i
@ -311,10 +319,12 @@ function AutoWarmth:scheduleNextWarmthChange(time, search_pos, from_resume)
end
-- update current warmth immediately
self:setWarmth(actual_warmth, false) -- no setWarmth rescheduling, don't force warmth
local next_sched_time = self.sched_times[self.sched_warmth_index] - time
if self.sched_warmth_index <= #self.sched_warmths and next_sched_time > 0 then
-- This setWarmth will call scheduleNextWarmthChange which will schedule setWarmth again.
UIManager:scheduleIn(next_sched_time, self.setWarmth, self, self.sched_warmths[self.sched_warmth_index], true)
if self.sched_warmth_index <= #self.sched_warmths then
local next_sched_time_s = self.sched_times_s[self.sched_warmth_index] - time_s
if next_sched_time_s > 0 then
-- This setWarmth will call scheduleNextWarmthChange which will schedule setWarmth again.
UIManager:scheduleIn(next_sched_time_s, self.setWarmth, self, self.sched_warmths[self.sched_warmth_index], true)
end
end
if from_resume then
@ -324,6 +334,29 @@ function AutoWarmth:scheduleNextWarmthChange(time, search_pos, from_resume)
-- see https://github.com/koreader/koreader/issues/8363
UIManager:scheduleIn(delay_s, self.setWarmth, self, next_warmth, true) -- no setWarmth rescheduling, force warmth
end
-- Check if AutoWarmth shall toggle frontlight daytime and twilight
if self.fl_off_during_day then
if time_s >= self.current_times_h[5]*3600 and time_s < self.current_times_h[7]*3600 then
-- during daytime (depending on choosens activation: SunTime, fixed Schedule, closer...
-- turn on frontlight off once, user can override this selection by a gesture
if Powerd:isFrontlightOn() then
if self.fl_turned_off ~= true then -- can be false or nil
Powerd:turnOffFrontlight()
UIManager:broadcastEvent(Event:new("FrontlightTurnedOff"))
end
end
self.fl_turned_off = true
else
-- outside of selected daytime, turn on frontlight once, user can override this selection by a gesture
if Powerd:isFrontlightOff() then
if self.fl_turned_off ~= false then -- can be true or nil
Powerd:turnOnFrontlight()
end
end
self.fl_turned_off = false
end
end
end
-- Set warmth and schedule the next warmth change
@ -333,12 +366,12 @@ function AutoWarmth:setWarmth(val, schedule_next, force_warmth)
if Device:hasNaturalLight() then
val = math.min(val, 100) -- "mask" night mode
Device.powerd:setWarmth(val, force_warmth)
Powerd:setWarmth(val, force_warmth)
end
end
if schedule_next then
local now = SunTime:getTimeInSec()
self:scheduleNextWarmthChange(now, self.sched_warmth_index, false)
local now_s = SunTime:getTimeInSec()
self:scheduleNextWarmthChange(now_s, self.sched_warmth_index, false)
end
end
@ -443,6 +476,28 @@ function AutoWarmth:getSubMenuItems()
end,
text = Device:hasNaturalLight() and _("Warmth and night mode settings") or _("Night mode settings"),
sub_item_table = self:getWarmthMenu(),
},
{
checked_func = function()
return self.fl_off_during_day
end,
text = _("Frontlight off during the day"),
callback = function(touchmenu_instance)
self.fl_off_during_day = not self.fl_off_during_day
G_reader_settings:saveSetting("autowarmth_fl_off_during_day", self.fl_off_during_day)
self:scheduleMidnightUpdate()
-- Turn the fl on during day if necessary;
-- no need to turn it off as this is done trough `scheduleMidnightUpdate()`
if not self.fl_off_during_day and Powerd:isFrontlightOff() then
Powerd:turnOnFrontlight()
end
if touchmenu_instance then
self:updateItems(touchmenu_instance)
end
end,
keep_menu_open = true,
separator = true,
},
self:getTimesMenu(_("Currently active parameters")),
@ -651,7 +706,7 @@ function AutoWarmth:getScheduleMenu()
UIManager:show(ConfirmBox:new{
text = _("This time is before the previous time.\nAdjust the previous time?"),
ok_callback = function()
for i = num-1, 1, -1 do
for i = num - 1, 1, -1 do
if self.scheduler_times[i] then
if new_time < self.scheduler_times[i] then
self.scheduler_times[i] = new_time
@ -810,14 +865,14 @@ end
-- title
-- location: add a location string
-- activator: nil .. current_times,
-- activator: nil .. current_times_h,
-- activate_sun .. sun times
-- activate_schedule .. scheduler times
-- request_easy: true if easy_mode should be used
function AutoWarmth:showTimesInfo(title, location, activator, request_easy)
local times
if not activator then
times = self.current_times
times = self.current_times_h
elseif activator == activate_sun then
times = SunTime.times
elseif activator == activate_schedule then
@ -852,7 +907,7 @@ function AutoWarmth:showTimesInfo(title, location, activator, request_easy)
local retval = string.rep(" ", indent) .. text .. string.rep(" ", tab_width - str_len)
.. self:hoursToClock(t[num])
if easy then
if t[num] and self.current_times[num] and self.current_times[num] ~= t[num] then
if t[num] and self.current_times_h[num] and self.current_times_h[num] ~= t[num] then
return text .. "\n"
else
return ""
@ -862,7 +917,7 @@ function AutoWarmth:showTimesInfo(title, location, activator, request_easy)
if not t[num] then -- entry deactivated
return retval .. "\n"
elseif Device:hasNaturalLight() then
if self.current_times[num] == t[num] then
if self.current_times_h[num] == t[num] then
if self.warmth[num] <= 100 then
return retval .. " (💡" .. self.warmth[num] .."%)\n"
else
@ -872,7 +927,7 @@ function AutoWarmth:showTimesInfo(title, location, activator, request_easy)
return retval .. "\n"
end
else
if self.current_times[num] == t[num] then
if self.current_times_h[num] == t[num] then
if self.warmth[num] <= 100 then
return retval .. " (☼)\n"
else
@ -922,7 +977,7 @@ end
-- title
-- location: add a location string
-- activator: nil .. current_times,
-- activator: nil .. current_times_h,
-- activate_sun .. sun times
-- activate_schedule .. scheduler times
function AutoWarmth:getTimesMenu(title, location, activator)

Loading…
Cancel
Save