Skip to content

Commit

Permalink
The great input handling spring cleanup (koreader#1332)
Browse files Browse the repository at this point in the history
* Updated <linux/input.h> FFI stuff in order to be able to use it in front instead of re-declaring everything there.
* Fixed lua-rapidjson build w/ CMake >= 3.20
* Added some errno consts to the POSIX ffi module
* Added clock_gettime & friends to the POSIX ffi module
* Added setpriority & friends to the POSIX ffi module
* Added sched_setscheduler & friends to the POSIX ffi module
* Stopped storing useless unused references to require'd FFI modules
* Switched SDL2 input events to synthetic CLOCK_MONOTONIC (native ones are not helpful, they're relative to the app's startup).
* Simplified SDL2 input polling to actually rely on SDL2 instead of implementing a weird timeout handling ourselves.
* Preserve input event timestamps on Android, they're in a sane time base (MONOTONIC).
* Simplified and documented input polling on Android. We don't actually use the weird delayed-callback mechanism from android_native_app_glue,
  so, make that clear by relying on ALooper's native API instead of a weird mix of the two.
* Made ffiUtil.runInSubProcess tweak the subprocess scheduling and priority to match the use-case: non-interactive background tasks.
  This ensures that the kernel will schedule it accordingly without requiring weird yield hackery in front.
* Revamped input handling on most platforms, and especially on embedded pure-Linux eInk devices:
  * The C/Lua input module has been modernized and simplified.
  * Of specific note, the API of waitForEvent has been updated to get rid of protected mode calls in front.
    This allows us to get rid of the cost of handling throws in an interactivity-sensitive and hot codepath,
    and makes error-checking much easier, because parsing dynamically generated strings is not my idea of fun.
    Timeout values are also preserved in a timeval-like format, preventing truncation.
  * A timerfd backend has been implemented for the "timed callbacks" API required by GestureDetector to handle holds & double-tap detection.
    This allows us, among other things, to honor the input event's timestamps without having to resort to time-base conversions or hackery
    on devices where input events use the REALTIME clock source.
  • Loading branch information
NiLuJe authored and roygbyte committed Mar 3, 2022
1 parent c96e3b3 commit 9dc6e4c
Show file tree
Hide file tree
Showing 33 changed files with 924 additions and 298 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ $(OUTPUT_DIR)/libs/libinkview-compat.so: input/inkview-compat.c
$(OUTPUT_DIR)/libs/libkoreader-input.so: input/*.c input/*.h $(if $(or $(KINDLE),$(REMARKABLE)),$(POPEN_NOSHELL_LIB),)
@echo "Building koreader input module..."
$(CC) $(DYNLIB_CFLAGS) $(SYMVIS_FLAGS) -I$(POPEN_NOSHELL_DIR) -I./input \
$(if $(CERVANTES),-DCERVANTES,) $(if $(KOBO),-DKOBO,) $(if $(KINDLE),-DKINDLE,) $(if $(POCKETBOOK),-DPOCKETBOOK,) $(if $(REMARKABLE),-DREMARKABLE,) $(if $(SONY_PRSTUX),-DSONY_PRSTUX,)\
$(if $(CERVANTES),-DCERVANTES,) $(if $(KOBO),-DKOBO,) $(if $(KINDLE),-DKINDLE,) $(if $(LEGACY),-DKINDLE_LEGACY,) $(if $(POCKETBOOK),-DPOCKETBOOK,) $(if $(REMARKABLE),-DREMARKABLE,) $(if $(SONY_PRSTUX),-DSONY_PRSTUX,) \
-o $@ \
input/input.c \
$(if $(or $(KINDLE),$(REMARKABLE)),$(POPEN_NOSHELL_LIB),) \
Expand Down
3 changes: 1 addition & 2 deletions Makefile.third
Original file line number Diff line number Diff line change
Expand Up @@ -768,10 +768,9 @@ $(LUA_RAPIDJSON_ROCK): $(THIRDPARTY_DIR)/lua-rapidjson/*.*
-DLUA_RAPIDJSON_VER=$(LUA_RAPIDJSON_VER) \
-DCC="$(CC)" \
-DCXX="$(CXX)" \
-DCFLAGS="$(CFLAGS)"\
-DCFLAGS="$(CFLAGS)" \
-DCXXFLAGS="$(CXXFLAGS)" \
-DLDFLAGS="$(LDFLAGS)" \
-I$(LUAJIT_DIR)/src \
-DLUAROCKS="$(LUAROCKS_BINARY)" \
-DLUA_INCDIR="$(LUA_INCDIR)" \
-DLUA_LIBDIR="$(LUA_LIBDIR)" \
Expand Down
7 changes: 7 additions & 0 deletions ffi-cdecl/linux_input_decl.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,17 @@ cdecl_const(EV_FF)
cdecl_const(EV_PWR)
cdecl_const(EV_FF_STATUS)
cdecl_const(EV_MAX)
// We manually append an EV_SDL = 'S' constant.

cdecl_const(SYN_REPORT)
cdecl_const(SYN_CONFIG)
cdecl_const(SYN_MT_REPORT)
cdecl_const(SYN_DROPPED)

cdecl_const(ABS_X)
cdecl_const(ABS_Y)
cdecl_const(ABS_PRESSURE)

cdecl_const(ABS_MT_SLOT)
cdecl_const(ABS_MT_TOUCH_MAJOR)
cdecl_const(ABS_MT_TOUCH_MINOR)
Expand All @@ -41,4 +46,6 @@ cdecl_const(ABS_MT_DISTANCE)
cdecl_const(ABS_MT_TOOL_X)
cdecl_const(ABS_MT_TOOL_Y)

cdecl_const(MSC_RAW)

cdecl_struct(input_event)
72 changes: 69 additions & 3 deletions ffi-cdecl/posix_decl.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// For Linux <sched.h> stuff
#define _GNU_SOURCE

#include <sys/mman.h>
//#include <stropts.h>
#include <unistd.h>
Expand All @@ -14,12 +17,27 @@
#include <libgen.h>
#include <sys/ioctl.h>
#include <mqueue.h>
#include <time.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sched.h>
#include <errno.h>

#include "ffi-cdecl.h"

cdecl_const(EINTR)
cdecl_const(ETIME)
cdecl_const(EAGAIN)
cdecl_const(EINVAL)
cdecl_const(ENOSYS)
cdecl_const(ETIMEDOUT)

cdecl_type(off_t)
cdecl_type(time_t)
cdecl_type(suseconds_t)

cdecl_struct(timeval)
cdecl_struct(timezone)
cdecl_struct(statvfs)

cdecl_func(pipe)
Expand All @@ -35,18 +53,18 @@ cdecl_const(O_RDONLY)
cdecl_const(O_WRONLY)
cdecl_const(O_NONBLOCK)
cdecl_const(O_CLOEXEC)
cdecl_const(S_IRWXU)
cdecl_const(S_IRUSR)
cdecl_const(S_IWUSR)
cdecl_const(S_IXUSR)
cdecl_const(S_IRWXG)
cdecl_const(S_IRWXU)
cdecl_const(S_IRGRP)
cdecl_const(S_IWGRP)
cdecl_const(S_IXGRP)
cdecl_const(S_IRWX0)
cdecl_const(S_IRWXG)
cdecl_const(S_IROTH)
cdecl_const(S_IWOTH)
cdecl_const(S_IXOTH)
cdecl_const(S_IRWXO)
cdecl_func(open)
cdecl_func(mq_open)
cdecl_func(mq_receive)
Expand Down Expand Up @@ -90,6 +108,20 @@ cdecl_func(realpath)
cdecl_func(basename) // NOTE: We'll want the GNU one (c.f., https://github.com/koreader/koreader/issues/4543)
cdecl_func(dirname)

// May require librt at runtime!
cdecl_struct(timespec)
cdecl_type(clockid_t)
cdecl_const(CLOCK_REALTIME)
cdecl_const(CLOCK_REALTIME_COARSE)
cdecl_const(CLOCK_MONOTONIC)
cdecl_const(CLOCK_MONOTONIC_COARSE)
cdecl_const(CLOCK_MONOTONIC_RAW)
cdecl_const(CLOCK_BOOTTIME)
cdecl_const(CLOCK_TAI)
cdecl_func(clock_getres)
cdecl_func(clock_gettime)
cdecl_func(clock_settime)

cdecl_func(malloc)
cdecl_func(calloc)
cdecl_func(free)
Expand Down Expand Up @@ -120,3 +152,37 @@ cdecl_func(fdatasync)
cdecl_func(setenv)
cdecl_func(unsetenv)
//cdecl_func(_putenv) // Win32

cdecl_type(id_t)
cdecl_enum(__priority_which)
cdecl_type(__priority_which_t)
cdecl_func(getpriority)
cdecl_func(setpriority)

cdecl_type(pid_t)
cdecl_struct(sched_param)
cdecl_const(SCHED_OTHER)
cdecl_const(SCHED_BATCH)
cdecl_const(SCHED_IDLE)
cdecl_const(SCHED_FIFO)
cdecl_const(SCHED_RR)
cdecl_const(SCHED_RESET_ON_FORK)
cdecl_func(sched_getscheduler)
cdecl_func(sched_setscheduler)
cdecl_func(sched_getparam)
cdecl_func(sched_setparam)
// No Glibc wrappers around these syscalls:
/*
cdecl_struct(sched_attr)
cdecl_const(SCHED_FLAG_RESET_ON_FORK)
cdecl_const(SCHED_FLAG_RECLAIM)
cdecl_const(SCHED_FLAG_DL_OVERRUN)
cdecl_const(SCHED_FLAG_KEEP_POLICY)
cdecl_const(SCHED_FLAG_KEEP_PARAMS)
cdecl_func(sched_getattr)
cdecl_func(sched_setattr)
*/
cdecl_type(cpu_set_t)
cdecl_func(sched_getaffinity)
cdecl_func(sched_setaffinity)
cdecl_func(sched_yield)
1 change: 0 additions & 1 deletion ffi-cdecl/rtc_cdecl.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ cdecl_const(RTC_RD_TIME)

cdecl_type(time_t)
cdecl_struct(tm)
cdecl_struct(timezone)
cdecl_func(time)
cdecl_func(gmtime)
cdecl_func(localtime)
Expand Down
68 changes: 32 additions & 36 deletions ffi/SDL2_0.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ local ffi = require("ffi")
local util = require("ffi/util")
local C = ffi.C

local dummy = require("ffi/SDL2_0_h")
local dummy = require("ffi/linux_input_h")
require("ffi/posix_h")
require("ffi/SDL2_0_h")
require("ffi/linux_input_h")

-----------------------------------------------------------------

Expand All @@ -42,9 +43,6 @@ local function SDL_Linked_Version_AtLeast(x, y, z)
return SDL_VersionNum(sdl_linked_ver[0].major, sdl_linked_ver[0].minor, sdl_linked_ver[0].patch) >= SDL_VersionNum(x, y, z)
end

-- for frontend SDL event handling
local EV_SDL = 53 -- ASCII code for S

local S = {
w = 0, h = 0,
screen = nil,
Expand Down Expand Up @@ -87,6 +85,7 @@ function S.open(w, h, x, y)
SDL.SDL_SetMainReady()

if SDL.SDL_Init(bit.bor(SDL.SDL_INIT_VIDEO,
SDL.SDL_INIT_EVENTS,
SDL.SDL_INIT_JOYSTICK,
SDL.SDL_INIT_GAMECONTROLLER)) ~= 0 then
error("Cannot initialize SDL: " .. ffi.string(SDL.SDL_GetError()))
Expand Down Expand Up @@ -160,13 +159,21 @@ end
local inputQueue = {}

local function genEmuEvent(evtype, code, value)
local secs, usecs = util.gettime()
-- NOTE: SDL timestamps are in ms since application startup, which doesn't tell us anything useful,
-- so, use synthetic ones in the same timescale as the UI.
local timespec = ffi.new("struct timespec")
C.clock_gettime(C.CLOCK_MONOTONIC_COARSE, timespec)
local timev = {
sec = tonumber(timespec.tv_sec),
-- ns to µs
usec = math.floor(tonumber(timespec.tv_nsec / 1000)),
}

local ev = {
type = tonumber(evtype),
code = tonumber(code),
value = tonumber(value) or value,
time = { sec = secs, usec = usecs },
time = timev,
}
table.insert(inputQueue, ev)
end
Expand All @@ -184,7 +191,7 @@ local function handleWindowEvent(event_window)
elseif (event_window.event == SDL.SDL_WINDOWEVENT_RESIZED
or event_window.event == SDL.SDL_WINDOWEVENT_SIZE_CHANGED
or event_window.event == SDL.SDL_WINDOWEVENT_MOVED) then
genEmuEvent(EV_SDL, event_window.event, event_window)
genEmuEvent(C.EV_SDL, event_window.event, event_window)
end
end

Expand Down Expand Up @@ -246,38 +253,25 @@ local SDL_BUTTON_LEFT = 1

local is_in_touch = false

function S.waitForEvent(usecs)
usecs = usecs or -1
function S.waitForEvent(sec, usec)
local event = ffi.new("union SDL_Event")
local countdown = usecs
-- TimeVal's :tomsecs if we were passed one to begin with, otherwise, -1 => block
local timeout = sec and math.floor(sec * 1000000 + usec + 0.5) / 1000 or -1
while true do
-- check for queued events
if #inputQueue > 0 then
-- return oldest FIFO element
return table.remove(inputQueue, 1)
return true, table.remove(inputQueue, 1)
end

-- otherwise, wait for event
local got_event = 0
if usecs < 0 then
got_event = SDL.SDL_WaitEvent(event);
else
-- timeout mode - use polling
while countdown > 0 and got_event == 0 do
got_event = SDL.SDL_PollEvent(event)
if got_event == 0 then
-- no event, wait 10 msecs before polling again
SDL.SDL_Delay(10)
countdown = countdown - 10000
end
end
end
local got_event = SDL.SDL_WaitEventTimeout(event, timeout)
if got_event == 0 then
error("Waiting for input failed: timeout\n")
-- ETIME
return false, C.ETIME
end

-- if we got an event, examine it here and generate
-- events for koreader
-- if we got an event, examine it here and generate events for koreader
if ffi.os == "OSX" and (event.type == SDL.SDL_FINGERMOTION or
event.type == SDL.SDL_FINGERDOWN or
event.type == SDL.SDL_FINGERUP) then
Expand All @@ -288,7 +282,7 @@ function S.waitForEvent(usecs)
elseif event.type == SDL.SDL_KEYUP then
genEmuEvent(C.EV_KEY, event.key.keysym.sym, 0)
elseif event.type == SDL.SDL_TEXTINPUT then
genEmuEvent(EV_SDL, SDL.SDL_TEXTINPUT, ffi.string(event.text.text))
genEmuEvent(C.EV_SDL, SDL.SDL_TEXTINPUT, ffi.string(event.text.text))
elseif event.type == SDL.SDL_MOUSEMOTION
or event.type == SDL.SDL_FINGERMOTION then
local is_finger = event.type == SDL.SDL_FINGERMOTION
Expand Down Expand Up @@ -316,14 +310,15 @@ function S.waitForEvent(usecs)
end
elseif event.type == SDL.SDL_MOUSEBUTTONUP
or event.type == SDL.SDL_FINGERUP then
is_in_touch = false;
is_in_touch = false
genEmuEvent(C.EV_ABS, C.ABS_MT_TRACKING_ID, -1)
genEmuEvent(C.EV_SYN, C.SYN_REPORT, 0)
elseif event.type == SDL.SDL_MOUSEBUTTONDOWN
or event.type == SDL.SDL_FINGERDOWN then
local is_finger = event.type == SDL.SDL_FINGERDOWN
if not is_finger and event.button.button ~= SDL_BUTTON_LEFT then
return
-- Not a left-click?
return false, C.ENOSYS
end
-- use mouse click to simulate single tap
is_in_touch = true
Expand All @@ -334,15 +329,15 @@ function S.waitForEvent(usecs)
is_finger and event.tfinger.y * S.h or event.button.y)
genEmuEvent(C.EV_SYN, C.SYN_REPORT, 0)
elseif event.type == SDL.SDL_MULTIGESTURE then
genEmuEvent(EV_SDL, SDL.SDL_MULTIGESTURE, event.mgesture)
genEmuEvent(C.EV_SDL, SDL.SDL_MULTIGESTURE, event.mgesture)
elseif event.type == SDL.SDL_MOUSEWHEEL then
genEmuEvent(EV_SDL, SDL.SDL_MOUSEWHEEL, event.wheel)
genEmuEvent(C.EV_SDL, SDL.SDL_MOUSEWHEEL, event.wheel)
elseif event.type == SDL.SDL_DROPFILE then
local dropped_file_path = ffi.string(event.drop.file)
genEmuEvent(EV_SDL, SDL.SDL_DROPFILE, dropped_file_path)
genEmuEvent(C.EV_SDL, SDL.SDL_DROPFILE, dropped_file_path)
elseif event.type == SDL.SDL_DROPTEXT then
local dropped_text = ffi.string(event.drop.file)
genEmuEvent(EV_SDL, SDL.SDL_DROPTEXT, dropped_text)
genEmuEvent(C.EV_SDL, SDL.SDL_DROPTEXT, dropped_text)
elseif event.type == SDL.SDL_WINDOWEVENT then
handleWindowEvent(event.window)
--- Gamepad support ---
Expand Down Expand Up @@ -401,6 +396,7 @@ function S.waitForEvent(usecs)
genEmuEvent(C.EV_KEY, 1073741903, 1)
end
elseif event.type == SDL.SDL_QUIT then
-- NOTE: Generated on SIGTERM, among other things. (Not SIGINT, because LuaJIT already installs a handler for that).
-- send Alt + F4
genEmuEvent(C.EV_KEY, 1073742050, 1)
genEmuEvent(C.EV_KEY, 1073741885, 1)
Expand Down
2 changes: 1 addition & 1 deletion ffi/blitbuffer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ local uint32pt = ffi.typeof("uint32_t*")
local uint16pt = ffi.typeof("uint16_t*")
local uint8pt = ffi.typeof("uint8_t*")
local uint8pt_rodata = ffi.typeof("const uint8_t*")
local posix = require("ffi/posix_h") -- luacheck: ignore 211
require("ffi/posix_h")

-- the following definitions are redundant.
-- they need to be since only this way we can set
Expand Down
4 changes: 2 additions & 2 deletions ffi/framebuffer_einkfb.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
local ffi = require("ffi")
local BB = require("ffi/blitbuffer")
local dummy = require("ffi/posix_h")
local dummy = require("ffi/einkfb_h")
require("ffi/posix_h")
require("ffi/einkfb_h")
local C = ffi.C

local framebuffer = {}
Expand Down
2 changes: 1 addition & 1 deletion ffi/framebuffer_mxcfb.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ local ffiUtil = require("ffi/util")
local BB = require("ffi/blitbuffer")
local C = ffi.C

local dummy = require("ffi/posix_h")
require("ffi/posix_h")

local band = bit.band
local bor = bit.bor
Expand Down
Loading

0 comments on commit 9dc6e4c

Please sign in to comment.