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 {
ctx.noiseSupressorState = supressorState(ctx)
ctx.noiseSupressorState, ctx.virtualDeviceInUse = supressorState(ctx)
if !c.Connected() {
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
c := ctx.paClient
var inpLoaded, outLoaded, inputInc, outputInc bool
var virtualDeviceInUse bool = false
if ctx.config.FilterInput {
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 {
log.Printf("Couldn't fetch module list to check for module-ladspa-source: %v\n", err)
}
virtualDeviceInUse = virtualDeviceInUse || (module.NUsed != 0)
inpLoaded = ladspasource
inputInc = false
} else {
@ -59,11 +60,13 @@ func supressorState(ctx *ntcontext) int {
if err != nil {
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 {
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 {
inpLoaded = true
} else if nullsink || ladspasink || loopback || remap {
@ -76,10 +79,11 @@ func supressorState(ctx *ntcontext) int {
if ctx.config.FilterOutput {
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 {
log.Printf("Couldn't fetch module list to check for module-ladspa-sink: %v\n", err)
}
virtualDeviceInUse = virtualDeviceInUse || (module.NUsed != 0)
outLoaded = ladspasink
outputInc = false
} else {
@ -95,10 +99,11 @@ func supressorState(ctx *ntcontext) int {
if err != nil {
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 {
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")
if err != nil {
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 {
return loaded
return loaded, virtualDeviceInUse
}
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 {

125
ui.go

@ -34,6 +34,7 @@ type ntcontext struct {
capsMismatch bool
views *ViewStack
serverInfo audioserverinfo
virtualDeviceInUse bool
}
//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) {
ctx.sourceListColdWidthIndex++ //recompute the with because of new elements
go writeConfig(ctx.config)
go (func() { ctx.noiseSupressorState = supressorState(ctx) })()
go (func() { ctx.noiseSupressorState, _ = supressorState(ctx) })()
}
if w.CheckboxText("Filter Headphones", &ctx.config.FilterOutput) {
ctx.sourceListColdWidthIndex++ //recompute the with because of new elements
go writeConfig(ctx.config)
go (func() { ctx.noiseSupressorState = supressorState(ctx) })()
go (func() { ctx.noiseSupressorState, _ = supressorState(ctx) })()
}
w.TreePop()
@ -239,21 +240,19 @@ func mainView(ctx *ntcontext, w *nucular.Window) {
w.Row(25).Dynamic(2)
if ctx.noiseSupressorState != unloaded {
if w.ButtonText("Unload NoiseTorch") {
ctx.views.Push(loadingView)
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 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 supressorState(ctx) != unloaded {
time.Sleep(time.Millisecond * 500)
}
}
ctx.views.Pop()
(*ctx.masterWindow).Changed()
}()
if ctx.virtualDeviceInUse {
confirm := makeConfirmView(ctx,
"Virtual Device in Use",
"Some applications may behave weirdly when you remove a device they're currently using",
"Unload",
"Go back",
func() { uiUnloadNoisetorch(ctx) },
func() {})
ctx.views.Push(confirm)
} else {
go uiUnloadNoisetorch(ctx)
}
}
} else {
w.Spacing(1)
@ -271,30 +270,20 @@ func mainView(ctx *ntcontext, w *nucular.Window) {
((ctx.config.FilterOutput && ctx.config.GuiltTripped) || !ctx.config.FilterOutput) &&
ctx.noiseSupressorState != inconsistent {
if w.ButtonText(txt) {
ctx.views.Push(loadingView)
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
for i := 0; i < 20; i++ {
if supressorState(ctx) != 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()
}()
if ctx.virtualDeviceInUse {
confirm := makeConfirmView(ctx,
"Virtual Device in Use",
"Some applications may behave weirdly when you reload a device they're currently using",
"Reload",
"Go back",
func() { uiReloadNoisetorch(ctx, inp, out) },
func() {})
ctx.views.Push(confirm)
} else {
go uiReloadNoisetorch(ctx, inp, out)
}
}
} else {
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) {
if current.checked != true {
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) {
ctx.views = NewViewStack()
ctx.views.Push(mainView)

Loading…
Cancel
Save