diff --git a/frontend/dispatcher.lua b/frontend/dispatcher.lua index 0db469ad1..41f2836aa 100644 --- a/frontend/dispatcher.lua +++ b/frontend/dispatcher.lua @@ -81,7 +81,8 @@ local settingsList = { reboot = {category="none", event="RequestReboot", title=_("Reboot the device"), device=true, condition=Device:canReboot()}, poweroff = {category="none", event="RequestPowerOff", title=_("Power off"), device=true, condition=Device:canPowerOff(), separator=true}, exit = {category="none", event="Exit", title=_("Exit KOReader"), device=true}, - toggle_hold_corners = {category="none", event="IgnoreHoldCorners", title=_("Toggle hold corners"), device=true, separator=true}, + toggle_hold_corners = {category="none", event="IgnoreHoldCorners", title=_("Toggle hold corners"), device=true}, + toggle_touch_input = {category="none", event="IgnoreTouchInput", title=_("Toggle touch input"), device=true, separator=true}, toggle_rotation = {category="none", event="SwapRotation", title=_("Toggle orientation"), device=true}, invert_rotation = {category="none", event="InvertRotation", title=_("Invert rotation"), device=true}, iterate_rotation = {category="none", event="IterateRotation", title=_("Rotate by 90° CW"), device=true}, @@ -237,6 +238,7 @@ local dispatcher_menu_order = { "poweroff", "toggle_hold_corners", + "toggle_touch_input", "toggle_gsensor", "rotation_mode", "toggle_rotation", @@ -939,9 +941,8 @@ end --[[-- Calls the events in a settings list arguments are: - 1) a reference to the uimanager - 2) the settings table - 3) optionally a `gestures`object + 1) the settings table + 2) optionally a `gestures` object --]]-- function Dispatcher:execute(settings, gesture) if settings.settings ~= nil and settings.settings.show_as_quickmenu == true then diff --git a/frontend/ui/widget/container/inputcontainer.lua b/frontend/ui/widget/container/inputcontainer.lua index afb83a8b5..fd40d3e9c 100644 --- a/frontend/ui/widget/container/inputcontainer.lua +++ b/frontend/ui/widget/container/inputcontainer.lua @@ -134,7 +134,7 @@ function InputContainer:registerTouchZones(zones) if self._zones[zone.id] then self.touch_zone_dg:removeNode(zone.id) end - self._zones[zone.id]= { + self._zones[zone.id] = { def = zone, handler = zone.handler, gs_range = GestureRange:new{ @@ -269,6 +269,76 @@ function InputContainer:onGesture(ev) end end +-- Will be overloaded by the Gestures plugin, if enabled, for use in _onGestureFiltered +function InputContainer:_isGestureAlwaysActive(ges, multiswipe_directions) + -- If the plugin isn't enabled, IgnoreTouchInput can still be emitted by Dispatcher (e.g., via Profile or QuickMenu). + -- Regardless of that, we still want to block all gestures anyway, as our own onResume handler will ensure + -- that the standard onGesture handler is restored on the next resume cycle, + -- allowing one to restore input handling automatically. + return false +end +InputContainer.isGestureAlwaysActive = InputContainer._isGestureAlwaysActive + +-- Filtered variant that only lets specific touch zones marked as "always active" through. +-- (This is used by the "toggle_touch_input" Dispatcher action). +function InputContainer:_onGestureFiltered(ev) + for _, tzone in ipairs(self._ordered_touch_zones) do + if self:isGestureAlwaysActive(tzone.def.id, ev.multiswipe_directions) and tzone.gs_range:match(ev) and tzone.handler(ev) then + return true + end + end + -- No ges_events at all, although if the need ever arises, we could also support an "always active" marker for those ;). + if self.stop_events_propagation then + return true + end +end + +-- NOTE: Monkey-patching InputContainer.onGesture allows us to effectively disable touch input, +-- because barely any InputContainer subclasses implement onGesture, meaning they all inherit this one, +-- making this specific method in this specific widget the only piece of code that handles the Gesture +-- Events sent by GestureDetector. +-- We would need to be slightly more creative if subclassed widgets did overload it in in any meaningful way[1]. +-- (i.e., use a broadcast Event, don't stop its propagation, and swap self.onGesture in every instance +-- while still only swapping Input.onGesture once...). +-- +-- [1] The most common implementation you'll see is a NOP for ReaderUI modules that defer gesture handling to ReaderUI. +-- Notification also implements a simple one to dismiss notifications on any user input, +-- which is something that doesn't impede our goal, which is why we don't need to deal with it. +function InputContainer:onIgnoreTouchInput(toggle) + local Notification = require("ui/widget/notification") + if toggle == false then + -- Restore the proper onGesture handler if we disabled it + if InputContainer._onGesture then + InputContainer.onGesture = InputContainer._onGesture + InputContainer._onGesture = nil + Notification:notify("Restored touch input") + end + elseif toggle == true then + -- Replace the onGesture handler w/ the minimal one if that's not already the case + if not InputContainer._onGesture then + InputContainer._onGesture = InputContainer.onGesture + InputContainer.onGesture = InputContainer._onGestureFiltered + Notification:notify("Disabled touch input") + end + else + -- Toggle the current state + if InputContainer._onGesture then + self:onIgnoreTouchInput(false) + else + self:onIgnoreTouchInput(true) + end + end + + -- We only affect the base class, once is enough ;). + return true +end + +function InputContainer:onResume() + -- Always restore touch input on resume, to avoid confusion for scatter-brained users ;). + -- It's also helpful when the IgnoreTouchInput event is emitted by Dispatcher through other means than Gestures. + self:onIgnoreTouchInput(false) +end + function InputContainer:onInput(input, ignore_first_hold_release) local InputDialog = require("ui/widget/inputdialog") self.input_dialog = InputDialog:new{ diff --git a/plugins/gestures.koplugin/main.lua b/plugins/gestures.koplugin/main.lua index b7b99e4e1..8ad8b61be 100644 --- a/plugins/gestures.koplugin/main.lua +++ b/plugins/gestures.koplugin/main.lua @@ -9,6 +9,7 @@ local Geom = require("ui/geometry") local GestureDetector = require("device/gesturedetector") local GestureRange = require("ui/gesturerange") local InfoMessage = require("ui/widget/infomessage") +local InputContainer = require("ui/widget/container/inputcontainer") local InputDialog = require("ui/widget/inputdialog") local LuaSettings = require("luasettings") local Screen = require("device").screen @@ -120,6 +121,22 @@ Multiswipes allow you to perform complex gestures built up out of multiple swipe These advanced gestures consist of either straight swipes or diagonal swipes. To ensure accuracy, they can't be mixed.]]) +-- If the gesture contains the "toggle_touch_input" action, +-- mark it "always active" to make sure that InputContainer won't block it after the IgnoreTouchInput Event. +function Gestures:isGestureAlwaysActive(ges, multiswipe_directions) + -- Handle multiswipes properly + -- NOTE: This is a bit clunky, as ges comes from the list of registered touch zones, + -- while multiswipe_directions comes from the actual input event. + -- Alas, all our multiswipe gestures are handled by a single "multiswipe" zone. + if self.multiswipes_enabled then + if ges == "multiswipe" and multiswipe_directions then + ges = "multiswipe_" .. self:safeMultiswipeName(multiswipe_directions) + end + end + + return self.gestures[ges] and self.gestures[ges].toggle_touch_input +end + function Gestures:init() local defaults_path = FFIUtil.joinPath(self.path, "defaults.lua") if not lfs.attributes(gestures_path, "mode") then @@ -184,6 +201,13 @@ function Gestures:init() self.ui.menu:registerToMainMenu(self) Dispatcher:init() self:initGesture() + -- Overload InputContainer's stub to allow it to recognize "always active" gestures + InputContainer.isGestureAlwaysActive = function(this, ges, multiswipe_directions) return self:isGestureAlwaysActive(ges, multiswipe_directions) end +end + +function Gestures:onCloseWidget() + -- Restore the stub implementation on teardown, to avoid pinning a stale instance of ourselves + InputContainer.isGestureAlwaysActive = InputContainer._isGestureAlwaysActive end function Gestures:gestureTitleFunc(ges)