[feat] Replace TimeVal (RIP) with time, fixed point time seconds (#8999)

reviewable/pr9032/r1
zwim 2 years ago committed by GitHub
parent 54ead5fc88
commit 9b9cfe29a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -16,7 +16,7 @@ memory_confirm_box = nil,
function ReaderDeviceStatus:init() function ReaderDeviceStatus:init()
if Device:hasBattery() then if Device:hasBattery() then
self.battery_interval = G_reader_settings:readSetting("device_status_battery_interval", 10) self.battery_interval_m = G_reader_settings:readSetting("device_status_battery_interval_minutes", 10)
self.battery_threshold = G_reader_settings:readSetting("device_status_battery_threshold", 20) self.battery_threshold = G_reader_settings:readSetting("device_status_battery_threshold", 20)
self.battery_threshold_high = G_reader_settings:readSetting("device_status_battery_threshold_high", 100) self.battery_threshold_high = G_reader_settings:readSetting("device_status_battery_threshold_high", 100)
self.checkLowBatteryLevel = function() self.checkLowBatteryLevel = function()
@ -45,13 +45,13 @@ function ReaderDeviceStatus:init()
UIManager:show(self.battery_confirm_box) UIManager:show(self.battery_confirm_box)
end end
end end
UIManager:scheduleIn(self.battery_interval * 60, self.checkLowBatteryLevel) UIManager:scheduleIn(self.battery_interval_m * 60, self.checkLowBatteryLevel)
end end
self:startBatteryChecker() self:startBatteryChecker()
end end
if not Device:isAndroid() then if not Device:isAndroid() then
self.memory_interval = G_reader_settings:readSetting("device_status_memory_interval", 5) self.memory_interval_m = G_reader_settings:readSetting("device_status_memory_interval_minutes", 5)
self.memory_threshold = G_reader_settings:readSetting("device_status_memory_threshold", 100) self.memory_threshold = G_reader_settings:readSetting("device_status_memory_threshold", 100)
self.checkHighMemoryUsage = function() self.checkHighMemoryUsage = function()
local statm = io.open("/proc/self/statm", "r") local statm = io.open("/proc/self/statm", "r")
@ -103,7 +103,7 @@ function ReaderDeviceStatus:init()
end end
end end
end end
UIManager:scheduleIn(self.memory_interval * 60, self.checkHighMemoryUsage) UIManager:scheduleIn(self.memory_interval_m * 60, self.checkHighMemoryUsage)
end end
self:startMemoryChecker() self:startMemoryChecker()
end end
@ -136,7 +136,7 @@ function ReaderDeviceStatus:addToMainMenu(menu_items)
table.insert(menu_items.device_status_alarm.sub_item_table, table.insert(menu_items.device_status_alarm.sub_item_table,
{ {
text_func = function() text_func = function()
return T(_("Check interval: %1 min"), self.battery_interval) return T(_("Check interval: %1 min"), self.battery_interval_m)
end, end,
enabled_func = function() enabled_func = function()
return G_reader_settings:isTrue("device_status_battery_alarm") return G_reader_settings:isTrue("device_status_battery_alarm")
@ -144,18 +144,18 @@ function ReaderDeviceStatus:addToMainMenu(menu_items)
keep_menu_open = true, keep_menu_open = true,
callback = function(touchmenu_instance) callback = function(touchmenu_instance)
UIManager:show(SpinWidget:new{ UIManager:show(SpinWidget:new{
value = self.battery_interval, value = self.battery_interval_m,
value_min = 1, value_min = 1,
value_max = 60, value_max = 60,
default_value = 10, default_value = 10,
value_hold_step = 5, value_hold_step = 5,
title_text = _("Battery check interval"), title_text = _("Battery check interval"),
callback = function(spin) callback = function(spin)
self.battery_interval = spin.value self.battery_interval_m = spin.value
G_reader_settings:saveSetting("device_status_battery_interval", self.battery_interval) G_reader_settings:saveSetting("device_status_battery_interval_minutes", self.battery_interval_m)
touchmenu_instance:updateItems() touchmenu_instance:updateItems()
powerd:setDismissBatteryStatus(false) powerd:setDismissBatteryStatus(false)
UIManager:scheduleIn(self.battery_interval * 60, self.checkLowBatteryLevel) UIManager:scheduleIn(self.battery_interval_m * 60, self.checkLowBatteryLevel)
end, end,
}) })
end, end,
@ -228,7 +228,7 @@ High level threshold is checked when the device is charging.]]),
table.insert(menu_items.device_status_alarm.sub_item_table, table.insert(menu_items.device_status_alarm.sub_item_table,
{ {
text_func = function() text_func = function()
return T(_("Check interval: %1 min"), self.memory_interval) return T(_("Check interval: %1 min"), self.memory_interval_m)
end, end,
enabled_func = function() enabled_func = function()
return G_reader_settings:isTrue("device_status_memory_alarm") return G_reader_settings:isTrue("device_status_memory_alarm")
@ -236,17 +236,17 @@ High level threshold is checked when the device is charging.]]),
keep_menu_open = true, keep_menu_open = true,
callback = function(touchmenu_instance) callback = function(touchmenu_instance)
UIManager:show(SpinWidget:new{ UIManager:show(SpinWidget:new{
value = self.memory_interval, value = self.memory_interval_m,
value_min = 1, value_min = 1,
value_max = 60, value_max = 60,
default_value = 5, default_value = 5,
value_hold_step = 5, value_hold_step = 5,
title_text = _("Memory check interval"), title_text = _("Memory check interval"),
callback = function(spin) callback = function(spin)
self.memory_interval = spin.value self.memory_interval_m = spin.value
G_reader_settings:saveSetting("device_status_memory_interval", self.memory_interval) G_reader_settings:saveSetting("device_status_memory_interval_minutes", self.memory_interval_m)
touchmenu_instance:updateItems() touchmenu_instance:updateItems()
UIManager:scheduleIn(self.memory_interval * 60, self.checkHighMemoryUsage) UIManager:scheduleIn(self.memory_interval_m * 60, self.checkHighMemoryUsage)
end, end,
}) })
end, end,

@ -13,11 +13,11 @@ local LuaData = require("luadata")
local MultiConfirmBox = require("ui/widget/multiconfirmbox") local MultiConfirmBox = require("ui/widget/multiconfirmbox")
local NetworkMgr = require("ui/network/manager") local NetworkMgr = require("ui/network/manager")
local SortWidget = require("ui/widget/sortwidget") local SortWidget = require("ui/widget/sortwidget")
local TimeVal = require("ui/timeval")
local Trapper = require("ui/trapper") local Trapper = require("ui/trapper")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local ffiUtil = require("ffi/util") local ffiUtil = require("ffi/util")
local logger = require("logger") local logger = require("logger")
local time = require("ui/time")
local util = require("util") local util = require("util")
local _ = require("gettext") local _ = require("gettext")
local T = ffiUtil.template local T = ffiUtil.template
@ -111,7 +111,7 @@ function ReaderDictionary:init()
-- Allow quick interruption or dismiss of search result window -- Allow quick interruption or dismiss of search result window
-- with tap if done before this delay. After this delay, the -- with tap if done before this delay. After this delay, the
-- result window is shown and dismiss prevented for a few 100ms -- result window is shown and dismiss prevented for a few 100ms
self.quick_dismiss_before_delay = TimeVal:new{ sec = 3, usec = 0 } self.quick_dismiss_before_delay = time.s(3)
-- Gather info about available dictionaries -- Gather info about available dictionaries
if not available_ifos then if not available_ifos then
@ -931,9 +931,10 @@ function ReaderDictionary:stardictLookup(word, dict_names, fuzzy_search, boxes,
self:showLookupInfo(word, self.lookup_msg_delay) self:showLookupInfo(word, self.lookup_msg_delay)
self._lookup_start_tv = UIManager:getTime() self._lookup_start_time = UIManager:getTime()
local results = self:startSdcv(word, dict_names, fuzzy_search) local results = self:startSdcv(word, dict_names, fuzzy_search)
if results and results.lookup_cancelled and TimeVal:now() - self._lookup_start_tv <= self.quick_dismiss_before_delay then if results and results.lookup_cancelled
and (time.now() - self._lookup_start_time) <= self.quick_dismiss_before_delay then
-- If interrupted quickly just after launch, don't display anything -- If interrupted quickly just after launch, don't display anything
-- (this might help avoiding refreshes and the need to dismiss -- (this might help avoiding refreshes and the need to dismiss
-- after accidental long-press when holding a device). -- after accidental long-press when holding a device).
@ -991,7 +992,8 @@ function ReaderDictionary:showDict(word, results, boxes, link)
self:dismissLookupInfo() self:dismissLookupInfo()
if results and results[1] then if results and results[1] then
UIManager:show(self.dict_window) UIManager:show(self.dict_window)
if not results.lookup_cancelled and self._lookup_start_tv and TimeVal:now() - self._lookup_start_tv > self.quick_dismiss_before_delay then if not results.lookup_cancelled and self._lookup_start_time
and (time.now() - self._lookup_start_time) > self.quick_dismiss_before_delay then
-- If the search took more than a few seconds to be done, discard -- If the search took more than a few seconds to be done, discard
-- queued and coming up events to avoid a voluntary dismissal -- queued and coming up events to avoid a voluntary dismissal
-- (because the user felt the result would not come) to kill the -- (because the user felt the result would not come) to kill the

@ -7,7 +7,6 @@ local InfoMessage = require("ui/widget/infomessage")
local InputContainer = require("ui/widget/container/inputcontainer") local InputContainer = require("ui/widget/container/inputcontainer")
local Notification = require("ui/widget/notification") local Notification = require("ui/widget/notification")
local TextViewer = require("ui/widget/textviewer") local TextViewer = require("ui/widget/textviewer")
local TimeVal = require("ui/timeval")
local Translator = require("ui/translator") local Translator = require("ui/translator")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local dbg = require("dbg") local dbg = require("dbg")
@ -15,6 +14,7 @@ local logger = require("logger")
local util = require("util") local util = require("util")
local Size = require("ui/size") local Size = require("ui/size")
local ffiUtil = require("ffi/util") local ffiUtil = require("ffi/util")
local time = require("ui/time")
local _ = require("gettext") local _ = require("gettext")
local C_ = _.pgettext local C_ = _.pgettext
local T = require("ffi/util").template local T = require("ffi/util").template
@ -51,7 +51,7 @@ function ReaderHighlight:init()
self._start_indicator_highlight = false self._start_indicator_highlight = false
self._current_indicator_pos = nil self._current_indicator_pos = nil
self._previous_indicator_pos = nil self._previous_indicator_pos = nil
self._last_indicator_move_args = {dx = 0, dy = 0, distance = 0, time = TimeVal:now()} self._last_indicator_move_args = {dx = 0, dy = 0, distance = 0, time = time:now()}
if Device:hasDPad() then if Device:hasDPad() then
-- Used for text selection with dpad/keys -- Used for text selection with dpad/keys
@ -897,9 +897,9 @@ dbg:guard(ReaderHighlight, "onShowHighlightMenu",
function ReaderHighlight:_resetHoldTimer(clear) function ReaderHighlight:_resetHoldTimer(clear)
if clear then if clear then
self.hold_last_tv = nil self.hold_last_time = nil
else else
self.hold_last_tv = UIManager:getTime() self.hold_last_time = UIManager:getTime()
end end
end end
@ -1423,14 +1423,14 @@ function ReaderHighlight:onHoldRelease()
end end
local long_final_hold = false local long_final_hold = false
if self.hold_last_tv then if self.hold_last_time then
local hold_duration = TimeVal:now() - self.hold_last_tv local hold_duration = time.now() - self.hold_last_time
local long_hold_threshold = G_reader_settings:readSetting("highlight_long_hold_threshold", 3) local long_hold_threshold_s = G_reader_settings:readSetting("highlight_long_hold_threshold", 3) -- seconds
if hold_duration > TimeVal:new{ sec = long_hold_threshold, usec = 0 } then if hold_duration > time.s(long_hold_threshold_s) then
-- We stayed 3 seconds before release without updating selection -- We stayed 3 seconds before release without updating selection
long_final_hold = true long_final_hold = true
end end
self.hold_last_tv = nil self.hold_last_time = nil
end end
if self.is_word_selection then -- single-word selection if self.is_word_selection then -- single-word selection
if long_final_hold or G_reader_settings:isTrue("highlight_action_on_single_word") then if long_final_hold or G_reader_settings:isTrue("highlight_action_on_single_word") then
@ -1947,14 +1947,14 @@ function ReaderHighlight:onMoveHighlightIndicator(args)
rect.x = rect.x + quick_move_distance_dx * dx rect.x = rect.x + quick_move_distance_dx * dx
rect.y = rect.y + quick_move_distance_dy * dy rect.y = rect.y + quick_move_distance_dy * dy
else else
local now = TimeVal:now() local now = time:now()
if dx == self._last_indicator_move_args.dx and dy == self._last_indicator_move_args.dy then if dx == self._last_indicator_move_args.dx and dy == self._last_indicator_move_args.dy then
local diff = now - self._last_indicator_move_args.time local diff = now - self._last_indicator_move_args.time
-- if press same arrow key in 1 second, speed up -- if press same arrow key in 1 second, speed up
-- double press: 4 single move distances, usually move to next word or line -- double press: 4 single move distances, usually move to next word or line
-- triple press: 16 single distances, usually skip several words or lines -- triple press: 16 single distances, usually skip several words or lines
-- quadruple press: 54 single distances, almost move to screen edge -- quadruple press: 54 single distances, almost move to screen edge
if diff < TimeVal:new{ sec = 1, usec = 0 } then if diff < time.s(1) then
move_distance = self._last_indicator_move_args.distance * 4 move_distance = self._last_indicator_move_args.distance * 4
end end
end end
@ -1998,7 +1998,7 @@ function ReaderHighlight:_createHighlightGesture(gesture)
return { return {
ges = gesture, ges = gesture,
pos = point, pos = point,
time = TimeVal:realtime(), time = time.realtime(),
} }
end end

@ -5,16 +5,15 @@ local Geom = require("ui/geometry")
local InputContainer = require("ui/widget/container/inputcontainer") local InputContainer = require("ui/widget/container/inputcontainer")
local Math = require("optmath") local Math = require("optmath")
local ReaderZooming = require("apps/reader/modules/readerzooming") local ReaderZooming = require("apps/reader/modules/readerzooming")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local bit = require("bit") local bit = require("bit")
local logger = require("logger") local logger = require("logger")
local util = require("util") local util = require("util")
local time = require("ui/time")
local _ = require("gettext") local _ = require("gettext")
local Input = Device.input local Input = Device.input
local Screen = Device.screen local Screen = Device.screen
local function copyPageState(page_state) local function copyPageState(page_state)
return { return {
page = page_state.page, page = page_state.page,
@ -98,7 +97,7 @@ function ReaderPaging:init()
{"0"}, doc = "go to end", event = "GotoPercent", args = 100, {"0"}, doc = "go to end", event = "GotoPercent", args = 100,
} }
end end
self.pan_interval = TimeVal:new{ usec = 1000000 / self.pan_rate } self.pan_interval = time.s(1 / self.pan_rate)
self.number_of_pages = self.ui.document.info.number_of_pages self.number_of_pages = self.ui.document.info.number_of_pages
end end
@ -321,9 +320,9 @@ function ReaderPaging:bookmarkFlipping(flipping_page, flipping_ges)
UIManager:setDirty(self.view.dialog, "partial") UIManager:setDirty(self.view.dialog, "partial")
end end
function ReaderPaging:onScrollSettingsUpdated(scroll_method, inertial_scroll_enabled, scroll_activation_delay) function ReaderPaging:onScrollSettingsUpdated(scroll_method, inertial_scroll_enabled, scroll_activation_delay_ms)
self.scroll_method = scroll_method self.scroll_method = scroll_method
self.scroll_activation_delay = TimeVal:new{ usec = scroll_activation_delay * 1000 } self.scroll_activation_delay = time.ms(scroll_activation_delay_ms)
if inertial_scroll_enabled then if inertial_scroll_enabled then
self.ui.scrolling:setInertialScrollCallbacks( self.ui.scrolling:setInertialScrollCallbacks(
function(distance) -- do_scroll_callback function(distance) -- do_scroll_callback
@ -408,7 +407,7 @@ function ReaderPaging:onPan(_, ges)
self._pan_has_scrolled = false self._pan_has_scrolled = false
self._pan_prev_relative_y = 0 self._pan_prev_relative_y = 0
self._pan_to_scroll_later = 0 self._pan_to_scroll_later = 0
self._pan_real_last_time = TimeVal.zero self._pan_real_last_time = 0
if ges.mousewheel_direction then if ges.mousewheel_direction then
self._pan_activation_time = false self._pan_activation_time = false
else else

@ -7,10 +7,10 @@ local InputContainer = require("ui/widget/container/inputcontainer")
local ProgressWidget = require("ui/widget/progresswidget") local ProgressWidget = require("ui/widget/progresswidget")
local ReaderPanning = require("apps/reader/modules/readerpanning") local ReaderPanning = require("apps/reader/modules/readerpanning")
local Size = require("ui/size") local Size = require("ui/size")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local bit = require("bit") local bit = require("bit")
local logger = require("logger") local logger = require("logger")
local time = require("ui/time")
local _ = require("gettext") local _ = require("gettext")
local Screen = Device.screen local Screen = Device.screen
local T = require("ffi/util").template local T = require("ffi/util").template
@ -112,7 +112,7 @@ function ReaderRolling:init()
{"0"}, doc = "go to end", event = "GotoPercent", args = 100, {"0"}, doc = "go to end", event = "GotoPercent", args = 100,
} }
end end
self.pan_interval = TimeVal:new{ usec = 1000000 / self.pan_rate } self.pan_interval = time.s(1 / self.pan_rate)
table.insert(self.ui.postInitCallback, function() table.insert(self.ui.postInitCallback, function()
self.rendering_hash = self.ui.document:getDocumentRenderingHash() self.rendering_hash = self.ui.document:getDocumentRenderingHash()
@ -403,9 +403,9 @@ function ReaderRolling:getLastPercent()
end end
end end
function ReaderRolling:onScrollSettingsUpdated(scroll_method, inertial_scroll_enabled, scroll_activation_delay) function ReaderRolling:onScrollSettingsUpdated(scroll_method, inertial_scroll_enabled, scroll_activation_delay_ms)
self.scroll_method = scroll_method self.scroll_method = scroll_method
self.scroll_activation_delay = TimeVal:new{ usec = scroll_activation_delay * 1000 } self.scroll_activation_delay = time.ms(scroll_activation_delay_ms)
if inertial_scroll_enabled then if inertial_scroll_enabled then
self.ui.scrolling:setInertialScrollCallbacks( self.ui.scrolling:setInertialScrollCallbacks(
function(distance) -- do_scroll_callback function(distance) -- do_scroll_callback
@ -478,7 +478,7 @@ function ReaderRolling:onPan(_, ges)
self._pan_has_scrolled = false self._pan_has_scrolled = false
self._pan_prev_relative_y = 0 self._pan_prev_relative_y = 0
self._pan_to_scroll_later = 0 self._pan_to_scroll_later = 0
self._pan_real_last_time = TimeVal.zero self._pan_real_last_time = 0
if ges.mousewheel_direction then if ges.mousewheel_direction then
self._pan_activation_time = false self._pan_activation_time = false
else else
@ -1173,8 +1173,8 @@ function ReaderRolling:handleEngineCallback(ev, ...)
-- ignore other events -- ignore other events
end end
local ENGINE_PROGRESS_INITIAL_DELAY = TimeVal:new{ sec = 2, usec = 0 } local ENGINE_PROGRESS_INITIAL_DELAY = time.s(2)
local ENGINE_PROGRESS_UPDATE_DELAY = TimeVal:new{ sec = 0, usec = 500000 } local ENGINE_PROGRESS_UPDATE_DELAY = time.ms(500)
function ReaderRolling:showEngineProgress(percent) function ReaderRolling:showEngineProgress(percent)
if G_reader_settings and G_reader_settings:isFalse("cre_show_progress") then if G_reader_settings and G_reader_settings:isFalse("cre_show_progress") then
@ -1186,7 +1186,7 @@ function ReaderRolling:showEngineProgress(percent)
end end
if percent then if percent then
local now = TimeVal:now() local now = time.now()
if self.engine_progress_update_not_before and now < self.engine_progress_update_not_before then if self.engine_progress_update_not_before and now < self.engine_progress_update_not_before then
return return
end end

@ -1,9 +1,9 @@
local Device = require("device") local Device = require("device")
local Event = require("ui/event") local Event = require("ui/event")
local InputContainer = require("ui/widget/container/inputcontainer") local InputContainer = require("ui/widget/container/inputcontainer")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local logger = require("logger") local logger = require("logger")
local time = require("ui/time")
local _ = require("gettext") local _ = require("gettext")
local T = require("ffi/util").template local T = require("ffi/util").template
local Screen = Device.screen local Screen = Device.screen
@ -22,7 +22,7 @@ local ReaderScrolling = InputContainer:new{
SCROLL_METHOD_ON_RELEASE = SCROLL_METHOD_ON_RELEASE, SCROLL_METHOD_ON_RELEASE = SCROLL_METHOD_ON_RELEASE,
scroll_method = SCROLL_METHOD_CLASSIC, scroll_method = SCROLL_METHOD_CLASSIC,
scroll_activation_delay = 0, -- 0 ms scroll_activation_delay_ms = 0, -- 0 ms
inertial_scroll = false, inertial_scroll = false,
pan_rate = 30, -- default 30 ops, will be adjusted in readerui pan_rate = 30, -- default 30 ops, will be adjusted in readerui
@ -30,7 +30,7 @@ local ReaderScrolling = InputContainer:new{
-- go at ending scrolling soon when we reach steps smaller than this -- go at ending scrolling soon when we reach steps smaller than this
end_scroll_dist = Screen:scaleBySize(10), end_scroll_dist = Screen:scaleBySize(10),
-- no inertial scrolling if 300ms pause without any movement before release -- no inertial scrolling if 300ms pause without any movement before release
pause_before_release_cancel_duration = TimeVal:new{ sec = 0, usec = 300000 }, pause_before_release_cancel_duration = time.ms(300),
-- Callbacks to be updated by readerrolling or readerpaging -- Callbacks to be updated by readerrolling or readerpaging
_do_scroll_callback = function(distance) return false end, _do_scroll_callback = function(distance) return false end,
@ -66,14 +66,14 @@ function ReaderScrolling:init()
-- we miss a first touch event. -- we miss a first touch event.
-- We can keep it obsolete, which will result in a long -- We can keep it obsolete, which will result in a long
-- duration and a small/zero velocity that won't hurt. -- duration and a small/zero velocity that won't hurt.
self._last_manual_scroll_timev = TimeVal.zero self._last_manual_scroll_timev = 0
self:_setupAction() self:_setupAction()
end end
self.ui.menu:registerToMainMenu(self) self.ui.menu:registerToMainMenu(self)
end end
function ReaderScrolling:getDefaultScrollActivationDelay() function ReaderScrolling:getDefaultScrollActivationDelay_ms()
if (self.ui.gestures and self.ui.gestures.multiswipes_enabled) if (self.ui.gestures and self.ui.gestures.multiswipes_enabled)
or G_reader_settings:readSetting("activate_menu") ~= "tap" then or G_reader_settings:readSetting("activate_menu") ~= "tap" then
-- If swipes to show menu or multiswipes are enabled, higher default -- If swipes to show menu or multiswipes are enabled, higher default
@ -143,11 +143,11 @@ This is interesting on eInk if you only pan to better adjust page vertical posit
}, },
{ {
text_func = function() text_func = function()
return T(_("Activation delay: %1 ms"), self.scroll_activation_delay) return T(_("Activation delay: %1 ms"), self.scroll_activation_delay_ms)
end, end,
keep_menu_open = true, keep_menu_open = true,
callback = function(touchmenu_instance) callback = function(touchmenu_instance)
local scroll_activation_delay_default = self:getDefaultScrollActivationDelay() local scroll_activation_delay_default_ms = self:getDefaultScrollActivationDelay_ms()
local SpinWidget = require("ui/widget/spinwidget") local SpinWidget = require("ui/widget/spinwidget")
local widget = SpinWidget:new{ local widget = SpinWidget:new{
title_text = _("Scroll activation delay"), title_text = _("Scroll activation delay"),
@ -155,17 +155,17 @@ This is interesting on eInk if you only pan to better adjust page vertical posit
A delay can be used to avoid scrolling when swipes or multiswipes are intended. A delay can be used to avoid scrolling when swipes or multiswipes are intended.
The delay value is in milliseconds and can range from 0 to 2000 (2 seconds). The delay value is in milliseconds and can range from 0 to 2000 (2 seconds).
Default value: %1 ms]]), scroll_activation_delay_default), Default value: %1 ms]]), scroll_activation_delay_default_ms),
width = math.floor(Screen:getWidth() * 0.75), width = math.floor(Screen:getWidth() * 0.75),
value = self.scroll_activation_delay, value = self.scroll_activation_delay_ms,
value_min = 0, value_min = 0,
value_max = 2000, value_max = 2000,
value_step = 100, value_step = 100,
value_hold_step = 500, value_hold_step = 500,
ok_text = _("Set delay"), ok_text = _("Set delay"),
default_value = scroll_activation_delay_default, default_value = scroll_activation_delay_default_ms,
callback = function(spin) callback = function(spin)
self.scroll_activation_delay = spin.value self.scroll_activation_delay_ms = spin.value
self:applyScrollSettings() self:applyScrollSettings()
if touchmenu_instance then touchmenu_instance:updateItems() end if touchmenu_instance then touchmenu_instance:updateItems() end
end end
@ -195,18 +195,18 @@ end
function ReaderScrolling:onReaderReady() function ReaderScrolling:onReaderReady()
-- We don't know if the gestures plugin is loaded in :init(), but we know it here -- We don't know if the gestures plugin is loaded in :init(), but we know it here
self.scroll_activation_delay = G_reader_settings:readSetting("scroll_activation_delay") self.scroll_activation_delay_ms = G_reader_settings:readSetting("scroll_activation_delay")
or self:getDefaultScrollActivationDelay() or self:getDefaultScrollActivationDelay_ms()
self:applyScrollSettings() self:applyScrollSettings()
end end
function ReaderScrolling:applyScrollSettings() function ReaderScrolling:applyScrollSettings()
G_reader_settings:saveSetting("scroll_method", self.scroll_method) G_reader_settings:saveSetting("scroll_method", self.scroll_method)
G_reader_settings:saveSetting("inertial_scroll", self.inertial_scroll) G_reader_settings:saveSetting("inertial_scroll", self.inertial_scroll)
if self.scroll_activation_delay == self:getDefaultScrollActivationDelay() then if self.scroll_activation_delay_ms == self:getDefaultScrollActivationDelay_ms() then
G_reader_settings:delSetting("scroll_activation_delay") G_reader_settings:delSetting("scroll_activation_delay")
else else
G_reader_settings:saveSetting("scroll_activation_delay", self.scroll_activation_delay) G_reader_settings:saveSetting("scroll_activation_delay", self.scroll_activation_delay_ms)
end end
if self.scroll_method == self.SCROLL_METHOD_CLASSIC then if self.scroll_method == self.SCROLL_METHOD_CLASSIC then
self._inertial_scroll_enabled = self.inertial_scroll self._inertial_scroll_enabled = self.inertial_scroll
@ -215,7 +215,7 @@ function ReaderScrolling:applyScrollSettings()
end end
self:setupTouchZones() self:setupTouchZones()
self.ui:handleEvent(Event:new("ScrollSettingsUpdated", self.scroll_method, self.ui:handleEvent(Event:new("ScrollSettingsUpdated", self.scroll_method,
self._inertial_scroll_enabled, self.scroll_activation_delay)) self._inertial_scroll_enabled, self.scroll_activation_delay_ms))
end end
function ReaderScrolling:setupTouchZones() function ReaderScrolling:setupTouchZones()
@ -339,14 +339,14 @@ function ReaderScrolling:_setupAction()
self._last_manual_scroll_dy = 0 self._last_manual_scroll_dy = 0
return false return false
end end
if self._last_manual_scroll_duration:isZero() or self._last_manual_scroll_dy == 0 then if self._last_manual_scroll_duration == 0 or self._last_manual_scroll_dy == 0 then
return false return false
end end
-- Initial velocity is the one of the last pan scroll given to accountManualScroll() -- Initial velocity is the one of the last pan scroll given to accountManualScroll()
local delay = self._last_manual_scroll_duration:tousecs() local delay_us = time.to_us(self._last_manual_scroll_duration)
if delay < 1 then delay = 1 end -- safety check if delay_us < 1 then delay_us = 1 end -- safety check
self._velocity = self._last_manual_scroll_dy * 1000000 / delay self._velocity = self._last_manual_scroll_dy * time.s(1 / delay_us)
self._last_manual_scroll_dy = 0 self._last_manual_scroll_dy = 0
self._inertial_scroll_action_scheduled = true self._inertial_scroll_action_scheduled = true

@ -314,15 +314,15 @@ function ReaderThumbnail:startTileGeneration(request)
local scale_factor = math.min(request.width / bb:getWidth(), request.height / bb:getHeight()) local scale_factor = math.min(request.width / bb:getWidth(), request.height / bb:getHeight())
local target_w = math.floor(bb:getWidth() * scale_factor) local target_w = math.floor(bb:getWidth() * scale_factor)
local target_h = math.floor(bb:getHeight() * scale_factor) local target_h = math.floor(bb:getHeight() * scale_factor)
-- local TimeVal = require("ui/timeval") -- local time = require("ui/time")
-- local start_tv = TimeVal:now() -- local start_time = time.now()
local tile = TileCacheItem:new{ local tile = TileCacheItem:new{
bb = RenderImage:scaleBlitBuffer(bb, target_w, target_h, true), bb = RenderImage:scaleBlitBuffer(bb, target_w, target_h, true),
pageno = request.page, pageno = request.page,
} }
tile.size = tonumber(tile.bb.stride) * tile.bb.h tile.size = tonumber(tile.bb.stride) * tile.bb.h
-- logger.info("tile size", tile.bb.w, tile.bb.h, "=>", tile.size) -- logger.info("tile size", tile.bb.w, tile.bb.h, "=>", tile.size)
-- logger.info(string.format(" scaling took %.3f seconds, %d bpp", TimeVal:getDuration(start_tv), tile.bb:getBpp())) -- logger.info(string.format(" scaling took %.3f seconds, %d bpp", time.to_s(time.since(start_time)), tile.bb:getBpp()))
-- bb:free() -- no need to spend time freeing, we're dying soon anyway! -- bb:free() -- no need to spend time freeing, we're dying soon anyway!
ffiutil.writeToFD(child_write_fd, self.codec.serialize(tile:totable()), true) ffiutil.writeToFD(child_write_fd, self.codec.serialize(tile:totable()), true)
@ -343,8 +343,8 @@ function ReaderThumbnail:checkTileGeneration(request)
local subprocess_done = ffiutil.isSubProcessDone(pid) local subprocess_done = ffiutil.isSubProcessDone(pid)
logger.dbg("subprocess_done:", subprocess_done, " stuff_to_read:", stuff_to_read) logger.dbg("subprocess_done:", subprocess_done, " stuff_to_read:", stuff_to_read)
if stuff_to_read then if stuff_to_read then
-- local TimeVal = require("ui/timeval") -- local time = require("ui/time")
-- local start_tv = TimeVal:now() -- local start_time = time.now()
local result, err = self.codec.deserialize(ffiutil.readAllFromFD(parent_read_fd)) local result, err = self.codec.deserialize(ffiutil.readAllFromFD(parent_read_fd))
if result then if result then
local tile = TileCacheItem:new{} local tile = TileCacheItem:new{}
@ -361,7 +361,7 @@ function ReaderThumbnail:checkTileGeneration(request)
request.when_generated_callback(nil, request.batch_id, true) request.when_generated_callback(nil, request.batch_id, true)
end end
end end
-- logger.info(string.format(" parsing result from subprocess took %.3f seconds", TimeVal:getDuration(start_tv))) -- logger.info(string.format(" parsing result from subprocess took %.3f seconds", time.to_s(time.since(start_time))))
if not subprocess_done then if not subprocess_done then
table.insert(pids_to_collect, pid) table.insert(pids_to_collect, pid)
return false, true return false, true

@ -14,12 +14,12 @@ local OverlapGroup = require("ui/widget/overlapgroup")
local ReaderDogear = require("apps/reader/modules/readerdogear") local ReaderDogear = require("apps/reader/modules/readerdogear")
local ReaderFlipping = require("apps/reader/modules/readerflipping") local ReaderFlipping = require("apps/reader/modules/readerflipping")
local ReaderFooter = require("apps/reader/modules/readerfooter") local ReaderFooter = require("apps/reader/modules/readerfooter")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local dbg = require("dbg") local dbg = require("dbg")
local logger = require("logger") local logger = require("logger")
local optionsutil = require("ui/data/optionsutil") local optionsutil = require("ui/data/optionsutil")
local Size = require("ui/size") local Size = require("ui/size")
local time = require("ui/time")
local _ = require("gettext") local _ = require("gettext")
local Screen = Device.screen local Screen = Device.screen
local T = require("ffi/util").template local T = require("ffi/util").template
@ -76,7 +76,7 @@ local ReaderView = OverlapGroup:extend{
-- in flipping state -- in flipping state
flipping_visible = false, flipping_visible = false,
-- to ensure periodic flush of settings -- to ensure periodic flush of settings
settings_last_save_btv = nil, settings_last_save_time = nil,
-- might be directly updated by readerpaging/readerrolling when -- might be directly updated by readerpaging/readerrolling when
-- they handle some panning/scrolling, to request "fast" refreshes -- they handle some panning/scrolling, to request "fast" refreshes
currently_scrolling = false, currently_scrolling = false,
@ -1038,17 +1038,17 @@ end
function ReaderView:onReaderReady() function ReaderView:onReaderReady()
self.ui.doc_settings:delSetting("docsettings_reset_done") self.ui.doc_settings:delSetting("docsettings_reset_done")
self.settings_last_save_btv = UIManager:getElapsedTimeSinceBoot() self.settings_last_save_time = UIManager:getElapsedTimeSinceBoot()
end end
function ReaderView:onResume() function ReaderView:onResume()
-- As settings were saved on suspend, reset this on resume, -- As settings were saved on suspend, reset this on resume,
-- as there's no need for a possibly immediate save. -- as there's no need for a possibly immediate save.
self.settings_last_save_btv = UIManager:getElapsedTimeSinceBoot() self.settings_last_save_time = UIManager:getElapsedTimeSinceBoot()
end end
function ReaderView:checkAutoSaveSettings() function ReaderView:checkAutoSaveSettings()
if not self.settings_last_save_btv then -- reader not yet ready if not self.settings_last_save_time then -- reader not yet ready
return return
end end
if G_reader_settings:nilOrFalse("auto_save_settings_interval_minutes") then if G_reader_settings:nilOrFalse("auto_save_settings_interval_minutes") then
@ -1056,11 +1056,11 @@ function ReaderView:checkAutoSaveSettings()
return return
end end
local interval = G_reader_settings:readSetting("auto_save_settings_interval_minutes") local interval_m = G_reader_settings:readSetting("auto_save_settings_interval_minutes")
interval = TimeVal:new{ sec = interval*60, usec = 0 } local interval = time.s(interval_m * 60)
local now_btv = UIManager:getElapsedTimeSinceBoot() local now = UIManager:getElapsedTimeSinceBoot()
if now_btv - self.settings_last_save_btv >= interval then if now - self.settings_last_save_time >= interval then
self.settings_last_save_btv = now_btv self.settings_last_save_time = now
-- I/O, delay until after the pageturn -- I/O, delay until after the pageturn
UIManager:tickAfterNext(function() UIManager:tickAfterNext(function()
self.ui:saveSettings() self.ui:saveSettings()

@ -56,11 +56,11 @@ local ReaderWikipedia = require("apps/reader/modules/readerwikipedia")
local ReaderZooming = require("apps/reader/modules/readerzooming") local ReaderZooming = require("apps/reader/modules/readerzooming")
local Screenshoter = require("ui/widget/screenshoter") local Screenshoter = require("ui/widget/screenshoter")
local SettingsMigration = require("ui/data/settings_migration") local SettingsMigration = require("ui/data/settings_migration")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local ffiUtil = require("ffi/util") local ffiUtil = require("ffi/util")
local lfs = require("libs/libkoreader-lfs") local lfs = require("libs/libkoreader-lfs")
local logger = require("logger") local logger = require("logger")
local time = require("ui/time")
local util = require("util") local util = require("util")
local _ = require("gettext") local _ = require("gettext")
local Screen = require("device").screen local Screen = require("device").screen
@ -290,19 +290,19 @@ function ReaderUI:init()
end end
-- make sure we render document first before calling any callback -- make sure we render document first before calling any callback
self:registerPostInitCallback(function() self:registerPostInitCallback(function()
local start_tv = TimeVal:now() local start_time = time.now()
if not self.document:loadDocument() then if not self.document:loadDocument() then
self:dealWithLoadDocumentFailure() self:dealWithLoadDocumentFailure()
end end
logger.dbg(string.format(" loading took %.3f seconds", TimeVal:getDuration(start_tv))) logger.dbg(string.format(" loading took %.3f seconds", time.to_s(time.since(start_time))))
-- used to read additional settings after the document has been -- used to read additional settings after the document has been
-- loaded (but not rendered yet) -- loaded (but not rendered yet)
self:handleEvent(Event:new("PreRenderDocument", self.doc_settings)) self:handleEvent(Event:new("PreRenderDocument", self.doc_settings))
start_tv = TimeVal:now() start_time = time.now()
self.document:render() self.document:render()
logger.dbg(string.format(" rendering took %.3f seconds", TimeVal:getDuration(start_tv))) logger.dbg(string.format(" rendering took %.3f seconds", time.to_s(time.since(start_time))))
-- Uncomment to output the built DOM (for debugging) -- Uncomment to output the built DOM (for debugging)
-- logger.dbg(self.document:getHTMLFromXPointer(".0", 0x6830)) -- logger.dbg(self.document:getHTMLFromXPointer(".0", 0x6830))

@ -6,7 +6,6 @@ This module defines stubs for common methods.
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
local Geom = require("ui/geometry") local Geom = require("ui/geometry")
local TimeVal = require("ui/timeval")
local logger = require("logger") local logger = require("logger")
local util = require("util") local util = require("util")
local _ = require("gettext") local _ = require("gettext")
@ -71,10 +70,10 @@ local Device = {
canSuspend = yes, canSuspend = yes,
canStandby = no, canStandby = no,
canPowerSaveWhileCharging = no, canPowerSaveWhileCharging = no,
total_standby_tv = TimeVal.zero, -- total time spent in standby total_standby_time = 0, -- total time spent in standby
last_standby_tv = TimeVal.zero, last_standby_time = 0,
total_suspend_tv = TimeVal.zero, -- total time spent in suspend total_suspend_time = 0, -- total time spent in suspend
last_suspend_tv = TimeVal.zero, last_suspend_time = 0,
canReboot = no, canReboot = no,
canPowerOff = no, canPowerOff = no,
canAssociateFileExtensions = no, canAssociateFileExtensions = no,

@ -1,7 +1,7 @@
local UIManager -- will be updated when available local UIManager -- will be updated when available
local Math = require("optmath") local Math = require("optmath")
local TimeVal = require("ui/timeval")
local logger = require("logger") local logger = require("logger")
local time = require("ui/time")
local BasePowerD = { local BasePowerD = {
fl_min = 0, -- min frontlight intensity fl_min = 0, -- min frontlight intensity
fl_max = 10, -- max frontlight intensity fl_max = 10, -- max frontlight intensity
@ -13,8 +13,8 @@ local BasePowerD = {
aux_batt_capacity = 0, -- auxiliary battery capacity aux_batt_capacity = 0, -- auxiliary battery capacity
device = nil, -- device object device = nil, -- device object
last_capacity_pull_time = TimeVal:new{ sec = -61, usec = 0}, -- timestamp of last pull last_capacity_pull_time = time.s(-61), -- timestamp of last pull
last_aux_capacity_pull_time = TimeVal:new{ sec = -61, usec = 0}, -- timestamp of last pull last_aux_capacity_pull_time = time.s(-61), -- timestamp of last pull
is_fl_on = false, -- whether the frontlight is on is_fl_on = false, -- whether the frontlight is on
} }
@ -216,17 +216,17 @@ end
function BasePowerD:getCapacity() function BasePowerD:getCapacity()
-- BasePowerD is loaded before UIManager. -- BasePowerD is loaded before UIManager.
-- Nothing *currently* calls this before UIManager is actually loaded, but future-proof this anyway. -- Nothing *currently* calls this before UIManager is actually loaded, but future-proof this anyway.
local now_btv local now
if UIManager then if UIManager then
now_btv = UIManager:getElapsedTimeSinceBoot() now = UIManager:getElapsedTimeSinceBoot()
else else
-- Add time the device was in standby and suspend -- Add time the device was in standby and suspend
now_btv = TimeVal:now() + self.device.total_standby_tv + self.device.total_suspend_tv now = time.now() + self.device.total_standby_time + self.device.total_suspend_time
end end
if (now_btv - self.last_capacity_pull_time):tonumber() >= 60 then if now - self.last_capacity_pull_time >= time.s(60) then
self.batt_capacity = self:getCapacityHW() self.batt_capacity = self:getCapacityHW()
self.last_capacity_pull_time = now_btv self.last_capacity_pull_time = now
end end
return self.batt_capacity return self.batt_capacity
end end
@ -240,29 +240,29 @@ function BasePowerD:isCharged()
end end
function BasePowerD:getAuxCapacity() function BasePowerD:getAuxCapacity()
local now_btv local now
if UIManager then if UIManager then
now_btv = UIManager:getElapsedTimeSinceBoot() now = UIManager:getElapsedTimeSinceBoot()
else else
-- Add time the device was in standby and suspend -- Add time the device was in standby and suspend
now_btv = TimeVal:now() + self.device.total_standby_tv + self.device.total_suspend_tv now = time.now() + self.device.total_standby_time + self.device.total_suspend_time
end end
if (now_btv - self.last_aux_capacity_pull_time):tonumber() >= 60 then if now - self.last_aux_capacity_pull_time >= time.s(60) then
local aux_batt_capa = self:getAuxCapacityHW() local aux_batt_capa = self:getAuxCapacityHW()
-- If the read failed, don't update our cache, and retry next time. -- If the read failed, don't update our cache, and retry next time.
if aux_batt_capa then if aux_batt_capa then
self.aux_batt_capacity = aux_batt_capa self.aux_batt_capacity = aux_batt_capa
self.last_aux_capacity_pull_time = now_btv self.last_aux_capacity_pull_time = now
end end
end end
return self.aux_batt_capacity return self.aux_batt_capacity
end end
function BasePowerD:invalidateCapacityCache() function BasePowerD:invalidateCapacityCache()
self.last_capacity_pull_time = TimeVal:new{ sec = -61, usec = 0} self.last_capacity_pull_time = time.s(-61)
self.last_aux_capacity_pull_time = TimeVal:new{ sec = -61, usec = 0} self.last_aux_capacity_pull_time = self.last_capacity_pull_time
end end
function BasePowerD:isAuxCharging() function BasePowerD:isAuxCharging()

@ -33,7 +33,7 @@ a touch event should have following format:
id = 46, id = 46,
x = 0, x = 0,
y = 1, y = 1,
timev = TimeVal:new{...}, timev = time.s(123.23),
} }
Don't confuse `tev` with raw evs from kernel, `tev` is built according to ev. Don't confuse `tev` with raw evs from kernel, `tev` is built according to ev.
@ -43,8 +43,8 @@ detection result when you feed a touch release event to it.
--]] --]]
local Geom = require("ui/geometry") local Geom = require("ui/geometry")
local TimeVal = require("ui/timeval")
local logger = require("logger") local logger = require("logger")
local time = require("ui/time")
local util = require("util") local util = require("util")
-- We're going to need some clockid_t constants -- We're going to need some clockid_t constants
@ -52,33 +52,22 @@ local ffi = require("ffi")
local C = ffi.C local C = ffi.C
require("ffi/posix_h") require("ffi/posix_h")
-- default values (all the time parameters are in microseconds) -- default values (time parameters are in milliseconds (ms))
local TAP_INTERVAL = 0 * 1000 local TAP_INTERVAL_MS = 0
local DOUBLE_TAP_INTERVAL = 300 * 1000 local DOUBLE_TAP_INTERVAL_MS = 300
local TWO_FINGER_TAP_DURATION = 300 * 1000 local TWO_FINGER_TAP_DURATION_MS = 300
local HOLD_INTERVAL = 500 * 1000 local HOLD_INTERVAL_MS = 500
local SWIPE_INTERVAL = 900 * 1000 local SWIPE_INTERVAL_MS = 900
-- current values
local ges_tap_interval = G_reader_settings:readSetting("ges_tap_interval") or TAP_INTERVAL
ges_tap_interval = TimeVal:new{ usec = ges_tap_interval }
local ges_double_tap_interval = G_reader_settings:readSetting("ges_double_tap_interval") or DOUBLE_TAP_INTERVAL
ges_double_tap_interval = TimeVal:new{ usec = ges_double_tap_interval }
local ges_two_finger_tap_duration = G_reader_settings:readSetting("ges_two_finger_tap_duration") or TWO_FINGER_TAP_DURATION
ges_two_finger_tap_duration = TimeVal:new{ usec = ges_two_finger_tap_duration }
local ges_hold_interval = G_reader_settings:readSetting("ges_hold_interval") or HOLD_INTERVAL
ges_hold_interval = TimeVal:new{ usec = ges_hold_interval }
local ges_swipe_interval = G_reader_settings:readSetting("ges_swipe_interval") or SWIPE_INTERVAL
ges_swipe_interval = TimeVal:new{ usec = ges_swipe_interval }
local GestureDetector = { local GestureDetector = {
-- must be initialized with the Input singleton class -- must be initialized with the Input singleton class
input = nil, input = nil,
-- default values (accessed for display by plugins/gestures.koplugin) -- default values (accessed for display by plugins/gestures.koplugin)
TAP_INTERVAL = TAP_INTERVAL, TAP_INTERVAL_MS = TAP_INTERVAL_MS,
DOUBLE_TAP_INTERVAL = DOUBLE_TAP_INTERVAL, DOUBLE_TAP_INTERVAL_MS = DOUBLE_TAP_INTERVAL_MS,
TWO_FINGER_TAP_DURATION = TWO_FINGER_TAP_DURATION, TWO_FINGER_TAP_DURATION_MS = TWO_FINGER_TAP_DURATION_MS,
HOLD_INTERVAL = HOLD_INTERVAL, HOLD_INTERVAL_MS = HOLD_INTERVAL_MS,
SWIPE_INTERVAL = SWIPE_INTERVAL, SWIPE_INTERVAL_MS = SWIPE_INTERVAL_MS,
-- pinch/spread direction table -- pinch/spread direction table
DIRECTION_TABLE = { DIRECTION_TABLE = {
east = "horizontal", east = "horizontal",
@ -106,6 +95,14 @@ local GestureDetector = {
last_taps = {}, last_taps = {},
-- for timestamp clocksource detection -- for timestamp clocksource detection
clock_id = nil, clock_id = nil,
-- current values
ges_tap_interval = time.ms(G_reader_settings:readSetting("ges_tap_interval_ms") or TAP_INTERVAL_MS),
ges_double_tap_interval = time.ms(G_reader_settings:readSetting("ges_double_tap_interval_ms")
or DOUBLE_TAP_INTERVAL_MS),
ges_two_finger_tap_duration = time.ms(G_reader_settings:readSetting("ges_two_finger_tap_duration_ms")
or TWO_FINGER_TAP_DURATION_MS),
ges_hold_interval = time.ms(G_reader_settings:readSetting("ges_hold_interval_ms") or HOLD_INTERVAL_MS),
ges_swipe_interval = time.ms(G_reader_settings:readSetting("ges_swipe_interval_ms") or SWIPE_INTERVAL_MS),
} }
function GestureDetector:new(o) function GestureDetector:new(o)
@ -167,38 +164,38 @@ tap2 is the later tap
function GestureDetector:isTapBounce(tap1, tap2, interval) function GestureDetector:isTapBounce(tap1, tap2, interval)
-- NOTE: If time went backwards, make the delta infinite to avoid misdetections, -- NOTE: If time went backwards, make the delta infinite to avoid misdetections,
-- as we can no longer compute a sensible value... -- as we can no longer compute a sensible value...
local tv_diff = tap2.timev - tap1.timev local time_diff = tap2.timev - tap1.timev
if not tv_diff:isPositive() then if time_diff < 0 then
tv_diff = TimeVal.huge time_diff = time.huge
end end
return ( return (
math.abs(tap1.x - tap2.x) < self.SINGLE_TAP_BOUNCE_DISTANCE and math.abs(tap1.x - tap2.x) < self.SINGLE_TAP_BOUNCE_DISTANCE and
math.abs(tap1.y - tap2.y) < self.SINGLE_TAP_BOUNCE_DISTANCE and math.abs(tap1.y - tap2.y) < self.SINGLE_TAP_BOUNCE_DISTANCE and
tv_diff < interval time_diff < interval
) )
end end
function GestureDetector:isDoubleTap(tap1, tap2) function GestureDetector:isDoubleTap(tap1, tap2)
local tv_diff = tap2.timev - tap1.timev local time_diff = tap2.timev - tap1.timev
if not tv_diff:isPositive() then if time_diff < 0 then
tv_diff = TimeVal.huge time_diff = time.huge
end end
return ( return (
math.abs(tap1.x - tap2.x) < self.DOUBLE_TAP_DISTANCE and math.abs(tap1.x - tap2.x) < self.DOUBLE_TAP_DISTANCE and
math.abs(tap1.y - tap2.y) < self.DOUBLE_TAP_DISTANCE and math.abs(tap1.y - tap2.y) < self.DOUBLE_TAP_DISTANCE and
tv_diff < ges_double_tap_interval time_diff < self.ges_double_tap_interval
) )
end end
-- Takes TimeVals as input, not a tev -- Takes times as input, not a tev
function GestureDetector:isHold(t1, t2) function GestureDetector:isHold(time1, time2)
local tv_diff = t2 - t1 local time_diff = time2 - time1
if not tv_diff:isPositive() then if time_diff < 0 then
tv_diff = TimeVal.zero time_diff = 0
end end
-- NOTE: We cheat by not checking a distance because we're only checking that in tapState, -- NOTE: We cheat by not checking a distance because we're only checking that in tapState,
-- which already ensures a stationary finger, by elimination ;). -- which already ensures a stationary finger, by elimination ;).
return tv_diff >= ges_hold_interval return time_diff >= self.ges_hold_interval
end end
function GestureDetector:isTwoFingerTap() function GestureDetector:isTwoFingerTap()
@ -212,21 +209,21 @@ function GestureDetector:isTwoFingerTap()
local x_diff1 = math.abs(self.last_tevs[s2].x - self.first_tevs[s2].x) local x_diff1 = math.abs(self.last_tevs[s2].x - self.first_tevs[s2].x)
local y_diff0 = math.abs(self.last_tevs[s1].y - self.first_tevs[s1].y) local y_diff0 = math.abs(self.last_tevs[s1].y - self.first_tevs[s1].y)
local y_diff1 = math.abs(self.last_tevs[s2].y - self.first_tevs[s2].y) local y_diff1 = math.abs(self.last_tevs[s2].y - self.first_tevs[s2].y)
local tv_diff0 = self.last_tevs[s1].timev - self.first_tevs[s1].timev local time_diff0 = self.last_tevs[s1].timev - self.first_tevs[s1].timev
if not tv_diff0:isPositive() then if time_diff0 < 0 then
tv_diff0 = TimeVal.huge time_diff0 = time.huge
end end
local tv_diff1 = self.last_tevs[s2].timev - self.first_tevs[s2].timev local time_diff1 = self.last_tevs[s2].timev - self.first_tevs[s2].timev
if not tv_diff1:isPositive() then if time_diff1 < 0 then
tv_diff1 = TimeVal.huge time_diff1 = time.huge
end end
return ( return (
x_diff0 < self.TWO_FINGER_TAP_REGION and x_diff0 < self.TWO_FINGER_TAP_REGION and
x_diff1 < self.TWO_FINGER_TAP_REGION and x_diff1 < self.TWO_FINGER_TAP_REGION and
y_diff0 < self.TWO_FINGER_TAP_REGION and y_diff0 < self.TWO_FINGER_TAP_REGION and
y_diff1 < self.TWO_FINGER_TAP_REGION and y_diff1 < self.TWO_FINGER_TAP_REGION and
tv_diff0 < ges_two_finger_tap_duration and time_diff0 < self.ges_two_finger_tap_duration and
tv_diff1 < ges_two_finger_tap_duration time_diff1 < self.ges_two_finger_tap_duration
) )
end end
@ -264,11 +261,11 @@ end
function GestureDetector:isSwipe(slot) function GestureDetector:isSwipe(slot)
if not self.first_tevs[slot] or not self.last_tevs[slot] then return end if not self.first_tevs[slot] or not self.last_tevs[slot] then return end
local tv_diff = self.last_tevs[slot].timev - self.first_tevs[slot].timev local time_diff = self.last_tevs[slot].timev - self.first_tevs[slot].timev
if not tv_diff:isPositive() then if time_diff < 0 then
tv_diff = TimeVal.huge time_diff = time.huge
end end
if tv_diff < ges_swipe_interval then if time_diff < self.ges_swipe_interval then
local x_diff = self.last_tevs[slot].x - self.first_tevs[slot].x local x_diff = self.last_tevs[slot].x - self.first_tevs[slot].x
local y_diff = self.last_tevs[slot].y - self.first_tevs[slot].y local y_diff = self.last_tevs[slot].y - self.first_tevs[slot].y
if x_diff ~= 0 or y_diff ~= 0 then if x_diff ~= 0 or y_diff ~= 0 then
@ -307,34 +304,6 @@ function GestureDetector:clearState(slot)
self.input:clearTimeout(slot, "hold") self.input:clearTimeout(slot, "hold")
end end
function GestureDetector:setNewInterval(type, interval)
if type == "ges_tap_interval" then
ges_tap_interval = TimeVal:new{ usec = interval }
elseif type == "ges_double_tap_interval" then
ges_double_tap_interval = TimeVal:new{ usec = interval }
elseif type == "ges_two_finger_tap_duration" then
ges_two_finger_tap_duration = TimeVal:new{ usec = interval }
elseif type == "ges_hold_interval" then
ges_hold_interval = TimeVal:new{ usec = interval }
elseif type == "ges_swipe_interval" then
ges_swipe_interval = TimeVal:new{ usec = interval }
end
end
function GestureDetector:getInterval(type)
if type == "ges_tap_interval" then
return ges_tap_interval:tousecs()
elseif type == "ges_double_tap_interval" then
return ges_double_tap_interval:tousecs()
elseif type == "ges_two_finger_tap_duration" then
return ges_two_finger_tap_duration:tousecs()
elseif type == "ges_hold_interval" then
return ges_hold_interval:tousecs()
elseif type == "ges_swipe_interval" then
return ges_swipe_interval:tousecs()
end
end
function GestureDetector:clearStates() function GestureDetector:clearStates()
for k, _ in pairs(self.states) do for k, _ in pairs(self.states) do
self:clearState(k) self:clearState(k)
@ -371,10 +340,10 @@ Attempts to figure out which clock source tap events are using...
function GestureDetector:probeClockSource(timev) function GestureDetector:probeClockSource(timev)
-- We'll check if that timestamp is +/- 2.5s away from the three potential clock sources supported by evdev. -- We'll check if that timestamp is +/- 2.5s away from the three potential clock sources supported by evdev.
-- We have bigger issues than this if we're parsing events more than 3s late ;). -- We have bigger issues than this if we're parsing events more than 3s late ;).
local threshold = TimeVal:new{ sec = 2, usec = 500000 } local threshold = time.s(2) + time.ms(500)
-- Start w/ REALTIME, because it's the easiest to detect ;). -- Start w/ REALTIME, because it's the easiest to detect ;).
local realtime = TimeVal:realtime_coarse() local realtime = time.realtime_coarse()
-- clock-threshold <= timev <= clock+threshold -- clock-threshold <= timev <= clock+threshold
if timev >= realtime - threshold and timev <= realtime + threshold then if timev >= realtime - threshold and timev <= realtime + threshold then
self.clock_id = C.CLOCK_REALTIME self.clock_id = C.CLOCK_REALTIME
@ -383,7 +352,7 @@ function GestureDetector:probeClockSource(timev)
end end
-- Then MONOTONIC, as it's (hopefully) more common than BOOTTIME (and also guaranteed to be an usable clock source) -- Then MONOTONIC, as it's (hopefully) more common than BOOTTIME (and also guaranteed to be an usable clock source)
local monotonic = TimeVal:monotonic_coarse() local monotonic = time.monotonic_coarse()
if timev >= monotonic - threshold and timev <= monotonic + threshold then if timev >= monotonic - threshold and timev <= monotonic + threshold then
self.clock_id = C.CLOCK_MONOTONIC self.clock_id = C.CLOCK_MONOTONIC
logger.info("GestureDetector:probeClockSource: Touch event timestamps appear to use CLOCK_MONOTONIC") logger.info("GestureDetector:probeClockSource: Touch event timestamps appear to use CLOCK_MONOTONIC")
@ -391,9 +360,9 @@ function GestureDetector:probeClockSource(timev)
end end
-- Finally, BOOTTIME -- Finally, BOOTTIME
local boottime = TimeVal:boottime() local boottime = time.boottime()
-- NOTE: It was implemented in Linux 2.6.39, so, reject 0, which would mean it's unsupported... -- NOTE: It was implemented in Linux 2.6.39, so, reject 0, which would mean it's unsupported...
if not boottime:isZero() and timev >= boottime - threshold and timev <= boottime + threshold then if not boottime == 0 and timev >= boottime - threshold and timev <= boottime + threshold then
self.clock_id = C.CLOCK_BOOTTIME self.clock_id = C.CLOCK_BOOTTIME
logger.info("GestureDetector:probeClockSource: Touch event timestamps appear to use CLOCK_BOOTTIME") logger.info("GestureDetector:probeClockSource: Touch event timestamps appear to use CLOCK_BOOTTIME")
return return
@ -403,10 +372,10 @@ function GestureDetector:probeClockSource(timev)
self.clock_id = -1 self.clock_id = -1
logger.info("GestureDetector:probeClockSource: Touch event clock source detection was inconclusive") logger.info("GestureDetector:probeClockSource: Touch event clock source detection was inconclusive")
-- Print all all the gory details in debug mode when this happens... -- Print all all the gory details in debug mode when this happens...
logger.dbg("Input frame :", timev:tonumber()) logger.dbg("Input frame :", time.format_time(timev))
logger.dbg("CLOCK_REALTIME :", realtime:tonumber()) logger.dbg("CLOCK_REALTIME :", time.format_time(realtime))
logger.dbg("CLOCK_MONOTONIC:", monotonic:tonumber()) logger.dbg("CLOCK_MONOTONIC:", time.format_time(monotonic))
logger.dbg("CLOCK_BOOTTIME :", boottime:tonumber()) logger.dbg("CLOCK_BOOTTIME :", time.format_time(boottime))
end end
function GestureDetector:getClockSource() function GestureDetector:getClockSource()
@ -498,10 +467,10 @@ function GestureDetector:handleDoubleTap(tev)
} }
-- Tap interval / bounce detection may be tweaked by widget (i.e. VirtualKeyboard) -- Tap interval / bounce detection may be tweaked by widget (i.e. VirtualKeyboard)
local tap_interval = self.input.tap_interval_override or ges_tap_interval local tap_interval = self.input.tap_interval_override or self.ges_tap_interval
-- We do tap bounce detection even when double tap is enabled (so, double tap -- We do tap bounce detection even when double tap is enabled (so, double tap
-- is triggered when: ges_tap_interval <= delay < ges_double_tap_interval) -- is triggered when: ges_tap_interval <= delay < ges_double_tap_interval)
if not tap_interval:isZero() and self.last_taps[slot] ~= nil and self:isTapBounce(self.last_taps[slot], cur_tap, tap_interval) then if tap_interval ~= 0 and self.last_taps[slot] ~= nil and self:isTapBounce(self.last_taps[slot], cur_tap, tap_interval) then
logger.dbg("tap bounce detected in slot", slot, ": ignored") logger.dbg("tap bounce detected in slot", slot, ": ignored")
-- Simply ignore it, and clear state as this is the end of a touch event -- Simply ignore it, and clear state as this is the end of a touch event
-- (this doesn't clear self.last_taps[slot], so a 3rd tap can be detected -- (this doesn't clear self.last_taps[slot], so a 3rd tap can be detected
@ -547,7 +516,7 @@ function GestureDetector:handleDoubleTap(tev)
logger.dbg("single tap detected in slot", slot, ges_ev.pos) logger.dbg("single tap detected in slot", slot, ges_ev.pos)
return ges_ev return ges_ev
end end
end, tev.timev, ges_double_tap_interval) end, tev.timev, self.ges_double_tap_interval)
-- we are already at the end of touch event -- we are already at the end of touch event
-- so reset the state -- so reset the state
self:clearState(slot) self:clearState(slot)
@ -573,7 +542,7 @@ function GestureDetector:handleNonTap(tev)
self.pending_hold_timer[slot] = nil self.pending_hold_timer[slot] = nil
return self:switchState("holdState", tev, true) return self:switchState("holdState", tev, true)
end end
end, tev.timev, ges_hold_interval) end, tev.timev, self.ges_hold_interval)
return { return {
ges = "touch", ges = "touch",
pos = Geom:new{ pos = Geom:new{

@ -7,10 +7,10 @@ local DEBUG = require("dbg")
local Event = require("ui/event") local Event = require("ui/event")
local GestureDetector = require("device/gesturedetector") local GestureDetector = require("device/gesturedetector")
local Key = require("device/key") local Key = require("device/key")
local TimeVal = require("ui/timeval")
local framebuffer = require("ffi/framebuffer") local framebuffer = require("ffi/framebuffer")
local input = require("ffi/input") local input = require("ffi/input")
local logger = require("logger") local logger = require("logger")
local time = require("ui/time")
local _ = require("gettext") local _ = require("gettext")
-- We're going to need a few <linux/input.h> constants... -- We're going to need a few <linux/input.h> constants...
@ -372,7 +372,7 @@ function Input:setTimeout(slot, ges, cb, origin, delay)
if input.setTimer then if input.setTimer then
-- If GestureDetector's clock source probing was inconclusive, do this on the UI timescale instead. -- If GestureDetector's clock source probing was inconclusive, do this on the UI timescale instead.
if clock_id == -1 then if clock_id == -1 then
deadline = TimeVal:now() + delay deadline = time.now() + delay
clock_id = C.CLOCK_MONOTONIC clock_id = C.CLOCK_MONOTONIC
else else
deadline = origin + delay deadline = origin + delay
@ -380,7 +380,8 @@ function Input:setTimeout(slot, ges, cb, origin, delay)
-- What this does is essentially to ask the kernel to wake us up when the timer expires, -- What this does is essentially to ask the kernel to wake us up when the timer expires,
-- instead of ensuring that ourselves via a polling timeout. -- instead of ensuring that ourselves via a polling timeout.
-- This ensures perfect accuracy, and allows it to be computed in the event's own timescale. -- This ensures perfect accuracy, and allows it to be computed in the event's own timescale.
timerfd = input.setTimer(clock_id, deadline.sec, deadline.usec) local sec, usec = time.split_s_us(deadline)
timerfd = input.setTimer(clock_id, sec, usec)
end end
if timerfd then if timerfd then
-- It worked, tweak the table a bit to make it clear the deadline will be handled by the kernel -- It worked, tweak the table a bit to make it clear the deadline will be handled by the kernel
@ -395,7 +396,7 @@ function Input:setTimeout(slot, ges, cb, origin, delay)
else else
-- Otherwise, fudge it by using a current timestamp in the UI's timescale (MONOTONIC). -- Otherwise, fudge it by using a current timestamp in the UI's timescale (MONOTONIC).
-- This isn't the end of the world in practice (c.f., #7415). -- This isn't the end of the world in practice (c.f., #7415).
deadline = TimeVal:now() + delay deadline = time.now() + delay
end end
item.deadline = deadline item.deadline = deadline
end end
@ -709,10 +710,8 @@ function Input:handleTouchEv(ev)
end end
elseif ev.type == C.EV_SYN then elseif ev.type == C.EV_SYN then
if ev.code == C.SYN_REPORT then if ev.code == C.SYN_REPORT then
-- Promote our event's time table to a real TimeVal
setmetatable(ev.time, TimeVal)
for _, MTSlot in ipairs(self.MTSlots) do for _, MTSlot in ipairs(self.MTSlots) do
self:setMtSlot(MTSlot.slot, "timev", ev.time) self:setMtSlot(MTSlot.slot, "timev", time.timeval(ev.time))
end end
-- feed ev in all slots to state machine -- feed ev in all slots to state machine
local touch_ges = self.gesture_detector:feedEvent(self.MTSlots) local touch_ges = self.gesture_detector:feedEvent(self.MTSlots)
@ -773,9 +772,8 @@ function Input:handleTouchEvPhoenix(ev)
end end
elseif ev.type == C.EV_SYN then elseif ev.type == C.EV_SYN then
if ev.code == C.SYN_REPORT then if ev.code == C.SYN_REPORT then
setmetatable(ev.time, TimeVal)
for _, MTSlot in ipairs(self.MTSlots) do for _, MTSlot in ipairs(self.MTSlots) do
self:setMtSlot(MTSlot.slot, "timev", ev.time) self:setMtSlot(MTSlot.slot, "timev", time.timeval(ev.time))
end end
-- feed ev in all slots to state machine -- feed ev in all slots to state machine
local touch_ges = self.gesture_detector:feedEvent(self.MTSlots) local touch_ges = self.gesture_detector:feedEvent(self.MTSlots)
@ -809,9 +807,8 @@ function Input:handleTouchEvLegacy(ev)
end end
elseif ev.type == C.EV_SYN then elseif ev.type == C.EV_SYN then
if ev.code == C.SYN_REPORT then if ev.code == C.SYN_REPORT then
setmetatable(ev.time, TimeVal)
for _, MTSlot in ipairs(self.MTSlots) do for _, MTSlot in ipairs(self.MTSlots) do
self:setMtSlot(MTSlot.slot, "timev", ev.time) self:setMtSlot(MTSlot.slot, "timev", time.timeval(ev.time))
end end
-- feed ev in all slots to state machine -- feed ev in all slots to state machine
@ -1022,8 +1019,8 @@ end
--- Main event handling. --- Main event handling.
-- `now` corresponds to UIManager:getTime() (a TimeVal), and it's just been updated by UIManager. -- `now` corresponds to UIManager:getTime() (an fts time), and it's just been updated by UIManager.
-- `deadline` (a TimeVal) is the absolute deadline imposed by UIManager:handleInput() (a.k.a., our main event loop ^^): -- `deadline` (an fts time) is the absolute deadline imposed by UIManager:handleInput() (a.k.a., our main event loop ^^):
-- it's either nil (meaning block forever waiting for input), or the earliest UIManager deadline (in most cases, that's the next scheduled task, -- it's either nil (meaning block forever waiting for input), or the earliest UIManager deadline (in most cases, that's the next scheduled task,
-- in much less common cases, that's the earliest of UIManager.INPUT_TIMEOUT (currently, only KOSync ever sets it) or UIManager.ZMQ_TIMEOUT if there are pending ZMQs). -- in much less common cases, that's the earliest of UIManager.INPUT_TIMEOUT (currently, only KOSync ever sets it) or UIManager.ZMQ_TIMEOUT if there are pending ZMQs).
function Input:waitEvent(now, deadline) function Input:waitEvent(now, deadline)
@ -1073,18 +1070,19 @@ function Input:waitEvent(now, deadline)
if poll_deadline then if poll_deadline then
-- If we haven't hit that deadline yet, poll until it expires, otherwise, -- If we haven't hit that deadline yet, poll until it expires, otherwise,
-- have select return immediately so that we trip a timeout. -- have select return immediately so that we trip a timeout.
now = now or TimeVal:now() now = now or time.now()
if poll_deadline > now then if poll_deadline > now then
-- Deadline hasn't been blown yet, honor it. -- Deadline hasn't been blown yet, honor it.
poll_timeout = poll_deadline - now poll_timeout = poll_deadline - now
else else
-- We've already blown the deadline: make select return immediately (most likely straight to timeout) -- We've already blown the deadline: make select return immediately (most likely straight to timeout)
poll_timeout = TimeVal.zero poll_timeout = 0
end end
end end
local timerfd local timerfd
ok, ev, timerfd = input.waitForEvent(poll_timeout and poll_timeout.sec, poll_timeout and poll_timeout.usec) local sec, usec = time.split_s_us(poll_timeout)
ok, ev, timerfd = input.waitForEvent(sec, usec)
-- We got an actual input event, go and process it -- We got an actual input event, go and process it
if ok then break end if ok then break end
@ -1102,7 +1100,7 @@ function Input:waitEvent(now, deadline)
-- We're only guaranteed to have blown the timer's deadline -- We're only guaranteed to have blown the timer's deadline
-- when our actual select deadline *was* the timer's! -- when our actual select deadline *was* the timer's!
consume_callback = true consume_callback = true
elseif TimeVal:now() >= self.timer_callbacks[1].deadline then elseif time.now() >= self.timer_callbacks[1].deadline then
-- But if it was a task deadline instead, we to have to check the timer's against the current time, -- But if it was a task deadline instead, we to have to check the timer's against the current time,
-- to double-check whether we blew it or not. -- to double-check whether we blew it or not.
consume_callback = true consume_callback = true
@ -1144,17 +1142,18 @@ function Input:waitEvent(now, deadline)
-- If UIManager put us on deadline, enforce it, otherwise, block forever. -- If UIManager put us on deadline, enforce it, otherwise, block forever.
if deadline then if deadline then
-- Convert that absolute deadline to value relative to *now*, as we may loop multiple times between UI ticks. -- Convert that absolute deadline to value relative to *now*, as we may loop multiple times between UI ticks.
now = now or TimeVal:now() now = now or time.now()
if deadline > now then if deadline > now then
-- Deadline hasn't been blown yet, honor it. -- Deadline hasn't been blown yet, honor it.
poll_timeout = deadline - now poll_timeout = deadline - now
else else
-- Deadline has been blown: make select return immediately. -- Deadline has been blown: make select return immediately.
poll_timeout = TimeVal.zero poll_timeout = 0
end end
end end
ok, ev = input.waitForEvent(poll_timeout and poll_timeout.sec, poll_timeout and poll_timeout.usec) local sec, usec = time.split_s_us(poll_timeout)
ok, ev = input.waitForEvent(sec, usec)
end -- if #timer_callbacks > 0 end -- if #timer_callbacks > 0
-- Handle errors -- Handle errors

@ -824,14 +824,14 @@ function Kobo:standby(max_duration)
self.wakeup_mgr:addTask(max_duration, standby_alarm) self.wakeup_mgr:addTask(max_duration, standby_alarm)
end end
local TimeVal = require("ui/timeval") local time = require("ui/time")
logger.info("Kobo standby: asking to enter standby . . .") logger.info("Kobo standby: asking to enter standby . . .")
local standby_time_tv = TimeVal:boottime_or_realtime_coarse() local standby_time = time.boottime_or_realtime_coarse()
local ret = writeToSys("standby", "/sys/power/state") local ret = writeToSys("standby", "/sys/power/state")
self.last_standby_tv = TimeVal:boottime_or_realtime_coarse() - standby_time_tv self.last_standby_time = time.boottime_or_realtime_coarse() - standby_time
self.total_standby_tv = self.total_standby_tv + self.last_standby_tv self.total_standby_time = self.total_standby_time + self.last_standby_time
if ret then if ret then
logger.info("Kobo standby: zZz zZz zZz zZz... And woke up!") logger.info("Kobo standby: zZz zZz zZz zZz... And woke up!")
@ -925,16 +925,16 @@ function Kobo:suspend()
end end
--]] --]]
local TimeVal = require("ui/timeval") local time = require("ui/time")
logger.info("Kobo suspend: asking for a suspend to RAM . . .") logger.info("Kobo suspend: asking for a suspend to RAM . . .")
local suspend_time_tv = TimeVal:boottime_or_realtime_coarse() local suspend_time = time.boottime_or_realtime_coarse()
ret = writeToSys("mem", "/sys/power/state") ret = writeToSys("mem", "/sys/power/state")
-- NOTE: At this point, we *should* be in suspend to RAM, as such, -- NOTE: At this point, we *should* be in suspend to RAM, as such,
-- execution should only resume on wakeup... -- execution should only resume on wakeup...
self.last_suspend_tv = TimeVal:boottime_or_realtime_coarse() - suspend_time_tv self.last_suspend_time = time.boottime_or_realtime_coarse() - suspend_time
self.total_suspend_tv = self.total_suspend_tv + self.last_suspend_tv self.total_suspend_time = self.total_suspend_time + self.last_suspend_time
if ret then if ret then
logger.info("Kobo suspend: ZzZ ZzZ ZzZ... And woke up!") logger.info("Kobo suspend: ZzZ ZzZ ZzZ... And woke up!")

