Skip to content

Commit

Permalink
Major refactoring:
Browse files Browse the repository at this point in the history
- Adhere to go naming standards (renaming of all constants)
- Improve error handling
- Remove C dependencies completely
- Add .gitignore
  • Loading branch information
Benjamin Dahlmanns committed Feb 11, 2017
1 parent 6ab7fee commit 232d2ab
Show file tree
Hide file tree
Showing 9 changed files with 470 additions and 448 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea/*
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
Uinput
======

This go package provides wrapper functions for the LINUX uinput device. As it stands right now, only virtual keyboards are supported. Support for realative and absolute input devices will be added later on.
This package provides pure go wrapper functions for the LINUX uinput device and therefore allows the creation of virtual input devices in userland. As it stands right now, only virtual keyboards are supported. Support for relative and absolute input devices will be added later on.

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. An example file could be named "99-user.rules" and the line you would need to add for "user", belonging to the group "utest" would be <pre><code>KERNEL=="uinput", GROUP="utest", MODE:="0660"</code></pre> Also, make sure to restart in order for these settings to work. Which approach you'll take is up to you, although I would encourage the creation of a udev rule, as it is the clean approach.

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.

Don't worry about the C sources, as CGO will take care of compiling these for you as well. However, you will need to make sure to have the necessray header files for gcc installed on your system. They should be located underneath "/usr/include/linux".

License
--------
The package falls under the MIT license. Please see the "LICENSE" file for details.
Expand Down
7 changes: 4 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
TODO
====

1. Create Tests for the uinput package
2. Migrate code from C to GO
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)
76 changes: 26 additions & 50 deletions uinput.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
Package uinput provides access to the userland input device driver uinput on linux systems.
For now, only the creation of a virtual keyboard is supported. The keycodes, that are available
and can be used to trigger key press events, are part of this package ("KEY_1" for number 1, for
and can be used to trigger key press events, are part of this package ("Key1" for number 1, for
example).
In order to use the virtual keyboard, you will need to follow these three steps:
Expand All @@ -10,22 +10,19 @@ In order to use the virtual keyboard, you will need to follow these three steps:
Example: vk, err := CreateKeyboard("/dev/uinput", "Virtual Keyboard")
2. Send Button events to the device
Example: err = vk.SendKeyPress(uinput.KEY_D)
err = vk.SendKeyRelease(uinput.KEY_D)
Example: err = vk.SendKeyPress(uinput.KeyD)
err = vk.SendKeyRelease(uinput.KeyD)
3. Close the device
Example: err = vk.Close()
*/
package uinput

/*
#include "uinputwrapper.h"
*/
import "C"
import (
"errors"
"io"
"unsafe"
"os"
"errors"
"fmt"
)

// A Keyboard is an key event output device. It is used to
Expand All @@ -43,65 +40,44 @@ type Keyboard interface {
}

type vKeyboard struct {
name string
fd int
name []byte
deviceFile *os.File
}

// CreateKeyboard will create a new keyboard using the given uinput
// device path of the uinput device.
func CreateKeyboard(path, name string) (Keyboard, error) {
func CreateKeyboard(path string, name []byte) (Keyboard, 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 := createVKeyboardDevice(path, name)
if err != nil {
return nil, err
}

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

// SendKeyPress will send the key code passed (see uinputdefs.go for available keycodes). Note that unless a key release
// event is sent to the device, the key will remain pressed and therefore input will continuously be generated. Therefore,
// do not forget to call "SendKeyRelease" afterwards.
func (vk vKeyboard) SendKeyPress(key int) error {
return sendBtnEvent(vk.fd, key, 1)
return sendBtnEvent(vk.deviceFile, key, btnStatePressed)
}

// SendKeyRelease will release the given key passed as a parameter (see uinputdefs.go for available keycodes). In most
// cases it is recommended to call this function immediately after the "SendKeyPress" function in order to only issue a
// singel key press.
func (vk vKeyboard) SendKeyRelease(key int) error {
return sendBtnEvent(vk.fd, key, 0)
return sendBtnEvent(vk.deviceFile, key, btnStateReleased)
}

// Close will close the device and free resources.
// It's usually a good idea to use defer to call this function.
func (vk vKeyboard) Close() error {
return closeDevice(vk.fd)
}

func createVKeyboardDevice(path, name string) (deviceID int, err error) {
uinputDevice := C.CString(path)
defer C.free(unsafe.Pointer(uinputDevice))

if name == "" {
name = "uinput_default_vkeyboard"
}
virtDeviceName := C.CString(name)
defer C.free(unsafe.Pointer(virtDeviceName))

var fd C.int
fd = C.initVKeyboardDevice(uinputDevice, virtDeviceName)
if fd < 0 {
// TODO: Map ErrValues into more specific Errors
return 0, errors.New("Could not initialize device")
}

return int(fd), nil
}

func sendBtnEvent(deviceID int, key int, btnState int) (err error) {
if C.sendBtnEvent(C.int(deviceID), C.int(key), C.int(btnState)) < 0 {
return errors.New("Sending keypress failed")
}
return nil
}

func closeDevice(deviceID int) (err error) {
if int(C.releaseDevice(C.int(deviceID))) < 0 {
return errors.New("Closing device failed")
}
return nil
return closeDevice(vk.deviceFile)
}
10 changes: 5 additions & 5 deletions uinput_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import (

// This test will create a basic VKeyboard, send a key command and then close the keyboard device
func TestBasicKeyboard(t *testing.T) {
vk, err := CreateKeyboard("/dev/uinput", "Test Basic Keyboard")
vk, err := CreateKeyboard("/dev/uinput", []byte("Test Basic Keyboard"))
if err != nil {
t.Fatalf("Failed to create the virtual keyboard. Last error was: %s\n", err)
}

err = vk.SendKeyPress(KEY_1)
err = vk.SendKeyPress(Key1)

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

err = vk.SendKeyRelease(KEY_1)
err = vk.SendKeyRelease(Key1)

if err != nil {
t.Fatalf("Failed to send key event. Last error was: %s\n", err)
Expand All @@ -33,7 +33,7 @@ func TestBasicKeyboard(t *testing.T) {
// This test will confirm that a proper error code is returned if an invalid uinput path is
// passed to the library
func TestInvalidDevicePath(t *testing.T) {
vk, err := CreateKeyboard("/invalid/path", "Invalid Device Path")
vk, err := CreateKeyboard("/invalid/path", []byte("Invalid Device Path"))
if err == nil {
// this usually shouldn't happen, but if the device is created, we need to close it
vk.Close()
Expand All @@ -44,7 +44,7 @@ func TestInvalidDevicePath(t *testing.T) {
// This test will confirm that a proper error code is returned if an invalid keycode is
// passed to the library
func TestInvalidKeycode(t *testing.T) {
vk, err := CreateKeyboard("/dev/uinput", "Test Keyboard")
vk, err := CreateKeyboard("/dev/uinput", []byte("Test Keyboard"))
if err != nil {
t.Fatalf("Failed to create the virtual keyboard. Last error was: %s\n", err)
}
Expand Down
Loading

0 comments on commit 232d2ab

Please sign in to comment.