blob: eff1a0a2b9c90c801395c9daf53056a96ac86a48 [file] [log] [blame]
//! Alacritty - The GPU Enhanced Terminal.
#![warn(rust_2018_idioms, future_incompatible)]
#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use)]
#![cfg_attr(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"#);
use std::error::Error;
use std::fmt::Write as _;
use std::io::{self, Write};
use std::path::PathBuf;
use std::{env, fs};
use log::info;
#[cfg(windows)]
use windows_sys::Win32::System::Console::{ATTACH_PARENT_PROCESS, AttachConsole, FreeConsole};
use winit::event_loop::EventLoop;
#[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))]
use winit::raw_window_handle::{HasDisplayHandle, RawDisplayHandle};
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;
mod migrate;
#[cfg(windows)]
mod panic;
mod renderer;
mod scheduler;
mod string;
mod window_context;
mod gl {
#![allow(clippy::all, unsafe_op_in_unsafe_fn)]
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}
#[cfg(unix)]
use crate::cli::MessageOptions;
#[cfg(not(any(target_os = "macos", windows)))]
use crate::cli::SocketMessage;
use crate::cli::{Options, Subcommands};
use crate::config::UiConfig;
use crate::config::monitor::ConfigMonitor;
use crate::event::{Event, Processor};
#[cfg(target_os = "macos")]
use crate::macos::locale;
fn main() -> Result<(), Box<dyn Error>> {
#[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();
match options.subcommands {
#[cfg(unix)]
Some(Subcommands::Msg(options)) => msg(options)?,
Some(Subcommands::Migrate(options)) => migrate::migrate(options),
None => alacritty(options)?,
}
Ok(())
}
/// `msg` subcommand entrypoint.
#[cfg(unix)]
#[allow(unused_mut)]
fn msg(mut options: MessageOptions) -> Result<(), Box<dyn Error>> {
#[cfg(not(any(target_os = "macos", windows)))]
if let SocketMessage::CreateWindow(window_options) = &mut options.message {
window_options.activation_token =
env::var("XDG_ACTIVATION_TOKEN").or_else(|_| env::var("DESKTOP_STARTUP_ID")).ok();
}
ipc::send_message(options.socket, options.message).map_err(|err| err.into())
}
/// 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(mut options: Options) -> Result<(), Box<dyn Error>> {
// Setup winit event loop.
let window_event_loop = EventLoop::<Event>::with_user_event().build()?;
// 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 matches!(
window_event_loop.display_handle().unwrap().as_raw(),
RawDisplayHandle::Wayland(_)
) {
"Wayland"
} else {
"X11"
}
);
#[cfg(not(any(feature = "x11", target_os = "macos", windows)))]
info!("Running on Wayland");
// Load configuration file.
let config = config::load(&mut options);
log_config_path(&config);
// Update the log level from config.
log::set_max_level(config.debug.log_level);
// Set tty environment variables.
tty::setup_env();
// Set env vars from config.
for (key, value) in config.env.iter() {
unsafe { env::set_var(key, value) };
}
// Switch to home directory.
#[cfg(target_os = "macos")]
env::set_current_dir(home::home_dir().unwrap()).unwrap();
// Set macOS locale.
#[cfg(target_os = "macos")]
locale::set_locale_environment();
// Create the IPC socket listener.
#[cfg(unix)]
let socket_path = if config.ipc_socket() {
match ipc::spawn_ipc_socket(&options, window_event_loop.create_proxy()) {
Ok(path) => Some(path),
Err(err) if options.daemon => return Err(err.into()),
Err(err) => {
log::warn!("Unable to create socket: {err:?}");
None
},
}
} 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 mut processor = Processor::new(config, options, &window_event_loop);
// Start event loop and block until shutdown.
let result = processor.run(window_event_loop);
// `Processor` must be dropped before calling `FreeConsole`.
//
// This is needed for 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.
// Terminate the config monitor.
if let Some(config_monitor) = processor.config_monitor.take() {
config_monitor.shutdown();
}
// Without explicitly detaching the console cmd won't redraw it's prompt.
#[cfg(windows)]
unsafe {
FreeConsole();
}
info!("Goodbye");
result
}
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}");
}