alacritty/alacritty/src/main.rs

243 lines
7.2 KiB
Rust

//! Alacritty - The GPU Enhanced Terminal.
#![warn(rust_2018_idioms, future_incompatible)]
#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use)]
#![cfg_attr(feature = "cargo-clippy", deny(warnings))]
// With the default subsystem, 'console', windows creates an additional console
// window for the program.
// This is silently ignored on non-windows systems.
// See https://msdn.microsoft.com/en-us/library/4cc7ya5b.aspx for more details.
#![windows_subsystem = "windows"]
#[cfg(not(any(feature = "x11", feature = "wayland", target_os = "macos", windows)))]
compile_error!(r#"at least one of the "x11"/"wayland" features must be enabled"#);
#[cfg(target_os = "macos")]
use std::env;
use std::fmt::Write as _;
use std::io::{self, Write};
use std::path::PathBuf;
use std::string::ToString;
use std::{fs, process};
use glutin::event_loop::EventLoop as GlutinEventLoop;
#[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))]
use glutin::platform::unix::EventLoopWindowTargetExtUnix;
use log::info;
#[cfg(windows)]
use winapi::um::wincon::{AttachConsole, FreeConsole, ATTACH_PARENT_PROCESS};
use alacritty_terminal::tty;
mod cli;
mod clipboard;
mod config;
mod daemon;
mod display;
mod event;
mod input;
#[cfg(unix)]
mod ipc;
mod logging;
#[cfg(target_os = "macos")]
mod macos;
mod message_bar;
#[cfg(windows)]
mod panic;
mod renderer;
mod scheduler;
mod string;
mod window_context;
mod gl {
#![allow(clippy::all)]
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}
use crate::cli::Options;
#[cfg(unix)]
use crate::cli::{MessageOptions, Subcommands};
use crate::config::{monitor, UiConfig};
use crate::event::{Event, Processor};
#[cfg(target_os = "macos")]
use crate::macos::locale;
fn main() {
#[cfg(windows)]
panic::attach_handler();
// When linked with the windows subsystem windows won't automatically attach
// to the console of the parent process, so we do it explicitly. This fails
// silently if the parent has no console.
#[cfg(windows)]
unsafe {
AttachConsole(ATTACH_PARENT_PROCESS);
}
// Load command line options.
let options = Options::new();
#[cfg(unix)]
let result = match options.subcommands {
Some(Subcommands::Msg(options)) => msg(options),
None => alacritty(options),
};
#[cfg(not(unix))]
let result = alacritty(options);
// Handle command failure.
if let Err(err) = result {
eprintln!("Error: {}", err);
process::exit(1);
}
}
/// `msg` subcommand entrypoint.
#[cfg(unix)]
fn msg(options: MessageOptions) -> Result<(), String> {
ipc::send_message(options.socket, options.message).map_err(|err| err.to_string())
}
/// Temporary files stored for Alacritty.
///
/// This stores temporary files to automate their destruction through its `Drop` implementation.
struct TemporaryFiles {
#[cfg(unix)]
socket_path: Option<PathBuf>,
log_file: Option<PathBuf>,
}
impl Drop for TemporaryFiles {
fn drop(&mut self) {
// Clean up the IPC socket file.
#[cfg(unix)]
if let Some(socket_path) = &self.socket_path {
let _ = fs::remove_file(socket_path);
}
// Clean up logfile.
if let Some(log_file) = &self.log_file {
if fs::remove_file(log_file).is_ok() {
let _ = writeln!(io::stdout(), "Deleted log file at \"{}\"", log_file.display());
}
}
}
}
/// Run main Alacritty entrypoint.
///
/// Creates a window, the terminal state, PTY, I/O event loop, input processor,
/// config change monitor, and runs the main display loop.
fn alacritty(options: Options) -> Result<(), String> {
// Setup glutin event loop.
let window_event_loop = GlutinEventLoop::<Event>::with_user_event();
// Initialize the logger as soon as possible as to capture output from other subsystems.
let log_file = logging::initialize(&options, window_event_loop.create_proxy())
.expect("Unable to initialize logger");
info!("Welcome to Alacritty");
info!("Version {}", env!("VERSION"));
#[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))]
info!("Running on {}", if window_event_loop.is_x11() { "X11" } else { "Wayland" });
#[cfg(not(any(feature = "x11", target_os = "macos", windows)))]
info!("Running on Wayland");
// Load configuration file.
let config = config::load(&options);
log_config_path(&config);
// Update the log level from config.
log::set_max_level(config.debug.log_level);
// Set environment variables.
tty::setup_env(&config.terminal_config);
// Switch to home directory.
#[cfg(target_os = "macos")]
env::set_current_dir(dirs::home_dir().unwrap()).unwrap();
// Set macOS locale.
#[cfg(target_os = "macos")]
locale::set_locale_environment();
// Create a config monitor when config was loaded from path.
//
// The monitor watches the config file for changes and reloads it. Pending
// config changes are processed in the main loop.
if config.live_config_reload {
monitor::watch(config.config_paths.clone(), window_event_loop.create_proxy());
}
// Create the IPC socket listener.
#[cfg(unix)]
let socket_path = if config.ipc_socket {
ipc::spawn_ipc_socket(&options, window_event_loop.create_proxy())
} else {
None
};
// Setup automatic RAII cleanup for our files.
let log_cleanup = log_file.filter(|_| !config.debug.persistent_logging);
let _files = TemporaryFiles {
#[cfg(unix)]
socket_path,
log_file: log_cleanup,
};
// Event processor.
let window_options = options.window_options.clone();
let mut processor = Processor::new(config, options, &window_event_loop);
// Create the first Alacritty window.
let proxy = window_event_loop.create_proxy();
processor
.create_window(&window_event_loop, proxy, window_options)
.map_err(|err| err.to_string())?;
info!("Initialisation complete");
// Start event loop and block until shutdown.
processor.run(window_event_loop);
// This explicit drop is needed for Windows, ConPTY backend. Otherwise a deadlock can occur.
// The cause:
// - Drop for ConPTY will deadlock if the conout pipe has already been dropped
// - ConPTY is dropped when the last of processor and window context are dropped, because both
// of them own an Arc<ConPTY>
//
// The fix is to ensure that processor is dropped first. That way, when window context (i.e.
// PTY) is dropped, it can ensure ConPTY is dropped before the conout pipe in the PTY drop
// order.
//
// FIXME: Change PTY API to enforce the correct drop order with the typesystem.
drop(processor);
// FIXME patch notify library to have a shutdown method.
// config_reloader.join().ok();
// Without explicitly detaching the console cmd won't redraw it's prompt.
#[cfg(windows)]
unsafe {
FreeConsole();
}
info!("Goodbye");
Ok(())
}
fn log_config_path(config: &UiConfig) {
if config.config_paths.is_empty() {
return;
}
let mut msg = String::from("Configuration files loaded from:");
for path in &config.config_paths {
let _ = write!(msg, "\n {:?}", path.display());
}
info!("{}", msg);
}