Files

90 lines
2.5 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"context"
"errors"
"time"
)
// The zero value preserves normal startup and the saved voice configuration.
// Modes last for this run only; they never alter enableSpeech or profiles.
type speechMode uint8
const (
speechModeSpeech speechMode = iota
speechModeBeeps
speechModeOff
)
func (mode speechMode) announcement() string {
switch mode {
case speechModeBeeps:
return "Speech Mode Beeps"
case speechModeOff:
return "Speech off"
default:
return "Speech Mode Speech"
}
}
func (worker *speechWorker) cycle_speech_mode() speechMode {
worker.mutex.Lock()
defer worker.mutex.Unlock()
worker.mode = (worker.mode + 1) % 3
worker.startupUntil = time.Time{}
worker.interruptLocked()
worker.logger.Debug("speech mode changed", "mode", worker.mode.announcement())
return worker.mode
}
// Capture mode and beep cancellation together so a concurrent mode change or
// key press cannot leave a beep running behind its interruption barrier.
func (worker *speechWorker) deliver_mode(ctx context.Context, request speechRequest) (bool, error) {
worker.mutex.Lock()
mode := worker.mode
if request.ForceSpeech || mode == speechModeSpeech {
worker.mutex.Unlock()
return false, nil
}
if mode == speechModeOff {
worker.mutex.Unlock()
worker.log_mode_delivery(ctx, request, mode)
return true, nil
}
beepContext, cancelBeep := context.WithCancel(ctx)
worker.beepCancel = cancelBeep
worker.mutex.Unlock()
worker.log_mode_delivery(ctx, request, mode)
defer func() {
cancelBeep()
worker.mutex.Lock()
worker.beepCancel = nil
worker.mutex.Unlock()
}()
if worker.beep == nil {
return true, errors.New("speech beep backend unavailable")
}
return true, worker.beep(beepContext)
}
func (worker *speechWorker) log_mode_delivery(ctx context.Context, request speechRequest, mode speechMode) {
// Say All depends on synth completion. In beeps/off, do not run those
// callbacks and race the reading cursor to the end of the document.
text := request.Text
if len(request.ReadAll) != 0 {
text = request.ReadAll[0].Text
}
if worker.history != nil && !request.ExcludeFromHistory && request.Cause != "speech-history" {
worker.history.add(text, request.Role)
}
if request.Role == "password text" {
text = "[REDACTED]"
}
worker.logger.DebugContext(ctx, "speech replaced by mode", "mode", mode.announcement(),
"cause", request.Cause, "role", request.Role, "utterance", text)
}