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,163 @@
|
||||
// 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 (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/input"
|
||||
)
|
||||
|
||||
type commandGrabController interface {
|
||||
SetCommandGrabs(bool)
|
||||
}
|
||||
|
||||
type focusGate struct {
|
||||
tentacleOutput *tentacleOutput
|
||||
|
||||
ocr *ocrSession
|
||||
mutex sync.Mutex
|
||||
speech speechEnqueuer
|
||||
suspended atomic.Bool
|
||||
secure atomic.Bool
|
||||
lockChanges chan struct{}
|
||||
commands commandGrabController
|
||||
internalUI interface{ InternalUIActive() bool }
|
||||
braille *brailleWorker
|
||||
}
|
||||
|
||||
func newFocusGate(commands commandListener) *focusGate {
|
||||
gate := &focusGate{lockChanges: make(chan struct{}, 1)}
|
||||
if controller, ok := commands.(commandGrabController); ok {
|
||||
gate.commands = controller
|
||||
}
|
||||
gate.internalUI, _ = commands.(interface{ InternalUIActive() bool })
|
||||
return gate
|
||||
}
|
||||
|
||||
func (gate *focusGate) SetSuspended(suspended bool) bool {
|
||||
if gate == nil {
|
||||
return false
|
||||
}
|
||||
if suspended {
|
||||
gate.ocr.Retire()
|
||||
}
|
||||
gate.mutex.Lock()
|
||||
defer gate.mutex.Unlock()
|
||||
if !gate.suspended.CompareAndSwap(!suspended, suspended) {
|
||||
return false
|
||||
}
|
||||
gate.retireTentacleOutput()
|
||||
if suspended {
|
||||
gate.tentacleOutput.Stop()
|
||||
}
|
||||
if suspended && !gate.secure.Load() && gate.speech != nil {
|
||||
gate.speech.Interrupt()
|
||||
}
|
||||
if gate.commands != nil {
|
||||
gate.commands.SetCommandGrabs(!suspended && !gate.secure.Load())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (gate *focusGate) InternalUIActive() bool {
|
||||
return gate != nil && gate.internalUI != nil && gate.internalUI.InternalUIActive()
|
||||
}
|
||||
|
||||
func (gate *focusGate) Suspended() bool {
|
||||
return gate != nil && gate.suspended.Load()
|
||||
}
|
||||
|
||||
func (gate *focusGate) retireTentacleOutput() {
|
||||
if gate != nil {
|
||||
gate.tentacleOutput.Retire()
|
||||
}
|
||||
}
|
||||
|
||||
func (gate *focusGate) dismiss_confirmation() {
|
||||
if gate != nil && gate.braille != nil {
|
||||
gate.braille.DismissConfirmation()
|
||||
}
|
||||
}
|
||||
|
||||
type gatedSpeech struct {
|
||||
speech speechEnqueuer
|
||||
gate *focusGate
|
||||
}
|
||||
|
||||
func (speaker gatedSpeech) Enqueue(request speechRequest) {
|
||||
if speaker.gate != nil {
|
||||
speaker.gate.mutex.Lock()
|
||||
defer speaker.gate.mutex.Unlock()
|
||||
}
|
||||
if speaker.gate.InternalUIActive() && request.Cause != "internal-ui" {
|
||||
return
|
||||
}
|
||||
if speaker.gate.Locked() || speaker.gate.Suspended() && !request.BypassFocusGate {
|
||||
return
|
||||
}
|
||||
speaker.speech.Enqueue(request)
|
||||
}
|
||||
|
||||
func (speaker gatedSpeech) Interrupt() {
|
||||
if speaker.gate.InternalUIActive() {
|
||||
return
|
||||
}
|
||||
speaker.InterruptInternalUI()
|
||||
}
|
||||
|
||||
func (speaker gatedSpeech) InterruptInternalUI() {
|
||||
if speaker.gate != nil {
|
||||
speaker.gate.mutex.Lock()
|
||||
defer speaker.gate.mutex.Unlock()
|
||||
}
|
||||
if !speaker.gate.Locked() && !speaker.gate.Suspended() {
|
||||
speaker.speech.Interrupt()
|
||||
}
|
||||
}
|
||||
|
||||
func (speaker gatedSpeech) InterruptForUser() {
|
||||
if speaker.gate.Locked() {
|
||||
return
|
||||
}
|
||||
speaker.gate.dismiss_confirmation()
|
||||
if interrupt, ok := speaker.speech.(interface{ InterruptForUser() }); ok {
|
||||
interrupt.InterruptForUser()
|
||||
} else {
|
||||
speaker.Interrupt()
|
||||
}
|
||||
}
|
||||
|
||||
func (speaker gatedSpeech) InterruptForInput(event input.Event) bool {
|
||||
if speaker.gate.InternalUIActive() {
|
||||
return false
|
||||
}
|
||||
if speaker.gate.Locked() {
|
||||
return false
|
||||
}
|
||||
if speech, ok := speaker.speech.(interface{ InterruptForInput(input.Event) bool }); ok {
|
||||
interrupted := speech.InterruptForInput(event)
|
||||
if interrupted {
|
||||
speaker.gate.dismiss_confirmation()
|
||||
}
|
||||
return interrupted
|
||||
}
|
||||
speaker.InterruptForUser()
|
||||
return true
|
||||
}
|
||||
Reference in new issue
Block a user