Cthulhu is no longer a fork. Now it is a screen reader written in Golang.

This commit is contained in:
Storm Dragon committed 2026-10-14 00:16:34 -04:00
1 parent a998fe67de
commit 069fc674dc
708 files changed
+112482 -532

No files matched your search

+153
View File
@@ -0,0 +1,153 @@
// 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 (
"slices"
"strings"
"unicode/utf8"
"git.stormux.org/storm/cthulhu/internal/remote"
"git.stormux.org/storm/cthulhu/internal/wine"
"github.com/godbus/dbus/v5"
)
type remoteSpeech struct {
speech speechEnqueuer
braille interface {
EnqueueMessage(string) bool
}
wine *wineEventPresenter
wineReview *wine.ReviewRegistry
showPreferences func()
quit func()
runtimeSnapshot func() string
live *liveSettings
preferences *preferencesWorker
}
func (presenter remoteSpeech) RegisterProvider(sender string, path dbus.ObjectPath, version uint32) bool {
return presenter.wineReview != nil && presenter.wineReview.RegisterProvider(sender, path, version)
}
func (presenter remoteSpeech) PresentFocus(sender string, notice wine.ReviewFocusNotice) bool {
return presenter.wineReview != nil && presenter.wineReview.PresentFocus(sender, notice)
}
func (presenter remoteSpeech) BindWineReviewConnection(connection *dbus.Conn) {
if presenter.wineReview != nil {
presenter.wineReview.BindWineReviewConnection(connection)
}
}
func (presenter remoteSpeech) ShowPreferences() bool {
if presenter.showPreferences == nil {
return false
}
presenter.showPreferences()
return true
}
func (presenter remoteSpeech) Quit() bool {
if presenter.quit == nil {
return false
}
presenter.quit()
return true
}
func (presenter remoteSpeech) RuntimeStateSnapshot() string {
if presenter.runtimeSnapshot == nil {
return ""
}
return presenter.runtimeSnapshot()
}
func (presenter remoteSpeech) PresentAccessibleEvent(event remote.AccessibleEvent) bool {
return presenter.wine != nil && presenter.wine.present_event(event, presenter.presentWineEditor)
}
func (presenter remoteSpeech) PresentMessage(message string) bool {
return presenter.presentMessage(message, "remote-present-message")
}
func (presenter remoteSpeech) presentMessage(message, cause string) bool {
return presenter.presentRequest(speechRequest{
Text: message, Role: "system", Cause: cause, Append: true,
})
}
func (presenter remoteSpeech) presentWineEditor(event remote.AccessibleEvent) bool {
// These action kinds are part of the helper editor protocol. Keep
// character identity for navigation and Backspace as well as typing.
kinds := strings.Fields(event.States)
inserted := slices.Contains(kinds, "kind=8")
character := inserted || utf8.RuneCountInString(event.Name) == 1
custom := false
if character && presenter.live != nil && (inserted || slices.Contains(kinds, "kind=7")) {
custom = customEcho(presenter.live.Load(), "character")
}
return presenter.presentRequest(speechRequest{
Text: event.Name, Role: "system", Cause: "wine-accessible-event", Append: true,
Character: character, CustomEcho: custom,
})
}
func (presenter remoteSpeech) presentRequest(request speechRequest) bool {
if request.Text == "" {
return false
}
accepted := false
if presenter.speech != nil {
presenter.speech.Enqueue(request)
accepted = true
}
if presenter.braille != nil && presenter.braille.EnqueueMessage(request.Text) {
accepted = true
}
return accepted
}
func (presenter remoteSpeech) SpeakText(text string, interrupt bool) bool {
if presenter.speech == nil || text == "" {
return false
}
if interrupt {
presenter.speech.Interrupt()
}
presenter.speech.Enqueue(speechRequest{
Text: text,
Role: "system",
Cause: "remote-speak-text",
Append: true,
})
return true
}
func (presenter remoteSpeech) BrailleMessage(text string) bool {
if presenter.braille == nil || text == "" {
return false
}
return presenter.braille.EnqueueMessage(text)
}
func (presenter remoteSpeech) CancelSpeech() bool {
if presenter.speech == nil {
return false
}
presenter.speech.Interrupt()
return true
}