Files

106 lines
3.8 KiB
Go

// Cthulhu screen reader.
// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
//
// 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 app
import (
"context"
"errors"
"log/slog"
"git.stormux.org/storm/cthulhu/internal/input"
"git.stormux.org/storm/cthulhu/internal/presentation"
"git.stormux.org/storm/cthulhu/internal/review"
)
func flatReviewCommand(command input.Command) (review.FlatUnit, int) {
switch command {
case input.ReviewWindowStart:
return review.WindowStart, 0
case input.ReviewWindowEnd:
return review.WindowEnd, 0
case input.ReviewPreviousLine:
return review.Line, -1
case input.ReviewCurrentLine:
return review.Line, 0
case input.ReviewNextLine:
return review.Line, 1
case input.ReviewPreviousWord:
return review.Word, -1
case input.ReviewCurrentWord, input.ReviewSpellWord, input.ReviewPhoneticWord:
return review.Word, 0
case input.ReviewNextWord:
return review.Word, 1
case input.ReviewPreviousCharacter:
return review.Character, -1
case input.ReviewCurrentCharacter, input.ReviewPhoneticCharacter:
return review.Character, 0
case input.ReviewNextCharacter:
return review.Character, 1
case input.ReviewWordAbove:
return review.WordAbove, 0
case input.ReviewWordBelow:
return review.WordBelow, 0
case input.ReviewLineEnd:
return review.LineEnd, 0
default:
return 0, 0
}
}
func presentFlatReviewResult(logger *slog.Logger, speech speechEnqueuer, result review.Result) {
text := ""
if result.Err != nil {
logger.Warn("flat review failed", "error", result.Err)
switch {
case errors.Is(result.Err, review.ErrLimitedReview):
text = "This window exceeds the current flat review text limit."
case errors.Is(result.Err, review.ErrNoReviewContent):
text = "No visible content."
default:
text = "Could not find current location."
}
} else if !result.Moved && (result.FlatStep != 0 || result.FlatUnit == review.WordAbove || result.FlatUnit == review.WordBelow) {
// Python's sequential review stops silently at window boundaries.
return
} else {
text = presentation.FlatReviewText(result.FlatPosition.Text, result.FlatUnit == review.Character || result.FlatUnit == review.LineEnd)
}
speech.Interrupt()
request := speechRequest{Text: text, Role: "text", Cause: "flat-review", Character: result.Err == nil && (result.FlatUnit == review.Character || result.FlatUnit == review.LineEnd)}
if result.Err == nil && result.FlatUnit == review.Line {
request.Line = &speechLine{Object: result.FlatPosition.Object, Raw: result.FlatPosition.Text}
}
if result.Err == nil {
set_review_spelling(&request, result.FlatPosition.Text, result.FlatSpeech)
}
speech.Enqueue(request)
if logger.Enabled(context.Background(), slog.LevelDebug) {
logger.Debug("queued flat review presentation",
"speech_text", request.Text, "spelling", request.Spelling, "speech_style", result.FlatSpeech,
"unit", result.FlatUnit,
"moved", result.Moved,
"bus", result.FlatPosition.Object.BusName,
"object_path", result.FlatPosition.Object.Path,
"offset", result.FlatPosition.Offset,
"has_offset", result.FlatPosition.HasOffset,
"capture_duration", result.CaptureDuration,
"capture_reason", result.CaptureReason,
)
}
}