diff --git a/go.mod b/go.mod
index dc9e36a..58180e8 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
require (
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
+ github.com/gorilla/websocket v1.5.0 // indirect
github.com/hajimehoshi/oto v0.7.1 // indirect
github.com/micmonay/keybd_event v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
diff --git a/go.sum b/go.sum
index 5927eae..02f0ba5 100644
--- a/go.sum
+++ b/go.sum
@@ -11,6 +11,8 @@ github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38r
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
+github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk=
diff --git a/src/device/input/raw/public/root.html b/src/device/input/raw/public/root.html
new file mode 100644
index 0000000..ee23bcf
--- /dev/null
+++ b/src/device/input/raw/public/root.html
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/device/input/raw/raw.go b/src/device/input/raw/raw.go
index bbf1f6f..f9630cd 100644
--- a/src/device/input/raw/raw.go
+++ b/src/device/input/raw/raw.go
@@ -9,6 +9,8 @@ import (
var (
FlagRawKeyboard = os.Getenv("RAW_KEYBOARD") == "true"
FlagRawUDP = os.Getenv("RAW_UDP")
+ FlagRawWS = os.Getenv("RAW_WS")
+ FlagDebug = os.Getenv("DEBUG") != ""
FlagRawRandomWeightFile = os.Getenv("RAW_RANDOM_WEIGHT_FILE")
)
@@ -21,6 +23,9 @@ func New(ctx context.Context) Raw {
if FlagRawKeyboard {
return NewKeyboard()
}
+ if port, _ := strconv.Atoi(FlagRawWS); port != 0 {
+ return NewWS(ctx, port)
+ }
if port, _ := strconv.Atoi(FlagRawUDP); port != 0 {
return NewUDP(ctx, port)
}
diff --git a/src/device/input/raw/raw_test.go b/src/device/input/raw/raw_test.go
index 1e3036d..3eb8e37 100644
--- a/src/device/input/raw/raw_test.go
+++ b/src/device/input/raw/raw_test.go
@@ -6,4 +6,5 @@ func TestRaw(t *testing.T) {
var _ Raw = &Random{}
var _ Raw = UDP{}
var _ Raw = Keyboard{}
+ var _ Raw = WS{}
}
diff --git a/src/device/input/raw/udp.go b/src/device/input/raw/udp.go
index 05b2329..45dd19d 100644
--- a/src/device/input/raw/udp.go
+++ b/src/device/input/raw/udp.go
@@ -4,14 +4,9 @@ import (
"context"
"log"
"net"
- "os"
"strconv"
)
-var (
- FlagDebug = os.Getenv("DEBUG") == "true"
-)
-
type UDP struct {
conn net.PacketConn
c chan []byte
diff --git a/src/device/input/raw/ws.go b/src/device/input/raw/ws.go
new file mode 100644
index 0000000..e5887ae
--- /dev/null
+++ b/src/device/input/raw/ws.go
@@ -0,0 +1,120 @@
+package raw
+
+import (
+ "context"
+ _ "embed"
+ "fmt"
+ "log"
+ "net/http"
+ "os"
+
+ "github.com/gorilla/websocket"
+)
+
+type WS struct {
+ ctx context.Context
+ can context.CancelFunc
+ ch chan []byte
+}
+
+func NewWS(ctx context.Context, port int) WS {
+ ctx, can := context.WithCancel(ctx)
+ ws := WS{ctx: ctx, can: can, ch: make(chan []byte, 256)}
+ go ws.listen(port)
+ return ws
+}
+
+func (ws WS) Read() []byte {
+ select {
+ case v := <-ws.ch:
+ return v
+ case <-ws.ctx.Done():
+ return nil
+ }
+}
+
+func (ws WS) Close() {
+ ws.can()
+}
+
+func (ws WS) listen(port int) {
+ server := &http.Server{
+ Addr: fmt.Sprintf(":%d", port),
+ Handler: ws,
+ }
+ go func() {
+ if err := server.ListenAndServe(); err != nil && ws.ctx.Err() == nil {
+ panic(err)
+ }
+ }()
+ log.Println("WS on", port)
+ <-ws.ctx.Done()
+ server.Close()
+}
+
+var upgrader = websocket.Upgrader{
+ ReadBufferSize: 1024,
+ WriteBufferSize: 1024,
+}
+
+func (ws WS) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ r = r.WithContext(ws.ctx)
+ if err := ws.serveHTTP(w, r); err != nil {
+ log.Println(err)
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+}
+
+func (ws WS) serveHTTP(w http.ResponseWriter, r *http.Request) error {
+ switch r.URL.Path {
+ case "/":
+ return ws.serveIndex(w, r)
+ case "/api/ws":
+ return ws.serveWS(w, r)
+ }
+ http.NotFound(w, r)
+ return nil
+}
+
+//go:embed public/root.html
+var rootHTML string
+
+func (ws WS) serveIndex(w http.ResponseWriter, r *http.Request) error {
+ v := rootHTML
+ if FlagDebug {
+ b, _ := os.ReadFile("src/device/input/raw/public/root.html")
+ v = string(b)
+ }
+ w.Write([]byte(v))
+ return nil
+}
+
+func (ws WS) serveWS(w http.ResponseWriter, r *http.Request) error {
+ if err := ws._serveWS(w, r); err != nil {
+ log.Println("_serveWS:", err)
+ }
+ return nil
+}
+
+func (ws WS) _serveWS(w http.ResponseWriter, r *http.Request) error {
+ conn, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ for ws.ctx.Err() == nil {
+ msgType, p, err := conn.ReadMessage()
+ if err != nil {
+ if websocket.IsCloseError(err) || websocket.IsUnexpectedCloseError(err) {
+ return nil
+ }
+ return err
+ }
+ if msgType == websocket.TextMessage {
+ log.Println(string(p))
+ ws.ch <- p
+ }
+ }
+ return nil
+}