// Fenrir console screen reader. // Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later // // 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 . package config import ( "bytes" "fmt" "github.com/pelletier/go-toml/v2" ) // decode_v1 is the explicit migration boundary for the prototype's flat // speech/sound tables. Unknown keys and mixed old/new schemas are rejected. func decode_v1(data []byte) (Settings, error) { defaults := Defaults() type oldSpeech struct { Enabled bool `toml:"enabled"` Rate int `toml:"rate"` Module string `toml:"module"` Socket string `toml:"socket"` } old := struct { Version int `toml:"version"` Review Review `toml:"review"` Speech oldSpeech `toml:"speech"` Sound Sound `toml:"sound"` Keyboard Keyboard `toml:"keyboard"` Reading Reading `toml:"reading"` Clipboard Clipboard `toml:"clipboard"` General General `toml:"general"` Scripts Scripts `toml:"scripts"` Remote Remote `toml:"remote"` }{Review: defaults.Review, Version: 1, Speech: oldSpeech{Enabled: true, Rate: defaults.Presentation.Speech.Rate}, Sound: defaults.Presentation.Sound, Keyboard: defaults.Keyboard, Reading: defaults.Reading, Clipboard: defaults.Clipboard, General: defaults.General, Scripts: defaults.Scripts, Remote: defaults.Remote} if err := toml.NewDecoder(bytes.NewReader(data)).DisallowUnknownFields().Decode(&old); err != nil { return Settings{}, fmt.Errorf("parse version 1 settings: %w", err) } defaults.Presentation.Speech.Enabled = old.Speech.Enabled defaults.Presentation.Speech.Rate = old.Speech.Rate defaults.Presentation.Speech.Module = old.Speech.Module defaults.Presentation.Sound = old.Sound defaults.Keyboard, defaults.Reading, defaults.Clipboard = old.Keyboard, old.Reading, old.Clipboard defaults.General, defaults.Scripts, defaults.Remote = old.General, old.Scripts, old.Remote defaults.Review = old.Review return defaults, defaults.Validate() } // Version 2 briefly exposed Socket under Presentation. Migrate it away without // carrying an endpoint override into either runtime settings or future saves. func decode_v2(data []byte) (Settings, error) { defaults := Defaults() var old struct { Version int `toml:"version"` Review Review `toml:"review"` Presentation struct { Speech struct { Speech Socket string `toml:"socket"` } `toml:"speech"` Sound Sound `toml:"sound"` } `toml:"presentation"` Keyboard Keyboard `toml:"keyboard"` Reading Reading `toml:"reading"` Clipboard Clipboard `toml:"clipboard"` General General `toml:"general"` Scripts Scripts `toml:"scripts"` Remote Remote `toml:"remote"` } old.Version = 2 old.Review = defaults.Review old.Presentation.Speech.Speech = defaults.Presentation.Speech old.Presentation.Sound = defaults.Presentation.Sound old.Keyboard, old.Reading, old.Clipboard = defaults.Keyboard, defaults.Reading, defaults.Clipboard old.General, old.Scripts, old.Remote = defaults.General, defaults.Scripts, defaults.Remote if err := toml.NewDecoder(bytes.NewReader(data)).DisallowUnknownFields().Decode(&old); err != nil { return Settings{}, fmt.Errorf("parse version 2 settings: %w", err) } defaults.Presentation.Speech = old.Presentation.Speech.Speech defaults.Presentation.Sound = old.Presentation.Sound defaults.Keyboard, defaults.Reading, defaults.Clipboard = old.Keyboard, old.Reading, old.Clipboard defaults.General, defaults.Scripts, defaults.Remote = old.General, old.Scripts, old.Remote defaults.Review = old.Review return defaults, defaults.Validate() }