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
|
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package triggerwatch
import (
"fmt"
"syscall"
"time"
"unsafe"
// TODO:UC20: not packaged, reimplement the minimal things we need?
evdev "github.com/gvalkov/golang-evdev"
"github.com/snapcore/snapd/logger"
)
type keyEvent struct {
Dev triggerDevice
Err error
}
type triggerEventFilter struct {
Key string
}
var (
strToKey = map[string]int{
"KEY_ESC": evdev.KEY_ESC,
"KEY_1": evdev.KEY_1,
"KEY_2": evdev.KEY_2,
"KEY_3": evdev.KEY_3,
"KEY_4": evdev.KEY_4,
"KEY_5": evdev.KEY_5,
"KEY_6": evdev.KEY_6,
"KEY_7": evdev.KEY_7,
"KEY_8": evdev.KEY_8,
"KEY_9": evdev.KEY_9,
"KEY_0": evdev.KEY_0,
}
evKeyCapability = evdev.CapabilityType{Type: evdev.EV_KEY, Name: "EV_KEY"}
// hold time needed to trigger the event
holdToTrigger = 2 * time.Second
)
func init() {
trigger = &evdevInput{}
}
type evdevKeyboardInputDevice struct {
keyCode uint16
dev *evdev.InputDevice
}
func (e *evdevKeyboardInputDevice) probeKeyState() (bool, error) {
// XXX: evdev defines EVIOCGKEY using MAX_NAME_SIZE which is larger than
// what is needed to store the key bitmap with KEY_MAX bits, but we need
// to play along since the value is already encoded
keyBitmap := new([evdev.MAX_NAME_SIZE]byte)
// obtain the large bitmap with all key states
// https://elixir.bootlin.com/linux/v5.5.5/source/drivers/input/evdev.c#L1163
_, _, err := syscall.RawSyscall(syscall.SYS_IOCTL, e.dev.File.Fd(), uintptr(evdev.EVIOCGKEY), uintptr(unsafe.Pointer(keyBitmap)))
if err != 0 {
return false, err
}
byteIdx := e.keyCode / 8
keyMask := byte(1 << (e.keyCode % 8))
isDown := keyBitmap[byteIdx]&keyMask != 0
return isDown, nil
}
func (e *evdevKeyboardInputDevice) WaitForTrigger(ch chan keyEvent) {
logger.Noticef("%s: starting wait, hold %s to trigger", e, holdToTrigger)
// XXX: do not mess with setting the key repeat rate, as it's cumbersome
// and golang-evdev SetRepeatRate() parameter order is actually reversed
// wrt. what the kernel does. The evdev interprets EVIOCSREP arguments
// as (delay, repeat)
// https://elixir.bootlin.com/linux/latest/source/drivers/input/evdev.c#L1072
// but the wrapper is passing is passing (repeat, delay)
// https://github.com/gvalkov/golang-evdev/blob/287e62b94bcb850ab42e711bd74b2875da83af2c/device.go#L226-L230
keyDown, err := e.probeKeyState()
if err != nil {
ch <- keyEvent{Err: fmt.Errorf("cannot obtain initial key state: %v", err), Dev: e}
}
if keyDown {
// looks like the key is pressed initially, we don't know when
// that happened, but pretend it happened just now
logger.Noticef("%s: key is already down", e)
}
type evdevEvent struct {
kev *evdev.KeyEvent
err error
}
// buffer large enough to collect some events
evChan := make(chan evdevEvent, 10)
monitorKey := func() {
for {
ies, err := e.dev.Read()
if err != nil {
evChan <- evdevEvent{err: err}
break
}
for _, ie := range ies {
if ie.Type != evdev.EV_KEY || ie.Code != e.keyCode {
continue
}
kev := evdev.NewKeyEvent(&ie)
evChan <- evdevEvent{kev: kev}
}
}
close(evChan)
}
go monitorKey()
holdTimer := time.NewTimer(holdToTrigger)
// no sense to keep it running later either
defer holdTimer.Stop()
if !keyDown {
// key isn't held yet, stop the timer
holdTimer.Stop()
}
// invariant: tholdTimer is running iff keyDown is true, otherwise is stopped
Loop:
for {
select {
case ev := <-evChan:
if ev.err != nil {
holdTimer.Stop()
ch <- keyEvent{Err: ev.err, Dev: e}
break Loop
}
kev := ev.kev
switch kev.State {
case evdev.KeyDown:
if keyDown {
// unexpected, but possible if we missed
// a key up event right after checking
// the initial keyboard state when the
// key was still down
if !holdTimer.Stop() {
// drain the channel before the
// timer gets reset
<-holdTimer.C
}
}
keyDown = true
// timer is stopped at this point
holdTimer.Reset(holdToTrigger)
logger.Noticef("%s: trigger key down", e)
case evdev.KeyHold:
if !keyDown {
keyDown = true
// timer is not running yet at this point
holdTimer.Reset(holdToTrigger)
logger.Noticef("%s: unexpected hold without down", e)
}
case evdev.KeyUp:
// no need to drain the channel, if it expired,
// we'll handle it in next iteration
holdTimer.Stop()
keyDown = false
logger.Noticef("%s: trigger key up", e)
}
case <-holdTimer.C:
logger.Noticef("%s: hold complete", e)
ch <- keyEvent{Dev: e}
break Loop
}
}
}
func (e *evdevKeyboardInputDevice) String() string {
return fmt.Sprintf("%s: %s", e.dev.Phys, e.dev.Name)
}
func (e *evdevKeyboardInputDevice) Close() {
e.dev.File.Close()
}
type evdevInput struct{}
func getCapabilityCode(Key string) (evdev.CapabilityCode, error) {
keyCode, ok := strToKey[Key]
if !ok {
return evdev.CapabilityCode{}, fmt.Errorf("cannot find a key matching the filter %q", Key)
}
return evdev.CapabilityCode{Code: keyCode, Name: Key}, nil
}
func matchDevice(cap evdev.CapabilityCode, dev *evdev.InputDevice) triggerDevice {
for _, cc := range dev.Capabilities[evKeyCapability] {
if cc == cap {
return &evdevKeyboardInputDevice{
dev: dev,
keyCode: uint16(cap.Code),
}
}
}
return nil
}
func (e *evdevInput) Open(filter triggerEventFilter, node string) (triggerDevice, error) {
evdevDevice, err := evdev.Open(node)
if err != nil {
return nil, err
}
cap, err := getCapabilityCode(filter.Key)
if err != nil {
return nil, err
}
return matchDevice(cap, evdevDevice), nil
}
func (e *evdevInput) FindMatchingDevices(filter triggerEventFilter) ([]triggerDevice, error) {
devices, err := evdev.ListInputDevices()
if err != nil {
return nil, fmt.Errorf("cannot list input devices: %v", err)
}
// NOTE: this supports so far only key input devices
cap, err := getCapabilityCode(filter.Key)
if err != nil {
return nil, err
}
// collect all input devices that can emit the trigger key
var devs []triggerDevice
for _, dev := range devices {
idev := matchDevice(cap, dev)
if idev != nil {
devs = append(devs, idev)
} else {
defer dev.File.Close()
}
}
return devs, nil
}
|