@ -1,7 +1,7 @@
local Generic = require("device/generic/device") -- <= look at this file! local Generic = require("device/generic/device") -- <= look at this file!
local TimeVal = require("ui/timeval")
local PluginShare = require("pluginshare") local PluginShare = require("pluginshare")
local logger = require("logger") local logger = require("logger")
local time = require("ui/time")
local ffi = require("ffi") local ffi = require("ffi")
local C = ffi.C local C = ffi.C
require("ffi/linux_input_h") require("ffi/linux_input_h")
@ -95,7 +95,7 @@ function Remarkable2:adjustTouchEvent(ev, by)
-- Inject CLOCK_MONOTONIC timestamps at the end of every input frame in order to have consistent gesture detection across input devices. -- Inject CLOCK_MONOTONIC timestamps at the end of every input frame in order to have consistent gesture detection across input devices.
-- c.f., #7536 -- c.f., #7536
if ev.type == C.EV_SYN and ev.code == C.SYN_REPORT then if ev.type == C.EV_SYN and ev.code == C.SYN_REPORT then
ev.time = TimeVal:now() ev.time = time.now()
end end
end end

@ -168,7 +168,6 @@ function Device:init()
event_map = require("device/sdl/event_map_sdl2"), event_map = require("device/sdl/event_map_sdl2"),
handleSdlEv = function(device_input, ev) handleSdlEv = function(device_input, ev)
local Geom = require("ui/geometry") local Geom = require("ui/geometry")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
-- SDL events can remain cdata but are almost completely transparent -- SDL events can remain cdata but are almost completely transparent
@ -192,8 +191,6 @@ function Device:init()
w = 0, h = 0, w = 0, h = 0,
} }
setmetatable(ev.time, TimeVal)
local fake_ges = { local fake_ges = {
ges = "pan", ges = "pan",
distance = 200, distance = 200,

@ -6,13 +6,13 @@ local FontList = require("fontlist")
local Geom = require("ui/geometry") local Geom = require("ui/geometry")
local RenderImage = require("ui/renderimage") local RenderImage = require("ui/renderimage")
local Screen = require("device").screen local Screen = require("device").screen
local TimeVal = require("ui/timeval")
local buffer = require("string.buffer") local buffer = require("string.buffer")
local ffi = require("ffi") local ffi = require("ffi")
local C = ffi.C local C = ffi.C
local lfs = require("libs/libkoreader-lfs") local lfs = require("libs/libkoreader-lfs")
local logger = require("logger") local logger = require("logger")
local lru = require("ffi/lru") local lru = require("ffi/lru")
local time = require("ui/time")
-- engine can be initialized only once, on first document opened -- engine can be initialized only once, on first document opened
local engine_initialized = false local engine_initialized = false
@ -694,16 +694,16 @@ function CreDocument:drawCurrentView(target, x, y, rect, pos)
-- We also honor the current smooth scaling setting, -- We also honor the current smooth scaling setting,
-- as well as the global SW dithering setting. -- as well as the global SW dithering setting.
--local start_tv = TimeVal:now() --local start_time = time.now()
self._drawn_images_count, self._drawn_images_surface_ratio = self._drawn_images_count, self._drawn_images_surface_ratio =
self._document:drawCurrentPage(self.buffer, self.render_color, Screen.night_mode and self._nightmode_images, self._smooth_scaling, Screen.sw_dithering) self._document:drawCurrentPage(self.buffer, self.render_color, Screen.night_mode and self._nightmode_images, self._smooth_scaling, Screen.sw_dithering)
--local end_tv = TimeVal:now() --local end_time = time.now()
--print(string.format("CreDocument:drawCurrentView: Rendering took %9.3f ms", (end_tv - start_tv):tomsecs())) --print(string.format("CreDocument:drawCurrentView: Rendering took %9.3f ms", time.to_ms(end_time - start_time))
--start_tv = TimeVal:now() --start = time.now()
target:blitFrom(self.buffer, x, y, 0, 0, rect.w, rect.h) target:blitFrom(self.buffer, x, y, 0, 0, rect.w, rect.h)
--end_tv = TimeVal:now() --end_time = time.now()
--print(string.format("CreDocument:drawCurrentView: Blitting took %9.3f ms", (end_tv - start_tv):tomsecs())) --print(string.format("CreDocument:drawCurrentView: Blitting took %9.3f ms", time.to_ms(end_time - start_time))
end end
function CreDocument:drawCurrentViewByPos(target, x, y, rect, pos) function CreDocument:drawCurrentViewByPos(target, x, y, rect, pos)
@ -1457,10 +1457,10 @@ function CreDocument:setupCallCache()
-- cache statistics -- cache statistics
self._call_cache_stats = {} self._call_cache_stats = {}
now = function() now = function()
return TimeVal:now() return time.now()
end end
addStatMiss = function(name, starttime, not_cached) addStatMiss = function(name, starttime, not_cached)
local duration = TimeVal:getDuration(starttime) local duration = time.since(starttime)
if not self._call_cache_stats[name] then if not self._call_cache_stats[name] then
self._call_cache_stats[name] = {0, 0.0, 1, duration, not_cached} self._call_cache_stats[name] = {0, 0.0, 1, duration, not_cached}
else else
@ -1470,7 +1470,7 @@ function CreDocument:setupCallCache()
end end
end end
addStatHit = function(name, starttime) addStatHit = function(name, starttime)
local duration = TimeVal:getDuration(starttime) local duration = time.since(starttime)
if not self._call_cache_stats[name] then if not self._call_cache_stats[name] then
self._call_cache_stats[name] = {1, duration, 0, 0.0} self._call_cache_stats[name] = {1, duration, 0, 0.0}
else else

@ -10,16 +10,16 @@
-- able to get there easily. -- able to get there easily.
-------- --------
local TimeVal = require("ui/timeval")
local logger = require("logger") local logger = require("logger")
local util = require("util") local util = require("util")
local time = require("ui/time")
local _ = require("gettext") local _ = require("gettext")
local N_ = _.ngettext local N_ = _.ngettext
local T = require("ffi/util").template local T = require("ffi/util").template
local K = require("frontend/ui/data/keyboardlayouts/ja_keyboard_keys") local K = require("frontend/ui/data/keyboardlayouts/ja_keyboard_keys")
local DEFAULT_KEITAI_TAP_INTERVAL = 2 local DEFAULT_KEITAI_TAP_INTERVAL_S = 2
-- "Keitai input" is an input mode similar to T9 mobile input, where you tap a -- "Keitai input" is an input mode similar to T9 mobile input, where you tap a
-- key to cycle through several candidate characters. The tap interval is how -- key to cycle through several candidate characters. The tap interval is how
@ -28,16 +28,16 @@ local DEFAULT_KEITAI_TAP_INTERVAL = 2
-- information. -- information.
local function getKeitaiTapInterval() local function getKeitaiTapInterval()
return G_reader_settings:readSetting("keyboard_japanese_keitai_tap_interval") or DEFAULT_KEITAI_TAP_INTERVAL return time.s(G_reader_settings:readSetting("keyboard_japanese_keitai_tap_interval", DEFAULT_KEITAI_TAP_INTERVAL_S))
end end
local function setKeitaiTapInterval(interval) local function setKeitaiTapInterval(interval)
G_reader_settings:saveSetting("keyboard_japanese_keitai_tap_interval", interval) G_reader_settings:saveSetting("keyboard_japanese_keitai_tap_interval", time.to_s(interval))
end end
local function exitKeitaiMode(inputbox) local function exitKeitaiMode(inputbox)
logger.dbg("ja_kbd: clearing keitai window last tap tv") logger.dbg("ja_kbd: clearing keitai window last tap tv")
inputbox._ja_last_tap_tv = nil inputbox._ja_last_tap_time = nil
end end
local function wrappedAddChars(inputbox, char) local function wrappedAddChars(inputbox, char)
@ -48,10 +48,10 @@ local function wrappedAddChars(inputbox, char)
-- For keitai buttons, are we still in the tap interval? -- For keitai buttons, are we still in the tap interval?
local within_tap_window local within_tap_window
if keitai_cycle then if keitai_cycle then
if inputbox._ja_last_tap_tv then if inputbox._ja_last_tap_time then
within_tap_window = TimeVal:getDuration(inputbox._ja_last_tap_tv) < getKeitaiTapInterval() within_tap_window = time.since(inputbox._ja_last_tap_time) < getKeitaiTapInterval()
end end
inputbox._ja_last_tap_tv = TimeVal:now() inputbox._ja_last_tap_time = time.now()
else else
-- This is a non-keitai or non-tap key, so break out of keitai window. -- This is a non-keitai or non-tap key, so break out of keitai window.
exitKeitaiMode(inputbox) exitKeitaiMode(inputbox)
@ -113,7 +113,7 @@ local function wrapInputBox(inputbox)
for _, wrapper in ipairs(wrappers) do for _, wrapper in ipairs(wrappers) do
wrapper:revert() wrapper:revert()
end end
inputbox._ja_last_tap_tv = nil inputbox._ja_last_tap_time = nil
inputbox._ja_wrapped = nil inputbox._ja_wrapped = nil
end end
end end
@ -127,7 +127,7 @@ local function genMenuItems(self)
local interval = getKeitaiTapInterval() local interval = getKeitaiTapInterval()
if interval ~= 0 then if interval ~= 0 then
-- @translators Keitai input is a kind of Japanese keyboard input mode (similar to T9 keypad input). See <https://en.wikipedia.org/wiki/Japanese_input_method#Mobile_phones> for more information. -- @translators Keitai input is a kind of Japanese keyboard input mode (similar to T9 keypad input). See <https://en.wikipedia.org/wiki/Japanese_input_method#Mobile_phones> for more information.
return T(N_("Keitai tap interval: %1 second", "Keitai tap interval: %1 seconds", interval), interval) return T(N_("Keitai tap interval: %1 second", "Keitai tap interval: %1 seconds", time.to_s(interval)), time.to_s(interval))
else else
-- @translators Flick and keitai are kinds of Japanese keyboard input modes. See <https://en.wikipedia.org/wiki/Japanese_input_method#Mobile_phones> for more information. -- @translators Flick and keitai are kinds of Japanese keyboard input modes. See <https://en.wikipedia.org/wiki/Japanese_input_method#Mobile_phones> for more information.
return _("Keitai input: disabled (flick-only input)") return _("Keitai input: disabled (flick-only input)")
@ -146,14 +146,14 @@ How long to wait (in seconds) for the next tap when in keitai input mode before
If set to 0, keitai input is disabled entirely and only flick input can be used.]]), If set to 0, keitai input is disabled entirely and only flick input can be used.]]),
width = math.floor(Screen:getWidth() * 0.75), width = math.floor(Screen:getWidth() * 0.75),
value = getKeitaiTapInterval(), value = time.to_s(getKeitaiTapInterval()),
value_min = 0, value_min = 0,
value_max = 10, value_max = 10,
value_step = 1, value_step = 1,
ok_text = _("Set interval"), ok_text = _("Set interval"),
default_value = DEFAULT_KEITAI_TAP_INTERVAL, default_value = DEFAULT_KEITAI_TAP_INTERVAL_S,
callback = function(spin) callback = function(spin)
setKeitaiTapInterval(spin.value) setKeitaiTapInterval(time.s(spin.value))
if touchmenu_instance then touchmenu_instance:updateItems() end if touchmenu_instance then touchmenu_instance:updateItems() end
end, end,
} }

