Files

141 lines
5.1 KiB
Go

// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package config
import (
"bytes"
"errors"
"fmt"
"os"
"reflect"
"strings"
"github.com/pelletier/go-toml/v2"
)
// Application_setting admits only settings which can be applied and restored
// without changing devices, routing, storage, or other process-wide services.
func Application_setting(path string) bool {
switch path {
case "presentation.speech.enabled", "presentation.speech.rate", "presentation.speech.pitch", "presentation.speech.volume", "presentation.speech.module", "presentation.speech.language", "presentation.speech.voice",
"presentation.sound.enabled", "presentation.sound.volume", "presentation.progress.enabled",
"presentation.time_and_date.time_format", "presentation.time_and_date.date_format",
"keyboard.character_echo", "keyboard.word_echo", "keyboard.echo_on_capslock":
return true
case "reading.automatic", "reading.cursor_echo", "reading.mode", "reading.punctuation_level",
"reading.raise_pitch_for_capitals", "reading.beep_for_capitals", "reading.say_cap_for_capitals",
"reading.indentation", "reading.attributes", "reading.attribute_format", "reading.spellcheck",
"reading.spellcheck_read_word", "reading.spellcheck_say_misspelled", "reading.spellcheck_sound",
"reading.emoticons", "reading.repeated_symbols", "reading.repeated_symbols_min":
return true
}
return false
}
// Open_application_document overlays a sparse, current-schema profile on the
// application's inherited settings. Missing files inherit everything.
func Open_application_document(path string, base Settings) (*Document, error) {
doc := &Document{Settings: base, path: path, applicationBase: &base, applicationFields: make(map[string]bool)}
data, err := read_document(path)
if errors.Is(err, os.ErrNotExist) {
return doc, nil
}
if err != nil {
return nil, err
}
var fields map[string]any
if err := toml.Unmarshal(data, &fields); err != nil {
return nil, err
}
var collect func(map[string]any, string) error
collect = func(fields map[string]any, prefix string) error {
for name, value := range fields {
path := prefix + name
if table, ok := value.(map[string]any); ok {
if err := collect(table, path+"."); err != nil {
return err
}
continue
}
if !Application_setting(path) {
return fmt.Errorf("setting %s is not supported in application profiles", path)
}
doc.applicationFields[path] = true
}
return nil
}
if err := collect(fields, ""); err != nil {
return nil, err
}
if err := toml.NewDecoder(bytes.NewReader(data)).DisallowUnknownFields().Decode(&doc.Settings); err != nil {
return nil, err
}
if err := doc.Settings.Validate(); err != nil {
return nil, err
}
doc.original, doc.existed = data, true
return doc, nil
}
// Apply_application preserves inherited session state for every omitted field.
func (d *Document) Apply_application(base Settings) Settings {
for path := range d.applicationFields {
setting_value(reflect.ValueOf(&base).Elem(), path).Set(setting_value(reflect.ValueOf(d.Settings), path))
}
return base
}
func (d *Document) save_application(settings Settings) error {
fields := make(map[string]any)
selectionChanged := false
for _, path := range []string{"presentation.speech.module", "presentation.speech.language", "presentation.speech.voice"} {
selectionChanged = selectionChanged || d.applicationFields[path] || !reflect.DeepEqual(setting_value(reflect.ValueOf(settings), path).Interface(), setting_value(reflect.ValueOf(*d.applicationBase), path).Interface())
}
var visit func(reflect.Value, reflect.Value, string)
visit = func(value, base reflect.Value, prefix string) {
for index := range value.NumField() {
path := prefix + value.Type().Field(index).Tag.Get("toml")
child, inherited := value.Field(index), base.Field(index)
if child.Kind() == reflect.Struct {
visit(child, inherited, path+".")
continue
}
speechSelection := selectionChanged && (path == "presentation.speech.module" || path == "presentation.speech.language" || path == "presentation.speech.voice")
if !Application_setting(path) || (!speechSelection && !d.applicationFields[path] && reflect.DeepEqual(child.Interface(), inherited.Interface())) {
continue
}
parts := strings.Split(path, ".")
table := fields
for _, part := range parts[:len(parts)-1] {
if table[part] == nil {
table[part] = make(map[string]any)
}
table = table[part].(map[string]any)
}
table[parts[len(parts)-1]] = child.Interface()
}
}
visit(reflect.ValueOf(settings), reflect.ValueOf(*d.applicationBase), "")
data, err := toml.Marshal(fields)
if err != nil {
return err
}
return save_data(d.path, data)
}
// Apply_application_settings copies the supported live settings only. Inherited
// next-launch choices must never replace the running backend or layout.
func Apply_application_settings(base Settings, source Settings) Settings {
for _, definition := range Definitions() {
if Application_setting(definition.Path) {
if err := definition.Set(&base, definition.Value(source)); err != nil {
panic(err)
}
}
}
base.Presentation.Progress = source.Presentation.Progress
return base
}