diff --git a/frontend/ui/elements/filemanager_menu_order.lua b/frontend/ui/elements/filemanager_menu_order.lua index 805478c5a..807cae8fd 100644 --- a/frontend/ui/elements/filemanager_menu_order.lua +++ b/frontend/ui/elements/filemanager_menu_order.lua @@ -90,6 +90,7 @@ local order = { "----------------------------", "screen_dpi", "screen_eink_opt", + "autodim", "autowarmth", "color_rendering", "----------------------------", diff --git a/frontend/ui/elements/reader_menu_order.lua b/frontend/ui/elements/reader_menu_order.lua index 3f7323a1a..3194fe76e 100644 --- a/frontend/ui/elements/reader_menu_order.lua +++ b/frontend/ui/elements/reader_menu_order.lua @@ -135,6 +135,7 @@ local order = { "----------------------------", "screen_dpi", "screen_eink_opt", + "autodim", "autowarmth", "color_rendering", "----------------------------", diff --git a/plugins/autodim.koplugin/_meta.lua b/plugins/autodim.koplugin/_meta.lua new file mode 100644 index 000000000..faf8689c9 --- /dev/null +++ b/plugins/autodim.koplugin/_meta.lua @@ -0,0 +1,6 @@ +local _ = require("gettext") +return { + name = "autodim", + fullname = _("Automatic dimmer"), + description = _("This plugin allows dimming the frontlight after a period of inactivity."), +} diff --git a/plugins/autodim.koplugin/main.lua b/plugins/autodim.koplugin/main.lua new file mode 100644 index 000000000..067718a7a --- /dev/null +++ b/plugins/autodim.koplugin/main.lua @@ -0,0 +1,274 @@ +--[[-- +Plugin for automatic dimming of the frontlight after an idle period. + +@module koplugin.autodim +--]]-- + +local Device = require("device") +local Event = require("ui/event") +local FFIUtil = require("ffi/util") +local SpinWidget = require("ui/widget/spinwidget") +local UIManager = require("ui/uimanager") +local WidgetContainer = require("ui/widget/container/widgetcontainer") +local TrapWidget = require("ui/widget/trapwidget") +local time = require("ui/time") +local util = require("util") +local _ = require("gettext") +local C_ = _.pgettext +local T = FFIUtil.template + +local DEFAULT_AUTODIM_STARTTIME_M = 5 +local DEFAULT_AUTODIM_DURATION_S = 5 +local DEFAULT_AUTODIM_FRACTION = 20 +local AUTODIM_EVENT_FREQUENCY = 2 -- in Hz; Frequenzy for FrontlightChangedEvent on E-Ink devices + +local AutoDim = WidgetContainer:new{ name = "autodim" } + +function AutoDim:init() + self.autodim_starttime_m = G_reader_settings:readSetting("autodim_starttime_minutes", -1) + self.autodim_duration_s = G_reader_settings:readSetting("autodim_duration_seconds", DEFAULT_AUTODIM_DURATION_S) + self.autodim_fraction = G_reader_settings:readSetting("autodim_fraction", DEFAULT_AUTODIM_FRACTION) + + self.last_action_time = UIManager:getElapsedTimeSinceBoot() + + self.ui.menu:registerToMainMenu(self) + UIManager.event_hook:registerWidget("InputEvent", self) + + self:_schedule_autodim_task() + self.isCurrentlyDimming = false -- true during or after the dimming ramp + self.trap_widget = nil +end + +function AutoDim:addToMainMenu(menu_items) + menu_items.autodim = self:getAutodimMenu() +end + +function AutoDim:getAutodimMenu() + return { + text = _("Automatic dimmer"), + checked_func = function() return self.autodim_starttime_m > 0 end, + sub_item_table = { + { + text_func = function() + return self.autodim_starttime_m <= 0 and _("Idle time for dimmer") or + T(_("Idle time for dimmer: %1"), + util.secondsToClockDuration("modern", self.autodim_starttime_m * 60, false, true, false, true)) + end, + checked_func = function() return self.autodim_starttime_m > 0 end, + callback = function(touchmenu_instance) + local idle_dialog = SpinWidget:new{ + title_text = _("Automatic dimmer idle time"), + info_text = _("Start the dimmer after the designated period of inactivity."), + value = self.autodim_starttime_m >=0 and self.autodim_starttime_m or 0.5, + default_value = DEFAULT_AUTODIM_STARTTIME_M, + value_min = 0.5, + value_max = 60, + value_step = 0.5, + value_hold_step = 5, + unit = C_("Time", "min"), + precision = "%0.1f", + ok_always_enabled = true, + callback = function(spin) + if not spin then return end + self.autodim_starttime_m = spin.value + G_reader_settings:saveSetting("autodim_starttime_minutes", spin.value) + self:_schedule_autodim_task() + if touchmenu_instance then touchmenu_instance:updateItems() end + end, + extra_text = _("Disable"), + extra_callback = function() + self.autodim_starttime_m = -1 + G_reader_settings:saveSetting("autodim_starttime_minutes", -1) + self:_schedule_autodim_task() + if touchmenu_instance then touchmenu_instance:updateItems() end + end, + } + UIManager:show(idle_dialog) + if touchmenu_instance then touchmenu_instance:updateItems() end + end, + keep_menu_open = true, + }, + { + text_func = function() + return T(_("Dimmer duration: %1"), + util.secondsToClockDuration("modern", self.autodim_duration_s, false, true, false, true)) + end, + enabled_func = function() return self.autodim_starttime_m > 0 end, + callback = function(touchmenu_instance) + local dimmer_dialog = SpinWidget:new{ + title_text = _("Automatic dimmer duration"), + info_text = _("Delay to reach the lowest brightness."), + value = self.autodim_duration_s, + default_value = DEFAULT_AUTODIM_DURATION_S, + value_min = 0, + value_max = 300, + value_step = 1, + value_hold_step = 10, + precision = "%1d", + unit = C_("Time", "s"), + callback = function(spin) + if not spin then return end + self.autodim_duration_s = spin.value + G_reader_settings:saveSetting("autodim_duration_seconds", spin.value) + self:_schedule_autodim_task() + if touchmenu_instance then touchmenu_instance:updateItems() end + end, + } + UIManager:show(dimmer_dialog) + if touchmenu_instance then touchmenu_instance:updateItems() end + end, + keep_menu_open = true, + }, + { + text_func = function() + return T(_("Dim to %1 % of the regular brightness"), self.autodim_fraction) + end, + enabled_func = function() return self.autodim_starttime_m > 0 end, + callback = function(touchmenu_instance) + local percentage_dialog = SpinWidget:new{ + title_text = _("Dim to percentage"), + info_text = _("The lowest brightness as a percentage of the regular brightness."), + value = self.autodim_fraction, + value_default = DEFAULT_AUTODIM_FRACTION, + value_min = 0, + value_max = 100, + value_hold_step = 10, + unit = "%", + callback = function(spin) + self.autodim_fraction = spin.value + G_reader_settings:saveSetting("autodim_fraction", spin.value) + self:_schedule_autodim_task() + if touchmenu_instance then touchmenu_instance:updateItems() end + end, + } + UIManager:show(percentage_dialog) + if touchmenu_instance then touchmenu_instance:updateItems() end + end, + keep_menu_open = true, + }, + } + } +end + +-- Schedules the first idle task, the consecutive ones are scheduled by the `autodim_task` itself. +-- `seconds` the initial scheduling delay of the first task +function AutoDim:_schedule_autodim_task(seconds) + UIManager:unschedule(self.autodim_task) + if self.autodim_starttime_m < 0 then + return + end + + seconds = seconds or self.autodim_starttime_m * 60 + UIManager:scheduleIn(seconds, self.autodim_task, self) +end + +function AutoDim:restoreFrontlight() + Device.powerd:setIntensity(self.autodim_save_fl) + UIManager:broadcastEvent(Event:new("UpdateFooter", true, true)) + self:_unschedule_ramp_task() + self:_schedule_autodim_task() +end + +function AutoDim:onInputEvent() + self.last_action_time = UIManager:getElapsedTimeSinceBoot() +end + +function AutoDim:_unschedule_autodim_task() + if self.isCurrentlyDimming then + UIManager:unschedule(self.ramp_task) + self.isCurrentlyDimming = false + end +end + +function AutoDim:onResume() + self.last_action_time = UIManager:getElapsedTimeSinceBoot() + if self.isCurrentlyDimming then + if self.trap_widget then + UIManager:close(self.trap_widget) + self.trap_widget = nil + end + UIManager:scheduleIn(1, function() + Device.powerd:setIntensity(self.autodim_save_fl) + UIManager:broadcastEvent(Event:new("UpdateFooter", true, true)) + end) + self.isCurrentlyDimming = false + end + self:_schedule_autodim_task() +end + +function AutoDim:onSuspend() + if self.isCurrentlyDimming then + self:_unschedule_autodim_task() + self:_unschedule_ramp_task() + self.isCurrentlyDimming = true -- message to self:onResume to go on with restoring + end +end + +function AutoDim:autodim_task() + if self.isCurrentlyDimming then return 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.trap_widget = TrapWidget:new{ + dismiss_callback = function() + self:restoreFrontlight() + self.trap_widget = nil + end + } + + UIManager:show(self.trap_widget) -- suppress taps during dimming + + self.autodim_save_fl = Device.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 + self.autodim_end_fl = 1 + end + local fl_diff = self.autodim_save_fl - self.autodim_end_fl + -- calculate time until the next decrease step + self.autodim_step_time_s = math.max(self.autodim_duration_s / fl_diff, 0.001) + self.ramp_event_countdown_startvalue = Device:hasEinkScreen() and + math.floor((1/AUTODIM_EVENT_FREQUENCY) / self.autodim_step_time_s + 0.5) or 0 + self.ramp_event_countdown = self.ramp_event_countdown_startvalue + + 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.to_s(check_delay)) + end +end + +function AutoDim:ramp_task() + self.isCurrentlyDimming = true -- this will disable rescheduling of the `autodim_task` + local fl_level = Device.powerd:frontlightIntensity() + if fl_level > self.autodim_end_fl then + Device.powerd:setIntensity(fl_level - 1) + 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 + UIManager:broadcastEvent(Event:new("UpdateFooter", true, true)) + self.ramp_event_countdown = self.ramp_event_countdown_startvalue + end + self:_schedule_ramp_task() -- Reschedule only if not ready + -- `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. + UIManager:broadcastEvent(Event:new("UpdateFooter", true, true)) + end +end + +function AutoDim:_schedule_ramp_task() + UIManager:scheduleIn(self.autodim_step_time_s, self.ramp_task, self) +end + +function AutoDim:_unschedule_ramp_task() + if self.isCurrentlyDimming then + UIManager:unschedule(self.ramp_task) + self.isCurrentlyDimming = false + end +end + +return AutoDim diff --git a/plugins/autowarmth.koplugin/_meta.lua b/plugins/autowarmth.koplugin/_meta.lua index 166adf5e5..78b82fcda 100644 --- a/plugins/autowarmth.koplugin/_meta.lua +++ b/plugins/autowarmth.koplugin/_meta.lua @@ -2,5 +2,5 @@ local _ = require("gettext") return { name = "autowarmth", fullname = require("device"):hasNaturalLight() and _("Auto warmth and night mode") or _("Auto night mode"), - description = _([[This plugin allows set the frontlight warmth automagically.]]), + description = _([[This plugin allows to set the frontlight warmth automagically.]]), } diff --git a/plugins/autowarmth.koplugin/main.lua b/plugins/autowarmth.koplugin/main.lua index b66f4ede6..f13401702 100644 --- a/plugins/autowarmth.koplugin/main.lua +++ b/plugins/autowarmth.koplugin/main.lua @@ -65,9 +65,9 @@ function AutoWarmth:init() self.longitude = G_reader_settings:readSetting("autowarmth_longitude") or -20.30 self.altitude = G_reader_settings:readSetting("autowarmth_altitude") or 200 self.timezone = G_reader_settings:readSetting("autowarmth_timezone") or 0 - self.scheduler_times = G_reader_settings:readSetting("autowarmth_scheduler_times") 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") + self.scheduler_times = G_reader_settings:readSetting("autowarmth_scheduler_times") + 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} -- schedule recalculation shortly afer midnight @@ -178,7 +178,7 @@ function AutoWarmth:scheduleMidnightUpdate() 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 delta_w = warmth_diff > 0 and 1 or -1 + local delta_w = warmth_diff > 0 and 1 or -1 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 @@ -354,7 +354,7 @@ function AutoWarmth:getSubMenuItems() callback = function() UIManager:show(InfoMessage:new{ text = about_text, - width = math.floor(Screen:getWidth() * 0.9), + width = math.floor(Screen:getWidth() * 0.9), }) end, keep_menu_open = true, @@ -475,8 +475,9 @@ function AutoWarmth:getLocationMenu() if touchmenu_instance then touchmenu_instance:updateItems() end end, }, - }} - } + } + }, + } UIManager:show(location_name_dialog) location_name_dialog:onShowKeyboard() end, @@ -560,8 +561,7 @@ function AutoWarmth:getScheduleMenu() self.scheduler_times[num] = new_time if num == 1 then if new_time then - self.scheduler_times[midnight_index] - = new_time + 24 -- next day + self.scheduler_times[midnight_index] = new_time + 24 -- next day else self.scheduler_times[midnight_index] = nil end @@ -609,7 +609,7 @@ function AutoWarmth:getScheduleMenu() end if num > 1 and new_time < get_valid_time(num, -1) then UIManager:show(ConfirmBox:new{ - text = _("This time is before the previous time.\nAdjust the previous time?"), + text = _("This time is before the previous time.\nAdjust the previous time?"), ok_callback = function() for i = num-1, 1, -1 do if self.scheduler_times[i] then @@ -625,9 +625,9 @@ function AutoWarmth:getScheduleMenu() }) elseif num < 10 and new_time > get_valid_time(num, 1) then UIManager:show(ConfirmBox:new{ - text = _("This time is after the subsequent time.\nAdjust the subsequent time?"), + text = _("This time is after the subsequent time.\nAdjust the subsequent time?"), ok_callback = function() - for i = num + 1, midnight_index - 1 do + for i = num + 1, midnight_index - 1 do if self.scheduler_times[i] then if new_time > self.scheduler_times[i] then self.scheduler_times[i] = new_time @@ -744,7 +744,6 @@ function AutoWarmth:getWarmthMenu() text = _("Cancel"), } }}, - }) end end, @@ -859,7 +858,7 @@ function AutoWarmth:showTimesInfo(title, location, activator, request_easy) UIManager:show(InfoMessage:new{ face = face, width = math.floor(Screen:getWidth() * (self.easy_mode and 0.75 or 0.90)), - text = title .. location_string .. ":\n\n" .. + text = title .. location_string .. ":\n\n" .. info_line(0, _("Solar midnight:"), times, 1, face, request_easy) .. add_line(_("Dawn"), request_easy) .. info_line(4, _("Astronomic:"), times, 2, face, request_easy) ..