forked from RoganDawes/P4wnP1_aloa
-
Notifications
You must be signed in to change notification settings - Fork 1
/
keyboard_led.go
483 lines (409 loc) · 16.2 KB
/
keyboard_led.go
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
package hid
import (
"sync"
"os"
"time"
"log"
"fmt"
"context"
)
var (
//privateCurrentLEDWatcher *HIDKeyboardLEDStateWatcher = nil
privateCurrentKeyboardLEDWatcher *KeyboardLEDStateWatcher = nil
)
const (
MaskNumLock = 1 << 0
MaskCapsLock = 1 << 1
MaskScrollLock = 1 << 2
MaskCompose = 1 << 3
MaskKana = 1 << 4
MaskNone = 1 << 7 //not really a mask, indicates no change
MaskAny = MaskNumLock | MaskCapsLock | MaskScrollLock | MaskCompose | MaskKana
MaskAnyOrNone = MaskNumLock | MaskCapsLock | MaskScrollLock | MaskCompose | MaskKana | MaskNone
)
type HIDLEDState struct {
NumLock bool
CapsLock bool
ScrollLock bool
Compose bool
Kana bool
}
func (s *HIDLEDState) fillState(stateByte byte) {
if stateByte & MaskNumLock > 0 { s.NumLock = true }
if stateByte & MaskCapsLock > 0 { s.CapsLock = true }
if stateByte & MaskScrollLock > 0 { s.ScrollLock = true }
if stateByte & MaskCompose > 0 { s.Compose = true }
if stateByte & MaskKana > 0 { s.Kana = true }
return
}
func (s HIDLEDState) AnyOn() bool {
return s.NumLock || s.CapsLock || s.ScrollLock || s.Compose || s.Kana
}
func (s HIDLEDState) Mask(mask HIDLEDState) (result HIDLEDState) {
result.NumLock = s.NumLock && mask.NumLock
result.CapsLock = s.CapsLock && mask.CapsLock
result.ScrollLock = s.ScrollLock && mask.ScrollLock
result.Compose = s.Compose && mask.Compose
result.Kana = s.Kana && mask.Kana
return
}
func (s HIDLEDState) Changes(other HIDLEDState) (result HIDLEDState) {
result.NumLock = s.NumLock != other.NumLock
result.CapsLock = s.CapsLock != other.CapsLock
result.ScrollLock = s.ScrollLock != other.ScrollLock
result.Compose = s.Compose != other.Compose
result.Kana = s.Kana != other.Kana
return
}
type lockableListenerMap struct {
sync.Mutex
m map[*KeyboardLEDStateListener]bool
}
type KeyboardLEDStateWatcher struct {
ledState *HIDLEDState //latest global LED state
listeners *lockableListenerMap //map of registered listeners
ledStateFile *os.File
hasInitialState bool //marks if the initial state is define (gets true after first LED state ha been read)
//listener which should be registered are put into a zero length channel, to allow blocking till a listener is added
//in case there isn't already a listener (avoid reading LED states, without having a single listener consuming them)
listenersToAdd chan *KeyboardLEDStateListener
readerToDispatcher chan HIDLEDState
ctx context.Context
cancelFunc context.CancelFunc
isUsable bool
}
func NewLEDStateWatcher(ctx context.Context, devFilePath string) (res *KeyboardLEDStateWatcher,err error) {
//try to open the devFile
devFile, err := os.Open(devFilePath)
if err != nil { return }
if privateCurrentKeyboardLEDWatcher != nil {
privateCurrentKeyboardLEDWatcher.Stop()
}
ctx,cancel := context.WithCancel(ctx)
res = &KeyboardLEDStateWatcher{
ledState: &HIDLEDState{},
hasInitialState: false,
ledStateFile: devFile,
listeners: &lockableListenerMap{
m: make(map[*KeyboardLEDStateListener]bool),
},
// Buffer at least one listener, to avoid blocking when one is added, the channel is only used to block the
// dispatchLoop, in case there's no registered listener (by reading from the listenerToAdd chanel
listenersToAdd: make(chan *KeyboardLEDStateListener,1),
readerToDispatcher: make(chan HIDLEDState), //communicates new LED states to from file reader loop to dispatcher loop, blocks till consumed
ctx: ctx,
cancelFunc: cancel,
/*
ledState: &HIDLEDState{},
listeners: &listenersmap{m: make(map[*HIDKeyboardLEDListener]*HIDKeyboardLEDListener)},
addListeners: make(chan *HIDKeyboardLEDListener,1), //Buffer at least one, to avoid blocking `CreateAndAddNewListener` (we only want to block `dispatchListeners` in case there's no listener)
*/
}
go res.readLoop()
go res.dispatchLoop()
privateCurrentKeyboardLEDWatcher = res
res.isUsable = true
return
}
func (w *KeyboardLEDStateWatcher) RetrieveNewListener() (l *KeyboardLEDStateListener, err error) {
if !w.isUsable { return nil, ErrNotAllowed }
//create listener and assgin watcher as parent
l = &KeyboardLEDStateListener{
isMarkedForDeletion: false,
changedLeds: make(chan HIDLEDState),
//interrupt: make(chan struct{}),
ledWatcher: w,
}
//addListener to map
w.listenersToAdd <- l
return l,nil
}
// - unregisters all listeners
// - listeners which are still processed receive an interrupt on their interrupt channel, it is the responsibility
// of the listener to deal with this interrupt and close the channel after the interrupt is consumed (wrting to the channel
// happens only once)
// - close ledStateFile
func (w *KeyboardLEDStateWatcher) Stop() (err error) {
//ToDo: A crash occurs from time to time, if the underlying file is already gone (gadget destroyed), this has to be called before the file is removed
err = w.ledStateFile.Close() //produces an error in readLoop which gets translated to an interrupt
return nil
}
//reads the LED state from device file, till os.File object is closed
func (w *KeyboardLEDStateWatcher) readLoop() {
fmt.Println("***Starting LED reader")
defer w.ledStateFile.Close() //Assure File object is closed after this loop, if the closed file wasn't the reason for loop abort
buf := make([]byte, 1)
for {
//fmt.Println("-----------\nLED READ LOOP\n-------------------")
n,err := w.ledStateFile.Read(buf)
if err != nil {
log.Printf("Keyboard LED watcher: LED file seems to be closed %s: %v\n...interrupting dispatcher loop!\n", w.ledStateFile.Name(), err)
//mark watcher as unusable
w.isUsable = false
//interrupt the dispatcher loop, by cancelling the context
w.cancelFunc()
break
}
for i:=0; i<n; i++ {
newState := HIDLEDState{}
newState.fillState(buf[i])
w.readerToDispatcher <- newState
}
}
fmt.Println("***Stopped LED reader")
}
func (w *KeyboardLEDStateWatcher) dispatchLoop() {
fmt.Println("***Starting LED dispatcher")
//try to consume a new LEDState (blocking wait if no interrupt)
L:
for {
select {
case newState:= <- w.readerToDispatcher:
//fmt.Printf("**** HANDLING new LED state\n")
//fmt.Printf("Old state: %+v\nNew state:%+v\n", w.ledState, newState)
//Translate received LED state to state change (if first received state, everything is considered as change
ledStateChange := w.ledState.Changes(newState)
//fmt.Printf("Changed state: %+v\n", ledStateChange)
if w.hasInitialState == false {
ledStateChange.fillState(MaskAny)
w.hasInitialState = true
}
//Store new LED state
w.ledState = &newState //Note: as this method blocks, in case there's no LED state listener, global LED state is only update in case a listener is registered
// check if there's at least one listener, if not block till a new one is registered
// Blocking, in case there's no listener, doesn't align to the usual approach of event driven
//
if len(w.listeners.m) == 0 {
//fmt.Println("Waiting fo at least one listener")
w.listeners.Lock()
w.listeners.m[<-w.listenersToAdd] = true
//fmt.Println("At least one listener added")
w.listeners.Unlock()
}
deleteList := make([]*KeyboardLEDStateListener,0)
//fan out to every listener, block if one doesn't consume the change
w.listeners.Lock()
for l,_ := range w.listeners.m {
// Beware the DeadLock:
// If the listener decides to remove itself (sets l.isMarkedForDeletion) based on the ledStateChange
// received on the l.changedLeds channel, without continuing consuming data, this could lead to a dead lock
// on w.listeners.
// Example: The listener consumes a single state change (by reading from l.changedLeds) does some time
// consuming processing and, as a result of this time consuming processing, decides to set l.isMarkedForDeletion to
// true. This means the listener consumes only the first state change from l.changedLeds. As isMarkedForDeletion
// isn't set to true immediately, this loop, again, tries to write data to the channel (if changed LED states are
// produced frequently or have been queued already), BUT WRITING BLOCKS as the listener is going to decide to mark
// itself for deletion after he is done with processing the first state change. This means the next line of code blocks,
// as the data written to the channel is never consumed by the listener (and the channel is marked
// for deletion too late, which would prevent writing to it). This again means, that the outer for loop,
// which iterates over the registered listeners, will never exit, ULTIMATELY LEAVING w.listeners IN LOCKED
// STATE. This becomes a deadlock, as soon as another routine tries to remove a listener from w.listeners,
// as this routine has to call w.listeners.Lock() itself. But exactly the case of trying to remove the listener
// from w.listeners is the expected one, after setting isMarkedForDeletion to true.
//
// Solution: No matter where l.isMarkedForDeletion gets set to true, it is the responsibility of exactly
// the same part of code, to consume all remaining channel data from l.changedLeds afterwards.
if !l.isMarkedForDeletion {
l.changedLeds <- ledStateChange
} else {
deleteList = append(deleteList, l)
}
}
//delete listeners which aren't used anymore
for _,l := range deleteList {
delete(w.listeners.m, l)
}
w.listeners.Unlock()
case <- w.ctx.Done():
//inform all listeners about the interrupt
w.listeners.Lock()
for l,_ := range w.listeners.m {
l.Unregister()
}
w.listeners.Unlock()
//close Watcher channels
close(w.listenersToAdd)
//End the dispatcher loop
break L
case newListener := <-w.listenersToAdd:
w.listeners.Lock()
w.listeners.m[newListener] = true
w.listeners.Unlock()
}
}
fmt.Println("***Stopped LED dispatcher")
}
type KeyboardLEDStateListener struct {
ledWatcher *KeyboardLEDStateWatcher //the parent LEDWatcher, containing global ledState
changedLeds chan HIDLEDState //changedLeds represents the LEDs which change since last report as bitfield (MaskNumLock, MaskCapsLock ...) the actual state has to be fetched from the respective field of the ledWatcher.ledState
//interrupt chan struct{}
isMarkedForDeletion bool
}
func (l *KeyboardLEDStateListener) Unregister() {
if l.isMarkedForDeletion {return}
l.isMarkedForDeletion = true
//consume remaining input data from channel
L:
for {
select {
case <-l.changedLeds:
//do nothinng
default:
break L
}
}
//close channels
//close(l.interrupt)
close(l.changedLeds)
}
/*
Waits for single LED state change
intendedChange: Mask values combined with logical or, to indicate which LEDs are allowed to trigger MaskNu
return value changed: Mask values combined with logical or, indicating which LED actually changed in order to stop waiting
*/
func (kbd *HIDKeyboard) WaitLEDStateChange(irqFunc <-chan func(), intendedChange byte, timeout time.Duration) (changed *HIDLEDState,err error) {
//register state change listener
l,err := kbd.LEDWatcher.RetrieveNewListener()
if err!= nil { return nil,err }
//defer kbd.LEDWatcher.removeListener(l)
defer l.Unregister()
startTime := time.Now()
remaining := timeout
intendedChangeStruct := HIDLEDState{}
intendedChangeStruct.fillState(intendedChange)
for {
fmt.Println("LED change loop...")
//calculate remaining timeout (error out if already reached
passedBy := time.Since(startTime)
if passedBy > timeout {
return nil, ErrTimeout
} else {
remaining = timeout - passedBy
}
//Wait for report of LED change
select {
case ledsChanged := <- l.changedLeds:
//we have a state change, check relevance
//fmt.Printf("LEDListener received state change on following LEDs %+v\n", ledsChanged)
relevantChanges := ledsChanged.Mask(intendedChangeStruct)
if relevantChanges.AnyOn() {
//We have an intended state change
//fmt.Printf("LEDListener: the following changes have been relevant %+v\n", relevantChanges)
return &relevantChanges, nil
}
// special case - MaskNone is enabled we report back an LED change, even if nothing changed
// this could be used to trigger on Keyboard re-attachment on Windows/som Linox distros, as a new
// LED state is reported everytime the keyboard is attached, even if it doesn't differ from the old one
if intendedChange & MaskNone > 0 {
//fmt.Printf("LEDListener: no changes, reporting back anyway %+v\n", relevantChanges)
return &relevantChanges, nil
}
//If here, there was a LED state change, but not one we want to use for triggering (continue outer loop, consuming channel data)
case <-l.ledWatcher.ctx.Done():
fmt.Println("...LEDWatcher aborted")
return nil, ErrAbort
case irq:=<-irqFunc:
fmt.Println("...WaitLEDStateChange received Irq")
irq()
return nil, ErrIrq
case <- time.After(remaining):
return nil, ErrTimeout
}
}
}
func (kbd *HIDKeyboard) WaitLEDStateChangeRepeated(irqFunc <-chan func(), intendedChange byte, repeatCount int, minRepeatDelay time.Duration, timeout time.Duration) (changed *HIDLEDState,err error) {
//register state change listener
l,err := kbd.LEDWatcher.RetrieveNewListener()
if err!= nil { return nil,err }
//defer kbd.LEDWatcher.removeListener(l)
defer l.Unregister()
startTime := time.Now()
remaining := timeout
intendedChangeStruct := HIDLEDState{}
intendedChangeStruct.fillState(intendedChange)
lastNum,lastCaps,lastScroll,lastCompose,lastKana := startTime,startTime,startTime,startTime,startTime
countNum,countCaps,countScroll,countCompose,countKana := 0,0,0,0,0
for {
//calculate remaining timeout (error out if already reached
passedBy := time.Since(startTime)
if passedBy > timeout {
return nil, ErrTimeout
} else {
remaining = timeout - passedBy
}
//Wait for report of LED change
select {
case ledsChanged := <- l.changedLeds:
//we have a state change, check relevance
//fmt.Printf("LEDListener received state change on following LEDs %+v\n", ledsChanged)
relevantChanges := ledsChanged.Mask(intendedChangeStruct)
// fmt.Printf("LEDs changed: %v\n", ledsChanged)
// fmt.Printf("Changes relevant: %v\n", relevantChanges)
if relevantChanges.AnyOn() {
now := time.Now()
//log.Printf("Duration: NUM %v, CAPS %v, SCROLL %v, COMPOSE %v, KANA %v\n", now.Sub(lastNum), now.Sub(lastCaps), now.Sub(lastScroll), now.Sub(lastCompose), now.Sub(lastKana))
//We have an intended state change, check if any was in intended delay and increment counter in case
if relevantChanges.NumLock {
if now.Sub(lastNum) < minRepeatDelay {
countNum += 1
} else {
countNum = 0
}
lastNum = now
}
if relevantChanges.CapsLock {
if now.Sub(lastCaps) < minRepeatDelay {
countCaps += 1
} else {
countCaps = 0
}
lastCaps = now
}
if relevantChanges.ScrollLock {
if now.Sub(lastScroll) < minRepeatDelay {
countScroll += 1
} else {
countScroll = 0
}
lastScroll = now
}
if relevantChanges.Compose {
if now.Sub(lastCompose) < minRepeatDelay {
countCompose += 1
} else {
countCompose = 0
}
lastCompose = now
}
if relevantChanges.Kana {
if now.Sub(lastKana) < minRepeatDelay {
countKana += 1
} else {
countKana = 0
}
lastKana = now
}
//log.Printf("\tRelevant LED changes after applying mask (interval %v) NUM: %v CAPS: %v SCROLL: %v COMPOSE: %v KANA: %v\n", minRepeatDelay, countNum, countCaps, countScroll, countCompose, countKana)
//check counters
result := &HIDLEDState{}
if countNum >= repeatCount { result.NumLock = true }
if countCaps >= repeatCount { result.CapsLock = true }
if countScroll >= repeatCount { result.ScrollLock = true }
if countCompose >= repeatCount { result.Compose = true }
if countKana >= repeatCount { result.Kana = true }
if result.AnyOn() {
return result, nil
}
//return &relevantChanges, nil
}
//If here, there was a LED state change, but not one we want to use for triggering (continue outer loop, consuming channel data)
case <-l.ledWatcher.ctx.Done():
return nil, ErrAbort
case irq:=<-irqFunc:
irq()
return nil, ErrIrq
case <- time.After(remaining):
return nil, ErrTimeout
}
}
}