Input: Make sure GestureDetector:feedEvent will actually consume all

slots

This ensures we won't leave *any* slot in an undefined state because
we skipped parsing 'em because what we consumed first yielded a
gesture.

(In particular, this could leave a few slots dangling in the "hold"
state in corner cases involving spider-hand finger tapping ;p).

Cleans up the slot state clearing in GestureDetector to only clear the
necessary slots (e.g., two-finger gestures now only clear their own two
slots; and holds only clear their own slot).
The fact that every slot will be consumed ensures that every slot will
naturally get their contact up handled, which wasn't the case before,
hence those crappy workarounds.

As far as timerfd callbacks are concerned, this *does* introduce the
possibility of deadline collisions, so, do reimplement minimal safety
checks to ensure we run (and free) the right timerfd callback.
reviewable/pr9441/r1
NiLuJe 2 years ago
parent 32ff263294
commit 841e67e018

@ -126,31 +126,29 @@ end
--[[-- --[[--
Feeds touch events to state machine. Feeds touch events to state machine.
For drivers that bundle multiple slots in the same input frame, Note that, in a single input frame, if the same slot gets multiple events, only the last one is kept.
events are consumed in LIFO order, because of table.remove ;). Every slot in the input frame is consumed, and that in FIFO order (slot order based on appearance in the frame).
Note that, in a single input frame, if the same slot gets multiple events,
only the last one is kept.
--]] --]]
function GestureDetector:feedEvent(tevs) function GestureDetector:feedEvent(tevs)
repeat local gestures = {}
local tev = table.remove(tevs) for _, tev in ipairs(tevs) do
if tev then local slot = tev.slot
local slot = tev.slot if not self.states[slot] then
if not self.states[slot] then self:clearState(slot) -- initialize slot state
self:clearState(slot) -- initiate state end
end local ges = self.states[slot](self, tev)
local ges = self.states[slot](self, tev) if tev.id ~= -1 then
if tev.id ~= -1 then -- NOTE: tev is actually a simple reference to Input's self.ev_slots[slot],
-- NOTE: tev is actually a simple reference to Input's self.ev_slots[slot], -- which means self.last_tevs[slot] doesn't actually point to the *previous*
-- which means self.last_tevs[slot] doesn't actually point to the *previous* -- input frame for a given slot, but always points to the *current* input frame for that slot!
-- input frame for a given slot, but always points to the *current* input frame for that slot! -- Compare to self.first_tevs below, which does create a copy...
-- Compare to self.first_tevs below, which does create a copy... self.last_tevs[slot] = tev
self.last_tevs[slot] = tev
end
-- return no more than one gesture
if ges then return ges end
end end
until tev == nil if ges then
table.insert(gestures, ges)
end
end
return gestures
end end
function GestureDetector:deepCopyEv(tev) function GestureDetector:deepCopyEv(tev)
@ -420,7 +418,8 @@ function GestureDetector:tapState(tev)
} }
local tap_span = pos0:distance(pos1) local tap_span = pos0:distance(pos1)
logger.dbg("two-finger tap detected with span", tap_span) logger.dbg("two-finger tap detected with span", tap_span)
self:clearStates() self:clearState(s1)
self:clearState(s2)
return { return {
ges = "two_finger_tap", ges = "two_finger_tap",
pos = pos0:midpoint(pos1), pos = pos0:midpoint(pos1),
@ -577,7 +576,8 @@ function GestureDetector:panState(tev)
local s2 = self.input.main_finger_slot + 1 local s2 = self.input.main_finger_slot + 1
if self.detectings[s1] and self.detectings[s2] then if self.detectings[s1] and self.detectings[s2] then
local ges_ev = self:handleTwoFingerPan(tev) local ges_ev = self:handleTwoFingerPan(tev)
self:clearStates() self:clearState(s1)
self:clearState(s2)
if ges_ev then if ges_ev then
if ges_ev.ges == "two_finger_pan" then if ges_ev.ges == "two_finger_pan" then
ges_ev.ges = "two_finger_swipe" ges_ev.ges = "two_finger_swipe"
@ -812,7 +812,8 @@ function GestureDetector:handlePanRelease(tev)
if self.detectings[s1] and self.detectings[s2] then if self.detectings[s1] and self.detectings[s2] then
logger.dbg("two finger pan release detected") logger.dbg("two finger pan release detected")
pan_ev.ges = "two_finger_pan_release" pan_ev.ges = "two_finger_pan_release"
self:clearStates() self:clearState(s1)
self:clearState(s2)
else else
logger.dbg("pan release detected in slot", slot) logger.dbg("pan release detected in slot", slot)
self:clearState(slot) self:clearState(slot)
@ -840,8 +841,7 @@ function GestureDetector:holdState(tev, hold)
logger.dbg("hold_release detected in slot", slot) logger.dbg("hold_release detected in slot", slot)
local last_x = self.last_tevs[slot].x local last_x = self.last_tevs[slot].x
local last_y = self.last_tevs[slot].y local last_y = self.last_tevs[slot].y
-- NOTE: Don't leave multiple slots "stuck" in hold state, as we've cleared their timeouts in the main input loop anyway. self:clearState(slot)
self:clearStates()
return { return {
ges = "hold_release", ges = "hold_release",
pos = Geom:new{ pos = Geom:new{

@ -742,14 +742,14 @@ function Input:handleTouchEv(ev)
self:setMtSlot(MTSlot.slot, "timev", time.timeval(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_gestures = self.gesture_detector:feedEvent(self.MTSlots)
self:newFrame() self:newFrame()
if touch_ges then local ges_evs = {}
for _, touch_ges in ipairs(touch_gestures) do
self:gestureAdjustHook(touch_ges) self:gestureAdjustHook(touch_ges)
return Event:new("Gesture", table.insert(ges_evs, Event:new("Gesture", self.gesture_detector:adjustGesCoordinate(touch_ges)))
self.gesture_detector:adjustGesCoordinate(touch_ges)
)
end end
return ges_evs
end end
end end
end end
@ -801,14 +801,14 @@ function Input:handleTouchEvPhoenix(ev)
self:setMtSlot(MTSlot.slot, "timev", time.timeval(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_gestures = self.gesture_detector:feedEvent(self.MTSlots)
self:newFrame() self:newFrame()
if touch_ges then local ges_evs = {}
for _, touch_ges in ipairs(touch_gestures) do
self:gestureAdjustHook(touch_ges) self:gestureAdjustHook(touch_ges)
return Event:new("Gesture", table.insert(ges_evs, Event:new("Gesture", self.gesture_detector:adjustGesCoordinate(touch_ges)))
self.gesture_detector:adjustGesCoordinate(touch_ges)
)
end end
return ges_evs
end end
end end
end end
@ -837,14 +837,14 @@ function Input:handleTouchEvLegacy(ev)
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_gestures = self.gesture_detector:feedEvent(self.MTSlots)
self:newFrame() self:newFrame()
if touch_ges then local ges_evs = {}
for _, touch_ges in ipairs(touch_gestures) do
self:gestureAdjustHook(touch_ges) self:gestureAdjustHook(touch_ges)
return Event:new("Gesture", table.insert(ges_evs, Event:new("Gesture", self.gesture_detector:adjustGesCoordinate(touch_ges)))
self.gesture_detector:adjustGesCoordinate(touch_ges)
)
end end
return ges_evs
end end
end end
end end
@ -1162,17 +1162,30 @@ function Input:waitEvent(now, deadline)
end end
if consume_callback then if consume_callback then
local touch_ges = self.timer_callbacks[1].callback() local touch_ges
-- If it was a timerfd, we *may* also need to close the fd. local timer_idx = 1
-- NOTE: The fact that deadlines are sorted *should* ensure that the timerfd that expired if timerfd then
-- is actually at the head of the list, without us having to double-check that... -- If there's a deadline collision, make sure we call the callback that matches the timerfd returned.
-- In the rare case we would get a storm of events, we rely on the fact that each event frame -- We'll handle the next one on the next iteration, as an expired timerfd will ensure
-- *should* have a slightly different timestamp to make sure we never have a deadline collision, -- that select will return immediately.
-- combined with the fact that GestureDetector:feedEvent will only ever return a *single* gesture for i, item in ipairs(self.timer_callbacks) do
-- per input frame, even if a frame contains multiple slots on the same timestamp. if item.timerfd == timerfd then
-- NOTE: GestureDetector only calls Input:setTimeout for "hold" & "double_tap" gestures. -- In the vast majority of cases, we should find our match on the first entry ;).
timer_idx = i
touch_ges = item.callback()
break
end
end
else
-- If there's a deadline collision, we'll just handle the next one on the next iteration,
-- because the blown deadline means we'll have asked waitForEvent to return immediately.
touch_ges = self.timer_callbacks[1].callback()
end
-- NOTE: If it was a timerfd, we *may* also need to close the fd.
-- GestureDetector only calls Input:setTimeout for "hold" & "double_tap" gestures.
-- For double taps, the callback itself doesn't interact with the timer_callbacks list, -- For double taps, the callback itself doesn't interact with the timer_callbacks list,
-- but for holds, it *will* call GestureDetector:clearStates on "hold_release" (and *only* then), -- but for holds, it *will* call GestureDetector:clearState on "hold_release" (and *only* then),
-- and *that* already takes care of pop'ping the (hold) timer and closing the fd, -- and *that* already takes care of pop'ping the (hold) timer and closing the fd,
-- via Input:clearTimeout(slot, "hold")... -- via Input:clearTimeout(slot, "hold")...
if not touch_ges or touch_ges.ges ~= "hold_release" then if not touch_ges or touch_ges.ges ~= "hold_release" then
@ -1180,13 +1193,9 @@ function Input:waitEvent(now, deadline)
if timerfd then if timerfd then
input.clearTimer(timerfd) input.clearTimer(timerfd)
end end
table.remove(self.timer_callbacks, 1) table.remove(self.timer_callbacks, timer_idx)
end end
if touch_ges then if touch_ges then
-- The timers we'll encounter are for finalizing a hold or (if enabled) double tap gesture,
-- as such, it makes no sense to try to detect *multiple* subsequent gestures.
-- This is why we clear the full list of timers on the first match ;).
self:clearTimeouts()
self:gestureAdjustHook(touch_ges) self:gestureAdjustHook(touch_ges)
return { return {
Event:new("Gesture", self.gesture_detector:adjustGesCoordinate(touch_ges)) Event:new("Gesture", self.gesture_detector:adjustGesCoordinate(touch_ges))
@ -1303,10 +1312,13 @@ function Input:waitEvent(now, deadline)
table.insert(handled, handled_ev) table.insert(handled, handled_ev)
end end
elseif event.type == C.EV_ABS or event.type == C.EV_SYN then elseif event.type == C.EV_ABS or event.type == C.EV_SYN then
local handled_ev = self:handleTouchEv(event) local handled_evs = self:handleTouchEv(event)
-- We don't generate an Event for *every* input event, so, make sure we don't push nil values to the array -- handleTouchEv only returns an array of Events once it gets a SYN_REPORT,
if handled_ev then -- so more often than not, we just get a nil here ;).
table.insert(handled, handled_ev) if handled_evs then
for _, handled_ev in ipairs(handled_evs) do
table.insert(handled, handled_ev)
end
end end
elseif event.type == C.EV_MSC then elseif event.type == C.EV_MSC then
local handled_ev = self:handleMiscEv(event) local handled_ev = self:handleMiscEv(event)

Loading…
Cancel
Save