2022-06-01 14:22:42 +00:00
|
|
|
--[[--
|
|
|
|
Plugin for automatic dimming of the frontlight after an idle period.
|
|
|
|
|
|
|
|
@module koplugin.autodim
|
|
|
|
--]]--
|
|
|
|
|
|
|
|
local Device = require("device")
|
|
|
|
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")
|
2022-11-17 04:53:35 +00:00
|
|
|
local datetime = require("datetime")
|
2022-06-01 14:22:42 +00:00
|
|
|
local time = require("ui/time")
|
|
|
|
local _ = require("gettext")
|
|
|
|
local C_ = _.pgettext
|
2022-07-28 04:22:25 +00:00
|
|
|
local Powerd = Device.powerd
|
2022-06-01 14:22:42 +00:00
|
|
|
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
|
|
|
|
|
Clarify our OOP semantics across the codebase (#9586)
Basically:
* Use `extend` for class definitions
* Use `new` for object instantiations
That includes some minor code cleanups along the way:
* Updated `Widget`'s docs to make the semantics clearer.
* Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283)
* Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass).
* Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events.
* Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier.
* Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references.
* ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak).
* Terminal: Make sure the shell is killed on plugin teardown.
* InputText: Fix Home/End/Del physical keys to behave sensibly.
* InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...).
* OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of.
* ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed!
* Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
|
|
|
local AutoDim = WidgetContainer:extend{
|
|
|
|
name = "autodim",
|
|
|
|
}
|
2022-06-01 14:22:42 +00:00
|
|
|
|
|
|
|
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
|
2022-07-14 18:08:46 +00:00
|
|
|
self.last_ramp_scheduling_time = nil -- holds start time of the next scheduled ramp task
|
2022-06-01 14:22:42 +00:00
|
|
|
self.trap_widget = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoDim:addToMainMenu(menu_items)
|
2022-07-28 04:22:25 +00:00
|
|
|
menu_items.autodim = self:getAutoDimMenu()
|
2022-06-01 14:22:42 +00:00
|
|
|
end
|
|
|
|
|
2022-07-28 04:22:25 +00:00
|
|
|
function AutoDim:getAutoDimMenu()
|
2022-06-01 14:22:42 +00:00
|
|
|
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"),
|
2023-02-12 22:22:11 +00:00
|
|
|
datetime.secondsToClockDuration("letters", self.autodim_starttime_m * 60, false, false, true))
|
2022-06-01 14:22:42 +00:00
|
|
|
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"),
|
2023-02-12 22:22:11 +00:00
|
|
|
datetime.secondsToClockDuration("letters", self.autodim_duration_s, false, false, true))
|
2022-06-01 14:22:42 +00:00
|
|
|
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
|
2022-07-28 04:22:25 +00:00
|
|
|
if self.onResume then
|
|
|
|
self:clearEventHandlers()
|
|
|
|
end
|
2022-06-01 14:22:42 +00:00
|
|
|
return
|
2022-07-28 04:22:25 +00:00
|
|
|
else
|
|
|
|
if not self.onResume then
|
|
|
|
self:setEventHandlers()
|
|
|
|
end
|
2022-06-01 14:22:42 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
seconds = seconds or self.autodim_starttime_m * 60
|
|
|
|
UIManager:scheduleIn(seconds, self.autodim_task, self)
|
|
|
|
end
|
|
|
|
|
2022-07-14 18:08:46 +00:00
|
|
|
function AutoDim:_unschedule_autodim_task()
|
|
|
|
UIManager:unschedule(self.autodim_task)
|
|
|
|
end
|
|
|
|
|
2022-06-01 14:22:42 +00:00
|
|
|
function AutoDim:restoreFrontlight()
|
2022-07-14 18:08:46 +00:00
|
|
|
if self.autodim_save_fl then
|
2022-07-28 04:22:25 +00:00
|
|
|
Powerd:setIntensity(self.autodim_save_fl)
|
2022-07-14 18:08:46 +00:00
|
|
|
self.autodim_save_fl = nil
|
|
|
|
end
|
2022-06-01 14:22:42 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function AutoDim:onInputEvent()
|
|
|
|
self.last_action_time = UIManager:getElapsedTimeSinceBoot()
|
2022-06-22 16:38:33 +00:00
|
|
|
-- Make sure the next scheduled autodim check is as much in the future
|
|
|
|
-- as possible, to reduce wakes from standby.
|
2022-07-14 18:08:46 +00:00
|
|
|
self:_unschedule_ramp_task()
|
2022-06-22 16:38:33 +00:00
|
|
|
self:_schedule_autodim_task()
|
2022-06-01 14:22:42 +00:00
|
|
|
end
|
|
|
|
|
2022-07-28 04:22:25 +00:00
|
|
|
function AutoDim:_onSuspend()
|
2022-07-14 18:08:46 +00:00
|
|
|
self:_unschedule_autodim_task()
|
2022-06-01 14:22:42 +00:00
|
|
|
if self.isCurrentlyDimming then
|
2022-07-14 18:08:46 +00:00
|
|
|
self:_unschedule_ramp_task()
|
|
|
|
self.isCurrentlyDimming = true -- message to self:onResume to go on with restoring
|
2022-06-01 14:22:42 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-07-28 04:22:25 +00:00
|
|
|
function AutoDim:_onResume()
|
2022-06-01 14:22:42 +00:00
|
|
|
self.last_action_time = UIManager:getElapsedTimeSinceBoot()
|
2022-07-14 18:08:46 +00:00
|
|
|
if self.trap_widget then
|
|
|
|
UIManager:scheduleIn(1, function()
|
2022-06-01 14:22:42 +00:00
|
|
|
UIManager:close(self.trap_widget)
|
|
|
|
self.trap_widget = nil
|
2022-07-14 18:08:46 +00:00
|
|
|
self:restoreFrontlight()
|
2022-06-01 14:22:42 +00:00
|
|
|
end)
|
|
|
|
self.isCurrentlyDimming = false
|
|
|
|
end
|
|
|
|
self:_schedule_autodim_task()
|
|
|
|
end
|
|
|
|
|
2022-07-28 04:22:25 +00:00
|
|
|
function AutoDim:setEventHandlers()
|
|
|
|
self.onResume = self._onResume
|
|
|
|
self.onSuspend = self._onSuspend
|
|
|
|
end
|
|
|
|
|
|
|
|
function AutoDim:clearEventHandlers()
|
|
|
|
self.onResume = nil
|
|
|
|
self.onSuspend = 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
|
|
|
|
|
2022-06-01 14:22:42 +00:00
|
|
|
function AutoDim:autodim_task()
|
|
|
|
if self.isCurrentlyDimming then return end
|
2022-07-28 04:22:25 +00:00
|
|
|
if Powerd:isFrontlightOff() then
|
|
|
|
self:_schedule_autodim_task()
|
|
|
|
end
|
2022-06-01 14:22:42 +00:00
|
|
|
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
|
2022-07-28 04:22:25 +00:00
|
|
|
self.autodim_save_fl = self.autodim_save_fl or Powerd:frontlightIntensity()
|
2022-10-10 20:21:27 +00:00
|
|
|
self.autodim_end_fl = math.floor(self.autodim_save_fl * self.autodim_fraction * (1/100) + 0.5)
|
2022-06-01 14:22:42 +00:00
|
|
|
-- 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
|
2022-06-17 18:48:37 +00:00
|
|
|
if fl_diff > 0 then
|
2022-10-25 11:42:21 +00:00
|
|
|
if self.trap_widget then
|
|
|
|
UIManager:close(self.trap_widget)
|
|
|
|
end
|
|
|
|
|
2022-06-17 18:48:37 +00:00
|
|
|
self.trap_widget = TrapWidget:new{
|
2023-02-01 23:29:23 +00:00
|
|
|
name = "AutoDim",
|
2022-06-17 18:48:37 +00:00
|
|
|
dismiss_callback = function()
|
|
|
|
self:restoreFrontlight()
|
|
|
|
self.trap_widget = nil
|
|
|
|
end
|
|
|
|
}
|
2022-06-01 14:22:42 +00:00
|
|
|
|
2022-06-17 18:48:37 +00:00
|
|
|
UIManager:show(self.trap_widget) -- suppress taps during dimming
|
|
|
|
|
|
|
|
-- 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
|
2022-07-28 04:22:25 +00:00
|
|
|
self.autodim_save_fl = nil
|
|
|
|
self:_schedule_autodim_task()
|
2022-06-17 18:48:37 +00:00
|
|
|
end
|
2022-06-01 14:22:42 +00:00
|
|
|
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`
|
2022-07-28 04:22:25 +00:00
|
|
|
local fl_level = Powerd:frontlightIntensity()
|
2022-06-01 14:22:42 +00:00
|
|
|
if fl_level > self.autodim_end_fl then
|
2022-07-28 04:22:25 +00:00
|
|
|
fl_level = fl_level - 1
|
|
|
|
Powerd:setIntensity(fl_level)
|
2022-06-01 14:22:42 +00:00
|
|
|
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
|
2022-11-20 04:33:44 +00:00
|
|
|
UIManager:broadcastEvent("UpdateFooter")
|
2022-06-01 14:22:42 +00:00
|
|
|
self.ramp_event_countdown = self.ramp_event_countdown_startvalue
|
2022-07-28 04:22:25 +00:00
|
|
|
self.last_ramp_scheduling_time = nil
|
2022-06-01 14:22:42 +00:00
|
|
|
end
|
2022-07-14 18:08:46 +00:00
|
|
|
self:_schedule_ramp_task(self.autodim_step_time_s) -- Reschedule only if ramp is not finished
|
2022-06-01 14:22:42 +00:00
|
|
|
-- `isCurrentlyDimming` stays true, to flag we have a dimmed FL.
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-07-14 18:08:46 +00:00
|
|
|
function AutoDim:_schedule_ramp_task(next_time_s)
|
|
|
|
self.last_ramp_scheduling_time = UIManager:getElapsedTimeSinceBoot()
|
|
|
|
UIManager:scheduleIn(next_time_s, self.ramp_task, self)
|
2022-06-01 14:22:42 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function AutoDim:_unschedule_ramp_task()
|
|
|
|
if self.isCurrentlyDimming then
|
|
|
|
UIManager:unschedule(self.ramp_task)
|
|
|
|
self.isCurrentlyDimming = false
|
2022-07-14 18:08:46 +00:00
|
|
|
self.last_ramp_scheduling_time = nil
|
2022-06-01 14:22:42 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return AutoDim
|