Skip to content

Commit

Permalink
Add virtual touch pad device
Browse files Browse the repository at this point in the history
The virtual touch pad device is similar to the mouse, but allows to move the cursor to an absolute position (x, y).
  • Loading branch information
Benjamin Dahlmanns committed Jun 4, 2017
1 parent 3a16773 commit d3f0187
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 8 deletions.
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
Uinput [![Build Status](https://travis-ci.org/bendahl/uinput.svg?branch=master)](https://travis-ci.org/bendahl/uinput) [![GoDoc](https://godoc.org/github.com/bendahl/uinput?status.png)](https://godoc.org/github.com/bendahl/uinput) [![Go Report Card](https://goreportcard.com/badge/github.com/bendahl/uinput)](https://goreportcard.com/report/github.com/bendahl/uinput)
====

This package provides pure go wrapper functions for the LINUX uinput device, which allows to create virtual input devices in userspace. At the moment this package offers a virtual keyboard implementation as well as a virtual mouse device. The keyboard can be used to either send single key presses or hold down a specified key and release it later (useful for building game controllers). The mouse device issues relative positional change events to the x and y asix of the mouse pointer and may also fire click events (left and right click). More functionality will be added in future version.
This package provides pure go wrapper functions for the LINUX uinput device, which allows to create virtual input devices
in userspace. At the moment this package offers a virtual keyboard implementation as well as a virtual mouse device and
a touch pad device.
The keyboard can be used to either send single key presses or hold down a specified key and release it later
(useful for building game controllers). The mouse device issues relative positional change events to the x and y axis
of the mouse pointer and may also fire click events (left and right click). More functionality will be added in future
version.
The touch pad, on the other hand can be used to move the mouse cursor to the specified position on the screen and to
issue left and right clicks. Note that you'll need to specify the region size of your screen first though (happens during
device creation).

Please note that you will need to make sure to have the necessary rights to write to uinput. You can either chmod your uinput device, or add a rule in /etc/udev/rules.d to allow your user's group or a dedicated group to write to the device.
You may use the following two commands to add the necessary rights for you current user to a file called 99-$USER.rules (where $USER is your current user's name):
Please note that you will need to make sure to have the necessary rights to write to uinput. You can either chmod your
uinput device, or add a rule in /etc/udev/rules.d to allow your user's group or a dedicated group to write to the device.
You may use the following two commands to add the necessary rights for you current user to a file called 99-$USER.rules
(where $USER is your current user's name):
<pre><code>
echo KERNEL==\"uinput\", GROUP=\"$USER\", MODE:=\"0660\" | sudo tee /etc/udev/rules.d/99-$USER.rules
sudo udevadm trigger
</code></pre>

Installation
-------------
Simply check out the repository and use the commands <pre><code>go build && go install</code></pre> The package will then be installed to your local respository, along with the package documentation. The documentation contains more details on the usage of this package.
Simply check out the repository and use the commands <pre><code>go build && go install</code></pre>
The package will then be installed to your local respository, along with the package documentation.
The documentation contains more details on the usage of this package.

License
--------
The package falls under the MIT license. Please see the "LICENSE" file for details.

ToDos
------------------
Besides mouse and keyboard events it would be great to introduce an input device that allows absolute mouse pointer movements (similar to a touchpad). This still needs to be done. Also, all testing has been done on Ubunu 14.04 and 16.04 x86\_64. Testing for other platforms will need to be done. To get an idea of the things that are on the current todo list, check out the file "TODO.md". As always, helpful comments and ideas are always welcome. Feel free to do some testing on your own if you're up to it.
All testing has been done on Ubunu 14.04 and 16.04 x86\_64.
Testing for other platforms will need to be done.
To get an idea of the things that are on the current todo list, check out the file "TODO.md".
As always, helpful comments and ideas are always welcome.
Feel free to do some testing on your own if you're up to it.

2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ TODO
1. ~~Create Tests for the uinput package~~
2. ~~Migrate code from C to GO~~
3. ~~Implement relative input~~
4. Implement absolute input
4. ~~Implement absolute input~~
5. Test on different platforms (besides x86_64)
6. Implement functions to allow mouse button up and down events (for region selects)
7. Extend test cases
82 changes: 82 additions & 0 deletions uinput.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,88 @@ type vMouse struct {
deviceFile *os.File
}

// A TouchPad is an input device that uses absolute axis events, meaning that you can specifiy
// the exact position the cursor should move to. Therefore, it is necessary to define the size
// of the rectangle in which the cursor may move upon creation of the device.
type TouchPad interface {
// MoveTo will move the cursor to the specified position on the screen
MoveTo(x int32, y int32) error

// LeftClick will issue a single left click.
LeftClick() error

// RightClick will issue a right click.
RightClick() error

io.Closer
}

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

// CreateTouchPad will create a new touch pad device. note that you will need to define the x and y axis boundaries
// (min and max) within which the cursor maybe moved around.
func CreateTouchPad(path string, name []byte, minX int32, maxX int32, minY int32, maxY int32) (TouchPad, 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 := createTouchPad(path, name, minX, maxX, minY, maxY)
if err != nil {
return nil, err
}

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

func (vTouch vTouchPad) MoveTo(x int32, y int32) error {
return sendAbsEvent(vTouch.deviceFile, x, y)
}

func (vTouch vTouchPad) LeftClick() error {
err := sendBtnEvent(vTouch.deviceFile, evBtnLeft, btnStatePressed)
if err != nil {
return fmt.Errorf("Failed to issue the LeftClick event: %v", err)
}

err = sendBtnEvent(vTouch.deviceFile, evBtnLeft, btnStateReleased)
if err != nil {
return fmt.Errorf("Failed to issue the KeyUp event: %v", err)
}

err = syncEvents(vTouch.deviceFile)
if err != nil {
return fmt.Errorf("sync to device file failed: %v", err)
}
return nil}

func (vTouch vTouchPad) RightClick() error {
err := sendBtnEvent(vTouch.deviceFile, evBtnRight, btnStatePressed)
if err != nil {
return fmt.Errorf("Failed to issue the RightClick event: %v", err)
}

err = sendBtnEvent(vTouch.deviceFile, evBtnRight, btnStateReleased)
if err != nil {
return fmt.Errorf("Failed to issue the KeyUp event: %v", err)
}

err = syncEvents(vTouch.deviceFile)
if err != nil {
return fmt.Errorf("sync to device file failed: %v", err)
}
return nil
}

func (vTouch vTouchPad) Close() error {
return closeDevice(vTouch.deviceFile)
}

// CreateMouse will create a new mouse input device. A mouse is a device that allows relative input.
// Relative input means that all changes to the x and y coordinates of the mouse pointer will be
func CreateMouse(path string, name []byte) (Mouse, error) {
Expand Down
31 changes: 31 additions & 0 deletions uinput_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,34 @@ func TestBasicMouseMoves(t *testing.T) {
t.Fatalf("Failed to close device. Last error was: %s\n", err)
}
}

func TestBasicTouchPadMoves(t *testing.T) {
absDev, err := CreateTouchPad("/dev/uinput", []byte("Test TouchPad"), 0, 1024, 0, 768)
if err != nil {
t.Fatalf("Failed to create the virtual touch pad. Last error was: %s\n", err)
}

err = absDev.MoveTo(0,0)
if err != nil {
t.Fatalf("Failed to move cursor to initial position. Last error was: %s\n", err)
}

err = absDev.MoveTo(100, 200)
if (err != nil) {
t.Fatalf("Failed to move cursor to position x:100, y:200. Last error was: %s\n", err)
}
err = absDev.RightClick()
if err != nil {
t.Fatalf("Failed to perform right click. Last error was: %s\n", err)
}

err = absDev.LeftClick()
if err != nil {
t.Fatalf("Failed to perform right click. Last error was: %s\n", err)
}

err = absDev.Close()
if err != nil {
t.Fatalf("Failed to close device. Last error was: %s\n", err)
}
}
90 changes: 88 additions & 2 deletions uinputwrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,67 @@ func toUinputName(name []byte) (uinputName [uinputMaxNameSize]byte) {
return fixedSizeName
}

func createTouchPad(path string, name []byte, minX int32, maxX int32, minY int32, maxY int32) (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(evKey))
if err != nil {
deviceFile.Close()
return nil, fmt.Errorf("failed to register key device: %v", err)
}
// register button events (in order to enable left and right click)
err = ioctl(deviceFile, uiSetKeyBit, uintptr(evBtnLeft))
if err != nil {
deviceFile.Close()
return nil, fmt.Errorf("failed to register left click event: %v", err)
}
err = ioctl(deviceFile, uiSetKeyBit, uintptr(evBtnRight))
if err != nil {
deviceFile.Close()
return nil, fmt.Errorf("failed to register right click event: %v", err)
}

err = registerDevice(deviceFile, uintptr(evAbs))
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, uiSetAbsBit, uintptr(absX))
if err != nil {
deviceFile.Close()
return nil, fmt.Errorf("failed to register absolute x axis events: %v", err)
}
err = ioctl(deviceFile, uiSetAbsBit, uintptr(absY))
if err != nil {
deviceFile.Close()
return nil, fmt.Errorf("failed to register absolute y axis events: %v", err)
}

var absMin [absSize]int32
absMin[absX] = minX
absMin[absY] = minY

var absMax [absSize]int32
absMax[absX] = maxX
absMax[absY] = maxY

return createUsbDevice(deviceFile,
uinputUserDev{
Name: toUinputName(name),
ID: inputID{
Bustype: busUsb,
Vendor: 0x4711,
Product: 0x0817,
Version: 1},
Absmin: absMin,
Absmax: absMax})
}

func createMouse(path string, name []byte) (fd *os.File, err error) {
deviceFile, err := createDeviceFile(path)
if err != nil {
Expand All @@ -149,7 +210,7 @@ func createMouse(path string, name []byte) (fd *os.File, err error) {
err = registerDevice(deviceFile, uintptr(evKey))
if err != nil {
deviceFile.Close()
return nil, fmt.Errorf("failed to register virtual mouse device: %v", err)
return nil, fmt.Errorf("failed to register key device: %v", err)
}
// register button events (in order to enable left and right click)
err = ioctl(deviceFile, uiSetKeyBit, uintptr(evBtnLeft))
Expand Down Expand Up @@ -231,6 +292,31 @@ func sendBtnEvent(deviceFile *os.File, key int, btnState int) (err error) {
return err
}

func sendAbsEvent(deviceFile *os.File, xPos int32, yPos int32) error {
var ev [2]inputEvent
ev[0].Type = evAbs
ev[0].Code = absX
ev[0].Value = xPos

ev[1].Type = evAbs
ev[1].Code = absY
ev[1].Value = yPos

for _, iev := range ev {
buf, err := inputEventToBuffer(iev)
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 sendRelEvent(deviceFile *os.File, eventCode uint16, pixel int32) error {
iev := inputEvent{
Time: syscall.Timeval{Sec: 0, Usec: 0},
Expand All @@ -245,7 +331,7 @@ func sendRelEvent(deviceFile *os.File, eventCode uint16, pixel int32) error {

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

return syncEvents(deviceFile)
Expand Down

0 comments on commit d3f0187

Please sign in to comment.