69 lines
2.3 KiB
Go
69 lines
2.3 KiB
Go
// Fenrir console screen reader.
|
|
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify it
|
|
// under the terms of the GNU General Public License as published by the Free
|
|
// Software Foundation, either version 3 of the License, or (at your option)
|
|
// any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
// more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along with
|
|
// this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
|
|
|
|
package sound
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/jfreymuth/pulse"
|
|
"github.com/jfreymuth/pulse/proto"
|
|
)
|
|
|
|
func open_pulse(s *Service, _ string) error {
|
|
options := []pulse.ClientOption{pulse.ClientApplicationName("Fenrir sounds"), pulse.ClientTimeout(500 * time.Millisecond)}
|
|
// Try the invoking user's session, then the bundled PipeWire helper's
|
|
// socket and the system-wide service, unless PULSE_SERVER selects a server.
|
|
options = append(options, pulse.ClientServerString(Server(os.Getenv("PULSE_SERVER"), os.Getenv("SUDO_UID"))))
|
|
client, err := pulse.NewClient(options...)
|
|
if err != nil {
|
|
return fmt.Errorf("connect sound server: %w", err)
|
|
}
|
|
stream, err := client.NewPlayback(pulse.Float32Reader(s.read_samples), pulse.PlaybackStereo, pulse.PlaybackSampleRate(sampleRate), pulse.PlaybackLatency(0.02), pulse.PlaybackMediaName("Fenrir sound cues"))
|
|
if err != nil {
|
|
client.Close()
|
|
return err
|
|
}
|
|
s.closeBackend = func() { stream.Close(); client.Close() }
|
|
// Drain even after the final reader callback reports EndOfData. The
|
|
// library's Drain helper skips idle streams, whose buffered tail may
|
|
// still be audible on the server.
|
|
s.drainBackend = func(_ context.Context) error {
|
|
return client.RawRequest(&proto.DrainPlaybackStream{StreamIndex: stream.StreamIndex()}, nil)
|
|
}
|
|
stream.Start()
|
|
s.workers.Go(func() {
|
|
tick := time.NewTicker(time.Second)
|
|
defer tick.Stop()
|
|
for {
|
|
select {
|
|
case <-s.done:
|
|
return
|
|
case <-tick.C:
|
|
if stream.Closed() {
|
|
s.errors <- fmt.Errorf("sound server connection closed")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
})
|
|
return nil
|
|
}
|