🎹

Goでevdevを扱う

2023/03/26に公開

evdevとは?

evdevとは、Linuxカーネル内の汎用の入力イベントインターフェース、つまりLinuxであらゆるデバイス(マウス、キーボード、ジョイスティック、ゲームコントローラ、Bluetoothシャッターなど)からの入力を取得するためのものです。
evdevを使えばいろんなデバイスからの入力を受け取ることができ、例えばゲームコントローラーからの入力を取得して画面上のキャラクターを動かすというのも可能になります。
ただし、evdevはあくまでもLinuxのみでしか動きません。WindowsやMacでデバイスから入力を受け取るにはまた別の方法が必要になります。

Goでのevdev

Pythonでevdevを使うのは簡単ですが、なんといってもやはり速度ならGoを使いたいですよね。GithubにGoでevdevを使うためのライブラリがありました。

https://github.com/gvalkov/golang-evdev

今回はこのライブラリを使ってキーボードの入力を受け取っていきたいと思います。

ドキュメント:https://pkg.go.dev/github.com/gvalkov/golang-evdev

go get github.com/gvalkov/golang-evdev

evdevの使い方

基本

main.go
package main
import (
    "github.com/gvalkov/golang-evdev"
)

func main() {
    dev, err := evdev.Open("/dev/input/event0") //イベント番号でペア
    if err != nil {
        panic(err)
    }
    defer dev.File.Close() //またはdefer dev.Close()
    //ここにデバイスからの入力などを取得するコードを書く
}

基本はこれだけです。evdev.Open()にターミナルls /dev/inputで特定したイベントと番号を入れます。
次に入力を取得してみます。(キーボード)

main.go
for {
	events, err := dev.Read()
	if err != nil {
		panic(err)
	}
	for i := range events {
		event := &events[i]
		if event.Type == evdev.EV_KEY {
			keyCode := event.Code //キーの番号。これでどのキーが押されたかを判別する
			if event.Value == 1{ //キーが「押された」時(key_down)
			fmt.Printf("Key_Down: %d", keyCode,)
			}
			if event.Value == 0{//キーを「離した時」(key_up)
			fmt.Printf("Key_UP: %d", keyCode,)
			}
		}
	}
}

forでループを作ってイベントを読み込みます。
event.Value0の場合、それは「キーが押されて離された時」になります。ちなみに

main.gp
if event.Value != 0{

にすれば、「長押ししている時」も含むようになります。

実際に動くコード
main.go
package main
import (
    "github.com/gvalkov/golang-evdev"
)

func main() {
    dev, err := evdev.Open("/dev/input/event0")
    if err != nil {
        panic(err)
    }
    defer dev.File.Close()
    for {
	events, err := dev.Read()
	if err != nil {
		panic(err)
	}
	for i := range events {
		event := &events[i]
		if event.Type == evdev.EV_KEY {
			keyCode := event.Code
			if event.Value == 1{
			fmt.Printf("Key_Down: %d", keyCode,)
			}
			if event.Value == 0{
			fmt.Printf("Key_UP: %d", keyCode,)
			}
		}
	}
	}
}

evdevのひな形

evdevでデバイスを設定するには、そのデバイスのイベント番号が必要になります。ターミナルで以下を叩いてください。

ls /dev/input/

するとこのようなリストが返ってきます。

by-id by-path event0 event1 event2 event3 event4 event5 mice mouse0

ここからデバイスを探し出してイベント番号を特定する方法もありますが、めんどくさいですし、全てのパソコンに接続されているデバイスのイベント番号が同じだとは限りません。なので、プログラムを作って手っ取り早く調べてしまう方法がいいでしょう。

main.go
package main
import(
	"fmt"
	"github.com/gvalkov/golang-evdev"
)
func main(){
	devices, err := evdev.ListInputDevices()
	if err != nil {
	panic(err)
	}
	for dev := range devices {
	fmt.Printf("%s %s %s", dev.Fn, dev.Name, dev.Phys)
	}
}

これを実行すると接続されている各デバイスの名前とイベント番号が表示されます。
また、以下のように名前からデバイスのイベント番号を取得することができます。

main.go
package main
import(
	"fmt"
	"github.com/gvalkov/golang-evdev"
	"strings"
)
func main(){
	devices, err := evdev.ListInputDevices()
	if err != nil {
	panic(err)
	}
	for dev := range devices {
	if strings.Contains(dev.Name, "Keyboard") == true { //もしデバイス名に'Keyboard'を含んでいたら
            devicepath := dev.Fn
            fmt.Printf(devicepath)
        }
	}
}

接続しているデバイスを取得し、一つずつKeyboardという名前を含んでいるかを確認し、もし含んでいたらイベント番号を表示するようにしています。

実際に動くコード
main.go
package main
import(
	"fmt"
	"github.com/gvalkov/golang-evdev"
	"strings"
)
func Search_device(dn string)string{
        devices, err := evdev.ListInputDevices()
	if err != nil {
	panic(err)
	}
	for dev := range devices {
	if strings.Contains(dev.Name,dn) == true {
            devicepath := dev.Fn
            return devicepath
	    break;
        }
	}
	return ""
}
func main(){
	path := Search_device("Keyboard")
	if path == ""{
	panic("Device not found.")
	}
	dev, err := evdev.Open(path)
        if err != nil {
	panic(err)
	}
	defer dev.File.Close()
	
}

まとめ

evdevは一番汎用性が高いLinuxの入力イベントインターフェースだと思います。evdevで様々なデバイスを同じコードで触ることができるのは本当に素晴らしいことです。是非ともevdevを組み合わせていろんなデバイスをコントローラーとして扱いましょう。
もっと知りたい人はドキュメントも見てください。
https://www.kernel.org/doc/html/v4.14/input/event-codes.html
https://pkg.go.dev/github.com/gvalkov/golang-evdev

Discussion