@ -7,7 +7,7 @@ local lfs = require("libs/libkoreader-lfs")
local logger = require("logger") local logger = require("logger")
-- Date at which the last migration snippet was added -- Date at which the last migration snippet was added
local CURRENT_MIGRATION_DATE = 20220205 local CURRENT_MIGRATION_DATE = 20220426
-- Retrieve the date of the previous migration, if any -- Retrieve the date of the previous migration, if any
local last_migration_date = G_reader_settings:readSetting("last_migration_date", 0) local last_migration_date = G_reader_settings:readSetting("last_migration_date", 0)
@ -366,5 +366,27 @@ if last_migration_date < 20220205 then
end end
end end
-- Rename several time storing settings and shift their value to the new meaning
if last_migration_date < 20220426 then
local function migrateSettingsName(old, new, factor)
factor = factor or 1
if G_reader_settings:readSetting(old) then
local value = math.floor(G_reader_settings:readSetting(old) * factor)
G_reader_settings:saveSetting(new, value)
G_reader_settings:delSetting(old)
end
end
migrateSettingsName("ges_tap_interval", "ges_tap_interval_ms", 1e-3)
migrateSettingsName("ges_double_tap_interval", "ges_double_tap_interval_ms", 1e-3)
migrateSettingsName("ges_two_finger_tap_duration", "ges_two_finger_tap_duration_ms", 1e-3)
migrateSettingsName("ges_hold_interval", "ges_hold_interval_ms", 1e-3)
migrateSettingsName("ges_swipe_interval", "ges_swipe_interval_ms", 1e-3)
migrateSettingsName("ges_tap_interval_on_keyboard", "ges_tap_interval_on_keyboard_ms", 1e-3)
migrateSettingsName("device_status_battery_interval", "device_status_battery_interval_minutes")
migrateSettingsName("device_status_memory_interval", "device_status_memory_interval_minutes")
end
-- We're done, store the current migration date -- We're done, store the current migration date
G_reader_settings:saveSetting("last_migration_date", CURRENT_MIGRATION_DATE) G_reader_settings:saveSetting("last_migration_date", CURRENT_MIGRATION_DATE)

