aboutsummaryrefslogtreecommitdiffhomepage
path: root/evdev.lua
blob: dcf5a57b750b3eba18a6deea1389217b30dcafd6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
local ffi = require "ffi"
local C = ffi.C

cdef_include "evdev.h"
cdef_safe[[
/*
 * These definitions are copied from input-event-codes.h
 */
enum applause_evdev_type {
	EV_KEY			= 0x01,
	EV_REL			= 0x02,
	EV_ABS			= 0x03
};

enum applause_evdev_rel {
	REL_X			= 0x00,
	REL_Y			= 0x01,
	REL_Z			= 0x02,
	REL_RX			= 0x03,
	REL_RY			= 0x04,
	REL_RZ			= 0x05,
	REL_HWHEEL		= 0x06,
	REL_DIAL		= 0x07,
	REL_WHEEL		= 0x08,
	REL_MISC		= 0x09,
	REL_WHEEL_HI_RES	= 0x0b,
	REL_HWHEEL_HI_RES	= 0x0c
};

enum applause_evdev_abs {
	ABS_X			= 0x00,
	ABS_Y			= 0x01,
	ABS_Z			= 0x02
};
]]

EvdevStream = DeriveClass(Stream)

function EvdevStream:ctor(id, grab)
	local grab = grab == nil or grab

	local node
	if type(id) == "number" then
		node = "/dev/input/event"..id
	else
		assert(type(id) == "string")
		-- id is assumed to be a Lua pattern to match against the device name
		local name
		local i = 0
		repeat
			node = "/dev/input/event"..i
			local buffer = ffi.gc(C.applause_evdev_getname(node), C.free)
			if buffer == nil then error("Evdev device not found!") end
			name = ffi.string(buffer)
			i = i + 1
		until name:match(id)
	end

	-- Creating only one object has the advantage that the device can be
	-- grabbed.
	-- NOTE: To reliably ungrab the device, the entire object needs to be niled
	-- and you have to drive the garbage collector manually.
	self.evdev = ffi.gc(C.applause_evdev_new(node, grab), C.applause_evdev_free)
	if self.evdev == nil then error("Evdev device not found!") end
end

function EvdevStream:gtick()
	local evdev = self.evdev
	local sample = ffi.new("applause_evdev_sample[1]")

	return function()
		-- EvdevStreams only have a single event queue no matter how often they are
		-- gticked. That's why it must always be cached.
		local cached_sample = sampleCache[self]
		if not cached_sample then
			C.applause_evdev_pull(evdev, sample)
			sampleCache[self] = sample[0]
			cached_sample = sample[0]
		end
		return cached_sample
	end
end

-- Relative devices like some mouses and the trackpoint.
-- The coordinate is returned in `resolution` steps between [-1, 1]
-- NOTE: `code` is optional (default: REL_X) and you can specify a number (0 or 1)
-- or string ('REL_X', 'REL_Y') as well.
function Stream:evrel(code, resolution)
	code = ffi.cast("enum applause_evdev_rel", code)
	resolution = resolution or 1000
	local min, max = math.min, math.max

	return self:scan(function(last, sample)
		last = last or 0
		return sample.type == C.EV_REL and sample.code == code and
		       min(max(last+sample.value, 0), resolution) or last
	end) / (resolution/2) - 1
end

-- FIXME: min and max can be read from the properties of the corresponding code.
-- This would however require us to do all postprocessing already in EvdevStream:gtick()
-- or even in applause_evdev_new().
function Stream:evabs(code, min, max)
	code = ffi.cast("enum applause_evdev_abs", code)

	return self:scan(function(last, sample)
		last = last or 0
		return sample.type == C.EV_ABS and sample.code == code and
		       (sample.value - min)/((max-min)/2) - 1 or last
	end)
end

-- FIXME: We haven't got constants for all KEY_X constants,#
-- so you will have to look up the key code in input-event-codes.h.
function Stream:evkey(key)
	return self:scan(function(last, sample)
		last = last or 0
		return sample.type == C.EV_KEY and sample.code == key and
		       sample.value or last
	end)
end