github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/cmd/snap-bootstrap/triggerwatch/evdev.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package triggerwatch 21 22 import ( 23 "fmt" 24 "syscall" 25 "time" 26 "unsafe" 27 28 // TODO:UC20: not packaged, reimplement the minimal things we need? 29 evdev "github.com/gvalkov/golang-evdev" 30 31 "github.com/snapcore/snapd/logger" 32 ) 33 34 type keyEvent struct { 35 Dev triggerDevice 36 Err error 37 } 38 39 type triggerEventFilter struct { 40 Key string 41 } 42 43 var ( 44 strToKey = map[string]int{ 45 "KEY_ESC": evdev.KEY_ESC, 46 "KEY_1": evdev.KEY_1, 47 "KEY_2": evdev.KEY_2, 48 "KEY_3": evdev.KEY_3, 49 "KEY_4": evdev.KEY_4, 50 "KEY_5": evdev.KEY_5, 51 "KEY_6": evdev.KEY_6, 52 "KEY_7": evdev.KEY_7, 53 "KEY_8": evdev.KEY_8, 54 "KEY_9": evdev.KEY_9, 55 "KEY_0": evdev.KEY_0, 56 } 57 evKeyCapability = evdev.CapabilityType{Type: evdev.EV_KEY, Name: "EV_KEY"} 58 59 // hold time needed to trigger the event 60 holdToTrigger = 2 * time.Second 61 ) 62 63 func init() { 64 trigger = &evdevInput{} 65 } 66 67 type evdevKeyboardInputDevice struct { 68 keyCode uint16 69 dev *evdev.InputDevice 70 } 71 72 func (e *evdevKeyboardInputDevice) probeKeyState() (bool, error) { 73 // XXX: evdev defines EVIOCGKEY using MAX_NAME_SIZE which is larger than 74 // what is needed to store the key bitmap with KEY_MAX bits, but we need 75 // to play along since the value is already encoded 76 keyBitmap := new([evdev.MAX_NAME_SIZE]byte) 77 78 // obtain the large bitmap with all key states 79 // https://elixir.bootlin.com/linux/v5.5.5/source/drivers/input/evdev.c#L1163 80 _, _, err := syscall.RawSyscall(syscall.SYS_IOCTL, e.dev.File.Fd(), uintptr(evdev.EVIOCGKEY), uintptr(unsafe.Pointer(keyBitmap))) 81 if err != 0 { 82 return false, err 83 } 84 byteIdx := e.keyCode / 8 85 keyMask := byte(1 << (e.keyCode % 8)) 86 isDown := keyBitmap[byteIdx]&keyMask != 0 87 return isDown, nil 88 } 89 90 func (e *evdevKeyboardInputDevice) WaitForTrigger(ch chan keyEvent) { 91 logger.Noticef("%s: starting wait, hold %s to trigger", e, holdToTrigger) 92 93 // XXX: do not mess with setting the key repeat rate, as it's cumbersome 94 // and golang-evdev SetRepeatRate() parameter order is actually reversed 95 // wrt. what the kernel does. The evdev interprets EVIOCSREP arguments 96 // as (delay, repeat) 97 // https://elixir.bootlin.com/linux/latest/source/drivers/input/evdev.c#L1072 98 // but the wrapper is passing is passing (repeat, delay) 99 // https://github.com/gvalkov/golang-evdev/blob/287e62b94bcb850ab42e711bd74b2875da83af2c/device.go#L226-L230 100 101 keyDown, err := e.probeKeyState() 102 if err != nil { 103 ch <- keyEvent{Err: fmt.Errorf("cannot obtain initial key state: %v", err), Dev: e} 104 } 105 if keyDown { 106 // looks like the key is pressed initially, we don't know when 107 // that happened, but pretend it happened just now 108 logger.Noticef("%s: key is already down", e) 109 } 110 111 type evdevEvent struct { 112 kev *evdev.KeyEvent 113 err error 114 } 115 116 // buffer large enough to collect some events 117 evChan := make(chan evdevEvent, 10) 118 119 monitorKey := func() { 120 for { 121 ies, err := e.dev.Read() 122 if err != nil { 123 evChan <- evdevEvent{err: err} 124 break 125 } 126 for _, ie := range ies { 127 if ie.Type != evdev.EV_KEY || ie.Code != e.keyCode { 128 continue 129 } 130 kev := evdev.NewKeyEvent(&ie) 131 evChan <- evdevEvent{kev: kev} 132 } 133 } 134 close(evChan) 135 } 136 137 go monitorKey() 138 139 holdTimer := time.NewTimer(holdToTrigger) 140 // no sense to keep it running later either 141 defer holdTimer.Stop() 142 143 if !keyDown { 144 // key isn't held yet, stop the timer 145 holdTimer.Stop() 146 } 147 148 // invariant: tholdTimer is running iff keyDown is true, otherwise is stopped 149 Loop: 150 for { 151 select { 152 case ev := <-evChan: 153 if ev.err != nil { 154 holdTimer.Stop() 155 ch <- keyEvent{Err: err, Dev: e} 156 break Loop 157 } 158 kev := ev.kev 159 switch kev.State { 160 case evdev.KeyDown: 161 if keyDown { 162 // unexpected, but possible if we missed 163 // a key up event right after checking 164 // the initial keyboard state when the 165 // key was still down 166 if !holdTimer.Stop() { 167 // drain the channel before the 168 // timer gets reset 169 <-holdTimer.C 170 } 171 } 172 keyDown = true 173 // timer is stopped at this point 174 holdTimer.Reset(holdToTrigger) 175 logger.Noticef("%s: trigger key down", e) 176 case evdev.KeyHold: 177 if !keyDown { 178 keyDown = true 179 // timer is not running yet at this point 180 holdTimer.Reset(holdToTrigger) 181 logger.Noticef("%s: unexpected hold without down", e) 182 } 183 case evdev.KeyUp: 184 // no need to drain the channel, if it expired, 185 // we'll handle it in next iteration 186 holdTimer.Stop() 187 keyDown = false 188 logger.Noticef("%s: trigger key up", e) 189 } 190 case <-holdTimer.C: 191 logger.Noticef("%s: hold complete", e) 192 ch <- keyEvent{Dev: e} 193 break Loop 194 } 195 } 196 } 197 198 func (e *evdevKeyboardInputDevice) String() string { 199 return fmt.Sprintf("%s: %s", e.dev.Phys, e.dev.Name) 200 } 201 202 func (e *evdevKeyboardInputDevice) Close() { 203 e.dev.File.Close() 204 } 205 206 type evdevInput struct{} 207 208 func (e *evdevInput) FindMatchingDevices(filter triggerEventFilter) ([]triggerDevice, error) { 209 devices, err := evdev.ListInputDevices() 210 if err != nil { 211 return nil, fmt.Errorf("cannot list input devices: %v", err) 212 } 213 214 // NOTE: this supports so far only key input devices 215 216 kc, ok := strToKey[filter.Key] 217 if !ok { 218 return nil, fmt.Errorf("cannot find a key matching the filter %q", filter.Key) 219 } 220 cap := evdev.CapabilityCode{Code: kc, Name: filter.Key} 221 222 match := func(dev *evdev.InputDevice) triggerDevice { 223 for _, cc := range dev.Capabilities[evKeyCapability] { 224 if cc == cap { 225 return &evdevKeyboardInputDevice{ 226 dev: dev, 227 keyCode: uint16(cap.Code), 228 } 229 } 230 } 231 return nil 232 } 233 // collect all input devices that can emit the trigger key 234 var devs []triggerDevice 235 for _, dev := range devices { 236 idev := match(dev) 237 if idev != nil { 238 devs = append(devs, idev) 239 } else { 240 defer dev.File.Close() 241 } 242 } 243 return devs, nil 244 }