@ -1,4 +1,4 @@
local TimeVal = require("ui/timeval") local time = require("ui/time")
local GestureRange = { local GestureRange = {
-- gesture matching type -- gesture matching type
@ -43,8 +43,8 @@ function GestureRange:match(gs)
-- This field sets up rate-limiting (in matches per second). -- This field sets up rate-limiting (in matches per second).
-- It's mostly useful for e-Ink devices with less powerful CPUs -- It's mostly useful for e-Ink devices with less powerful CPUs
-- and screens that cannot handle the amount of gesture events that would otherwise be generated. -- and screens that cannot handle the amount of gesture events that would otherwise be generated.
local last_time = self.last_time or TimeVal.zero local last_time = self.last_time or 0
if gs.time - last_time > TimeVal:new{ usec = 1000000 / self.rate } then if gs.time - last_time > time.s(1 / self.rate) then
self.last_time = gs.time self.last_time = gs.time
else else
return false return false

@ -123,8 +123,8 @@ function NetworkListener:_unscheduleActivityCheck()
if self._last_tx_packets then if self._last_tx_packets then
self._last_tx_packets = nil self._last_tx_packets = nil
end end
if self._activity_check_delay then if self._activity_check_delay_seconds then
self._activity_check_delay = nil self._activity_check_delay_seconds = nil
end end
end end
@ -135,8 +135,8 @@ function NetworkListener:_scheduleActivityCheck()
local tx_packets = NetworkListener:_getTxPackets() local tx_packets = NetworkListener:_getTxPackets()
if self._last_tx_packets and tx_packets then if self._last_tx_packets and tx_packets then
-- Compute noise threshold based on the current delay -- Compute noise threshold based on the current delay
local delay = self._activity_check_delay or default_network_timeout_seconds local delay_seconds = self._activity_check_delay_seconds or default_network_timeout_seconds
local noise_threshold = delay / default_network_timeout_seconds * network_activity_noise_margin local noise_threshold = delay_seconds / default_network_timeout_seconds * network_activity_noise_margin
local delta = tx_packets - self._last_tx_packets local delta = tx_packets - self._last_tx_packets
-- If there was no meaningful activity (+/- a couple packets), kill the Wi-Fi -- If there was no meaningful activity (+/- a couple packets), kill the Wi-Fi
if delta <= noise_threshold then if delta <= noise_threshold then
@ -161,19 +161,19 @@ function NetworkListener:_scheduleActivityCheck()
self._last_tx_packets = tx_packets self._last_tx_packets = tx_packets
-- If it's already been scheduled, increase the delay until we hit the ceiling -- If it's already been scheduled, increase the delay until we hit the ceiling
if self._activity_check_delay then if self._activity_check_delay_seconds then
self._activity_check_delay = self._activity_check_delay + default_network_timeout_seconds self._activity_check_delay_seconds = self._activity_check_delay_seconds + default_network_timeout_seconds
if self._activity_check_delay > max_network_timeout_seconds then if self._activity_check_delay_seconds > max_network_timeout_seconds then
self._activity_check_delay = max_network_timeout_seconds self._activity_check_delay_seconds = max_network_timeout_seconds
end end
else else
self._activity_check_delay = default_network_timeout_seconds self._activity_check_delay_seconds = default_network_timeout_seconds
end end
UIManager:scheduleIn(self._activity_check_delay, self._scheduleActivityCheck, self) UIManager:scheduleIn(self._activity_check_delay_seconds, self._scheduleActivityCheck, self)
self._activity_check_scheduled = true self._activity_check_scheduled = true
logger.dbg("NetworkListener: network activity check scheduled in", self._activity_check_delay, "seconds") logger.dbg("NetworkListener: network activity check scheduled in", self._activity_check_delay_seconds, "seconds")
end end
function NetworkListener:onNetworkConnected() function NetworkListener:onNetworkConnected()
@ -211,5 +211,4 @@ function NetworkListener:onSuspend()
self:onNetworkDisconnected() self:onNetworkDisconnected()
end end
return NetworkListener return NetworkListener

@ -0,0 +1,307 @@
--[[--
A runtime optimized module to compare and do simple arithmetics with fixed point time values (which are called fts in here).
Also implements functions to retrieve time from various system clocks (monotonic, monotonic_coarse, realtime, realtime_coarse, boottime ...).
**Encode:**
Don't store a numerical constant in an fts encoded time. Use the functions provided here!
To convert real world units to an fts, you can use the following functions: time.s(seconds), time.ms(milliseconds), time.us(microseconds).
You can calculate an fts encoded time of 3 s with `time.s(3)`.
Special values: `0` can be used for a zero time and `time.huge` can be used for the longest possible time.
Beware of float encoding precision, though. For instance, take 2.1s: 2.1 cannot be encoded with full precision, so time.s(2.1) would be slightly inaccurate.
(For small values (under 10 secs) the error will be ±1µs, for values below a minute the error will be below ±2µs, for values below an hour the error will be ±100µs.)
When full precision is necessary, use `time.s(2) + time.ms(100)` or `time.s(2) + time.us(100000)` instead.
(For more information about floating-point-representation see: https://stackoverflow.com/questions/3448777/how-to-represent-0-1-in-floating-point-arithmetic-and-decimal)
**Decode:**
You can get the number of seconds in an fts encoded time with `time.to_s(time_fts)`.
You can get the number of milliseconds in an fts encoded time with `time.to_ms(time_fts)`.
You can get the number of microseconds in an fts encoded time with `time.to_us(time_fts)`.
Please be aware, that `time.to_number` is the same as a `time.to_s` with a precision of four decimal places.
**Supported calculations:**
You can add and subtract all fts encoded times, without any problems.
You can multiply or divide fts encoded times by numerical constants. So if you need the half of a time, `time_fts/2` is correct.
A division of two fts encoded times would give you a number. (e.g., `time.s(2.5)/time.s(0.5)` equals `5`).
The functions `math.abs()`, `math.min()`, `math.max()` and `math.huge` will work as expected.
Comparisons (`>`, `>=`, `==`, `<`, `<=` and `~=`) of two fts encoded times work as expected.
If you want a duration form a given time_fts to *now*, `time.since(time_fts)` as a shortcut (or simply use `fts.now - time_fts`) will return an fts encoded time. If you need milliseconds use `time.to_ms(time.since(time_fts))`.
**Unsupported calculations:**
Don't add a numerical constant to an fts time (in the best case, the numerical constant is interpreted as µs).
Don't multiply two fts_encoded times (the position of the comma is wrong).
But please be aware that _all other not explicitly supported_ math on fts encoded times (`math.xxx()`) won't work as expected. (If you really, really need that, you have to shift the position of the comma yourself!)
**Background:**
Numbers in Lua are double float which have a mantissa (precision) of 53 bit (plus sign + exponent)
We won't use the exponent here.
So we can store 2^53 = 9.0072E15 different values. If we use the lower 6 digits for µs, we can store
up to 9.0072E9 seconds.
A year has 365.25*24*3600 = 3.15576E7 s, so we can store up to 285 years (9.0072E9/3.15576E7) with µs precision.
The module has been tested with the fixed point comma at 10^6 (other values might work, but are not really tested).
**Recommendations:**
If the name of a variable implies a time (now, when, until, xxxdeadline, xxxtime, getElapsedTimeSinceBoot, lastxxxtimexxx, ...) we assume this value to be a time (fts encoded).
Other objects which are times (like `last_tap`, `tap_interval_override`, ...) shall be renamed to something like `last_tap_time` (so to make it clear that they are fts encoded).
All other time variables (a handful) get the appropriate suffix `_ms`, `_us`, `_s` (`_m`, `_h`, `_d`) denoting their status as plain Lua numbers and their resolution.
@module time
@usage
local time = require("ui/time")
local start_time = time.now()
-- Do some stuff.
-- You can add and subtract `fts times` objects.
local duration = time.now() - start.fts
-- And convert that object to various more human-readable formats, e.g.,
print(string.format("Stuff took %.3fms", time.to_ms(duration)))
local offset = time.s(100)
print(string.format("Stuff plus 100s took %.3fms", time.to_ms(duration + offset)))
]]
local ffi = require("ffi")
require("ffi/posix_h")
local logger = require("logger")
local C = ffi.C
-- An FTS_PRECISION of 1e6 will give us a µs precision.
local FTS_PRECISION = 1e6
local S2FTS = FTS_PRECISION
local MS2FTS = FTS_PRECISION / 1e3
local US2FTS = FTS_PRECISION / 1e6
local NS2FTS = FTS_PRECISION / 1e9
local FTS2S = 1 / S2FTS
local FTS2MS = 1 / MS2FTS
local FTS2US = 1 / US2FTS
-- Fixed point time
local time = {}
--- Sometimes we need a very large time
time.huge = math.huge
--- Creates a time (fts) from a number in seconds
function time.s(seconds)
return math.floor(seconds * S2FTS)
end
--- Creates a time (fts) from a number in milliseconds
function time.ms(msec)
return math.floor(msec * MS2FTS)
end
--- Creates a time (fts) from a number in microseconds
function time.us(usec)
return math.floor(usec * US2FTS)
end
--- Creates a time (fts) from a structure similar to timeval
function time.timeval(tv)
return tv.sec * S2FTS + tv.usec * US2FTS
end
--- Converts an fts time to a Lua (decimal) number (sec.usecs) (accurate to the ms, rounded to 4 decimal places)
function time.to_number(time_fts)
-- Round to 4 decimal places
return math.floor(time.to_s(time_fts) * 10000 + 0.5) / 10000
end
--- Converts an fts to a Lua (int) number (resolution: 1µs)
function time.to_s(time_fts)
-- Time in seconds with µs precision (without decimal places)
return time_fts * FTS2S
end
--[[-- Converts a fts to a Lua (int) number (resolution: 1ms, rounded).
(Mainly useful when computing a time lapse for benchmarking purposes).
]]
function time.to_ms(time_fts)
-- Time in milliseconds ms (without decimal places)
return math.floor(time_fts * FTS2MS + 0.5)
end
--- Converts an fts to a Lua (int) number (resolution: 1µs, rounded)
function time.to_us(time_fts)
-- Time in microseconds µs (without decimal places)
return math.floor(time_fts * FTS2US + 0.5)
end
--[[-- Compare a past *MONOTONIC* fts time to *now*, returning the elapsed time between the two. (sec.usecs variant)
Returns a Lua (decimal) number (sec.usecs, with decimal places) (accurate to the µs)
]]
function time.since(start_time)
-- Time difference
return time.now() - start_time
end
--- Splits an fts to seconds and microseconds.
-- If argument is nil, returns nil,nil.
function time.split_s_us(time_fts)
if not time_fts then return nil, nil end
local sec = math.floor(time_fts * FTS2S)
local usec = math.floor(time_fts - sec * S2FTS) * FTS2US
-- Seconds and µs
return sec, usec
end
-- ffi object for C.clock_gettime calls
local timespec = ffi.new("struct timespec")
-- We prefer CLOCK_MONOTONIC_COARSE if it's available and has a decent resolution,
-- as we generally don't need nano/micro second precision,
-- and it can be more than twice as fast as CLOCK_MONOTONIC/CLOCK_REALTIME/gettimeofday...
local PREFERRED_MONOTONIC_CLOCKID = C.CLOCK_MONOTONIC
-- Ditto for REALTIME (for :realtime_coarse only, :realtime uses gettimeofday ;)).
local PREFERRED_REALTIME_CLOCKID = C.CLOCK_REALTIME
-- CLOCK_BOOTTIME is only available on Linux 2.6.39+...
local HAVE_BOOTTIME = false
if ffi.os == "Linux" then
-- Unfortunately, it was only implemented in Linux 2.6.32, and we may run on older kernels than that...
-- So, just probe it to see if we can rely on it.
local probe_ts = ffi.new("struct timespec")
if C.clock_getres(C.CLOCK_MONOTONIC_COARSE, probe_ts) == 0 then
-- Now, it usually has a 1ms resolution on modern x86_64 systems,
-- but it only provides a 10ms resolution on all my armv7 devices :/.
if probe_ts.tv_sec == 0 and probe_ts.tv_nsec <= 1000000 then
PREFERRED_MONOTONIC_CLOCKID = C.CLOCK_MONOTONIC_COARSE
end
end
logger.dbg("fts: Preferred MONOTONIC clock source is", PREFERRED_MONOTONIC_CLOCKID == C.CLOCK_MONOTONIC_COARSE and "CLOCK_MONOTONIC_COARSE" or "CLOCK_MONOTONIC")
if C.clock_getres(C.CLOCK_REALTIME_COARSE, probe_ts) == 0 then
if probe_ts.tv_sec == 0 and probe_ts.tv_nsec <= 1000000 then
PREFERRED_REALTIME_CLOCKID = C.CLOCK_REALTIME_COARSE
end
end
logger.dbg("fts: Preferred REALTIME clock source is", PREFERRED_REALTIME_CLOCKID == C.CLOCK_REALTIME_COARSE and "CLOCK_REALTIME_COARSE" or "CLOCK_REALTIME")
if C.clock_getres(C.CLOCK_BOOTTIME, probe_ts) == 0 then
HAVE_BOOTTIME = true
end
logger.dbg("fts: BOOTTIME clock source is", HAVE_BOOTTIME and "supported" or "NOT supported")
probe_ts = nil --luacheck: ignore
end
--[[--
Returns an fts time based on the current wall clock time.
(e.g., gettimeofday / clock_gettime(CLOCK_REALTIME).
This is a simple wrapper around clock_gettime(CLOCK_REALTIME) to get all the niceties of a time.
If you don't need sub-second precision, prefer os.time().
Which means that, yes, this is a fancier POSIX Epoch ;).
@usage
local time = require("ui/time")
local fts_start = time.realtime()
-- Do some stuff.
-- You can add and substract fts times
local fts_duration = time.realtime() - fts_start
@treturn fts fixed point time
]]
function time.realtime()
C.clock_gettime(C.CLOCK_REALTIME, timespec)
-- TIMESPEC_TO_FTS
return tonumber(timespec.tv_sec) * S2FTS + math.floor(tonumber(timespec.tv_nsec) * NS2FTS)
end
--[[--
Returns an fts time based on the current value from the system's MONOTONIC clock source.
(e.g., clock_gettime(CLOCK_MONOTONIC).)
POSIX guarantees that this clock source will *never* go backwards (but it *may* return the same value multiple times).
On Linux, this will not account for time spent with the device in suspend (unlike CLOCK_BOOTTIME).
@treturn fts fixed point time
]]
function time.monotonic()
C.clock_gettime(C.CLOCK_MONOTONIC, timespec)
-- TIMESPEC_TO_FTS
return tonumber(timespec.tv_sec) * S2FTS + math.floor(tonumber(timespec.tv_nsec) * NS2FTS)
end
--- Ditto, but w/ CLOCK_MONOTONIC_COARSE if it's available and has a 1ms resolution or better (uses CLOCK_MONOTONIC otherwise).
function time.monotonic_coarse()
C.clock_gettime(PREFERRED_MONOTONIC_CLOCKID, timespec)
-- TIMESPEC_TO_FTS
return tonumber(timespec.tv_sec) * S2FTS + math.floor(tonumber(timespec.tv_nsec) * NS2FTS)
end
-- Ditto, but w/ CLOCK_REALTIME_COARSE if it's available and has a 1ms resolution or better (uses CLOCK_REALTIME otherwise).
function time.realtime_coarse()
C.clock_gettime(PREFERRED_REALTIME_CLOCKID, timespec)
-- TIMESPEC_TO_FTS
return tonumber(timespec.tv_sec) * S2FTS + math.floor(tonumber(timespec.tv_nsec) * NS2FTS)
end
--- Since CLOCK_BOOTIME may not be supported, we offer a few aliases with automatic fallbacks to MONOTONIC or REALTIME
if HAVE_BOOTTIME then
--- Ditto, but w/ CLOCK_BOOTTIME (will return an fts time set to 0, 0 if the clock source is unsupported, as it's 2.6.39+)
--- Only use it if you *know* it's going to be supported, otherwise, prefer the four following aliases.
function time.boottime()
C.clock_gettime(C.CLOCK_BOOTTIME, timespec)
-- TIMESPEC_TO_FTS
return tonumber(timespec.tv_sec) * S2FTS + math.floor(tonumber(timespec.tv_nsec) * NS2FTS)
end
time.boottime_or_monotonic = time.boottime
time.boottime_or_monotonic_coarse = time.boottime
time.boottime_or_realtime = time.boottime
time.boottime_or_realtime_coarse = time.boottime
else
function time.boottime()
logger.warn("fts: Attemped to call boottime on a platform where it's unsupported!")
return 0
end
time.boottime_or_monotonic = time.monotonic
time.boottime_or_monotonic_coarse = time.monotonic_coarse
time.boottime_or_realtime = time.realtime
time.boottime_or_realtime_coarse = time.realtime_coarse
end
--[[-- Alias for `monotonic_coarse`.
The assumption being anything that requires accurate timestamps expects a monotonic clock source.
This is certainly true for KOReader's UI scheduling.
]]
time.now = time.monotonic_coarse
--- Converts an fts time to a string (seconds with 6 decimal places)
function time.format_time(time_fts)
return string.format("%.06f", time_fts * FTS2S)
end
return time

@ -5,11 +5,11 @@ This module manages widgets.
local Device = require("device") local Device = require("device")
local Event = require("ui/event") local Event = require("ui/event")
local Geom = require("ui/geometry") local Geom = require("ui/geometry")
local TimeVal = require("ui/timeval")
local dbg = require("dbg") local dbg = require("dbg")
local logger = require("logger") local logger = require("logger")
local ffiUtil = require("ffi/util") local ffiUtil = require("ffi/util")
local util = require("util") local util = require("util")
local time = require("ui/time")
local _ = require("gettext") local _ = require("gettext")
local Input = Device.input local Input = Device.input
local Screen = Device.screen local Screen = Device.screen
@ -30,7 +30,7 @@ local UIManager = {
event_handlers = nil, event_handlers = nil,
_running = true, _running = true,
_now = TimeVal:now(), _now = time.now(),
_window_stack = {}, _window_stack = {},
_task_queue = {}, _task_queue = {},
_task_queue_dirty = false, _task_queue_dirty = false,
@ -523,14 +523,14 @@ function UIManager:close(widget, refreshtype, refreshregion, refreshdither)
end end
-- schedule an execution task, task queue is in ascendant order -- schedule an execution task, task queue is in ascendant order
function UIManager:schedule(time, action, ...) function UIManager:schedule(sched_time, action, ...)
local p, s, e = 1, 1, #self._task_queue local p, s, e = 1, 1, #self._task_queue
if e ~= 0 then if e ~= 0 then
-- do a binary insert -- do a binary insert
repeat repeat
p = math.floor(s + (e - s) / 2) p = math.floor((e + s) / 2) -- Not necessary to use (s + (e -s) / 2) here!
local p_time = self._task_queue[p].time local p_time = self._task_queue[p].time
if time > p_time then if sched_time > p_time then
if s == e then if s == e then
p = e + 1 p = e + 1
break break
@ -539,11 +539,11 @@ function UIManager:schedule(time, action, ...)
else else
s = p s = p
end end
elseif time < p_time then elseif sched_time < p_time then
e = p if s == p then
if s == e then
break break
end end
e = p
else else
-- for fairness, it's better to make p+1 is strictly less than -- for fairness, it's better to make p+1 is strictly less than
-- p might want to revisit here in the future -- p might want to revisit here in the future
@ -552,7 +552,7 @@ function UIManager:schedule(time, action, ...)
until e < s until e < s
end end
table.insert(self._task_queue, p, { table.insert(self._task_queue, p, {
time = time, time = sched_time,
action = action, action = action,
argc = select('#', ...), argc = select('#', ...),
args = {...}, args = {...},
@ -560,8 +560,8 @@ function UIManager:schedule(time, action, ...)
self._task_queue_dirty = true self._task_queue_dirty = true
end end
dbg:guard(UIManager, 'schedule', dbg:guard(UIManager, 'schedule',
function(self, time, action) function(self, sched_time, action)
assert(time.sec >= 0, "Only positive time allowed") assert(sched_time >= 0, "Only positive time allowed")
assert(action ~= nil, "No action") assert(action ~= nil, "No action")
end) end)
@ -577,7 +577,7 @@ Schedules a task to be run a certain amount of seconds from now.
function UIManager:scheduleIn(seconds, action, ...) function UIManager:scheduleIn(seconds, action, ...)
-- We might run significantly late inside an UI frame, so we can't use the cached value here. -- We might run significantly late inside an UI frame, so we can't use the cached value here.
-- It would also cause some bad interactions with the way nextTick & co behave. -- It would also cause some bad interactions with the way nextTick & co behave.
local when = TimeVal:now() + TimeVal:fromnumber(seconds) local when = time.now() + time.s(seconds)
self:schedule(when, action, ...) self:schedule(when, action, ...)
end end
dbg:guard(UIManager, 'scheduleIn', dbg:guard(UIManager, 'scheduleIn',
@ -1065,15 +1065,15 @@ function UIManager:discardEvents(set_or_seconds)
-- sometimes > 500ms on some devices/temperatures. -- sometimes > 500ms on some devices/temperatures.
-- So, block for 400ms (to have it displayed) + 400ms -- So, block for 400ms (to have it displayed) + 400ms
-- for user reaction to it -- for user reaction to it
delay = TimeVal:new{ sec = 0, usec = 800000 } delay = time.ms(800)
else else
-- On non-eInk screen, display is usually instantaneous -- On non-eInk screen, display is usually instantaneous
delay = TimeVal:new{ sec = 0, usec = 400000 } delay = time.ms(400)
end end
else -- we expect a number else -- we expect a number
delay = TimeVal:new{ sec = set_or_seconds, usec = 0 } delay = time.s(set_or_seconds)
end end
self._discard_events_till = TimeVal:now() + delay self._discard_events_till = time.now() + delay
end end
--[[-- --[[--
@ -1089,7 +1089,7 @@ function UIManager:sendEvent(event)
-- Ensure discardEvents -- Ensure discardEvents
if self._discard_events_till then if self._discard_events_till then
if TimeVal:now() < self._discard_events_till then if time.now() < self._discard_events_till then
return return
else else
self._discard_events_till = nil self._discard_events_till = nil
@ -1173,10 +1173,10 @@ end
--[[ --[[
function UIManager:getNextTaskTimes(count) function UIManager:getNextTaskTimes(count)
count = count or 1 count = math.min(count or 1, #self._task_queue)
local times = {} local times = {}
for i = 1, math.min(count, #self._task_queue) do for i = 1, count do
times[i] = self._task_queue[i].time - TimeVal:now() times[i] = self._task_queue[i].time - time.now()
end end
return times return times
end end
@ -1184,14 +1184,14 @@ end
function UIManager:getNextTaskTime() function UIManager:getNextTaskTime()
if #self._task_queue > 0 then if #self._task_queue > 0 then
return self._task_queue[1].time - TimeVal:now() return self._task_queue[1].time - time:now()
else else
return nil return nil
end end
end end
function UIManager:_checkTasks() function UIManager:_checkTasks()
self._now = TimeVal:now() self._now = time.now()
local wait_until = nil local wait_until = nil
-- task.action may schedule other events -- task.action may schedule other events
@ -1202,8 +1202,8 @@ function UIManager:_checkTasks()
break break
end end
local next_task = self._task_queue[1] local next_task = self._task_queue[1]
local task_tv = next_task.time or TimeVal.zero local task_time = next_task.time or 0
if task_tv <= self._now then if task_time <= self._now then
-- remove from table -- remove from table
local task = table.remove(self._task_queue, 1) local task = table.remove(self._task_queue, 1)
-- task is pending to be executed right now. do it. -- task is pending to be executed right now. do it.
@ -1213,7 +1213,7 @@ function UIManager:_checkTasks()
else else
-- queue is sorted in ascendant order, safe to assume all items -- queue is sorted in ascendant order, safe to assume all items
-- are future tasks for now -- are future tasks for now
wait_until = next_task.time wait_until = task_time
break break
end end
end end
@ -1222,9 +1222,9 @@ function UIManager:_checkTasks()
end end
--[[-- --[[--
Returns a TimeVal object corresponding to the last UI tick. Returns a time (fts) corresponding to the last tick.
This is essentially a cached TimeVal:now(), computed at the top of every iteration of the main UI loop, This is essentially a cached time.now(), computed at the top of every iteration of the main UI loop,
(right before checking/running scheduled tasks). (right before checking/running scheduled tasks).
This is mainly useful to compute/schedule stuff in the same time scale as the UI loop (i.e., MONOTONIC), This is mainly useful to compute/schedule stuff in the same time scale as the UI loop (i.e., MONOTONIC),
without having to resort to a syscall. without having to resort to a syscall.
@ -1233,7 +1233,7 @@ unless you're blocking the UI for a significant amount of time in a single UI fr
That is to say, its granularity is an UI frame. That is to say, its granularity is an UI frame.
Prefer the appropriate TimeVal method for your needs if you require perfect accuracy or better granularity Prefer the appropriate time function for your needs if you require perfect accuracy or better granularity
(e.g., when you're actually working on the event loop *itself* (UIManager, Input, GestureDetector), (e.g., when you're actually working on the event loop *itself* (UIManager, Input, GestureDetector),
or if you're dealing with intra-frame timers). or if you're dealing with intra-frame timers).
@ -1244,10 +1244,10 @@ function UIManager:getTime()
end end
--[[-- --[[--
Returns a TimeVal object corresponding to the last UI tick plus the time in standby and suspend. Returns a time (fts) corresponding to the last UI tick plus the time in standby.
]] ]]
function UIManager:getElapsedTimeSinceBoot() function UIManager:getElapsedTimeSinceBoot()
return self:getTime() + Device.total_standby_tv + Device.total_suspend_tv return self:getTime() + Device.total_standby_time + Device.total_suspend_time
end end
-- precedence of refresh modes: -- precedence of refresh modes:
@ -1676,7 +1676,7 @@ function UIManager:handleInput()
-- We pass that on as an absolute deadline, not a relative wait time. -- We pass that on as an absolute deadline, not a relative wait time.
if wait_us then if wait_us then
deadline = now + TimeVal:new{ usec = wait_us } deadline = now + time.us(wait_us)
end end
-- If there's a scheduled task pending, that puts an upper bound on how long to wait. -- If there's a scheduled task pending, that puts an upper bound on how long to wait.
@ -1721,7 +1721,6 @@ function UIManager:handleInput()
end end
end end
function UIManager:onRotation() function UIManager:onRotation()
self:setDirty("all", "full") self:setDirty("all", "full")
self:forceRePaint() self:forceRePaint()

@ -18,7 +18,6 @@ local ScrollHtmlWidget = require("ui/widget/scrollhtmlwidget")
local ScrollTextWidget = require("ui/widget/scrolltextwidget") local ScrollTextWidget = require("ui/widget/scrolltextwidget")
local Size = require("ui/size") local Size = require("ui/size")
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
local TimeVal = require("ui/timeval")
local TitleBar = require("ui/widget/titlebar") local TitleBar = require("ui/widget/titlebar")
local Translator = require("ui/translator") local Translator = require("ui/translator")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
@ -32,6 +31,7 @@ local C_ = _.pgettext
local Input = Device.input local Input = Device.input
local Screen = Device.screen local Screen = Device.screen
local T = require("ffi/util").template local T = require("ffi/util").template
local time = require("ui/time")
--[[ --[[
Display quick lookup word definition Display quick lookup word definition
@ -154,7 +154,7 @@ function DictQuickLookup:init()
args = function(text, hold_duration) args = function(text, hold_duration)
-- do this lookup in the same domain (dict/wikipedia) -- do this lookup in the same domain (dict/wikipedia)
local lookup_wikipedia = self.is_wiki local lookup_wikipedia = self.is_wiki
if hold_duration >= TimeVal:new{ sec = 3, usec = 0 } then if hold_duration >= time.s(3) then
-- but allow switching domain with a long hold -- but allow switching domain with a long hold
lookup_wikipedia = not lookup_wikipedia lookup_wikipedia = not lookup_wikipedia
end end

@ -13,13 +13,13 @@ local InputContainer = require("ui/widget/container/inputcontainer")
local LineWidget = require("ui/widget/linewidget") local LineWidget = require("ui/widget/linewidget")
local ScrollHtmlWidget = require("ui/widget/scrollhtmlwidget") local ScrollHtmlWidget = require("ui/widget/scrollhtmlwidget")
local Size = require("ui/size") local Size = require("ui/size")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local VerticalGroup = require("ui/widget/verticalgroup") local VerticalGroup = require("ui/widget/verticalgroup")
local VerticalSpan = require("ui/widget/verticalspan") local VerticalSpan = require("ui/widget/verticalspan")
local _ = require("gettext") local _ = require("gettext")
local Screen = Device.screen local Screen = Device.screen
local T = require("ffi/util").template local T = require("ffi/util").template
local time = require("ui/time")
-- If we wanted to use the default font set for the book, -- If we wanted to use the default font set for the book,
-- we'd need to add a few functions to crengine and cre.cpp -- we'd need to add a few functions to crengine and cre.cpp
@ -195,7 +195,7 @@ function FootnoteWidget:init()
-- callback function when HoldReleaseText is handled as args -- callback function when HoldReleaseText is handled as args
args = function(text, hold_duration) args = function(text, hold_duration)
if self.dialog then if self.dialog then
local lookup_target = hold_duration < TimeVal:new{ sec = 3, usec = 0 } and "LookupWord" or "LookupWikipedia" local lookup_target = hold_duration < time.s(3) and "LookupWord" or "LookupWikipedia"
self.dialog:handleEvent( self.dialog:handleEvent(
Event:new(lookup_target, text) Event:new(lookup_target, text)
) )

@ -15,12 +15,12 @@ local NaturalLight = require("ui/widget/naturallightwidget")
local ProgressWidget = require("ui/widget/progresswidget") local ProgressWidget = require("ui/widget/progresswidget")
local Size = require("ui/size") local Size = require("ui/size")
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
local TimeVal = require("ui/timeval")
local TitleBar = require("ui/widget/titlebar") local TitleBar = require("ui/widget/titlebar")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local VerticalGroup = require("ui/widget/verticalgroup") local VerticalGroup = require("ui/widget/verticalgroup")
local VerticalSpan = require("ui/widget/verticalspan") local VerticalSpan = require("ui/widget/verticalspan")
local WidgetContainer = require("ui/widget/container/widgetcontainer") local WidgetContainer = require("ui/widget/container/widgetcontainer")
local time = require("ui/time")
local _ = require("gettext") local _ = require("gettext")
local Screen = Device.screen local Screen = Device.screen
@ -30,7 +30,7 @@ local FrontLightWidget = FocusManager:new{
-- This should stay active during natural light configuration -- This should stay active during natural light configuration
is_always_active = true, is_always_active = true,
rate = Screen.low_pan_rate and 3 or 30, -- Widget update rate. rate = Screen.low_pan_rate and 3 or 30, -- Widget update rate.
last_time = TimeVal.zero, -- Tracks last update time to prevent update spamming. last_time = 0, -- Tracks last update time to prevent update spamming.
} }
function FrontLightWidget:init() function FrontLightWidget:init()
@ -569,9 +569,9 @@ function FrontLightWidget:onTapProgress(arg, ges_ev)
-- But limit the widget update frequency on E Ink. -- But limit the widget update frequency on E Ink.
if Screen.low_pan_rate then if Screen.low_pan_rate then
local current_time = TimeVal:now() local current_time = time.now()
local last_time = self.last_time or TimeVal.zero local last_time = self.last_time or 0
if current_time - last_time > TimeVal:new{ usec = 1000000 / self.rate } then if current_time - last_time > time.s(1 / self.rate) then
self.last_time = current_time self.last_time = current_time
else else
-- Schedule a final update after we stop panning. -- Schedule a final update after we stop panning.

@ -9,9 +9,9 @@ local GestureRange = require("ui/gesturerange")
local InputContainer = require("ui/widget/container/inputcontainer") local InputContainer = require("ui/widget/container/inputcontainer")
local Mupdf = require("ffi/mupdf") local Mupdf = require("ffi/mupdf")
local Screen = Device.screen local Screen = Device.screen
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local logger = require("logger") local logger = require("logger")
local time = require("ui/time")
local util = require("util") local util = require("util")
local HtmlBoxWidget = InputContainer:new{ local HtmlBoxWidget = InputContainer:new{
@ -21,7 +21,7 @@ local HtmlBoxWidget = InputContainer:new{
page_count = 0, page_count = 0,
page_number = 1, page_number = 1,
hold_start_pos = nil, hold_start_pos = nil,
hold_start_tv = nil, hold_start_time = nil,
html_link_tapped_callback = nil, html_link_tapped_callback = nil,
} }
@ -166,7 +166,7 @@ function HtmlBoxWidget:onHoldStartText(_, ges)
return false -- let event be processed by other widgets return false -- let event be processed by other widgets
end end
self.hold_start_tv = UIManager:getTime() self.hold_start_time = UIManager:getTime()
return true return true
end end
@ -230,7 +230,7 @@ function HtmlBoxWidget:onHoldReleaseText(callback, ges)
return false return false
end end
local hold_duration = TimeVal.now() - self.hold_start_tv local hold_duration = time.now() - self.hold_start_time
local page = self.document:openPage(self.page_number) local page = self.document:openPage(self.page_number)
local lines = page:getPageText() local lines = page:getPageText()

@ -13,10 +13,10 @@ local InputContainer = require("ui/widget/container/inputcontainer")
local RectSpan = require("ui/widget/rectspan") local RectSpan = require("ui/widget/rectspan")
local Size = require("ui/size") local Size = require("ui/size")
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local VerticalGroup = require("ui/widget/verticalgroup") local VerticalGroup = require("ui/widget/verticalgroup")
local Input = Device.input local Input = Device.input
local time = require("ui/time")
local Screen = Device.screen local Screen = Device.screen
local band = bit.band local band = bit.band
@ -168,9 +168,9 @@ function Notification:_cleanShownStack(num)
-- to follow what is happening). -- to follow what is happening).
-- As a sanity check, we also forget those shown for -- As a sanity check, we also forget those shown for
-- more than 30s in case no close event was received. -- more than 30s in case no close event was received.
local expire_tv = UIManager:getTime() - TimeVal:new{ sec = 30, usec = 0 } local expire_time = UIManager:getTime() - time.s(30)
for i=#Notification._nums_shown, 1, -1 do for i=#Notification._nums_shown, 1, -1 do
if Notification._nums_shown[i] and Notification._nums_shown[i] > expire_tv then if Notification._nums_shown[i] and Notification._nums_shown[i] > expire_time then
break -- still shown (or not yet expired) break -- still shown (or not yet expired)
end end
table.remove(Notification._nums_shown, i) table.remove(Notification._nums_shown, i)

@ -24,11 +24,11 @@ local RenderText = require("ui/rendertext")
local RightContainer = require("ui/widget/container/rightcontainer") local RightContainer = require("ui/widget/container/rightcontainer")
local Size = require("ui/size") local Size = require("ui/size")
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local Math = require("optmath") local Math = require("optmath")
local logger = require("logger") local logger = require("logger")
local dbg = require("dbg") local dbg = require("dbg")
local time = require("ui/time")
local util = require("util") local util = require("util")
local Screen = require("device").screen local Screen = require("device").screen
@ -1852,18 +1852,18 @@ function TextBoxWidget:onHoldStartText(_, ges)
-- check coordinates are actually inside our area -- check coordinates are actually inside our area
if self.hold_start_x < 0 or self.hold_start_x > self.dimen.w or if self.hold_start_x < 0 or self.hold_start_x > self.dimen.w or
self.hold_start_y < 0 or self.hold_start_y > self.dimen.h then self.hold_start_y < 0 or self.hold_start_y > self.dimen.h then
self.hold_start_tv = nil -- don't process coming HoldRelease event self.hold_start_time = nil -- don't process coming HoldRelease event
return false -- let event be processed by other widgets return false -- let event be processed by other widgets
end end
self.hold_start_tv = UIManager:getTime() self.hold_start_time = UIManager:getTime()
return true return true
end end
function TextBoxWidget:onHoldPanText(_, ges) function TextBoxWidget:onHoldPanText(_, ges)
-- We don't highlight the currently selected text, but just let this -- We don't highlight the currently selected text, but just let this
-- event pop up if we are not currently selecting text -- event pop up if we are not currently selecting text
if not self.hold_start_tv then if not self.hold_start_time then
return false return false
end end
-- Don't let that event be processed by other widget -- Don't let that event be processed by other widget
@ -1877,7 +1877,7 @@ function TextBoxWidget:onHoldReleaseText(callback, ges)
local hold_end_y = ges.pos.y - self.dimen.y local hold_end_y = ges.pos.y - self.dimen.y
-- check we have seen a HoldStart event -- check we have seen a HoldStart event
if not self.hold_start_tv then if not self.hold_start_time then
return false return false
end end
-- check start and end coordinates are actually inside our area -- check start and end coordinates are actually inside our area
@ -1888,7 +1888,7 @@ function TextBoxWidget:onHoldReleaseText(callback, ges)
return false return false
end end
local hold_duration = TimeVal.now() - self.hold_start_tv local hold_duration = time.now() - self.hold_start_time
-- If page contains an image, check if Hold is on this image and deal -- If page contains an image, check if Hold is on this image and deal
-- with it directly -- with it directly
@ -1950,7 +1950,7 @@ function TextBoxWidget:onHoldReleaseText(callback, ges)
-- a missed start event -- a missed start event
self.hold_start_x = nil self.hold_start_x = nil
self.hold_start_y = nil self.hold_start_y = nil
self.hold_start_tv = nil self.hold_start_time = nil
if self.use_xtext then if self.use_xtext then
-- With xtext and fribidi, words may not be laid out in logical order, -- With xtext and fribidi, words may not be laid out in logical order,
@ -1982,7 +1982,7 @@ function TextBoxWidget:onHoldReleaseText(callback, ges)
-- to consider when looking for word boundaries) -- to consider when looking for word boundaries)
local selected_text = self._xtext:getSelectedWords(sel_start_idx, sel_end_idx, 50) local selected_text = self._xtext:getSelectedWords(sel_start_idx, sel_end_idx, 50)
logger.dbg("onHoldReleaseText (duration:", hold_duration:tonumber(), ") :", logger.dbg("onHoldReleaseText (duration:", time.format_time(hold_duration), ") :",
sel_start_idx, ">", sel_end_idx, "=", selected_text) sel_start_idx, ">", sel_end_idx, "=", selected_text)
callback(selected_text, hold_duration) callback(selected_text, hold_duration)
return true return true
@ -2000,7 +2000,7 @@ function TextBoxWidget:onHoldReleaseText(callback, ges)
end end
local selected_text = table.concat(self.charlist, "", sel_start_idx, sel_end_idx) local selected_text = table.concat(self.charlist, "", sel_start_idx, sel_end_idx)
logger.dbg("onHoldReleaseText (duration:", hold_duration:tonumber(), ") :", sel_start_idx, ">", sel_end_idx, "=", selected_text) logger.dbg("onHoldReleaseText (duration:", time.format_time(hold_duration), ") :", sel_start_idx, ">", sel_end_idx, "=", selected_text)
callback(selected_text, hold_duration) callback(selected_text, hold_duration)
return true return true
end end

@ -14,12 +14,12 @@ local InputContainer = require("ui/widget/container/inputcontainer")
local KeyboardLayoutDialog = require("ui/widget/keyboardlayoutdialog") local KeyboardLayoutDialog = require("ui/widget/keyboardlayoutdialog")
local Size = require("ui/size") local Size = require("ui/size")
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local VerticalGroup = require("ui/widget/verticalgroup") local VerticalGroup = require("ui/widget/verticalgroup")
local VerticalSpan = require("ui/widget/verticalspan") local VerticalSpan = require("ui/widget/verticalspan")
local WidgetContainer = require("ui/widget/container/widgetcontainer") local WidgetContainer = require("ui/widget/container/widgetcontainer")
local logger = require("logger") local logger = require("logger")
local time = require("ui/time")
local util = require("util") local util = require("util")
local Screen = Device.screen local Screen = Device.screen
@ -665,8 +665,7 @@ function VirtualKeyPopup:init()
} }
}, },
} }
self.tap_interval_override = G_reader_settings:readSetting("ges_tap_interval_on_keyboard", 0) self.tap_interval_override = time.ms(G_reader_settings:readSetting("ges_tap_interval_on_keyboard_ms", 0))
self.tap_interval_override = TimeVal:new{ usec = self.tap_interval_override }
if Device:hasKeys() then if Device:hasKeys() then
self.key_events.Close = { {Device.input.group.Back}, doc = "close keyboard" } self.key_events.Close = { {Device.input.group.Back}, doc = "close keyboard" }
@ -787,8 +786,7 @@ function VirtualKeyboard:init()
self.min_layer = keyboard.min_layer self.min_layer = keyboard.min_layer
self.max_layer = keyboard.max_layer self.max_layer = keyboard.max_layer
self:initLayer(self.keyboard_layer) self:initLayer(self.keyboard_layer)
self.tap_interval_override = G_reader_settings:readSetting("ges_tap_interval_on_keyboard", 0) self.tap_interval_override = time.ms(G_reader_settings:readSetting("ges_tap_interval_on_keyboard_ms", 0))
self.tap_interval_override = TimeVal:new{ usec = self.tap_interval_override }
if Device:hasKeys() then if Device:hasKeys() then
self.key_events.Close = { {"Back"}, doc = "close keyboard" } self.key_events.Close = { {"Back"}, doc = "close keyboard" }
end end

@ -13,11 +13,11 @@ end
local Event = require("ui/event") local Event = require("ui/event")
local NetworkMgr = require("ui/network/manager") local NetworkMgr = require("ui/network/manager")
local PluginShare = require("pluginshare") local PluginShare = require("pluginshare")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer") local WidgetContainer = require("ui/widget/container/widgetcontainer")
local logger = require("logger") local logger = require("logger")
local util = require("util") local util = require("util")
local time = require("ui/time")
local _ = require("gettext") local _ = require("gettext")
local Math = require("optmath") local Math = require("optmath")
local T = require("ffi/util").template local T = require("ffi/util").template
@ -32,7 +32,7 @@ local AutoSuspend = WidgetContainer:new{
autoshutdown_timeout_seconds = default_autoshutdown_timeout_seconds, autoshutdown_timeout_seconds = default_autoshutdown_timeout_seconds,
auto_suspend_timeout_seconds = default_auto_suspend_timeout_seconds, auto_suspend_timeout_seconds = default_auto_suspend_timeout_seconds,
auto_standby_timeout_seconds = default_auto_standby_timeout_seconds, auto_standby_timeout_seconds = default_auto_standby_timeout_seconds,
last_action_tv = TimeVal.zero, last_action_time = 0,
is_standby_scheduled = false, is_standby_scheduled = false,
task = nil, task = nil,
standby_task = nil, standby_task = nil,
@ -58,7 +58,7 @@ function AutoSuspend:_schedule(shutdown_only)
return return
end end
local suspend_delay, shutdown_delay local suspend_delay_seconds, shutdown_delay_seconds
local is_charging local is_charging
-- On devices with an auxiliary battery, we only care about the auxiliary battery being charged... -- On devices with an auxiliary battery, we only care about the auxiliary battery being charged...
local powerd = Device:getPowerDevice() local powerd = Device:getPowerDevice()
@ -68,29 +68,29 @@ function AutoSuspend:_schedule(shutdown_only)
is_charging = powerd:isCharging() is_charging = powerd:isCharging()
end end
if PluginShare.pause_auto_suspend or is_charging then if PluginShare.pause_auto_suspend or is_charging then
suspend_delay = self.auto_suspend_timeout_seconds suspend_delay_seconds = self.auto_suspend_timeout_seconds
shutdown_delay = self.autoshutdown_timeout_seconds shutdown_delay_seconds = self.autoshutdown_timeout_seconds
else else
local now_tv = UIManager:getElapsedTimeSinceBoot() local now = UIManager:getElapsedTimeSinceBoot()
suspend_delay = self.auto_suspend_timeout_seconds - (now_tv - self.last_action_tv):tonumber() suspend_delay_seconds = self.auto_suspend_timeout_seconds - time.to_number(now - self.last_action_time)
shutdown_delay = self.autoshutdown_timeout_seconds - (now_tv - self.last_action_tv):tonumber() shutdown_delay_seconds = self.autoshutdown_timeout_seconds - time.to_number(now - self.last_action_time)
end end
-- Try to shutdown first, as we may have been woken up from suspend just for the sole purpose of doing that. -- Try to shutdown first, as we may have been woken up from suspend just for the sole purpose of doing that.
if self:_enabledShutdown() and shutdown_delay <= 0 then if self:_enabledShutdown() and shutdown_delay_seconds <= 0 then
logger.dbg("AutoSuspend: initiating shutdown") logger.dbg("AutoSuspend: initiating shutdown")
UIManager:poweroff_action() UIManager:poweroff_action()
elseif self:_enabled() and suspend_delay <= 0 and not shutdown_only then elseif self:_enabled() and suspend_delay_seconds <= 0 and not shutdown_only then
logger.dbg("AutoSuspend: will suspend the device") logger.dbg("AutoSuspend: will suspend the device")
UIManager:suspend() UIManager:suspend()
else else
if self:_enabled() and not shutdown_only then if self:_enabled() and not shutdown_only then
logger.dbg("AutoSuspend: scheduling next suspend check in", suspend_delay) logger.dbg("AutoSuspend: scheduling next suspend check in", suspend_delay_seconds)
UIManager:scheduleIn(suspend_delay, self.task) UIManager:scheduleIn(suspend_delay_seconds, self.task)
end end
if self:_enabledShutdown() then if self:_enabledShutdown() then
logger.dbg("AutoSuspend: scheduling next shutdown check in", shutdown_delay) logger.dbg("AutoSuspend: scheduling next shutdown check in", shutdown_delay_seconds)
UIManager:scheduleIn(shutdown_delay, self.task) UIManager:scheduleIn(shutdown_delay_seconds, self.task)
end end
end end
end end
@ -104,14 +104,14 @@ end
function AutoSuspend:_start() function AutoSuspend:_start()
if self:_enabled() or self:_enabledShutdown() then if self:_enabled() or self:_enabledShutdown() then
logger.dbg("AutoSuspend: start suspend/shutdown timer at", self.last_action_tv:tonumber()) logger.dbg("AutoSuspend: start suspend/shutdown timer at", time.format_time(self.last_action_time))
self:_schedule() self:_schedule()
end end
end end
function AutoSuspend:_start_standby() function AutoSuspend:_start_standby()
if self:_enabledStandby() then if self:_enabledStandby() then
logger.dbg("AutoSuspend: start standby timer at", self.last_action_tv:tonumber()) logger.dbg("AutoSuspend: start standby timer at", time.format_time(self.last_action_time))
self:_schedule_standby() self:_schedule_standby()
end end
end end
@ -119,7 +119,7 @@ end
-- Variant that only re-engages the shutdown timer for onUnexpectedWakeupLimit -- Variant that only re-engages the shutdown timer for onUnexpectedWakeupLimit
function AutoSuspend:_restart() function AutoSuspend:_restart()
if self:_enabledShutdown() then if self:_enabledShutdown() then
logger.dbg("AutoSuspend: restart shutdown timer at", self.last_action_tv:tonumber()) logger.dbg("AutoSuspend: restart shutdown timer at", time.format_time(self.last_action_time))
self:_schedule(true) self:_schedule(true)
end end
end end
@ -161,7 +161,7 @@ function AutoSuspend:init()
-- Make sure we only have an AllowStandby handler when we actually want one... -- Make sure we only have an AllowStandby handler when we actually want one...
self:toggleStandbyHandler(self:_enabledStandby()) self:toggleStandbyHandler(self:_enabledStandby())
self.last_action_tv = UIManager:getElapsedTimeSinceBoot() self.last_action_time = UIManager:getElapsedTimeSinceBoot()
self:_start() self:_start()
self:_start_standby() self:_start_standby()
@ -185,7 +185,7 @@ end
function AutoSuspend:onInputEvent() function AutoSuspend:onInputEvent()
logger.dbg("AutoSuspend: onInputEvent") logger.dbg("AutoSuspend: onInputEvent")
self.last_action_tv = UIManager:getElapsedTimeSinceBoot() self.last_action_time = UIManager:getElapsedTimeSinceBoot()
end end
function AutoSuspend:_unschedule_standby() function AutoSuspend:_unschedule_standby()
@ -219,41 +219,41 @@ function AutoSuspend:_schedule_standby()
end end
-- When we're in a state where entering suspend is undesirable, we simply postpone the check by the full delay. -- When we're in a state where entering suspend is undesirable, we simply postpone the check by the full delay.
local standby_delay local standby_delay_seconds
if NetworkMgr:isWifiOn() then if NetworkMgr:isWifiOn() then
-- Don't enter standby if wifi is on, as this will break in fun and interesting ways (from Wi-Fi issues to kernel deadlocks). -- Don't enter standby if wifi is on, as this will break in fun and interesting ways (from Wi-Fi issues to kernel deadlocks).
--logger.dbg("AutoSuspend: WiFi is on, delaying standby") --logger.dbg("AutoSuspend: WiFi is on, delaying standby")
standby_delay = self.auto_standby_timeout_seconds standby_delay_seconds = self.auto_standby_timeout_seconds
elseif Device.powerd:isCharging() and not Device:canPowerSaveWhileCharging() then elseif Device.powerd:isCharging() and not Device:canPowerSaveWhileCharging() then
-- Don't enter standby when charging on devices where charging prevents entering low power states. -- Don't enter standby when charging on devices where charging prevents entering low power states.
-- NOTE: Minor simplification here, we currently don't do the hasAuxBattery dance like in _schedule, -- NOTE: Minor simplification here, we currently don't do the hasAuxBattery dance like in _schedule,
-- because all the hasAuxBattery devices can currently enter PM states while charging ;). -- because all the hasAuxBattery devices can currently enter PM states while charging ;).
--logger.dbg("AutoSuspend: charging, delaying standby") --logger.dbg("AutoSuspend: charging, delaying standby")
standby_delay = self.auto_standby_timeout_seconds standby_delay_seconds = self.auto_standby_timeout_seconds
else else
local now_tv = UIManager:getElapsedTimeSinceBoot() local now = UIManager:getElapsedTimeSinceBoot()
standby_delay = self.auto_standby_timeout_seconds - (now_tv - self.last_action_tv):tonumber() standby_delay_seconds = self.auto_standby_timeout_seconds - time.to_number(now - self.last_action_time)
-- If we blow past the deadline on the first call of a scheduling cycle, -- If we blow past the deadline on the first call of a scheduling cycle,
-- make sure we don't go straight to allowStandby, as we haven't called preventStandby yet... -- make sure we don't go straight to allowStandby, as we haven't called preventStandby yet...
if not self.is_standby_scheduled and standby_delay <= 0 then if not self.is_standby_scheduled and standby_delay_seconds <= 0 then
-- If this happens, it means we hit LeaveStandby or Resume *before* consuming new input events, -- If this happens, it means we hit LeaveStandby or Resume *before* consuming new input events,
-- e.g., if there weren't any input events at all (woken up by an alarm), -- e.g., if there weren't any input events at all (woken up by an alarm),
-- or if the only input events we consumed did not trigger an InputEvent event (woken up by gyro events), -- or if the only input events we consumed did not trigger an InputEvent event (woken up by gyro events),
-- meaning self.last_action_tv is further in the past than it ought to. -- meaning self.last_action_time is further in the past than it ought to.
-- Delay by the full amount to avoid further bad scheduling interactions. -- Delay by the full amount to avoid further bad scheduling interactions.
standby_delay = self.auto_standby_timeout_seconds standby_delay_seconds = self.auto_standby_timeout_seconds
end end
end end
if standby_delay <= 0 then if standby_delay_seconds <= 0 then
-- We blew the deadline, tell UIManager we're ready to enter standby -- We blew the deadline, tell UIManager we're ready to enter standby
self:allowStandby() self:allowStandby()
else else
-- Reschedule standby for the full or remaining delay -- Reschedule standby for the full or remaining delay
-- NOTE: This is fairly chatty, given the low delays, but really helpful nonetheless... :/ -- NOTE: This is fairly chatty, given the low delays, but really helpful nonetheless... :/
logger.dbg("AutoSuspend: scheduling next standby check in", standby_delay) logger.dbg("AutoSuspend: scheduling next standby check in", standby_delay_seconds)
UIManager:scheduleIn(standby_delay, self.standby_task) UIManager:scheduleIn(standby_delay_seconds, self.standby_task)
-- Prevent standby until we actually blow the deadline -- Prevent standby until we actually blow the deadline
if not self.is_standby_scheduled then if not self.is_standby_scheduled then
@ -318,7 +318,7 @@ function AutoSuspend:onResume()
end end
-- Unschedule in case we tripped onUnexpectedWakeupLimit first... -- Unschedule in case we tripped onUnexpectedWakeupLimit first...
self:_unschedule() self:_unschedule()
-- We should always follow an InputEvent, so last_action_tv is already up to date :). -- We should always follow an InputEvent, so last_action_time is already up to date :).
self:_start() self:_start()
self:_unschedule_standby() self:_unschedule_standby()
self:_start_standby() self:_start_standby()
@ -391,13 +391,13 @@ function AutoSuspend:pickTimeoutValue(touchmenu_instance, title, info, setting,
ok_text = _("Set timeout"), ok_text = _("Set timeout"),
title_text = title, title_text = title,
info_text = info, info_text = info,
callback = function(time) callback = function(spinner)
if time_scale == 2 then if time_scale == 2 then
self[setting] = (time.hour * 24 + time.min) * 3600 self[setting] = (spinner.hour * 24 + spinner.min) * 3600
elseif time_scale == 1 then elseif time_scale == 1 then
self[setting] = time.hour * 3600 + time.min * 60 self[setting] = spinner.hour * 3600 + spinner.min * 60
else else
self[setting] = time.hour * 60 + time.min self[setting] = spinner.hour * 60 + spinner.min
end end
self[setting] = Math.clamp(self[setting], range[1], range[2]) self[setting] = Math.clamp(self[setting], range[1], range[2])
G_reader_settings:saveSetting(setting, self[setting]) G_reader_settings:saveSetting(setting, self[setting])
@ -571,7 +571,7 @@ function AutoSuspend:AllowStandbyHandler()
if next_task_time then if next_task_time then
-- Wake up slightly after the formerly scheduled event, -- Wake up slightly after the formerly scheduled event,
-- to avoid resheduling the same function after a fraction of a second again (e.g. don't draw footer twice). -- to avoid resheduling the same function after a fraction of a second again (e.g. don't draw footer twice).
wake_in = math.floor(next_task_time:tonumber()) + 1 wake_in = math.floor(time.to_number(next_task_time)) + 1
else else
wake_in = math.huge wake_in = math.huge
end end
@ -583,12 +583,12 @@ function AutoSuspend:AllowStandbyHandler()
-- This obviously needs a matching implementation in Device, the canonical one being Kobo. -- This obviously needs a matching implementation in Device, the canonical one being Kobo.
Device:standby(wake_in) Device:standby(wake_in)
logger.dbg("AutoSuspend: left standby after", Device.last_standby_tv:tonumber(), "s") logger.dbg("AutoSuspend: left standby after", time.format_time(Device.last_standby_time), "s")
-- We delay the LeaveStandby event (our onLeaveStandby handler is responsible for rescheduling everything properly), -- We delay the LeaveStandby event (our onLeaveStandby handler is responsible for rescheduling everything properly),
-- to make sure UIManager will consume the input events that woke us up first -- to make sure UIManager will consume the input events that woke us up first
-- (in case we were woken up by user input, as opposed to an rtc wake alarm)! -- (in case we were woken up by user input, as opposed to an rtc wake alarm)!
-- (This ensures we'll use an up to date last_action_tv, and that it only ever gets updated from *user* input). -- (This ensures we'll use an up to date last_action_time, and that it only ever gets updated from *user* input).
-- NOTE: UIManager consumes scheduled tasks before input events, which is why we can't use nextTick. -- NOTE: UIManager consumes scheduled tasks before input events, which is why we can't use nextTick.
UIManager:tickAfterNext(self.leave_standby_task) UIManager:tickAfterNext(self.leave_standby_task)
end end

@ -1,10 +1,10 @@
local Device = require("device") local Device = require("device")
local Event = require("ui/event") local Event = require("ui/event")
local PluginShare = require("pluginshare") local PluginShare = require("pluginshare")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer") local WidgetContainer = require("ui/widget/container/widgetcontainer")
local logger = require("logger") local logger = require("logger")
local time = require("ui/time")
local _ = require("gettext") local _ = require("gettext")
local T = require("ffi/util").template local T = require("ffi/util").template
@ -14,7 +14,7 @@ local AutoTurn = WidgetContainer:new{
autoturn_sec = 0, autoturn_sec = 0,
autoturn_distance = 1, autoturn_distance = 1,
enabled = false, enabled = false,
last_action_tv = TimeVal.zero, last_action_time = 0,
task = nil, task = nil,
} }
@ -28,21 +28,21 @@ function AutoTurn:_schedule()
return return
end end
local delay = self.last_action_tv + TimeVal:new{ sec = self.autoturn_sec, usec = 0 } - UIManager:getTime() local delay = self.last_action_time + time.s(self.autoturn_sec) - UIManager:getTime()
delay = delay:tonumber()
if delay <= 0 then if delay <= 0 then
if UIManager:getTopWidget() == "ReaderUI" then if UIManager:getTopWidget() == "ReaderUI" then
logger.dbg("AutoTurn: go to next page") logger.dbg("AutoTurn: go to next page")
self.ui:handleEvent(Event:new("GotoViewRel", self.autoturn_distance)) self.ui:handleEvent(Event:new("GotoViewRel", self.autoturn_distance))
self.last_action_tv = UIManager:getTime() self.last_action_time = UIManager:getTime()
end end
logger.dbg("AutoTurn: schedule in", self.autoturn_sec) logger.dbg("AutoTurn: schedule in", self.autoturn_sec)
UIManager:scheduleIn(self.autoturn_sec, self.task) UIManager:scheduleIn(self.autoturn_sec, self.task)
self.scheduled = true self.scheduled = true
else else
logger.dbg("AutoTurn: schedule in", delay) local delay_s = time.to_number(delay)
UIManager:scheduleIn(delay, self.task) logger.dbg("AutoTurn: schedule in", delay_s, "s")
UIManager:scheduleIn(delay_s, self.task)
self.scheduled = true self.scheduled = true
end end
end end
@ -58,10 +58,10 @@ end
function AutoTurn:_start() function AutoTurn:_start()
if self:_enabled() then if self:_enabled() then
local now_tv = UIManager:getTime() local now = UIManager:getTime()
logger.dbg("AutoTurn: start at", now_tv:tonumber()) logger.dbg("AutoTurn: start at", time.format_time(now))
PluginShare.pause_auto_suspend = true PluginShare.pause_auto_suspend = true
self.last_action_tv = now_tv self.last_action_time = now
self:_schedule() self:_schedule()
local text local text
@ -107,7 +107,7 @@ end
function AutoTurn:onInputEvent() function AutoTurn:onInputEvent()
logger.dbg("AutoTurn: onInputEvent") logger.dbg("AutoTurn: onInputEvent")
self.last_action_tv = UIManager:getTime() self.last_action_time = UIManager:getTime()
end end
function AutoTurn:onEnterStandby() function AutoTurn:onEnterStandby()
@ -122,9 +122,9 @@ function AutoTurn:onSuspend()
end end
function AutoTurn:_onLeaveStandby() function AutoTurn:_onLeaveStandby()
self.last_action_tv = self.last_action_tv - Device.last_standby_tv self.last_action_time = self.last_action_time - Device.last_standby_time
-- We messed with last_action_tv, so a complete reschedule has to be done. -- We messed with last_action_time, so a complete reschedule has to be done.
if self:_enabled() then if self:_enabled() then
self:_unschedule() self:_unschedule()
self:_schedule() self:_schedule()

@ -1,6 +1,6 @@
local logger = require("logger") local logger = require("logger")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local time = require("ui/time")
local CommandRunner = { local CommandRunner = {
pio = nil, pio = nil,
@ -37,7 +37,7 @@ function CommandRunner:start(job)
assert(self.pio == nil) assert(self.pio == nil)
assert(self.job == nil) assert(self.job == nil)
self.job = job self.job = job
self.job.start_tv = UIManager:getTime() self.job.start_time = UIManager:getTime()
assert(type(self.job.executable) == "string") assert(type(self.job.executable) == "string")
local command = self:createEnvironment() .. " " .. local command = self:createEnvironment() .. " " ..
"sh plugins/backgroundrunner.koplugin/luawrapper.sh " .. "sh plugins/backgroundrunner.koplugin/luawrapper.sh " ..
@ -77,7 +77,7 @@ function CommandRunner:poll()
UIManager:allowStandby() UIManager:allowStandby()
self.pio:close() self.pio:close()
self.pio = nil self.pio = nil
self.job.end_tv = TimeVal:now() self.job.end_time = time.now()
local job = self.job local job = self.job
self.job = nil self.job = nil
return job return job

@ -9,10 +9,10 @@ end
local CommandRunner = require("commandrunner") local CommandRunner = require("commandrunner")
local PluginShare = require("pluginshare") local PluginShare = require("pluginshare")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer") local WidgetContainer = require("ui/widget/container/widgetcontainer")
local logger = require("logger") local logger = require("logger")
local time = require("ui/time")
local _ = require("gettext") local _ = require("gettext")
-- BackgroundRunner is an experimental feature to execute non-critical jobs in -- BackgroundRunner is an experimental feature to execute non-critical jobs in
@ -72,9 +72,9 @@ local _ = require("gettext")
-- bad_command: boolean, whether the command is not found. Not available for -- bad_command: boolean, whether the command is not found. Not available for
-- function executable. -- function executable.
-- blocked: boolean, whether the job is blocked. -- blocked: boolean, whether the job is blocked.
-- start_tv: number, the TimeVal when the job was started. -- start_time: number, the time (fts) when the job was started.
-- end_tv: number, the TimeVal when the job was stopped. -- end_time: number, the time (fts) when the job was stopped.
-- insert_tv: number, the TimeVal when the job was inserted into queue. -- insert_time: number, the time (fts) when the job was inserted into queue.
-- (All of them in the monotonic time scale, like the main event loop & task queue). -- (All of them in the monotonic time scale, like the main event loop & task queue).
local BackgroundRunner = { local BackgroundRunner = {
@ -117,9 +117,9 @@ end
function BackgroundRunner:_finishJob(job) function BackgroundRunner:_finishJob(job)
assert(self ~= nil) assert(self ~= nil)
if type(job.executable) == "function" then if type(job.executable) == "function" then
local tv_diff = job.end_tv - job.start_tv local time_diff = job.end_time - job.start_time
local threshold = TimeVal:new{ sec = 1, usec = 0 } local threshold = time.s(1)
job.timeout = (tv_diff > threshold) job.timeout = (time_diff > threshold)
end end
job.blocked = job.timeout job.blocked = job.timeout
if not job.blocked and self:_shouldRepeat(job) then if not job.blocked and self:_shouldRepeat(job) then
@ -141,7 +141,7 @@ function BackgroundRunner:_executeJob(job)
CommandRunner:start(job) CommandRunner:start(job)
return true return true
elseif type(job.executable) == "function" then elseif type(job.executable) == "function" then
job.start_tv = UIManager:getTime() job.start_time = UIManager:getTime()
local status, err = pcall(job.executable) local status, err = pcall(job.executable)
if status then if status then
job.result = 0 job.result = 0
@ -149,7 +149,7 @@ function BackgroundRunner:_executeJob(job)
job.result = 1 job.result = 1
job.exception = err job.exception = err
end end
job.end_tv = TimeVal:now() job.end_time = time.now()
self:_finishJob(job) self:_finishJob(job)
return true return true
else else
@ -176,10 +176,10 @@ function BackgroundRunner:_execute()
local round = 0 local round = 0
while #self.jobs > 0 do while #self.jobs > 0 do
local job = table.remove(self.jobs, 1) local job = table.remove(self.jobs, 1)
if job.insert_tv == nil then if job.insert_time == nil then
-- Jobs are first inserted to jobs table from external users. -- Jobs are first inserted to jobs table from external users.
-- So they may not have an insert field. -- So they may not have an insert field.
job.insert_tv = UIManager:getTime() job.insert_time = UIManager:getTime()
end end
local should_execute = false local should_execute = false
local should_ignore = false local should_ignore = false
@ -192,7 +192,7 @@ function BackgroundRunner:_execute()
end end
elseif type(job.when) == "number" then elseif type(job.when) == "number" then
if job.when >= 0 then if job.when >= 0 then
should_execute = ((UIManager:getTime() - job.insert_tv) >= TimeVal:fromnumber(job.when)) should_execute = (UIManager:getTime() - job.insert_time >= time.s(job.when))
else else
should_ignore = true should_ignore = true
end end
@ -253,7 +253,7 @@ end
function BackgroundRunner:_insert(job) function BackgroundRunner:_insert(job)
assert(self ~= nil) assert(self ~= nil)
job.insert_tv = UIManager:getTime() job.insert_time = UIManager:getTime()
table.insert(self.jobs, job) table.insert(self.jobs, job)
end end

@ -8,12 +8,12 @@ of storing it.
@module koplugin.calibre.metadata @module koplugin.calibre.metadata
--]]-- --]]--
local TimeVal = require("ui/timeval")
local lfs = require("libs/libkoreader-lfs") local lfs = require("libs/libkoreader-lfs")
local rapidjson = require("rapidjson") local rapidjson = require("rapidjson")
local logger = require("logger") local logger = require("logger")
local parser = require("parser") local parser = require("parser")
local util = require("util") local util = require("util")
local time = require("ui/time")
local used_metadata = { local used_metadata = {
"uuid", "uuid",
@ -242,7 +242,7 @@ end
function CalibreMetadata:init(dir, is_search) function CalibreMetadata:init(dir, is_search)
if not dir then return end if not dir then return end
local start = TimeVal:now() local start_time = time.now()
self.path = dir self.path = dir
local ok_meta, ok_drive, file_meta, file_drive = findCalibreFiles(dir) local ok_meta, ok_drive, file_meta, file_drive = findCalibreFiles(dir)
self.driveinfo = file_drive self.driveinfo = file_drive
@ -261,12 +261,12 @@ function CalibreMetadata:init(dir, is_search)
if is_search then if is_search then
self:cleanUnused(is_search) self:cleanUnused(is_search)
msg = string.format("(search) in %.3f milliseconds: %d books", msg = string.format("(search) in %.3f milliseconds: %d books",
TimeVal:getDurationMs(start), #self.books) time.to_ms(time.since(start_time())), #self.books)
else else
local deleted_count = self:prune() local deleted_count = self:prune()
self:cleanUnused() self:cleanUnused()
msg = string.format("in %.3f milliseconds: %d books. %d pruned", msg = string.format("in %.3f milliseconds: %d books. %d pruned",
TimeVal:getDurationMs(start), #self.books, deleted_count) time.to_ms(time.since(start_time())), #self.books, deleted_count)
end end
logger.info(string.format("calibre info loaded from disk %s", msg)) logger.info(string.format("calibre info loaded from disk %s", msg))
return true return true

@ -13,12 +13,12 @@ local InputContainer = require("ui/widget/container/inputcontainer")
local Menu = require("ui/widget/menu") local Menu = require("ui/widget/menu")
local Persist = require("persist") local Persist = require("persist")
local Screen = require("device").screen local Screen = require("device").screen
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local lfs = require("libs/libkoreader-lfs") local lfs = require("libs/libkoreader-lfs")
local logger = require("logger") local logger = require("logger")
local rapidjson = require("rapidjson") local rapidjson = require("rapidjson")
local util = require("util") local util = require("util")
local time = require("ui/time")
local _ = require("gettext") local _ = require("gettext")
local T = require("ffi/util").template local T = require("ffi/util").template
@ -325,10 +325,10 @@ function CalibreSearch:find(option)
end end
-- measure time elapsed searching -- measure time elapsed searching
local start = TimeVal:now() local start_time = time.now()
self:browse(option) self:browse(option)
logger.info(string.format("search done in %.3f milliseconds (%s, %s, %s, %s, %s)", logger.info(string.format("search done in %.3f milliseconds (%s, %s, %s, %s, %s)",
TimeVal:getDurationMs(start), time.to_ms(time.since(start_time)),
option == "find" and "books" or option, option == "find" and "books" or option,
"case sensitive: " .. tostring(not self.case_insensitive), "case sensitive: " .. tostring(not self.case_insensitive),
"title: " .. tostring(self.find_by_title), "title: " .. tostring(self.find_by_title),
@ -552,7 +552,7 @@ end
-- get metadata from cache or calibre files -- get metadata from cache or calibre files
function CalibreSearch:getMetadata() function CalibreSearch:getMetadata()
local start = TimeVal:now() local start_time = time.now()
local template = "metadata: %d books imported from %s in %.3f milliseconds" local template = "metadata: %d books imported from %s in %.3f milliseconds"
-- try to load metadata from cache -- try to load metadata from cache
@ -594,7 +594,7 @@ function CalibreSearch:getMetadata()
end end
end end
if is_newer then if is_newer then
logger.info(string.format(template, #cache, "cache", TimeVal:getDurationMs(start))) logger.info(string.format(template, #cache, "cache", time.to_ms(time.since(start_time))))
return cache return cache
else else
logger.warn("cache is older than metadata, ignoring it") logger.warn("cache is older than metadata, ignoring it")
@ -623,7 +623,7 @@ function CalibreSearch:getMetadata()
logger.info("Failed to serialize calibre metadata cache:", err) logger.info("Failed to serialize calibre metadata cache:", err)
end end
end end
logger.info(string.format(template, #books, "calibre", TimeVal:getDurationMs(start))) logger.info(string.format(template, #books, "calibre", time.to_ms(time.since(start_time))))
return books return books
end end

@ -7,12 +7,12 @@ local FFIUtil = require("ffi/util")
local InfoMessage = require("ui/widget/infomessage") local InfoMessage = require("ui/widget/infomessage")
local RenderImage = require("ui/renderimage") local RenderImage = require("ui/renderimage")
local SQ3 = require("lua-ljsqlite3/init") local SQ3 = require("lua-ljsqlite3/init")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local lfs = require("libs/libkoreader-lfs") local lfs = require("libs/libkoreader-lfs")
local logger = require("logger") local logger = require("logger")
local util = require("util") local util = require("util")
local zstd = require("ffi/zstd") local zstd = require("ffi/zstd")
local time = require("ui/time")
local _ = require("gettext") local _ = require("gettext")
local N_ = _.ngettext local N_ = _.ngettext
local T = FFIUtil.template local T = FFIUtil.template
@ -137,8 +137,8 @@ function BookInfoManager:init()
self.subprocesses_collector = nil self.subprocesses_collector = nil
self.subprocesses_collect_interval = 10 -- do that every 10 seconds self.subprocesses_collect_interval = 10 -- do that every 10 seconds
self.subprocesses_pids = {} self.subprocesses_pids = {}
self.subprocesses_last_added_tv = TimeVal.zero self.subprocesses_last_added_time = 0
self.subprocesses_killall_timeout_tv = TimeVal:new{ sec = 300, usec = 0 } -- cleanup timeout for stuck subprocesses self.subprocesses_killall_timeout_time = time.s(300) -- cleanup timeout for stuck subprocesses
-- 300 seconds should be more than enough to open and get info from 9-10 books -- 300 seconds should be more than enough to open and get info from 9-10 books
-- Whether to use former blitbuffer:scale() (default to using MuPDF) -- Whether to use former blitbuffer:scale() (default to using MuPDF)
self.use_legacy_image_scaling = G_reader_settings:isTrue("legacy_image_scaling") self.use_legacy_image_scaling = G_reader_settings:isTrue("legacy_image_scaling")
@ -657,7 +657,7 @@ function BookInfoManager:collectSubprocesses()
-- the user has not left FileManager or changed page - that would -- the user has not left FileManager or changed page - that would
-- have caused a terminateBackgroundJobs() - if we're here, it's -- have caused a terminateBackgroundJobs() - if we're here, it's
-- that user has left reader in FileBrower and went away) -- that user has left reader in FileBrower and went away)
if TimeVal:now() > self.subprocesses_last_added_tv + self.subprocesses_killall_timeout_tv then if time.now() > self.subprocesses_last_added_time + self.subprocesses_killall_timeout_time then
logger.warn("Some subprocesses were running for too long, killing them") logger.warn("Some subprocesses were running for too long, killing them")
self:terminateBackgroundJobs() self:terminateBackgroundJobs()
-- we'll collect them next time we're run -- we'll collect them next time we're run
@ -730,7 +730,7 @@ function BookInfoManager:extractInBackground(files)
-- counter on each task, and undo that inside collectSubprocesses() zombie reaper. -- counter on each task, and undo that inside collectSubprocesses() zombie reaper.
UIManager:preventStandby() UIManager:preventStandby()
table.insert(self.subprocesses_pids, task_pid) table.insert(self.subprocesses_pids, task_pid)
self.subprocesses_last_added_tv = TimeVal:now() self.subprocesses_last_added_time = time.now()
-- We need to collect terminated jobs pids (so they do not stay "zombies" -- We need to collect terminated jobs pids (so they do not stay "zombies"
-- and fill linux processes table) -- and fill linux processes table)

@ -17,6 +17,7 @@ local SpinWidget = require("ui/widget/spinwidget")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local util = require("util") local util = require("util")
local T = FFIUtil.template local T = FFIUtil.template
local time = require("ui/time")
local _ = require("gettext") local _ = require("gettext")
local logger = require("logger") local logger = require("logger")
@ -478,16 +479,16 @@ Any other taps made within this interval after a first tap will be considered ac
The interval value is in milliseconds and can range from 0 (0 seconds) to 2000 (2 seconds).]]), The interval value is in milliseconds and can range from 0 (0 seconds) to 2000 (2 seconds).]]),
width = math.floor(Screen:getWidth() * 0.75), width = math.floor(Screen:getWidth() * 0.75),
value = GestureDetector:getInterval("ges_tap_interval")/1000, value = time.to_ms(GestureDetector.ges_tap_interval),
value_min = 0, value_min = 0,
value_max = 2000, value_max = 2000,
value_step = 50, value_step = 50,
value_hold_step = 200, value_hold_step = 200,
ok_text = _("Set interval"), ok_text = _("Set interval"),
default_value = GestureDetector.TAP_INTERVAL/1000, default_value = GestureDetector.TAP_INTERVAL_MS,
callback = function(spin) callback = function(spin)
G_reader_settings:saveSetting("ges_tap_interval", spin.value*1000) G_reader_settings:saveSetting("ges_tap_interval_ms", spin.value)
GestureDetector:setNewInterval("ges_tap_interval", spin.value*1000) GestureDetector.ges_tap_interval = time.ms(spin.value)
end end
} }
UIManager:show(items) UIManager:show(items)
@ -504,7 +505,7 @@ Any other taps made within this interval after a first tap will be considered ac
The interval value is in milliseconds and can range from 0 (0 seconds) to 2000 (2 seconds).]]), The interval value is in milliseconds and can range from 0 (0 seconds) to 2000 (2 seconds).]]),
width = math.floor(Screen:getWidth() * 0.75), width = math.floor(Screen:getWidth() * 0.75),
value = G_reader_settings:readSetting("ges_tap_interval_on_keyboard", 0)/1000, value = time.to_ms(G_reader_settings:readSetting("ges_tap_interval_on_keyboard_ms", 0)),
value_min = 0, value_min = 0,
value_max = 2000, value_max = 2000,
value_step = 50, value_step = 50,
@ -512,7 +513,7 @@ The interval value is in milliseconds and can range from 0 (0 seconds) to 2000 (
ok_text = _("Set interval"), ok_text = _("Set interval"),
default_value = 0, default_value = 0,
callback = function(spin) callback = function(spin)
G_reader_settings:saveSetting("ges_tap_interval_on_keyboard", spin.value*1000) G_reader_settings:saveSetting("ges_tap_interval_on_keyboard_ms", spin.value)
end end
} }
UIManager:show(items) UIManager:show(items)
@ -529,16 +530,16 @@ When double tap is enabled, this sets the time to wait for the second tap. A sin
The interval value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds).]]), The interval value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds).]]),
width = math.floor(Screen:getWidth() * 0.75), width = math.floor(Screen:getWidth() * 0.75),
value = GestureDetector:getInterval("ges_double_tap_interval")/1000, value = time.to_ms(GestureDetector.ges_double_tap_interval),
value_min = 100, value_min = 100,
value_max = 2000, value_max = 2000,
value_step = 100, value_step = 100,
value_hold_step = 500, value_hold_step = 500,
ok_text = _("Set interval"), ok_text = _("Set interval"),
default_value = GestureDetector.DOUBLE_TAP_INTERVAL/1000, default_value = GestureDetector.DOUBLE_TAP_INTERVAL_MS,
callback = function(spin) callback = function(spin)
G_reader_settings:saveSetting("ges_double_tap_interval", spin.value*1000) G_reader_settings:saveSetting("ges_double_tap_interval_ms", spin.value)
GestureDetector:setNewInterval("ges_double_tap_interval", spin.value*1000) GestureDetector.ges_double_tap_interval = time.ms(spin.value)
end end
} }
UIManager:show(items) UIManager:show(items)
@ -555,16 +556,16 @@ This sets the allowed duration of any of the two fingers touch/release for the c
The duration value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds).]]), The duration value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds).]]),
width = math.floor(Screen:getWidth() * 0.75), width = math.floor(Screen:getWidth() * 0.75),
value = GestureDetector:getInterval("ges_two_finger_tap_duration")/1000, value = time.to_ms(GestureDetector.ges_two_finger_tap_duration),
value_min = 100, value_min = 100,
value_max = 2000, value_max = 2000,
value_step = 100, value_step = 100,
value_hold_step = 500, value_hold_step = 500,
ok_text = _("Set duration"), ok_text = _("Set duration"),
default_value = GestureDetector.TWO_FINGER_TAP_DURATION/1000, default_value = GestureDetector.TWO_FINGER_TAP_DURATION_MS,
callback = function(spin) callback = function(spin)
G_reader_settings:saveSetting("ges_two_finger_tap_duration", spin.value*1000) G_reader_settings:saveSetting("ges_two_finger_tap_duration_ms", spin.value)
GestureDetector:setNewInterval("ges_two_finger_tap_duration", spin.value*1000) GestureDetector.ges_two_finger_tap_duration = time.ms(spin.value)
end end
} }
UIManager:show(items) UIManager:show(items)
@ -581,16 +582,16 @@ If a touch is not released in this interval, it is considered a long-press. On d
The interval value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds).]]), The interval value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds).]]),
width = math.floor(Screen:getWidth() * 0.75), width = math.floor(Screen:getWidth() * 0.75),
value = GestureDetector:getInterval("ges_hold_interval")/1000, value = time.to_ms(GestureDetector.ges_hold_interval),
value_min = 100, value_min = 100,
value_max = 2000, value_max = 2000,
value_step = 100, value_step = 100,
value_hold_step = 500, value_hold_step = 500,
ok_text = _("Set interval"), ok_text = _("Set interval"),
default_value = GestureDetector.HOLD_INTERVAL/1000, default_value = GestureDetector.HOLD_INTERVAL_MS,
callback = function(spin) callback = function(spin)
G_reader_settings:saveSetting("ges_hold_interval", spin.value*1000) G_reader_settings:saveSetting("ges_hold_interval_ms", spin.value)
GestureDetector:setNewInterval("ges_hold_interval", spin.value*1000) GestureDetector.ges_hold_interval = time.ms(spin.value)
end end
} }
UIManager:show(items) UIManager:show(items)
@ -607,16 +608,16 @@ This sets the maximum delay between the start and the end of a swipe for it to b
The interval value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds).]]), The interval value is in milliseconds and can range from 100 (0.1 seconds) to 2000 (2 seconds).]]),
width = math.floor(Screen:getWidth() * 0.75), width = math.floor(Screen:getWidth() * 0.75),
value = GestureDetector:getInterval("ges_swipe_interval")/1000, value = time.to_ms(GestureDetector.ges_swipe_interval),
value_min = 100, value_min = 100,
value_max = 2000, value_max = 2000,
value_step = 100, value_step = 100,
value_hold_step = 500, value_hold_step = 500,
ok_text = _("Set interval"), ok_text = _("Set interval"),
default_value = GestureDetector.SWIPE_INTERVAL/1000, default_value = GestureDetector.SWIPE_INTERVAL_MS,
callback = function(spin) callback = function(spin)
G_reader_settings:saveSetting("ges_swipe_interval", spin.value*1000) G_reader_settings:saveSetting("ges_swipe_interval_ms", spin.value)
GestureDetector:setNewInterval("ges_swipe_interval", spin.value*1000) GestureDetector.ges_swipe_interval = time.ms(spin.value)
end end
} }
UIManager:show(items) UIManager:show(items)

