Skip to content

Commit

Permalink
First tentative iplementation of a virtual mouse (relative input)
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjamin Dahlmanns committed Feb 13, 2017
1 parent 252bded commit 14e3ea8
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 46 deletions.
40 changes: 40 additions & 0 deletions uinput.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,46 @@ type vKeyboard struct {
deviceFile *os.File
}

// An Mouse is a device that will trigger an absolute change event.
// For details see: https://www.kernel.org/doc/Documentation/input/event-codes.txt
type Mouse interface {
MoveCursor(x, y int32) error

io.Closer
}

type vMouse struct {
name []byte
deviceFile *os.File
}

func CreateMouse(path string, name []byte) (Mouse, error) {
if path == "" {
return nil, errors.New("device path must not be empty")
}
if len(name) > uinputMaxNameSize {
return nil, fmt.Errorf("device name %s is too long (maximum of %d characters allowed)", name, uinputMaxNameSize)
}

fd, err := createMouse(path, name)
if err != nil {
return nil, err
}

return vMouse{name: name, deviceFile: fd}, nil
}

// MoveCursor sets the absolute position of the device. Values will have to be within the boundaries specified during
// intialization
func (vAbs vMouse) MoveCursor(x, y int32) error {
return sendRelEvent(vAbs.deviceFile, x, y)
}

// Close closes the device and releases the
func (vAbs vMouse) Close() error {
return closeDevice(vAbs.deviceFile)
}

