diff options
author | Kirill Chibisov <contact@kchibisov.com> | 2021-11-22 21:34:09 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-22 18:34:09 +0000 |
commit | 8681f71084894db6d1e258be17db1f80bb669314 (patch) | |
tree | 24d3c0ced916d2d171fd03f50cd34dcda8f0aa06 | |
parent | c89939b5d14e581e1aeaa940d81843192e0abc79 (diff) | |
download | alacritty-8681f71084894db6d1e258be17db1f80bb669314.tar.gz alacritty-8681f71084894db6d1e258be17db1f80bb669314.zip |
Add parameters to `msg create-window` subcommand
Alacritty's `msg create-window` subcommand would previously inherit all
the CLI parameters from the original executable. However not only could
this lead to unexpected behavior, it also prevents multi-window users
from making use of parameters like `-e`, `--working-directory`, or
`--hold`.
This is solved by adding a JSON-based message format to the IPC socket
messages which instructs the Alacritty server on which CLI parameters
should be used to create the new window.
Fixes #5562.
Fixes #5561.
Fixes #5560.
28 files changed, 470 insertions, 353 deletions
diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs index d939aff5..fb3cb011 100644 --- a/alacritty/src/cli.rs +++ b/alacritty/src/cli.rs @@ -2,13 +2,14 @@ use std::cmp::max; use std::path::PathBuf; use log::{self, error, LevelFilter}; +use serde::{Deserialize, Serialize}; use serde_yaml::Value; use structopt::StructOpt; -use alacritty_terminal::config::Program; +use alacritty_terminal::config::{Program, PtyConfig}; use crate::config::window::{Class, DEFAULT_NAME}; -use crate::config::{serde_utils, Config}; +use crate::config::{serde_utils, UiConfig}; /// CLI options for the main Alacritty executable. #[derive(StructOpt, Debug)] @@ -34,10 +35,6 @@ pub struct Options { #[structopt(long)] pub embed: Option<String>, - /// Start the shell in the specified working directory. - #[structopt(long)] - pub working_directory: Option<PathBuf>, - /// Specify alternative configuration file [default: $XDG_CONFIG_HOME/alacritty/alacritty.yml]. #[cfg(not(any(target_os = "macos", windows)))] #[structopt(long)] @@ -53,10 +50,6 @@ pub struct Options { #[structopt(long)] pub config_file: Option<PathBuf>, - /// Remain open after child process exits. - #[structopt(long)] - pub hold: bool, - /// Path for IPC socket creation. #[cfg(unix)] #[structopt(long)] @@ -70,10 +63,6 @@ pub struct Options { #[structopt(short, conflicts_with("quiet"), parse(from_occurrences))] verbose: u8, - /// Command and args to execute (must be last argument). - #[structopt(short = "e", long, allow_hyphen_values = true)] - command: Vec<String>, - /// Override configuration file options [example: cursor.style=Beam]. #[structopt(short = "o", long)] option: Vec<String>, @@ -82,6 +71,10 @@ pub struct Options { #[structopt(skip)] pub config_options: Value, + /// Terminal options which could be passed via IPC. + #[structopt(flatten)] + pub terminal_options: TerminalOptions, + /// Subcommand passed to the CLI. #[cfg(unix)] #[structopt(subcommand)] @@ -106,42 +99,42 @@ impl Options { } /// Override configuration file with options from the CLI. - pub fn override_config(&self, config: &mut Config) { - if let Some(working_directory) = &self.working_directory { + pub fn override_config(&self, config: &mut UiConfig) { + if let Some(working_directory) = &self.terminal_options.working_directory { if working_directory.is_dir() { - config.working_directory = Some(working_directory.to_owned()); + config.terminal_config.pty_config.working_directory = + Some(working_directory.to_owned()); } else { error!("Invalid working directory: {:?}", working_directory); } } - if let Some(command) = self.command() { - config.shell = Some(command); + if let Some(command) = self.terminal_options.command() { + config.terminal_config.pty_config.shell = Some(command); } - config.hold = self.hold; + config.terminal_config.pty_config.hold = self.terminal_options.hold; if let Some(title) = self.title.clone() { - config.ui_config.window.title = title + config.window.title = title } if let Some(class) = &self.class { - config.ui_config.window.class = class.clone(); + config.window.class = class.clone(); } #[cfg(unix)] { - config.ui_config.ipc_socket |= self.socket.is_some(); + config.ipc_socket |= self.socket.is_some(); } - config.ui_config.window.dynamic_title &= self.title.is_none(); - config.ui_config.window.embed = self.embed.as_ref().and_then(|embed| embed.parse().ok()); - config.ui_config.debug.print_events |= self.print_events; - config.ui_config.debug.log_level = max(config.ui_config.debug.log_level, self.log_level()); - config.ui_config.debug.ref_test |= self.ref_test; + config.window.dynamic_title &= self.title.is_none(); + config.window.embed = self.embed.as_ref().and_then(|embed| embed.parse().ok()); + config.debug.print_events |= self.print_events; + config.debug.log_level = max(config.debug.log_level, self.log_level()); + config.debug.ref_test |= self.ref_test; - if config.ui_config.debug.print_events { - config.ui_config.debug.log_level = - max(config.ui_config.debug.log_level, LevelFilter::Info); + if config.debug.print_events { + config.debug.log_level = max(config.debug.log_level, LevelFilter::Info); } } @@ -164,12 +157,6 @@ impl Options { (..) => LevelFilter::Off, } } - - /// Shell override passed through the CLI. - pub fn command(&self) -> Option<Program> { - let (program, args) = self.command.split_first()?; - Some(Program::WithArgs { program: program.clone(), args: args.to_vec() }) - } } /// Format an option in the format of `parent.field=value` to a serde Value. @@ -214,6 +201,47 @@ fn parse_class(input: &str) -> Result<Class, String> { } } +/// Terminal specific cli options which can be passed to new windows via IPC. +#[derive(Serialize, Deserialize, StructOpt, Default, Debug, Clone, PartialEq)] +pub struct TerminalOptions { + /// Start the shell in the specified working directory. + #[structopt(long)] + pub working_directory: Option<PathBuf>, + + /// Remain open after child process exit. + #[structopt(long)] + pub hold: bool, + + /// Command and args to execute (must be last argument). + #[structopt(short = "e", long, allow_hyphen_values = true)] + command: Vec<String>, +} + +impl TerminalOptions { + pub fn new() -> Self { + Default::default() + } + + pub fn is_empty(&self) -> bool { + self.working_directory.is_none() && !self.hold && self.command.is_empty() + } + + /// Shell override passed through the CLI. + pub fn command(&self) -> Option<Program> { + let (program, args) = self.command.split_first()?; + Some(Program::WithArgs { program: program.clone(), args: args.to_vec() }) + } +} + +impl From<TerminalOptions> for PtyConfig { + fn from(mut options: TerminalOptions) -> Self { + let working_directory = options.working_directory.take(); + let shell = options.command(); + let hold = options.hold; + PtyConfig { hold, shell, working_directory } + } +} + /// Available CLI subcommands. #[cfg(unix)] #[derive(StructOpt, Debug)] @@ -236,10 +264,10 @@ pub struct MessageOptions { /// Available socket messages. #[cfg(unix)] -#[derive(StructOpt, Debug)] +#[derive(StructOpt, Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum SocketMessage { /// Create a new window in the same Alacritty process. - CreateWindow, + CreateWindow(TerminalOptions), } #[cfg(test)] @@ -257,32 +285,32 @@ mod tests { #[test] fn dynamic_title_ignoring_options_by_default() { - let mut config = Config::default(); - let old_dynamic_title = config.ui_config.window.dynamic_title; + let mut config = UiConfig::default(); + let old_dynamic_title = config.window.dynamic_title; Options::new().override_config(&mut config); - assert_eq!(old_dynamic_title, config.ui_config.window.dynamic_title); + assert_eq!(old_dynamic_title, config.window.dynamic_title); } #[test] fn dynamic_title_overridden_by_options() { - let mut config = Config::default(); + let mut config = UiConfig::default(); let options = Options { title: Some("foo".to_owned()), ..Options::new() }; options.override_config(&mut config); - assert!(!config.ui_config.window.dynamic_title); + assert!(!config.window.dynamic_title); } #[test] fn dynamic_title_not_overridden_by_config() { - let mut config = Config::default(); + let mut config = UiConfig::default(); - config.ui_config.window.title = "foo".to_owned(); + config.window.title = "foo".to_owned(); Options::new().override_config(&mut config); - assert!(config.ui_config.window.dynamic_title); + assert!(config.window.dynamic_title); } #[test] diff --git a/alacritty/src/config/mod.rs b/alacritty/src/config/mod.rs index 4a3c0ae9..10e504d3 100644 --- a/alacritty/src/config/mod.rs +++ b/alacritty/src/config/mod.rs @@ -7,7 +7,7 @@ use serde::Deserialize; use serde_yaml::mapping::Mapping; use serde_yaml::Value; -use alacritty_terminal::config::{Config as TermConfig, LOG_TARGET_CONFIG}; +use alacritty_terminal::config::LOG_TARGET_CONFIG; pub mod bell; pub mod color; @@ -27,13 +27,11 @@ pub use crate::config::bindings::{ }; #[cfg(test)] pub use crate::config::mouse::{ClickHandler, Mouse}; -use crate::config::ui_config::UiConfig; +pub use crate::config::ui_config::UiConfig; /// Maximum number of depth for the configuration file imports. const IMPORT_RECURSION_LIMIT: usize = 5; -pub type Config = TermConfig<UiConfig>; - /// Result from config loading. pub type Result<T> = std::result::Result<T, Error>; @@ -100,7 +98,7 @@ impl From<serde_yaml::Error> for Error { } /// Load the configuration file. -pub fn load(options: &Options) -> Config { +pub fn load(options: &Options) -> UiConfig { let config_options = options.config_options.clone(); let config_path = options.config_file.clone().or_else(installed_config); @@ -112,9 +110,9 @@ pub fn load(options: &Options) -> Config { .as_ref() .and_then(|config_path| load_from(config_path, config_options.clone()).ok()) .unwrap_or_else(|| { - let mut config = Config::deserialize(config_options).unwrap_or_default(); + let mut config = UiConfig::deserialize(config_options).unwrap_or_default(); match config_path { - Some(config_path) => config.ui_config.config_paths.push(config_path), + Some(config_path) => config.config_paths.push(config_path), None => info!(target: LOG_TARGET_CONFIG, "No config file found; using default"), } config @@ -126,7 +124,7 @@ pub fn load(options: &Options) -> Config { } /// Attempt to reload the configuration file. -pub fn reload(config_path: &Path, options: &Options) -> Result<Config> { +pub fn reload(config_path: &Path, options: &Options) -> Result<UiConfig> { // Load config, propagating errors. let config_options = options.config_options.clone(); let mut config = load_from(config_path, config_options)?; @@ -136,17 +134,17 @@ pub fn reload(config_path: &Path, options: &Options) -> Result<Config> { Ok(config) } -/// Modifications after the `Config` object is created. -fn after_loading(config: &mut Config, options: &Options) { +/// Modifications after the `UiConfig` object is created. +fn after_loading(config: &mut UiConfig, options: &Options) { // Override config with CLI options. options.override_config(config); // Create key bindings for regex hints. - config.ui_config.generate_hint_bindings(); + config.generate_hint_bindings(); } /// Load configuration file and log errors. -fn load_from(path: &Path, cli_config: Value) -> Result<Config> { +fn load_from(path: &Path, cli_config: Value) -> Result<UiConfig> { match read_config(path, cli_config) { Ok(config) => Ok(config), Err(err) => { @@ -157,7 +155,7 @@ fn load_from(path: &Path, cli_config: Value) -> Result<Config> { } /// Deserialize configuration file from path. -fn read_config(path: &Path, cli_config: Value) -> Result<Config> { +fn read_config(path: &Path, cli_config: Value) -> Result<UiConfig> { let mut config_paths = Vec::new(); let mut config_value = parse_config(path, &mut config_paths, IMPORT_RECURSION_LIMIT)?; @@ -165,8 +163,8 @@ fn read_config(path: &Path, cli_config: Value) -> Result<Config> { config_value = serde_utils::merge(config_value, cli_config); // Deserialize to concrete type. - let mut config = Config::deserialize(config_value)?; - config.ui_config.config_paths = config_paths; + let mut config = UiConfig::deserialize(config_value)?; + config.config_paths = config_paths; Ok(config) } @@ -307,7 +305,7 @@ mod tests { fn config_read_eof() { let config_path: PathBuf = DEFAULT_ALACRITTY_CONFIG.into(); let mut config = read_config(&config_path, Value::Null).unwrap(); - config.ui_config.config_paths = Vec::new(); - assert_eq!(config, Config::default()); + config.config_paths = Vec::new(); + assert_eq!(config, UiConfig::default()); } } diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs index 3ba59ea8..353249d5 100644 --- a/alacritty/src/config/ui_config.rs +++ b/alacritty/src/config/ui_config.rs @@ -9,7 +9,9 @@ use serde::{self, Deserialize, Deserializer}; use unicode_width::UnicodeWidthChar; use alacritty_config_derive::ConfigDeserialize; -use alacritty_terminal::config::{Percentage, Program, LOG_TARGET_CONFIG}; +use alacritty_terminal::config::{ + Config as TerminalConfig, Percentage, Program, LOG_TARGET_CONFIG, +}; use alacritty_terminal::term::search::RegexSearch; use crate::config::bell::BellConfig; @@ -66,6 +68,10 @@ pub struct UiConfig { #[cfg(unix)] pub ipc_socket: bool, + /// Config for the alacritty_terminal itself. + #[config(flatten)] + pub terminal_config: TerminalConfig, + /// Keybindings. key_bindings: KeyBindings, @@ -91,6 +97,7 @@ impl Default for UiConfig { config_paths: Default::default(), key_bindings: Default::default(), mouse_bindings: Default::default(), + terminal_config: Default::default(), background_opacity: Default::default(), bell: Default::default(), colors: Default::default(), diff --git a/alacritty/src/daemon.rs b/alacritty/src/daemon.rs index 4a6b6f83..b199c353 100644 --- a/alacritty/src/daemon.rs +++ b/alacritty/src/daemon.rs @@ -8,7 +8,6 @@ use std::os::windows::process::CommandExt; use std::process::{Command, Stdio}; use log::{debug, warn}; - #[cfg(windows)] use winapi::um::winbase::{CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW}; diff --git a/alacritty/src/display/content.rs b/alacritty/src/display/content.rs index db73a73b..72d79f7e 100644 --- a/alacritty/src/display/content.rs +++ b/alacritty/src/display/content.rs @@ -4,7 +4,6 @@ use std::mem; use std::ops::{Deref, DerefMut, RangeInclusive}; use alacritty_terminal::ansi::{Color, CursorShape, NamedColor}; -use alacritty_terminal::config::Config; use alacritty_terminal::event::EventListener; use alacritty_terminal::grid::{Dimensions, Indexed}; use alacritty_terminal::index::{Column, Direction, Line, Point}; @@ -13,7 +12,7 @@ use alacritty_terminal::term::color::{CellRgb, Rgb}; use alacritty_terminal::term::search::{Match, RegexIter, RegexSearch}; use alacritty_terminal::term::{RenderableContent as TerminalContent, Term, TermMode}; -use crate::config::ui_config::UiConfig; +use crate::config::UiConfig; use crate::display::color::{List, DIM_FACTOR}; use crate::display::hint::HintState; use crate::display::{self, Display, MAX_SEARCH_LINES}; @@ -32,14 +31,14 @@ pub struct RenderableContent<'a> { cursor_point: Point<usize>, search: Option<Regex<'a>>, hint: Option<Hint<'a>>, - config: &'a Config<UiConfig>, + config: &'a UiConfig, colors: &'a List, focused_match: Option<&'a Match>, } impl<'a> RenderableContent<'a> { pub fn new<T: EventListener>( - config: &'a Config<UiConfig>, + config: &'a UiConfig, display: &'a mut Display, term: &'a Term<T>, search_state: &'a SearchState, @@ -54,7 +53,7 @@ impl<'a> RenderableContent<'a> { || search_state.regex().is_some() { CursorShape::Hidden - } else if !term.is_focused && config.cursor.unfocused_hollow { + } else if !term.is_focused && config.terminal_config.cursor.unfocused_hollow { CursorShape::HollowBlock } else { terminal_content.cursor.shape @@ -113,9 +112,9 @@ impl<'a> RenderableContent<'a> { // Cursor colors. let color = if self.terminal_content.mode.contains(TermMode::VI) { - self.config.ui_config.colors.vi_mode_cursor + self.config.colors.vi_mode_cursor } else { - self.config.ui_config.colors.cursor + self.config.colors.cursor }; let cursor_color = self.terminal_content.colors[NamedColor::Cursor].map_or(color.background, CellRgb::Rgb); @@ -131,8 +130,8 @@ impl<'a> RenderableContent<'a> { // Invert cursor color with insufficient contrast to prevent invisible cursors. if insufficient_contrast { - cursor_color = self.config.ui_config.colors.primary.foreground; - text_color = self.config.ui_config.colors.primary.background; + cursor_color = self.config.colors.primary.foreground; + text_color = self.config.colors.primary.background; } Some(RenderableCursor { @@ -204,7 +203,7 @@ impl RenderableCell { mem::swap(&mut fg, &mut bg); 1.0 } else { - Self::compute_bg_alpha(&content.config.ui_config, cell.bg) + Self::compute_bg_alpha(content.config, cell.bg) }; let is_selected = content.terminal_content.selection.map_or(false, |selection| { @@ -217,7 +216,7 @@ impl RenderableCell { let display_offset = content.terminal_content.display_offset; let viewport_start = Point::new(Line(-(display_offset as i32)), Column(0)); - let colors = &content.config.ui_config.colors; + let colors = &content.config.colors; let mut character = cell.c; if let Some((c, is_first)) = @@ -293,18 +292,18 @@ impl RenderableCell { /// Get the RGB color from a cell's foreground color. fn compute_fg_rgb(content: &mut RenderableContent<'_>, fg: Color, flags: Flags) -> Rgb { - let ui_config = &content.config.ui_config; + let config = &content.config; match fg { Color::Spec(rgb) => match flags & Flags::DIM { Flags::DIM => rgb * DIM_FACTOR, _ => rgb, }, Color::Named(ansi) => { - match (ui_config.draw_bold_text_with_bright_colors, flags & Flags::DIM_BOLD) { + match (config.draw_bold_text_with_bright_colors, flags & Flags::DIM_BOLD) { // If no bright foreground is set, treat it like the BOLD flag doesn't exist. (_, Flags::DIM_BOLD) if ansi == NamedColor::Foreground - && ui_config.colors.primary.bright_foreground.is_none() => + && config.colors.primary.bright_foreground.is_none() => { content.color(NamedColor::DimForeground as usize) }, @@ -320,7 +319,7 @@ impl RenderableCell { }, Color::Indexed(idx) => { let idx = match ( - ui_config.draw_bold_text_with_bright_colors, + config.draw_bold_text_with_bright_colors, flags & Flags::DIM_BOLD, idx, ) { diff --git a/alacritty/src/display/hint.rs b/alacritty/src/display/hint.rs index 2157a058..c318fc09 100644 --- a/alacritty/src/display/hint.rs +++ b/alacritty/src/display/hint.rs @@ -8,7 +8,7 @@ use alacritty_terminal::term::search::{Match, RegexIter, RegexSearch}; use alacritty_terminal::term::{Term, TermMode}; use crate::config::ui_config::{Hint, HintAction}; -use crate::config::Config; +use crate::config::UiConfig; use crate::display::content::RegexMatches; use crate::display::MAX_SEARCH_LINES; @@ -242,13 +242,13 @@ impl HintLabels { /// Check if there is a hint highlighted at the specified point. pub fn highlighted_at<T>( term: &Term<T>, - config: &Config, + config: &UiConfig, point: Point, mouse_mods: ModifiersState, ) -> Option<HintMatch> { let mouse_mode = term.mode().intersects(TermMode::MOUSE_MODE); - config.ui_config.hints.enabled.iter().find_map(|hint| { + config.hints.enabled.iter().find_map(|hint| { // Check if all required modifiers are pressed. let highlight = hint.mouse.map_or(false, |mouse| { mouse.enabled diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs index a942a88d..5cd59711 100644 --- a/alacritty/src/display/mod.rs +++ b/alacritty/src/display/mod.rs @@ -35,7 +35,7 @@ use crate::config::font::Font; use crate::config::window::Dimensions; #[cfg(not(windows))] use crate::config::window::StartupMode; -use crate::config::Config; +use crate::config::UiConfig; use crate::display::bell::VisualBell; use crate::display::color::List; use crate::display::content::RenderableContent; @@ -202,7 +202,7 @@ pub struct Display { impl Display { pub fn new<E>( - config: &Config, + config: &UiConfig, event_loop: &EventLoopWindowTarget<E>, #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] wayland_event_queue: Option<&EventQueue>, @@ -221,11 +221,11 @@ impl Display { }; // Guess the target window dimensions. - let metrics = GlyphCache::static_metrics(config.ui_config.font.clone(), estimated_dpr)?; + let metrics = GlyphCache::static_metrics(config.font.clone(), estimated_dpr)?; let (cell_width, cell_height) = compute_cell_size(config, &metrics); // Guess the target window size if the user has specified the number of lines/columns. - let dimensions = config.ui_config.window.dimensions(); + let dimensions = config.window.dimensions(); let estimated_size = dimensions.map(|dimensions| { window_size(config, dimensions, cell_width, cell_height, estimated_dpr) }); @@ -261,7 +261,7 @@ impl Display { } } - let padding = config.ui_config.window.padding(window.dpr); + let padding = config.window.padding(window.dpr); let viewport_size = window.inner_size(); // Create new size with at least one column and row. @@ -272,7 +272,7 @@ impl Display { cell_height, padding.0, padding.1, - config.ui_config.window.dynamic_padding && dimensions.is_none(), + config.window.dynamic_padding && dimensions.is_none(), ); info!("Cell size: {} x {}", cell_width, cell_height); @@ -283,25 +283,25 @@ impl Display { renderer.resize(&size_info); // Clear screen. - let background_color = config.ui_config.colors.primary.background; - renderer.with_api(&config.ui_config, &size_info, |api| { + let background_color = config.colors.primary.background; + renderer.with_api(config, &size_info, |api| { api.clear(background_color); }); // Set subpixel anti-aliasing. #[cfg(target_os = "macos")] - crossfont::set_font_smoothing(config.ui_config.font.use_thin_strokes); + crossfont::set_font_smoothing(config.font.use_thin_strokes); // Disable shadows for transparent windows on macOS. #[cfg(target_os = "macos")] - window.set_has_shadow(config.ui_config.window_opacity() >= 1.0); + window.set_has_shadow(config.window_opacity() >= 1.0); // On Wayland we can safely ignore this call, since the window isn't visible until you // actually draw something into it and commit those changes. #[cfg(not(any(target_os = "macos", windows)))] if is_x11 { window.swap_buffers(); - renderer.with_api(&config.ui_config, &size_info, |api| { + renderer.with_api(config, &size_info, |api| { api.finish(); }); } @@ -312,13 +312,13 @@ impl Display { // // TODO: replace `set_position` with `with_position` once available. // Upstream issue: https://github.com/rust-windowing/winit/issues/806. - if let Some(position) = config.ui_config.window.position { + if let Some(position) = config.window.position { window.set_outer_position(PhysicalPosition::from((position.x, position.y))); } #[allow(clippy::single_match)] #[cfg(not(windows))] - match config.ui_config.window.startup_mode { + match config.window.startup_mode { #[cfg(target_os = "macos")] StartupMode::SimpleFullscreen => window.set_simple_fullscreen(true), #[cfg(not(target_os = "macos"))] @@ -326,7 +326,7 @@ impl Display { _ => (), } - let hint_state = HintState::new(config.ui_config.hints.alphabet()); + let hint_state = HintState::new(config.hints.alphabet()); Ok(Self { window, @@ -340,8 +340,8 @@ impl Display { #[cfg(not(any(target_os = "macos", windows)))] is_x11, cursor_hidden: false, - visual_bell: VisualBell::from(&config.ui_config.bell), - colors: List::from(&config.ui_config.colors), + visual_bell: VisualBell::from(&config.bell), + colors: List::from(&config.colors), pending_update: Default::default(), }) } @@ -349,10 +349,10 @@ impl Display { fn new_glyph_cache( dpr: f64, renderer: &mut QuadRenderer, - config: &Config, + config: &UiConfig, ) -> Result<(GlyphCache, f32, f32), Error> { - let font = config.ui_config.font.clone(); - let rasterizer = Rasterizer::new(dpr as f32, config.ui_config.font.use_thin_strokes)?; + let font = config.font.clone(); + let rasterizer = Rasterizer::new(dpr as f32, config.font.use_thin_strokes)?; // Initialize glyph cache. let glyph_cache = { @@ -380,7 +380,7 @@ impl Display { /// Update font size and cell dimensions. /// /// This will return a tuple of the cell width and height. - fn update_glyph_cache(&mut self, config: &Config, font: &Font) -> (f32, f32) { + fn update_glyph_cache(&mut self, config: &UiConfig, font: &Font) -> (f32, f32) { let cache = &mut self.glyph_cache; let dpr = self.window.dpr; @@ -407,7 +407,7 @@ impl Display { pty_resize_handle: &mut dyn OnResize, message_buffer: &MessageBuffer, search_active: bool, - config: &Config, + config: &UiConfig, ) where T: EventListener, { @@ -436,7 +436,7 @@ impl Display { height = dimensions.height as f32; } - let padding = config.ui_config.window.padding(self.window.dpr); + let padding = config.window.padding(self.window.dpr); self.size_info = SizeInfo::new( width, @@ -445,7 +445,7 @@ impl Display { cell_height, padding.0, padding.1, - config.ui_config.window.dynamic_padding, + config.window.dynamic_padding, ); // Update number of column/lines in the viewport. @@ -478,7 +478,7 @@ impl Display { &mut self, terminal: MutexGuard<'_, Term<T>>, message_buffer: &MessageBuffer, - config: &Config, + config: &UiConfig, search_state: &SearchState, ) { // Collect renderable content before the terminal is dropped. @@ -505,7 +505,7 @@ impl Display { // Make sure this window's OpenGL context is active. self.window.make_current(); - self.renderer.with_api(&config.ui_config, &size_info, |api| { + self.renderer.with_api(config, &size_info, |api| { api.clear(background_color); }); @@ -522,7 +522,7 @@ impl Display { let glyph_cache = &mut self.glyph_cache; let highlighted_hint = &self.highlighted_hint; let vi_highlighted_hint = &self.vi_highlighted_hint; - self.renderer.with_api(&config.ui_config, &size_info, |mut api| { + self.renderer.with_api(config, &size_info, |mut api| { // Iterate over all non-empty cells in the grid. for mut cell in grid_cells { // Underline hints hovered by mouse or vi mode cursor. @@ -559,7 +559,7 @@ impl Display { // Push the cursor rects for rendering. if let Some(cursor) = cursor { - for rect in cursor.rects(&size_info, config.cursor.thickness()) { + for rect in cursor.rects(&size_info, config.terminal_config.cursor.thickness()) { rects.push(rect); } } @@ -572,7 +572,7 @@ impl Display { 0., size_info.width(), size_info.height(), - config.ui_config.bell.color, + config.bell.color, visual_bell_intensity as f32, ); rects.push(visual_bell_rect); @@ -587,8 +587,8 @@ impl Display { let y = size_info.cell_height().mul_add(start_line as f32, size_info.padding_y()); let bg = match message.ty() { - MessageType::Error => config.ui_config.colors.normal.red, - MessageType::Warning => config.ui_config.colors.normal.yellow, + MessageType::Error => config.colors.normal.red, + MessageType::Warning => config.colors.normal.yellow, }; let message_bar_rect = @@ -602,10 +602,10 @@ impl Display { // Relay messages to the user. let glyph_cache = &mut self.glyph_cache; - let fg = config.ui_config.colors.primary.background; + let fg = config.colors.primary.background; for (i, message_text) in text.iter().enumerate() { let point = Point::new(start_line + i, Column(0)); - self.renderer.with_api(&config.ui_config, &size_info, |mut api| { + self.renderer.with_api(config, &size_info, |mut api| { api.render_string(glyph_cache, point, fg, bg, message_text); }); } @@ -651,16 +651,16 @@ impl Display { // On X11 `swap_buffers` does not block for vsync. However the next OpenGl command // will block to synchronize (this is `glClear` in Alacritty), which causes a // permanent one frame delay. - self.renderer.with_api(&config.ui_config, &size_info, |api| { + self.renderer.with_api(config, &size_info, |api| { api.finish(); }); } } /// Update to a new configuration. - pub fn update_config(&mut self, config: &Config) { - self.visual_bell.update_config(&config.ui_config.bell); - self.colors = List::from(&config.ui_config.colors); + pub fn update_config(&mut self, config: &UiConfig) { + self.visual_bell.update_config(&config.bell); + self.colors = List::from(&config.colors); } /// Update the mouse/vi mode cursor hint highlighting. @@ -669,7 +669,7 @@ impl Display { pub fn update_highlighted_hints<T>( &mut self, term: &Term<T>, - config: &Config, + config: &UiConfig, mouse: &Mouse, modifiers: ModifiersState, ) -> bool { @@ -744,7 +744,7 @@ impl Display { } /// Draw current search regex. - fn draw_search(&mut self, config: &Config, size_info: &SizeInfo, text: &str) { + fn draw_search(&mut self, config: &UiConfig, size_info: &SizeInfo, text: &str) { let glyph_cache = &mut self.glyph_cache; let num_cols = size_info.columns(); @@ -752,17 +752,17 @@ impl Display { let text = format!("{:<1$}", text, num_cols); let point = Point::new(size_info.screen_lines(), Column(0)); - let fg = config.ui_config.colors.search_bar_foreground(); - let bg = config.ui_config.colors.search_bar_background(); + let fg = config.colors.search_bar_foreground(); + let bg = config.colors.search_bar_background(); - self.renderer.with_api(&config.ui_config, size_info, |mut api| { + self.renderer.with_api(config, size_info, |mut api| { api.render_string(glyph_cache, point, fg, bg, &text); }); } /// Draw render timer. - fn draw_render_timer(&mut self, config: &Config, size_info: &SizeInfo) { - if !config.ui_config.debug.render_timer { + fn draw_render_timer(&mut self, config: &UiConfig, size_info: &SizeInfo) { + if !config.debug.render_timer { return; } @@ -770,10 +770,10 @@ impl Display { let timing = format!("{:.3} usec", self.meter.average()); let point = Point::new(size_info.screen_lines().saturating_sub(2), Column(0)); - let fg = config.ui_config.colors.primary.background; - let bg = config.ui_config.colors.normal.red; + let fg = config.colors.primary.background; + let bg = config.colors.normal.red; - self.renderer.with_api(&config.ui_config, size_info, |mut api| { + self.renderer.with_api(config, size_info, |mut api| { api.render_string(glyph_cache, point, fg, bg, &timing); }); } @@ -781,7 +781,7 @@ impl Display { /// Draw an indicator for the position of a line in history. fn draw_line_indicator( &mut self, - config: &Config, + config: &UiConfig, size_info: &SizeInfo, total_lines: usize, obstructed_column: Option<Column>, @@ -789,14 +789,14 @@ impl Display { ) { let text = format!("[{}/{}]", line, total_lines - 1); let column = Column(size_info.columns().saturating_sub(text.len())); - let colors = &config.ui_config.colors; + let colors = &config.colors; let fg = colors.line_indicator.foreground.unwrap_or(colors.primary.background); let bg = colors.line_indicator.background.unwrap_or(colors.primary.foreground); // Do not render anything if it would obscure the vi mode cursor. if obstructed_column.map_or(true, |obstructed_column| obstructed_column < column) { let glyph_cache = &mut self.glyph_cache; - self.renderer.with_api(&config.ui_config, size_info, |mut api| { + self.renderer.with_api(config, size_info, |mut api| { api.render_string(glyph_cache, Point::new(0, column), fg, bg, &text); }); } @@ -847,9 +847,9 @@ pub fn viewport_to_point(display_offset: usize, point: Point<usize>) -> Point { /// /// This will return a tuple of the cell width and height. #[inline] -fn compute_cell_size(config: &Config, metrics: &crossfont::Metrics) -> (f32, f32) { - let offset_x = f64::from(config.ui_config.font.offset.x); - let offset_y = f64::from(config.ui_config.font.offset.y); +fn compute_cell_size(config: &UiConfig, metrics: &crossfont::Metrics) -> (f32, f32) { + let offset_x = f64::from(config.font.offset.x); + let offset_y = f64::from(config.font.offset.y); ( (metrics.average_advance + offset_x).floor().max(1.) as f32, (metrics.line_height + offset_y).floor().max(1.) as f32, @@ -858,13 +858,13 @@ fn compute_cell_size(config: &Config, metrics: &crossfont::Metrics) -> (f32, f32 /// Calculate the size of the window given padding, terminal dimensions and cell size. fn window_size( - config: &Config, + config: &UiConfig, dimensions: Dimensions, cell_width: f32, cell_height: f32, dpr: f64, ) -> PhysicalSize<u32> { - let padding = config.ui_config.window.padding(dpr); + let padding = config.window.padding(dpr); let grid_width = cell_width * dimensions.columns.0.max(MIN_COLUMNS) as f32; let grid_height = cell_height * dimensions.lines.max(MIN_SCREEN_LINES) as f32; diff --git a/alacritty/src/display/window.rs b/alacritty/src/display/window.rs index 16932dc4..feeb6fb2 100644 --- a/alacritty/src/display/window.rs +++ b/alacritty/src/display/window.rs @@ -54,7 +54,7 @@ use alacritty_terminal::index::Point; use alacritty_terminal::term::SizeInfo; use crate::config::window::{Decorations, WindowConfig}; -use crate::config::Config; +use crate::config::UiConfig; use crate::gl; /// Window icon for `_NET_WM_ICON` property. @@ -172,12 +172,12 @@ impl Window { /// This creates a window and fully initializes a window. pub fn new<E>( event_loop: &EventLoopWindowTarget<E>, - config: &Config, + config: &UiConfig, size: Option<PhysicalSize<u32>>, #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] wayland_event_queue: Option<&EventQueue>, ) -> Result<Window> { - let window_config = &config.ui_config.window; + let window_config = &config.window; let window_builder = Window::get_platform_window(&window_config.title, window_config); // Check if we're running Wayland to disable vsync. @@ -210,7 +210,7 @@ impl Window { #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] let wayland_surface = if is_wayland { // Apply client side decorations theme. - let theme = AlacrittyWaylandTheme::new(&config.ui_config.colors); + let theme = AlacrittyWaylandTheme::new(&config.colors); windowed_context.window().set_wayland_theme(theme); // Attach surface to Alacritty's internal wayland queue to handle frame callbacks. diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index 09e74a9d..4af372c9 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -5,8 +5,6 @@ use std::cmp::{max, min}; use std::collections::{HashMap, VecDeque}; use std::error::Error; use std::fmt::Debug; -#[cfg(not(any(target_os = "macos", windows)))] -use std::fs; use std::path::PathBuf; use std::time::{Duration, Instant}; use std::{env, f32, mem}; @@ -35,10 +33,10 @@ use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode}; #[cfg(not(windows))] use alacritty_terminal::tty; -use crate::cli::Options as CliOptions; +use crate::cli::{Options as CliOptions, TerminalOptions as TerminalCliOptions}; use crate::clipboard::Clipboard; use crate::config::ui_config::{HintAction, HintInternalAction}; -use crate::config::{self, Config}; +use crate::config::{self, UiConfig}; use crate::daemon::start_daemon; use crate::display::hint::HintMatch; use crate::display::window::Window; @@ -89,7 +87,7 @@ pub enum EventType { ConfigReload(PathBuf), Message(Message), Scroll(Scroll), - CreateWindow, + CreateWindow(Option<TerminalCliOptions>), BlinkCursor, SearchNext, } @@ -180,7 +178,7 @@ pub struct ActionContext<'a, N, T> { pub modifiers: &'a mut ModifiersState, pub display: &'a mut Display, pub message_buffer: &'a mut MessageBuffer, - pub config: &'a mut Config, + pub config: &'a mut UiConfig, pub event_loop: &'a EventLoopWindowTarget<Event>, pub event_proxy: &'a EventLoopProxy<Event>, pub scheduler: &'a mut Scheduler, @@ -241,7 +239,8 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon None => return, }; - if ty == ClipboardType::Selection && self.config.selection.save_to_clipboard { + if ty == ClipboardType::Selection && self.config.terminal_config.selection.save_to_clipboard + { self.clipboard.store(ClipboardType::Clipboard, text.clone()); } self.clipboard.store(ty, text); @@ -318,17 +317,17 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon #[inline] fn received_count(&mut self) -> &mut usize { - &mut self.received_count + self.received_count } #[inline] fn suppress_chars(&mut self) -> &mut bool { - &mut self.suppress_chars + self.suppress_chars } #[inline] fn modifiers(&mut self) -> &mut ModifiersState { - &mut self.modifiers + self.modifiers } #[inline] @@ -338,7 +337,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon #[inline] fn display(&mut self) -> &mut Display { - &mut self.display + self.display } #[inline] @@ -355,26 +354,12 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon let mut env_args = env::args(); let alacritty = env_args.next().unwrap(); + // Use working directory of controlling process, or fallback to initial shell, then add it + // as working-directory parameter. #[cfg(unix)] - let mut args = { - // Use working directory of controlling process, or fallback to initial shell. - let mut pid = unsafe { libc::tcgetpgrp(tty::master_fd()) }; - if pid < 0 { - pid = tty::child_pid(); - } - - #[cfg(not(any(target_os = "macos", target_os = "freebsd")))] - let link_path = format!("/proc/{}/cwd", pid); - #[cfg(target_os = "freebsd")] - let link_path = format!("/compat/linux/proc/{}/cwd", pid); - #[cfg(not(target_os = "macos"))] - let cwd = fs::read_link(link_path); - #[cfg(target_os = "macos")] - let cwd = macos::proc::cwd(pid); - - // Add the current working directory as parameter. - cwd.map(|path| vec!["--working-directory".into(), path]).unwrap_or_default() - }; + let mut args = foreground_process_path() + .map(|path| vec!["--working-directory".into(), path]) + .unwrap_or_default(); #[cfg(not(unix))] let mut args: Vec<PathBuf> = Vec::new(); @@ -395,20 +380,34 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon start_daemon(&alacritty, &args); } + #[cfg(not(windows))] + fn create_new_window(&mut self) { + let options = if let Ok(working_directory) = foreground_process_path() { + let mut options = TerminalCliOptions::new(); + options.working_directory = Some(working_directory); + Some(options) + } else { + None + }; + + let _ = self.event_proxy.send_event(Event::new(EventType::CreateWindow(options), None)); + } + + #[cfg(windows)] fn create_new_window(&mut self) { - let _ = self.event_proxy.send_event(Event::new(EventType::CreateWindow, None)); + let _ = self.event_proxy.send_event(Event::new(EventType::CreateWindow(None), None)); } fn change_font_size(&mut self, delta: f32) { *self.font_size = max(*self.font_size + delta, Size::new(FONT_SIZE_STEP)); - let font = self.config.ui_config.font.clone().with_size(*self.font_size); + let font = self.config.font.clone().with_size(*self.font_size); self.display.pending_update.set_font(font); *self.dirty = true; } fn reset_font_size(&mut self) { - *self.font_size = self.config.ui_config.font.size(); - self.display.pending_update.set_font(self.config.ui_config.font.clone()); + *self.font_size = self.config.font.size(); + self.display.pending_update.set_font(self.config.font.clone()); *self.dirty = true; } @@ -632,14 +631,15 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon // Disable cursor blinking. let timer_id = TimerId::new(Topic::BlinkCursor, self.display.window.id()); if let Some(timer) = self.scheduler.unschedule(timer_id) { - let interval = Duration::from_millis(self.config.cursor.blink_interval()); + let interval = + Duration::from_millis(self.config.terminal_config.cursor.blink_interval()); self.scheduler.schedule(timer.event, interval, true, timer.id); self.display.cursor_hidden = false; *self.dirty = true; } // Hide mouse cursor. - if self.config.ui_config.mouse.hide_when_typing { + if self.config.mouse.hide_when_typing { self.display.window.set_mouse_visible(false); } } @@ -769,7 +769,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon self.message_buffer.message() } - fn config(&self) -> &Config { + fn config(&self) -> &UiConfig { self.config } @@ -794,7 +794,7 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { }; // Hide cursor while typing into the search bar. - if self.config.ui_config.mouse.hide_when_typing { + if self.config.mouse.hide_when_typing { self.display.window.set_mouse_visible(false); } @@ -905,9 +905,9 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { /// Update the cursor blinking state. fn update_cursor_blinking(&mut self) { // Get config cursor style. - let mut cursor_style = self.config.cursor.style; + let mut cursor_style = self.config.terminal_config.cursor.style; if self.terminal.mode().contains(TermMode::VI) { - cursor_style = self.config.cursor.vi_mode_style.unwrap_or(cursor_style); + cursor_style = self.config.terminal_config.cursor.vi_mode_style.unwrap_or(cursor_style); }; // Check terminal cursor style. @@ -919,7 +919,8 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { self.scheduler.unschedule(timer_id); if blinking && self.terminal.is_focused { let event = Event::new(EventType::BlinkCursor, self.display.window.id()); - let interval = Duration::from_millis(self.config.cursor.blink_interval()); + let interval = + Duration::from_millis(self.config.terminal_config.cursor.blink_interval()); self.scheduler.schedule(event, interval, true, timer_id); } else { self.display.cursor_hidden = false; @@ -1004,7 +1005,7 @@ impl input::Processor<EventProxy, ActionContext<'_, Notifier, EventProxy>> { let display_update_pending = &mut self.ctx.display.pending_update; // Push current font to update its DPR. - let font = self.ctx.config.ui_config.font.clone(); + let font = self.ctx.config.font.clone(); display_update_pending.set_font(font.with_size(*self.ctx.font_size)); // Resize to event's dimensions, since no resize event is emitted on Wayland. @@ -1026,15 +1027,13 @@ impl input::Processor<EventProxy, ActionContext<'_, Notifier, EventProxy>> { }, EventType::Terminal(event) => match event { TerminalEvent::Title(title) => { - let ui_config = &self.ctx.config.ui_config; - if ui_config.window.dynamic_title { + if self.ctx.config.window.dynamic_title { self.ctx.window().set_title(&title); } }, TerminalEvent::ResetTitle => { - let ui_config = &self.ctx.config.ui_config; - if ui_config.window.dynamic_title { - self.ctx.display.window.set_title(&ui_config.window.title); + if self.ctx.config.window.dynamic_title { + self.ctx.display.window.set_title(&self.ctx.config.window.title); } }, TerminalEvent::Wakeup => *self.ctx.dirty = true, @@ -1049,7 +1048,7 @@ impl input::Processor<EventProxy, ActionContext<'_, Notifier, EventProxy>> { self.ctx.display.visual_bell.ring(); // Execute bell command. - if let Some(bell_command) = &self.ctx.config.ui_config.bell.command { + if let Some(bell_command) = &self.ctx.config.bell.command { start_daemon(bell_command.program(), bell_command.args()); } }, @@ -1069,7 +1068,7 @@ impl input::Processor<EventProxy, ActionContext<'_, Notifier, EventProxy>> { TerminalEvent::Exit => (), TerminalEvent::CursorBlinkingChange => self.ctx.update_cursor_blinking(), }, - EventType::ConfigReload(_) | EventType::CreateWindow => (), + EventType::ConfigReload(_) | EventType::CreateWindow(_) => (), }, GlutinEvent::RedrawRequested(_) => *self.ctx.dirty = true, GlutinEvent::WindowEvent { event, .. } => { @@ -1162,7 +1161,7 @@ pub struct Processor { wayland_event_queue: Option<EventQueue>, windows: HashMap<WindowId, WindowContext>, cli_options: CliOptions, - config: Config, + config: UiConfig, } impl Processor { @@ -1170,7 +1169,7 @@ impl Processor { /// /// Takes a writer which is expected to be hooked up to the write end of a PTY. pub fn new( - config: Config, + config: UiConfig, cli_options: CliOptions, _event_loop: &EventLoop<Event>, ) -> Processor { @@ -1194,10 +1193,12 @@ impl Processor { pub fn create_window( &mut self, event_loop: &EventLoopWindowTarget<Event>, + options: Option<TerminalCliOptions>, proxy: EventLoopProxy<Event>, ) -> Result<(), Box<dyn Error>> { let window_context = WindowContext::new( &self.config, + options, event_loop, proxy, #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] @@ -1219,7 +1220,7 @@ impl Processor { let mut clipboard = Clipboard::new(); event_loop.run_return(|event, event_loop, control_flow| { - if self.config.ui_config.debug.print_events { + if self.config.debug.print_events { info!("glutin event: {:?}", event); } @@ -1246,7 +1247,7 @@ impl Processor { // Shutdown if no more terminals are open. if self.windows.is_empty() { // Write ref tests of last window to disk. - if self.config.ui_config.debug.ref_test { + if self.config.debug.ref_test { window_context.write_ref_test_results(); } @@ -1302,8 +1303,10 @@ impl Processor { } }, // Create a new terminal window. - GlutinEvent::UserEvent(Event { payload: EventType::CreateWindow, .. }) => { - if let Err(err) = self.create_window(event_loop, proxy.clone()) { + GlutinEvent::UserEvent(Event { + payload: EventType::CreateWindow(options), .. + }) => { + if let Err(err) = self.create_window(event_loop, options, proxy.clone()) { error!("Could not open window: {:?}", err); } }, @@ -1386,3 +1389,24 @@ impl EventListener for EventProxy { let _ = self.proxy.send_event(Event::new(event.into(), self.window_id)); } } + +#[cfg(not(windows))] +pub fn foreground_process_path() -> Result<PathBuf, Box<dyn Error>> { + let mut pid = unsafe { libc::tcgetpgrp(tty::master_fd()) }; + if pid < 0 { + pid = tty::child_pid(); + } + + #[cfg(not(any(target_os = "macos", target_os = "freebsd")))] + let link_path = format!("/proc/{}/cwd", pid); + #[cfg(target_os = "freebsd")] + let link_path = format!("/compat/linux/proc/{}/cwd", pid); + + #[cfg(not(target_os = "macos"))] + let cwd = std::fs::read_link(link_path)?; + + #[cfg(target_os = "macos")] + let cwd = macos::proc::cwd(pid)?; + + Ok(cwd) +} diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index ca5742ee..07b3154c 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -29,7 +29,7 @@ use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode}; use alacritty_terminal::vi_mode::ViMotion; use crate::clipboard::Clipboard; -use crate::config::{Action, BindingMode, Config, Key, MouseAction, SearchAction, ViAction}; +use crate::config::{Action, BindingMode, Key, MouseAction, SearchAction, UiConfig, ViAction}; use crate::daemon::start_daemon; use crate::display::hint::HintMatch; use crate::display::window::Window; @@ -85,7 +85,7 @@ pub trait ActionContext<T: EventListener> { fn reset_font_size(&mut self) {} fn pop_message(&mut self) {} fn message(&self) -> Option<&Message>; - fn config(&self) -> &Config; + fn config(&self) -> &UiConfig; fn event_loop(&self) -> &EventLoopWindowTarget<Event>; fn mouse_mode(&self) -> bool; fn clipboard_mut(&mut self) -> &mut Clipboard; @@ -515,7 +515,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { self.ctx.mouse_mut().last_click_timestamp = now; // Update multi-click state. - let mouse_config = &self.ctx.config().ui_config.mouse; + let mouse_config = &self.ctx.config().mouse; self.ctx.mouse_mut().click_state = match self.ctx.mouse().click_state { // Reset click state if button has changed. _ if button != self.ctx.mouse().last_click_button => { @@ -643,7 +643,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { .contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL) && !self.ctx.modifiers().shift() { - let multiplier = f64::from(self.ctx.config().scrolling.multiplier); + let multiplier = f64::from(self.ctx.config().terminal_config.scrolling.multiplier); self.ctx.mouse_mut().scroll_px += new_scroll_px * multiplier; let cmd = if new_scroll_px > 0. { b'A' } else { b'B' }; @@ -657,7 +657,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { } self.ctx.write_to_pty(content); } else { - let multiplier = f64::from(self.ctx.config().scrolling.multiplier); + let multiplier = f64::from(self.ctx.config().terminal_config.scrolling.multiplier); self.ctx.mouse_mut().scroll_px += new_scroll_px * multiplier; let lines = self.ctx.mouse().scroll_px / height; @@ -801,7 +801,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { c.encode_utf8(&mut bytes[..]); } - if self.ctx.config().ui_config.alt_send_esc + if self.ctx.config().alt_send_esc && *self.ctx.received_count() == 0 && self.ctx.modifiers().alt() && utf8_len == 1 @@ -823,8 +823,8 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { let mods = *self.ctx.modifiers(); let mut suppress_chars = None; - for i in 0..self.ctx.config().ui_config.key_bindings().len() { - let binding = &self.ctx.config().ui_config.key_bindings()[i]; + for i in 0..self.ctx.config().key_bindings().len() { + let binding = &self.ctx.config().key_bindings()[i]; let key = match (binding.trigger, input.virtual_keycode) { (Key::Scancode(_), _) => Key::Scancode(input.scancode), @@ -854,8 +854,8 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { let mouse_mode = self.ctx.mouse_mode(); let mods = *self.ctx.modifiers(); - for i in 0..self.ctx.config().ui_config.mouse_bindings().len() { - let mut binding = self.ctx.config().ui_config.mouse_bindings()[i].clone(); + for i in 0..self.ctx.config().mouse_bindings().len() { + let mut binding = self.ctx.config().mouse_bindings()[i].clone(); // Require shift for all modifiers when mouse mode is active. if mouse_mode { @@ -974,7 +974,7 @@ mod tests { pub received_count: usize, pub suppress_chars: bool, pub modifiers: ModifiersState, - config: &'a Config, + config: &'a UiConfig, } impl<'a, T: EventListener> super::ActionContext<T> for ActionContext<'a, T> { @@ -1057,7 +1057,7 @@ mod tests { self.message_buffer.message() } - fn config(&self) -> &Config { + fn config(&self) -> &UiConfig { self.config } @@ -1085,7 +1085,7 @@ mod tests { #[test] fn $name() { let mut clipboard = Clipboard::new_nop(); - let cfg = Config::default(); + let cfg = UiConfig::default(); let size = SizeInfo::new( 21.0, 51.0, @@ -1096,7 +1096,7 @@ mod tests { false, ); - let mut terminal = Term::new(&cfg, size, MockEventProxy); + let mut terminal = Term::new(&cfg.terminal_config, size, MockEventProxy); let mut mouse = Mouse { click_state: $initial_state, diff --git a/alacritty/src/ipc.rs b/alacritty/src/ipc.rs index 02aaf85f..6f02e757 100644 --- a/alacritty/src/ipc.rs +++ b/alacritty/src/ipc.rs @@ -1,8 +1,8 @@ //! Alacritty socket IPC. use std::ffi::OsStr; -use std::io::{Error as IoError, ErrorKind, Result as IoResult}; -use std::os::unix::net::UnixDatagram; +use std::io::{BufRead, BufReader, Error as IoError, ErrorKind, Result as IoResult, Write}; +use std::os::unix::net::{UnixListener, UnixStream}; use std::path::PathBuf; use std::{env, fs, process}; @@ -11,12 +11,9 @@ use log::warn; use alacritty_terminal::thread; -use crate::cli::Options; +use crate::cli::{Options, SocketMessage}; use crate::event::{Event, EventType}; -/// IPC socket message for creating a new window. -pub const SOCKET_MESSAGE_CREATE_WINDOW: [u8; 1] = [1]; - /// Environment variable name for the IPC socket path. const ALACRITTY_SOCKET_ENV: &str = "ALACRITTY_SOCKET"; @@ -30,8 +27,8 @@ pub fn spawn_ipc_socket(options: &Options, event_proxy: EventLoopProxy<Event>) - }); env::set_var(ALACRITTY_SOCKET_ENV, socket_path.as_os_str()); - let socket = match UnixDatagram::bind(&socket_path) { - Ok(socket) => socket, + let listener = match UnixListener::bind(&socket_path) { + Ok(listener) => listener, Err(err) => { warn!("Unable to create socket: {:?}", err); return None; @@ -40,13 +37,31 @@ pub fn spawn_ipc_socket(options: &Options, event_proxy: EventLoopProxy<Event>) - // Spawn a thread to listen on the IPC socket. thread::spawn_named("socket listener", move || { - // Accept up to 2 bytes to ensure only one byte is received. - // This ensures forward-compatibility. - let mut buf = [0; 2]; - - while let Ok(received) = socket.recv(&mut buf) { - if buf[..received] == SOCKET_MESSAGE_CREATE_WINDOW { - let _ = event_proxy.send_event(Event::new(EventType::CreateWindow, None)); + let mut data = String::new(); + for stream in listener.incoming().filter_map(Result::ok) { + data.clear(); + let mut stream = BufReader::new(stream); + + match stream.read_line(&mut data) { + Ok(0) | Err(_) => continue, + Ok(_) => (), + }; + + // Read pending events on socket. + let message: SocketMessage = match serde_json::from_str(&data) { + Ok(message) => message, + Err(err) => { + warn!("Failed to convert data from socket: {}", err); + continue; + }, + }; + + // Handle IPC events. + match message { + SocketMessage::CreateWindow(terminal_options) => { + let event = Event::new(EventType::CreateWindow(Some(terminal_options)), None); + let _ = event_proxy.send_event(event); + }, } } }); @@ -55,9 +70,13 @@ pub fn spawn_ipc_socket(options: &Options, event_proxy: EventLoopProxy<Event>) - } /// Send a message to the active Alacritty socket. -pub fn send_message(socket: Option<PathBuf>, message: &[u8]) -> IoResult<()> { - let socket = find_socket(socket)?; - socket.send(message)?; +pub fn send_message(socket: Option<PathBuf>, message: SocketMessage) -> IoResult<()> { + let mut socket = find_socket(socket)?; + + let message = serde_json::to_string(&message)?; + socket.write_all(message[..].as_bytes())?; + let _ = socket.flush(); + Ok(()) } @@ -78,22 +97,20 @@ fn socket_dir() -> PathBuf { } /// Find the IPC socket path. -fn find_socket(socket_path: Option<PathBuf>) -> IoResult<UnixDatagram> { - let socket = UnixDatagram::unbound()?; - +fn find_socket(socket_path: Option<PathBuf>) -> IoResult<UnixStream> { // Handle --socket CLI override. if let Some(socket_path) = socket_path { // Ensure we inform the user about an invalid path. - socket.connect(&socket_path).map_err(|err| { + return UnixStream::connect(&socket_path).map_err(|err| { let message = format!("invalid socket path {:?}", socket_path); IoError::new(err.kind(), message) - })?; + }); } // Handle environment variable. if let Ok(path) = env::var(ALACRITTY_SOCKET_ENV) { let socket_path = PathBuf::from(path); - if socket.connect(&socket_path).is_ok() { + if let Ok(socket) = UnixStream::connect(&socket_path) { return Ok(socket); } } @@ -114,8 +131,8 @@ fn find_socket(socket_path: Option<PathBuf>) -> IoResult<UnixDatagram> { } // Attempt to connect to the socket. - match socket.connect(&path) { - Ok(_) => return Ok(socket), + match UnixStream::connect(&path) { + Ok(socket) => return Ok(socket), // Delete orphan sockets. Err(error) if error.kind() == ErrorKind::ConnectionRefused => { let _ = fs::remove_file(&path); diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs index 74e27b84..a5624a7b 100644 --- a/alacritty/src/main.rs +++ b/alacritty/src/main.rs @@ -53,10 +53,8 @@ mod gl { use crate::cli::Options; #[cfg(unix)] use crate::cli::{MessageOptions, Subcommands}; -use crate::config::{monitor, Config}; +use crate::config::{monitor, UiConfig}; use crate::event::{Event, Processor}; -#[cfg(unix)] -use crate::ipc::SOCKET_MESSAGE_CREATE_WINDOW; #[cfg(target_os = "macos")] use crate::macos::locale; @@ -94,7 +92,7 @@ fn main() { /// `msg` subcommand entrypoint. #[cfg(unix)] fn msg(options: MessageOptions) -> Result<(), String> { - ipc::send_message(options.socket, &SOCKET_MESSAGE_CREATE_WINDOW).map_err(|err| err.to_string()) + ipc::send_message(options.socket, options.message).map_err(|err| err.to_string()) } /// Temporary files stored for Alacritty. @@ -142,10 +140,10 @@ fn alacritty(options: Options) -> Result<(), String> { log_config_path(&config); // Update the log level from config. - log::set_max_level(config.ui_config.debug.log_level); + log::set_max_level(config.debug.log_level); // Set environment variables. - tty::setup_env(&config); + tty::setup_env(&config.terminal_config); // Switch to home directory. #[cfg(target_os = "macos")] @@ -159,20 +157,20 @@ fn alacritty(options: Options) -> Result<(), String> { // // The monitor watches the config file for changes and reloads it. Pending // config changes are processed in the main loop. - if config.ui_config.live_config_reload { - monitor::watch(config.ui_config.config_paths.clone(), window_event_loop.create_proxy()); + 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.ui_config.ipc_socket { + 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.ui_config.debug.persistent_logging); + let log_cleanup = log_file.filter(|_| !config.debug.persistent_logging); let _files = TemporaryFiles { #[cfg(unix)] socket_path, @@ -184,7 +182,7 @@ fn alacritty(options: Options) -> Result<(), String> { // Create the first Alacritty window. let proxy = window_event_loop.create_proxy(); - processor.create_window(&window_event_loop, proxy).map_err(|err| err.to_string())?; + processor.create_window(&window_event_loop, None, proxy).map_err(|err| err.to_string())?; info!("Initialisation complete"); @@ -217,13 +215,13 @@ fn alacritty(options: Options) -> Result<(), String> { Ok(()) } -fn log_config_path(config: &Config) { - if config.ui_config.config_paths.is_empty() { +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.ui_config.config_paths { + for path in &config.config_paths { msg.push_str(&format!("\n {:?}", path.display())); } diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs index b9ec728c..eaaa97bb 100644 --- a/alacritty/src/renderer/mod.rs +++ b/alacritty/src/renderer/mod.rs @@ -673,7 +673,7 @@ impl QuadRenderer { gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); // Restore viewport with padding. - self.set_viewport(&size_info); + self.set_viewport(size_info); } } diff --git a/alacritty/src/window_context.rs b/alacritty/src/window_context.rs index caa69851..6763420c 100644 --- a/alacritty/src/window_context.rs +++ b/alacritty/src/window_context.rs @@ -1,5 +1,6 @@ //! Terminal window context. +use std::borrow::Cow; use std::error::Error; use std::fs::File; use std::io::Write; @@ -25,8 +26,9 @@ use alacritty_terminal::sync::FairMutex; use alacritty_terminal::term::{Term, TermMode}; use alacritty_terminal::tty; +use crate::cli::TerminalOptions as TerminalCliOptions; use crate::clipboard::Clipboard; -use crate::config::Config; +use crate::config::UiConfig; use crate::display::Display; use crate::event::{ActionContext, Event, EventProxy, EventType, Mouse, SearchState}; use crate::input; @@ -52,7 +54,8 @@ pub struct WindowContext { impl WindowContext { /// Create a new terminal window context. pub fn new( - config: &Config, + config: &UiConfig, + options: Option<TerminalCliOptions>, window_event_loop: &EventLoopWindowTarget<Event>, proxy: EventLoopProxy<Event>, #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] @@ -81,7 +84,7 @@ impl WindowContext { // This object contains all of the state about what's being displayed. It's // wrapped in a clonable mutex since both the I/O loop and display need to // access it. - let terminal = Term::new(config, display.size_info, event_proxy.clone()); + let terminal = Term::new(&config.terminal_config, display.size_info, event_proxy.clone()); let terminal = Arc::new(FairMutex::new(terminal)); // Create the PTY. @@ -89,7 +92,10 @@ impl WindowContext { // The PTY forks a process to run the shell on the slave side of the // pseudoterminal. A file descriptor for the master side is retained for // reading/writing to the shell. - let pty = tty::new(config, &display.size_info, display.window.x11_window_id()); + let pty_config = options + .map(|o| Cow::Owned(o.into())) + .unwrap_or(Cow::Borrowed(&config.terminal_config.pty_config)); + let pty = tty::new(&pty_config, &display.size_info, display.window.x11_window_id())?; // Create the pseudoterminal I/O loop. // @@ -101,8 +107,8 @@ impl WindowContext { Arc::clone(&terminal), event_proxy.clone(), pty, - config.hold, - config.ui_config.debug.ref_test, + pty_config.hold, + config.debug.ref_test, ); // The event loop channel allows write requests from the event processor @@ -113,13 +119,13 @@ impl WindowContext { let _io_thread = event_loop.spawn(); // Start cursor blinking, in case `Focused` isn't sent on startup. - if config.cursor.style().blinking { + if config.terminal_config.cursor.style().blinking { event_proxy.send_event(TerminalEvent::CursorBlinkingChange.into()); } // Create context for the Alacritty window. Ok(WindowContext { - font_size: config.ui_config.font.size(), + font_size: config.font.size(), notifier: Notifier(loop_tx), terminal, display, @@ -135,53 +141,55 @@ impl WindowContext { } /// Update the terminal window to the latest config. - pub fn update_config(&mut self, old_config: &Config, config: &Config) { + pub fn update_config(&mut self, old_config: &UiConfig, config: &UiConfig) { self.display.update_config(config); - self.terminal.lock().update_config(config); + self.terminal.lock().update_config(&config.terminal_config); // Reload cursor if its thickness has changed. - if (old_config.cursor.thickness() - config.cursor.thickness()).abs() > f32::EPSILON { + if (old_config.terminal_config.cursor.thickness() + - config.terminal_config.cursor.thickness()) + .abs() + > f32::EPSILON + { self.display.pending_update.set_cursor_dirty(); } - if old_config.ui_config.font != config.ui_config.font { + if old_config.font != config.font { // Do not update font size if it has been changed at runtime. - if self.font_size == old_config.ui_config.font.size() { - self.font_size = config.ui_config.font.size(); + if self.font_size == old_config.font.size() { + self.font_size = config.font.size(); } - let font = config.ui_config.font.clone().with_size(self.font_size); + let font = config.font.clone().with_size(self.font_size); self.display.pending_update.set_font(font); } // Update display if padding options were changed. - let window_config = &old_config.ui_config.window; - if window_config.padding(1.) != config.ui_config.window.padding(1.) - || window_config.dynamic_padding != config.ui_config.window.dynamic_padding + let window_config = &old_config.window; + if window_config.padding(1.) != config.window.padding(1.) + || window_config.dynamic_padding != config.window.dynamic_padding { self.display.pending_update.dirty = true; } // Live title reload. - if !config.ui_config.window.dynamic_title - || old_config.ui_config.window.title != config.ui_config.window.title - { - self.display.window.set_title(&config.ui_config.window.title); + if !config.window.dynamic_title || old_config.window.title != config.window.title { + self.display.window.set_title(&config.window.title); } #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] - self.display.window.set_wayland_theme(&config.ui_config.colors); + self.display.window.set_wayland_theme(&config.colors); // Set subpixel anti-aliasing. #[cfg(target_os = "macos")] - crossfont::set_font_smoothing(config.ui_config.font.use_thin_strokes); + crossfont::set_font_smoothing(config.font.use_thin_strokes); // Disable shadows for transparent windows on macOS. #[cfg(target_os = "macos")] - self.display.window.set_has_shadow(config.ui_config.window_opacity() >= 1.0); + self.display.window.set_has_shadow(config.window_opacity() >= 1.0); // Update hint keys. - self.display.hint_state.update_alphabet(config.ui_config.hints.alphabet()); + self.display.hint_state.update_alphabet(config.hints.alphabet()); // Update cursor blinking. let event = Event::new(TerminalEvent::CursorBlinkingChange.into(), None); @@ -195,7 +203,7 @@ impl WindowContext { &mut self, event_loop: &EventLoopWindowTarget<Event>, event_proxy: &EventLoopProxy<Event>, - config: &mut Config, + config: &mut UiConfig, clipboard: &mut Clipboard, scheduler: &mut Scheduler, event: GlutinEvent<'_, Event>, @@ -334,7 +342,7 @@ impl WindowContext { message_buffer: &MessageBuffer, search_state: &SearchState, old_is_searching: bool, - config: &Config, + config: &UiConfig, ) { // Compute cursor positions before resize. let num_lines = terminal.screen_lines(); diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs index 0b313598..09161e03 100644 --- a/alacritty_terminal/src/config/mod.rs +++ b/alacritty_terminal/src/config/mod.rs @@ -15,37 +15,43 @@ pub use crate::config::scrolling::Scrolling; pub const LOG_TARGET_CONFIG: &str = "alacritty_config_derive"; const MIN_BLINK_INTERVAL: u64 = 10; -pub type MockConfig = Config<HashMap<String, serde_yaml::Value>>; - /// Top-level config type. #[derive(ConfigDeserialize, Debug, PartialEq, Default)] -pub struct Config<T> { +pub struct Config { /// TERM env variable. pub env: HashMap<String, String>, pub selection: Selection, - /// Path to a shell program to run on startup. - pub shell: Option<Program>, - /// How much scrolling history to keep. pub scrolling: Scrolling, /// Cursor configuration. pub cursor: Cursor, + #[config(flatten)] + pub pty_config: PtyConfig, +} + +#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Default)] +pub struct PtyConfig { + /// Path to a shell program to run on startup. + pub shell: Option<Program>, + /// Shell startup directory. pub working_directory: Option<PathBuf>, - /// Additional configuration options not directly required by the terminal. - #[config(flatten)] - pub ui_config: T, - /// Remain open after child process exits. #[config(skip)] pub hold: bool, } +impl PtyConfig { + pub fn new() -> Self { + Default::default() + } +} + #[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)] pub struct Selection { pub semantic_escape_chars: String, diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs index 47a49910..f00622d1 100644 --- a/alacritty_terminal/src/selection.rs +++ b/alacritty_terminal/src/selection.rs @@ -394,13 +394,13 @@ impl Selection { mod tests { use super::*; - use crate::config::MockConfig; + use crate::config::Config; use crate::index::{Column, Point, Side}; use crate::term::{SizeInfo, Term}; fn term(height: usize, width: usize) -> Term<()> { let size = SizeInfo::new(width as f32, height as f32, 1.0, 1.0, 0.0, 0.0, false); - Term::new(&MockConfig::default(), size, ()) + Term::new(&Config::default(), size, ()) } /// Test case of single cell selection. diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index 894bd763..9b7a4c35 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -288,7 +288,7 @@ impl<T> Term<T> { self.vi_mode_recompute_selection(); } - pub fn new<C>(config: &Config<C>, size: SizeInfo, event_proxy: T) -> Term<T> { + pub fn new(config: &Config, size: SizeInfo, event_proxy: T) -> Term<T> { let num_cols = size.columns; let num_lines = size.screen_lines; @@ -323,7 +323,7 @@ impl<T> Term<T> { } } - pub fn update_config<C>(&mut self, config: &Config<C>) + pub fn update_config(&mut self, config: &Config) where T: EventListener, { @@ -1903,7 +1903,7 @@ pub mod test { // Create terminal with the appropriate dimensions. let size = SizeInfo::new(num_cols as f32, lines.len() as f32, 1., 1., 0., 0., false); - let mut term = Term::new(&Config::<()>::default(), size, ()); + let mut term = Term::new(&Config::default(), size, ()); // Fill terminal with content. for (line, text) in lines.iter().enumerate() { @@ -1938,7 +1938,7 @@ mod tests { use std::mem; use crate::ansi::{self, CharsetIndex, Handler, StandardCharset}; - use crate::config::MockConfig; + use crate::config::Config; use crate::grid::{Grid, Scroll}; use crate::index::{Column, Point, Side}; use crate::selection::{Selection, SelectionType}; @@ -1947,7 +1947,7 @@ mod tests { #[test] fn scroll_display_page_up() { let size = SizeInfo::new(5., 10., 1.0, 1.0, 0.0, 0.0, false); - let mut term = Term::new(&MockConfig::default(), size, ()); + let mut term = Term::new(&Config::default(), size, ()); // Create 11 lines of scrollback. for _ in 0..20 { @@ -1973,7 +1973,7 @@ mod tests { #[test] fn scroll_display_page_down() { let size = SizeInfo::new(5., 10., 1.0, 1.0, 0.0, 0.0, false); - let mut term = Term::new(&MockConfig::default(), size, ()); + let mut term = Term::new(&Config::default(), size, ()); // Create 11 lines of scrollback. for _ in 0..20 { @@ -2003,7 +2003,7 @@ mod tests { #[test] fn semantic_selection_works() { let size = SizeInfo::new(5., 3., 1.0, 1.0, 0.0, 0.0, false); - let mut term = Term::new(&MockConfig::default(), size, ()); + let mut term = Term::new(&Config::default(), size, ()); let mut grid: Grid<Cell> = Grid::new(3, 5, 0); for i in 0..5 { for j in 0..2 { @@ -2051,7 +2051,7 @@ mod tests { #[test] fn line_selection_works() { let size = SizeInfo::new(5., 1., 1.0, 1.0, 0.0, 0.0, false); - let mut term = Term::new(&MockConfig::default(), size, ()); + let mut term = Term::new(&Config::default(), size, ()); let mut grid: Grid<Cell> = Grid::new(1, 5, 0); for i in 0..5 { grid[Line(0)][Column(i)].c = 'a'; @@ -2072,7 +2072,7 @@ mod tests { #[test] fn selecting_empty_line() { let size = SizeInfo::new(3.0, 3.0, 1.0, 1.0, 0.0, 0.0, false); - let mut term = Term::new(&MockConfig::default(), size, ()); + let mut term = Term::new(&Config::default(), size, ()); let mut grid: Grid<Cell> = Grid::new(3, 3, 0); for l in 0..3 { if l != 1 { @@ -2110,7 +2110,7 @@ mod tests { #[test] fn input_line_drawing_character() { let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false); - let mut term = Term::new(&MockConfig::default(), size, ()); + let mut term = Term::new(&Config::default(), size, ()); let cursor = Point::new(Line(0), Column(0)); term.configure_charset(CharsetIndex::G0, StandardCharset::SpecialCharacterAndLineDrawing); term.input('a'); @@ -2121,7 +2121,7 @@ mod tests { #[test] fn clear_saved_lines() { let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false); - let mut term = Term::new(&MockConfig::default(), size, ()); + let mut term = Term::new(&Config::default(), size, ()); // Add one line of scrollback. term.grid.scroll_up(&(Line(0)..Line(1)), 1); @@ -2143,7 +2143,7 @@ mod tests { #[test] fn grow_lines_updates_active_cursor_pos() { let mut size = SizeInfo::new(100.0, 10.0, 1.0, 1.0, 0.0, 0.0, false); - let mut term = Term::new(&MockConfig::default(), size, ()); + let mut term = Term::new(&Config::default(), size, ()); // Create 10 lines of scrollback. for _ in 0..19 { @@ -2163,7 +2163,7 @@ mod tests { #[test] fn grow_lines_updates_inactive_cursor_pos() { let mut size = SizeInfo::new(100.0, 10.0, 1.0, 1.0, 0.0, 0.0, false); - let mut term = Term::new(&MockConfig::default(), size, ()); + let mut term = Term::new(&Config::default(), size, ()); // Create 10 lines of scrollback. for _ in 0..19 { @@ -2189,7 +2189,7 @@ mod tests { #[test] fn shrink_lines_updates_active_cursor_pos() { let mut size = SizeInfo::new(100.0, 10.0, 1.0, 1.0, 0.0, 0.0, false); - let mut term = Term::new(&MockConfig::default(), size, ()); + let mut term = Term::new(&Config::default(), size, ()); // Create 10 lines of scrollback. for _ in 0..19 { @@ -2209,7 +2209,7 @@ mod tests { #[test] fn shrink_lines_updates_inactive_cursor_pos() { let mut size = SizeInfo::new(100.0, 10.0, 1.0, 1.0, 0.0, 0.0, false); - let mut term = Term::new(&MockConfig::default(), size, ()); + let mut term = Term::new(&Config::default(), size, ()); // Create 10 lines of scrollback. for _ in 0..19 { @@ -2235,7 +2235,7 @@ mod tests { #[test] fn window_title() { let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false); - let mut term = Term::new(&MockConfig::default(), size, ()); + let mut term = Term::new(&Config::default(), size, ()); // Title None by default. assert_eq!(term.title, None); diff --git a/alacritty_terminal/src/term/search.rs b/alacritty_terminal/src/term/search.rs index 08de602f..07403b67 100644 --- a/alacritty_terminal/src/term/search.rs +++ b/alacritty_terminal/src/term/search.rs @@ -785,7 +785,7 @@ mod tests { #[test] fn wide_without_spacer() { let size = SizeInfo::new(2., 2., 1., 1., 0., 0., false); - let mut term = Term::new(&Config::<()>::default(), size, ()); + let mut term = Term::new(&Config::default(), size, ()); term.grid[Line(0)][Column(0)].c = 'x'; term.grid[Line(0)][Column(1)].c = '字'; term.grid[Line(0)][Column(1)].flags = Flags::WIDE_CHAR; diff --git a/alacritty_terminal/src/tty/mod.rs b/alacritty_terminal/src/tty/mod.rs index a1c8c0c1..cae6a4b9 100644 --- a/alacritty_terminal/src/tty/mod.rs +++ b/alacritty_terminal/src/tty/mod.rs @@ -60,7 +60,7 @@ pub trait EventedPty: EventedReadWrite { } /// Setup environment variables. -pub fn setup_env<C>(config: &Config<C>) { +pub fn setup_env(config: &Config) { // Default to 'alacritty' terminfo if it is available, otherwise // default to 'xterm-256color'. May be overridden by user's config // below. diff --git a/alacritty_terminal/src/tty/unix.rs b/alacritty_terminal/src/tty/unix.rs index 697cb2d7..0d123b8c 100644 --- a/alacritty_terminal/src/tty/unix.rs +++ b/alacritty_terminal/src/tty/unix.rs @@ -5,12 +5,13 @@ use std::borrow::Cow; use std::env; use std::ffi::CStr; use std::fs::File; +use std::io::{Error, ErrorKind, Result}; use std::mem::MaybeUninit; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::process::CommandExt; use std::process::{Child, Command, Stdio}; +use std::ptr; use std::sync::atomic::{AtomicI32, AtomicUsize, Ordering}; -use std::{io, ptr}; use libc::{self, c_int, pid_t, winsize, TIOCSCTTY}; use log::error; @@ -21,7 +22,7 @@ use nix::sys::termios::{self, InputFlags, SetArg}; use signal_hook::consts as sigconsts; use signal_hook_mio::v0_6::Signals; -use crate::config::{Config, Program}; +use crate::config::{Program, PtyConfig}; use crate::event::OnResize; use crate::grid::Dimensions; use crate::term::SizeInfo; @@ -73,7 +74,7 @@ fn set_controlling_terminal(fd: c_int) { }; if res < 0 { - die!("ioctl TIOCSCTTY failed: {}", io::Error::last_os_error()); + die!("ioctl TIOCSCTTY failed: {}", Error::last_os_error()); } } @@ -143,7 +144,7 @@ fn default_shell(pw: &Passwd<'_>) -> Program { } /// Create a new TTY and return a handle to interact with it. -pub fn new<C>(config: &Config<C>, size: &SizeInfo, window_id: Option<usize>) -> Pty { +pub fn new(config: &PtyConfig, size: &SizeInfo, window_id: Option<usize>) -> Result<Pty> { let (master, slave) = make_pty(size.to_winsize()); #[cfg(any(target_os = "linux", target_os = "macos"))] @@ -192,7 +193,7 @@ pub fn new<C>(config: &Config<C>, size: &SizeInfo, window_id: Option<usize>) -> // Create a new process group. let err = libc::setsid(); if err == -1 { - die!("Failed to set session id: {}", io::Error::last_os_error()); + return Err(Error::new(ErrorKind::Other, "Failed to set session id")); } set_controlling_terminal(slave); @@ -240,9 +241,12 @@ pub fn new<C>(config: &Config<C>, size: &SizeInfo, window_id: Option<usize>) -> signals_token: mio::Token::from(0), }; pty.on_resize(size); - pty + Ok(pty) }, - Err(err) => die!("Failed to spawn command '{}': {}", shell.program(), err), + Err(err) => Err(Error::new( + ErrorKind::NotFound, + format!("Failed to spawn command '{}': {}", shell.program(), err), + )), } } @@ -267,7 +271,7 @@ impl EventedReadWrite for Pty { token: &mut dyn Iterator<Item = mio::Token>, interest: mio::Ready, poll_opts: mio::PollOpt, - ) -> io::Result<()> { + ) -> Result<()> { self.token = token.next().unwrap(); poll.register(&EventedFd(&self.fd.as_raw_fd()), self.token, interest, poll_opts)?; @@ -286,7 +290,7 @@ impl EventedReadWrite for Pty { poll: &mio::Poll, interest: mio::Ready, poll_opts: mio::PollOpt, - ) -> io::Result<()> { + ) -> Result<()> { poll.reregister(&EventedFd(&self.fd.as_raw_fd()), self.token, interest, poll_opts)?; poll.reregister( @@ -298,7 +302,7 @@ impl EventedReadWrite for Pty { } #[inline] - fn deregister(&mut self, poll: &mio::Poll) -> io::Result<()> { + fn deregister(&mut self, poll: &mio::Poll) -> Result<()> { poll.deregister(&EventedFd(&self.fd.as_raw_fd()))?; poll.deregister(&self.signals) } @@ -360,7 +364,7 @@ impl OnResize for Pty { let res = unsafe { libc::ioctl(self.fd.as_raw_fd(), libc::TIOCSWINSZ, &win as *const _) }; if res < 0 { - die!("ioctl TIOCSWINSZ failed: {}", io::Error::last_os_error()); + die!("ioctl TIOCSWINSZ failed: {}", Error::last_os_error()); } } } diff --git a/alacritty_terminal/src/tty/windows/conpty.rs b/alacritty_terminal/src/tty/windows/conpty.rs index 002022ea..9556be8b 100644 --- a/alacritty_terminal/src/tty/windows/conpty.rs +++ b/alacritty_terminal/src/tty/windows/conpty.rs @@ -15,7 +15,7 @@ use winapi::um::processthreadsapi::{ use winapi::um::winbase::{EXTENDED_STARTUPINFO_PRESENT, STARTF_USESTDHANDLES, STARTUPINFOEXW}; use winapi::um::wincontypes::{COORD, HPCON}; -use crate::config::Config; +use crate::config::PtyConfig; use crate::event::OnResize; use crate::grid::Dimensions; use crate::term::SizeInfo; @@ -40,7 +40,7 @@ impl Drop for Conpty { // The ConPTY handle can be sent between threads. unsafe impl Send for Conpty {} -pub fn new<C>(config: &Config<C>, size: &SizeInfo) -> Option<Pty> { +pub fn new(config: &PtyConfig, size: &SizeInfo) -> Option<Pty> { let mut pty_handle = 0 as HPCON; // Passing 0 as the size parameter allows the "system default" buffer @@ -136,7 +136,7 @@ pub fn new<C>(config: &Config<C>, size: &SizeInfo) -> Option<Pty> { } } - let cmdline = win32_string(&cmdline(&config)); + let cmdline = win32_string(&cmdline(config)); let cwd = config.working_directory.as_ref().map(win32_string); let mut proc_info: PROCESS_INFORMATION = Default::default(); diff --git a/alacritty_terminal/src/tty/windows/mod.rs b/alacritty_terminal/src/tty/windows/mod.rs index 644253f4..0cc6a8bc 100644 --- a/alacritty_terminal/src/tty/windows/mod.rs +++ b/alacritty_terminal/src/tty/windows/mod.rs @@ -1,10 +1,10 @@ use std::ffi::OsStr; -use std::io; +use std::io::{self, Error, ErrorKind, Result}; use std::iter::once; use std::os::windows::ffi::OsStrExt; use std::sync::mpsc::TryRecvError; -use crate::config::{Config, Program}; +use crate::config::{Program, PtyConfig}; use crate::event::OnResize; use crate::term::SizeInfo; use crate::tty::windows::child::ChildExitWatcher; @@ -28,8 +28,8 @@ pub struct Pty { child_watcher: ChildExitWatcher, } -pub fn new<C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>) -> Pty { - conpty::new(config, size).expect("Failed to create ConPTY backend") +pub fn new(config: &PtyConfig, size: &SizeInfo, _window_id: Option<usize>) -> Result<Pty> { + conpty::new(config, size).ok_or_else(|| Error::new(ErrorKind::Other, "failed to spawn conpty")) } impl Pty { @@ -165,11 +165,11 @@ impl OnResize for Pty { } } -fn cmdline<C>(config: &Config<C>) -> String { +fn cmdline(config: &PtyConfig) -> String { let default_shell = Program::Just("powershell".to_owned()); let shell = config.shell.as_ref().unwrap_or(&default_shell); - once(shell.program().as_ref()) + once(shell.program()) .chain(shell.args().iter().map(|a| a.as_ref())) .collect::<Vec<_>>() .join(" ") diff --git a/alacritty_terminal/src/vi_mode.rs b/alacritty_terminal/src/vi_mode.rs index 5bf5eaed..de5c61b5 100644 --- a/alacritty_terminal/src/vi_mode.rs +++ b/alacritty_terminal/src/vi_mode.rs @@ -379,13 +379,13 @@ mod tests { use super::*; use crate::ansi::Handler; - use crate::config::MockConfig; + use crate::config::Config; use crate::index::{Column, Line}; use crate::term::{SizeInfo, Term}; fn term() -> Term<()> { let size = SizeInfo::new(20., 20., 1.0, 1.0, 0.0, 0.0, false); - Term::new(&MockConfig::default(), size, ()) + Term::new(&Config::default(), size, ()) } #[test] diff --git a/alacritty_terminal/tests/ref.rs b/alacritty_terminal/tests/ref.rs index a9968736..28b0dd19 100644 --- a/alacritty_terminal/tests/ref.rs +++ b/alacritty_terminal/tests/ref.rs @@ -6,7 +6,7 @@ use std::io::Read; use std::path::Path; use alacritty_terminal::ansi; -use alacritty_terminal::config::MockConfig; +use alacritty_terminal::config::Config; use alacritty_terminal::event::{Event, EventListener}; use alacritty_terminal::grid::{Dimensions, Grid}; use alacritty_terminal::index::{Column, Line}; @@ -101,7 +101,7 @@ fn ref_test(dir: &Path) { let grid: Grid<Cell> = json::from_str(&serialized_grid).unwrap(); let ref_config: RefConfig = json::from_str(&serialized_cfg).unwrap(); - let mut config = MockConfig::default(); + let mut config = Config::default(); config.scrolling.set_history(ref_config.history_size); let mut terminal = Term::new(&config, size, Mock); diff --git a/extra/alacritty-msg.man b/extra/alacritty-msg.man index 818169d0..5ccb3014 100644 --- a/extra/alacritty-msg.man +++ b/extra/alacritty-msg.man @@ -2,18 +2,28 @@ .SH NAME alacritty-msg \- Send messages to Alacritty .SH "SYNOPSIS" -alacritty msg [OPTIONS] [MESSAGES] +alacritty msg [OPTIONS] <MESSAGE> [MESSAGE_OPTIONS] .SH DESCRIPTION This command communicates with running Alacritty instances through a socket, making it possible to control Alacritty without directly accessing it. .SH "OPTIONS" -.TP \fB\-s\fR, \fB\-\-socket\fR <socket> Path for IPC socket creation .SH "MESSAGES" -.TP \fBcreate-window\fR Create a new window in the same Alacritty process +.TP +.SH "\tOPTIONS" +.RS 12 +\fB\-\-hold\fR +Remain open after child process exits + +\fB\-\-working\-directory\fR <working\-directory> +Start the shell in the specified working directory + +\fB\-e\fR, \fB\-\-command\fR <command>... +Command and args to execute (must be last argument) +.RE .SH "SEE ALSO" See the alacritty github repository at https://github.com/alacritty/alacritty for the full documentation. .SH "BUGS" diff --git a/extra/completions/_alacritty b/extra/completions/_alacritty index 32086910..59ed92b2 100644 --- a/extra/completions/_alacritty +++ b/extra/completions/_alacritty @@ -19,18 +19,18 @@ _alacritty() { '--title=[Defines the window title \[default: Alacritty\]]' \ '--class=[Defines window class/app_id on X11/Wayland \[default: Alacritty\]]' \ '--embed=[Defines the X11 window ID (as a decimal integer) to embed Alacritty within]' \ -'--working-directory=[Start the shell in the specified working directory]' \ '--config-file=[Specify alternative configuration file \[default: $XDG_CONFIG_HOME/alacritty/alacritty.yml\]]' \ '--socket=[Path for IPC socket creation]' \ -'*-e+[Command and args to execute (must be last argument)]' \ -'*--command=[Command and args to execute (must be last argument)]' \ '*-o+[Override configuration file options \[example: cursor.style=Beam\]]' \ '*--option=[Override configuration file options \[example: cursor.style=Beam\]]' \ +'--working-directory=[Start the shell in the specified working directory]' \ +'*-e+[Command and args to execute (must be last argument)]' \ +'*--command=[Command and args to execute (must be last argument)]' \ '--print-events[Print all events to stdout]' \ '--ref-test[Generates ref test]' \ -'--hold[Remain open after child process exits]' \ '(-v)*-q[Reduces the level of verbosity (the min level is -qq)]' \ '(-q)*-v[Increases the level of verbosity (the max level is -vvv)]' \ +'--hold[Remain open after child process exit]' \ '-h[Prints help information]' \ '--help[Prints help information]' \ '-V[Prints version information]' \ @@ -63,6 +63,10 @@ case $state in case $line[1] in (create-window) _arguments "${_arguments_options[@]}" \ +'--working-directory=[Start the shell in the specified working directory]' \ +'*-e+[Command and args to execute (must be last argument)]' \ +'*--command=[Command and args to execute (must be last argument)]' \ +'--hold[Remain open after child process exit]' \ '-h[Prints help information]' \ '--help[Prints help information]' \ '-V[Prints version information]' \ diff --git a/extra/completions/alacritty.bash b/extra/completions/alacritty.bash index 0a9b286e..7ce57f9d 100644 --- a/extra/completions/alacritty.bash +++ b/extra/completions/alacritty.bash @@ -29,7 +29,7 @@ _alacritty() { case "${cmd}" in alacritty) - opts=" -q -v -h -V -t -e -o --print-events --ref-test --hold --help --version --title --class --embed --working-directory --config-file --socket --command --option msg help" + opts=" -q -v -h -V -t -o -e --print-events --ref-test --hold --help --version --title --class --embed --config-file --socket --option --working-directory --command msg help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -52,31 +52,31 @@ _alacritty() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --working-directory) + --config-file) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --config-file) + --socket) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --socket) + --option) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --command) + -o) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - -e) + --working-directory) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --option) + --command) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - -o) + -e) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; @@ -127,13 +127,25 @@ _alacritty() { return 0 ;; alacritty__msg__create__window) - opts=" -h -V --help --version " + opts=" -h -V -e --hold --help --version --working-directory --command " if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in + --working-directory) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --command) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -e) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; diff --git a/extra/completions/alacritty.fish b/extra/completions/alacritty.fish index 48f118c2..09cc6c0f 100644 --- a/extra/completions/alacritty.fish +++ b/extra/completions/alacritty.fish @@ -1,16 +1,16 @@ complete -c alacritty -n "__fish_use_subcommand" -s t -l title -d 'Defines the window title [default: Alacritty]' complete -c alacritty -n "__fish_use_subcommand" -l class -d 'Defines window class/app_id on X11/Wayland [default: Alacritty]' complete -c alacritty -n "__fish_use_subcommand" -l embed -d 'Defines the X11 window ID (as a decimal integer) to embed Alacritty within' -complete -c alacritty -n "__fish_use_subcommand" -l working-directory -d 'Start the shell in the specified working directory' complete -c alacritty -n "__fish_use_subcommand" -l config-file -d 'Specify alternative configuration file [default: $XDG_CONFIG_HOME/alacritty/alacritty.yml]' complete -c alacritty -n "__fish_use_subcommand" -l socket -d 'Path for IPC socket creation' -complete -c alacritty -n "__fish_use_subcommand" -s e -l command -d 'Command and args to execute (must be last argument)' complete -c alacritty -n "__fish_use_subcommand" -s o -l option -d 'Override configuration file options [example: cursor.style=Beam]' +complete -c alacritty -n "__fish_use_subcommand" -l working-directory -d 'Start the shell in the specified working directory' +complete -c alacritty -n "__fish_use_subcommand" -s e -l command -d 'Command and args to execute (must be last argument)' complete -c alacritty -n "__fish_use_subcommand" -l print-events -d 'Print all events to stdout' complete -c alacritty -n "__fish_use_subcommand" -l ref-test -d 'Generates ref test' -complete -c alacritty -n "__fish_use_subcommand" -l hold -d 'Remain open after child process exits' complete -c alacritty -n "__fish_use_subcommand" -s q -d 'Reduces the level of verbosity (the min level is -qq)' complete -c alacritty -n "__fish_use_subcommand" -s v -d 'Increases the level of verbosity (the max level is -vvv)' +complete -c alacritty -n "__fish_use_subcommand" -l hold -d 'Remain open after child process exit' complete -c alacritty -n "__fish_use_subcommand" -s h -l help -d 'Prints help information' complete -c alacritty -n "__fish_use_subcommand" -s V -l version -d 'Prints version information' complete -c alacritty -n "__fish_use_subcommand" -f -a "msg" -d 'Available socket messages' @@ -20,6 +20,9 @@ complete -c alacritty -n "__fish_seen_subcommand_from msg" -s h -l help -d 'Prin complete -c alacritty -n "__fish_seen_subcommand_from msg" -s V -l version -d 'Prints version information' complete -c alacritty -n "__fish_seen_subcommand_from msg" -f -a "create-window" -d 'Create a new window in the same Alacritty process' complete -c alacritty -n "__fish_seen_subcommand_from msg" -f -a "help" -d 'Prints this message or the help of the given subcommand(s)' +complete -c alacritty -n "__fish_seen_subcommand_from create-window" -l working-directory -d 'Start the shell in the specified working directory' +complete -c alacritty -n "__fish_seen_subcommand_from create-window" -s e -l command -d 'Command and args to execute (must be last argument)' +complete -c alacritty -n "__fish_seen_subcommand_from create-window" -l hold -d 'Remain open after child process exit' complete -c alacritty -n "__fish_seen_subcommand_from create-window" -s h -l help -d 'Prints help information' complete -c alacritty -n "__fish_seen_subcommand_from create-window" -s V -l version -d 'Prints version information' complete -c alacritty -n "__fish_seen_subcommand_from help" -s h -l help -d 'Prints help information' |