feat: harden engine config, discovery and backpressure stats
- config: validate port/quality/fps/source/stream-index ranges on load and before engine start (flinger.Config.Validate) - discovery: add Announce option to disable multicast (GUI checkbox, CLI --no-announce); absent JSON key keeps the default true - backpressure: count dropped frames in the TCP sender, expose via engine Status and a live counter in the GUI status label - docs: record M3/M4 decisions and X11 coverage via the portal backend
This commit is contained in:
+58
-8
@@ -10,6 +10,7 @@ import (
|
||||
"errors"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/app"
|
||||
@@ -43,14 +44,16 @@ type App struct {
|
||||
lock bool // serialises start/stop against UI actions
|
||||
|
||||
// UI state.
|
||||
startBtn *widget.Button
|
||||
statusLab *widget.Label
|
||||
portEnt *widget.Entry
|
||||
qualitySel *widget.Select
|
||||
fpsSel *widget.Select
|
||||
nameEnt *widget.Entry
|
||||
audioChk *widget.Check
|
||||
srcSel *widget.Select
|
||||
startBtn *widget.Button
|
||||
statusLab *widget.Label
|
||||
portEnt *widget.Entry
|
||||
qualitySel *widget.Select
|
||||
fpsSel *widget.Select
|
||||
nameEnt *widget.Entry
|
||||
audioChk *widget.Check
|
||||
announceChk *widget.Check
|
||||
srcSel *widget.Select
|
||||
statsDone chan struct{}
|
||||
}
|
||||
|
||||
// Run starts the GUI and blocks until the app exits.
|
||||
@@ -120,6 +123,14 @@ func (g *App) buildUI() {
|
||||
g.audioChk = widget.NewCheck("Capture system audio", nil)
|
||||
g.audioChk.SetChecked(g.cfg.Audio)
|
||||
|
||||
// Announce over multicast.
|
||||
g.announceChk = widget.NewCheck("Announce on LAN", nil)
|
||||
announce := true
|
||||
if g.cfg.Announce != nil {
|
||||
announce = *g.cfg.Announce
|
||||
}
|
||||
g.announceChk.SetChecked(announce)
|
||||
|
||||
// Status label.
|
||||
g.statusLab = widget.NewLabel("Stopped")
|
||||
g.statusLab.Importance = widget.MediumImportance
|
||||
@@ -136,6 +147,7 @@ func (g *App) buildUI() {
|
||||
{Text: "Quality", Widget: g.qualitySel},
|
||||
{Text: "Frame rate", Widget: g.fpsSel},
|
||||
{Text: "", Widget: g.audioChk},
|
||||
{Text: "", Widget: g.announceChk},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -168,6 +180,7 @@ func (g *App) start() {
|
||||
quality, _ := atoi(g.qualitySel.Selected)
|
||||
fps, _ := atoi(g.fpsSel.Selected)
|
||||
|
||||
announce := g.announceChk.Checked
|
||||
g.cfg = config.Config{
|
||||
Name: g.nameEnt.Text,
|
||||
Port: port,
|
||||
@@ -176,6 +189,7 @@ func (g *App) start() {
|
||||
Source: g.srcSel.Selected,
|
||||
Audio: g.audioChk.Checked,
|
||||
StreamIndex: g.cfg.StreamIndex,
|
||||
Announce: &announce,
|
||||
}
|
||||
if err := config.Save(g.cfg); err != nil {
|
||||
log.Printf("gui: config save: %v", err)
|
||||
@@ -194,10 +208,46 @@ func (g *App) start() {
|
||||
g.statusLab.SetText("Streaming (port " + itoa(g.cfg.Port) + ")")
|
||||
g.statusLab.Importance = widget.SuccessImportance
|
||||
g.refresh()
|
||||
g.watchStats()
|
||||
}
|
||||
|
||||
// watchStats refreshes the status label with live counters while streaming.
|
||||
func (g *App) watchStats() {
|
||||
done := make(chan struct{})
|
||||
g.statsDone = done
|
||||
go func() {
|
||||
tick := time.NewTicker(2 * time.Second)
|
||||
defer tick.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
if g.eng == nil {
|
||||
return
|
||||
}
|
||||
st := g.eng.Status()
|
||||
g.statusLab.SetText(formatStatus(st))
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// formatStatus renders the live status line.
|
||||
func formatStatus(st flinger.Status) string {
|
||||
base := "Streaming · " + itoa(int(st.Frames)) + " frames"
|
||||
if st.Dropped > 0 {
|
||||
base += " · " + itoa(int(st.Dropped)) + " dropped"
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
// stop halts the engine and returns the UI to the stopped state.
|
||||
func (g *App) stop() {
|
||||
if g.statsDone != nil {
|
||||
close(g.statsDone)
|
||||
g.statsDone = nil
|
||||
}
|
||||
if g.eng != nil {
|
||||
g.eng.Stop()
|
||||
g.eng = nil
|
||||
|
||||
Reference in New Issue
Block a user