@ -3,6 +3,7 @@ local Dispatcher = require("dispatcher")
local KeyValuePage = require("ui/widget/keyvaluepage") local KeyValuePage = require("ui/widget/keyvaluepage")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer") local WidgetContainer = require("ui/widget/container/widgetcontainer")
local time = require("ui/time")
local util = require("util") local util = require("util")
local _ = require("gettext") local _ = require("gettext")
@ -48,11 +49,11 @@ function SystemStat:appendCounters()
util.secondsToClockDuration("", os.difftime(os.time(), self.start_sec), false, true, true)}) util.secondsToClockDuration("", os.difftime(os.time(), self.start_sec), false, true, true)})
if Device:canSuspend() then if Device:canSuspend() then
self:put({" " .. _("Time in suspend"), self:put({" " .. _("Time in suspend"),
util.secondsToClockDuration("", Device.total_suspend_tv:tonumber(), false, true, true)}) util.secondsToClockDuration("", time.to_number(Device.total_suspend_time), false, true, true)})
end end
if Device:canStandby() then if Device:canStandby() then
self:put({" " .. _("Time in standby"), self:put({" " .. _("Time in standby"),
util.secondsToClockDuration("", Device.total_standby_tv:tonumber(), false, true, true)}) util.secondsToClockDuration("", time.to_number(Device.total_standby_time), false, true, true)})
end end
self:put({_("Counters"), ""}) self:put({_("Counters"), ""})
self:put({_(" wake-ups"), self.wakeup_count}) self:put({_(" wake-ups"), self.wakeup_count})

