diff --git a/plugins/autodim.koplugin/main.lua b/plugins/autodim.koplugin/main.lua index dfd2dc84a..b831e187f 100644 --- a/plugins/autodim.koplugin/main.lua +++ b/plugins/autodim.koplugin/main.lua @@ -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) diff --git a/plugins/autowarmth.koplugin/main.lua b/plugins/autowarmth.koplugin/main.lua index b6188cf70..0fc672d43 100644 --- a/plugins/autowarmth.koplugin/main.lua +++ b/plugins/autowarmth.koplugin/main.lua @@ -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)