Files
cthulhu/internal/app/runtime_workers.go

97 lines
4.0 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"
"git.stormux.org/storm/cthulhu/internal/config"
"git.stormux.org/storm/cthulhu/internal/input"
)
// runtimeWorkers owns the event-loop companion workers. Only the event loop
// consumes reviewRequests; command and clipboard workers send to bounded queues.
// Each done channel closes after its worker exits. commandError is read after
// commandDone closes, so no additional synchronization is needed.
type runtimeWorkers struct {
reviewRequests <-chan input.Command
attributeRequests <-chan struct{}
commandDone <-chan struct{}
clipboardDone <-chan struct{}
inputDone <-chan struct{}
copyDone <-chan struct{}
autoClipboardDone <-chan struct{}
commandError error
internalUIDone <-chan struct{}
internalUI *internalUI
}
// startRuntimeWorkers preserves startup order: clipboard, command, input, copy.
// The caller cancels ctx and joins these workers before stopping output services.
func startRuntimeWorkers(ctx context.Context, command commandRuntime, inputListener InputListener, settings config.Settings, copyChanges <-chan struct{}, braille *brailleWorker) *runtimeWorkers {
speech := gatedSpeech{speech: command.speechOutput, gate: command.focusGate}
learn := &learnModePresenter{tentacles: command.tentacles, ctx: ctx, logger: command.logger, speech: speech, braille: braille, live: command.live}
if owner, ok := command.commands.(interface{ BrowseNavigationEnabled() bool }); ok {
learn.browse = owner
}
command.learn = learn
clipboardRequests := make(chan struct{}, 1)
clipboardDone := runClipboardWorker(ctx, clipboardRequests, speech, readClipboardMessage)
command.clipboardRequests = clipboardRequests
reviewRequests := make(chan input.Command, 1)
command.reviewRequests = reviewRequests
attributeRequests := make(chan struct{}, 1)
command.attributeRequests = attributeRequests
commandDone := make(chan struct{})
workers := &runtimeWorkers{
reviewRequests: reviewRequests,
attributeRequests: attributeRequests,
commandDone: commandDone,
clipboardDone: clipboardDone,
}
if keys, ok := command.commands.(internalUIKeys); ok {
ui := &internalUI{notices: make(chan internalUINotice, 4), keys: keys, actions: command.actionSource, interest: command.objectReview, speech: speech, braille: braille, gate: command.focusGate, events: make(chan input.CommandEvent, 32), requests: make(chan internalUIRequest, 1), done: make(chan struct{})}
command.internalUI = ui
if command.tentacles != nil {
command.tentacles.forms = ui
command.tentacles.focusedItem = focusedTentacleItem(command.actionSource, ui)
}
workers.internalUI = ui
workers.internalUIDone = ui.done
go ui.run(ctx)
}
go func() {
defer close(commandDone)
workers.commandError = command.run(ctx)
}()
inputDone := make(chan struct{})
if inputListener != nil {
go func() {
defer close(inputDone)
processInputEvents(ctx, command.logger, speech, inputListener, inputEchoSettingsFromConfig(settings), command.live, command.commands, command.ocr)
}()
} else {
close(inputDone)
}
workers.inputDone = inputDone
workers.copyDone = runCopyFeedback(ctx, copyChanges, inputListener, speech, command.live, command.ocr)
workers.autoClipboardDone = run_automatic_clipboard(ctx, command.logger, command.live, command.focusGate, speech)
return workers
}