diff --git a/frontend/device/android/powerd.lua b/frontend/device/android/powerd.lua index 57e50f222..faabbed73 100644 --- a/frontend/device/android/powerd.lua +++ b/frontend/device/android/powerd.lua @@ -4,10 +4,6 @@ local _, android = pcall(require, "android") local AndroidPowerD = BasePowerD:new{ fl_min = 0, fl_max = 25, fl_intensity = 10, - batt_capacity_file = "/sys/class/power_supply/battery/capacity", - is_charging_file = "/sys/class/power_supply/battery/charging_enabled", - battCapacity = nil, - is_charging = nil, } function AndroidPowerD:init() @@ -18,13 +14,11 @@ function AndroidPowerD:setIntensityHW() end function AndroidPowerD:getCapacityHW() - self.battCapacity = android.getBatteryLevel() - return self.battCapacity + return android.getBatteryLevel() end function AndroidPowerD:isChargingHW() - self.is_charging = android.isCharging() - return self.is_charging + return android.isCharging() end return AndroidPowerD diff --git a/frontend/device/generic/powerd.lua b/frontend/device/generic/powerd.lua index 6a6e52a41..ba4ac7acd 100644 --- a/frontend/device/generic/powerd.lua +++ b/frontend/device/generic/powerd.lua @@ -1,14 +1,13 @@ local logger = require("logger") local BasePowerD = { - fl_min = 0, -- min frontlight intensity - fl_max = 10, -- max frontlight intensity - fl_intensity = nil, -- frontlight intensity - battCapacity = nil, -- battery capacity - device = nil, -- device object + fl_min = 0, -- min frontlight intensity + fl_max = 10, -- max frontlight intensity + fl_intensity = nil, -- frontlight intensity + battCapacity = 0, -- battery capacity + device = nil, -- device object - capacity_pulled_count = 0, - capacity_cached_count = 10, + last_capacity_pull_time = 0, -- timestamp of last pull } function BasePowerD:new(o) @@ -22,8 +21,8 @@ end function BasePowerD:init() end function BasePowerD:toggleFrontlight() end function BasePowerD:setIntensityHW() end -function BasePowerD:getCapacityHW() return "0" end -function BasePowerD:isChargingHW() end +function BasePowerD:getCapacityHW() return 0 end +function BasePowerD:isChargingHW() return false end -- Anything needs to be done before do a real hardware suspend. Such as turn off -- front light. function BasePowerD:beforeSuspend() end @@ -66,19 +65,17 @@ function BasePowerD:setIntensity(intensity) end function BasePowerD:getCapacity() - if self.capacity_pulled_count == self.capacity_cached_count then - self.capacity_pulled_count = 0 - return self:getCapacityHW() - else - self.capacity_pulled_count = self.capacity_pulled_count + 1 - return self.battCapacity or self:getCapacityHW() + if os.time() - self.last_capacity_pull_time >= 60 then + self.battCapacity = self:getCapacityHW() + self.last_capacity_pull_time = os.time() end + return self.battCapacity end function BasePowerD:refreshCapacity() -- We want our next getCapacity call to actually pull up to date info -- instead of a cached value ;) - self.capacity_pulled_count = self.capacity_cached_count + self.last_capacity_pull_time = 0 end function BasePowerD:isCharging() diff --git a/frontend/device/kindle/powerd.lua b/frontend/device/kindle/powerd.lua index faa146cf4..b0e1a026e 100644 --- a/frontend/device/kindle/powerd.lua +++ b/frontend/device/kindle/powerd.lua @@ -5,8 +5,6 @@ local KindlePowerD = BasePowerD:new{ fl_min = 0, fl_max = 24, fl_intensity = nil, - battCapacity = nil, - is_charging = nil, lipc_handle = nil, is_fl_on = false, @@ -55,20 +53,20 @@ end function KindlePowerD:getCapacityHW() if self.lipc_handle ~= nil then - self.battCapacity = self.lipc_handle:get_int_property("com.lab126.powerd", "battLevel") + return self.lipc_handle:get_int_property("com.lab126.powerd", "battLevel") else - self.battCapacity = self:read_int_file(self.batt_capacity_file) + return self:read_int_file(self.batt_capacity_file) end - return self.battCapacity end function KindlePowerD:isChargingHW() + local is_charging if self.lipc_handle ~= nil then - self.is_charging = self.lipc_handle:get_int_property("com.lab126.powerd", "isCharging") + is_charging = self.lipc_handle:get_int_property("com.lab126.powerd", "isCharging") else - self.is_charging = self:read_int_file(self.is_charging_file) + is_charging = self:read_int_file(self.is_charging_file) end - return self.is_charging == 1 + return is_charging == 1 end function KindlePowerD:__gc() diff --git a/frontend/device/kobo/powerd.lua b/frontend/device/kobo/powerd.lua index d52a7874a..98bcb4fae 100644 --- a/frontend/device/kobo/powerd.lua +++ b/frontend/device/kobo/powerd.lua @@ -19,8 +19,6 @@ local KoboPowerD = BasePowerD:new{ batt_capacity_file = batt_state_folder .. "capacity", is_charging_file = batt_state_folder .. "status", - battCapacity = nil, - is_charging = nil, } function KoboPowerD:init() @@ -77,13 +75,11 @@ function KoboPowerD:setIntensityHW() end function KoboPowerD:getCapacityHW() - self.battCapacity = self:read_int_file(self.batt_capacity_file) - return self.battCapacity + return self:read_int_file(self.batt_capacity_file) end function KoboPowerD:isChargingHW() - self.is_charging = self:read_str_file(self.is_charging_file) == "Charging\n" - return self.is_charging + return self:read_str_file(self.is_charging_file) == "Charging\n" end -- Turn off front light before suspend. diff --git a/frontend/device/pocketbook/powerd.lua b/frontend/device/pocketbook/powerd.lua index 49ab9d838..a1e23c171 100644 --- a/frontend/device/pocketbook/powerd.lua +++ b/frontend/device/pocketbook/powerd.lua @@ -7,7 +7,6 @@ int IsCharging(); ]] local PocketBookPowerD = BasePowerD:new{ - battCapacity = nil, is_charging = nil, batt_capacity_file = "/sys/devices/platform/sun5i-i2c.0/i2c-0/0-0034/axp20-supplyer.28/power_supply/battery/capacity", is_charging_file = "/sys/devices/platform/sun5i-i2c.0/i2c-0/0-0034/axp20-supplyer.28/power_supply/battery/status", @@ -17,8 +16,7 @@ function PocketBookPowerD:init() end function PocketBookPowerD:getCapacityHW() - self.battCapacity = self:read_int_file(self.batt_capacity_file) - return self.battCapacity + return self:read_int_file(self.batt_capacity_file) end function PocketBookPowerD:isChargingHW() diff --git a/frontend/docsettings.lua b/frontend/docsettings.lua index 9b7d0a05e..87ef50752 100644 --- a/frontend/docsettings.lua +++ b/frontend/docsettings.lua @@ -2,7 +2,6 @@ local lfs = require("libs/libkoreader-lfs") local DataStorage = require("datastorage") local dump = require("dump") local purgeDir = require("ffi/util").purgeDir -local logger = require("logger") local DocSettings = {} @@ -23,9 +22,6 @@ function DocSettings:getSidecarDir(doc_path) if file_without_suffix then return file_without_suffix..".sdr" end - -- We shouldn't be called with anything but files with registered - -- extensions, but in case we are, return something useful - logger.err("getSidecarFile called with unexpected path:", doc_path) return doc_path..".sdr" end diff --git a/frontend/document/credocument.lua b/frontend/document/credocument.lua index 69e729612..73d204a4f 100644 --- a/frontend/document/credocument.lua +++ b/frontend/document/credocument.lua @@ -459,7 +459,9 @@ end function CreDocument:register(registry) registry:addProvider("txt", "application/txt", self) + registry:addProvider("log", "application/txt", self) registry:addProvider("txt.zip", "application/zip", self) + registry:addProvider("log.zip", "application/zip", self) registry:addProvider("epub", "application/epub", self) registry:addProvider("fb2", "application/fb2", self) registry:addProvider("fb2.zip", "application/zip", self) diff --git a/frontend/luasettings.lua b/frontend/luasettings.lua index bd8629b7c..a08707329 100644 --- a/frontend/luasettings.lua +++ b/frontend/luasettings.lua @@ -60,6 +60,10 @@ function LuaSettings:flipTrue(key) end end +function LuaSettings:reset(table) + self.data = table +end + function LuaSettings:flush() local f_out = io.open(self.file, "w") if f_out ~= nil then diff --git a/frontend/pluginloader.lua b/frontend/pluginloader.lua index 26e300179..fa39a3352 100644 --- a/frontend/pluginloader.lua +++ b/frontend/pluginloader.lua @@ -22,13 +22,14 @@ function PluginLoader:loadPlugins() local ok, plugin_module = pcall(dofile, mainfile) if not ok or not plugin_module then logger.warn("Error when loading", mainfile, plugin_module) - end - if ok and plugin_module and not plugin_module.disabled then + elseif type(plugin_module.disabled) ~= "boolean" or not plugin_module.disabled then package.path = package_path package.cpath = package_cpath plugin_module.path = path plugin_module.name = plugin_module.name or path:match("/(.-)%.koplugin") table.insert(self.plugins, plugin_module) + else + logger.info("Plugin ", mainfile, " has been disabled.") end end end diff --git a/frontend/ui/uimanager.lua b/frontend/ui/uimanager.lua index 0138f98bc..889f09be6 100644 --- a/frontend/ui/uimanager.lua +++ b/frontend/ui/uimanager.lua @@ -60,11 +60,12 @@ function UIManager:init() self:_initAutoSuspend() self.event_handlers["Suspend"] = function() self:_stopAutoSuspend() + self:broadcastEvent(Event:new("Suspend")) Device:onPowerEvent("Suspend") end self.event_handlers["Resume"] = function() Device:onPowerEvent("Resume") - self:sendEvent(Event:new("Resume")) + self:broadcastEvent(Event:new("Resume")) self:_startAutoSuspend() end self.event_handlers["PowerPress"] = function() @@ -99,11 +100,17 @@ function UIManager:init() Device:getPowerDevice():toggleFrontlight() end self.event_handlers["Charging"] = function() + self:broadcastEvent(Event:new("Charging")) + if Device.screen_saver_mode then + self.event_handlers["Suspend"]() + end + end + self.event_handlers["NotCharging"] = function() + self:broadcastEvent(Event:new("NotCharging")) if Device.screen_saver_mode then self.event_handlers["Suspend"]() end end - self.event_handlers["NotCharging"] = self.event_handlers["Charging"] self.event_handlers["__default__"] = function(input_event) if Device.screen_saver_mode then -- Suspension in Kobo can be interrupted by screen updates. We @@ -116,18 +123,20 @@ function UIManager:init() end elseif Device:isKindle() then self.event_handlers["IntoSS"] = function() + self:broadcastEvent(Event:new("Suspend")) Device:intoScreenSaver() end self.event_handlers["OutOfSS"] = function() Device:outofScreenSaver() - self:sendEvent(Event:new("Resume")) + self:broadcastEvent(Event:new("Resume")) end self.event_handlers["Charging"] = function() + self:broadcastEvent(Event:new("Charging")) Device:usbPlugIn() end self.event_handlers["NotCharging"] = function() Device:usbPlugOut() - self:sendEvent(Event:new("NotCharging")) + self:broadcastEvent(Event:new("NotCharging")) end end end @@ -728,7 +737,7 @@ function UIManager:_initAutoSuspend() local now = util.gettime() -- Do not repeat auto suspend procedure after suspend. if self.last_action_sec + self.auto_suspend_sec <= now then - Device:onPowerEvent("Suspend") + self.event_handlers["Suspend"]() else self:scheduleIn( self.last_action_sec + self.auto_suspend_sec - now, diff --git a/plugins/batterystat.koplugin/main.lua b/plugins/batterystat.koplugin/main.lua new file mode 100644 index 000000000..d8217835a --- /dev/null +++ b/plugins/batterystat.koplugin/main.lua @@ -0,0 +1,283 @@ + +local DataStorage = require("datastorage") +local KeyValuePage = require("ui/widget/keyvaluepage") +local LuaSettings = require("luasettings") +local PowerD = require("device"):getPowerDevice() +local UIManager = require("ui/uimanager") +local WidgetContainer = require("ui/widget/container/widgetcontainer") +local T = require("ffi/util").template +local logger = require("logger") +local util = require("ffi/util") +local _ = require("gettext") + +local State = {} + +function State:new(o) + o = o or {} + setmetatable(o, self) + self.__index = self + if o.percentage == nil or o.timestamp == nil then + o.percentage = PowerD:getCapacity() + o.timestamp = os.time() + end + return o +end + +function State:toString() + return string.format("{%d @ %s}", self.percentage, os.date("%c", self.timestamp)) +end + +local Usage = {} + +function Usage:new(o) + o = o or {} + setmetatable(o, self) + self.__index = self + if o.percentage == nil or o.time == nil then + o.percentage = 0 + o.time = 0 + end + return o +end + +function Usage:append(state) + local curr = State:new() + self.percentage = self.percentage + (state.percentage - curr.percentage) + self.time = self.time + os.difftime(curr.timestamp - state.timestamp) +end + +function Usage:minutes() + return self.time / 60 +end + +function Usage:hours() + return self:minutes() / 60 +end + +function Usage:percentagePerHour() + if self.time == 0 then + return 0 + else + return self.percentage / self:hours() + end +end + +function Usage:remainingHours() + local curr = State:new() + return curr.percentage / self:percentagePerHour() +end + +function Usage:chargingHours() + local curr = State:new() + return (curr.percentage - 100) / self:percentagePerHour() +end + +local function shorten(number) + return string.format("%.2f", number); +end + +function Usage:dump(kv_pairs) + table.insert(kv_pairs, {_(" Consumed %"), shorten(self.percentage)}) + table.insert(kv_pairs, {_(" Total minutes"), shorten(self:minutes())}) + table.insert(kv_pairs, {_(" % per hour"), shorten(self:percentagePerHour())}) +end + +function Usage:dumpRemaining(kv_pairs) + table.insert(kv_pairs, {_(" Estimated remaining hours"), shorten(self:remainingHours())}) +end + +function Usage:dumpCharging(kv_pairs) + table.insert(kv_pairs, {_(" Estimated hours for charging"), shorten(self:chargingHours())}) +end + +local BatteryStat = { + name = "batterstat", + settings = LuaSettings:open(DataStorage:getSettingsDir() .. "/batterstat.lua"), + dump_file = util.realpath(DataStorage:getDataDir()) .. "/batterystat.log", + debugging = false, +} + +function BatteryStat:init() + self.charging = Usage:new(self.settings:readSetting("charging")) + self.decharging = Usage:new(self.settings:readSetting("decharging")) + self.awake = Usage:new(self.settings:readSetting("awake")) + self.sleeping = Usage:new(self.settings:readSetting("sleeping")) + + -- Note: these fields are not the "real" timestamp and battery usage, but + -- the unaccumulated values. + self.charging_state = State:new(self.settings:readSetting("charging_state")) + self.awake_state = State:new(self.settings:readSetting("awake_state")) + self:initCurrentState() + + if self.debugging then + self.debugOutput = self._debugOutput + else + self.debugOutput = function() end + end +end + +function BatteryStat:initCurrentState() + -- Whether the device was suspending before current timestamp. + self.was_suspending = false + -- Whether the device was charging before current timestamp. + self.was_charging = PowerD:isCharging() +end + +function BatteryStat:onFlushSettings() + self.settings:reset({ + charging = self.charging, + decharging = self.decharging, + awake = self.awake, + sleeping = self.sleeping, + charging_state = self.charging_state, + awake_state = self.awake_state, + }) + self.settings:flush() +end + +function BatteryStat:accumulate() + if self.was_suspending then + -- Suspending to awake. + self.sleeping:append(self.awake_state) + else + -- Awake to suspending, time between self.awake_state and now should belong to awake. + self.awake:append(self.awake_state) + end + if self.was_charging then + -- Decharging to charging. + self.charging:append(self.charging_state) + else + self.decharging:append(self.charging_state) + end + self.awake_state = State:new() + self.charging_state = State:new() +end + +function BatteryStat:dumpOrLog(content) + local file = io.open(self.dump_file, "a") + if file then + file:write(content .. "\n") + file:close() + else + logger.warn("Failed to dump output ", content, " into ", self.dump_file ) + end +end + +function BatteryStat:_debugOutput(event) + self:dumpOrLog(event .. " @ " .. State:new():toString() .. + ", awake_state " .. self.awake_state:toString() .. + ", charging_state " .. self.charging_state:toString()) +end + +function BatteryStat:onSuspend() + self:debugOutput("onSuspend") + self.was_suspending = false + self:accumulate() +end + +function BatteryStat:onResume() + self:debugOutput("onResume") + self.was_suspending = true + self:accumulate() +end + +function BatteryStat:onCharging() + self:debugOutput("onCharging") + self.was_charging = false + self:dumpToText() + self.charging = Usage:new() + self.awake = Usage:new() + self.sleeping = Usage:new() + self:accumulate() +end + +function BatteryStat:onNotCharging() + self:debugOutput("onNotCharging") + self.was_charging = true + self:dumpToText() + self.decharging = Usage:new() + self.awake = Usage:new() + self.sleeping = Usage:new() + self:accumulate() +end + +function BatteryStat:onCallback() + self:initCurrentState() + self:accumulate() + local kv_pairs = self:dump() + table.insert(kv_pairs, "----------") + table.insert(kv_pairs, {_("Historical records are dumped to"), ""}) + table.insert(kv_pairs, {self.dump_file, ""}) + UIManager:show(KeyValuePage:new{ + title = _("Battery statistics"), + kv_pairs = kv_pairs, + }) +end + +function BatteryStat:dumpToText() + local kv_pairs = self:dump() + local content = T(_("Dump at %1"), os.date("%c")) + for _, pair in ipairs(kv_pairs) do + content = content .. "\n" .. pair[1] + if pair[2] ~= nil and pair[2] ~= "" then + content = content .. "\t" .. pair[2] + end + end + self:dumpOrLog(content .. "\n-=-=-=-=-=-\n") +end + +function BatteryStat:dump() + local kv_pairs = {} + table.insert(kv_pairs, {_("Awake since last charge"), ""}) + self.awake:dump(kv_pairs) + self.awake:dumpRemaining(kv_pairs) + table.insert(kv_pairs, {_("Sleeping since last charge"), ""}) + self.sleeping:dump(kv_pairs) + self.sleeping:dumpRemaining(kv_pairs) + table.insert(kv_pairs, {_("During last charge"), ""}) + self.charging:dump(kv_pairs) + self.charging:dumpCharging(kv_pairs) + table.insert(kv_pairs, {_("Since last charge"), ""}) + self.decharging:dump(kv_pairs) + self.decharging:dumpRemaining(kv_pairs) + return kv_pairs +end + +BatteryStat:init() + +local BatteryStatWidget = WidgetContainer:new() + +function BatteryStatWidget:init() + self.ui.menu:registerToMainMenu(self) +end + +function BatteryStatWidget:addToMainMenu(tab_item_table) + table.insert(tab_item_table.plugins, { + text = _("Battery statistics"), + callback = function() + BatteryStat:onCallback() + end, + }) +end + +function BatteryStatWidget:onFlushSettings() + BatteryStat:onFlushSettings() +end + +function BatteryStatWidget:onSuspend() + BatteryStat:onSuspend() +end + +function BatteryStatWidget:onResume() + BatteryStat:onResume() +end + +function BatteryStatWidget:onCharging() + BatteryStat:onCharging() +end + +function BatteryStatWidget:onNotCharging() + BatteryStat:onNotCharging() +end + +return BatteryStatWidget diff --git a/plugins/kobolight.koplugin/demo.png b/plugins/kobolight.koplugin/demo.png new file mode 100644 index 000000000..61b76f38f Binary files /dev/null and b/plugins/kobolight.koplugin/demo.png differ diff --git a/plugins/kobolight.koplugin/main.lua b/plugins/kobolight.koplugin/main.lua index e8a10cd45..0b5284674 100644 --- a/plugins/kobolight.koplugin/main.lua +++ b/plugins/kobolight.koplugin/main.lua @@ -1,20 +1,24 @@ local Device = require("device") -if not ((Device:isKindle() or Device:isKobo()) and Device:hasFrontlight()) then +local with_frontlight = (Device:isKindle() or Device:isKobo()) and Device:hasFrontlight() +if not (with_frontlight or Device:isSDL()) then return { disabled = true, } end -local WidgetContainer = require("ui/widget/container/widgetcontainer") -local Screen = Device.screen -local UIManager = require("ui/uimanager") +local ConfirmBox = require("ui/widget/confirmbox") +local ImageWidget = require("ui/widget/imagewidget") +local InfoMessage = require("ui/widget/infomessage") local Notification = require("ui/widget/notification") +local PluginLoader = require("pluginloader") +local Screen = require("device").screen +local UIManager = require("ui/uimanager") +local WidgetContainer = require("ui/widget/container/widgetcontainer") local T = require("ffi/util").template local _ = require("gettext") local tap_touch_zone_ratio = { x = 0, y = 15/16, w = 1/10, h = 1/16, } local swipe_touch_zone_ratio = { x = 0, y = 1/8, w = 1/10, h = 7/8, } - local KoboLight = WidgetContainer:new{ name = 'kobolight', gestureScale = nil, -- initialized in self:resetLayout() @@ -28,6 +32,8 @@ function KoboLight:init() do self.steps[i] = math.ceil(self.steps[i] * scale) end + + self.ui.menu:registerToMainMenu(self) end function KoboLight:onReaderReady() @@ -35,8 +41,13 @@ function KoboLight:onReaderReady() self:resetLayout() end +function KoboLight:disabled() + return G_reader_settings:isTrue("disable_kobolight") +end + function KoboLight:setupTouchZones() if not Device:isTouchDevice() then return end + if self:disabled() then return end local swipe_zone = { ratio_x = swipe_touch_zone_ratio.x, ratio_y = swipe_touch_zone_ratio.y, ratio_w = swipe_touch_zone_ratio.w, ratio_h = swipe_touch_zone_ratio.h, @@ -148,4 +159,39 @@ function KoboLight:onSwipe(_, ges) return true end +function KoboLight:addToMainMenu(tab_item_table) + table.insert(tab_item_table.plugins, { + text = _("Frontlight gesture controller"), + callback = function() + local image = ImageWidget:new{ + file = PluginLoader.plugin_path .. "/kobolight.koplugin/demo.png", + height = Screen:getHeight(), + width = Screen:getWidth(), + scale_factor = 0, + } + UIManager:show(image) + UIManager:show(ConfirmBox:new{ + text = T(_("Frontlight gesture controller can:\n- Turn on or off frontlight by tapping bottom left of the screen.\n- Change frontlight intensity by swiping up or down on the left of the screen.\n\nDo you want to %1 it?"), + self:disabled() and _("enable") or _("disable")), + ok_text = self:disabled() and _("Enable") or _("Disable"), + ok_callback = function() + UIManager:close(image) + UIManager:setDirty("all", "full") + UIManager:show(InfoMessage:new{ + text = T(_("You have %1 the frontlight gesture controller. It will take effect on next restart."), + self:disabled() and _("enabled") or _("disabled")) + }) + G_reader_settings:flipTrue("disable_kobolight") + end, + cancel_text = _("Close"), + cancel_callback = function() + UIManager:close(image) + UIManager:setDirty("all", "full") + end, + }) + UIManager:setDirty("all", "full") + end, + }) +end + return KoboLight