@ -39,6 +39,7 @@ describe("BackgroundRunner widget tests", function()
MockTime:increase(2) MockTime:increase(2)
UIManager:handleInput() UIManager:handleInput()
assert.is_false(executed)
MockTime:increase(9) MockTime:increase(9)
UIManager:handleInput() UIManager:handleInput()
assert.is_false(executed) assert.is_false(executed)
@ -132,7 +133,7 @@ describe("BackgroundRunner widget tests", function()
table.insert(PluginShare.backgroundJobs, job) table.insert(PluginShare.backgroundJobs, job)
notifyBackgroundJobsUpdated() notifyBackgroundJobsUpdated()
while job.end_tv == nil do while job.end_time == nil do
MockTime:increase(2) MockTime:increase(2)
UIManager:handleInput() UIManager:handleInput()
end end
@ -157,7 +158,7 @@ describe("BackgroundRunner widget tests", function()
table.insert(PluginShare.backgroundJobs, job) table.insert(PluginShare.backgroundJobs, job)
notifyBackgroundJobsUpdated() notifyBackgroundJobsUpdated()
while job.end_tv == nil do while job.end_time == nil do
MockTime:increase(2) MockTime:increase(2)
UIManager:handleInput() UIManager:handleInput()
end end
@ -171,11 +172,11 @@ describe("BackgroundRunner widget tests", function()
ENV1 = "yes", ENV1 = "yes",
ENV2 = "no", ENV2 = "no",
} }
job.end_tv = nil job.end_time = nil
table.insert(PluginShare.backgroundJobs, job) table.insert(PluginShare.backgroundJobs, job)
notifyBackgroundJobsUpdated() notifyBackgroundJobsUpdated()
while job.end_tv == nil do while job.end_time == nil do
MockTime:increase(2) MockTime:increase(2)
UIManager:handleInput() UIManager:handleInput()
end end
@ -206,7 +207,7 @@ describe("BackgroundRunner widget tests", function()
table.insert(PluginShare.backgroundJobs, job) table.insert(PluginShare.backgroundJobs, job)
notifyBackgroundJobsUpdated() notifyBackgroundJobsUpdated()
while job.end_tv == nil do while job.end_time == nil do
MockTime:increase(2) MockTime:increase(2)
UIManager:handleInput() UIManager:handleInput()
end end
@ -216,12 +217,12 @@ describe("BackgroundRunner widget tests", function()
assert.is_false(job.timeout) assert.is_false(job.timeout)
assert.is_false(job.bad_command) assert.is_false(job.bad_command)
job.end_tv = nil job.end_time = nil
env2 = "no" env2 = "no"
table.insert(PluginShare.backgroundJobs, job) table.insert(PluginShare.backgroundJobs, job)
notifyBackgroundJobsUpdated() notifyBackgroundJobsUpdated()
while job.end_tv == nil do while job.end_time == nil do
MockTime:increase(2) MockTime:increase(2)
UIManager:handleInput() UIManager:handleInput()
end end
@ -244,7 +245,7 @@ describe("BackgroundRunner widget tests", function()
table.insert(PluginShare.backgroundJobs, job) table.insert(PluginShare.backgroundJobs, job)
notifyBackgroundJobsUpdated() notifyBackgroundJobsUpdated()
while job.end_tv == nil do while job.end_time == nil do
MockTime:increase(2) MockTime:increase(2)
UIManager:handleInput() UIManager:handleInput()
end end

@ -1,5 +1,6 @@
require("commonrequire") require("commonrequire")
local TimeVal = require("ui/timeval") local TimeVal = require("ui/timeval")
local time = require("ui/time")
local ffi = require("ffi") local ffi = require("ffi")
local dummy = require("ffi/posix_h") local dummy = require("ffi/posix_h")
local logger = require("logger") local logger = require("logger")
@ -21,6 +22,10 @@ local MockTime = {
realtime = 0, realtime = 0,
boottime = 0, boottime = 0,
boottime_or_realtime_coarse = 0, boottime_or_realtime_coarse = 0,
monotonic_time = 0,
realtime_time = 0,
boottime_time = 0,
boottime_or_realtime_coarse_time = 0,
} }
function MockTime:install() function MockTime:install()
@ -100,7 +105,72 @@ function MockTime:install()
logger.dbg("MockTime:TimeVal.now: ", self.monotonic) logger.dbg("MockTime:TimeVal.now: ", self.monotonic)
return TimeVal:new{ sec = self.monotonic } return TimeVal:new{ sec = self.monotonic }
end end
end
if self.original_tv_realtime_time == nil then
self.original_tv_realtime_time = time.realtime
assert(self.original_tv_realtime_time ~= nil)
end
if self.original_tv_realtime_coarse_time == nil then
self.original_tv_realtime_coarse_time = time.realtime_coarse
assert(self.original_tv_realtime_coarse_time ~= nil)
end
if self.original_tv_monotonic_time == nil then
self.original_tv_monotonic_time = time.monotonic
assert(self.original_tv_monotonic_time ~= nil)
end
if self.original_tv_monotonic_coarse_time == nil then
self.original_tv_monotonic_coarse_time = time.monotonic_coarse
assert(self.original_tv_monotonic_coarse_time ~= nil)
end
if self.original_tv_boottime_time == nil then
self.original_tv_boottime_time = time.boottime
assert(self.original_tv_boottime_time ~= nil)
end
if self.original_tv_boottime_or_realtime_coarse_time == nil then
self.original_tv_boottime_or_realtime_coarse_time = time.boottime_or_realtime_coarse
assert(self.original_tv_boottime_or_realtime_coarse_time ~= nil)
end
if self.original_tv_now == nil then
self.original_tv_now = time.now
assert(self.original_tv_now ~= nil)
end
-- Store both REALTIME & MONOTONIC clocks for fts
self.realtime_time = os.time() * 1e6
local timespec_time = ffi.new("struct timespec")
C.clock_gettime(C.CLOCK_MONOTONIC_COARSE, timespec_time)
self.monotonic = tonumber(timespec.tv_sec) * 1e6
time.realtime = function()
logger.dbg("MockTime:TimeVal.realtime: ", self.realtime_time)
return self.realtime_time
end
time.realtime_coarse = function()
logger.dbg("MockTime:TimeVal.realtime_coarse: ", self.realtime_coarse_time)
return self.realtime_coarse_time
end
time.monotonic = function()
logger.dbg("MockTime:TimeVal.monotonic: ", self.monotonic)
return self.monotonic_time
end
time.monotonic_coarse = function()
logger.dbg("MockTime:TimeVal.monotonic_coarse: ", self.monotonic)
return self.monotonic_time
end
time.boottime = function()
logger.dbg("MockTime:TimeVal.boottime: ", self.boottime_time)
return self.boottime_time
end
time.boottime_or_realtime_coarse = function()
logger.dbg("MockTime:TimeVal.boottime: ", self.boottime_or_realtime_coarse_time)
return self.boottime_or_realtime_coarse_time
end
time.now = function()
logger.dbg("MockTime:TimeVal.now: ", self.monotonic)
return self.monotonic_time
end
end
function MockTime:uninstall() function MockTime:uninstall()
assert(self ~= nil) assert(self ~= nil)
@ -240,6 +310,17 @@ function MockTime:increase(value)
logger.dbg("MockTime:increase (boottime) ", self.boottime) logger.dbg("MockTime:increase (boottime) ", self.boottime)
self.boottime_or_realtime_coarse = math.floor(self.boottime_or_realtime_coarse + value) self.boottime_or_realtime_coarse = math.floor(self.boottime_or_realtime_coarse + value)
logger.dbg("MockTime:increase (boottime) ", self.boottime_or_realtime_coarse) logger.dbg("MockTime:increase (boottime) ", self.boottime_or_realtime_coarse)
local value_time = value * 1e6
self.realtime_time = math.floor(self.realtime_time + value_time)
logger.dbg("MockTime:increase (realtime) ", self.realtime_time)
self.monotonic_time = math.floor(self.monotonic_time + value_time)
logger.dbg("MockTime:increase (monotonic) ", self.monotonic)
self.boottime_time = math.floor(self.boottime_time + value_time)
logger.dbg("MockTime:increase (boottime) ", self.boottime_time)
self.boottime_or_realtime_coarse_time = math.floor(self.boottime_or_realtime_coarse_time + value_time)
logger.dbg("MockTime:increase (boottime) ", self.boottime_or_realtime_coarse_time)
return true return true
end end

@ -0,0 +1,135 @@
describe("Time module", function()
local time, dbg, dbg_on
setup(function()
require("commonrequire")
time = require("ui/time")
dbg = require("dbg")
dbg_on = dbg.is_on
end)
after_each(function()
if dbg_on then
dbg:turnOn()
else
dbg:turnOff()
end
end)
it("should set", function()
local time1 = time.s(12)
local time2 = time.ms(12)
local time3 = time.us(12)
assert.is.same(12000000, time1)
assert.is.same(12000, time2)
assert.is.same(12, time3)
end)
it("should convert", function()
local time1 = 12000000
local time2 = 12000
local time3 = 12
local time4 = time.s(12) + time.us(40)
local time5 = time.s(12) + time.us(60)
assert.is.same(12, time.to_s(time1))
assert.is.same(12000, time.to_ms(time1))
assert.is.same(12000000, time.to_us(time1))
assert.is.same(0.012, time.to_s(time2))
assert.is.same(12, time.to_ms(time2))
assert.is.same(12000, time.to_us(time2))
assert.is.same(0.000012, time.to_s(time3))
assert.is.same(math.floor(0.012), time.to_ms(time3))
assert.is.same(12, time.to_us(time3))
assert.is.same(12.0000, time.to_number(time4))
assert.is.same(12.0001, time.to_number(time5))
end)
it("should add", function()
local time1 = time.s(5) + time.us(5000)
local time2 = time.s(10) + time.us(6000)
local time3 = time.s(10) + time.us(50000000)
assert.is.same(time.s(15) + time.us(11000), time1 + time2)
assert.is.same(time.s(65) + time.us(5000), time1 + time3)
end)
it("should subtract", function()
local time1 = time.s(5.005)
local time2 = time.s(10.006)
assert.is.same(time.s(5.001), time2 - time1)
local backwards_sub = time1 - time2
assert.is.same(time.s(-5.001), backwards_sub)
-- Check that to/from float conversions behave, even for negative values.
assert.is.same(-5.001, time.to_number(backwards_sub))
assert.is.same(time.s(-6) + time.us(999000), time.s(-5.001))
local tv = time.s(-6) + time.us(1000)
assert.is.same(-5.999, time.to_number(tv))
assert.is.same(time.s(-6) + time.us(1000), time.s(-5.999))
-- We lose precision because of rounding if we go higher resolution than a ms...
tv = time.s(-6) + time.us(101)
assert.is.same(-5.9999, time.to_number(tv))
assert.is.same(time.s(-6) + time.us(100), time.s(-5.9999))
-- ^ precision loss
tv = time.s(-6) + time.us(11)
assert.is.same(-6, time.to_number(tv))
-- ^ precision loss
assert.is.same(time.s(-6) + time.us(10), time.s(-5.99999))
-- ^ precision loss
tv = time.s(-6) + time.us(1)
assert.is.same(-6, time.to_number(tv))
-- ^ precision loss
assert.is.same(time.s(-6) + time.us(1), time.s(-5.999999))
end)
it("should derive sec and usec from more than 1 sec worth of usec", function()
local time1 = time.s(5) + time.us(5000000)
assert.is.same(time.s(10), time1)
end)
it("should compare", function()
local time1 = time.s(5) + time.us(5000)
local time2 = time.s(10) + time.us(6000)
local time3 = time.s(5) + time.us(5000)
local time4 = time.s(5) + time.us(6000)
assert.is_true(time2 > time1)
assert.is_false(time2 < time1)
assert.is_true(time2 >= time1)
assert.is_true(time4 > time1)
assert.is_false(time4 < time1)
assert.is_true(time4 >= time1)
assert.is_true(time1 < time2)
assert.is_false(time1 > time2)
assert.is_true(time1 <= time2)
assert.is_true(time1 == time3)
assert.is_false(time1 == time2)
assert.is_true(time1 >= time3)
assert.is_true(time1 <= time3)
end)
it("should calculate durations", function()
local time1 = time.s(5) + time.us(500000)
local function now() return time.s(10) end
local now_save = time.now
time.now = now
assert.is.equal(time.to_s(time.s(4) + time.us(500000)), time.to_s(time.since(time1)))
assert.is.equal(time.to_ms(time.s(4) + time.us(500000)), time.to_ms(time.since(time1)))
assert.is.equal(time.to_us(time.s(4) + time.us(500000)), time.to_us(time.since(time1)))
time.now = now_save
end)
end)

@ -1,50 +1,54 @@
require("commonrequire") require("commonrequire")
local util = require("ffi/util")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local time = require("ui/time")
local NB_TESTS = 40000
local noop = function() end local noop = function() end
describe("UIManager checkTasks benchmark", function() describe("UIManager checkTasks benchmark", function()
local now = { util.gettime() } local now = time.now()
local wait_until -- luacheck: no unused local wait_until -- luacheck: no unused
UIManager:quit() UIManager:quit()
UIManager._task_queue = {} UIManager._task_queue = {}
for i=1,1000000 do for i=1, NB_TESTS do
table.insert( table.insert(
UIManager._task_queue, UIManager._task_queue,
{ time = { now[1] + 10000+i, now[2] }, action = noop } { time = now + i, action = noop, argc = 0, args = {} }
) )
end end
-- for i=1,1000 do for i=1, NB_TESTS do
wait_until, now = UIManager:_checkTasks() -- luacheck: no unused wait_until, now = UIManager:_checkTasks() -- luacheck: no unused
-- end end
end) end)
describe("UIManager schedule benchmark", function() describe("UIManager schedule benchmark", function()
local now = { util.gettime() } local now = time.now()
UIManager:quit() UIManager:quit()
UIManager._task_queue = {} UIManager._task_queue = {}
for i=1,100000 do
UIManager:schedule({ now[1] + i, now[2] }, noop) for i=1, NB_TESTS do
UIManager:schedule(now + i, noop)
end end
end) end)
describe("UIManager unschedule benchmark", function() describe("UIManager unschedule benchmark", function()
local now = { util.gettime() } local now = time.now()
UIManager:quit() UIManager:quit()
UIManager._task_queue = {} UIManager._task_queue = {}
for i=1,1000 do for i=1, NB_TESTS do
table.insert( table.insert(
UIManager._task_queue, UIManager._task_queue,
{ time = { now[1] + 10000+i, now[2] }, action = 'a' } { time = now + i, action = 'a', argc=0, args={} }
) )
end end
for i=1,1000 do for i=1, NB_TESTS do
UIManager:schedule(now, noop) UIManager:schedule(now + i, noop)
UIManager:unschedule(noop) UIManager:unschedule(noop)
end end
end) end)

@ -1,22 +1,22 @@
describe("UIManager spec", function() describe("UIManager spec", function()
local TimeVal, UIManager local time, UIManager
local now, wait_until local now, wait_until
local noop = function() end local noop = function() end
setup(function() setup(function()
require("commonrequire") require("commonrequire")
TimeVal = require("ui/timeval") time = require("ui/time")
UIManager = require("ui/uimanager") UIManager = require("ui/uimanager")
end) end)
it("should consume due tasks", function() it("should consume due tasks", function()
now = TimeVal:now() now = time.now()
local future = TimeVal:new{ sec = now.sec + 60000, usec = now.usec } local future = now + time.s(60000)
local future2 = TimeVal:new{ sec = future.sec + 5, usec = future.usec} local future2 = future + time.s(5)
UIManager:quit() UIManager:quit()
UIManager._task_queue = { UIManager._task_queue = {
{ time = TimeVal:new{ sec = now.sec - 10, usec = now.usec }, action = noop, args = {}, argc = 0 }, { time = now - time.s(10), action = noop, args = {}, argc = 0 },
{ time = TimeVal:new{ sec = now.sec, usec = now.usec - 5 }, action = noop, args = {}, argc = 0 }, { time = now - time.us(5), action = noop, args = {}, argc = 0 },
{ time = now, action = noop, args = {}, argc = 0 }, { time = now, action = noop, args = {}, argc = 0 },
{ time = future, action = noop, args = {}, argc = 0 }, { time = future, action = noop, args = {}, argc = 0 },
{ time = future2, action = noop, args = {}, argc = 0 }, { time = future2, action = noop, args = {}, argc = 0 },
@ -28,26 +28,25 @@ describe("UIManager spec", function()
end) end)
it("should calcualte wait_until properly in checkTasks routine", function() it("should calcualte wait_until properly in checkTasks routine", function()
now = TimeVal:now() now = time.now()
local future = TimeVal:new{ sec = now.sec + 60000, usec = now.usec } local future_time = now + time.s(60000)
UIManager:quit() UIManager:quit()
UIManager._task_queue = { UIManager._task_queue = {
{ time = TimeVal:new{ sec = now.sec - 10, usec = now.usec }, action = noop, args = {}, argc = 0 }, { time = now - time.s(10), action = noop, args = {}, argc = 0 },
{ time = TimeVal:new{ sec = now.sec, usec = now.usec - 5 }, action = noop, args = {}, argc = 0 }, { time = now - time.us(5), action = noop, args = {}, argc = 0 },
{ time = now, action = noop, args = {}, argc = 0 }, { time = now, action = noop, args = {}, argc = 0 },
{ time = future, action = noop, args = {}, argc = 0 }, { time = future_time, action = noop, args = {}, argc = 0 },
{ time = TimeVal:new{ sec = future.sec + 5, usec = future.usec }, action = noop, args = {}, argc = 0 },
} }
wait_until, now = UIManager:_checkTasks() wait_until, now = UIManager:_checkTasks()
assert.are.same(future, wait_until) assert.are.same(future_time, wait_until)
end) end)
it("should return nil wait_until properly in checkTasks routine", function() it("should return nil wait_until properly in checkTasks routine", function()
now = TimeVal:now() now = time.now()
UIManager:quit() UIManager:quit()
UIManager._task_queue = { UIManager._task_queue = {
{ time = TimeVal:new{ sec = now.sec - 10, usec = now.usec }, action = noop, args = {}, argc = 0 }, { time = now - time.s(10), action = noop, args = {}, argc = 0 },
{ time = TimeVal:new{ sec = now.sec, usec = now.usec - 5 }, action = noop, args = {}, argc = 0 }, { time = now - time.us(5), action = noop, args = {}, argc = 0 },
{ time = now, action = noop, args = {}, argc = 0 }, { time = now, action = noop, args = {}, argc = 0 },
} }
wait_until, now = UIManager:_checkTasks() wait_until, now = UIManager:_checkTasks()
@ -55,7 +54,7 @@ describe("UIManager spec", function()
end) end)
it("should insert new task properly in empty task queue", function() it("should insert new task properly in empty task queue", function()
now = TimeVal:now() now = time.now()
UIManager:quit() UIManager:quit()
UIManager._task_queue = {} UIManager._task_queue = {}
assert.are.same(0, #UIManager._task_queue) assert.are.same(0, #UIManager._task_queue)
@ -65,11 +64,11 @@ describe("UIManager spec", function()
end) end)
it("should insert new task properly in single task queue", function() it("should insert new task properly in single task queue", function()
now = TimeVal:now() now = time.now()
local future = TimeVal:new{ sec = now.sec + 10000, usec = now.usec } local future_time = now + time.s(10000)
UIManager:quit() UIManager:quit()
UIManager._task_queue = { UIManager._task_queue = {
{ time = future, action = '1', args = {}, argc = 0 }, { time = future_time, action = '1', args = {}, argc = 0 },
} }
assert.are.same(1, #UIManager._task_queue) assert.are.same(1, #UIManager._task_queue)
UIManager:scheduleIn(150, 'quz') UIManager:scheduleIn(150, 'quz')
@ -90,59 +89,59 @@ describe("UIManager spec", function()
end) end)
it("should insert new task in ascendant order", function() it("should insert new task in ascendant order", function()
now = TimeVal:now() now = time.now()
UIManager:quit() UIManager:quit()
UIManager._task_queue = { UIManager._task_queue = {
{ time = TimeVal:new{ sec = now.sec - 10, usec = now.usec }, action = '1', args = {}, argc = 0 }, { time = now - time.s(10), action = '1', args = {}, argc = 0 },
{ time = TimeVal:new{ sec = now.sec, usec = now.usec - 5 }, action = '2', args = {}, argc = 0 }, { time = now - time.us(5), action = '2', args = {}, argc = 0 },
{ time = now, action = '3', args = {}, argc = 0 }, { time = now, action = '3', args = {}, argc = 0 },
} }
-- insert into the tail slot -- insert into the tail slot
UIManager:scheduleIn(10, 'foo') UIManager:scheduleIn(10, 'foo')
assert.are.same('foo', UIManager._task_queue[4].action) assert.are.same('foo', UIManager._task_queue[4].action)
-- insert into the second slot -- insert into the second slot
UIManager:schedule(TimeVal:new{ sec = now.sec - 5, usec = now.usec }, 'bar') UIManager:schedule(now - time.s(5), 'bar')
assert.are.same('bar', UIManager._task_queue[2].action) assert.are.same('bar', UIManager._task_queue[2].action)
-- insert into the head slot -- insert into the head slot
UIManager:schedule(TimeVal:new{ sec = now.sec - 15, usec = now.usec }, 'baz') UIManager:schedule(now - time.s(15), 'baz')
assert.are.same('baz', UIManager._task_queue[1].action) assert.are.same('baz', UIManager._task_queue[1].action)
-- insert into the last second slot -- insert into the last second slot
UIManager:scheduleIn(5, 'qux') UIManager:scheduleIn(5, 'qux')
assert.are.same('qux', UIManager._task_queue[6].action) assert.are.same('qux', UIManager._task_queue[6].action)
-- insert into the middle slot -- insert into the middle slot
UIManager:schedule(TimeVal:new{ sec = now.sec, usec = now.usec - 1 }, 'quux') UIManager:schedule(now - time.us(1), 'quux')
assert.are.same('quux', UIManager._task_queue[5].action) assert.are.same('quux', UIManager._task_queue[5].action)
end) end)
it("should unschedule all the tasks with the same action", function() it("should unschedule all the tasks with the same action", function()
now = TimeVal:now() now = time.now()
UIManager:quit() UIManager:quit()
UIManager._task_queue = { UIManager._task_queue = {
{ time = TimeVal:new{ sec = now.sec - 15, usec = now.usec }, action = '3', args = {}, argc = 0 }, { time = now - time.s(15), action = '3', args = {}, argc = 0 },
{ time = TimeVal:new{ sec = now.sec - 10, usec = now.usec }, action = '1', args = {}, argc = 0 }, { time = now - time.s(10), action = '1', args = {}, argc = 0 },
{ time = TimeVal:new{ sec = now.sec, usec = now.usec - 6 }, action = '3', args = {}, argc = 0 }, { time = now - time.us(6), action = '3', args = {}, argc = 0 },
{ time = TimeVal:new{ sec = now.sec, usec = now.usec - 5 }, action = '2', args = {}, argc = 0 }, { time = now - time.us(5), action = '2', args = {}, argc = 0 },
{ time = now, action = '3', args = {}, argc = 0 }, { time = now, action = '3', args = {}, argc = 0 },
} }
-- insert into the tail slot -- insert into the tail slot
UIManager:unschedule('3') UIManager:unschedule('3')
assert.are.same({ assert.are.same({
{ time = TimeVal:new{ sec = now.sec - 10, usec = now.usec }, action = '1', args = {}, argc = 0 }, { time = now - time.s(10), action = '1', args = {}, argc = 0 },
{ time = TimeVal:new{ sec = now.sec, usec = now.usec - 5 }, action = '2', args = {}, argc = 0 }, { time = now - time.us(5), action = '2', args = {}, argc = 0 },
}, UIManager._task_queue) }, UIManager._task_queue)
end) end)
it("should not have race between unschedule and _checkTasks", function() it("should not have race between unschedule and _checkTasks", function()
now = TimeVal:now() now = time.now()
local run_count = 0 local run_count = 0
local task_to_remove = function() local task_to_remove = function()
run_count = run_count + 1 run_count = run_count + 1
end end
UIManager:quit() UIManager:quit()
UIManager._task_queue = { UIManager._task_queue = {
{ time = TimeVal:new{ sec = now.sec, usec = now.usec - 5 }, action = task_to_remove, args = {}, argc = 0 }, { time = now - time.us(5), action = task_to_remove, args = {}, argc = 0 },
{ {
time = TimeVal:new{ sec = now.sec - 10, usec = now.usec }, time = now - time.s(10),
action = function() action = function()
run_count = run_count + 1 run_count = run_count + 1
UIManager:unschedule(task_to_remove) UIManager:unschedule(task_to_remove)

Loading…
Cancel
Save