Skip to main content

par_term/app/
mod.rs

1//! Application module for par-term
2//!
3//! This module contains the main application logic, including:
4//! - `App`: Entry point that initializes and runs the event loop
5//! - `WindowManager`: Manages multiple windows and coordinates menu events
6//! - `WindowState`: Per-window state including terminal, renderer, and UI
7
8use crate::cli::RuntimeOptions;
9use crate::config::Config;
10use anyhow::Result;
11use std::sync::Arc;
12use tokio::runtime::Runtime;
13use winit::event_loop::{ControlFlow, EventLoop};
14/// Events injected into the winit event loop by platform observers and the
15/// UI-test driver.
16#[derive(Debug, Clone)]
17pub(crate) enum AppEvent {
18    /// The macOS display topology or parameters changed.
19    #[cfg(target_os = "macos")]
20    DisplayConfigurationChanged,
21    /// One scripted `--ui-test` step, ready to execute.
22    UiTestStep(ui_test::UiTestStep),
23    /// The script ran out; write the report and exit.
24    UiTestFinish,
25}
26
27pub(crate) mod display_recovery;
28
29pub(crate) mod copy_mode;
30mod file_transfers;
31pub mod handler;
32pub mod input_events;
33pub mod mouse_events;
34pub(crate) mod pane_hint_select;
35pub(crate) mod render_pipeline;
36pub mod tab_ops;
37mod tmux_handler;
38mod triggers;
39pub(crate) mod ui_test;
40pub mod window_manager;
41pub mod window_state;
42
43pub(crate) use window_manager::WindowManager;
44
45/// Main application entry point
46pub struct App {
47    config: Config,
48    runtime: Arc<Runtime>,
49    runtime_options: RuntimeOptions,
50}
51
52impl App {
53    /// Create a new application
54    pub fn new(runtime: Arc<Runtime>, runtime_options: RuntimeOptions) -> Result<Self> {
55        let mut config = Config::load()?;
56
57        // Apply CLI shader override if specified
58        if let Some(ref shader) = runtime_options.shader {
59            config.shader.custom_shader = Some(shader.clone());
60            config.shader.custom_shader_enabled = true;
61            // Disable background image so it doesn't compete with the shader
62            config.background.background_image_enabled = false;
63            log::info!("CLI override: using shader '{}'", shader);
64        }
65
66        // Apply CLI session logging override if specified
67        if runtime_options.log_session {
68            config.session_log.auto_log_sessions = true;
69            log::info!("CLI override: session logging enabled");
70        }
71
72        // Apply config log level (unless CLI --log-level was specified).
73        // The record must precede the apply: with the default config
74        // (`log_level: off`) the level drops below Info here, and a record
75        // emitted after it would be silently dropped — leaving the log with
76        // no hint that logging was turned down by config.
77        if runtime_options.log_level.is_none() {
78            log::info!("Config log level: {}", config.log_level.display_name());
79            crate::debug::set_log_level(config.log_level.to_level_filter());
80        }
81
82        Ok(Self {
83            config,
84            runtime,
85            runtime_options,
86        })
87    }
88
89    /// Run the application
90    pub fn run(self) -> Result<()> {
91        let event_loop = EventLoop::<AppEvent>::with_user_event().build()?;
92
93        // Use Wait for power-efficient event handling
94        // Combined with WaitUntil in about_to_wait for precise timing
95        event_loop.set_control_flow(ControlFlow::Wait);
96        #[cfg(target_os = "macos")]
97        let _display_observer =
98            display_recovery::register_display_change_observer(event_loop.create_proxy());
99
100        // Scripted UI testing: parse eagerly so a bad script fails before any
101        // window opens, then drive the app through the event-loop proxy — the
102        // one injection route that needs no host (TCC) permission.
103        let ui_test_script = self
104            .runtime_options
105            .ui_test
106            .as_deref()
107            .map(ui_test::load_script)
108            .transpose()?;
109        if let Some(script) = ui_test_script {
110            let proxy = event_loop.create_proxy();
111            std::thread::spawn(move || {
112                for step in script.steps {
113                    std::thread::sleep(std::time::Duration::from_millis(step.wait_ms));
114                    if proxy.send_event(AppEvent::UiTestStep(step)).is_err() {
115                        return; // Event loop gone; nothing to drive.
116                    }
117                }
118                // Settle: let the final step's redraw + any async dispatch land
119                // before the report is written.
120                std::thread::sleep(std::time::Duration::from_millis(600));
121                let _ = proxy.send_event(AppEvent::UiTestFinish);
122            });
123        }
124
125        let mut window_manager =
126            WindowManager::new(self.config, self.runtime, self.runtime_options);
127
128        event_loop.run_app(&mut window_manager)?;
129
130        Ok(())
131    }
132}