Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,89 @@
|
||||
// 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)
|
||||
}
|
||||
Reference in new issue
Block a user