UI: Add confirm screen for unload/reload when device in use

Reloading/Unloading destroys the virtual device that an application
is currently using. Add a warning that this may cause strange behaviour.
master 0.11.3
lawl 3 years ago
parent cb2ee053b6
commit a2a45bc88b

@ -24,8 +24,7 @@ func updateNoiseSupressorLoaded(ctx *ntcontext) {
} }
for { for {
ctx.noiseSupressorState = supressorState(ctx) ctx.noiseSupressorState, ctx.virtualDeviceInUse = supressorState(ctx)
if !c.Connected() { if !c.Connected() {
break break
} }
@ -34,16 +33,18 @@ func updateNoiseSupressorLoaded(ctx *ntcontext) {
} }
} }
func supressorState(ctx *ntcontext) int { func supressorState(ctx *ntcontext) (int, bool) {
//perform some checks to see if it looks like the noise supressor is loaded //perform some checks to see if it looks like the noise supressor is loaded
c := ctx.paClient c := ctx.paClient
var inpLoaded, outLoaded, inputInc, outputInc bool var inpLoaded, outLoaded, inputInc, outputInc bool
var virtualDeviceInUse bool = false
if ctx.config.FilterInput { if ctx.config.FilterInput {
if ctx.serverInfo.servertype == servertype_pipewire { if ctx.serverInfo.servertype == servertype_pipewire {
_, ladspasource, err := findModule(c, "module-ladspa-source", "source_name='NoiseTorch Microphone'") module, ladspasource, err := findModule(c, "module-ladspa-source", "source_name='NoiseTorch Microphone'")
if err != nil { if err != nil {
log.Printf("Couldn't fetch module list to check for module-ladspa-source: %v\n", err) log.Printf("Couldn't fetch module list to check for module-ladspa-source: %v\n", err)
} }
virtualDeviceInUse = virtualDeviceInUse || (module.NUsed != 0)
inpLoaded = ladspasource inpLoaded = ladspasource
inputInc = false inputInc = false
} else { } else {
@ -59,11 +60,13 @@ func supressorState(ctx *ntcontext) int {
if err != nil { if err != nil {
log.Printf("Couldn't fetch module list to check for module-loopback: %v\n", err) log.Printf("Couldn't fetch module list to check for module-loopback: %v\n", err)
} }
_, remap, err := findModule(c, "module-remap-source", "master=nui_mic_denoised_out.monitor source_name=nui_mic_remap") module, remap, err := findModule(c, "module-remap-source", "master=nui_mic_denoised_out.monitor source_name=nui_mic_remap")
if err != nil { if err != nil {
log.Printf("Couldn't fetch module list to check for module-remap-source: %v\n", err) log.Printf("Couldn't fetch module list to check for module-remap-source: %v\n", err)
} }
virtualDeviceInUse = virtualDeviceInUse || (module.NUsed != 0)
if nullsink && ladspasink && loopback && remap { if nullsink && ladspasink && loopback && remap {
inpLoaded = true inpLoaded = true
} else if nullsink || ladspasink || loopback || remap { } else if nullsink || ladspasink || loopback || remap {
@ -76,10 +79,11 @@ func supressorState(ctx *ntcontext) int {
if ctx.config.FilterOutput { if ctx.config.FilterOutput {
if ctx.serverInfo.servertype == servertype_pipewire { if ctx.serverInfo.servertype == servertype_pipewire {
_, ladspasink, err := findModule(c, "module-ladspa-sink", "sink_name='NoiseTorch Headphones'") module, ladspasink, err := findModule(c, "module-ladspa-sink", "sink_name='NoiseTorch Headphones'")
if err != nil { if err != nil {
log.Printf("Couldn't fetch module list to check for module-ladspa-sink: %v\n", err) log.Printf("Couldn't fetch module list to check for module-ladspa-sink: %v\n", err)
} }
virtualDeviceInUse = virtualDeviceInUse || (module.NUsed != 0)
outLoaded = ladspasink outLoaded = ladspasink
outputInc = false outputInc = false
} else { } else {
@ -95,10 +99,11 @@ func supressorState(ctx *ntcontext) int {
if err != nil { if err != nil {
log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err) log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err)
} }
_, outin, err := findModule(c, "module-null-sink", "sink_name=nui_out_in_sink") module, outin, err := findModule(c, "module-null-sink", "sink_name=nui_out_in_sink")
if err != nil { if err != nil {
log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err) log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err)
} }
virtualDeviceInUse = virtualDeviceInUse || (module.NUsed != 0)
_, loop2, err := findModule(c, "module-loopback", "source=nui_out_in_sink.monitor") _, loop2, err := findModule(c, "module-loopback", "source=nui_out_in_sink.monitor")
if err != nil { if err != nil {
log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err) log.Printf("Couldn't fetch module list to check for output module-ladspa-sink: %v\n", err)
@ -112,14 +117,14 @@ func supressorState(ctx *ntcontext) int {
} }
if (inpLoaded || !ctx.config.FilterInput) && (outLoaded || !ctx.config.FilterOutput) && !inputInc { if (inpLoaded || !ctx.config.FilterInput) && (outLoaded || !ctx.config.FilterOutput) && !inputInc {
return loaded return loaded, virtualDeviceInUse
} }
if (inpLoaded && ctx.config.FilterInput) || (outLoaded && ctx.config.FilterOutput) || inputInc || outputInc { if (inpLoaded && ctx.config.FilterInput) || (outLoaded && ctx.config.FilterOutput) || inputInc || outputInc {
return inconsistent return inconsistent, virtualDeviceInUse
} }
return unloaded return unloaded, virtualDeviceInUse
} }
func loadSupressor(ctx *ntcontext, inp *device, out *device) error { func loadSupressor(ctx *ntcontext, inp *device, out *device) error {

125
ui.go

@ -34,6 +34,7 @@ type ntcontext struct {
capsMismatch bool capsMismatch bool
views *ViewStack views *ViewStack
serverInfo audioserverinfo serverInfo audioserverinfo
virtualDeviceInUse bool
} }
//TODO pull some of these strucs out of UI, they don't belong here //TODO pull some of these strucs out of UI, they don't belong here
@ -149,13 +150,13 @@ func mainView(ctx *ntcontext, w *nucular.Window) {
if w.CheckboxText("Filter Microphone", &ctx.config.FilterInput) { if w.CheckboxText("Filter Microphone", &ctx.config.FilterInput) {
ctx.sourceListColdWidthIndex++ //recompute the with because of new elements ctx.sourceListColdWidthIndex++ //recompute the with because of new elements
go writeConfig(ctx.config) go writeConfig(ctx.config)
go (func() { ctx.noiseSupressorState = supressorState(ctx) })() go (func() { ctx.noiseSupressorState, _ = supressorState(ctx) })()
} }
if w.CheckboxText("Filter Headphones", &ctx.config.FilterOutput) { if w.CheckboxText("Filter Headphones", &ctx.config.FilterOutput) {
ctx.sourceListColdWidthIndex++ //recompute the with because of new elements ctx.sourceListColdWidthIndex++ //recompute the with because of new elements
go writeConfig(ctx.config) go writeConfig(ctx.config)
go (func() { ctx.noiseSupressorState = supressorState(ctx) })() go (func() { ctx.noiseSupressorState, _ = supressorState(ctx) })()
} }
w.TreePop() w.TreePop()
@ -239,21 +240,19 @@ func mainView(ctx *ntcontext, w *nucular.Window) {
w.Row(25).Dynamic(2) w.Row(25).Dynamic(2)
if ctx.noiseSupressorState != unloaded { if ctx.noiseSupressorState != unloaded {
if w.ButtonText("Unload NoiseTorch") { if w.ButtonText("Unload NoiseTorch") {
ctx.views.Push(loadingView)
ctx.reloadRequired = false ctx.reloadRequired = false
go func() { // don't block the UI thread, just display a working screen so user can't run multiple loads/unloads if ctx.virtualDeviceInUse {
if err := unloadSupressor(ctx); err != nil { confirm := makeConfirmView(ctx,
log.Println(err) "Virtual Device in Use",
} "Some applications may behave weirdly when you remove a device they're currently using",
//wait until PA reports it has actually loaded it, timeout at 10s "Unload",
for i := 0; i < 20; i++ { "Go back",
if supressorState(ctx) != unloaded { func() { uiUnloadNoisetorch(ctx) },
time.Sleep(time.Millisecond * 500) func() {})
} ctx.views.Push(confirm)
} } else {
ctx.views.Pop() go uiUnloadNoisetorch(ctx)
(*ctx.masterWindow).Changed() }
}()
} }
} else { } else {
w.Spacing(1) w.Spacing(1)
@ -271,30 +270,20 @@ func mainView(ctx *ntcontext, w *nucular.Window) {
((ctx.config.FilterOutput && ctx.config.GuiltTripped) || !ctx.config.FilterOutput) && ((ctx.config.FilterOutput && ctx.config.GuiltTripped) || !ctx.config.FilterOutput) &&
ctx.noiseSupressorState != inconsistent { ctx.noiseSupressorState != inconsistent {
if w.ButtonText(txt) { if w.ButtonText(txt) {
ctx.views.Push(loadingView)
ctx.reloadRequired = false ctx.reloadRequired = false
go func() { // don't block the UI thread, just display a working screen so user can't run multiple loads/unloads
if ctx.noiseSupressorState == loaded {
if err := unloadSupressor(ctx); err != nil {
log.Println(err)
}
}
if err := loadSupressor(ctx, &inp, &out); err != nil {
log.Println(err)
}
//wait until PA reports it has actually loaded it, timeout at 10s if ctx.virtualDeviceInUse {
for i := 0; i < 20; i++ { confirm := makeConfirmView(ctx,
if supressorState(ctx) != loaded { "Virtual Device in Use",
time.Sleep(time.Millisecond * 500) "Some applications may behave weirdly when you reload a device they're currently using",
} "Reload",
} "Go back",
ctx.config.LastUsedInput = inp.ID func() { uiReloadNoisetorch(ctx, inp, out) },
ctx.config.LastUsedOutput = out.ID func() {})
go writeConfig(ctx.config) ctx.views.Push(confirm)
ctx.views.Pop() } else {
(*ctx.masterWindow).Changed() go uiReloadNoisetorch(ctx, inp, out)
}() }
} }
} else { } else {
w.Spacing(1) w.Spacing(1)
@ -302,6 +291,45 @@ func mainView(ctx *ntcontext, w *nucular.Window) {
} }
func uiUnloadNoisetorch(ctx *ntcontext) {
ctx.views.Push(loadingView)
if err := unloadSupressor(ctx); err != nil {
log.Println(err)
}
//wait until PA reports it has actually loaded it, timeout at 10s
for i := 0; i < 20; i++ {
if state, _ := supressorState(ctx); state != unloaded {
time.Sleep(time.Millisecond * 500)
}
}
ctx.views.Pop()
(*ctx.masterWindow).Changed()
}
func uiReloadNoisetorch(ctx *ntcontext, inp, out device) {
ctx.views.Push(loadingView)
if ctx.noiseSupressorState == loaded {
if err := unloadSupressor(ctx); err != nil {
log.Println(err)
}
}
if err := loadSupressor(ctx, &inp, &out); err != nil {
log.Println(err)
}
//wait until PA reports it has actually loaded it, timeout at 10s
for i := 0; i < 20; i++ {
if state, _ := supressorState(ctx); state != loaded {
time.Sleep(time.Millisecond * 500)
}
}
ctx.config.LastUsedInput = inp.ID
ctx.config.LastUsedOutput = out.ID
go writeConfig(ctx.config)
ctx.views.Pop()
(*ctx.masterWindow).Changed()
}
func ensureOnlyOneInputSelected(inps *[]device, current *device) { func ensureOnlyOneInputSelected(inps *[]device, current *device) {
if current.checked != true { if current.checked != true {
return return
@ -443,6 +471,27 @@ func makeFatalErrorView(ctx *ntcontext, errorMsg string) ViewFunc {
} }
} }
func makeConfirmView(ctx *ntcontext, title, text, confirmText, denyText string, confirmfunc, denyfunc func()) ViewFunc {
return func(ctx *ntcontext, w *nucular.Window) {
w.Row(15).Dynamic(1)
w.Label(title, "CB")
w.Row(15).Dynamic(1)
w.Label(text, "CB")
w.Row(40).Dynamic(1)
w.Row(25).Dynamic(2)
if w.ButtonText(denyText) {
ctx.views.Pop()
go denyfunc()
return
}
if w.ButtonText(confirmText) {
ctx.views.Pop()
go confirmfunc()
return
}
}
}
func resetUI(ctx *ntcontext) { func resetUI(ctx *ntcontext) {
ctx.views = NewViewStack() ctx.views = NewViewStack()
ctx.views.Push(mainView) ctx.views.Push(mainView)

Loading…
Cancel
Save