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  }