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,124 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package preferences
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/speech"
|
||||
)
|
||||
|
||||
const initialMessageLimit = 512 * 1024
|
||||
|
||||
type catalogPart struct {
|
||||
Catalog speech.Catalog
|
||||
CatalogPending bool
|
||||
}
|
||||
|
||||
// EncodeInitial keeps each protocol line bounded without removing voices or
|
||||
// languages. The helper assembles the catalog before constructing GTK controls.
|
||||
func EncodeInitial(encoder *json.Encoder, response Response) error {
|
||||
encoded, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(encoded)+1 <= initialMessageLimit {
|
||||
return encoder.Encode(json.RawMessage(encoded))
|
||||
}
|
||||
catalog := response.Catalog
|
||||
response.Catalog = nil
|
||||
response.CatalogPending = true
|
||||
encoded, err = json.Marshal(response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(encoded)+1 > initialMessageLimit {
|
||||
return fmt.Errorf("preferences settings exceed %d-byte protocol limit", initialMessageLimit)
|
||||
}
|
||||
if err := encoder.Encode(json.RawMessage(encoded)); err != nil {
|
||||
return err
|
||||
}
|
||||
modules := make([]string, 0, len(catalog))
|
||||
for module := range catalog {
|
||||
modules = append(modules, module)
|
||||
}
|
||||
sort.Strings(modules)
|
||||
for _, module := range modules {
|
||||
voices := catalog[module]
|
||||
for {
|
||||
count := min(len(voices), 128)
|
||||
for {
|
||||
encoded, err = json.Marshal(catalogPart{
|
||||
Catalog: speech.Catalog{module: voices[:count]}, CatalogPending: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(encoded)+1 <= initialMessageLimit {
|
||||
break
|
||||
}
|
||||
if count <= 1 {
|
||||
return fmt.Errorf("preferences voice in module %q exceeds %d-byte protocol limit", module, initialMessageLimit)
|
||||
}
|
||||
count /= 2
|
||||
}
|
||||
if err := encoder.Encode(json.RawMessage(encoded)); err != nil {
|
||||
return err
|
||||
}
|
||||
voices = voices[count:]
|
||||
if len(voices) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return encoder.Encode(catalogPart{})
|
||||
}
|
||||
|
||||
// read_responses owns catalog assembly for the lifetime of the helper pipe.
|
||||
// Only complete responses reach GTK, including after application selection.
|
||||
func read_responses(ctx context.Context, input io.Reader, updates chan<- Response, errorsIn chan<- error) {
|
||||
scanner := bufio.NewScanner(input)
|
||||
scanner.Buffer(make([]byte, 4096), 1024*1024)
|
||||
var initial *Response
|
||||
for scanner.Scan() {
|
||||
var response Response
|
||||
if err := json.Unmarshal(scanner.Bytes(), &response); err != nil {
|
||||
errorsIn <- err
|
||||
return
|
||||
}
|
||||
if initial != nil {
|
||||
if initial.Catalog == nil {
|
||||
initial.Catalog = make(speech.Catalog)
|
||||
}
|
||||
for module, voices := range response.Catalog {
|
||||
initial.Catalog[module] = append(initial.Catalog[module], voices...)
|
||||
}
|
||||
if response.CatalogPending {
|
||||
continue
|
||||
}
|
||||
response = *initial
|
||||
response.CatalogPending = false
|
||||
initial = nil
|
||||
} else if response.CatalogPending {
|
||||
initial = &response
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case updates <- response:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
err := scanner.Err()
|
||||
if err == nil && initial != nil {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
errorsIn <- err
|
||||
}
|
||||
Reference in new issue
Block a user