mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Input: Process input events in batches (#7483)
Requires https://github.com/koreader/koreader-base/pull/1344 & https://github.com/koreader/koreader-base/pull/1346 (fix #7485) Assorted input fixes: * Actually handle errors in the "there's a callback timer" input polling loop. * Don't break timerfd when the clock probe was inconclusive. Not directly related, but noticed because of duplicate onInputEvent handlers: * HookContainer: Fix deregistration to actually deregister properly. "Regression" extant since its inception in #2933 (!). * Made sure the three plugins (basically the trio of AutoThingies ;p) that were using HookContainer actually unschedule their task on teardown.
This commit is contained in:
@@ -361,6 +361,7 @@ function Input:setTimeout(slot, ges, cb, origin, delay)
|
||||
-- If GestureDetector's clock source probing was inconclusive, do this on the UI timescale instead.
|
||||
if clock_id == -1 then
|
||||
deadline = TimeVal:now() + delay
|
||||
clock_id = C.CLOCK_MONOTONIC
|
||||
else
|
||||
deadline = origin + delay
|
||||
end
|
||||
@@ -934,15 +935,16 @@ end
|
||||
function Input:waitEvent(now, deadline)
|
||||
-- On the first iteration of the loop, we don't need to update now, we're following closely (a couple ms at most) behind UIManager.
|
||||
local ok, ev
|
||||
-- Wrapper around the platform-specific input.waitForEvent (which itself is generally poll-like).
|
||||
-- Wrapper around the platform-specific input.waitForEvent (which itself is generally poll-like, and supposed to poll *once*).
|
||||
-- Speaking of input.waitForEvent, it can return:
|
||||
-- * true, ev: When an input event was read. ev is a table mapped after the input_event <linux/input.h> struct.
|
||||
-- * true, ev: When a batch of input events was read.
|
||||
-- ev is an array of event tables, themselves mapped after the input_event <linux/input.h> struct.
|
||||
-- * false, errno, timerfd: When no input event was read, possibly for benign reasons.
|
||||
-- One such common case is after a polling timeout, in which case errno is C.ETIME.
|
||||
-- If the timerfd backend is in use, and the early return was caused by a timerfd expiring,
|
||||
-- it returns false, C.ETIME, timerfd; where timerfd is a C pointer (i.e., light userdata)
|
||||
-- to the timerfd node that expired (so as to be able to free it later, c.f., input/timerfd-callbacks.h).
|
||||
-- Otherwise, errno is the actual error code from the backend (e.g., select's errno for the C backend).
|
||||
-- One such common case is after a polling timeout, in which case errno is C.ETIME.
|
||||
-- If the timerfd backend is in use, and the early return was caused by a timerfd expiring,
|
||||
-- it returns false, C.ETIME, timerfd; where timerfd is a C pointer (i.e., light userdata)
|
||||
-- to the timerfd node that expired (so as to be able to free it later, c.f., input/timerfd-callbacks.h).
|
||||
-- Otherwise, errno is the actual error code from the backend (e.g., select's errno for the C backend).
|
||||
-- * nil: When something terrible happened (e.g., fatal poll/read failure). We abort in such cases.
|
||||
while true do
|
||||
if #self.timer_callbacks > 0 then
|
||||
@@ -1028,14 +1030,17 @@ function Input:waitEvent(now, deadline)
|
||||
-- This is why we clear the full list of timers on the first match ;).
|
||||
self:clearTimeouts()
|
||||
self:gestureAdjustHook(touch_ges)
|
||||
return Event:new("Gesture",
|
||||
self.gesture_detector:adjustGesCoordinate(touch_ges)
|
||||
)
|
||||
return {
|
||||
Event:new("Gesture", self.gesture_detector:adjustGesCoordinate(touch_ges))
|
||||
}
|
||||
end -- if touch_ges
|
||||
end -- if poll_deadline reached
|
||||
else
|
||||
-- Something went wrong, jump to error handling *now*
|
||||
break
|
||||
end -- if poll returned ETIME
|
||||
|
||||
-- Refresh now on the next iteration (e.g., when we have multiple timers to check)
|
||||
-- Refresh now on the next iteration (e.g., when we have multiple timers to check, and we've just timed out)
|
||||
now = nil
|
||||
end -- while #timer_callbacks > 0
|
||||
else
|
||||
@@ -1071,7 +1076,7 @@ function Input:waitEvent(now, deadline)
|
||||
-- Retry on EINTR
|
||||
else
|
||||
-- Warn, report, and go back to UIManager
|
||||
logger.warn("Polling for input events returned an error:", ev)
|
||||
logger.warn("Polling for input events returned an error:", ev, "->", ffi.string(C.strerror(ev)))
|
||||
break
|
||||
end
|
||||
elseif ok == nil then
|
||||
@@ -1087,55 +1092,80 @@ function Input:waitEvent(now, deadline)
|
||||
end
|
||||
|
||||
if ok and ev then
|
||||
if DEBUG.is_on then
|
||||
DEBUG:logEv(ev)
|
||||
if ev.type == C.EV_KEY then
|
||||
logger.dbg(string.format(
|
||||
"key event => code: %d (%s), value: %s, time: %d.%d",
|
||||
ev.code, self.event_map[ev.code] or linux_evdev_key_code_map[ev.code], ev.value,
|
||||
ev.time.sec, ev.time.usec))
|
||||
elseif ev.type == C.EV_SYN then
|
||||
logger.dbg(string.format(
|
||||
"input event => type: %d (%s), code: %d (%s), value: %s, time: %d.%d",
|
||||
ev.type, linux_evdev_type_map[ev.type], ev.code, linux_evdev_syn_code_map[ev.code], ev.value,
|
||||
ev.time.sec, ev.time.usec))
|
||||
elseif ev.type == C.EV_ABS then
|
||||
logger.dbg(string.format(
|
||||
"input event => type: %d (%s), code: %d (%s), value: %s, time: %d.%d",
|
||||
ev.type, linux_evdev_type_map[ev.type], ev.code, linux_evdev_abs_code_map[ev.code], ev.value,
|
||||
ev.time.sec, ev.time.usec))
|
||||
elseif ev.type == C.EV_MSC then
|
||||
logger.dbg(string.format(
|
||||
"input event => type: %d (%s), code: %d (%s), value: %s, time: %d.%d",
|
||||
ev.type, linux_evdev_type_map[ev.type], ev.code, linux_evdev_msc_code_map[ev.code], ev.value,
|
||||
ev.time.sec, ev.time.usec))
|
||||
local handled = {}
|
||||
-- We're guaranteed that ev is an array of event tables. Might be an array of *one* event, but an array nonetheless ;).
|
||||
for __, event in ipairs(ev) do
|
||||
if DEBUG.is_on then
|
||||
DEBUG:logEv(event)
|
||||
if event.type == C.EV_KEY then
|
||||
logger.dbg(string.format(
|
||||
"key event => code: %d (%s), value: %s, time: %d.%d",
|
||||
event.code, self.event_map[event.code] or linux_evdev_key_code_map[event.code], event.value,
|
||||
event.time.sec, event.time.usec))
|
||||
elseif event.type == C.EV_SYN then
|
||||
logger.dbg(string.format(
|
||||
"input event => type: %d (%s), code: %d (%s), value: %s, time: %d.%d",
|
||||
event.type, linux_evdev_type_map[event.type], event.code, linux_evdev_syn_code_map[event.code], event.value,
|
||||
event.time.sec, event.time.usec))
|
||||
elseif event.type == C.EV_ABS then
|
||||
logger.dbg(string.format(
|
||||
"input event => type: %d (%s), code: %d (%s), value: %s, time: %d.%d",
|
||||
event.type, linux_evdev_type_map[event.type], event.code, linux_evdev_abs_code_map[event.code], event.value,
|
||||
event.time.sec, event.time.usec))
|
||||
elseif event.type == C.EV_MSC then
|
||||
logger.dbg(string.format(
|
||||
"input event => type: %d (%s), code: %d (%s), value: %s, time: %d.%d",
|
||||
event.type, linux_evdev_type_map[event.type], event.code, linux_evdev_msc_code_map[event.code], event.value,
|
||||
event.time.sec, event.time.usec))
|
||||
else
|
||||
logger.dbg(string.format(
|
||||
"input event => type: %d (%s), code: %d, value: %s, time: %d.%d",
|
||||
event.type, linux_evdev_type_map[event.type], event.code, event.value,
|
||||
event.time.sec, event.time.usec))
|
||||
end
|
||||
end
|
||||
self:eventAdjustHook(event)
|
||||
if event.type == C.EV_KEY then
|
||||
local handled_ev = self:handleKeyBoardEv(event)
|
||||
if handled_ev then
|
||||
table.insert(handled, handled_ev)
|
||||
end
|
||||
elseif event.type == C.EV_ABS and event.code == ABS_OASIS_ORIENTATION then
|
||||
local handled_ev = self:handleOasisOrientationEv(event)
|
||||
if handled_ev then
|
||||
table.insert(handled, handled_ev)
|
||||
end
|
||||
elseif event.type == C.EV_ABS or event.type == C.EV_SYN then
|
||||
local handled_ev = self:handleTouchEv(event)
|
||||
-- We don't gnerate an Event for *every* input event, so, make sure we don't push nil values to the array
|
||||
if handled_ev then
|
||||
table.insert(handled, handled_ev)
|
||||
end
|
||||
elseif event.type == C.EV_MSC then
|
||||
local handled_ev = self:handleMiscEv(event)
|
||||
if handled_ev then
|
||||
table.insert(handled, handled_ev)
|
||||
end
|
||||
elseif event.type == C.EV_SDL then
|
||||
local handled_ev = self:handleSdlEv(event)
|
||||
if handled_ev then
|
||||
table.insert(handled, handled_ev)
|
||||
end
|
||||
else
|
||||
logger.dbg(string.format(
|
||||
"input event => type: %d (%s), code: %d, value: %s, time: %d.%d",
|
||||
ev.type, linux_evdev_type_map[ev.type], ev.code, ev.value,
|
||||
ev.time.sec, ev.time.usec))
|
||||
-- Received some other kind of event that we do not know how to specifically handle yet
|
||||
table.insert(handled, Event:new("GenericInput", event))
|
||||
end
|
||||
end
|
||||
self:eventAdjustHook(ev)
|
||||
if ev.type == C.EV_KEY then
|
||||
return self:handleKeyBoardEv(ev)
|
||||
elseif ev.type == C.EV_ABS and ev.code == ABS_OASIS_ORIENTATION then
|
||||
return self:handleOasisOrientationEv(ev)
|
||||
elseif ev.type == C.EV_ABS or ev.type == C.EV_SYN then
|
||||
return self:handleTouchEv(ev)
|
||||
elseif ev.type == C.EV_MSC then
|
||||
return self:handleMiscEv(ev)
|
||||
elseif ev.type == C.EV_SDL then
|
||||
return self:handleSdlEv(ev)
|
||||
else
|
||||
-- Received some other kind of event that we do not know how to specifically handle yet
|
||||
return Event:new("GenericInput", ev)
|
||||
end
|
||||
return handled
|
||||
elseif ok == false and ev then
|
||||
return Event:new("InputError", ev)
|
||||
return {
|
||||
Event:new("InputError", ev)
|
||||
}
|
||||
elseif ok == nil then
|
||||
-- No ok and no ev? Hu oh...
|
||||
return Event:new("InputError", "Catastrophic")
|
||||
return {
|
||||
Event:new("InputError", "Catastrophic")
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user