Compare commits

...

9 Commits

Author SHA1 Message Date
lawl 1aa277446b Add PipeWire support to README 3 years ago
lawl a2a45bc88b 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.
3 years ago
lawl cb2ee053b6 Handle non-semver versions in audio client gracefully-ish 3 years ago
lawl d1aa44c3c7 pulseaudio: update module with fixed cookie lookup 3 years ago
lawl 077b4763fe CLI: Cleanup temp files on exit
Fixes #135
3 years ago
Andreas Schneider 809f184e87 c:ladspa: Fix linking with libm
Fixes #140
3 years ago
Andreas Schneider a32f73509f Revert "Temporarily force clang if available"
This reverts commit c2bfe0202f.
3 years ago
lawl c2bfe0202f Temporarily force clang if available
GCC10 seems to refuse to link libm. GCC11 and clang seem to work fine.
3 years ago
lawl a57cc20110 display error screen if module load fails 3 years ago

@ -5,7 +5,7 @@
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Last Release](https://img.shields.io/github/v/release/lawl/NoiseTorch?label=latest&style=flat-square)](https://github.com/lawl/NoiseTorch/releases)
NoiseTorch is an easy to use open source application for Linux with PulseAudio. It creates a virtual microphone that suppresses noise, in any application. Use whichever conferencing or VOIP application you like and simply select the NoiseTorch Virtual Microphone as input to torch the sound of your mechanical keyboard, computer fans, trains and the likes.
NoiseTorch is an easy to use open source application for Linux with PulseAudio or PipeWire. It creates a virtual microphone that suppresses noise, in any application. Use whichever conferencing or VOIP application you like and simply select the NoiseTorch Virtual Microphone as input to torch the sound of your mechanical keyboard, computer fans, trains and the likes.
Don't forget to ~~like, comment and subscribe~~ leave a star ⭐ if this sounds useful to you!

@ -1,3 +1,3 @@
default:
$(CC) -Wall -Werror -O2 -c -fPIC ../ringbuf.c ../rnnoise/*.c module.c
$(CC) -shared -lm -Wl,--version-script=export.txt -o rnnoise_ladspa.so *.o
$(CC) -o rnnoise_ladspa.so *.o -shared -Wl,--version-script=export.txt -lm

@ -39,15 +39,15 @@ func doCLI(opt CLIOpts, config *config, librnnoise string) {
if opt.setcap {
err := makeBinarySetcapped()
if err != nil {
os.Exit(1)
cleanupExit(librnnoise, 1)
}
os.Exit(0)
cleanupExit(librnnoise, 0)
}
paClient, err := pulseaudio.NewClient()
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't create pulseaudio client: %v\n", err)
os.Exit(1)
cleanupExit(librnnoise, 1)
}
defer paClient.Close()
@ -77,7 +77,7 @@ func doCLI(opt CLIOpts, config *config, librnnoise string) {
fmt.Printf("\tDevice Name: %s\n\tDevice ID: %s\n\n", sinks[i].Name, sinks[i].ID)
}
os.Exit(0)
cleanupExit(librnnoise, 0)
}
if opt.threshold > 0 {
@ -93,9 +93,9 @@ func doCLI(opt CLIOpts, config *config, librnnoise string) {
err := unloadSupressor(&ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "Error unloading PulseAudio Module: %+v\n", err)
os.Exit(1)
cleanupExit(librnnoise, 1)
}
os.Exit(0)
cleanupExit(librnnoise, 0)
}
if opt.loadInput {
@ -105,7 +105,7 @@ func doCLI(opt CLIOpts, config *config, librnnoise string) {
defaultSource, err := getDefaultSourceID(paClient)
if err != nil {
fmt.Fprintf(os.Stderr, "No source specified to load and failed to load default source: %+v\n", err)
os.Exit(1)
cleanupExit(librnnoise, 1)
}
opt.sinkName = defaultSource
}
@ -115,13 +115,13 @@ func doCLI(opt CLIOpts, config *config, librnnoise string) {
err := loadSupressor(&ctx, &sources[i], &device{})
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading PulseAudio Module: %+v\n", err)
os.Exit(1)
cleanupExit(librnnoise, 1)
}
os.Exit(0)
cleanupExit(librnnoise, 0)
}
}
fmt.Fprintf(os.Stderr, "PulseAudio source not found: %s\n", opt.sinkName)
os.Exit(1)
cleanupExit(librnnoise, 1)
}
if opt.loadOutput {
@ -131,7 +131,7 @@ func doCLI(opt CLIOpts, config *config, librnnoise string) {
defaultSink, err := getDefaultSinkID(paClient)
if err != nil {
fmt.Fprintf(os.Stderr, "No sink specified to load and failed to load default sink: %+v\n", err)
os.Exit(1)
cleanupExit(librnnoise, 1)
}
opt.sinkName = defaultSink
}
@ -141,13 +141,18 @@ func doCLI(opt CLIOpts, config *config, librnnoise string) {
err := loadSupressor(&ctx, &device{}, &sinks[i])
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading PulseAudio Module: %+v\n", err)
os.Exit(1)
cleanupExit(librnnoise, 1)
}
os.Exit(0)
cleanupExit(librnnoise, 0)
}
}
fmt.Fprintf(os.Stderr, "PulseAudio sink not found: %s\n", opt.sinkName)
os.Exit(1)
cleanupExit(librnnoise, 1)
}
}
func cleanupExit(librnnoise string, exitCode int) {
removeLib(librnnoise)
os.Exit(exitCode)
}

@ -7,7 +7,7 @@ require (
github.com/BurntSushi/toml v0.3.1
github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046
github.com/aarzilli/nucular v0.0.0-20200615134801-81910c722bba
github.com/lawl/pulseaudio v0.0.0-20200802093727-ab0735955fd0
github.com/lawl/pulseaudio v0.0.0-20210604102109-cb2596d6a8ef
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
)

@ -21,6 +21,8 @@ github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad h1:eMxs9EL0Pv
github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/lawl/pulseaudio v0.0.0-20200802093727-ab0735955fd0 h1:JrvOwrr1teFiqsp0EQxgEPJsm0pet+YLTL+HdYmnMx0=
github.com/lawl/pulseaudio v0.0.0-20200802093727-ab0735955fd0/go.mod h1:9h36x4KH7r2V8DOCKoPMt87IXZ++X90y8D5nnuwq290=
github.com/lawl/pulseaudio v0.0.0-20210604102109-cb2596d6a8ef h1:mpCJg3O6C+B8mh5xoO147NG3Z70GBcFNgqgz2DH/rLQ=
github.com/lawl/pulseaudio v0.0.0-20210604102109-cb2596d6a8ef/go.mod h1:9h36x4KH7r2V8DOCKoPMt87IXZ++X90y8D5nnuwq290=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

@ -247,19 +247,20 @@ func serverInfo(paClient *pulseaudio.Client) (audioserverinfo, error) {
res := versionRegex.FindStringSubmatch(versionString)
if len(res) != 4 {
return audioserverinfo{}, fmt.Errorf("couldn't parse server version, regexp didn't match.")
log.Printf("couldn't parse server version, regexp didn't match version: %s\n", versionString)
return audioserverinfo{servertype: servertype}, nil
}
major, err = strconv.Atoi(res[1])
if err != nil {
return audioserverinfo{}, err
return audioserverinfo{servertype: servertype}, err
}
minor, err = strconv.Atoi(res[2])
if err != nil {
return audioserverinfo{}, err
return audioserverinfo{servertype: servertype}, err
}
patch, err = strconv.Atoi(res[3])
if err != nil {
return audioserverinfo{}, err
return audioserverinfo{servertype: servertype}, err
}
if isPipewire && major <= 0 && minor <= 3 && patch < 28 {
log.Printf("pipewire version %d.%d.%d too old.\n", major, minor, patch)

@ -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 {
@ -176,10 +181,20 @@ func loadSupressor(ctx *ntcontext, inp *device, out *device) error {
return nil
}
func loadModule(ctx *ntcontext, module, args string) (uint32, error) {
idx, err := ctx.paClient.LoadModule(module, args)
//14 = module initialisation failed
if paErr, ok := err.(*pulseaudio.Error); ok && paErr.Code == 14 {
resetUI(ctx)
ctx.views.Push(makeErrorView(ctx, fmt.Sprintf("Could not load module '%s'. This is likely a problem with your system or distribution.", module)))
}
return idx, err
}
func loadPipeWireInput(ctx *ntcontext, inp *device) error {
c := ctx.paClient
log.Printf("Loading supressor for pipewire\n")
idx, err := c.LoadModule("module-ladspa-source",
idx, err := loadModule(ctx, "module-ladspa-source",
fmt.Sprintf("source_name='NoiseTorch Microphone' master=%s "+
"rate=48000 channels=1 "+
"label=noisetorch plugin=%s control=%d", inp.ID, ctx.librnnoise, ctx.config.Threshold))
@ -192,9 +207,8 @@ func loadPipeWireInput(ctx *ntcontext, inp *device) error {
}
func loadPipeWireOutput(ctx *ntcontext, out *device) error {
c := ctx.paClient
log.Printf("Loading supressor for pipewire\n")
idx, err := c.LoadModule("module-ladspa-sink",
idx, err := loadModule(ctx, "module-ladspa-sink",
fmt.Sprintf("sink_name='NoiseTorch Headphones' master=%s "+
"rate=48000 channels=1 "+
"label=noisetorch plugin=%s control=%d", out.ID, ctx.librnnoise, ctx.config.Threshold))
@ -207,15 +221,14 @@ func loadPipeWireOutput(ctx *ntcontext, out *device) error {
}
func loadPulseInput(ctx *ntcontext, inp *device) error {
c := ctx.paClient
log.Printf("Loading supressor for pulse\n")
idx, err := c.LoadModule("module-null-sink", "sink_name=nui_mic_denoised_out rate=48000")
idx, err := loadModule(ctx, "module-null-sink", "sink_name=nui_mic_denoised_out rate=48000")
if err != nil {
return err
}
log.Printf("Loaded null sink as idx: %d\n", idx)
idx, err = c.LoadModule("module-ladspa-sink",
idx, err = loadModule(ctx, "module-ladspa-sink",
fmt.Sprintf("sink_name=nui_mic_raw_in sink_master=nui_mic_denoised_out "+
"label=noisetorch plugin=%s control=%d", ctx.librnnoise, ctx.config.Threshold))
if err != nil {
@ -224,14 +237,14 @@ func loadPulseInput(ctx *ntcontext, inp *device) error {
log.Printf("Loaded ladspa sink as idx: %d\n", idx)
if inp.dynamicLatency {
idx, err = c.LoadModule("module-loopback",
idx, err = loadModule(ctx, "module-loopback",
fmt.Sprintf("source=%s sink=nui_mic_raw_in channels=1 latency_msec=1 source_dont_move=true sink_dont_move=true", inp.ID))
if err != nil {
return err
}
log.Printf("Loaded loopback as idx: %d\n", idx)
} else {
idx, err = c.LoadModule("module-loopback",
idx, err = loadModule(ctx, "module-loopback",
fmt.Sprintf("source=%s sink=nui_mic_raw_in channels=1 latency_msec=50 source_dont_move=true sink_dont_move=true adjust_time=1", inp.ID))
if err != nil {
return err
@ -239,7 +252,7 @@ func loadPulseInput(ctx *ntcontext, inp *device) error {
log.Printf("Loaded fixed latency loopback as idx: %d\n", idx)
}
idx, err = c.LoadModule("module-remap-source", `master=nui_mic_denoised_out.monitor `+
idx, err = loadModule(ctx, "module-remap-source", `master=nui_mic_denoised_out.monitor `+
`source_name=nui_mic_remap source_properties="device.description='NoiseTorch Microphone'"`)
if err != nil {
return err
@ -249,31 +262,30 @@ func loadPulseInput(ctx *ntcontext, inp *device) error {
}
func loadPulseOutput(ctx *ntcontext, out *device) error {
c := ctx.paClient
_, err := c.LoadModule("module-null-sink", `sink_name=nui_out_out_sink`)
_, err := loadModule(ctx, "module-null-sink", `sink_name=nui_out_out_sink`)
if err != nil {
return err
}
_, err = c.LoadModule("module-null-sink", `sink_name=nui_out_in_sink sink_properties="device.description='NoiseTorch Headphones'"`)
_, err = loadModule(ctx, "module-null-sink", `sink_name=nui_out_in_sink sink_properties="device.description='NoiseTorch Headphones'"`)
if err != nil {
return err
}
_, err = c.LoadModule("module-ladspa-sink", fmt.Sprintf(`sink_name=nui_out_ladspa sink_master=nui_out_out_sink `+
_, err = loadModule(ctx, "module-ladspa-sink", fmt.Sprintf(`sink_name=nui_out_ladspa sink_master=nui_out_out_sink `+
`label=noisetorch channels=1 plugin=%s control=%d rate=%d`,
ctx.librnnoise, ctx.config.Threshold, 48000))
if err != nil {
return err
}
_, err = c.LoadModule("module-loopback",
_, err = loadModule(ctx, "module-loopback",
fmt.Sprintf("source=nui_out_out_sink.monitor sink=%s channels=2 latency_msec=50 source_dont_move=true sink_dont_move=true", out.ID))
if err != nil {
return err
}
_, err = c.LoadModule("module-loopback",
_, err = loadModule(ctx, "module-loopback",
fmt.Sprintf("source=nui_out_in_sink.monitor sink=nui_out_ladspa channels=1 latency_msec=50 source_dont_move=true sink_dont_move=true"))
if err != nil {
return err

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)

@ -383,7 +383,7 @@ func cookiePath() (string, error) {
return p, nil
}
p = filepath.Join(os.Getenv("HOME"), "/.pulse_cookie")
p = filepath.Join(os.Getenv("HOME"), "/.pulse-cookie")
if exists(p) {
return p, nil
}

@ -71,7 +71,7 @@ github.com/golang/freetype/truetype
# github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad
github.com/hashicorp/golang-lru
github.com/hashicorp/golang-lru/simplelru
# github.com/lawl/pulseaudio v0.0.0-20200802093727-ab0735955fd0
# github.com/lawl/pulseaudio v0.0.0-20210604102109-cb2596d6a8ef
## explicit
github.com/lawl/pulseaudio
# github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635

Loading…
Cancel
Save