106 lines
3.8 KiB
Go
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,
|
|
)
|
|
}
|
|
}
|