/gopi-input

gopi input services

Primary LanguageGoApache License 2.0Apache-2.0

gopi-input

This respository contains input and keymap modules for gopi and some example programs in the cmd folder. The input module supports keyboards, mice and touchscreens at present, and also allows ypu to add your own devices (for example, the remotes repository contains devices which create key presses from IR remote controls).

There are also some examples for remotely accessing input events from devices, so you could for example run a service taking input from devices in one place and consume those input events on a different host.

The gopi modules provided by this repository are:

Platform Import Type Name
Linux github.com/djthorpe/gopi-input/sys/input gopi.MODULE_TYPE_INPUT sys/input/linux
Any github.com/djthorpe/gopi-input/sys/keymap gopi.MODULE_TYPE_KEYMAP sys/keymap

The input module provides an Input Manager which can be used for discovering input devices (keyboards, mouse or touchscreen). It publishes events when the input devices receive events (key presses, releases and cursor moves, for example). This is currently implemented for Linux.

The keymap module can receive input events from keyboards and output runes based on a set of rules. For example, the 'A' key pressed whilst the shift key is pressed will result in the upper-case 'A' rune event being published, and so forth. You can create, modify and delete keymap files through this module. Ultimately the storage for the keymap files should be abstracted, so you could for example store keymap files centrally, not on a local filesystem.

Using the Input Manager

You create an input manager by importing the module into your code and then specifying you want the input manager module. For example:

package main

import (
    // Frameworks
    gopi "github.com/djthorpe/gopi"

    // Import Input Manager
    _ "github.com/djthorpe/gopi-input/sys/input"
)

func Main(app *gopi.AppInstance, done chan<- struct{}) error {
    // Open all devices 
    if _, err := app.Input.OpenDevicesByName("",gopi.INPUT_TYPE_ANY,gopi.INPUT_BUS_ANY); err != nil {
        return err
    }
    // Assuming there were some devices opened, subscribe to events
    // from the input manager
    events := app.Input.Subscribe()
    // Wait for a single event and print it out
    select {
        case evt := <- event.(gopi.InputEvent):
            fmt.Println(evt)
    }
    // Unsubscribe from input manager events
    app.Input.Unsubscribe(events)
    // Return success
    return nil
}

func main() {
    // We want to use the input manager
    config := gopi.NewAppConfig("input")
    // Start main loop
    os.Exit(gopi.CommandLineTool(config, Main))
}

In reality, your code might use a Main function to set up the devices and a RunLoop background function to subscribe to events and process them. At present the input manager emits events which conform to the gopi.InputEvent interface. The concepts for any emitted event are:

Event Data Event Type Description
Timestamp() All Increasing counter of when an event happened. You might want to use this information to determine if an event is a single click, double click, etc.
DeviceType() All Information on the type of device emitting the event, for example, Keyboard, Mouse, Touchscreen
Device() All Unique identifier for the device emitting the event
EventType() All Type of event. For example, key press release, mouse move, and so forth
KeyCode() INPUT_EVENT_KEYPRESS, INPUT_EVENT_KEYRELEASE, INPUT_EVENT_KEYREPEAT, INPUT_EVENT_TOUCHPRESS, INPUT_EVENT_TOUCHRELEASE Provides the code which key was pressed
KeyState() All Current state of certain toggle keys (Shift, Control, Alt and so forth)
ScanCode() INPUT_EVENT_KEYPRESS, INPUT_EVENT_KEYRELEASE, INPUT_EVENT_KEYREPEAT Raw code for the key, which usually relates to the key position on the keyboard
Position() INPUT_EVENT_ABSPOSITION, INPUT_EVENT_RELPOSITION, INPUT_EVENT_TOUCHPOSITION Absolute position recorded
Relative() INPUT_EVENT_RELPOSITION Relative movement for a mouse since the last mouse movement
Slot() INPUT_EVENT_TOUCHPRESS, INPUT_EVENT_TOUCHRELEASE Slot number of a touchscreen event, where a touchscreen supports multitouch events (when more than one touch happens simultaneously on a screen)

See the interface definitions for gopi for more information on input events.

Implementing an InputDevice

You can implement your own input device which can emit events through an inout manager. There is an example implementation which emits key presses. This example demonstates creating an InputDevice module and hooking it into the input manager in the init method:

func init() {
	gopi.RegisterModule(gopi.Module{
		Name:     "input/device/helloworld",
		Requires: []string{"input"},
		Type:     gopi.MODULE_TYPE_OTHER,
		New: func(app *gopi.AppInstance) (gopi.Driver, error) {
			return gopi.Open(InputDevice{}, app.Logger)
		},
		Run: func(app *gopi.AppInstance, device gopi.Driver) error {
			if app.Input == nil {
				return fmt.Errorf("Missing InputManager module instance")
			} else if err := app.Input.AddDevice(device.(gopi.InputDevice)); err != nil {
				return err
			} else {
				return nil
			}
		},
	})
}

