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,143 @@
|
||||
// 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 presentation
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/atspi"
|
||||
)
|
||||
|
||||
func TypingEchoText(event atspi.Event, previousText string, options Options) string {
|
||||
text, _ := TypingEcho(event, previousText, options)
|
||||
return text
|
||||
}
|
||||
|
||||
func TypingEcho(event atspi.Event, previousText string, options Options) (string, string) {
|
||||
kind := ""
|
||||
if event.Type == atspi.TextInsertedEvent && utf8.RuneCountInString(event.Payload) == 1 {
|
||||
kind = "key"
|
||||
if options.EnableEchoByCharacter {
|
||||
kind = "character"
|
||||
}
|
||||
}
|
||||
characterEcho := EventTextWithOptions(event, options)
|
||||
if event.Type != atspi.TextInsertedEvent || !options.EnableKeyEcho || utf8.RuneCountInString(event.Payload) != 1 {
|
||||
return characterEcho, kind
|
||||
}
|
||||
context := event.Source.TextBeforeCaret
|
||||
if context == "" {
|
||||
context = event.Source.Text
|
||||
}
|
||||
if (event.Payload == "\n" || event.Payload == "\t") && strings.TrimSpace(context) == "" {
|
||||
context = previousText
|
||||
}
|
||||
if options.EnableEchoBySentence {
|
||||
if sentence := completedSentence(context, event.Payload); sentence != "" {
|
||||
return sentence, "sentence"
|
||||
}
|
||||
}
|
||||
if options.EnableEchoByWord {
|
||||
if word := completedWord(context, event.Payload); word != "" {
|
||||
return word, "word"
|
||||
}
|
||||
}
|
||||
return characterEcho, kind
|
||||
}
|
||||
|
||||
func completedWord(context, inserted string) string {
|
||||
if !isWordDelimiter(inserted) {
|
||||
return ""
|
||||
}
|
||||
context = strings.TrimSuffix(context, inserted)
|
||||
characters := []rune(context)
|
||||
if len(characters) == 0 || isWordDelimiter(string(characters[len(characters)-1])) {
|
||||
return ""
|
||||
}
|
||||
start := len(characters) - 1
|
||||
for start >= 0 && !isWordDelimiter(string(characters[start])) {
|
||||
start--
|
||||
}
|
||||
return string(characters[start+1:])
|
||||
}
|
||||
|
||||
func completedSentence(context, inserted string) string {
|
||||
context = strings.TrimSuffix(context, inserted)
|
||||
characters := []rune(context)
|
||||
if len(characters) == 0 {
|
||||
return ""
|
||||
}
|
||||
if inserted != "\n" && inserted != "\r" {
|
||||
if !unicode.IsSpace(firstRune(inserted)) || !strings.ContainsRune("!.?:;", characters[len(characters)-1]) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
start := 0
|
||||
for index := 1; index < len(characters); index++ {
|
||||
if characters[index] == '\n' || characters[index] == '\r' ||
|
||||
unicode.IsSpace(characters[index]) && strings.ContainsRune("!.?:;", characters[index-1]) {
|
||||
start = index + 1
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(string(characters[start:]))
|
||||
}
|
||||
|
||||
func isWordDelimiter(value string) bool {
|
||||
if utf8.RuneCountInString(value) != 1 {
|
||||
return false
|
||||
}
|
||||
r := firstRune(value)
|
||||
return unicode.IsSpace(r) || strings.ContainsRune("!*+,-./:;<=>?@[\\]^_{|}", r) || r == '\u00a0'
|
||||
}
|
||||
|
||||
func firstRune(value string) rune {
|
||||
r, _ := utf8.DecodeRuneInString(value)
|
||||
return r
|
||||
}
|
||||
|
||||
func shouldEchoInsertion(text string, options Options) bool {
|
||||
if text == "" {
|
||||
return false
|
||||
}
|
||||
if utf8.RuneCountInString(text) != 1 {
|
||||
return true
|
||||
}
|
||||
if !options.EnableKeyEcho {
|
||||
return false
|
||||
}
|
||||
r, _ := utf8.DecodeRuneInString(text)
|
||||
if options.EnableEchoByCharacter && unicode.IsPrint(r) {
|
||||
return true
|
||||
}
|
||||
switch {
|
||||
case unicode.IsLetter(r):
|
||||
return options.EnableAlphabeticKeys
|
||||
case unicode.IsNumber(r):
|
||||
return options.EnableNumericKeys
|
||||
case unicode.IsPunct(r) || unicode.IsSymbol(r):
|
||||
return options.EnablePunctuationKeys
|
||||
case r == ' ':
|
||||
return options.EnableSpace
|
||||
case unicode.Is(unicode.Mn, r) || unicode.Is(unicode.Mc, r) || unicode.Is(unicode.Me, r):
|
||||
return options.EnableDiacriticalKeys
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user