// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package app // read_all_locked starts ordinary paced speech from the current virtual // offset. Progress moves only OCR's cursor and braille; native focus is intact. func (session *ocrSession) read_all_locked(timestamp uint32) { if session.cursor == nil || session.readAll == nil { session.status_locked("OCR Say All is unavailable") return } lines := session.cursor.LinesFrom() if len(lines) == 0 { session.status_locked("OCR text boundary") return } job := session.job session.selectionAnchor = -1 ctx := session.readAll.BeginReadAll(job.ctx, timestamp) session.readAllContext = ctx session.readAllDone = make(chan error, 1) parts := make([]readAllPart, 0, len(lines)) for _, line := range lines { offset := line.Offset parts = append(parts, readAllPart{Text: line.Text, Began: func() { session.mu.Lock() defer session.mu.Unlock() if ctx.Err() != nil || !session.current_locked(job) || session.cursor == nil { return } position, ok := session.cursor.Set(offset) if !ok { return } frame := session.frame_locked(position) if session.display != nil { session.display.EnqueueFrame(frame) } session.logger.Debug("OCR Say All progress", "command", "read-all", "generation", job.generation, "focus_path", session.focus.Path, "source_path", session.source.Object.Path, "offset", position.Offset, "selection_start", -1, "selection_end", -1, "speech_text", line.Text, "braille_text", frame.Text, "braille_cursor", frame.Cursor) }}) } session.speech.Enqueue(speechRequest{ReadAll: parts, Role: "text", Cause: "read-all", context: ctx, result: session.readAllDone}) }