Then in your application you simply need to import the custom device:

package main

import (      
	_ "github.com/djthorpe/gopi-input/sys/input"
	_ "github.com/djthorpe/gopi-input/sys/input-device-helloworld"
	_ "github.com/djthorpe/gopi/sys/logger"
)

func EventLoop(app *gopi.AppInstance, done <-chan struct{}) error {
	evt_input := app.Input.Subscribe()
FOR_LOOP:
	for {
		select {
		case <-done:
			break FOR_LOOP
		case event := <-evt_input:
                  // This is where you process incoming input events
			fmt.Println(event)
		}
	}
	app.Input.Unsubscribe(evt_input)
	return nil
}

func Main(app *gopi.AppInstance, done chan<- struct{}) error {
      // Wait for termination
	app.WaitForSignal()
	done <- gopi.DONE
	return nil
}

func main() {
	config := gopi.NewAppConfig("input/device/helloworld")
	os.Exit(gopi.CommandLineTool(config, Main, EventLoop))
}

It's important to note that any devices added to the input manager using the AddDevice method are not automatically closed when the input manager closes.

Features and Bugs

At the moment the following features are in progress:

  • Deal with the case where devices are added and removed whilst the software is running
  • Implement protocol buffers Devices methods that work
  • Provide events for when devices are added and removed so that they can be consumed and more devices opened.
  • Implement the keymap module which translates key presses into runes.
  • Implement a barcode reading module which validates barcodes and perhaps looks up products using an API

I'd appreciate it if you filed feature requests and bugs on github

Examples

Input Tester

The first example is the input-tester which allows you to view input devices and events. In order to build on Linux:

bash% cd gopi-input && go install cmd/input-tester.go
bash% input-tester -help
Usage of input-tester:
  -bus string
        Filter by one or more device busses (none,pci,isapnp,usb,hil,bluetooth,virtual,isa,i8042,xtkbd,rs232,gameport,parport,amiga,adb,i2c,host,gsc,atari,spi)
  -debug
        Set debugging mode
  -input.exclusive
        Input device exclusivity (default true)
  -log.append
        When writing log to file, append output to end of file
  -log.file string
        File for logging (default: log to stderr)
  -name string
        Filter by device name or alias
  -type string
        Filter by type of device (none,keyboard,mouse,touchscreen,joystick,remote)
  -verbose
        Verbose logging
  -watch
        Watch for device events

The -name, -type and -bus flags allow you to chose the devices you want to open. Without specifying these flags, any device would be chosen. By default, the program displays opened input devices and quits. By specifying the -watch flag the program prints out events generated until interrupted (CTRL+C):

bash$ input-tester -watch
+------------------------+----------------------------+----------------+
|          TYPE          |            NAME            |      BUS       |
+------------------------+----------------------------+----------------+
| INPUT_TYPE_KEYBOARD    | Kano Keyboard              | INPUT_BUS_USB  |
| INPUT_TYPE_MOUSE       | Kano Keyboard              | INPUT_BUS_USB  |
| INPUT_TYPE_KEYBOARD    | USB Adapter USB Device     | INPUT_BUS_USB  |
| INPUT_TYPE_TOUCHSCREEN | FT5406 memory based driver | INPUT_BUS_NONE |
+------------------------+----------------------------+----------------+

Watching for events, press CTRL+C to end

DEVICE                    KEY/POSITION              EVENT           STATE          
------------------------- ------------------------- --------------- ---------------
Kano Keyboard [keyboard]  H                         KEYPRESS        none           
Kano Keyboard [keyboard]  H                         KEYRELEASE      none           
Kano Keyboard [keyboard]  E                         KEYPRESS        none           
Kano Keyboard [keyboard]  E                         KEYRELEASE      none           
Kano Keyboard [keyboard]  L                         KEYPRESS        none           
Kano Keyboard [keyboard]  L                         KEYRELEASE      none           
Kano Keyboard [keyboard]  L                         KEYPRESS        none           
Kano Keyboard [keyboard]  L                         KEYRELEASE      none           
Kano Keyboard [keyboard]  O                         KEYPRESS        none           
Kano Keyboard [keyboard]  O                         KEYRELEASE      none           
Kano Keyboard [keyboard]  W                         KEYPRESS        none           
Kano Keyboard [keyboard]  O                         KEYPRESS        none           
Kano Keyboard [keyboard]  W                         KEYRELEASE      none           
Kano Keyboard [keyboard]  O                         KEYRELEASE      none           
Kano Keyboard [keyboard]  R                         KEYPRESS        none           
Kano Keyboard [keyboard]  R                         KEYRELEASE      none           
Kano Keyboard [keyboard]  L                         KEYPRESS        none           
Kano Keyboard [keyboard]  L                         KEYRELEASE      none           
Kano Keyboard [keyboard]  D                         KEYPRESS        none           
Kano Keyboard [keyboard]  D                         KEYRELEASE      none           
Kano Keyboard [mouse]     BTNLEFT                   KEYPRESS        N/A            
Kano Keyboard [mouse]     BTNLEFT                   KEYRELEASE      N/A            
Kano Keyboard [mouse]     BTNLEFT                   KEYPRESS        N/A            
Kano Keyboard [mouse]     BTNLEFT                   KEYRELEASE      N/A            
FT5406 [touchscreen]      BTNTOUCH                  NONE            N/A            
FT5406 [touchscreen]      gopi.Point{ 362.0,145.0 } ABSPOSITION     N/A            
FT5406 [touchscreen]      BTNTOUCH                  TOUCHRELEASE    N/A            
FT5406 [touchscreen]      gopi.Point{ 362.0,145.0 } ABSPOSITION     N/A       

