Add text-to-speech for announcing mode changes.
This commit is contained in:
parent
3e4367f5e7
commit
8262d78b54
8 changed files with 209 additions and 32 deletions
10
.vscode/tasks.json
vendored
10
.vscode/tasks.json
vendored
|
@ -22,11 +22,6 @@
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
},
|
},
|
||||||
"options": {
|
|
||||||
"env": {
|
|
||||||
"CGO_ENABLED": "0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -39,11 +34,6 @@
|
||||||
"kind": "test",
|
"kind": "test",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
},
|
},
|
||||||
"options": {
|
|
||||||
"env": {
|
|
||||||
"CGO_ENABLED": "0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -2,23 +2,22 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/holoplot/go-evdev"
|
||||||
|
flag "github.com/spf13/pflag"
|
||||||
|
|
||||||
"git.annabunches.net/annabunches/joyful/internal/config"
|
"git.annabunches.net/annabunches/joyful/internal/config"
|
||||||
"git.annabunches.net/annabunches/joyful/internal/logger"
|
"git.annabunches.net/annabunches/joyful/internal/logger"
|
||||||
"git.annabunches.net/annabunches/joyful/internal/mappingrules"
|
"git.annabunches.net/annabunches/joyful/internal/mappingrules"
|
||||||
"git.annabunches.net/annabunches/joyful/internal/virtualdevice"
|
"git.annabunches.net/annabunches/joyful/internal/virtualdevice"
|
||||||
"github.com/holoplot/go-evdev"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getConfigDir() string {
|
func getConfigDir(dir string) string {
|
||||||
configFlag := flag.String("config", "~/.config/joyful", "Directory to read configuration from.")
|
configDir := strings.ReplaceAll(dir, "~", "${HOME}")
|
||||||
flag.Parse()
|
|
||||||
configDir := strings.ReplaceAll(*configFlag, "~", "${HOME}")
|
|
||||||
return os.ExpandEnv(configDir)
|
return os.ExpandEnv(configDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,16 +61,31 @@ func initPhysicalDevices(config *config.ConfigParser) map[string]*evdev.InputDev
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// parse command-line
|
||||||
|
var configFlag string
|
||||||
|
var ttsVoiceFlag string
|
||||||
|
var ttsFlag bool
|
||||||
|
flag.StringVarP(&configFlag, "config", "c", "~/.config/joyful", "Directory to read configuration from.")
|
||||||
|
addTTSFlags(&ttsFlag, &ttsVoiceFlag)
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
// parse configs
|
// parse configs
|
||||||
configDir := getConfigDir()
|
configDir := getConfigDir(configFlag)
|
||||||
config := readConfig(configDir)
|
config := readConfig(configDir)
|
||||||
|
|
||||||
|
tts, err := newTTS(ttsFlag, ttsVoiceFlag)
|
||||||
|
logger.LogIfError(err, "Failed to initialize TTS")
|
||||||
|
if tts != nil {
|
||||||
|
defer tts.Cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize virtual devices with event buffers
|
// Initialize virtual devices with event buffers
|
||||||
vBuffersByName, vBuffersByDevice := initVirtualBuffers(config)
|
vBuffersByName, vBuffersByDevice := initVirtualBuffers(config)
|
||||||
|
|
||||||
// Initialize physical devices
|
// Initialize physical devices
|
||||||
pDevices := initPhysicalDevices(config)
|
pDevices := initPhysicalDevices(config)
|
||||||
|
|
||||||
|
// Load the rules
|
||||||
rules, eventChannel, cancel, wg := loadRules(config, pDevices, getVirtualDevices(vBuffersByName))
|
rules, eventChannel, cancel, wg := loadRules(config, pDevices, getVirtualDevices(vBuffersByName))
|
||||||
|
|
||||||
// initialize the mode variable
|
// initialize the mode variable
|
||||||
|
@ -83,6 +97,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
lastMode := mode
|
||||||
// Get an event (blocks if necessary)
|
// Get an event (blocks if necessary)
|
||||||
channelEvent := <-eventChannel
|
channelEvent := <-eventChannel
|
||||||
|
|
||||||
|
@ -124,6 +139,10 @@ func main() {
|
||||||
rules, eventChannel, cancel, wg = loadRules(config, pDevices, getVirtualDevices(vBuffersByName))
|
rules, eventChannel, cancel, wg = loadRules(config, pDevices, getVirtualDevices(vBuffersByName))
|
||||||
fmt.Println("Config re-loaded. Only rule changes applied. Device and Mode changes require restart.")
|
fmt.Println("Config re-loaded. Only rule changes applied. Device and Mode changes require restart.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if lastMode != mode && tts != nil {
|
||||||
|
tts.Say(mode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
106
cmd/joyful/tts.go
Normal file
106
cmd/joyful/tts.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
//go:build !notts
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.annabunches.net/annabunches/joyful/internal/logger"
|
||||||
|
"github.com/amitybell/piper"
|
||||||
|
asset "github.com/amitybell/piper-asset"
|
||||||
|
alan "github.com/amitybell/piper-voice-alan"
|
||||||
|
jenny "github.com/amitybell/piper-voice-jenny"
|
||||||
|
"github.com/ebitengine/oto/v3"
|
||||||
|
flag "github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TTS struct {
|
||||||
|
piper.TTS
|
||||||
|
dataDir string
|
||||||
|
otoCtx *oto.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
playbackCheckIntervalMs = 250
|
||||||
|
playbackSeekOffsetBytes = 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
func addTTSFlags(ttsFlag *bool, ttsVoiceFlag *string) {
|
||||||
|
flag.BoolVar(ttsFlag, "notts", false, "Disable text-to-speech on mode change.")
|
||||||
|
flag.StringVar(ttsVoiceFlag, "voice", "alan", "Which voice to use for TTS; must be 'alan' or 'jenny'")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTTS(disable bool, voice string) (*TTS, error) {
|
||||||
|
if disable {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dataDir, err := os.MkdirTemp("", "joyful-piper.")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ass asset.Asset
|
||||||
|
switch voice {
|
||||||
|
case "jenny":
|
||||||
|
ass = jenny.Asset
|
||||||
|
case "alan":
|
||||||
|
ass = alan.Asset
|
||||||
|
default:
|
||||||
|
ass = alan.Asset
|
||||||
|
}
|
||||||
|
|
||||||
|
pTTS, err := piper.NewEmbedded(dataDir, ass)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
op := &oto.NewContextOptions{
|
||||||
|
SampleRate: 22050,
|
||||||
|
ChannelCount: 1,
|
||||||
|
Format: oto.FormatSignedInt16LE,
|
||||||
|
}
|
||||||
|
|
||||||
|
otoCtx, readyChan, err := oto.NewContext(op)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
<-readyChan // wait for initialization
|
||||||
|
|
||||||
|
return &TTS{
|
||||||
|
TTS: *pTTS,
|
||||||
|
dataDir: dataDir,
|
||||||
|
otoCtx: otoCtx,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Say" generates TTS audio and plays it in a go routine
|
||||||
|
func (t *TTS) Say(msg string) {
|
||||||
|
go func() {
|
||||||
|
wav, err := t.Synthesize(msg)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.LogError(err, "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wavReader := bytes.NewReader(wav)
|
||||||
|
player := t.otoCtx.NewPlayer(wavReader)
|
||||||
|
// We seek some bytes into the generated audio because there's a click
|
||||||
|
// and a long delay at the beginning of the data.
|
||||||
|
player.Seek(playbackSeekOffsetBytes, io.SeekStart)
|
||||||
|
player.Play()
|
||||||
|
for player.IsPlaying() {
|
||||||
|
time.Sleep(playbackCheckIntervalMs * time.Millisecond)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TTS) Cleanup() {
|
||||||
|
os.RemoveAll(t.dataDir)
|
||||||
|
}
|
16
cmd/joyful/tts_stub.go
Normal file
16
cmd/joyful/tts_stub.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
//go:build notts
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
type Speaker interface {
|
||||||
|
Say(string)
|
||||||
|
Cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTTS(_ bool, _ string) (Speaker, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTTSFlags(ttsFlag *bool, ttsVoiceFlag *string) {
|
||||||
|
return
|
||||||
|
}
|
|
@ -36,7 +36,7 @@ All `rules` must have a `type` parameter. Valid values for this parameter are:
|
||||||
* `axis-to-button` - causes an axis input to produce a button output. This can be repeated with variable speed proportional to the axis' input value
|
* `axis-to-button` - causes an axis input to produce a button output. This can be repeated with variable speed proportional to the axis' input value
|
||||||
* `axis-to-relaxis` - like axis-to-button, but produces a "relative axis" output value. This is useful for simulating mouse scrollwheel and movement events.
|
* `axis-to-relaxis` - like axis-to-button, but produces a "relative axis" output value. This is useful for simulating mouse scrollwheel and movement events.
|
||||||
|
|
||||||
Configuration options for each rule type vary. See <examples/ruletypes.yml> for an example of each type with all options specified.
|
Configuration options for each rule type vary. See [examples/ruletypes.yml](examples/ruletypes.yml) for an example of each type with all options specified.
|
||||||
|
|
||||||
### Event Codes
|
### Event Codes
|
||||||
|
|
||||||
|
|
13
go.mod
13
go.mod
|
@ -3,17 +3,28 @@ module git.annabunches.net/annabunches/joyful
|
||||||
go 1.24.4
|
go 1.24.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/amitybell/piper v0.0.0-20250621082041-2bb74e3a4a55
|
||||||
|
github.com/amitybell/piper-asset v0.0.0-20231030194325-d36a29e3b1fd
|
||||||
|
github.com/amitybell/piper-voice-alan v0.0.0-20231118093148-059963c24dbd
|
||||||
|
github.com/amitybell/piper-voice-jenny v0.0.0-20231118093224-dcf0d49e46b7
|
||||||
|
github.com/ebitengine/oto/v3 v3.3.3
|
||||||
github.com/goccy/go-yaml v1.18.0
|
github.com/goccy/go-yaml v1.18.0
|
||||||
github.com/holoplot/go-evdev v0.0.0-20240306072622-217e18f17db1
|
github.com/holoplot/go-evdev v0.0.0-20240306072622-217e18f17db1
|
||||||
github.com/jonboulle/clockwork v0.5.0
|
github.com/jonboulle/clockwork v0.5.0
|
||||||
github.com/spf13/pflag v1.0.7
|
github.com/spf13/pflag v1.0.7
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/adrg/xdg v0.5.3 // indirect
|
||||||
|
github.com/amitybell/piper-bin-linux v0.0.0-20250621082830-f5d5d85fa076 // indirect
|
||||||
|
github.com/amitybell/piper-bin-windows v0.0.0-20231118093113-cc2cef2f6b74 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/ebitengine/purego v0.8.4 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
28
go.sum
28
go.sum
|
@ -1,11 +1,33 @@
|
||||||
|
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||||
|
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||||
|
github.com/amitybell/piper v0.0.0-20250621082041-2bb74e3a4a55 h1:8MKEDgDBbYKAphlRUcAEHT1Uam7xBjA5E/SmGHhNH10=
|
||||||
|
github.com/amitybell/piper v0.0.0-20250621082041-2bb74e3a4a55/go.mod h1:y0aDZdCM3erPmpX+rDGoF0O2ZdCqZvAxNjYUPrK/O7U=
|
||||||
|
github.com/amitybell/piper-asset v0.0.0-20231030194325-d36a29e3b1fd h1:4MLHn2cCVhzhPLlPO6946h1S0yk3o7Ry1831DEa5EcE=
|
||||||
|
github.com/amitybell/piper-asset v0.0.0-20231030194325-d36a29e3b1fd/go.mod h1:MiDKnt4NenfcrsVxYAxQW0nu4zjFYQPjGzzLB5MvOz8=
|
||||||
|
github.com/amitybell/piper-bin-linux v0.0.0-20250621082830-f5d5d85fa076 h1:aST7iEpuMr507piwgx0WNDezW6ycWIE+ejtnXXaMgI0=
|
||||||
|
github.com/amitybell/piper-bin-linux v0.0.0-20250621082830-f5d5d85fa076/go.mod h1:dVR33O0l/AFgQNmZfywfgNZ6qlpCKPhLnn9UpeMMLdM=
|
||||||
|
github.com/amitybell/piper-bin-windows v0.0.0-20231118093113-cc2cef2f6b74 h1:T5hXX0Z2JaE5gtZ7LScjG0r0BmDk0+FWlzyZ2b1nboo=
|
||||||
|
github.com/amitybell/piper-bin-windows v0.0.0-20231118093113-cc2cef2f6b74/go.mod h1:5Ea0Pc0QdO8FeriIXcqZtHViM2fi589jtFubrjaAk6w=
|
||||||
|
github.com/amitybell/piper-voice-alan v0.0.0-20231118093148-059963c24dbd h1:DsXuiWSHsbBkVNL7cBAdXD95kNwrE0Ck05OasSeUZ4g=
|
||||||
|
github.com/amitybell/piper-voice-alan v0.0.0-20231118093148-059963c24dbd/go.mod h1:5ghO6mSctWNXfDoh3r46HQEMIcPr5DqE5TMYfp5hskY=
|
||||||
|
github.com/amitybell/piper-voice-jenny v0.0.0-20231030195502-2afb5ebf3c45 h1:V/HZAQuprvdo0xXToxAuTLSwD3YrqRpDZLVBOOD+2aE=
|
||||||
|
github.com/amitybell/piper-voice-jenny v0.0.0-20231030195502-2afb5ebf3c45/go.mod h1:eKG2Bo69QGTVKKKKApafZr+4v4zk40jYNijh0s8/PzU=
|
||||||
|
github.com/amitybell/piper-voice-jenny v0.0.0-20231118093224-dcf0d49e46b7 h1:GMYJcgP1OKBMBuQfP7r0aRk4PS0AaviHVTERtdt/e/o=
|
||||||
|
github.com/amitybell/piper-voice-jenny v0.0.0-20231118093224-dcf0d49e46b7/go.mod h1:eKG2Bo69QGTVKKKKApafZr+4v4zk40jYNijh0s8/PzU=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/ebitengine/oto/v3 v3.3.3 h1:m6RV69OqoXYSWCDsHXN9rc07aDuDstGHtait7HXSM7g=
|
||||||
|
github.com/ebitengine/oto/v3 v3.3.3/go.mod h1:MZeb/lwoC4DCOdiTIxYezrURTw7EvK/yF863+tmBI+U=
|
||||||
|
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||||
|
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/holoplot/go-evdev v0.0.0-20240306072622-217e18f17db1 h1:92OsBIf5KB1Tatx+uUGOhah73jyNUrt7DmfDRXXJ5Xo=
|
github.com/holoplot/go-evdev v0.0.0-20240306072622-217e18f17db1 h1:92OsBIf5KB1Tatx+uUGOhah73jyNUrt7DmfDRXXJ5Xo=
|
||||||
github.com/holoplot/go-evdev v0.0.0-20240306072622-217e18f17db1/go.mod h1:iHAf8OIncO2gcQ8XOjS7CMJ2aPbX2Bs0wl5pZyanEqk=
|
github.com/holoplot/go-evdev v0.0.0-20240306072622-217e18f17db1/go.mod h1:iHAf8OIncO2gcQ8XOjS7CMJ2aPbX2Bs0wl5pZyanEqk=
|
||||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||||
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||||
|
@ -14,8 +36,10 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
||||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||||
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|
33
readme.md
33
readme.md
|
@ -29,6 +29,7 @@ Joyful is ideal for Linux gamers who enjoy space and flight sims and miss the fe
|
||||||
* Hat support
|
* Hat support
|
||||||
* HIDRAW support for more button options.
|
* HIDRAW support for more button options.
|
||||||
* Sensitivity Curves.
|
* Sensitivity Curves.
|
||||||
|
* Packaged builds for Arch and possibly other distributions.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
@ -44,19 +45,29 @@ After building (see below) and writing your configuration (see above), just run
|
||||||
|
|
||||||
Pressing `<enter>` in the running terminal window will reload the `rules` section of your config files, so you can make changes to your rules without restarting the application. Applying any changes to `devices` or `modes` requires exiting and re-launching the program.
|
Pressing `<enter>` in the running terminal window will reload the `rules` section of your config files, so you can make changes to your rules without restarting the application. Applying any changes to `devices` or `modes` requires exiting and re-launching the program.
|
||||||
|
|
||||||
|
## Build & Install
|
||||||
|
|
||||||
|
To build joyful, first use your distribution's package manager to install `go` and `alsa-lib` (this may be `libasound2-dev` or `libasound2-devel` depending on your distribution), then run:
|
||||||
|
|
||||||
|
```
|
||||||
|
go build -o build/ ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, copy the binaries in the `build/` directory to somewhere in your `$PATH`. (details depend on your setup, but typically somewhere like `/usr/local/bin` or `~/bin`)
|
||||||
|
|
||||||
|
### Machine Learning Disclosure
|
||||||
|
|
||||||
|
Joyful's text-to-speech support is dependent on [Piper](https://github.com/rhasspy/piper), which uses an offline Machine Learning (ML) model for speech synthesis. The project authors are extremely skeptical of ML/AI technologies in general, but consider speech synthesis, especially offline/local speech synthesis, to be one of the most defensible use cases for it. Since it is very difficult to find text-to-speech systems that don't use ML under the hood (especially that have extant golang wrappers or bindings), this is considered a necessary tradeoff.
|
||||||
|
|
||||||
|
However, if you don't want any ML running on your system, you can optionally choose to skip TTS support at compile-time by building with this command:
|
||||||
|
|
||||||
|
```
|
||||||
|
go build -o build -tags notts ./...
|
||||||
|
```
|
||||||
|
|
||||||
## Technical details
|
## Technical details
|
||||||
|
|
||||||
Joyful is written in golang, and uses evdev/uinput to manage devices. See `cmd/joyful/main.go` for the program's entry point.
|
Joyful is written in golang, and uses `evdev`/`uinput` to manage devices, `piper` and `oto` for TTS. See [cmd/joyful/main.go](cmd/joyful/main.go) for the program's entry point.
|
||||||
|
|
||||||
### Build & Install
|
|
||||||
|
|
||||||
To build joyful, install `go` via your package manager, then run:
|
|
||||||
|
|
||||||
```
|
|
||||||
CGO_ENABLED=0 go build -o build/ ./...
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy the binaries in the `build/` directory to somewhere in your `$PATH`. (details depend on your setup, but typically somewhere like `/usr/local/bin` or `~/bin`)
|
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue