par-term 0.48.0

Cross-platform GPU-accelerated terminal emulator with inline graphics support (Sixel, iTerm2, Kitty)
//! Application module for par-term
//!
//! This module contains the main application logic, including:
//! - `App`: Entry point that initializes and runs the event loop
//! - `WindowManager`: Manages multiple windows and coordinates menu events
//! - `WindowState`: Per-window state including terminal, renderer, and UI

use crate::cli::RuntimeOptions;
use crate::config::Config;
use anyhow::Result;
use std::sync::Arc;
use tokio::runtime::Runtime;
use winit::event_loop::{ControlFlow, EventLoop};
/// Events injected into the winit event loop by platform observers and the
/// UI-test driver.
#[derive(Debug, Clone)]
pub(crate) enum AppEvent {
    /// The macOS display topology or parameters changed.
    #[cfg(target_os = "macos")]
    DisplayConfigurationChanged,
    /// One scripted `--ui-test` step, ready to execute.
    UiTestStep(ui_test::UiTestStep),
    /// The script ran out; write the report and exit.
    UiTestFinish,
}

pub(crate) mod display_recovery;

pub(crate) mod copy_mode;
mod file_transfers;
pub mod handler;
pub mod input_events;
pub(crate) mod leader;
pub mod mouse_events;
pub(crate) mod overlay;
pub(crate) mod pane_hint_select;
pub(crate) mod pane_resize_mode;
pub(crate) mod render_pipeline;
pub mod tab_ops;
mod tmux_handler;
mod triggers;
pub(crate) mod ui_test;
pub mod window_manager;
pub mod window_state;

pub(crate) use window_manager::WindowManager;

/// Main application entry point
pub struct App {
    config: Config,
    runtime: Arc<Runtime>,
    runtime_options: RuntimeOptions,
}

impl App {
    /// Create a new application
    pub fn new(runtime: Arc<Runtime>, runtime_options: RuntimeOptions) -> Result<Self> {
        let mut config = Config::load()?;

        // UX MP3 (MD3): per-profile shortcut strings become registry
        // bindings once, persisted before any window builds its registry.
        crate::profile::actions::migrate_profile_shortcuts_on_disk(&mut config);

        // Apply CLI shader override if specified
        if let Some(ref shader) = runtime_options.shader {
            config.shader.custom_shader = Some(shader.clone());
            config.shader.custom_shader_enabled = true;
            // Disable background image so it doesn't compete with the shader
            config.background.background_image_enabled = false;
            log::info!("CLI override: using shader '{}'", shader);
        }

        // Apply CLI session logging override if specified
        if runtime_options.log_session {
            config.session_log.auto_log_sessions = true;
            log::info!("CLI override: session logging enabled");
        }

        // Apply config log level (unless CLI --log-level was specified).
        // The record must precede the apply: with the default config
        // (`log_level: off`) the level drops below Info here, and a record
        // emitted after it would be silently dropped — leaving the log with
        // no hint that logging was turned down by config.
        if runtime_options.log_level.is_none() {
            log::info!("Config log level: {}", config.log_level.display_name());
            crate::debug::set_log_level(config.log_level.to_level_filter());
        }

        Ok(Self {
            config,
            runtime,
            runtime_options,
        })
    }

    /// Run the application
    pub fn run(self) -> Result<()> {
        let event_loop = EventLoop::<AppEvent>::with_user_event().build()?;

        // Use Wait for power-efficient event handling
        // Combined with WaitUntil in about_to_wait for precise timing
        event_loop.set_control_flow(ControlFlow::Wait);
        #[cfg(target_os = "macos")]
        let _display_observer =
            display_recovery::register_display_change_observer(event_loop.create_proxy());

        // Scripted UI testing: parse eagerly so a bad script fails before any
        // window opens, then drive the app through the event-loop proxy — the
        // one injection route that needs no host (TCC) permission.
        let ui_test_script = self
            .runtime_options
            .ui_test
            .as_deref()
            .map(ui_test::load_script)
            .transpose()?;
        if let Some(script) = ui_test_script {
            let proxy = event_loop.create_proxy();
            std::thread::spawn(move || {
                for step in script.steps {
                    std::thread::sleep(std::time::Duration::from_millis(step.wait_ms));
                    if proxy.send_event(AppEvent::UiTestStep(step)).is_err() {
                        return; // Event loop gone; nothing to drive.
                    }
                }
                // Settle: let the final step's redraw + any async dispatch land
                // before the report is written.
                std::thread::sleep(std::time::Duration::from_millis(600));
                let _ = proxy.send_event(AppEvent::UiTestFinish);
            });
        }

        let mut window_manager =
            WindowManager::new(self.config, self.runtime, self.runtime_options);

        event_loop.run_app(&mut window_manager)?;

        Ok(())
    }
}