After commit c018f5fd29 Fenrir is being switched over to go. If you do not want to be part of the testing process, please switch to master.

This commit is contained in:
Storm Dragon committed 2026-10-27 20:30:55 -04:00
1 parent c018f5fd29
commit 0e50de4c76
1047 files changed
+58364 -63747

No files matched your search

+195
View File
@@ -0,0 +1,195 @@
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"fmt"
"os"
"strings"
"git.stormux.org/storm/fenrir/internal/config"
"git.stormux.org/storm/fenrir/internal/control"
)
// These operations run only after the shared credential and settings gates.
// A save keeps its reply pending until the worker reports the actual disk result.
func (a *application) remote_settings_request(text string, request control.Request) (string, bool) {
verb, parameter, _ := strings.Cut(text, " ")
verb, parameter = strings.ToUpper(verb), strings.TrimSpace(parameter)
if a.speechPending {
return "Settings are busy; try again after the current operation", false
}
if verb == "SET" {
updated, err := remote_settings(a.effective_settings(), text)
if err != nil {
return err.Error(), false
}
if !same_speech_selection(updated.Presentation.Speech, a.settings.live.Presentation.Speech) {
if a.saver.busy || a.ui.active {
return "Settings are busy; try again after the current operation", false
}
a.change_speech(updated.Presentation.Speech, func(err error) {
response := ""
if err != nil {
response = err.Error()
} else {
response = a.apply_remote_settings(resolved_remote_selection(text, updated))
}
remote_reply(request.Reply, response)
})
return "", true
}
}
switch verb {
case "SAVE", "SAVEAS", "RESET":
if a.saver.busy || a.ui.active {
return "Settings are busy; try again after the current operation", false
}
default:
return a.apply_remote_settings(text), false
}
if verb != "SAVEAS" && parameter != "" {
return verb + " does not accept parameters", false
}
if verb == "RESET" {
updated, _, err := a.remote_reset_state()
if err != nil {
return err.Error(), false
}
if !same_speech_selection(updated.Presentation.Speech, a.settings.live.Presentation.Speech) {
a.change_speech(updated.Presentation.Speech, func(err error) {
response := ""
if err != nil {
response = err.Error()
} else {
response = a.reset_remote_settings()
}
remote_reply(request.Reply, response)
})
return "", true
}
return a.reset_remote_settings(), false
}
path := a.options.ConfigPath
if verb == "SAVEAS" {
// A root TTY reader also admits its active console user for ordinary
// commands. That must not grant arbitrary root-owned file writes.
if request.UID != 0 && request.UID != os.Geteuid() {
return "SAVEAS requires the reader owner or root", false
}
path = parameter
}
if path == "" || strings.ContainsAny(path, "\x00\r\n") {
return "A valid settings destination is required", false
}
if !a.saver.save(a.ctx, path, a.settings_to_save()) {
return "Settings are busy; try again after the current operation", false
}
a.saver.revision = a.settings.remoteRevision
a.saver.reply = request.Reply
return "", true
}
func (a *application) settings_save_result(err error) {
if !a.saver.busy {
return
}
if settings_committed(err) {
a.settings.resetBase = a.saver.snapshot
for path, revision := range a.settings.remoteFields {
if revision <= a.saver.revision {
delete(a.settings.remoteFields, path)
}
}
}
if done := a.saver.menuDone; done != nil {
a.saver.busy, a.saver.menuDone = false, nil
done(err)
} else if reply := a.saver.reply; reply != nil {
a.saver.busy, a.saver.reply = false, nil
response := ""
if err != nil {
response = "Could not save settings: " + err.Error()
}
remote_reply(reply, response)
} else {
a.saver.result(err, a.voice)
}
}
// RESET clears temporary quick/remote overrides, as in Python's
// reset_setting_arg_dict. It never reloads a file or installs factory defaults.
func (a *application) remote_reset_state() (config.Settings, config.Settings, error) {
updated, persisted := a.effective_settings(), a.settings.saveDraft
for _, target := range []*config.Settings{&updated, &persisted} {
copy_quick_settings(target, a.settings.resetBase)
if a.quick.selectionChanged {
copy_speech_selection(&target.Presentation.Speech, a.settings.resetBase.Presentation.Speech)
}
copy_remote_settings(target, a.settings.resetBase, a.settings.remoteFields)
if err := target.Validate(); err != nil {
return updated, persisted, err
}
}
if updated.Presentation.Speech.Enabled && !a.settings.speechAvailable {
return updated, persisted, fmt.Errorf("Speech was disabled at startup; enable it in settings and relaunch")
}
return updated, persisted, nil
}
func (a *application) reset_remote_settings() string {
updated, persisted, err := a.remote_reset_state()
if err != nil {
return err.Error()
}
a.settings.saveDraft = persisted
_, speechOverridden := a.settings.remoteFields["speech#enabled"]
a.apply_remote_state(updated, speechOverridden)
clear(a.settings.remoteFields)
a.quick.changed = false
a.quick.selectionChanged = false
return ""
}
// Copy only remotely overridden fields; unrelated session mute/AutoRead,
// next-launch menu choices, reading regions and keyboard routing survive.
func copy_remote_settings(target *config.Settings, source config.Settings, fields map[string]uint64) {
for path := range fields {
switch path {
case "reading#mode", "focus#cursor", "focus#highlight":
target.Reading.Mode = source.Reading.Mode
case "speech#enabled":
target.Presentation.Speech.Enabled = source.Presentation.Speech.Enabled
case "speech#module", "speech#language", "speech#voice":
copy_speech_selection(&target.Presentation.Speech, source.Presentation.Speech)
case "speech#rate":
target.Presentation.Speech.Rate = source.Presentation.Speech.Rate
case "speech#pitch":
target.Presentation.Speech.Pitch = source.Presentation.Speech.Pitch
case "speech#volume":
target.Presentation.Speech.Volume = source.Presentation.Speech.Volume
case "sound#enabled":
target.Presentation.Sound.Enabled = source.Presentation.Sound.Enabled
case "sound#volume":
target.Presentation.Sound.Volume = source.Presentation.Sound.Volume
case "sound#progress_monitoring":
target.Presentation.Progress.Enabled = source.Presentation.Progress.Enabled
case "general#punctuation_level":
target.Reading.PunctuationLevel = source.Reading.PunctuationLevel
case "speech#auto_read_incoming", "reading#automatic":
target.Reading.Automatic = source.Reading.Automatic
case "reading#cursor_echo":
target.Reading.CursorEcho = source.Reading.CursorEcho
case "keyboard#char_echo_mode":
target.Keyboard.CharacterEcho = source.Keyboard.CharacterEcho
target.Keyboard.EchoOnCapsLock = source.Keyboard.EchoOnCapsLock
case "keyboard#word_echo":
target.Keyboard.WordEcho = source.Keyboard.WordEcho
case "keyboard#echo_on_capslock":
target.Keyboard.EchoOnCapsLock = source.Keyboard.EchoOnCapsLock
case "screen#ignore_screen":
target.Screen.IgnoredVTs = append([]int{}, source.Screen.IgnoredVTs...)
}
}
}