split src/devices/input into src/devices/input/{raw,wrap}
This commit is contained in:
57
src/device/input/raw/keyboard.go
Normal file
57
src/device/input/raw/keyboard.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package raw
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type Keyboard struct {
|
||||
}
|
||||
|
||||
func NewKeyboard() Keyboard {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
if err := exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run(); err != nil {
|
||||
panic(err)
|
||||
} else if err := exec.Command("stty", "-F", "/dev/tty", "-echo").Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "darwin":
|
||||
if err := exec.Command("stty", "-f", "/dev/tty", "cbreak", "min", "1").Run(); err != nil {
|
||||
panic(err)
|
||||
} else if err := exec.Command("stty", "-f", "/dev/tty", "-echo").Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
default:
|
||||
panic(runtime.GOOS)
|
||||
}
|
||||
return Keyboard{}
|
||||
}
|
||||
|
||||
func (kb Keyboard) Close() {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
if err := exec.Command("stty", "-F", "/dev/tty", "echo").Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "darwin":
|
||||
if err := exec.Command("stty", "-f", "/dev/tty", "echo").Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (kb Keyboard) Read() []byte {
|
||||
b := make([]byte, 5)
|
||||
n, err := os.Stdin.Read(b)
|
||||
if err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
}
|
||||
if os.Getenv("DEBUG") == "true" {
|
||||
log.Printf("raw.Keyboard.Read() %s", b[:n])
|
||||
}
|
||||
return b[:n]
|
||||
}
|
||||
92
src/device/input/raw/random.go
Normal file
92
src/device/input/raw/random.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package raw
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/go-yaml/yaml"
|
||||
)
|
||||
|
||||
type Random struct {
|
||||
generator func() byte
|
||||
down []byte
|
||||
}
|
||||
|
||||
func NewRandom(generator func() byte) *Random {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return &Random{generator: generator}
|
||||
}
|
||||
|
||||
func (r *Random) Close() {
|
||||
}
|
||||
|
||||
func (r *Random) Read() []byte {
|
||||
return []byte{r.generator()}
|
||||
}
|
||||
|
||||
func randomCharFromRange(start, stop byte) func() byte {
|
||||
return func() byte {
|
||||
return start + byte(rand.Int()%int(1+stop-start))
|
||||
}
|
||||
}
|
||||
|
||||
func randomCharFromWeightFile(p string) func() byte {
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return randomCharFromWeightReader(bytes.NewReader(b))
|
||||
}
|
||||
|
||||
func randomCharFromWeightReader(r io.Reader) func() byte {
|
||||
var m map[string]int
|
||||
if err := yaml.NewDecoder(r).Decode(&m); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
byted := map[byte]int{}
|
||||
for k, v := range m {
|
||||
if len(k) != 1 {
|
||||
panic(fmt.Sprintf("keys must be 1 character but got %q", k))
|
||||
}
|
||||
byted[k[0]] = v
|
||||
}
|
||||
return randomCharFromWeights(byted)
|
||||
}
|
||||
|
||||
func randomCharFromWeights(m map[byte]int) func() byte {
|
||||
type pair struct {
|
||||
b byte
|
||||
i int
|
||||
}
|
||||
result := make([]pair, 0, len(m))
|
||||
sum := 0
|
||||
for k, v := range m {
|
||||
result = append(result, pair{b: k, i: v})
|
||||
if v < 0 {
|
||||
panic("each weight must each be natural")
|
||||
}
|
||||
sum += v
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].i < result[j].i
|
||||
})
|
||||
if sum <= 0 {
|
||||
panic("weights must total nonzero")
|
||||
}
|
||||
return func() byte {
|
||||
r := rand.Int()
|
||||
n := r % (sum + 1)
|
||||
for _, v := range result {
|
||||
n -= v.i
|
||||
if n <= 0 {
|
||||
return v.b
|
||||
}
|
||||
}
|
||||
panic("how")
|
||||
}
|
||||
}
|
||||
45
src/device/input/raw/random_test.go
Normal file
45
src/device/input/raw/random_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package raw
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRandomCharFromWeights(t *testing.T) {
|
||||
weights := map[byte]int{
|
||||
'a': 1,
|
||||
'b': 20,
|
||||
}
|
||||
foo := randomCharFromWeights(weights)
|
||||
for {
|
||||
got := foo()
|
||||
if got == 'a' {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomCharFromWeightsReader(t *testing.T) {
|
||||
t.Run("json", func(t *testing.T) {
|
||||
b := `{"a": 1}`
|
||||
r := strings.NewReader(b)
|
||||
foo := randomCharFromWeightReader(r)
|
||||
for i := 0; i < 10; i++ {
|
||||
if got := foo(); got != 'a' {
|
||||
t.Errorf("%c", got)
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("yaml", func(t *testing.T) {
|
||||
b := `
|
||||
a: 2000
|
||||
`
|
||||
r := strings.NewReader(b)
|
||||
foo := randomCharFromWeightReader(r)
|
||||
for i := 0; i < 10; i++ {
|
||||
if got := foo(); got != 'a' {
|
||||
t.Errorf("%c", got)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
26
src/device/input/raw/raw.go
Normal file
26
src/device/input/raw/raw.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package raw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Raw interface {
|
||||
Read() []byte
|
||||
Close()
|
||||
}
|
||||
|
||||
func New(ctx context.Context) Raw {
|
||||
if os.Getenv("RAW_KEYBOARD") == "true" {
|
||||
return NewKeyboard()
|
||||
}
|
||||
if port, _ := strconv.Atoi(os.Getenv("RAW_UDP")); port != 0 {
|
||||
return NewUDP(port)
|
||||
}
|
||||
generator := randomCharFromRange('a', 'g')
|
||||
if p, ok := os.LookupEnv("RAW_RANDOM_WEIGHT_FILE"); ok && len(p) > 0 {
|
||||
generator = randomCharFromWeightFile(p)
|
||||
}
|
||||
return NewRandom(generator)
|
||||
}
|
||||
9
src/device/input/raw/raw_test.go
Normal file
9
src/device/input/raw/raw_test.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package raw
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRaw(t *testing.T) {
|
||||
var _ Raw = &Random{}
|
||||
var _ Raw = UDP{}
|
||||
var _ Raw = Keyboard{}
|
||||
}
|
||||
39
src/device/input/raw/udp.go
Normal file
39
src/device/input/raw/udp.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package raw
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type UDP struct {
|
||||
conn net.PacketConn
|
||||
}
|
||||
|
||||
func NewUDP(port int) UDP {
|
||||
conn, err := net.ListenPacket("udp", ":"+strconv.Itoa(port))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return UDP{
|
||||
conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
func (udp UDP) Read() []byte {
|
||||
panic("NEEDS TO BE IN BG THREAD SO WE CAN SHUT DOWN WITHOUT BLOCKING ON READ OR AT LEAST BE RESPONSIVE")
|
||||
buff := make([]byte, 1024)
|
||||
n, _, err := udp.conn.ReadFrom(buff)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if os.Getenv("DEBUG") == "true" {
|
||||
log.Printf("raw.UDP.Read() => %s", buff[:n])
|
||||
}
|
||||
return buff[:n]
|
||||
}
|
||||
|
||||
func (udp UDP) Close() {
|
||||
udp.conn.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user