Coverage Report

Created: 2026-10-23 22:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
super-stt-app/src/core/app/init.rs
Line
Count
Source
1
// SPDX-License-Identifier: GPL-3.0-only
2
3
use crate::daemon::client::load_audio_themes;
4
use crate::state::{AudioTheme, ContextPage, DaemonStatus, RecordingStatus};
5
use crate::ui::icons;
6
use crate::ui::messages::{Message, ModelMessage, RecordingMessage};
7
use cosmic::prelude::*;
8
use cosmic::widget::nav_bar;
9
use std::collections::HashMap;
10
11
use super::{AppModel, DeviceState};
12
13
/// Builds the navigation bar with all Super STT pages inserted in order.
14
0
fn build_nav() -> nav_bar::Model {
15
0
    let mut nav = nav_bar::Model::default();
16
17
    // Models is the primary page (the active backend) — first in the rail and
18
    // active on launch. Library (manage/install backends) sits directly below.
19
0
    nav.insert()
20
0
        .text("Models")
21
0
        .data::<crate::state::Page>(crate::state::Page::Models)
22
0
        .icon(icons::phosphor(icons::BRAIN))
23
0
        .activate();
24
25
0
    nav.insert()
26
0
        .text("Library")
27
0
        .data::<crate::state::Page>(crate::state::Page::Library)
28
0
        .icon(icons::phosphor(icons::BOOKS));
29
30
0
    nav.insert()
31
0
        .text("Contexts")
32
0
        .data::<crate::state::Page>(crate::state::Page::Contexts)
33
0
        .icon(icons::phosphor(icons::CARDS));
34
35
0
    nav.insert()
36
0
        .text("Customization")
37
0
        .data::<crate::state::Page>(crate::state::Page::Customization)
38
0
        .icon(icons::phosphor(icons::GEAR));
39
40
0
    nav.insert()
41
0
        .text("Recording")
42
0
        .data::<crate::state::Page>(crate::state::Page::Recording)
43
0
        .icon(icons::phosphor(icons::MICROPHONE));
44
45
0
    nav.insert()
46
0
        .text("Input Simulation")
47
0
        .data::<crate::state::Page>(crate::state::Page::InputSimulation)
48
0
        .icon(icons::phosphor(icons::KEYBOARD));
49
50
0
    nav.insert()
51
0
        .text("Connection")
52
0
        .data::<crate::state::Page>(crate::state::Page::Connection)
53
0
        .icon(icons::phosphor(icons::PLUG));
54
55
0
    nav.insert()
56
0
        .text("Updates")
57
0
        .data::<crate::state::Page>(crate::state::Page::Updates)
58
0
        .icon(icons::phosphor(icons::ARROWS_CLOCKWISE));
59
60
0
    nav
61
0
}
62
63
/// Builds the initial batch of startup tasks (audio themes, daemon ping, data load).
64
0
fn initial_load_tasks(
65
0
    title_command: Task<cosmic::Action<Message>>,
66
0
) -> Task<cosmic::Action<Message>> {
67
    // Load audio themes on startup (always available)
68
0
    let load_themes = Task::perform(load_audio_themes(), |themes| {
69
0
        cosmic::Action::App(Message::Recording(RecordingMessage::AudioThemesLoaded(
70
0
            themes,
71
0
        )))
72
0
    });
73
74
    // Try to ping the daemon on startup
75
0
    let initial_ping = crate::core::app::handlers::tasks::ping_task();
76
77
    // Load initial data (models + device info) on startup
78
0
    let load_initial_data = Task::perform(
79
0
        async move {
80
            // Small delay to let daemon connection establish
81
0
            tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
82
0
        },
83
0
        |()| cosmic::Action::App(Message::Model(ModelMessage::LoadInitialData)),
84
    );
85
86
0
    Task::batch([title_command, load_themes, initial_ping, load_initial_data])
87
0
}
88
89
impl AppModel {
90
    /// Initializes the application with any given flags and startup commands.
91
0
    pub(super) fn init_model(
92
0
        core: cosmic::Core,
93
0
        _flags: crate::Flags,
94
0
    ) -> (Self, Task<cosmic::Action<Message>>) {
95
0
        let nav = build_nav();
96
97
        // Construct the app model with the runtime's core.
98
0
        let mut app = AppModel {
99
0
            core,
100
0
            context_page: ContextPage::default(),
101
0
            contexts: crate::state::contexts::ContextsState::default(),
102
0
            nav,
103
0
            // Initialize Super STT state using proper socket path
104
0
            socket_path: super_stt_shared::validation::get_http_socket_path(),
105
0
            daemon_status: DaemonStatus::Disconnected,
106
0
            reconnect_retry: super_stt_shared::daemon::retry::RetryStrategy::for_initial_connection(
107
0
            ),
108
0
            recording_status: RecordingStatus::Idle,
109
0
            transcription_text: String::new(),
110
0
            preview_text: String::new(),
111
0
            preview_source: None,
112
0
            audio_level: 0.0,
113
0
            is_speech_detected: false,
114
0
            audio_themes: Vec::new(),
115
0
            selected_audio_theme: AudioTheme::default(),
116
0
            last_non_silent_theme: AudioTheme::default(),
117
0
            udp_restart_counter: 0,
118
0
            last_udp_data: std::time::Instant::now(),
119
0
120
0
            // Initialize model state
121
0
            current_model: String::new(),
122
0
            current_source: String::new(),
123
0
            current_model_epoch: 0,
124
0
            model_operations: crate::state::model_operations::ModelOperations::opening(
125
0
                "Loading initial model state...".to_string(),
126
0
            ),
127
0
128
0
            // Initialize device state
129
0
            current_device: String::new(), // Empty until loaded from daemon
130
0
            gpu_info: Vec::new(),
131
0
            device_state: DeviceState::Ready,
132
0
            last_event_timestamp: None,
133
0
134
0
            // Initialize preview typing state (disabled by default as beta feature)
135
0
            preview_typing_enabled: false,
136
0
            // Replaced by the daemon's own state as soon as the settings load
137
0
            // completes; the default is the daemon's default too (off).
138
0
            post_processor: crate::daemon::client::StageState::default(),
139
0
            staged_picks: crate::state::staged_picks::StagedPicks::default(),
140
0
            device_offers: crate::state::device_offers::DeviceOffers::default(),
141
0
            stage_catalog: crate::state::stage_catalog::StageCatalog::default(),
142
0
            recording_stop_mode:
143
0
                super_stt_shared::models::recording_stop_mode::RecordingStopMode::default(),
144
0
            write_method: super_stt_shared::models::write_method::WriteMethod::default(),
145
0
            write_method_test_text: String::new(),
146
0
            resolved_write_method: None,
147
0
            write_method_test_countdown: None,
148
0
            notification_method:
149
0
                super_stt_shared::models::notification_method::NotificationMethod::default(),
150
0
            volume: 100,
151
0
            last_committed_volume: 100,
152
0
153
0
            // Custom models directory
154
0
            custom_models_dir: None,
155
0
            custom_models_dir_input: String::new(),
156
0
157
0
            // Models page UI state
158
0
            models_page: crate::state::models_page::ModelsPageState::default(),
159
0
160
0
            // Transcription language state
161
0
            language: crate::state::language::LanguageState::default(),
162
0
163
0
            // Backend catalog + per-backend configuration state
164
0
            backends: Vec::new(),
165
0
            backend_secret_inputs: HashMap::new(),
166
0
            backend_secret_configured: HashMap::new(),
167
0
            backend_option_inputs: HashMap::new(),
168
0
169
0
            // Registry state
170
0
            registry: crate::state::registry::RegistryState::default(),
171
0
172
0
            // Self-update state
173
0
            update: crate::state::update::UpdateState::default(),
174
0
175
0
            // No pending scoped action error at startup.
176
0
            action_error: None,
177
0
178
0
            // Measured once the window opens.
179
0
            #[cfg(target_os = "macos")]
180
0
            title_bar: super::macos::TitleBar::default(),
181
0
182
0
            // The look `main` gave the app its theme from.
183
0
            #[cfg(target_os = "macos")]
184
0
            look: super::macos::appearance::launch_look(),
185
0
186
0
            #[cfg(target_os = "macos")]
187
0
            daemon_agent: None,
188
0
        };
189
190
        // On macOS the native frame and the app's own toolbar replace
191
        // libcosmic's header bar; see `macos`.
192
        //
193
        // libcosmic also draws a window edge — a 1px border, rounded corners,
194
        // and a transparent backdrop outside them — for compositors that
195
        // leave that to the client. It skips all three for a maximized
196
        // window, so the app tells it the window is maximized: the native
197
        // frame draws the edge. Only Wayland ever resets the flag.
198
        #[cfg(target_os = "macos")]
199
        {
200
            app.core.window.show_headerbar = false;
201
            app.core.window.is_maximized = true;
202
        }
203
204
        // Opened from `Super STT.app`, the app is what installs the daemon;
205
        // see `macos::bundle`.
206
        #[cfg(target_os = "macos")]
207
        if super::macos::bundle::in_bundle() {
208
            super::macos::bundle::request_notification_permission();
209
            std::thread::spawn(super::macos::bundle::register_agents);
210
        }
211
212
        // Create startup commands
213
0
        let title_command = app.update_title();
214
0
        (app, initial_load_tasks(title_command))
215
0
    }
216
}