Coverage Report

Created: 2026-11-04 02:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
super-tts-app/src/daemon/client/v1/settings/audio_theme.rs
Line
Count
Source
1
// SPDX-License-Identifier: GPL-3.0-only
2
//! `/settings/audio_theme` — audio-cue theme selection, listing, and audition.
3
//!
4
//! The cues are the short tones that mark the start and end of an utterance,
5
//! not the speech itself: choosing `silent` here does not silence synthesis.
6
//! How loud they play is [`super::volume`], a separate setting because a user
7
//! who wants quieter cues usually does not want different ones.
8
//!
9
//! Three paths sit in this one file because they are one subject and each is
10
//! small: what is selected, what may be selected, and playing the selection
11
//! once so the user can hear it.
12
13
use crate::daemon::client::internal::response::{require_message, require_success};
14
use crate::daemon::client::internal::session::with_settings_token;
15
use crate::state::AudioTheme;
16
use super_tts_shared::daemon::http_client::HttpResult;
17
use super_tts_shared::daemon::http_client::transport;
18
19
/// The themes on offer, falling back to the built-in set when the daemon
20
/// cannot be reached.
21
///
22
/// The fallback is what keeps the Customization page usable while the daemon is
23
/// down: the picker still renders, and the user's existing choice still shows,
24
/// where an empty dropdown would read as "this build has no themes".
25
0
pub async fn load_audio_themes() -> Vec<AudioTheme> {
26
0
    list_available_audio_themes()
27
0
        .await
28
0
        .unwrap_or_else(|_| AudioTheme::all_themes())
29
0
}
30
31
/// List available audio themes (HTTP `GET /settings/audio_theme/list`).
32
///
33
/// Read from the daemon rather than hard-coded here so a build that adds a
34
/// theme does not need the app rebuilt to offer it. A token the app does not
35
/// recognize is dropped rather than failing the list — an unknown theme is one
36
/// this app is too old to render a label for, not a broken response.
37
0
pub async fn list_available_audio_themes() -> HttpResult<Vec<AudioTheme>> {
38
0
    with_settings_token(|socket, token| async move {
39
0
        let resp = require_success(
40
0
            transport::settings_get(socket, &token, "/settings/audio_theme/list").await?,
41
0
            "list_themes",
42
0
        )?;
43
0
        let themes = resp
44
0
            .available_audio_themes
45
0
            .unwrap_or_default()
46
0
            .into_iter()
47
0
            .map(|t| t.to_string())
48
0
            .filter_map(|s| s.parse::<AudioTheme>().ok())
49
0
            .collect();
50
0
        Ok(themes)
51
0
    })
52
0
    .await
53
0
}
54
55
/// Read the configured audio-cue theme (HTTP `GET /settings/audio_theme`).
56
///
57
/// `silent` is a real selection, not an absent one: it means the user chose to
58
/// hear no cues, which is a different state from the volume being turned down.
59
0
pub async fn get_current_audio_theme() -> HttpResult<AudioTheme> {
60
0
    with_settings_token(|socket, token| async move {
61
0
        let resp = require_success(
62
0
            transport::settings_get(socket, &token, "/settings/audio_theme").await?,
63
0
            "get_audio_theme",
64
0
        )?;
65
0
        Ok(resp
66
0
            .audio_theme
67
0
            .unwrap_or_default()
68
0
            .parse()
69
0
            .unwrap_or_default())
70
0
    })
71
0
    .await
72
0
}
73
74
/// Select a theme without playing it (HTTP `POST /settings/audio_theme`).
75
///
76
/// Returns the daemon's confirmation text, which the Customization page shows
77
/// as its save feedback. An unknown token is rejected by the daemon rather than
78
/// quietly falling back to the default, so a typo cannot look like success.
79
0
pub async fn set_audio_theme(theme: AudioTheme) -> HttpResult<String> {
80
0
    let theme_str = theme.to_string().to_lowercase();
81
0
    with_settings_token(move |socket, token| {
82
0
        let theme_str = theme_str.clone();
83
0
        async move {
84
0
            let resp = transport::settings_post(
85
0
                socket,
86
0
                &token,
87
0
                "/settings/audio_theme",
88
0
                &serde_json::json!({ "theme": theme_str }),
89
0
            )
90
0
            .await?;
91
0
            require_message(resp, "set_theme")
92
0
        }
93
0
    })
94
0
    .await
95
0
}
96
97
/// Select a theme and audition it
98
/// (`POST /settings/audio_theme` then `POST /settings/audio_theme/test`).
99
///
100
/// Two requests, in that order, because the test plays the *selected* theme:
101
/// auditioning first would play the old one. The sound comes out of the host
102
/// the daemon runs on, so a remote caller gets a success nobody hears.
103
0
pub async fn set_and_test_audio_theme(theme: AudioTheme) -> HttpResult<String> {
104
0
    set_audio_theme(theme).await?;
105
0
    with_settings_token(|socket, token| async move {
106
0
        let resp = transport::settings_post(
107
0
            socket,
108
0
            &token,
109
0
            "/settings/audio_theme/test",
110
0
            &serde_json::json!({}),
111
0
        )
112
0
        .await?;
113
0
        require_message(resp, "test_theme")
114
0
    })
115
0
    .await
116
0
}