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,223 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/config"
|
||||
"git.stormux.org/storm/cthulhu/internal/input"
|
||||
"git.stormux.org/storm/cthulhu/internal/preferences"
|
||||
)
|
||||
|
||||
func (worker *preferencesWorker) response(message string, accepted bool) preferences.Response {
|
||||
settings := worker.settings
|
||||
if worker.editingActive {
|
||||
settings = worker.editing
|
||||
}
|
||||
store, err := config.NewStore(settings.Path)
|
||||
if err == nil {
|
||||
_, err = store.Load()
|
||||
}
|
||||
return preferences.Response{Values: preferences.FromSettings(settings), CanSave: err == nil,
|
||||
Message: message, Accepted: accepted}
|
||||
}
|
||||
|
||||
func (worker *preferencesWorker) apply(ctx context.Context, request preferences.Request) preferences.Response {
|
||||
base := worker.settings
|
||||
if worker.editingActive {
|
||||
base = worker.editing
|
||||
}
|
||||
next, err := request.Values.Merge(base)
|
||||
if err != nil {
|
||||
return worker.response("Settings were not applied: "+err.Error(), false)
|
||||
}
|
||||
return worker.applyNext(ctx, next, request.Save, request.Close)
|
||||
}
|
||||
|
||||
func (worker *preferencesWorker) applyNext(ctx context.Context, next config.Settings, save, close bool) preferences.Response {
|
||||
scopeApp := worker.activeApp
|
||||
if worker.editingActive {
|
||||
scopeApp = worker.editApp
|
||||
}
|
||||
if err := config.Validate(next); err != nil {
|
||||
return worker.response("Settings were not applied: "+err.Error(), false)
|
||||
}
|
||||
if save && !worker.response("", false).CanSave {
|
||||
return worker.response("This settings file cannot be saved by the Go preview. Use Apply for this session.", false)
|
||||
}
|
||||
if err := worker.applyRuntime(ctx, next); err != nil {
|
||||
return worker.response(err.Error(), false)
|
||||
}
|
||||
if worker.editingActive {
|
||||
worker.editing = next
|
||||
}
|
||||
if scopeApp != "" {
|
||||
worker.appSettings[scopeApp] = next
|
||||
} else {
|
||||
oldGlobal := worker.globalSettings
|
||||
for application, settings := range worker.appSettings {
|
||||
worker.appSettings[application] = config.RebaseApplicationSettings(oldGlobal, next, settings)
|
||||
}
|
||||
worker.globalSettings = next
|
||||
}
|
||||
message := "Settings applied for this session."
|
||||
if save {
|
||||
store, err := config.NewStore(next.Path)
|
||||
if err == nil {
|
||||
if scopeApp != "" {
|
||||
err = store.SaveForApplication(scopeApp, next)
|
||||
} else {
|
||||
err = store.Save(next)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
message = "Applied for this session, but could not save: " + err.Error()
|
||||
} else {
|
||||
message = "Settings applied and saved."
|
||||
}
|
||||
}
|
||||
response := worker.response(message, true)
|
||||
response.Close = close && save && message == "Settings applied and saved."
|
||||
if worker.editingActive && worker.editApp != worker.activeApp {
|
||||
previousApp := worker.activeApp
|
||||
worker.activeApp = ""
|
||||
if previousApp != "" {
|
||||
worker.switchApplication(ctx, previousApp)
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func (worker *preferencesWorker) applyRuntime(ctx context.Context, next config.Settings) error {
|
||||
keyboardChanged := next.KeyboardLayout != worker.settings.KeyboardLayout || !reflect.DeepEqual(next.CthulhuModifierKeys, worker.settings.CthulhuModifierKeys)
|
||||
bindingsChanged := keyboardChanged || !reflect.DeepEqual(next.KeyBindings, worker.settings.KeyBindings)
|
||||
bindingsCommitted := false
|
||||
if bindingsChanged {
|
||||
if worker.bindings == nil {
|
||||
return errors.New("Key bindings cannot be changed by this input backend.")
|
||||
}
|
||||
if err := worker.apply_keyboard(ctx, next, keyboardChanged); err != nil {
|
||||
return errors.New("Key bindings were not applied: " + err.Error())
|
||||
}
|
||||
defer func() {
|
||||
if !bindingsCommitted {
|
||||
rollbackCtx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
if err := worker.apply_keyboard(rollbackCtx, worker.settings, keyboardChanged); err != nil {
|
||||
worker.logger.Error("restore key bindings after failed preferences apply", "error", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
speechChanged := next.SpeechServerFactory != worker.settings.SpeechServerFactory ||
|
||||
!reflect.DeepEqual(next.UppercaseVoice, worker.settings.UppercaseVoice) ||
|
||||
!reflect.DeepEqual(next.HyperlinkVoice, worker.settings.HyperlinkVoice) ||
|
||||
!reflect.DeepEqual(next.SystemVoice, worker.settings.SystemVoice) ||
|
||||
next.HardwareSpeechDevice != worker.settings.HardwareSpeechDevice ||
|
||||
next.HardwareSpeechBaudRate != worker.settings.HardwareSpeechBaudRate || !reflect.DeepEqual(speechSettingsFromConfig(next), speechSettingsFromConfig(worker.settings)) ||
|
||||
next.UseCustomEchoVoice != worker.settings.UseCustomEchoVoice ||
|
||||
(next.UseCustomEchoVoice && !reflect.DeepEqual(echoSpeechSettings(next), echoSpeechSettings(worker.settings)))
|
||||
if next.EnableSpeech && (speechChanged || !worker.settings.EnableSpeech) {
|
||||
if message := worker.apply_speech_settings(ctx, next); message != "" {
|
||||
return errors.New(message)
|
||||
}
|
||||
}
|
||||
if next.EnableBraille != worker.settings.EnableBraille {
|
||||
if worker.braille == nil {
|
||||
return errors.New("Braille is unavailable in this build.")
|
||||
}
|
||||
if err := worker.braille.Configure(ctx, next.EnableBraille); err != nil {
|
||||
return errors.New("Braille settings were not applied: " + err.Error())
|
||||
}
|
||||
}
|
||||
if next.EnableContractedBraille != worker.settings.EnableContractedBraille ||
|
||||
next.BrailleContractionTable != worker.settings.BrailleContractionTable {
|
||||
if updater, ok := worker.braille.(interface{ SetContraction(bool, string) }); ok {
|
||||
updater.SetContraction(next.EnableContractedBraille, next.BrailleContractionTable)
|
||||
}
|
||||
}
|
||||
bindingsCommitted = true
|
||||
worker.commit_settings(next)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (worker *preferencesWorker) apply_keyboard(ctx context.Context, settings config.Settings, keyboardChanged bool) error {
|
||||
if updater, ok := worker.bindings.(interface {
|
||||
SetKeyboardConfiguration(context.Context, input.KeyboardLayout, []string, []input.BindingOverride) error
|
||||
}); ok {
|
||||
return updater.SetKeyboardConfiguration(ctx, input.KeyboardLayout(settings.KeyboardLayout), settings.CthulhuModifierKeys, settings.KeyBindings)
|
||||
}
|
||||
if keyboardChanged {
|
||||
return errors.New("this input backend cannot change Cthulhu keys or keyboard layout")
|
||||
}
|
||||
return worker.bindings.SetKeyBindings(ctx, settings.KeyBindings)
|
||||
}
|
||||
|
||||
func (worker *preferencesWorker) apply_speech_settings(ctx context.Context, next config.Settings) string {
|
||||
var err error
|
||||
|
||||
if worker.updater == nil {
|
||||
return "Speech is disabled or this backend cannot change settings."
|
||||
}
|
||||
if updater, ok := worker.updater.(interface {
|
||||
UpdateConfiguration(context.Context, config.Settings) error
|
||||
}); ok {
|
||||
err = updater.UpdateConfiguration(ctx, next)
|
||||
} else if updater, ok := worker.updater.(speechSettingsUpdater); ok {
|
||||
if next.UseCustomEchoVoice {
|
||||
return "This speech backend cannot use a custom echo voice."
|
||||
}
|
||||
err = updater.UpdateSettings(ctx, speechSettingsFromConfig(next))
|
||||
} else {
|
||||
return "This backend cannot change speech settings."
|
||||
}
|
||||
if err != nil {
|
||||
return "Settings were not applied: " + err.Error()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Commit follows the existing backend updates and keyboard rollback boundary.
|
||||
// It publishes settings to runtime consumers without changing their ownership.
|
||||
func (worker *preferencesWorker) commit_settings(next config.Settings) {
|
||||
soundChanged := soundConfiguration(next) != soundConfiguration(worker.settings)
|
||||
worker.settings = next
|
||||
if updater, ok := worker.braille.(interface{ SetWordWrap(bool) }); ok {
|
||||
updater.SetWordWrap(next.Braille.WordWrap)
|
||||
}
|
||||
if worker.speechWorker != nil && !next.EnableSpeech {
|
||||
worker.speechWorker.SetEnabled(false)
|
||||
}
|
||||
if worker.history != nil {
|
||||
worker.history.setLimit(next.SpeechHistoryLimit)
|
||||
if worker.refreshHistory != nil {
|
||||
worker.refreshHistory()
|
||||
}
|
||||
}
|
||||
if worker.documentReview != nil {
|
||||
worker.documentReview.SetDocumentBrailleEnabled(next.EnableBraille)
|
||||
}
|
||||
// Configure cancels playback. An application switch or unrelated setting
|
||||
// must leave the startup cue running when its sound policy is unchanged.
|
||||
if worker.sound != nil && soundChanged {
|
||||
worker.sound.Configure(soundConfiguration(next))
|
||||
}
|
||||
if worker.geometry != nil {
|
||||
worker.geometry.SetCaptureSoundGeometry(captureSoundGeometry(next))
|
||||
}
|
||||
if worker.textAttributes != nil {
|
||||
worker.textAttributes.SetCaptureTextAttributes(captureTextAttributes(next))
|
||||
}
|
||||
if worker.live != nil {
|
||||
worker.live.Store(next)
|
||||
}
|
||||
if worker.speechWorker != nil && next.EnableSpeech {
|
||||
worker.speechWorker.SetEnabled(true)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user