// CreateKeyboard will create a new keyboard using the given uinput
// device path of the uinput device.
func CreateKeyboard(path string, name []byte) (Keyboard, error) {
Expand Down
20 changes: 20 additions & 0 deletions uinput_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,23 @@ func TestInvalidKeycode(t *testing.T) {

vk.Close()
}

// This test will create a basic Mouse, send an absolute axis event and close the device
func TestBasicMouse(t *testing.T) {
relDev, err := CreateMouse("/dev/uinput", []byte("Test Basic Mouse"))
if err != nil {
t.Fatalf("Failed to create the virtual mouse. Last error was: %s\n", err)
}

err = relDev.MoveCursor(1000, 100)

if err != nil {
t.Fatalf("Failed to send key event. Last error was: %s\n", err)
}

err = relDev.Close()

if err != nil {
t.Fatalf("Failed to close device. Last error was: %s\n", err)
}
}
169 changes: 123 additions & 46 deletions uinputwrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const (
uiDevDestroy = 0x5502
uiSetEvBit = 0x40045564
uiSetKeyBit = 0x40045565
uiSetRelBit = 0x40045566
uiSetAbsBit = 0x40045567
busUsb = 0x03
)

Expand All @@ -26,12 +28,17 @@ const (
evKey = 0x01
evRel = 0x02
evAbs = 0x03
relX = 0x0
relY = 0x1
absX = 0x0
absY = 0x1
synReport = 0
)

const (
btnStateReleased = 0
btnStatePressed = 1
absSize = 64
)

type inputID struct {
Expand All @@ -46,10 +53,10 @@ type uinputUserDev struct {
Name [uinputMaxNameSize]byte
ID inputID
EffectsMax uint32
Absmax [64]int32
Absmin [64]int32
Absfuzz [64]int32
Absflat [64]int32
Absmax [absSize]int32
Absmin [absSize]int32
Absfuzz [absSize]int32
Absflat [absSize]int32
}

// translated to go from input.h
Expand All @@ -60,30 +67,50 @@ type inputEvent struct {
Value int32
}

func closeDevice(deviceID *os.File) (err error) {
err = releaseDevice(deviceID)
func closeDevice(deviceFile *os.File) (err error) {
err = releaseDevice(deviceFile)
if err != nil {
return fmt.Errorf("failed to close device: %v", err)
}
return nil
return deviceFile.Close()
}

func releaseDevice(deviceFile *os.File) (err error) {
return ioctl(deviceFile, uiDevDestroy, uintptr(0))
}

func initKeyboardDevice(path string, name []byte) (deviceFile *os.File, err error) {
deviceFile, err = os.OpenFile(path, syscall.O_WRONLY|syscall.O_NONBLOCK|syscall.O_NDELAY, 0666)
func createDeviceFile(path string) (fd *os.File, err error) {
deviceFile, err := os.OpenFile(path, syscall.O_WRONLY|syscall.O_NONBLOCK|syscall.O_NDELAY, 0666)
if err != nil {
return nil, errors.New("could not open device file")
}
return deviceFile, err
}

// register device
err = ioctl(deviceFile, uiSetEvBit, uintptr(evKey))
func registerDevice(deviceFile *os.File, evType uintptr) (error) {
err := ioctl(deviceFile, uiSetEvBit, evType)
if err != nil {
err = releaseDevice(deviceFile)
if err != nil {
deviceFile.Close()
return nil, fmt.Errorf("failed to close device: %v", err)
return fmt.Errorf("failed to close device: %v", err)
}
deviceFile.Close()
return nil, fmt.Errorf("invalid file handle returned from ioctl: %v", err)
return fmt.Errorf("invalid file handle returned from ioctl: %v", err)
}
return nil
}

func createVKeyboardDevice(path string, name []byte) (fd *os.File, err error) {
deviceFile, err := createDeviceFile(path)
if err != nil {
return nil, fmt.Errorf("failed to create virtual keyboard device: %v", err)
}

err = registerDevice(deviceFile, uintptr(evKey))
if err != nil {
deviceFile.Close()
return nil, fmt.Errorf("failed to register virtual keyboard device: %v", err)
}

// register key events
Expand All @@ -94,20 +121,59 @@ func initKeyboardDevice(path string, name []byte) (deviceFile *os.File, err erro
return nil, fmt.Errorf("failed to register key number %d: %v", i, err)
}
}

return createUsbDevice(deviceFile,
uinputUserDev{
Name: toUinputName(name),
ID: inputID{
Bustype: busUsb,
Vendor: 0x4711,
Product: 0x0815,
Version: 1}})
}

func toUinputName(name []byte) (uinputName [uinputMaxNameSize]byte) {
var fixedSizeName [uinputMaxNameSize]byte
copy(fixedSizeName[:], name)
return fixedSizeName
}

uidev :=
func createMouse(path string, name [] byte) (fd *os.File, err error) {
deviceFile, err := createDeviceFile(path)
if err != nil {
return nil, fmt.Errorf("could not create absolute axis input device: %v", err)
}

err = registerDevice(deviceFile, uintptr(evRel))
if err != nil {
deviceFile.Close()
return nil, fmt.Errorf("failed to register absolute axis input device: %v", err)
}

// register x and y axis events
err = ioctl(deviceFile, uiSetRelBit, uintptr(relX))
if err != nil {
deviceFile.Close()
return nil, fmt.Errorf("failed to register absolute x axis events: %v", err)
}
err = ioctl(deviceFile, uiSetRelBit, uintptr(relY))
if err != nil {
deviceFile.Close()
return nil, fmt.Errorf("failed to register absolute y axis events: %v", err)
}
return createUsbDevice(deviceFile,
uinputUserDev{
Name: fixedSizeName,
Name: toUinputName(name),
ID: inputID{
Bustype: busUsb,
Vendor: 0x4711,
Product: 0x0815,
Version: 1}}
Version: 1}})
}

func createUsbDevice(deviceFile *os.File, dev uinputUserDev) (fd *os.File, err error) {
buf := new(bytes.Buffer)
err = binary.Write(buf, binary.LittleEndian, uidev)
err = binary.Write(buf, binary.LittleEndian, dev)
if err != nil {
deviceFile.Close()
return nil, fmt.Errorf("failed to write user device buffer: %v", err)
Expand All @@ -123,36 +189,21 @@ func initKeyboardDevice(path string, name []byte) (deviceFile *os.File, err erro
deviceFile.Close()
return nil, fmt.Errorf("failed to create device: %v", err)
}
time.Sleep(time.Millisecond * 200)
return deviceFile, err
}

func releaseDevice(deviceFile *os.File) (err error) {
return ioctl(deviceFile, uiDevDestroy, uintptr(0))
}

// original function taken from: https://github.com/tianon/debian-golang-pty/blob/master/ioctl.go
func ioctl(deviceFile *os.File, cmd, ptr uintptr) error {
_, _, errorCode := syscall.Syscall(syscall.SYS_IOCTL, deviceFile.Fd(), cmd, ptr)
if errorCode != 0 {
return errorCode
}
return nil
}
func createVKeyboardDevice(path string, name []byte) (fd *os.File, err error) {
fd, err = initKeyboardDevice(path, name)
if err != nil {
return nil, fmt.Errorf("error during initialization of keyboard '%s' at %s: %v", name, path, err)
}
time.Sleep(time.Millisecond * 200)

return fd, nil
return deviceFile, err
}

func sendBtnEvent(deviceFile *os.File, key int, btnState int) (err error) {
if key < 1 || key > keyMax {
return fmt.Errorf("could not send key event: invalid keycode '%d'", key);
}
buf, err := inputEventToBuffer(evKey, uint16(key), int32(btnState))
buf, err := inputEventToBuffer(inputEvent{
Time: syscall.Timeval{0, 0},
Type: evKey,
Code: uint16(key),
Value: int32(btnState)})
if err != nil {
return fmt.Errorf("key event could not be set: %v", err)
}
Expand All @@ -167,25 +218,51 @@ func sendBtnEvent(deviceFile *os.File, key int, btnState int) (err error) {
return nil
}

func sendRelEvent(deviceFile *os.File, x, y int32) error {
ievX := inputEvent{
Time: syscall.Timeval{0, 0},
Type: evRel,
Code: relX,
Value: x}

buf, err := inputEventToBuffer(ievX)
if err != nil {
return fmt.Errorf("writing abs event failed: %v", err)
}
_, err = deviceFile.Write(buf)
if err != nil {
return fmt.Errorf("failed to write abs event to device file: %v", err)
}
return syncEvents(deviceFile)
}

func syncEvents(deviceFile *os.File) (err error) {
buf, err := inputEventToBuffer(evSyn, 0, int32(synReport))
buf, err := inputEventToBuffer(inputEvent{
Time: syscall.Timeval{0, 0},
Type: evSyn,
Code: 0,
Value: int32(synReport)})
if err != nil {
return fmt.Errorf("writing sync event failed: %v", err)
}
_, err = deviceFile.Write(buf)
return err
}

func inputEventToBuffer(evType uint16, evCode uint16, evValue int32)(buffer []byte, err error) {
iev := inputEvent{
Time: syscall.Timeval{0,0},
Type: evType,
Code: evCode,
Value: evValue}
func inputEventToBuffer(iev interface{}) (buffer []byte, err error) {
buf := new(bytes.Buffer)
err = binary.Write(buf, binary.LittleEndian, iev)
if err != nil {
return nil, fmt.Errorf("failed to write input event to buffer: %v", err)
}
return buf.Bytes(), nil
}

// original function taken from: https://github.com/tianon/debian-golang-pty/blob/master/ioctl.go
func ioctl(deviceFile *os.File, cmd, ptr uintptr) error {
_, _, errorCode := syscall.Syscall(syscall.SYS_IOCTL, deviceFile.Fd(), cmd, ptr)
if errorCode != 0 {
return errorCode
}
return nil
}

0 comments on commit 14e3ea8

Please sign in to comment.