Input Microservice

The input microservice emits input events to any connected microservice clients using gRPC. The protobuf file defines how a client should interact with the service.

Firstly, install the protoc compiler and the GRPC plugin for golang. On Debian Linux (including Raspian Linux) use the following commands:

bash% sudo apt install protobuf-compiler
bash% sudo apt install libprotobuf-dev
bash% go get -u github.com/golang/protobuf/protoc-gen-go

Then, in order to build the microservice:

bash% cd gopi-input && \
  go generate github.com/djthorpe/gopi-input/rpc/protobuf && \
  go install cmd/input-service.go
bash% input-service -help
Usage of input-service:
  -debug
        Set debugging mode
  -input.bus string
        Filter by one or more device busses (none,pci,isapnp,usb,hil,bluetooth,virtual,isa,i8042,xtkbd,rs232,gameport,parport,amiga,adb,i2c,host,gsc,atari,spi)
  -input.exclusive
        Input device exclusivity (default true)
  -input.name string
        Filter by device name or alias
  -input.type string
        Filter by type of device (none,keyboard,mouse,touchscreen,joystick,remote)
  -log.append
        When writing log to file, append output to end of file
  -log.file string
        File for logging (default: log to stderr)
  -rpc.port uint
        Server Port
  -rpc.sslcert string
        SSL Certificate Path
  -rpc.sslkey string
        SSL Key Path
  -verbose
        Verbose logging

In addition to the -input.name,-input.type and -input.bus arguments as before, the -rpc.port specifies a port to listen for client requests on. The -rpc.sslcert and -rpc.sslkey arguments can be used to specify a path for your SSL certificate and key. If you need to generate these, use the following commands, replacing $ORG with your own organization name:

bash% export DAYS=99999 OUT=/var/local/ssl ORG="mutablelogic" HOST=`hostname` && \
  install -d "${OUT}" && \
  openssl req \
    -x509 -nodes \
    -newkey rsa:2048 \
    -keyout "${OUT}/selfsigned.key" \
    -out "${OUT}/selfsigned.crt" \
    -days "${DAYS}" \
    -subj "/O=${ORG}/CN=${HOST}"

Then you can run your microservice as follows:

bash% input-service \
  -rpc.port 8000 \
  -rpc.sslkey="${OUT}/selfsigned.key" -rpc.sslcert="${OUT}/selfsigned.crt"

You will receive a warning that "Microservice discovery is not enabled, continuing" but this simply means that microservice discovery is not enabled. Since you specified a port you can use this port number in your client when connecting.

Input Client

There is a client which can connect to the input microservice. You can install it as follows:

bash% cd gopi-input && \
  go generate github.com/djthorpe/gopi-input/rpc/protobuf && \
  go install cmd/input-client.go
bash% input-client -help
Usage of input-client:
  -addr string
        Gateway address
  -debug
        Set debugging mode
  -log.append
        When writing log to file, append output to end of file
  -log.file string
        File for logging (default: log to stderr)
  -rpc.insecure
        Disable SSL Connection
  -rpc.service string
        Comma-separated list of service names
  -rpc.skipverify
        Skip SSL Verification (default true)
  -rpc.timeout duration
        Connection timeout
  -verbose
        Verbose logging

The output from running the client is the same as running the input-tester except ypu use the -addr flag to determine which microservice to connect to:

bash% input-client -addr rpi3plus.local:8000
DEVICE                    KEY/POSITION              EVENT           STATE          
------------------------- ------------------------- --------------- ---------------
keyboard                  H                         KEYPRESS        none           
keyboard                  H                         KEYRELEASE      none           
keyboard                  E                         KEYPRESS        none           
keyboard                  E                         KEYRELEASE      none           
keyboard                  L                         KEYPRESS        none           
keyboard                  L                         KEYRELEASE      none           
keyboard                  L                         KEYPRESS        none           
keyboard                  L                         KEYRELEASE      none           
keyboard                  O                         KEYPRESS        none           
keyboard                  O                         KEYRELEASE      none           

Use the -rpc.insecure flag on the command line if you don't use SSL for communication.