blob: 1abaa0f4006b3b340b9ee61ddd68c109d11b3650 [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package input
import (
"context"
"fmt"
"math/big"
"os"
"unsafe"
"chromiumos/tast/errors"
"chromiumos/tast/testing"
)
// TrackpadEventWriter supports injecting events into a virtual trackpad device.
type TrackpadEventWriter struct {
TouchscreenEventWriter
}
var nextVirtTrackpadNum = 1 // appended to virtual trackpad device name
// Trackpad returns an EventWriter that injects events a trackpad device.
//
// If a physical trackpad is present, it is used.
// Otherwise, a one-off virtual device is created.
func Trackpad(ctx context.Context) (*TrackpadEventWriter, error) {
infos, err := readDevices("")
if err != nil {
return nil, errors.Wrap(err, "failed to read devices")
}
for _, info := range infos {
// When the trackpad doesn't support multitouch, use a virtual trackpad instead.
if !info.isTrackpad() || !info.hasBit(absGroup, uint16(ABS_MT_SLOT)) {
continue
}
testing.ContextLogf(ctx, "Opening trackpad device %+v", info)
// Get trackpad properties: bounds, max touches, max pressure and max track id.
f, err := os.Open(info.path)
if err != nil {
return nil, err
}
defer f.Close()
var infoX, infoY, infoSlot, infoTrackingID, infoPressure absInfo
for _, entry := range []struct {
ec EventCode
dst *absInfo
}{
{ABS_X, &infoX},
{ABS_Y, &infoY},
{ABS_MT_SLOT, &infoSlot},
{ABS_MT_TRACKING_ID, &infoTrackingID},
{ABS_MT_PRESSURE, &infoPressure},
} {
if err := ioctl(int(f.Fd()), evIOCGAbs(uint(entry.ec)), uintptr(unsafe.Pointer(entry.dst))); err != nil {
return nil, err
}
}
if infoTrackingID.maximum < infoSlot.maximum {
return nil, errors.Errorf("invalid MT tracking ID %d; should be >= max slots %d",
infoTrackingID.maximum, infoSlot.maximum)
}
device, err := Device(ctx, info.path)
if err != nil {
return nil, err
}
return &TrackpadEventWriter{TouchscreenEventWriter{
rw: device,
width: TouchCoord(infoX.maximum),
height: TouchCoord(infoY.maximum),
maxTouchSlot: int(infoSlot.maximum),
maxTrackingID: int(infoTrackingID.maximum),
maxPressure: int(infoPressure.maximum),
}}, nil
}
// If we didn't find a real trackpad, create a virtual one.
return VirtualTrackpad(ctx)
}
// VirtualTrackpad creates a virtual trackpad device and returns an EventWriter that injects events into it.
func VirtualTrackpad(ctx context.Context) (*TrackpadEventWriter, error) {
const (
// Most trackpads use I2C bus. But hardcoding to USB since it is supported
// in all Chromebook devices.
busType = 0x3 // BUS_USB from input.h
// Device constants taken from Pixelbook.
vendor = 0x18d1
product = 0x5028
version = 0x100
// Input characteristics.
props = 1<<INPUT_PROP_POINTER | 1<<INPUT_PROP_BUTTONPAD
evTypes = 1<<EV_KEY | 1<<EV_ABS | 1<<EV_SYN
// Abs axes supported in our virtual device.
absSupportedAxes = 1<<ABS_X | 1<<ABS_Y | 1<<ABS_PRESSURE | 1<<ABS_MT_SLOT |
1<<ABS_MT_TOUCH_MAJOR | 1<<ABS_MT_TOUCH_MINOR | 1<<ABS_MT_ORIENTATION |
1<<ABS_MT_POSITION_X | 1<<ABS_MT_POSITION_Y |
1<<ABS_MT_TRACKING_ID | 1<<ABS_MT_PRESSURE | 1<<ABS_MT_DISTANCE
// Abs axis constants. Taken from Pixelbook.
axisMaxX = 13184
axisMaxY = 8704
axisMaxTracking = 65535
axisMaxPressure = 255
axisCoordResolution = 128
)
axisMaxTouchSlot := 9
// Include our PID in the device name to be extra careful in case an old bundle process hasn't exited.
name := fmt.Sprintf("Tast virtual trackpad %d.%d", os.Getpid(), nextVirtTrackpadNum)
nextVirtTrackpadNum++
testing.ContextLogf(ctx, "Creating virtual trackpad device %q", name)
dev, virt, err := createVirtual(name, devID{busType, vendor, product, version}, props, evTypes,
map[EventType]*big.Int{
EV_KEY: makeBigInt([]uint64{0xe520, 0x10000, 0, 0, 0, 0}),
EV_ABS: big.NewInt(absSupportedAxes),
}, map[EventCode]Axis{
ABS_X: {axisMaxX, 0, 0, 0, axisCoordResolution},
ABS_Y: {axisMaxY, 0, 0, 0, axisCoordResolution},
ABS_PRESSURE: {axisMaxPressure, 0, 0, 0, 0},
ABS_MT_SLOT: {int32(axisMaxTouchSlot), 0, 0, 0, 0},
ABS_MT_TOUCH_MAJOR: {13184, 0, 0, 0, 1},
ABS_MT_TOUCH_MINOR: {8704, 0, 0, 0, 1},
ABS_MT_ORIENTATION: {90, -90, 0, 0, 0},
ABS_MT_POSITION_X: {axisMaxX, 0, 0, 0, axisCoordResolution},
ABS_MT_POSITION_Y: {axisMaxY, 0, 0, 0, axisCoordResolution},
ABS_MT_TRACKING_ID: {axisMaxTracking, 0, 0, 0, 0},
ABS_MT_PRESSURE: {axisMaxPressure, 0, 0, 0, 0},
ABS_MT_DISTANCE: {1, 0, 0, 0, 0},
})
if err != nil {
return nil, err
}
device, err := Device(ctx, dev)
if err != nil {
return nil, err
}
return &TrackpadEventWriter{TouchscreenEventWriter{
rw: device,
dev: dev,
virt: virt,
width: axisMaxX,
height: axisMaxY,
maxTouchSlot: axisMaxTouchSlot,
maxTrackingID: axisMaxTracking,
maxPressure: axisMaxPressure,
}}, nil
}