|
|
@ -1,26 +1,26 @@
|
|
|
|
package main
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
import (
|
|
|
|
"crypto/ed25519"
|
|
|
|
"crypto/ed25519"
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/x509"
|
|
|
|
"crypto/x509"
|
|
|
|
"encoding/binary"
|
|
|
|
"encoding/binary"
|
|
|
|
"encoding/pem"
|
|
|
|
"encoding/pem"
|
|
|
|
"flag"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os"
|
|
|
|
"os/signal"
|
|
|
|
"os/signal"
|
|
|
|
"syscall"
|
|
|
|
"syscall"
|
|
|
|
"time"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"git.metznet.ca/MetzNet/go-ncurses"
|
|
|
|
"git.metznet.ca/MetzNet/go-ncurses"
|
|
|
|
"git.metznet.ca/MetzNet/pnyx"
|
|
|
|
"git.metznet.ca/MetzNet/pnyx"
|
|
|
|
"github.com/gen2brain/malgo"
|
|
|
|
"github.com/gen2brain/malgo"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/hraban/opus"
|
|
|
|
"github.com/hraban/opus"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
var decoders = map[pnyx.PeerID]chan[]byte{}
|
|
|
|
var decoders = map[pnyx.PeerID]chan []byte{}
|
|
|
|
var encoder *opus.Encoder
|
|
|
|
var encoder *opus.Encoder
|
|
|
|
var sample_rate int = 0
|
|
|
|
var sample_rate int = 0
|
|
|
|
|
|
|
|
|
|
|
@ -33,18 +33,18 @@ func set_sample_rate(audio_data chan []int16, new_sample_rate int) error {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for peer_id, decoder_chan := range(decoders) {
|
|
|
|
for peer_id, decoder_chan := range decoders {
|
|
|
|
if decoder_chan != nil {
|
|
|
|
if decoder_chan != nil {
|
|
|
|
decoder_chan <- nil
|
|
|
|
decoder_chan <- nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
new_chan := make(chan[]byte, 1000)
|
|
|
|
new_chan := make(chan []byte, 1000)
|
|
|
|
decoders[peer_id] = new_chan
|
|
|
|
decoders[peer_id] = new_chan
|
|
|
|
go handle_peer_decode(audio_data, peer_id, decoders[peer_id], sample_rate)
|
|
|
|
go handle_peer_decode(audio_data, peer_id, decoders[peer_id], sample_rate)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func handle_peer_decode(audio_data chan []int16, peer_id pnyx.PeerID, decode_chan chan[]byte, sample_rate int){
|
|
|
|
func handle_peer_decode(audio_data chan []int16, peer_id pnyx.PeerID, decode_chan chan []byte, sample_rate int) {
|
|
|
|
decoder, err := opus.NewDecoder(sample_rate, 1)
|
|
|
|
decoder, err := opus.NewDecoder(sample_rate, 1)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
panic(err)
|
|
|
@ -54,11 +54,11 @@ func handle_peer_decode(audio_data chan []int16, peer_id pnyx.PeerID, decode_cha
|
|
|
|
missed := 0
|
|
|
|
missed := 0
|
|
|
|
for running {
|
|
|
|
for running {
|
|
|
|
select {
|
|
|
|
select {
|
|
|
|
case <-time.After(20*time.Millisecond):
|
|
|
|
case <-time.After(20 * time.Millisecond):
|
|
|
|
missed += 1
|
|
|
|
missed += 1
|
|
|
|
|
|
|
|
|
|
|
|
if missed > 100 {
|
|
|
|
if missed > 100 {
|
|
|
|
decode_chan <- <- decode_chan
|
|
|
|
decode_chan <- <-decode_chan
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pcm := make([]int16, sample_rate/50)
|
|
|
|
pcm := make([]int16, sample_rate/50)
|
|
|
@ -88,11 +88,11 @@ func mixer(data_chan chan []int16, speaker_chan chan []int16) {
|
|
|
|
var samples []int16 = nil
|
|
|
|
var samples []int16 = nil
|
|
|
|
for true {
|
|
|
|
for true {
|
|
|
|
if samples == nil {
|
|
|
|
if samples == nil {
|
|
|
|
samples = <- data_chan
|
|
|
|
samples = <-data_chan
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
select {
|
|
|
|
select {
|
|
|
|
case new_samples := <- data_chan:
|
|
|
|
case new_samples := <-data_chan:
|
|
|
|
for i, sample := range(new_samples) {
|
|
|
|
for i, sample := range new_samples {
|
|
|
|
samples[i] += sample
|
|
|
|
samples[i] += sample
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case speaker_chan <- samples:
|
|
|
|
case speaker_chan <- samples:
|
|
|
@ -108,13 +108,13 @@ func setup_audio(mic chan []byte, speaker chan []int16) (*malgo.AllocatedContext
|
|
|
|
panic(err)
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
infos, err := ctx.Devices(malgo.Playback)
|
|
|
|
infos, err := ctx.Devices(malgo.Playback)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var playback_device *malgo.DeviceInfo = nil
|
|
|
|
var playback_device *malgo.DeviceInfo = nil
|
|
|
|
for _, info := range infos {
|
|
|
|
for _, info := range infos {
|
|
|
|
if info.IsDefault != 0 {
|
|
|
|
if info.IsDefault != 0 {
|
|
|
|
full, err := ctx.DeviceInfo(malgo.Playback, info.ID, malgo.Shared)
|
|
|
|
full, err := ctx.DeviceInfo(malgo.Playback, info.ID, malgo.Shared)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
@ -122,19 +122,19 @@ func setup_audio(mic chan []byte, speaker chan []int16) (*malgo.AllocatedContext
|
|
|
|
}
|
|
|
|
}
|
|
|
|
playback_device = &full
|
|
|
|
playback_device = &full
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if playback_device == nil {
|
|
|
|
if playback_device == nil {
|
|
|
|
panic("No default playback device")
|
|
|
|
panic("No default playback device")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
infos, err = ctx.Devices(malgo.Capture)
|
|
|
|
infos, err = ctx.Devices(malgo.Capture)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var capture_device *malgo.DeviceInfo = nil
|
|
|
|
var capture_device *malgo.DeviceInfo = nil
|
|
|
|
for _, info := range infos {
|
|
|
|
for _, info := range infos {
|
|
|
|
if info.IsDefault != 0 {
|
|
|
|
if info.IsDefault != 0 {
|
|
|
|
full, err := ctx.DeviceInfo(malgo.Capture, info.ID, malgo.Shared)
|
|
|
|
full, err := ctx.DeviceInfo(malgo.Capture, info.ID, malgo.Shared)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
@ -142,33 +142,33 @@ func setup_audio(mic chan []byte, speaker chan []int16) (*malgo.AllocatedContext
|
|
|
|
}
|
|
|
|
}
|
|
|
|
capture_device = &full
|
|
|
|
capture_device = &full
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if capture_device == nil {
|
|
|
|
if capture_device == nil {
|
|
|
|
panic("No default capture device")
|
|
|
|
panic("No default capture device")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
inDeviceConfig := malgo.DefaultDeviceConfig(malgo.Capture)
|
|
|
|
inDeviceConfig := malgo.DefaultDeviceConfig(malgo.Capture)
|
|
|
|
inDeviceConfig.Capture.Format = malgo.FormatS16
|
|
|
|
inDeviceConfig.Capture.Format = malgo.FormatS16
|
|
|
|
inDeviceConfig.Capture.Channels = 1
|
|
|
|
inDeviceConfig.Capture.Channels = 1
|
|
|
|
inDeviceConfig.Capture.DeviceID = capture_device.ID.Pointer()
|
|
|
|
inDeviceConfig.Capture.DeviceID = capture_device.ID.Pointer()
|
|
|
|
inDeviceConfig.SampleRate = 48000
|
|
|
|
inDeviceConfig.SampleRate = 48000
|
|
|
|
inDeviceConfig.PeriodSizeInFrames = 960
|
|
|
|
inDeviceConfig.PeriodSizeInFrames = 960
|
|
|
|
inDeviceConfig.Alsa.NoMMap = 1
|
|
|
|
inDeviceConfig.Alsa.NoMMap = 1
|
|
|
|
inDeviceConfig.Capture.ShareMode = malgo.Shared
|
|
|
|
inDeviceConfig.Capture.ShareMode = malgo.Shared
|
|
|
|
|
|
|
|
|
|
|
|
outDeviceConfig := malgo.DefaultDeviceConfig(malgo.Playback)
|
|
|
|
outDeviceConfig := malgo.DefaultDeviceConfig(malgo.Playback)
|
|
|
|
outDeviceConfig.Playback.Format = malgo.FormatS16
|
|
|
|
outDeviceConfig.Playback.Format = malgo.FormatS16
|
|
|
|
outDeviceConfig.Playback.Channels = 1
|
|
|
|
outDeviceConfig.Playback.Channels = 1
|
|
|
|
outDeviceConfig.Playback.DeviceID = playback_device.ID.Pointer()
|
|
|
|
outDeviceConfig.Playback.DeviceID = playback_device.ID.Pointer()
|
|
|
|
outDeviceConfig.SampleRate = 48000
|
|
|
|
outDeviceConfig.SampleRate = 48000
|
|
|
|
outDeviceConfig.PeriodSizeInFrames = 960
|
|
|
|
outDeviceConfig.PeriodSizeInFrames = 960
|
|
|
|
outDeviceConfig.Alsa.NoMMap = 1
|
|
|
|
outDeviceConfig.Alsa.NoMMap = 1
|
|
|
|
outDeviceConfig.Playback.ShareMode = malgo.Shared
|
|
|
|
outDeviceConfig.Playback.ShareMode = malgo.Shared
|
|
|
|
|
|
|
|
|
|
|
|
onSendFrames := func(output_samples []byte, input_samples []byte, framecount uint32) {
|
|
|
|
onSendFrames := func(output_samples []byte, input_samples []byte, framecount uint32) {
|
|
|
|
select {
|
|
|
|
select {
|
|
|
|
case pcm := <- speaker:
|
|
|
|
case pcm := <-speaker:
|
|
|
|
for i := 0; i < sample_rate/50; i++ {
|
|
|
|
for i := 0; i < sample_rate/50; i++ {
|
|
|
|
binary.LittleEndian.PutUint16(output_samples[i*2:], uint16(pcm[i]))
|
|
|
|
binary.LittleEndian.PutUint16(output_samples[i*2:], uint16(pcm[i]))
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -176,9 +176,9 @@ func setup_audio(mic chan []byte, speaker chan []int16) (*malgo.AllocatedContext
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
playbackCallbacks := malgo.DeviceCallbacks{
|
|
|
|
playbackCallbacks := malgo.DeviceCallbacks{
|
|
|
|
Data: onSendFrames,
|
|
|
|
Data: onSendFrames,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
outDevice, err := malgo.InitDevice(ctx.Context, outDeviceConfig, playbackCallbacks)
|
|
|
|
outDevice, err := malgo.InitDevice(ctx.Context, outDeviceConfig, playbackCallbacks)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
@ -190,7 +190,7 @@ func setup_audio(mic chan []byte, speaker chan []int16) (*malgo.AllocatedContext
|
|
|
|
panic(err)
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onRecvFrames := func(output_samples []byte, input_samples []byte, framecount uint32) {
|
|
|
|
onRecvFrames := func(output_samples []byte, input_samples []byte, framecount uint32) {
|
|
|
|
if encoder != nil {
|
|
|
|
if encoder != nil {
|
|
|
|
pcm := make([]int16, len(input_samples)/2)
|
|
|
|
pcm := make([]int16, len(input_samples)/2)
|
|
|
|
for i := 0; i < len(input_samples)/2; i++ {
|
|
|
|
for i := 0; i < len(input_samples)/2; i++ {
|
|
|
@ -207,21 +207,21 @@ func setup_audio(mic chan []byte, speaker chan []int16) (*malgo.AllocatedContext
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
captureCallbacks := malgo.DeviceCallbacks{
|
|
|
|
captureCallbacks := malgo.DeviceCallbacks{
|
|
|
|
Data: onRecvFrames,
|
|
|
|
Data: onRecvFrames,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
inDevice, err := malgo.InitDevice(ctx.Context, inDeviceConfig, captureCallbacks)
|
|
|
|
inDevice, err := malgo.InitDevice(ctx.Context, inDeviceConfig, captureCallbacks)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
err = inDevice.Start()
|
|
|
|
err = inDevice.Start()
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ctx, outDevice, inDevice
|
|
|
|
return ctx, outDevice, inDevice
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -257,7 +257,7 @@ func get_private_key(path string, generate bool) ed25519.PrivateKey {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
key_pem := pem.EncodeToMemory(&pem.Block{
|
|
|
|
key_pem := pem.EncodeToMemory(&pem.Block{
|
|
|
|
Type: "PRIVATE KEY",
|
|
|
|
Type: "PRIVATE KEY",
|
|
|
|
Bytes: key_pkcs8,
|
|
|
|
Bytes: key_pkcs8,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
@ -271,21 +271,21 @@ func get_private_key(path string, generate bool) ed25519.PrivateKey {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func print_decoration(window, title, channels, body ncurses.Window, top_left string) {
|
|
|
|
func print_decoration(window, title, channels, body ncurses.Window, top_left string) {
|
|
|
|
max_y := ncurses.GetMaxY(*ncurses.CurScr)
|
|
|
|
max_y := ncurses.GetMaxY(*ncurses.CurScr)
|
|
|
|
max_x := ncurses.GetMaxX(*ncurses.CurScr)
|
|
|
|
max_x := ncurses.GetMaxX(*ncurses.CurScr)
|
|
|
|
|
|
|
|
|
|
|
|
ncurses.WResize(window, max_y, max_x)
|
|
|
|
ncurses.WResize(window, max_y, max_x)
|
|
|
|
ncurses.WResize(title, 1, max_x/4-2)
|
|
|
|
ncurses.WResize(title, 1, max_x/4-2)
|
|
|
|
ncurses.MvWin(title, 1, 1)
|
|
|
|
ncurses.MvWin(title, 1, 1)
|
|
|
|
ncurses.WResize(channels, max_y - 4, (max_x / 4) - 2)
|
|
|
|
ncurses.WResize(channels, max_y-4, (max_x/4)-1)
|
|
|
|
ncurses.MvWin(channels, 3, 1)
|
|
|
|
ncurses.MvWin(channels, 3, 1)
|
|
|
|
ncurses.WResize(body, max_y - 2, max_x - (max_x / 4) - 2)
|
|
|
|
ncurses.WResize(body, max_y-2, max_x-(max_x/4)-2)
|
|
|
|
ncurses.MvWin(body, 1, (max_x / 4) + 1)
|
|
|
|
ncurses.MvWin(body, 1, (max_x/4)+1)
|
|
|
|
|
|
|
|
|
|
|
|
ncurses.MvWAddStr(title, 0, 0, top_left)
|
|
|
|
ncurses.MvWAddStr(title, 0, 0, top_left)
|
|
|
|
|
|
|
|
|
|
|
|
for i := 1; i < max_x - 1; i++ {
|
|
|
|
for i := 1; i < max_x-1; i++ {
|
|
|
|
ncurses.MvWAddStr(window, 0, i, "═")
|
|
|
|
ncurses.MvWAddStr(window, 0, i, "═")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -306,7 +306,7 @@ func print_decoration(window, title, channels, body ncurses.Window, top_left str
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for i := 1; i < max_x-1; i++ {
|
|
|
|
for i := 1; i < max_x-1; i++ {
|
|
|
|
ncurses.MvWAddStr(window, max_y - 1, i, "═")
|
|
|
|
ncurses.MvWAddStr(window, max_y-1, i, "═")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ncurses.MvWAddStr(window, 0, 0, "╔")
|
|
|
|
ncurses.MvWAddStr(window, 0, 0, "╔")
|
|
|
@ -324,12 +324,231 @@ func print_decoration(window, title, channels, body ncurses.Window, top_left str
|
|
|
|
ncurses.WRefresh(body)
|
|
|
|
ncurses.WRefresh(body)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type ChannelState struct {
|
|
|
|
|
|
|
|
ID pnyx.ChannelID
|
|
|
|
|
|
|
|
Name string
|
|
|
|
|
|
|
|
Members []pnyx.PeerID
|
|
|
|
|
|
|
|
Modes []pnyx.ModeID
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type PeerState struct {
|
|
|
|
|
|
|
|
Name string
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type UIState struct {
|
|
|
|
|
|
|
|
SelectedBody int
|
|
|
|
|
|
|
|
SelectedPeer int
|
|
|
|
|
|
|
|
SelectedChannel int
|
|
|
|
|
|
|
|
SelectedArea int
|
|
|
|
|
|
|
|
Channels []ChannelState
|
|
|
|
|
|
|
|
Peers map[pnyx.PeerID]PeerState
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
|
|
AREA_CHANNELS = 0
|
|
|
|
|
|
|
|
AREA_BODY = 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BODY_CHANNEL = 0
|
|
|
|
|
|
|
|
BODY_DETAIL = 1
|
|
|
|
|
|
|
|
BODY_QUIT = 2
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func handle_back(state *UIState) {
|
|
|
|
|
|
|
|
if state.SelectedArea == AREA_BODY {
|
|
|
|
|
|
|
|
if state.SelectedBody == BODY_QUIT {
|
|
|
|
|
|
|
|
state.SelectedBody = BODY_CHANNEL
|
|
|
|
|
|
|
|
} else if state.SelectedBody == BODY_DETAIL {
|
|
|
|
|
|
|
|
state.SelectedBody = BODY_CHANNEL
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
state.SelectedArea = AREA_CHANNELS
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func handle_up(state *UIState) {
|
|
|
|
|
|
|
|
if state.SelectedArea == AREA_CHANNELS {
|
|
|
|
|
|
|
|
if state.SelectedPeer > 0 {
|
|
|
|
|
|
|
|
state.SelectedPeer -= 1
|
|
|
|
|
|
|
|
} else if state.SelectedChannel > 0 {
|
|
|
|
|
|
|
|
state.SelectedChannel -= 1
|
|
|
|
|
|
|
|
state.SelectedPeer = len(state.Channels[state.SelectedChannel].Members)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func handle_down(state *UIState) {
|
|
|
|
|
|
|
|
if state.SelectedArea == AREA_CHANNELS {
|
|
|
|
|
|
|
|
if state.SelectedPeer < len(state.Channels[state.SelectedChannel].Members) {
|
|
|
|
|
|
|
|
state.SelectedPeer += 1
|
|
|
|
|
|
|
|
} else if state.SelectedChannel < len(state.Channels) - 1 {
|
|
|
|
|
|
|
|
state.SelectedChannel += 1
|
|
|
|
|
|
|
|
state.SelectedPeer = 0
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func handle_select(state *UIState) {
|
|
|
|
|
|
|
|
if state.SelectedArea == AREA_CHANNELS {
|
|
|
|
|
|
|
|
state.SelectedArea = AREA_BODY
|
|
|
|
|
|
|
|
state.SelectedBody = BODY_CHANNEL
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func handle_edit(state *UIState) {
|
|
|
|
|
|
|
|
if state.SelectedArea == AREA_CHANNELS {
|
|
|
|
|
|
|
|
state.SelectedArea = AREA_BODY
|
|
|
|
|
|
|
|
state.SelectedBody = BODY_DETAIL
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func handle_quit(state *UIState) {
|
|
|
|
|
|
|
|
state.SelectedArea = AREA_BODY
|
|
|
|
|
|
|
|
state.SelectedBody = BODY_QUIT
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func handle_input(state *UIState, char []byte) {
|
|
|
|
|
|
|
|
switch char[0] {
|
|
|
|
|
|
|
|
case 'q':
|
|
|
|
|
|
|
|
handle_quit(state)
|
|
|
|
|
|
|
|
case 'e':
|
|
|
|
|
|
|
|
handle_edit(state)
|
|
|
|
|
|
|
|
case '\r':
|
|
|
|
|
|
|
|
handle_select(state)
|
|
|
|
|
|
|
|
case 0x1B:
|
|
|
|
|
|
|
|
if len(char) == 1 {
|
|
|
|
|
|
|
|
handle_back(state)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
switch char[1] {
|
|
|
|
|
|
|
|
case '[':
|
|
|
|
|
|
|
|
if len(char) == 2 {
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
switch char[2] {
|
|
|
|
|
|
|
|
case 'A': // Up
|
|
|
|
|
|
|
|
handle_up(state)
|
|
|
|
|
|
|
|
case 'B': // Down
|
|
|
|
|
|
|
|
handle_down(state)
|
|
|
|
|
|
|
|
case 'C': // Right
|
|
|
|
|
|
|
|
handle_select(state)
|
|
|
|
|
|
|
|
case 'D': // Left
|
|
|
|
|
|
|
|
handle_back(state)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func draw_channels(state *UIState, channels ncurses.Window) {
|
|
|
|
|
|
|
|
channel_highlight_color := 2
|
|
|
|
|
|
|
|
if state.SelectedArea == AREA_CHANNELS {
|
|
|
|
|
|
|
|
channel_highlight_color = 1
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ncurses.WErase(channels)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ncurses.WMove(channels, 0, 0)
|
|
|
|
|
|
|
|
for i, channel_state := range(state.Channels) {
|
|
|
|
|
|
|
|
if i == state.SelectedChannel && state.SelectedPeer == 0 {
|
|
|
|
|
|
|
|
ncurses.WAttrOn(channels, ncurses.COLOR_PAIR(channel_highlight_color))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ncurses.WAddStr(channels, channel_state.Name)
|
|
|
|
|
|
|
|
ncurses.WAddCh(channels, '\n')
|
|
|
|
|
|
|
|
for j, peer_id := range(channel_state.Members) {
|
|
|
|
|
|
|
|
ncurses.WAddStr(channels, " ↳")
|
|
|
|
|
|
|
|
peer_state, found := state.Peers[peer_id]
|
|
|
|
|
|
|
|
if state.SelectedChannel == i && state.SelectedPeer == (j+1) {
|
|
|
|
|
|
|
|
ncurses.WAttrOn(channels, ncurses.COLOR_PAIR(channel_highlight_color))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if found {
|
|
|
|
|
|
|
|
ncurses.WAddStr(channels, peer_state.Name)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
ncurses.WAddStr(channels, fmt.Sprintf("%s", peer_id))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if state.SelectedChannel == i && state.SelectedPeer == (j+1) {
|
|
|
|
|
|
|
|
ncurses.WAttrOff(channels, ncurses.COLOR_PAIR(channel_highlight_color))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ncurses.WAddCh(channels, '\n')
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if i == state.SelectedChannel && state.SelectedPeer == 0 {
|
|
|
|
|
|
|
|
ncurses.WAttrOff(channels, ncurses.COLOR_PAIR(channel_highlight_color))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ncurses.WRefresh(channels)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func draw_body_channel(state *UIState, body ncurses.Window) {
|
|
|
|
|
|
|
|
ncurses.WErase(body)
|
|
|
|
|
|
|
|
ncurses.MvWAddStr(body, 0, 0, "channel")
|
|
|
|
|
|
|
|
ncurses.WRefresh(body)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func draw_body_detail(state *UIState, body ncurses.Window) {
|
|
|
|
|
|
|
|
ncurses.WErase(body)
|
|
|
|
|
|
|
|
ncurses.MvWAddStr(body, 0, 0, "detail")
|
|
|
|
|
|
|
|
ncurses.WRefresh(body)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func draw_body_quit(state *UIState, body ncurses.Window) {
|
|
|
|
|
|
|
|
ncurses.WErase(body)
|
|
|
|
|
|
|
|
ncurses.MvWAddStr(body, 0, 0, "quit")
|
|
|
|
|
|
|
|
ncurses.WRefresh(body)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func draw_gui(state *UIState, channels, body ncurses.Window) {
|
|
|
|
|
|
|
|
draw_channels(state, channels)
|
|
|
|
|
|
|
|
switch state.SelectedBody {
|
|
|
|
|
|
|
|
case BODY_CHANNEL:
|
|
|
|
|
|
|
|
draw_body_channel(state, body)
|
|
|
|
|
|
|
|
case BODY_DETAIL:
|
|
|
|
|
|
|
|
draw_body_detail(state, body)
|
|
|
|
|
|
|
|
case BODY_QUIT:
|
|
|
|
|
|
|
|
draw_body_quit(state, body)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func main_loop(client *pnyx.Client, audio_data chan []int16, window ncurses.Window, packet_chan chan pnyx.Payload, user_chan chan []byte, sigwinch_channel chan os.Signal) {
|
|
|
|
func main_loop(client *pnyx.Client, audio_data chan []int16, window ncurses.Window, packet_chan chan pnyx.Payload, user_chan chan []byte, sigwinch_channel chan os.Signal) {
|
|
|
|
channels := ncurses.NewWin(0, 0, 0, 0)
|
|
|
|
channels := ncurses.NewWin(0, 0, 0, 0)
|
|
|
|
body := ncurses.NewWin(0, 0, 0, 0)
|
|
|
|
body := ncurses.NewWin(0, 0, 0, 0)
|
|
|
|
title := ncurses.NewWin(0, 0, 0, 0)
|
|
|
|
title := ncurses.NewWin(0, 0, 0, 0)
|
|
|
|
|
|
|
|
state := &UIState{
|
|
|
|
|
|
|
|
Peers: map[pnyx.PeerID]PeerState{
|
|
|
|
|
|
|
|
[32]byte{}: {
|
|
|
|
|
|
|
|
Name: "Test User 0",
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
[32]byte{0x01}: {
|
|
|
|
|
|
|
|
Name: "Test User 1",
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
[32]byte{0x02}: {
|
|
|
|
|
|
|
|
Name: "Test User 2",
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
Channels: []ChannelState{
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
ID: 0,
|
|
|
|
|
|
|
|
Name: "Channel 0",
|
|
|
|
|
|
|
|
Members: []pnyx.PeerID{[32]byte{}, [32]byte{0x01}},
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
ID: 1,
|
|
|
|
|
|
|
|
Name: "Channel 1",
|
|
|
|
|
|
|
|
Members: []pnyx.PeerID{[32]byte{0x02}},
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ncurses.InitColor(1, 1000, 1000, 1000)
|
|
|
|
|
|
|
|
ncurses.InitColor(2, 300, 300, 300)
|
|
|
|
|
|
|
|
ncurses.InitColor(3, 150, 150, 150)
|
|
|
|
|
|
|
|
ncurses.InitColor(4, 150, 150, 300)
|
|
|
|
|
|
|
|
ncurses.InitPair(1, 1, 2)
|
|
|
|
|
|
|
|
ncurses.InitPair(2, 1, 3)
|
|
|
|
|
|
|
|
ncurses.InitPair(3, 1, 4)
|
|
|
|
|
|
|
|
ncurses.ScrollOk(body, true)
|
|
|
|
|
|
|
|
|
|
|
|
print_decoration(window, title, channels, body, client.Remote())
|
|
|
|
print_decoration(window, title, channels, body, client.Remote())
|
|
|
|
|
|
|
|
draw_gui(state, channels, body)
|
|
|
|
|
|
|
|
|
|
|
|
for client.Active() {
|
|
|
|
for client.Active() {
|
|
|
|
select {
|
|
|
|
select {
|
|
|
@ -338,6 +557,7 @@ func main_loop(client *pnyx.Client, audio_data chan []int16, window ncurses.Wind
|
|
|
|
ncurses.WRefresh(window)
|
|
|
|
ncurses.WRefresh(window)
|
|
|
|
ncurses.WClear(window)
|
|
|
|
ncurses.WClear(window)
|
|
|
|
print_decoration(window, title, channels, body, client.Remote())
|
|
|
|
print_decoration(window, title, channels, body, client.Remote())
|
|
|
|
|
|
|
|
draw_gui(state, channels, body)
|
|
|
|
case packet := <-packet_chan:
|
|
|
|
case packet := <-packet_chan:
|
|
|
|
switch packet := packet.(type) {
|
|
|
|
switch packet := packet.(type) {
|
|
|
|
case pnyx.ChannelCommandPacket:
|
|
|
|
case pnyx.ChannelCommandPacket:
|
|
|
@ -366,7 +586,7 @@ func main_loop(client *pnyx.Client, audio_data chan []int16, window ncurses.Wind
|
|
|
|
decode_chan, exists := decoders[packet.Peer]
|
|
|
|
decode_chan, exists := decoders[packet.Peer]
|
|
|
|
if exists == false {
|
|
|
|
if exists == false {
|
|
|
|
if sample_rate != 0 {
|
|
|
|
if sample_rate != 0 {
|
|
|
|
decode_chan = make(chan[]byte, 1000)
|
|
|
|
decode_chan = make(chan []byte, 1000)
|
|
|
|
decoders[packet.Peer] = decode_chan
|
|
|
|
decoders[packet.Peer] = decode_chan
|
|
|
|
go handle_peer_decode(audio_data, packet.Peer, decoders[packet.Peer], sample_rate)
|
|
|
|
go handle_peer_decode(audio_data, packet.Peer, decoders[packet.Peer], sample_rate)
|
|
|
|
decode_chan <- packet.Data
|
|
|
|
decode_chan <- packet.Data
|
|
|
@ -379,15 +599,15 @@ func main_loop(client *pnyx.Client, audio_data chan []int16, window ncurses.Wind
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case char := <-user_chan:
|
|
|
|
case char := <-user_chan:
|
|
|
|
ncurses.WAddStr(body, string(char))
|
|
|
|
handle_input(state, char)
|
|
|
|
ncurses.WRefresh(body)
|
|
|
|
draw_gui(state, channels, body)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func process_mic(client *pnyx.Client, mic chan []byte) {
|
|
|
|
func process_mic(client *pnyx.Client, mic chan []byte) {
|
|
|
|
for true {
|
|
|
|
for true {
|
|
|
|
data := <- mic
|
|
|
|
data := <-mic
|
|
|
|
err := client.Send(pnyx.NewDataPacket(pnyx.ChannelID(0), pnyx.MODE_AUDIO, data))
|
|
|
|
err := client.Send(pnyx.NewDataPacket(pnyx.ChannelID(0), pnyx.MODE_AUDIO, data))
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
panic(err)
|
|
|
@ -415,7 +635,6 @@ func main() {
|
|
|
|
defer inDevice.Uninit()
|
|
|
|
defer inDevice.Uninit()
|
|
|
|
defer inDevice.Stop()
|
|
|
|
defer inDevice.Stop()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
user_chan := make(chan []byte, 1024)
|
|
|
|
user_chan := make(chan []byte, 1024)
|
|
|
|
packet_chan := make(chan pnyx.Payload, 1024)
|
|
|
|
packet_chan := make(chan pnyx.Payload, 1024)
|
|
|
|
|
|
|
|
|
|
|
@ -431,7 +650,7 @@ func main() {
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
join_packet := pnyx.NewChannelCommandPacket(uuid.New(), pnyx.ChannelID(0), pnyx.MODE_CHANNEL, pnyx.CHANNEL_COMMAND_JOIN, nil)
|
|
|
|
join_packet := pnyx.NewChannelCommandPacket(uuid.New(), pnyx.ChannelID(0), pnyx.MODE_CHANNEL, pnyx.CHANNEL_COMMAND_JOIN, nil)
|
|
|
|
err = client.Send(join_packet)
|
|
|
|
err = client.Send(join_packet)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
@ -446,11 +665,11 @@ func main() {
|
|
|
|
|
|
|
|
|
|
|
|
go process_mic(client, mic)
|
|
|
|
go process_mic(client, mic)
|
|
|
|
|
|
|
|
|
|
|
|
locale := ncurses.SetLocale(0, "")
|
|
|
|
ncurses.SetLocale(0, "")
|
|
|
|
fmt.Printf("locale: %s\n", locale)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
user_chan, stdin_active := ncurses.UTF8Listener(100, os.Stdin)
|
|
|
|
user_chan, stdin_active := ncurses.UTF8Listener(100, os.Stdin)
|
|
|
|
window := ncurses.InitScr()
|
|
|
|
window := ncurses.InitScr()
|
|
|
|
|
|
|
|
ncurses.CursSet(0)
|
|
|
|
ret := ncurses.StartColor()
|
|
|
|
ret := ncurses.StartColor()
|
|
|
|
if ret != 0 {
|
|
|
|
if ret != 0 {
|
|
|
|
panic(ret)
|
|
|
|
panic(ret)
|
|
|
|