Application specific preferences saving now enabled.

This commit is contained in:
Storm Dragon committed 2026-10-28 04:51:39 -04:00
1 parent f88e5b81a2
commit 60abcc37d9
23 files changed
+1004 -248

No files matched your search

+2
View File
@@ -53,6 +53,7 @@ type application struct {
saver *settingsSaver
speechPending bool
speechResults chan speechChangeResult
overrideLoads chan func()
speechChoices chan preferences.Discovery
prepareSpeech func(config.Speech) (speech.Transport, error)
@@ -99,6 +100,7 @@ func new_application(ctx context.Context, source Source, keyboards Keyboards, vo
a.ui.previewSpelling = a.preview_spelling
a.ui.discoverChoices, a.ui.saveSettings = options.SettingsChoices, a.save_menu_settings
a.speechResults = make(chan speechChangeResult, 1)
a.overrideLoads = make(chan func(), 1)
a.speechChoices = make(chan preferences.Discovery, 1)
a.prepareSpeech = prepare_speech
a.quick.selectSpeech = func(target config.Speech, done func(error, bool)) {
+95 -7
View File
@@ -5,11 +5,13 @@ package app
import (
"maps"
"path/filepath"
"slices"
"git.stormux.org/storm/fenrir/internal/applicationoverrides"
"git.stormux.org/storm/fenrir/internal/applicationoverrides/networkmanager"
"git.stormux.org/storm/fenrir/internal/applicationoverrides/ninjam"
"git.stormux.org/storm/fenrir/internal/config"
"git.stormux.org/storm/fenrir/internal/i18n"
"git.stormux.org/storm/fenrir/internal/interaction"
"git.stormux.org/storm/fenrir/internal/prompt"
@@ -26,6 +28,9 @@ type overrideSnapshot struct {
type applicationOverride struct {
id string
path string
base config.Settings
profile *config.Document
terminal int
adapter applicationoverrides.Adapter
underlying, temporary overrideSnapshot
@@ -33,7 +38,59 @@ type applicationOverride struct {
initialized, recognized, exitPending bool
}
func (a *application) saves_unavailable() bool { return len(a.overrides) != 0 || a.overrideTransition }
func (a *application) saves_unavailable() bool {
return a.overrideTransition || a.overrideRestoreFailed || a.overrideFailed != nil || (a.overrideCurrent != nil && (!a.overrideCurrent.recognized || a.overrideCurrent.path == ""))
}
func (a *application) settings_path() string {
if a.overrideCurrent != nil {
return a.overrideCurrent.path
}
return a.options.ConfigPath
}
func (a *application) start_settings_save(path string, settings config.Settings) bool {
if selected := a.overrideCurrent; selected != nil {
base := selected.base
return a.saver.start(a.ctx, settings, func() error {
doc, err := config.Open_application_document(path, base)
if err != nil {
return err
}
return doc.Save(settings)
})
}
return a.saver.save(a.ctx, path, settings)
}
// Profile reads are bounded and run off the event loop, like settings writes.
func (a *application) load_application_profile(selected *applicationOverride) {
selected.base = config.Apply_application_settings(a.effective_settings(), a.settings_to_save())
// Inherit the actual working selection, including startup fallback, rather
// than unavailable saved voices or selections for a next-launch backend.
copy_speech_selection(&selected.base.Presentation.Speech, a.settings.live.Presentation.Speech)
selected.adapter.Configure(&selected.base)
path, base := selected.path, selected.base
a.overrideTransition = true
go func() {
doc, err := config.Open_application_document(path, base)
select {
case a.overrideLoads <- func() {
a.overrideTransition = false
if err != nil {
selected.adapter.Close()
delete(a.overrides, selected.terminal)
a.voice.Say(i18n.Format("Could not load application settings: {0}", err.Error()))
} else {
selected.profile = doc
selected.adapter.Reset()
}
a.overrideDirty = true
}:
case <-a.ctx.Done():
}
}()
}
func (a *application) capture_override(terminal int) overrideSnapshot {
settings := a.settings
@@ -68,7 +125,7 @@ func (a *application) restore_override(done func()) bool {
done()
return true
}
if a.speechPending || a.overrideTransition || a.overrideRestoreFailed {
if a.speechPending || a.saver.busy || a.overrideTransition || a.overrideRestoreFailed {
return false
}
// A form opened under the application must never outlive that context:
@@ -142,6 +199,9 @@ func (a *application) select_override(id string) {
a.overrides = make(map[int]*applicationOverride)
}
selected := &applicationOverride{id: id, terminal: terminal, adapter: adapter}
if a.options.ConfigPath != "" {
selected.path = filepath.Join(filepath.Dir(a.options.ConfigPath), "applications", id+".toml")
}
selected.shell.Observe(a.lastSnapshot)
a.overrides[terminal] = selected
a.voice.Say(i18n.Format("{0} application override selected", i18n.T(adapter.Name())))
@@ -151,7 +211,7 @@ func (a *application) select_override(id string) {
// Called before reader.Update so exit detection and restored settings affect
// the first shell snapshot, rather than interpreting it with the old adapter.
func (a *application) observe_override(next screen.Snapshot) (applicationoverrides.Frame, bool) {
if a.overrideTransition {
if a.overrideTransition || (a.saver.busy && len(a.overrides) != 0) {
return applicationoverrides.Frame{}, false
}
if a.overrideRestoreFailed {
@@ -188,14 +248,42 @@ func (a *application) observe_override(next screen.Snapshot) (applicationoverrid
return applicationoverrides.Frame{}, false
}
a.ui.close()
if !selected.initialized && selected.profile == nil {
a.load_application_profile(selected)
return applicationoverrides.Frame{}, false
}
selected.underlying = a.capture_override(next.Terminal)
if !selected.initialized {
settings := a.effective_settings()
selected.adapter.Configure(&settings)
a.apply_remote_state(settings, false)
a.configure_reader()
a.engine.Clear_session_regions(next.Terminal)
selected.initialized = true
settings = selected.profile.Apply_application(settings)
activate := func() {
a.apply_remote_state(settings, false)
a.settings.saveDraft, a.settings.resetBase = selected.profile.Settings, selected.profile.Settings
a.settings.remoteFields = make(map[string]uint64)
a.settings.remoteRevision = 0
*a.quick = *new_quick_settings(settings.Presentation.Speech.Driver)
a.configure_reader()
a.engine.Clear_session_regions(next.Terminal)
selected.initialized, selected.recognized = true, true
a.overrideCurrent = selected
}
if !same_speech_selection(settings.Presentation.Speech, a.settings.live.Presentation.Speech) {
a.overrideTransition = true
a.change_speech(settings.Presentation.Speech, func(err error) {
a.overrideTransition = false
if err != nil {
selected.adapter.Close()
delete(a.overrides, selected.terminal)
a.voice.Say(i18n.Format("Could not load application settings: {0}", err.Error()))
return
}
activate()
selected.adapter.Reset()
})
return applicationoverrides.Frame{}, false
}
activate()
} else {
// Numeric/runtime settings are synchronous. Speech selection is
// restored separately before resuming a suspended adapter.
+166 -14
View File
@@ -6,10 +6,12 @@ package app
import (
"errors"
"os"
"path/filepath"
"reflect"
"slices"
"strings"
"testing"
"time"
"git.stormux.org/storm/fenrir/internal/command"
"git.stormux.org/storm/fenrir/internal/config"
@@ -50,8 +52,8 @@ func TestOverrideRestoresSynthesizerAndKeepsSaveBlockedUntilCompletion(t *testin
if response := request_selection(t, a, "SET speech#voice=Temporary;speech#module=fixture"); response != "" {
t.Fatal(response)
}
if !reflect.DeepEqual(a.settings.saveDraft, baseline) {
t.Fatal("temporary voice modified draft")
if a.settings.saveDraft.Presentation.Speech.Voice != "Temporary" || !same_speech_selection(a.overrideCurrent.underlying.settings.saveDraft.Presentation.Speech, baseline.Presentation.Speech) {
t.Fatal("application voice did not stay in its own draft")
}
if failed {
a.prepareSpeech = func(config.Speech) (speech.Transport, error) { return nil, errors.New("fixture unavailable") }
@@ -109,6 +111,19 @@ func override_read(t *testing.T, a *application, snapshot screen.Snapshot) {
if err := a.read_screen(); err != nil {
t.Fatal(err)
}
// Model the event loop's asynchronous profile-read completion, leaving
// speech transitions to the tests which explicitly exercise them.
if a.overrideTransition && !a.speechPending {
select {
case loaded := <-a.overrideLoads:
loaded()
case <-time.After(time.Second):
t.Fatal("profile load stalled")
}
if err := a.read_screen(); err != nil {
t.Fatal(err)
}
}
}
func TestApplicationsMenuActivatesOnlyOnEnterAndReleasesCapture(t *testing.T) {
@@ -183,7 +198,7 @@ func TestApplicationsMenuPrefixSearch(t *testing.T) {
}
}
func TestOverrideCannotSaveOrContaminateDraftAndRestoresSession(t *testing.T) {
func TestOverrideSavesOwnDraftAndRestoresSession(t *testing.T) {
a, voice := persistence_application(t, true)
a.handle_command(command.NextQuickValue) // Preserve a pre-existing unsaved adjustment.
a.handle_command(command.AutoRead)
@@ -202,20 +217,30 @@ func TestOverrideCannotSaveOrContaminateDraftAndRestoresSession(t *testing.T) {
if response := a.apply_remote_settings("SET speech#rate=0.77;keyboard#word_echo=True"); response != "" {
t.Fatal(response)
}
if !reflect.DeepEqual(a.settings.saveDraft, baseSettings.saveDraft) {
t.Fatal("override modified save draft")
if !reflect.DeepEqual(a.overrideCurrent.underlying.settings, baseSettings) {
t.Fatal("ordinary draft changed")
}
profilePath := filepath.Join(filepath.Dir(a.options.ConfigPath), "applications", "ninjam.toml")
a.save_settings()
if a.saver.busy || voice.messages[len(voice.messages)-1] != "Save unavailable." {
t.Fatal("shortcut save admitted")
finish_persistence(t, a, false)
if !strings.Contains(voice.messages[len(voice.messages)-1], "Ninjam application settings saved") {
t.Fatal(voice.messages)
}
for _, text := range []string{"SETTING SAVE", "SETTING SAVEAS " + a.options.ConfigPath} {
if got := runtime_request(a, text); got != "Save unavailable." {
t.Fatal(text, got)
}
doc, err := config.Open_application_document(profilePath, a.overrideCurrent.base)
if err != nil || doc.Settings.Presentation.Speech.Rate != a.settings.live.Presentation.Speech.Rate || !doc.Settings.Keyboard.WordEcho {
t.Fatal("profile not saved", err)
}
reply := persistence_request(a, "SAVE")
finish_persistence(t, a, false)
persistence_reply(t, reply, false)
if got := runtime_request(a, "SETTING SAVEAS "+a.options.ConfigPath); got != "SAVEAS is unavailable in application settings" {
t.Fatal(got)
}
a.open_settings()
receive_ui(t, a.ui)
if !strings.Contains(voice.messages[len(voice.messages)-1], "Ninjam application settings") {
t.Fatal(voice.messages)
}
for range 100 {
if control, ok := a.ui.session.Focused_control(); ok && control.Action == "save" {
break
@@ -223,9 +248,7 @@ func TestOverrideCannotSaveOrContaminateDraftAndRestoresSession(t *testing.T) {
a.ui.handle(interaction.Key{Name: "tab"})
}
a.ui.handle(interaction.Key{Name: "enter"})
if !a.ui.active || a.saver.busy || voice.messages[len(voice.messages)-1] != "Save unavailable." {
t.Fatal("menu save admitted", voice.messages)
}
finish_persistence(t, a, false)
a.ui.close()
a.select_override("default")
if !reflect.DeepEqual(a.settings, baseSettings) || a.quick.revision != baseQuick || !reflect.DeepEqual(a.engine.Capture_session(), baseRuntime) {
@@ -355,3 +378,132 @@ func TestActivationRejectsPendingOperationsAndRestartStartsWithoutOverride(t *te
t.Fatal("restart retained override", err)
}
}
func TestApplicationProfileReloadAndDefaultInheritance(t *testing.T) {
for _, terminalMode := range []bool{false, true} {
a, _ := persistence_application(t, terminalMode)
override_read(t, a, override_screen(1, "ninjam"))
a.select_override("ninjam")
override_read(t, a, override_screen(1, "ninjam"))
a.handle_command(command.NextQuickValue)
savedRate := a.settings.live.Presentation.Speech.Rate
a.save_settings()
// A terminal switch must wait for the admitted application write.
override_read(t, a, override_screen(3, "other terminal"))
if a.overrideCurrent == nil {
t.Fatal("save ownership lost before completion")
}
finish_persistence(t, a, false)
override_read(t, a, override_screen(3, "other terminal"))
if a.overrideCurrent != nil || a.settings.live.Presentation.Speech.Rate == savedRate {
t.Fatal("application leaked across terminal")
}
// Change an unrelated ordinary setting, then discard/reselect the adapter.
if text := a.apply_remote_settings("SET speech#pitch=0.6"); text != "" {
t.Fatal(text)
}
pitch := a.settings.live.Presentation.Speech.Pitch
override_read(t, a, override_screen(1, "ninjam"))
a.select_override("default")
a.select_override("ninjam")
override_read(t, a, override_screen(1, "ninjam"))
if a.settings.live.Presentation.Speech.Rate != savedRate || a.settings.live.Presentation.Speech.Pitch != pitch {
t.Fatal("saved rate or new inherited pitch missing", a.settings.live.Presentation.Speech)
}
if text := a.apply_remote_settings("SET speech#rate=0.1"); text != "" {
t.Fatal(text)
}
if text := a.reset_remote_settings(); text != "" {
t.Fatal(text)
}
if a.settings.live.Presentation.Speech.Rate != savedRate {
t.Fatal("RESET did not restore profile")
}
before := a.settings
if text := a.apply_remote_settings("SET speech#rate=0.2;screen#ignore_screen=1"); text == "" || !reflect.DeepEqual(a.settings, before) {
t.Fatal("global remote change was not rejected atomically")
}
override_read(t, a, override_screen(1, "[Username@host directory] $"))
if a.overrideCurrent != nil || a.settings.live.Presentation.Speech.Rate == savedRate {
t.Fatal("exit did not restore default")
}
}
}
func TestApplicationProfileVoiceLoadAndFailureLeaveDefaultIntact(t *testing.T) {
for _, failure := range []string{"", "invalid", "unavailable"} {
a, _, path := selection_application(t, true)
profilePath := filepath.Join(filepath.Dir(path), "applications", "ninjam.toml")
if err := os.MkdirAll(filepath.Dir(profilePath), 0700); err != nil {
t.Fatal(err)
}
data := "[presentation.speech]\nvoice = 'Application voice'\nrate = 12\n"
if failure == "invalid" {
data = "[presentation.speech]\nrate = 999\n"
}
if err := os.WriteFile(profilePath, []byte(data), 0600); err != nil {
t.Fatal(err)
}
a.prepareSpeech = func(config.Speech) (speech.Transport, error) {
if failure == "unavailable" {
return nil, errors.New("voice unavailable")
}
return &preparedVoice{}, nil
}
baseline := a.settings
override_read(t, a, override_screen(1, "ninjam"))
a.select_override("ninjam")
override_read(t, a, override_screen(1, "ninjam"))
if a.speechPending {
finish_selection(t, a)
}
if failure != "" {
if a.overrideCurrent != nil || !reflect.DeepEqual(a.settings, baseline) || a.saves_unavailable() {
t.Fatal("failed profile changed ordinary settings")
}
continue
}
override_read(t, a, override_screen(1, "ninjam"))
if a.settings.live.Presentation.Speech.Voice != "Application voice" || a.settings.live.Presentation.Speech.Rate != 12 {
t.Fatal("profile voice not applied")
}
a.select_override("default")
finish_selection(t, a)
if !reflect.DeepEqual(a.settings, baseline) {
t.Fatal("default voice not restored")
}
}
}
func TestApplicationMenuPreservesSessionTogglesAndLaunchChoices(t *testing.T) {
a, _ := persistence_application(t, true)
a.settings.saveDraft.Keyboard.Layout = "laptop"
a.settings.saveDraft.Presentation.Speech.Driver = "dectalk"
a.settings.saveDraft.Presentation.Speech.Voice = "Next launch voice"
override_read(t, a, override_screen(1, "ninjam"))
a.select_override("ninjam")
override_read(t, a, override_screen(1, "ninjam"))
a.handle_command(command.Mute)
a.handle_command(command.AutoRead)
a.open_settings()
receive_ui(t, a.ui)
for range 100 {
if control, ok := a.ui.session.Focused_control(); ok && control.Action == "save" {
break
}
a.ui.handle(interaction.Key{Name: "tab"})
}
a.ui.handle(interaction.Key{Name: "enter"})
finish_persistence(t, a, false)
if !a.muted || a.engine.Automatic_reading() || a.settings.live.Keyboard.Layout != "desktop" || a.settings.live.Presentation.Speech.Driver != "speech-dispatcher" || a.settings.live.Presentation.Speech.Voice != "" {
t.Fatal("menu changed session toggles or launch-only choices")
}
data, err := os.ReadFile(a.overrideCurrent.path)
if err != nil || strings.Contains(string(data), "enabled") || strings.Contains(string(data), "automatic") || strings.Contains(string(data), "driver") || strings.Contains(string(data), "layout") || strings.Contains(string(data), "voice") {
t.Fatal("menu saved inherited/session/launch settings", string(data), err)
}
a.select_override("default")
if a.settings.saveDraft.Keyboard.Layout != "laptop" || a.settings.saveDraft.Presentation.Speech.Driver != "dectalk" {
t.Fatal("ordinary next-launch choices lost")
}
}
+2 -6
View File
@@ -121,9 +121,7 @@ func (a *application) handle_command(id command.ID) {
case command.ProgressMonitor:
a.quick.mark_changed("presentation.progress.enabled")
a.settings.live.Presentation.Progress.Enabled = !a.settings.live.Presentation.Progress.Enabled
if a.overrideCurrent == nil {
a.settings.saveDraft.Presentation.Progress = a.settings.live.Presentation.Progress
}
a.settings.saveDraft.Presentation.Progress = a.settings.live.Presentation.Progress
a.engine.Configure_progress(a.settings.live.Presentation.Progress.Enabled)
if a.settings.live.Presentation.Progress.Enabled {
a.voice.Say(i18n.T("Progress monitoring enabled"))
@@ -176,9 +174,7 @@ func (a *application) review_command(id command.ID) {
}
switch id {
case command.ToggleTracking:
if a.overrideCurrent == nil {
a.settings.saveDraft.Reading.Mode = a.engine.Tracking_mode()
}
a.settings.saveDraft.Reading.Mode = a.engine.Tracking_mode()
}
if spelling := result.Spelling; spelling != nil && !a.temporarySilence {
a.voice.say_spelling(*spelling)
+3
View File
@@ -114,6 +114,8 @@ func (a *application) loop(ticks <-chan time.Time) error {
select {
case discovery := <-a.speechChoices:
a.quick.discovery = discovery
case loaded := <-a.overrideLoads:
loaded()
case result := <-a.speechResults:
a.speech_result(result)
if len(a.overrides) != 0 || a.overrideCurrent != nil {
@@ -126,6 +128,7 @@ func (a *application) loop(ticks <-chan time.Time) error {
a.spellcheck_result(result)
case err := <-a.saver.results:
a.settings_save_result(err)
a.overrideDirty = true
a.quitting = a.quitAfterSave
case result := <-scriptResults:
if err := a.poll_input(false); err != nil {
+35 -6
View File
@@ -27,7 +27,15 @@ import (
)
func (ui *internalUI) open_settings(settings config.Settings, path string, liveQuick ...config.Settings) {
generation, err := ui.begin(preferences.MenuTitle)
ui.open_settings_context(settings, path, nil, preferences.MenuTitle, "", liveQuick...)
}
func (ui *internalUI) open_application_settings(settings config.Settings, path string, base config.Settings, name string) {
ui.open_settings_context(settings, path, &base, i18n.Format("{0} application settings", name), i18n.Format("{0} application settings saved.", name))
}
func (ui *internalUI) open_settings_context(settings config.Settings, path string, applicationBase *config.Settings, title string, savedMessage string, liveQuick ...config.Settings) {
generation, err := ui.begin(title)
if err != nil {
ui.failed("Could not open Fenrir Menu: {0}", err)
return
@@ -47,7 +55,16 @@ func (ui *internalUI) open_settings(settings config.Settings, path string, liveQ
previewChanged := ui.changed
speechOverride := ui.speechOverride
go func() {
doc, err := config.Open_document(path, settings)
var doc *config.Document
var err error
if applicationBase != nil {
doc, err = config.Open_application_document(path, *applicationBase)
if err == nil {
doc.Settings = settings
}
} else {
doc, err = config.Open_document(path, settings)
}
if err != nil {
ui.post(uiUpdate{generation: generation, close: true, speech: i18n.Format("Could not open Fenrir Menu: {0}", err.Error())})
return
@@ -62,11 +79,20 @@ func (ui *internalUI) open_settings(settings config.Settings, path string, liveQ
if ui.discoverChoices != nil {
discovery = ui.discoverChoices(doc.Settings)
}
session, err := preferences.New_session(doc.Settings, doc.Order, discovery.Choices(doc.Settings))
var session *interaction.Session
if applicationBase != nil {
session, err = preferences.New_application_session(doc.Settings, doc.Order, title, discovery.Choices(doc.Settings))
} else {
session, err = preferences.New_session(doc.Settings, doc.Order, discovery.Choices(doc.Settings))
}
if err != nil {
ui.post(uiUpdate{generation: generation, close: true, speech: i18n.Format("Could not open Fenrir Menu: {0}", err.Error())})
return
}
readSettings := preferences.Settings
if applicationBase != nil {
readSettings = preferences.Application_settings
}
previous := doc.Settings
catalogs := map[string]preferences.Discovery{previous.Presentation.Speech.Driver: discovery}
pending := map[string]bool{}
@@ -77,7 +103,7 @@ func (ui *internalUI) open_settings(settings config.Settings, path string, liveQ
previewChanged(session)
}
if ui.cancelTest != nil {
draft, err := preferences.Settings(session, doc.Settings)
draft, err := readSettings(session, doc.Settings)
if err != nil || draft.Presentation.Speech != tested {
ui.stop_test()
}
@@ -121,7 +147,7 @@ func (ui *internalUI) open_settings(settings config.Settings, path string, liveQ
var action func(string, *interaction.Session)
action = func(name string, session *interaction.Session) {
if name == "test-synthesizer" {
draft, err := preferences.Settings(session, doc.Settings)
draft, err := readSettings(session, doc.Settings)
if err != nil {
ui.test_failed(err)
return
@@ -169,7 +195,7 @@ func (ui *internalUI) open_settings(settings config.Settings, path string, liveQ
if name != "save" {
return
}
draft, err := preferences.Settings(session, doc.Settings)
draft, err := readSettings(session, doc.Settings)
if err != nil {
ui.failed("Could not save settings: {0}", err)
return
@@ -184,6 +210,9 @@ func (ui *internalUI) open_settings(settings config.Settings, path string, liveQ
ui.onClosed = nil // Successful save commits the form preview.
}
ui.update(uiUpdate{generation: generation, close: true})
if applicationBase != nil {
ui.voice.Say(savedMessage)
}
}) {
ui.session = nil
}
+27 -6
View File
@@ -10,6 +10,7 @@ import (
"git.stormux.org/storm/fenrir/internal/config"
"git.stormux.org/storm/fenrir/internal/control"
"git.stormux.org/storm/fenrir/internal/i18n"
)
// These operations run only after the shared credential and settings gates.
@@ -27,6 +28,9 @@ func (a *application) remote_settings_request(text string, request control.Reque
return "Settings are busy; try again after the current operation", false
}
if verb == "SET" {
if err := a.check_application_remote(text); err != nil {
return err.Error(), false
}
updated, err := remote_settings(a.effective_settings(), text)
if err != nil {
return err.Error(), false
@@ -77,8 +81,11 @@ func (a *application) remote_settings_request(text string, request control.Reque
}
return a.reset_remote_settings(), false
}
path := a.options.ConfigPath
path := a.settings_path()
if verb == "SAVEAS" {
if a.overrideCurrent != nil {
return "SAVEAS is unavailable in application settings", false
}
// 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() {
@@ -89,7 +96,7 @@ func (a *application) remote_settings_request(text string, request control.Reque
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()) {
if !a.start_settings_save(path, a.settings_to_save()) {
return "Settings are busy; try again after the current operation", false
}
a.saver.revision = a.settings.remoteRevision
@@ -119,6 +126,9 @@ func (a *application) settings_save_result(err error) {
response = "Could not save settings: " + err.Error()
}
remote_reply(reply, response)
} else if a.overrideCurrent != nil && err == nil {
a.saver.busy = false
a.voice.Say(i18n.Format("{0} application settings saved.", i18n.T(a.overrideCurrent.adapter.Name())))
} else {
a.saver.result(err, a.voice)
}
@@ -149,10 +159,6 @@ func (a *application) reset_remote_settings() string {
if err != nil {
return err.Error()
}
if a.overrideCurrent != nil {
a.apply_remote_state(updated, false)
return ""
}
a.settings.saveDraft = persisted
_, speechOverridden := a.settings.remoteFields["speech#enabled"]
a.apply_remote_state(updated, speechOverridden)
@@ -203,3 +209,18 @@ func copy_remote_settings(target *config.Settings, source config.Settings, field
}
}
}
// Screen routing is global and has no application-profile counterpart.
func (a *application) check_application_remote(text string) error {
if a.overrideCurrent == nil {
return nil
}
_, parameters, _ := strings.Cut(text, " ")
for _, assignment := range remote_assignments(parameters) {
path, _, _ := strings.Cut(assignment, "=")
if strings.EqualFold(strings.TrimSpace(path), "screen#ignore_screen") {
return fmt.Errorf("screen#ignore_screen is unavailable in application settings")
}
}
return nil
}
+27 -6
View File
@@ -139,6 +139,10 @@ func (a *application) open_settings() {
selected := a.settings.live.Presentation.Speech
a.ui.speechOverride = &selected
}
if selected := a.overrideCurrent; selected != nil {
a.ui.open_application_settings(a.settings_to_save(), selected.path, selected.base, i18n.T(selected.adapter.Name()))
return
}
if a.quick.changed {
a.ui.open_settings(a.settings.live, a.options.ConfigPath, a.settings.live)
} else {
@@ -155,7 +159,7 @@ func (a *application) save_settings() {
a.voice.Say(i18n.T("Failed to save settings."))
return
}
if a.saver.save(a.ctx, a.options.ConfigPath, a.settings_to_save()) {
if a.start_settings_save(a.settings_path(), a.settings_to_save()) {
a.saver.revision = a.settings.remoteRevision
}
}
@@ -172,6 +176,7 @@ func (a *application) save_menu_settings(doc *config.Document, draft config.Sett
a.ui.failed("Could not save settings: {0}", fmt.Errorf("speech selection in progress"))
return false
}
previousDraft := a.settings.saveDraft
speechApplied := a.settings.speechAvailable && draft.Presentation.Speech.Driver == a.settings.live.Presentation.Speech.Driver && !same_speech_selection(draft.Presentation.Speech, a.settings.saveDraft.Presentation.Speech)
if !a.saver.start(a.ctx, draft, func() error {
var prepared speech.Transport
@@ -220,7 +225,24 @@ func (a *application) save_menu_settings(doc *config.Document, draft config.Sett
if a.quick.fields["presentation.progress.enabled"] > quickRevision {
merged.Presentation.Progress = a.settings.live.Presentation.Progress
}
a.apply_saved_settings(merged, speechApplied && err == nil)
if a.overrideCurrent != nil {
live := config.Apply_application_settings(a.effective_settings(), merged)
if merged.Presentation.Speech.Enabled == previousDraft.Presentation.Speech.Enabled {
live.Presentation.Speech.Enabled = !a.muted
}
if merged.Reading.Automatic == previousDraft.Reading.Automatic {
live.Reading.Automatic = a.engine.Automatic_reading()
}
if !speechApplied || err != nil {
copy_speech_selection(&live.Presentation.Speech, a.settings.live.Presentation.Speech)
}
a.apply_remote_state(live, false)
a.configure_reader()
a.settings.saveDraft = merged
a.quick.selectionChanged = false
} else {
a.apply_saved_settings(merged, speechApplied && err == nil)
}
a.settings.resetBase = draft // RESET reflects disk, not later overrides.
a.quick.changed = newerQuick
}
@@ -249,6 +271,9 @@ func (a *application) effective_settings() config.Settings {
// Remote assignments are validated against effective session state, then
// separately overlaid on the save draft so unrelated next-launch choices survive.
func (a *application) apply_remote_settings(text string) string {
if err := a.check_application_remote(text); err != nil {
return err.Error()
}
updated, err := remote_settings(a.effective_settings(), text)
if err != nil {
return err.Error()
@@ -256,10 +281,6 @@ func (a *application) apply_remote_settings(text string) string {
if updated.Presentation.Speech.Enabled && !a.settings.speechAvailable {
return "Speech was disabled at startup; enable it in settings and relaunch"
}
if a.overrideCurrent != nil {
a.apply_remote_state(updated, false)
return ""
}
// Apply only the requested assignments to the persistence
// draft, retaining unrelated next-launch menu settings.
draftBase := a.settings.saveDraft
+140
View File
@@ -0,0 +1,140 @@
// 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
}
+117
View File
@@ -0,0 +1,117 @@
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package config
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestApplicationDocumentSparseInheritanceAndExplicitValues(t *testing.T) {
path := filepath.Join(t.TempDir(), "applications", "ninjam.toml")
base := Defaults()
doc, err := Open_application_document(path, base)
if err != nil {
t.Fatal(err)
}
saved := base
saved.Presentation.Speech.Rate = 12
saved.Keyboard.WordEcho = true
if err := doc.Save(saved); err != nil {
t.Fatal(err)
}
data, _ := os.ReadFile(path)
if strings.Contains(string(data), "driver") || strings.Contains(string(data), "automatic") || strings.Contains(string(data), "version") {
t.Fatal("inherited values copied", string(data))
}
info, _ := os.Stat(path)
if info.Mode().Perm() != 0600 {
t.Fatal(info.Mode())
}
// A new ordinary voice is inherited; explicitly saved rate/echo survive.
base.Presentation.Speech.Voice = "New default"
base.Presentation.Speech.Rate = 12
doc, err = Open_application_document(path, base)
if err != nil || doc.Settings.Presentation.Speech.Voice != "New default" || !doc.Settings.Keyboard.WordEcho {
t.Fatal(doc, err)
}
if err := doc.Save(doc.Settings); err != nil {
t.Fatal(err)
}
base.Presentation.Speech.Rate = 60
doc, err = Open_application_document(path, base)
if err != nil || doc.Settings.Presentation.Speech.Rate != 12 {
t.Fatal("explicit value equal to previous base lost", doc, err)
}
session := base
session.Presentation.Speech.Enabled = false
session.Reading.Automatic = false
effective := doc.Apply_application(session)
if effective.Presentation.Speech.Enabled || effective.Reading.Automatic || effective.Presentation.Speech.Rate != 12 {
t.Fatal("session state overwritten", effective)
}
}
func TestApplicationDocumentRejectsInvalidProfilesAndExternalChanges(t *testing.T) {
path := filepath.Join(t.TempDir(), "profile.toml")
for _, data := range []string{
"[presentation.speech]\nrate = 101\n",
"[presentation.speech]\nrate = 'fast'\n",
"[presentation.speech]\ndriver = 'dectalk'\n",
"[keyboard]\nlayout = 'laptop'\n",
"[screen]\nignored_vts = [1]\n",
"[reading]\nunknown = true\n",
"[reading]\nmode = 'invalid'\n",
"[broken",
} {
if err := os.WriteFile(path, []byte(data), 0600); err != nil {
t.Fatal(err)
}
if _, err := Open_application_document(path, Defaults()); err == nil {
t.Fatal("invalid profile accepted", data)
}
}
if err := os.WriteFile(path, []byte("[presentation.speech]\nrate = 10\n"), 0600); err != nil {
t.Fatal(err)
}
doc, err := Open_application_document(path, Defaults())
if err != nil {
t.Fatal(err)
}
changed := []byte("[presentation.speech]\nrate = 20\n")
if err := os.WriteFile(path, changed, 0600); err != nil {
t.Fatal(err)
}
if err := doc.Save(doc.Settings); err == nil {
t.Fatal("external edit overwritten")
}
data, _ := os.ReadFile(path)
if string(data) != string(changed) {
t.Fatal(string(data))
}
}
func TestApplicationVoiceSelectionSavedTogether(t *testing.T) {
path := filepath.Join(t.TempDir(), "profile.toml")
base := Defaults()
base.Presentation.Speech.Module = "original-module"
base.Presentation.Speech.Language = "en"
doc, err := Open_application_document(path, base)
if err != nil {
t.Fatal(err)
}
chosen := base
chosen.Presentation.Speech.Voice = "Application voice"
if err := doc.Save(chosen); err != nil {
t.Fatal(err)
}
base.Presentation.Speech.Module = "different-module"
base.Presentation.Speech.Language = "fr"
doc, err = Open_application_document(path, base)
if err != nil || doc.Settings.Presentation.Speech.Module != "original-module" || doc.Settings.Presentation.Speech.Language != "en" || doc.Settings.Presentation.Speech.Voice != "Application voice" {
t.Fatal("coupled voice selection changed", doc, err)
}
}
+10 -5
View File
@@ -34,11 +34,13 @@ import (
// Document retains file identity and table order for an isolated edit. Version
// is metadata, not a UI tab. Omitted tables follow in schema order.
type Document struct {
Settings Settings
Order []string
path string
original []byte
existed bool
Settings Settings
Order []string
path string
original []byte
existed bool
applicationBase *Settings
applicationFields map[string]bool
}
func read_document(path string) ([]byte, error) {
@@ -156,6 +158,9 @@ func (d *Document) Save(settings Settings) error {
if err := settings.Validate(); err != nil {
return err
}
if d.applicationBase != nil {
return d.save_application(settings)
}
// The same schema drives persistence and the form, including nested groups.
var output bytes.Buffer
fmt.Fprintf(&output, "version = %d\n", settings.Version)
+44 -31
View File
@@ -67,11 +67,11 @@ msgstr "Wütend"
msgid "Apple"
msgstr "Apfel"
#: internal/app/application_overrides.go:237
#: internal/app/application_overrides.go:325
msgid "Application"
msgstr "Anwendung"
#: internal/app/application_overrides.go:236
#: internal/app/application_overrides.go:324
msgid "Applications"
msgstr "Anwendungen"
@@ -163,8 +163,8 @@ msgstr "Schmetterling"
msgid "Canada"
msgstr "Kanada"
#: internal/app/application_overrides.go:238
#: internal/preferences/settings.go:79
#: internal/app/application_overrides.go:326
#: internal/preferences/settings.go:93
msgid "Cancel"
msgstr "Abbrechen"
@@ -256,7 +256,7 @@ msgstr "Live-Bereich 9 löschen"
msgid "Clipboard"
msgstr "Zwischenablage"
#: internal/app/application.go:142 internal/app/application.go:145
#: internal/app/application.go:144 internal/app/application.go:147
#: internal/app/clipboard.go:218 internal/app/clipboard.go:374
msgid "Clipboard import failed"
msgstr "Import aus der Zwischenablage fehlgeschlagen"
@@ -289,8 +289,13 @@ msgstr "Cool"
msgid "Could not check spelling: {0}"
msgstr "Rechtschreibung konnte nicht geprüft werden: {0}"
#: internal/app/application_overrides.go:241
#: internal/app/application_overrides.go:245
#: internal/app/application_overrides.go:278
#: internal/app/application_overrides.go:83
msgid "Could not load application settings: {0}"
msgstr "Anwendungseinstellungen konnten nicht geladen werden: {0}"
#: internal/app/application_overrides.go:329
#: internal/app/application_overrides.go:333
msgid "Could not open Applications: {0}"
msgstr "Anwendungen konnten nicht geöffnet werden: {0}"
@@ -298,8 +303,8 @@ msgstr "Anwendungen konnten nicht geöffnet werden: {0}"
msgid "Could not open Emoji Menu: {0}"
msgstr "Emoji-Menü konnte nicht geöffnet werden: {0}"
#: internal/app/fenrir_menu.go:32 internal/app/fenrir_menu.go:52
#: internal/app/fenrir_menu.go:67 internal/app/runtime_settings.go:133
#: internal/app/fenrir_menu.go:40 internal/app/fenrir_menu.go:69
#: internal/app/fenrir_menu.go:89 internal/app/runtime_settings.go:133
msgid "Could not open Fenrir Menu: {0}"
msgstr "Fenrir-Menü konnte nicht geöffnet werden: {0}"
@@ -307,8 +312,8 @@ msgstr "Fenrir-Menü konnte nicht geöffnet werden: {0}"
msgid "Could not open Spellcheck Menu: {0}"
msgstr "Rechtschreibmenü konnte nicht geöffnet werden: {0}"
#: internal/app/application_overrides.go:104
#: internal/app/application_overrides.go:208
#: internal/app/application_overrides.go:161
#: internal/app/application_overrides.go:296
msgid "Could not restore application settings: {0}"
msgstr "Anwendungseinstellungen konnten nicht wiederhergestellt werden: {0}"
@@ -316,8 +321,8 @@ msgstr "Anwendungseinstellungen konnten nicht wiederhergestellt werden: {0}"
msgid "Could not save exceptions: {0}"
msgstr "Ausnahmen konnten nicht gespeichert werden: {0}"
#: internal/app/fenrir_menu.go:174 internal/app/fenrir_menu.go:180
#: internal/app/runtime_settings.go:172 internal/app/runtime_settings.go:199
#: internal/app/fenrir_menu.go:200 internal/app/fenrir_menu.go:206
#: internal/app/runtime_settings.go:176 internal/app/runtime_settings.go:204
msgid "Could not save settings: {0}"
msgstr "Einstellungen konnten nicht gespeichert werden: {0}"
@@ -337,12 +342,12 @@ msgstr "Dolch"
msgid "Date format"
msgstr "Datumsformat"
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "Default"
msgstr "Standard"
#: internal/app/application_overrides.go:129
#: internal/app/application_overrides.go:182
#: internal/app/application_overrides.go:186
#: internal/app/application_overrides.go:242
msgid "Default settings restored"
msgstr "Standardeinstellungen wiederhergestellt"
@@ -445,7 +450,7 @@ msgstr ""
msgid "Face with symbols"
msgstr "Gesicht mit Symbolen"
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:155
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:159
msgid "Failed to save settings."
msgstr "Einstellungen konnten nicht gespeichert werden."
@@ -489,7 +494,7 @@ msgstr "Gefaltete Hände"
msgid "Food"
msgstr "Essen"
#: internal/app/commands.go:145
#: internal/app/commands.go:143
msgid "Forward next keypress"
msgstr "Nächste Taste durchreichen"
@@ -726,7 +731,7 @@ msgstr "Übelkeit"
msgid "Netherlands"
msgstr "Niederlande"
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "NetworkManager"
msgstr "NetworkManager"
@@ -734,7 +739,7 @@ msgstr "NetworkManager"
msgid "New channel"
msgstr "Neuer Kanal"
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "Ninjam"
msgstr "Ninjam"
@@ -787,7 +792,7 @@ msgstr "Nicht im Lesemodus"
msgid "Not in window Mode"
msgstr "Nicht im Fenstermodus"
#: internal/preferences/settings.go:79
#: internal/preferences/settings.go:93
msgid "OK"
msgstr "OK"
@@ -850,11 +855,11 @@ msgstr ""
"Gibt den aktuell ausgewählten Text aus, der in die Zwischenablage kopiert "
"wird"
#: internal/app/commands.go:131
#: internal/app/commands.go:129
msgid "Progress monitoring disabled"
msgstr "Fortschrittsüberwachung deaktiviert"
#: internal/app/commands.go:129
#: internal/app/commands.go:127
msgid "Progress monitoring enabled"
msgstr "Fortschrittsüberwachung aktiviert"
@@ -1006,7 +1011,7 @@ msgstr "Traurig"
msgid "Santa"
msgstr "Weihnachtsmann"
#: internal/app/runtime_settings.go:151 internal/app/runtime_settings.go:168
#: internal/app/runtime_settings.go:155 internal/app/runtime_settings.go:172
msgid "Save unavailable."
msgstr "Speichern nicht verfügbar."
@@ -1030,7 +1035,7 @@ msgstr "„Falsch geschrieben“ ansagen"
msgid "Screaming"
msgstr "Schreiend"
#: internal/app/commands.go:154 internal/app/scripts.go
#: internal/app/commands.go:152 internal/app/scripts.go
msgid "Script failed"
msgstr "Skript fehlgeschlagen"
@@ -1078,8 +1083,8 @@ msgstr "Live-Bereich aus einer oder zwei Markierungen festlegen"
msgid "Set window begin and end marks"
msgstr "Setze Fensteranfangs- und -endmarken"
#: internal/app/application_overrides.go:114
#: internal/app/application_overrides.go:227 internal/app/commands.go:84
#: internal/app/application_overrides.go:171
#: internal/app/application_overrides.go:315 internal/app/commands.go:84
msgid "Settings are busy"
msgstr "Einstellungen sind beschäftigt"
@@ -1227,11 +1232,11 @@ msgstr "Symbole"
msgid "Taco"
msgstr "Taco"
#: internal/preferences/settings.go:72
#: internal/preferences/settings.go:83
msgid "Test synthesizer"
msgstr "Sprachsynthesizer testen"
#: internal/app/fenrir_menu.go:139
#: internal/app/fenrir_menu.go:165
msgid "Testing voice"
msgstr "Stimme wird getestet"
@@ -2187,7 +2192,7 @@ msgstr "Tonhöhe"
msgid "speech rate"
msgstr "Sprechgeschwindigkeit"
#: internal/app/commands.go:140
#: internal/app/commands.go:138
msgid "speech temporary disabled"
msgstr "Sprachausgabe vorübergehend ausgeschaltet"
@@ -2282,10 +2287,18 @@ msgstr "Zwinkern"
msgid "{0} added to dictionary"
msgstr "{0} zum Wörterbuch hinzugefügt"
#: internal/app/application_overrides.go:147
#: internal/app/application_overrides.go:207
msgid "{0} application override selected"
msgstr "Anwendungseinstellungen für {0} ausgewählt"
#: internal/app/fenrir_menu.go:34
msgid "{0} application settings"
msgstr "Anwendungseinstellungen für {0}"
#: internal/app/fenrir_menu.go:34 internal/app/remote_persistence.go:131
msgid "{0} application settings saved."
msgstr "Anwendungseinstellungen für {0} gespeichert."
#: internal/interaction/presentation.go:53
msgid "{0} button"
msgstr "{0} Schaltfläche"
+44 -31
View File
@@ -67,11 +67,11 @@ msgstr "Enfadado"
msgid "Apple"
msgstr "Manzana"
#: internal/app/application_overrides.go:237
#: internal/app/application_overrides.go:325
msgid "Application"
msgstr "Aplicación"
#: internal/app/application_overrides.go:236
#: internal/app/application_overrides.go:324
msgid "Applications"
msgstr "Aplicaciones"
@@ -163,8 +163,8 @@ msgstr "Mariposa"
msgid "Canada"
msgstr "Canadá"
#: internal/app/application_overrides.go:238
#: internal/preferences/settings.go:79
#: internal/app/application_overrides.go:326
#: internal/preferences/settings.go:93
msgid "Cancel"
msgstr "Cancelar"
@@ -256,7 +256,7 @@ msgstr "Borrar la región dinámica 9"
msgid "Clipboard"
msgstr "Portapapeles"
#: internal/app/application.go:142 internal/app/application.go:145
#: internal/app/application.go:144 internal/app/application.go:147
#: internal/app/clipboard.go:218 internal/app/clipboard.go:374
msgid "Clipboard import failed"
msgstr "Error al importar el portapapeles"
@@ -289,8 +289,13 @@ msgstr "Genial"
msgid "Could not check spelling: {0}"
msgstr "No se pudo revisar la ortografía: {0}"
#: internal/app/application_overrides.go:241
#: internal/app/application_overrides.go:245
#: internal/app/application_overrides.go:278
#: internal/app/application_overrides.go:83
msgid "Could not load application settings: {0}"
msgstr "No se pudo cargar la configuración de la aplicación: {0}"
#: internal/app/application_overrides.go:329
#: internal/app/application_overrides.go:333
msgid "Could not open Applications: {0}"
msgstr "No se pudo abrir Aplicaciones: {0}"
@@ -298,8 +303,8 @@ msgstr "No se pudo abrir Aplicaciones: {0}"
msgid "Could not open Emoji Menu: {0}"
msgstr "No se pudo abrir el menú de emojis: {0}"
#: internal/app/fenrir_menu.go:32 internal/app/fenrir_menu.go:52
#: internal/app/fenrir_menu.go:67 internal/app/runtime_settings.go:133
#: internal/app/fenrir_menu.go:40 internal/app/fenrir_menu.go:69
#: internal/app/fenrir_menu.go:89 internal/app/runtime_settings.go:133
msgid "Could not open Fenrir Menu: {0}"
msgstr "No se pudo abrir el menú de Fenrir: {0}"
@@ -307,8 +312,8 @@ msgstr "No se pudo abrir el menú de Fenrir: {0}"
msgid "Could not open Spellcheck Menu: {0}"
msgstr "No se pudo abrir el menú de ortografía: {0}"
#: internal/app/application_overrides.go:104
#: internal/app/application_overrides.go:208
#: internal/app/application_overrides.go:161
#: internal/app/application_overrides.go:296
msgid "Could not restore application settings: {0}"
msgstr "No se pudo restaurar la configuración de la aplicación: {0}"
@@ -316,8 +321,8 @@ msgstr "No se pudo restaurar la configuración de la aplicación: {0}"
msgid "Could not save exceptions: {0}"
msgstr "No se pudieron guardar las excepciones: {0}"
#: internal/app/fenrir_menu.go:174 internal/app/fenrir_menu.go:180
#: internal/app/runtime_settings.go:172 internal/app/runtime_settings.go:199
#: internal/app/fenrir_menu.go:200 internal/app/fenrir_menu.go:206
#: internal/app/runtime_settings.go:176 internal/app/runtime_settings.go:204
msgid "Could not save settings: {0}"
msgstr "No se pudo guardar la configuración: {0}"
@@ -337,12 +342,12 @@ msgstr "Daga"
msgid "Date format"
msgstr "Formato de fecha"
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "Default"
msgstr "Predeterminado"
#: internal/app/application_overrides.go:129
#: internal/app/application_overrides.go:182
#: internal/app/application_overrides.go:186
#: internal/app/application_overrides.go:242
msgid "Default settings restored"
msgstr "Configuración predeterminada restaurada"
@@ -446,7 +451,7 @@ msgstr ""
msgid "Face with symbols"
msgstr "Cara con símbolos"
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:155
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:159
msgid "Failed to save settings."
msgstr "No se pudo guardar la configuración."
@@ -490,7 +495,7 @@ msgstr "Manos juntas"
msgid "Food"
msgstr "Comida"
#: internal/app/commands.go:145
#: internal/app/commands.go:143
msgid "Forward next keypress"
msgstr "Enviar la siguiente tecla"
@@ -734,7 +739,7 @@ msgstr "Náuseas"
msgid "Netherlands"
msgstr "Países Bajos"
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "NetworkManager"
msgstr "NetworkManager"
@@ -742,7 +747,7 @@ msgstr "NetworkManager"
msgid "New channel"
msgstr "Nuevo canal"
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "Ninjam"
msgstr "Ninjam"
@@ -796,7 +801,7 @@ msgstr "Fuera del modo de revisión"
msgid "Not in window Mode"
msgstr "No está en modo ventana"
#: internal/preferences/settings.go:79
#: internal/preferences/settings.go:93
msgid "OK"
msgstr "Aceptar"
@@ -857,11 +862,11 @@ msgid ""
"Presents the currently selected text that will be copied to the clipboard"
msgstr "Presenta el texto seleccionado que se copiará al portapapeles"
#: internal/app/commands.go:131
#: internal/app/commands.go:129
msgid "Progress monitoring disabled"
msgstr "Seguimiento del progreso desactivado"
#: internal/app/commands.go:129
#: internal/app/commands.go:127
msgid "Progress monitoring enabled"
msgstr "Seguimiento del progreso activado"
@@ -1013,7 +1018,7 @@ msgstr "Triste"
msgid "Santa"
msgstr "Papá Noel"
#: internal/app/runtime_settings.go:151 internal/app/runtime_settings.go:168
#: internal/app/runtime_settings.go:155 internal/app/runtime_settings.go:172
msgid "Save unavailable."
msgstr "Guardado no disponible."
@@ -1037,7 +1042,7 @@ msgstr "Decir «mal escrita»"
msgid "Screaming"
msgstr "Gritando"
#: internal/app/commands.go:154 internal/app/scripts.go
#: internal/app/commands.go:152 internal/app/scripts.go
msgid "Script failed"
msgstr "El script falló"
@@ -1085,8 +1090,8 @@ msgstr "Definir una región dinámica a partir de una o dos marcas"
msgid "Set window begin and end marks"
msgstr "Establece una marca de principio y otra para el final de la ventana"
#: internal/app/application_overrides.go:114
#: internal/app/application_overrides.go:227 internal/app/commands.go:84
#: internal/app/application_overrides.go:171
#: internal/app/application_overrides.go:315 internal/app/commands.go:84
msgid "Settings are busy"
msgstr "La configuración está ocupada"
@@ -1234,11 +1239,11 @@ msgstr "Símbolos"
msgid "Taco"
msgstr "Taco"
#: internal/preferences/settings.go:72
#: internal/preferences/settings.go:83
msgid "Test synthesizer"
msgstr "Probar sintetizador"
#: internal/app/fenrir_menu.go:139
#: internal/app/fenrir_menu.go:165
msgid "Testing voice"
msgstr "Probando la voz"
@@ -2193,7 +2198,7 @@ msgstr "tono de voz"
msgid "speech rate"
msgstr "velocidad de voz"
#: internal/app/commands.go:140
#: internal/app/commands.go:138
msgid "speech temporary disabled"
msgstr "Habla desactivada temporalmente"
@@ -2288,10 +2293,18 @@ msgstr "guiño"
msgid "{0} added to dictionary"
msgstr "{0} añadido al diccionario"
#: internal/app/application_overrides.go:147
#: internal/app/application_overrides.go:207
msgid "{0} application override selected"
msgstr "Ajustes de aplicación seleccionados para {0}"
#: internal/app/fenrir_menu.go:34
msgid "{0} application settings"
msgstr "Configuración de la aplicación {0}"
#: internal/app/fenrir_menu.go:34 internal/app/remote_persistence.go:131
msgid "{0} application settings saved."
msgstr "Configuración de la aplicación {0} guardada."
#: internal/interaction/presentation.go:53
msgid "{0} button"
msgstr "{0} botón"
+36 -24
View File
@@ -57,11 +57,11 @@ msgstr ""
msgid "Apple"
msgstr ""
#: internal/app/application_overrides.go:237
#: internal/app/application_overrides.go:325
msgid "Application"
msgstr ""
#: internal/app/application_overrides.go:236
#: internal/app/application_overrides.go:324
msgid "Applications"
msgstr ""
@@ -153,7 +153,7 @@ msgstr ""
msgid "Canada"
msgstr ""
#: internal/app/application_overrides.go:238 internal/preferences/settings.go:79
#: internal/app/application_overrides.go:326 internal/preferences/settings.go:93
msgid "Cancel"
msgstr ""
@@ -245,7 +245,7 @@ msgstr ""
msgid "Clipboard"
msgstr ""
#: internal/app/application.go:142 internal/app/application.go:145 internal/app/clipboard.go:218 internal/app/clipboard.go:374
#: internal/app/application.go:144 internal/app/application.go:147 internal/app/clipboard.go:218 internal/app/clipboard.go:374
msgid "Clipboard import failed"
msgstr ""
@@ -277,7 +277,11 @@ msgstr ""
msgid "Could not check spelling: {0}"
msgstr ""
#: internal/app/application_overrides.go:241 internal/app/application_overrides.go:245
#: internal/app/application_overrides.go:278 internal/app/application_overrides.go:83
msgid "Could not load application settings: {0}"
msgstr ""
#: internal/app/application_overrides.go:329 internal/app/application_overrides.go:333
msgid "Could not open Applications: {0}"
msgstr ""
@@ -285,7 +289,7 @@ msgstr ""
msgid "Could not open Emoji Menu: {0}"
msgstr ""
#: internal/app/fenrir_menu.go:32 internal/app/fenrir_menu.go:52 internal/app/fenrir_menu.go:67 internal/app/runtime_settings.go:133
#: internal/app/fenrir_menu.go:40 internal/app/fenrir_menu.go:69 internal/app/fenrir_menu.go:89 internal/app/runtime_settings.go:133
msgid "Could not open Fenrir Menu: {0}"
msgstr ""
@@ -293,7 +297,7 @@ msgstr ""
msgid "Could not open Spellcheck Menu: {0}"
msgstr ""
#: internal/app/application_overrides.go:104 internal/app/application_overrides.go:208
#: internal/app/application_overrides.go:161 internal/app/application_overrides.go:296
msgid "Could not restore application settings: {0}"
msgstr ""
@@ -301,7 +305,7 @@ msgstr ""
msgid "Could not save exceptions: {0}"
msgstr ""
#: internal/app/fenrir_menu.go:174 internal/app/fenrir_menu.go:180 internal/app/runtime_settings.go:172 internal/app/runtime_settings.go:199
#: internal/app/fenrir_menu.go:200 internal/app/fenrir_menu.go:206 internal/app/runtime_settings.go:176 internal/app/runtime_settings.go:204
msgid "Could not save settings: {0}"
msgstr ""
@@ -321,11 +325,11 @@ msgstr ""
msgid "Date format"
msgstr ""
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "Default"
msgstr ""
#: internal/app/application_overrides.go:129 internal/app/application_overrides.go:182
#: internal/app/application_overrides.go:186 internal/app/application_overrides.go:242
msgid "Default settings restored"
msgstr ""
@@ -417,7 +421,7 @@ msgstr ""
msgid "Face with symbols"
msgstr ""
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:155
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:159
msgid "Failed to save settings."
msgstr ""
@@ -461,7 +465,7 @@ msgstr ""
msgid "Food"
msgstr ""
#: internal/app/commands.go:145
#: internal/app/commands.go:143
msgid "Forward next keypress"
msgstr ""
@@ -697,7 +701,7 @@ msgstr ""
msgid "Netherlands"
msgstr ""
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "NetworkManager"
msgstr ""
@@ -705,7 +709,7 @@ msgstr ""
msgid "New channel"
msgstr ""
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "Ninjam"
msgstr ""
@@ -753,7 +757,7 @@ msgstr ""
msgid "Not in window Mode"
msgstr ""
#: internal/preferences/settings.go:79
#: internal/preferences/settings.go:93
msgid "OK"
msgstr ""
@@ -813,11 +817,11 @@ msgstr ""
msgid "Presents the currently selected text that will be copied to the clipboard"
msgstr ""
#: internal/app/commands.go:131
#: internal/app/commands.go:129
msgid "Progress monitoring disabled"
msgstr ""
#: internal/app/commands.go:129
#: internal/app/commands.go:127
msgid "Progress monitoring enabled"
msgstr ""
@@ -969,7 +973,7 @@ msgstr ""
msgid "Santa"
msgstr ""
#: internal/app/runtime_settings.go:151 internal/app/runtime_settings.go:168
#: internal/app/runtime_settings.go:155 internal/app/runtime_settings.go:172
msgid "Save unavailable."
msgstr ""
@@ -993,7 +997,7 @@ msgstr ""
msgid "Screaming"
msgstr ""
#: internal/app/commands.go:154 internal/app/scripts.go
#: internal/app/commands.go:152 internal/app/scripts.go
msgid "Script failed"
msgstr ""
@@ -1041,7 +1045,7 @@ msgstr ""
msgid "Set window begin and end marks"
msgstr ""
#: internal/app/application_overrides.go:114 internal/app/application_overrides.go:227 internal/app/commands.go:84
#: internal/app/application_overrides.go:171 internal/app/application_overrides.go:315 internal/app/commands.go:84
msgid "Settings are busy"
msgstr ""
@@ -1189,11 +1193,11 @@ msgstr ""
msgid "Taco"
msgstr ""
#: internal/preferences/settings.go:72
#: internal/preferences/settings.go:83
msgid "Test synthesizer"
msgstr ""
#: internal/app/fenrir_menu.go:139
#: internal/app/fenrir_menu.go:165
msgid "Testing voice"
msgstr ""
@@ -2085,7 +2089,7 @@ msgstr ""
msgid "speech rate"
msgstr ""
#: internal/app/commands.go:140
#: internal/app/commands.go:138
msgid "speech temporary disabled"
msgstr ""
@@ -2177,10 +2181,18 @@ msgstr ""
msgid "{0} added to dictionary"
msgstr ""
#: internal/app/application_overrides.go:147
#: internal/app/application_overrides.go:207
msgid "{0} application override selected"
msgstr ""
#: internal/app/fenrir_menu.go:34
msgid "{0} application settings"
msgstr ""
#: internal/app/fenrir_menu.go:34 internal/app/remote_persistence.go:131
msgid "{0} application settings saved."
msgstr ""
#: internal/interaction/presentation.go:53
msgid "{0} button"
msgstr ""
+44 -31
View File
@@ -68,11 +68,11 @@ msgstr "Złość"
msgid "Apple"
msgstr "Jabłko"
#: internal/app/application_overrides.go:237
#: internal/app/application_overrides.go:325
msgid "Application"
msgstr "Aplikacja"
#: internal/app/application_overrides.go:236
#: internal/app/application_overrides.go:324
msgid "Applications"
msgstr "Aplikacje"
@@ -164,8 +164,8 @@ msgstr "Motyl"
msgid "Canada"
msgstr "Kanada"
#: internal/app/application_overrides.go:238
#: internal/preferences/settings.go:79
#: internal/app/application_overrides.go:326
#: internal/preferences/settings.go:93
msgid "Cancel"
msgstr "Anuluj"
@@ -257,7 +257,7 @@ msgstr "Wyczyść obszar dynamiczny 9"
msgid "Clipboard"
msgstr "Schowek"
#: internal/app/application.go:142 internal/app/application.go:145
#: internal/app/application.go:144 internal/app/application.go:147
#: internal/app/clipboard.go:218 internal/app/clipboard.go:374
msgid "Clipboard import failed"
msgstr "Importowanie schowka nie powiodło się"
@@ -290,8 +290,13 @@ msgstr "Wyluzowanie"
msgid "Could not check spelling: {0}"
msgstr "Nie można sprawdzić pisowni: {0}"
#: internal/app/application_overrides.go:241
#: internal/app/application_overrides.go:245
#: internal/app/application_overrides.go:278
#: internal/app/application_overrides.go:83
msgid "Could not load application settings: {0}"
msgstr "Nie można wczytać ustawień aplikacji: {0}"
#: internal/app/application_overrides.go:329
#: internal/app/application_overrides.go:333
msgid "Could not open Applications: {0}"
msgstr "Nie można otworzyć menu aplikacji: {0}"
@@ -299,8 +304,8 @@ msgstr "Nie można otworzyć menu aplikacji: {0}"
msgid "Could not open Emoji Menu: {0}"
msgstr "Nie można otworzyć menu emoji: {0}"
#: internal/app/fenrir_menu.go:32 internal/app/fenrir_menu.go:52
#: internal/app/fenrir_menu.go:67 internal/app/runtime_settings.go:133
#: internal/app/fenrir_menu.go:40 internal/app/fenrir_menu.go:69
#: internal/app/fenrir_menu.go:89 internal/app/runtime_settings.go:133
msgid "Could not open Fenrir Menu: {0}"
msgstr "Nie można otworzyć menu Fenrira: {0}"
@@ -308,8 +313,8 @@ msgstr "Nie można otworzyć menu Fenrira: {0}"
msgid "Could not open Spellcheck Menu: {0}"
msgstr "Nie można otworzyć menu sprawdzania pisowni: {0}"
#: internal/app/application_overrides.go:104
#: internal/app/application_overrides.go:208
#: internal/app/application_overrides.go:161
#: internal/app/application_overrides.go:296
msgid "Could not restore application settings: {0}"
msgstr "Nie można przywrócić ustawień aplikacji: {0}"
@@ -317,8 +322,8 @@ msgstr "Nie można przywrócić ustawień aplikacji: {0}"
msgid "Could not save exceptions: {0}"
msgstr "Nie można zapisać wyjątków: {0}"
#: internal/app/fenrir_menu.go:174 internal/app/fenrir_menu.go:180
#: internal/app/runtime_settings.go:172 internal/app/runtime_settings.go:199
#: internal/app/fenrir_menu.go:200 internal/app/fenrir_menu.go:206
#: internal/app/runtime_settings.go:176 internal/app/runtime_settings.go:204
msgid "Could not save settings: {0}"
msgstr "Nie można zapisać ustawień: {0}"
@@ -338,12 +343,12 @@ msgstr "Sztylet"
msgid "Date format"
msgstr "Format daty"
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "Default"
msgstr "Domyślne"
#: internal/app/application_overrides.go:129
#: internal/app/application_overrides.go:182
#: internal/app/application_overrides.go:186
#: internal/app/application_overrides.go:242
msgid "Default settings restored"
msgstr "Przywrócono ustawienia domyślne"
@@ -446,7 +451,7 @@ msgstr ""
msgid "Face with symbols"
msgstr "Twarz z symbolami"
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:155
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:159
msgid "Failed to save settings."
msgstr "Nie udało się zapisać ustawień."
@@ -490,7 +495,7 @@ msgstr "Złożone dłonie"
msgid "Food"
msgstr "Jedzenie"
#: internal/app/commands.go:145
#: internal/app/commands.go:143
msgid "Forward next keypress"
msgstr "Przepuść następny klawisz"
@@ -730,7 +735,7 @@ msgstr "Nudności"
msgid "Netherlands"
msgstr "Holandia"
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "NetworkManager"
msgstr "NetworkManager"
@@ -738,7 +743,7 @@ msgstr "NetworkManager"
msgid "New channel"
msgstr "Nowy kanał"
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "Ninjam"
msgstr "Ninjam"
@@ -791,7 +796,7 @@ msgstr "Poza trybem przeglądu"
msgid "Not in window Mode"
msgstr "Nie znajdujesz się w trybie okna."
#: internal/preferences/settings.go:79
#: internal/preferences/settings.go:93
msgid "OK"
msgstr "OK"
@@ -852,11 +857,11 @@ msgid ""
"Presents the currently selected text that will be copied to the clipboard"
msgstr "Odczytuje zaznaczony tekst, który zostanie skopiowany do schowka"
#: internal/app/commands.go:131
#: internal/app/commands.go:129
msgid "Progress monitoring disabled"
msgstr "Monitorowanie postępu wyłączone"
#: internal/app/commands.go:129
#: internal/app/commands.go:127
msgid "Progress monitoring enabled"
msgstr "Monitorowanie postępu włączone"
@@ -1008,7 +1013,7 @@ msgstr "Smutek"
msgid "Santa"
msgstr "Święty Mikołaj"
#: internal/app/runtime_settings.go:151 internal/app/runtime_settings.go:168
#: internal/app/runtime_settings.go:155 internal/app/runtime_settings.go:172
msgid "Save unavailable."
msgstr "Zapisywanie niedostępne."
@@ -1032,7 +1037,7 @@ msgstr "Mów „błędna pisownia”"
msgid "Screaming"
msgstr "Krzyk"
#: internal/app/commands.go:154 internal/app/scripts.go
#: internal/app/commands.go:152 internal/app/scripts.go
msgid "Script failed"
msgstr "Skrypt nie powiódł się"
@@ -1080,8 +1085,8 @@ msgstr "Ustaw obszar dynamiczny za pomocą jednego lub dwóch znaczników"
msgid "Set window begin and end marks"
msgstr "Ustaw początek i koniec okna."
#: internal/app/application_overrides.go:114
#: internal/app/application_overrides.go:227 internal/app/commands.go:84
#: internal/app/application_overrides.go:171
#: internal/app/application_overrides.go:315 internal/app/commands.go:84
msgid "Settings are busy"
msgstr "Ustawienia są zajęte"
@@ -1229,11 +1234,11 @@ msgstr "Symbole"
msgid "Taco"
msgstr "Taco"
#: internal/preferences/settings.go:72
#: internal/preferences/settings.go:83
msgid "Test synthesizer"
msgstr "Testuj syntezator"
#: internal/app/fenrir_menu.go:139
#: internal/app/fenrir_menu.go:165
msgid "Testing voice"
msgstr "Testowanie głosu"
@@ -2185,7 +2190,7 @@ msgstr "wysokość głosu"
msgid "speech rate"
msgstr "szybkość mowy"
#: internal/app/commands.go:140
#: internal/app/commands.go:138
msgid "speech temporary disabled"
msgstr "Mowa tymczasowo wyłączona."
@@ -2280,10 +2285,18 @@ msgstr "mrugnięcie"
msgid "{0} added to dictionary"
msgstr "Dodano {0} do słownika"
#: internal/app/application_overrides.go:147
#: internal/app/application_overrides.go:207
msgid "{0} application override selected"
msgstr "Wybrano ustawienia aplikacji {0}"
#: internal/app/fenrir_menu.go:34
msgid "{0} application settings"
msgstr "Ustawienia aplikacji {0}"
#: internal/app/fenrir_menu.go:34 internal/app/remote_persistence.go:131
msgid "{0} application settings saved."
msgstr "Ustawienia aplikacji {0} zapisane."
#: internal/interaction/presentation.go:53
msgid "{0} button"
msgstr "{0} przycisk"
+44 -31
View File
@@ -66,11 +66,11 @@ msgstr "Zangado"
msgid "Apple"
msgstr "Maçã"
#: internal/app/application_overrides.go:237
#: internal/app/application_overrides.go:325
msgid "Application"
msgstr "Aplicativo"
#: internal/app/application_overrides.go:236
#: internal/app/application_overrides.go:324
msgid "Applications"
msgstr "Aplicativos"
@@ -162,8 +162,8 @@ msgstr "Borboleta"
msgid "Canada"
msgstr "Canadá"
#: internal/app/application_overrides.go:238
#: internal/preferences/settings.go:79
#: internal/app/application_overrides.go:326
#: internal/preferences/settings.go:93
msgid "Cancel"
msgstr "Cancelar"
@@ -255,7 +255,7 @@ msgstr "Limpar região dinâmica 9"
msgid "Clipboard"
msgstr "Área de transferência"
#: internal/app/application.go:142 internal/app/application.go:145
#: internal/app/application.go:144 internal/app/application.go:147
#: internal/app/clipboard.go:218 internal/app/clipboard.go:374
msgid "Clipboard import failed"
msgstr "Falha ao importar a área de transferência"
@@ -288,8 +288,13 @@ msgstr "Legal"
msgid "Could not check spelling: {0}"
msgstr "Não foi possível verificar a ortografia: {0}"
#: internal/app/application_overrides.go:241
#: internal/app/application_overrides.go:245
#: internal/app/application_overrides.go:278
#: internal/app/application_overrides.go:83
msgid "Could not load application settings: {0}"
msgstr "Não foi possível carregar as configurações do aplicativo: {0}"
#: internal/app/application_overrides.go:329
#: internal/app/application_overrides.go:333
msgid "Could not open Applications: {0}"
msgstr "Não foi possível abrir Aplicativos: {0}"
@@ -297,8 +302,8 @@ msgstr "Não foi possível abrir Aplicativos: {0}"
msgid "Could not open Emoji Menu: {0}"
msgstr "Não foi possível abrir o menu de emojis: {0}"
#: internal/app/fenrir_menu.go:32 internal/app/fenrir_menu.go:52
#: internal/app/fenrir_menu.go:67 internal/app/runtime_settings.go:133
#: internal/app/fenrir_menu.go:40 internal/app/fenrir_menu.go:69
#: internal/app/fenrir_menu.go:89 internal/app/runtime_settings.go:133
msgid "Could not open Fenrir Menu: {0}"
msgstr "Não foi possível abrir o menu do Fenrir: {0}"
@@ -306,8 +311,8 @@ msgstr "Não foi possível abrir o menu do Fenrir: {0}"
msgid "Could not open Spellcheck Menu: {0}"
msgstr "Não foi possível abrir o menu de ortografia: {0}"
#: internal/app/application_overrides.go:104
#: internal/app/application_overrides.go:208
#: internal/app/application_overrides.go:161
#: internal/app/application_overrides.go:296
msgid "Could not restore application settings: {0}"
msgstr "Não foi possível restaurar as configurações do aplicativo: {0}"
@@ -315,8 +320,8 @@ msgstr "Não foi possível restaurar as configurações do aplicativo: {0}"
msgid "Could not save exceptions: {0}"
msgstr "Não foi possível salvar as exceções: {0}"
#: internal/app/fenrir_menu.go:174 internal/app/fenrir_menu.go:180
#: internal/app/runtime_settings.go:172 internal/app/runtime_settings.go:199
#: internal/app/fenrir_menu.go:200 internal/app/fenrir_menu.go:206
#: internal/app/runtime_settings.go:176 internal/app/runtime_settings.go:204
msgid "Could not save settings: {0}"
msgstr "Não foi possível salvar as configurações: {0}"
@@ -336,12 +341,12 @@ msgstr "Adaga"
msgid "Date format"
msgstr "Formato da data"
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "Default"
msgstr "Padrão"
#: internal/app/application_overrides.go:129
#: internal/app/application_overrides.go:182
#: internal/app/application_overrides.go:186
#: internal/app/application_overrides.go:242
msgid "Default settings restored"
msgstr "Configurações padrão restauradas"
@@ -446,7 +451,7 @@ msgstr ""
msgid "Face with symbols"
msgstr "Rosto com símbolos"
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:155
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:159
msgid "Failed to save settings."
msgstr "Falha ao salvar as configurações."
@@ -490,7 +495,7 @@ msgstr "Mãos juntas"
msgid "Food"
msgstr "Comida"
#: internal/app/commands.go:145
#: internal/app/commands.go:143
msgid "Forward next keypress"
msgstr "Avançar próxima tecla pressionada"
@@ -732,7 +737,7 @@ msgstr "Enjoado"
msgid "Netherlands"
msgstr "Países Baixos"
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "NetworkManager"
msgstr "NetworkManager"
@@ -740,7 +745,7 @@ msgstr "NetworkManager"
msgid "New channel"
msgstr "Novo canal"
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "Ninjam"
msgstr "Ninjam"
@@ -794,7 +799,7 @@ msgstr "Fora do modo de revisão"
msgid "Not in window Mode"
msgstr "Não no Modo de Janela"
#: internal/preferences/settings.go:79
#: internal/preferences/settings.go:93
msgid "OK"
msgstr "OK"
@@ -856,11 +861,11 @@ msgid ""
msgstr ""
"Apresenta o texto selecionado que será copiado para a área de transferência"
#: internal/app/commands.go:131
#: internal/app/commands.go:129
msgid "Progress monitoring disabled"
msgstr "Monitoramento de progresso desativado"
#: internal/app/commands.go:129
#: internal/app/commands.go:127
msgid "Progress monitoring enabled"
msgstr "Monitoramento de progresso ativado"
@@ -1012,7 +1017,7 @@ msgstr "Triste"
msgid "Santa"
msgstr "Papai Noel"
#: internal/app/runtime_settings.go:151 internal/app/runtime_settings.go:168
#: internal/app/runtime_settings.go:155 internal/app/runtime_settings.go:172
msgid "Save unavailable."
msgstr "Salvamento indisponível."
@@ -1036,7 +1041,7 @@ msgstr "Dizer “erro ortográfico”"
msgid "Screaming"
msgstr "Gritando"
#: internal/app/commands.go:154 internal/app/scripts.go
#: internal/app/commands.go:152 internal/app/scripts.go
msgid "Script failed"
msgstr "O script falhou"
@@ -1084,8 +1089,8 @@ msgstr "Definir uma região dinâmica a partir de uma ou duas marcas"
msgid "Set window begin and end marks"
msgstr "Definir marcas de início e fim da janela"
#: internal/app/application_overrides.go:114
#: internal/app/application_overrides.go:227 internal/app/commands.go:84
#: internal/app/application_overrides.go:171
#: internal/app/application_overrides.go:315 internal/app/commands.go:84
msgid "Settings are busy"
msgstr "As configurações estão ocupadas"
@@ -1233,11 +1238,11 @@ msgstr "Símbolos"
msgid "Taco"
msgstr "Taco"
#: internal/preferences/settings.go:72
#: internal/preferences/settings.go:83
msgid "Test synthesizer"
msgstr "Testar sintetizador"
#: internal/app/fenrir_menu.go:139
#: internal/app/fenrir_menu.go:165
msgid "Testing voice"
msgstr "Testando a voz"
@@ -2198,7 +2203,7 @@ msgstr "tom da fala"
msgid "speech rate"
msgstr "velocidade da fala"
#: internal/app/commands.go:140
#: internal/app/commands.go:138
msgid "speech temporary disabled"
msgstr "fala temporariamente desativada"
@@ -2293,10 +2298,18 @@ msgstr "piscadela"
msgid "{0} added to dictionary"
msgstr "{0} adicionado ao dicionário"
#: internal/app/application_overrides.go:147
#: internal/app/application_overrides.go:207
msgid "{0} application override selected"
msgstr "Configurações do aplicativo {0} selecionadas"
#: internal/app/fenrir_menu.go:34
msgid "{0} application settings"
msgstr "Configurações do aplicativo {0}"
#: internal/app/fenrir_menu.go:34 internal/app/remote_persistence.go:131
msgid "{0} application settings saved."
msgstr "Configurações do aplicativo {0} salvas."
#: internal/interaction/presentation.go:53
msgid "{0} button"
msgstr "{0} botão"
+44 -31
View File
@@ -65,11 +65,11 @@ msgstr "Злость"
msgid "Apple"
msgstr "Яблоко"
#: internal/app/application_overrides.go:237
#: internal/app/application_overrides.go:325
msgid "Application"
msgstr "Приложение"
#: internal/app/application_overrides.go:236
#: internal/app/application_overrides.go:324
msgid "Applications"
msgstr "Приложения"
@@ -161,8 +161,8 @@ msgstr "Бабочка"
msgid "Canada"
msgstr "Канада"
#: internal/app/application_overrides.go:238
#: internal/preferences/settings.go:79
#: internal/app/application_overrides.go:326
#: internal/preferences/settings.go:93
msgid "Cancel"
msgstr "Отмена"
@@ -254,7 +254,7 @@ msgstr "Очистить динамическую область 9"
msgid "Clipboard"
msgstr "Буфер обмена"
#: internal/app/application.go:142 internal/app/application.go:145
#: internal/app/application.go:144 internal/app/application.go:147
#: internal/app/clipboard.go:218 internal/app/clipboard.go:374
msgid "Clipboard import failed"
msgstr "Не удалось импортировать буфер обмена"
@@ -287,8 +287,13 @@ msgstr "Круто"
msgid "Could not check spelling: {0}"
msgstr "Не удалось проверить правописание: {0}"
#: internal/app/application_overrides.go:241
#: internal/app/application_overrides.go:245
#: internal/app/application_overrides.go:278
#: internal/app/application_overrides.go:83
msgid "Could not load application settings: {0}"
msgstr "Не удалось загрузить настройки приложения: {0}"
#: internal/app/application_overrides.go:329
#: internal/app/application_overrides.go:333
msgid "Could not open Applications: {0}"
msgstr "Не удалось открыть меню приложений: {0}"
@@ -296,8 +301,8 @@ msgstr "Не удалось открыть меню приложений: {0}"
msgid "Could not open Emoji Menu: {0}"
msgstr "Не удалось открыть меню эмодзи: {0}"
#: internal/app/fenrir_menu.go:32 internal/app/fenrir_menu.go:52
#: internal/app/fenrir_menu.go:67 internal/app/runtime_settings.go:133
#: internal/app/fenrir_menu.go:40 internal/app/fenrir_menu.go:69
#: internal/app/fenrir_menu.go:89 internal/app/runtime_settings.go:133
msgid "Could not open Fenrir Menu: {0}"
msgstr "Не удалось открыть меню Fenrir: {0}"
@@ -305,8 +310,8 @@ msgstr "Не удалось открыть меню Fenrir: {0}"
msgid "Could not open Spellcheck Menu: {0}"
msgstr "Не удалось открыть меню проверки правописания: {0}"
#: internal/app/application_overrides.go:104
#: internal/app/application_overrides.go:208
#: internal/app/application_overrides.go:161
#: internal/app/application_overrides.go:296
msgid "Could not restore application settings: {0}"
msgstr "Не удалось восстановить настройки приложения: {0}"
@@ -314,8 +319,8 @@ msgstr "Не удалось восстановить настройки прил
msgid "Could not save exceptions: {0}"
msgstr "Не удалось сохранить исключения: {0}"
#: internal/app/fenrir_menu.go:174 internal/app/fenrir_menu.go:180
#: internal/app/runtime_settings.go:172 internal/app/runtime_settings.go:199
#: internal/app/fenrir_menu.go:200 internal/app/fenrir_menu.go:206
#: internal/app/runtime_settings.go:176 internal/app/runtime_settings.go:204
msgid "Could not save settings: {0}"
msgstr "Не удалось сохранить настройки: {0}"
@@ -335,12 +340,12 @@ msgstr "Кинжал"
msgid "Date format"
msgstr "Формат даты"
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "Default"
msgstr "По умолчанию"
#: internal/app/application_overrides.go:129
#: internal/app/application_overrides.go:182
#: internal/app/application_overrides.go:186
#: internal/app/application_overrides.go:242
msgid "Default settings restored"
msgstr "Настройки по умолчанию восстановлены"
@@ -442,7 +447,7 @@ msgstr "Экспорт текущего буфера обмена fenrir в бу
msgid "Face with symbols"
msgstr "Лицо с символами"
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:155
#: internal/app/quick_settings.go:295 internal/app/runtime_settings.go:159
msgid "Failed to save settings."
msgstr "Не удалось сохранить настройки."
@@ -486,7 +491,7 @@ msgstr "Сложенные ладони"
msgid "Food"
msgstr "Еда"
#: internal/app/commands.go:145
#: internal/app/commands.go:143
msgid "Forward next keypress"
msgstr "Переслать следующую клавишу."
@@ -725,7 +730,7 @@ msgstr "Тошнота"
msgid "Netherlands"
msgstr "Нидерланды"
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "NetworkManager"
msgstr "NetworkManager"
@@ -733,7 +738,7 @@ msgstr "NetworkManager"
msgid "New channel"
msgstr "Новый канал"
#: internal/app/application_overrides.go:230
#: internal/app/application_overrides.go:318
msgid "Ninjam"
msgstr "Ninjam"
@@ -786,7 +791,7 @@ msgstr "Нет в режиме просмотра."
msgid "Not in window Mode"
msgstr "Не в режиме окна."
#: internal/preferences/settings.go:79
#: internal/preferences/settings.go:93
msgid "OK"
msgstr "ОК"
@@ -848,11 +853,11 @@ msgid ""
msgstr ""
"Сообщает текущий выбранный текст, который будет скопирован в буфер обмена."
#: internal/app/commands.go:131
#: internal/app/commands.go:129
msgid "Progress monitoring disabled"
msgstr "Отслеживание хода выполнения отключено"
#: internal/app/commands.go:129
#: internal/app/commands.go:127
msgid "Progress monitoring enabled"
msgstr "Отслеживание хода выполнения включено"
@@ -1004,7 +1009,7 @@ msgstr "Грусть"
msgid "Santa"
msgstr "Дед Мороз"
#: internal/app/runtime_settings.go:151 internal/app/runtime_settings.go:168
#: internal/app/runtime_settings.go:155 internal/app/runtime_settings.go:172
msgid "Save unavailable."
msgstr "Сохранение недоступно."
@@ -1028,7 +1033,7 @@ msgstr "Говорить «ошибка»"
msgid "Screaming"
msgstr "Крик"
#: internal/app/commands.go:154 internal/app/scripts.go
#: internal/app/commands.go:152 internal/app/scripts.go
msgid "Script failed"
msgstr "Ошибка сценария"
@@ -1076,8 +1081,8 @@ msgstr "Задать динамическую область по одной и
msgid "Set window begin and end marks"
msgstr "Установка начала и конца окна"
#: internal/app/application_overrides.go:114
#: internal/app/application_overrides.go:227 internal/app/commands.go:84
#: internal/app/application_overrides.go:171
#: internal/app/application_overrides.go:315 internal/app/commands.go:84
msgid "Settings are busy"
msgstr "Настройки заняты"
@@ -1225,11 +1230,11 @@ msgstr "Символы"
msgid "Taco"
msgstr "Тако"
#: internal/preferences/settings.go:72
#: internal/preferences/settings.go:83
msgid "Test synthesizer"
msgstr "Проверить синтезатор"
#: internal/app/fenrir_menu.go:139
#: internal/app/fenrir_menu.go:165
msgid "Testing voice"
msgstr "Проверка голоса"
@@ -2182,7 +2187,7 @@ msgstr "высота голоса"
msgid "speech rate"
msgstr "скорость речи"
#: internal/app/commands.go:140
#: internal/app/commands.go:138
msgid "speech temporary disabled"
msgstr "Речь временно отключена"
@@ -2277,10 +2282,18 @@ msgstr "подмигивание"
msgid "{0} added to dictionary"
msgstr "{0} добавлено в словарь"
#: internal/app/application_overrides.go:147
#: internal/app/application_overrides.go:207
msgid "{0} application override selected"
msgstr "Выбраны настройки приложения {0}"
#: internal/app/fenrir_menu.go:34
msgid "{0} application settings"
msgstr "Настройки приложения {0}"
#: internal/app/fenrir_menu.go:34 internal/app/remote_persistence.go:131
msgid "{0} application settings saved."
msgstr "Настройки приложения {0} сохранены."
#: internal/interaction/presentation.go:53
msgid "{0} button"
msgstr "{0}, кнопка"
+34
View File
@@ -0,0 +1,34 @@
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package preferences
import (
"git.stormux.org/storm/fenrir/internal/config"
"git.stormux.org/storm/fenrir/internal/interaction"
"testing"
)
func TestApplicationSettingsOnlyExposeRestorableControls(t *testing.T) {
base := config.Defaults()
session, err := New_application_session(base, nil, "Ninjam application settings")
if err != nil {
t.Fatal(err)
}
values := session.Values()
for _, path := range []string{"presentation.speech.driver", "presentation.sound.theme", "keyboard.layout", "keyboard.devices", "reading.punctuation_profile", "clipboard.persistent"} {
if _, exists := values[path]; exists {
t.Fatal("startup/global control exposed", path)
}
}
if _, exists := values["presentation.speech.voice"]; !exists {
t.Fatal("voice control missing")
}
session.Handle(interaction.Key{Name: "tab"})
session.Handle(interaction.Key{Name: "tab"})
session.Handle(interaction.Key{Name: "up"})
saved, err := Application_settings(session, base)
if err != nil || saved.Presentation.Speech.Rate != base.Presentation.Speech.Rate+1 || saved.Keyboard.Layout != base.Keyboard.Layout {
t.Fatal(saved, err)
}
}
+3
View File
@@ -119,6 +119,9 @@ func (d Discovery) Refresh_speech(session *interaction.Session, previous *config
"presentation.speech.voice": &next.Presentation.Speech.Voice,
} {
control, ok := values[path]
if !ok && path == "presentation.speech.driver" {
continue
}
if !ok || control.Selected < 0 || control.Selected >= len(control.Choices) {
return false
}
+29 -5
View File
@@ -38,10 +38,18 @@ type Choices map[string][]config.Choice
const MenuTitle = "Fenrir Menu"
func New_session(settings config.Settings, order []string, discovered ...Choices) (*interaction.Session, error) {
return new_session(settings, order, MenuTitle, false, discovered...)
}
func New_application_session(settings config.Settings, order []string, title string, discovered ...Choices) (*interaction.Session, error) {
return new_session(settings, order, title, true, discovered...)
}
func new_session(settings config.Settings, order []string, title string, application bool, discovered ...Choices) (*interaction.Session, error) {
if err := settings.Validate(); err != nil {
return nil, err
}
form := interaction.Form{Type: interaction.TabForm, Title: MenuTitle}
form := interaction.Form{Type: interaction.TabForm, Title: title}
for _, section := range config.Ordered_sections(config.Sections(), order) {
if section.Label == "" {
continue
@@ -50,6 +58,9 @@ func New_session(settings config.Settings, order []string, discovered ...Choices
var append_section func(config.Section, string) error
append_section = func(section config.Section, group string) error {
for _, definition := range section.Settings {
if application && !config.Application_setting(definition.Path) {
continue
}
control, err := make_control(definition, settings, discovered)
if err != nil {
return err
@@ -76,6 +87,9 @@ func New_session(settings config.Settings, order []string, discovered ...Choices
if err := append_section(section, ""); err != nil {
return nil, err
}
if len(tab.Controls) == 0 {
continue
}
tab.Controls = append(tab.Controls, interaction.Control{ID: "ok", Label: "OK", Kind: interaction.Button, Action: "save"}, interaction.Control{ID: "cancel", Label: "Cancel", Kind: interaction.Button, Action: "cancel"})
form.Tabs = append(form.Tabs, tab)
}
@@ -137,15 +151,25 @@ func make_control(definition config.Definition, settings config.Settings, discov
}
func Settings(session *interaction.Session, base config.Settings) (config.Settings, error) {
return settings_values(session, base, false)
}
func Application_settings(session *interaction.Session, base config.Settings) (config.Settings, error) {
return settings_values(session, base, true)
}
func settings_values(session *interaction.Session, base config.Settings, application bool) (config.Settings, error) {
values := session.Values()
// Visibility follows the form's selected driver, including unsaved switches.
driver := values["presentation.speech.driver"]
if driver.Selected < 0 || driver.Selected >= len(driver.Choices) {
driver, hasDriver := values["presentation.speech.driver"]
if (!application && !hasDriver) || (hasDriver && (driver.Selected < 0 || driver.Selected >= len(driver.Choices))) {
return config.Settings{}, fmt.Errorf("invalid speech driver selection")
}
base.Presentation.Speech.Driver = driver.Choices[driver.Selected].ID
if hasDriver {
base.Presentation.Speech.Driver = driver.Choices[driver.Selected].ID
}
for _, definition := range config.Definitions() {
if !definition.Available(base) {
if !definition.Available(base) || (application && !config.Application_setting(definition.Path)) {
continue
}
control, ok := values[definition.Path]