monome / norns

norns is many sound instruments.
http://monome.org
GNU General Public License v3.0
621 stars 144 forks source link

higher-level keyboard input module #1251

Closed catfact closed 3 years ago

catfact commented 3 years ago

@tehn expressed desire for a higher-level keyboard interface in lua, which would automatically be used when a device supporting keyboard interface is connected. whereas now, we don't distinguish between different HID classes.

i think such a distinction would have to happen at the C level

tehn commented 3 years ago

my goal (i think this is a worthy goal?) is to make the keyboard a special case rather than a generic HID. this way we could manage its behavior (ie, to control the menu/passwords/etc) with a focus-sharing layer, and also provide the keycode decoding by default (right now keyboard-centric scripts all have redundant libs for managing the keyboard).

some backnotes:

it's pretty easy to grab the keyboard via evdev similarly to how we collect key/encoder data in matron presently. devices can be queried for names, and "keyboard" shows up in that name, so i could autoselect. weirdly (as seen in HID menu also) keyboards enumerate twice, and it's the first instance that sends data (tested with only two keyboards, however). succeeded in testing similarly on the laptop... keyboard shows up fine.

one issue with usb keyboards is a disconnection (ie, dev path vanishes) will hose the thread, flooding repeat data. so i'm not sure if a path monitoring thread is also required, to kill the process when disconnected.

here's a good working example: https://github.com/darkelement/simplistic-examples/blob/master/evdev/evdev.c

trying to get more info from udev brought me back to considering smarter device detection (ie, check manufacturer strings) which is weirder than i'd hoped... because you have to traverse the parent nodes until you get a device with the manufacturer attribute. for a crow it's two steps back, but for FTDI devices the manufacturer string is something like 4 parents up, so i'd need to write a smarter traversal mechanism.

the attr tree can be read such: udevadm info --name=/dev/ttyUSB0 --attribute-walk

but back to the keyboard... might be able to check an attr for device class, rather than trying to match the word "keyboard" (i mean, that's how the kernel would do it, right...)

catfact commented 3 years ago

IMHO: this does not have to be a complicated change or a parallel device type in C.

yes: we can check an evdev device attribute to see whether a given HID device supports the keyboard interface class. we can pass this flag on to lua when the device is added and take it from there.

catfact commented 3 years ago

(i have code for this somewhere. will see if i can get the interface-detection step working this evening.)

catfact commented 3 years ago

ok... actually, code i have is for lower level USB host driver. there we can inspect interface subclass and proctocol (keyboard and mouse.)

but when using evdev, we already have both a higher-level and more-granular description of the device in the form of what event codes it emits. AFAICT, checking these is actually the way that linux frameworks distnguish between HID device categories.

really, this makes sense: "keyboard" and "mouse" are not really homogenous categories. a keyboard may or may not have media keys, fn keys, numpad, katagana/hiragana, etc.... mouse may have lots of buttons or only 1.. and so on.

so, an "ascii keyboard" module should really only care if a device emits event codes corresponding to a full range of ascii characters. a "pointer" module should only care if a device emits EV_REL types; a gamepad should have EV_ABS and/or BTN.... keys... and so on.

all this went into consideration when i constructed the HID interface. in the Hid module, the entire hierarchy of supported types and codes is reported, as arguments to the device constructor.

however! i sort of punted the question of what, if anything to do with those tables. so they are not actually stored in the device object, they are not actually passed on to the Hid.add callbacks, and no attempt is made to examine them to determine what functional type of device this is. this was a combination of oversight and exhaustion on my part, and the work should be completed.

long story short: we can and should do everything in lua using what is already there. the main thing is to add a little module that parses the types/codes tables passed to the Hid.new constructor. i will put a little time into this right now, as a POC / breadcrumb if nothing else....

catfact commented 3 years ago

as a side effect to completing this work, we should be able to clean up and silence the multiple "useless" devices that appear with many keyboards.

for example, i added some statements to the HID constructor (Hid.new()... in hid.lua):


  print("\nHID device constructor!")
  print("event types supported:")
  tab.print(types)
  print("event codes supported, per type:")
  for k,v in pairs(codes) do
    print("type: " .. types[k] .. " :")
    tab.print(v)
  end

plugging in a no-name, but pretty nice mechanical keyboard gives me this (long) output:

HID device constructor!
event types supported:
1   0
2   1
3   4
4   17
5   20
event codes supported, per type:
type: 0 :
1   0
2   1
3   2
4   3
5   4
6   5
7   6
8   7
9   8
10  9
11  10
12  11
13  12
14  13
15  14
type: 1 :
1   1
2   2
3   3
4   4
5   5
6   6
7   7
8   8
9   9
10  10
11  11
12  12
13  13
14  14
15  15
16  16
17  17
18  18
19  19
20  20
21  21
22  22
23  23
24  24
25  25
26  26
27  27
28  28
29  29
30  30
31  31
32  32
33  33
34  34
35  35
36  36
37  37
38  38
39  39
40  40
41  41
42  42
43  43
44  44
45  45
46  46
47  47
48  48
49  49
50  50
51  51
52  52
53  53
54  54
55  55
56  56
57  57
58  58
59  59
60  60
61  61
62  62
63  63
64  64
65  65
66  66
67  67
68  68
69  69
70  70
71  71
72  72
73  73
74  74
75  75
76  76
77  77
78  78
79  79
80  80
81  81
82  82
83  83
84  85
85  86
86  87
87  88
88  89
89  90
90  91
91  92
92  93
93  94
94  95
95  96
96  97
97  98
98  99
99  100
100 102
101 103
102 104
103 105
104 106
105 107
106 108
107 109
108 110
109 111
110 113
111 114
112 115
113 116
114 117
115 119
116 121
117 122
118 123
119 124
120 125
121 126
122 127
123 128
124 129
125 130
126 131
127 132
128 133
129 134
130 135
131 136
132 137
133 138
134 183
135 184
136 185
137 186
138 187
139 188
140 189
141 190
142 191
143 192
144 193
145 194
146 240
type: 4 :
1   4
type: 17 :
1   0
2   1
3   2
4   3
5   4
type: 20 :
1   0
HID device was added:   2   USB Keyboard

HID device constructor!
event types supported:
1   0
2   1
3   4
event codes supported, per type:
type: 0 :
1   0
2   1
3   2
4   3
5   4
6   5
7   6
8   7
9   8
10  9
11  10
12  11
13  12
14  13
15  14
type: 1 :
1   113
2   114
3   115
4   155
5   163
6   164
7   165
8   172
type: 4 :
1   4
HID device was added:   3   USB Keyboard 2

HID device constructor!
event types supported:
1   0
2   1
3   4
event codes supported, per type:
type: 0 :
1   0
2   1
3   2
4   3
5   4
6   5
7   6
8   7
9   8
10  9
11  10
12  11
13  12
14  13
15  14
type: 1 :
1   116
2   142
3   143
type: 4 :
1   4
HID device was added:   4   USB Keyboard 3

this thing enumerates as 3 devices, all of which support only event types EV_SYN (which is for synchronization events; frankly i don't understand these and would prefer to ignore them if possible!), EV_KEY (keys, natch), and EV_MSC (for the SCAN event.)

we don't have a handy way of getting human-readable descriptions of what all the codes are (which is kinda what this issue is about), but by cross-referencing with hid_events.lua we can determine that:


a useful reference on event types lives here: https://www.kernel.org/doc/Documentation/input/event-codes.txt

tehn commented 3 years ago

fixed by https://github.com/monome/norns/pull/1271