diff --git a/CHANGELOG.md b/CHANGELOG.md index d1bbd42..abc9e48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Block selection mode when Control is held while starting a selection +- Allow setting general window class on X11 using CLI or config (`window.class.general`) +- Config option `window.gtk_theme_variant` to set GTK theme variant ### Fixed diff --git a/alacritty.yml b/alacritty.yml index 73046a6..ea9457c 100644 --- a/alacritty.yml +++ b/alacritty.yml @@ -66,7 +66,17 @@ window: #title: Alacritty # Window class (Linux only): - #class: Alacritty + class: + # Application instance name + instance: Alacritty + # General application class + general: Alacritty + + # GTK theme variant (Linux only) + # + # Override the variant of the GTK theme. Commonly supported values are `dark` and `light`. + # Set this to `None` to use the default theme variant. + gtk_theme_variant: None scrolling: # Maximum number of lines in the scrollback buffer. diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs index 9e7493b..dbd5563 100644 --- a/alacritty/src/cli.rs +++ b/alacritty/src/cli.rs @@ -250,7 +250,14 @@ impl Options { config.window.dimensions = self.dimensions.unwrap_or(config.window.dimensions); config.window.position = self.position.or(config.window.position); config.window.title = self.title.or(config.window.title); - config.window.class = self.class.or(config.window.class); + + if let Some(class) = self.class { + let parts : Vec<_> = class.split(',').collect(); + config.window.class.instance = parts[0].into(); + if let Some(&general) = parts.get(1) { + config.window.class.general = general.into(); + } + } config.set_dynamic_title(config.dynamic_title() && config.window.title.is_none()); diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs index 54af0fd..50606ef 100644 --- a/alacritty_terminal/src/config/mod.rs +++ b/alacritty_terminal/src/config/mod.rs @@ -15,8 +15,10 @@ use std::borrow::Cow; use std::collections::HashMap; use std::path::PathBuf; +use std::fmt::Display; use serde::{Deserialize, Deserializer}; +use serde_yaml::Value; mod bindings; mod colors; @@ -92,7 +94,7 @@ pub struct Config { pub mouse: Mouse, /// Path to a shell program to run on startup - #[serde(default, deserialize_with = "failure_default")] + #[serde(default, deserialize_with = "from_string_or_deserialize")] pub shell: Option>, /// Path where config was loaded from @@ -134,8 +136,8 @@ pub struct Config { alt_send_esc: DefaultTrueBool, /// Shell startup directory - #[serde(default, deserialize_with = "failure_default")] - working_directory: WorkingDirectory, + #[serde(default, deserialize_with = "option_explicit_none")] + working_directory: Option, /// Debug options #[serde(default, deserialize_with = "failure_default")] @@ -212,37 +214,12 @@ impl Config { #[inline] pub fn working_directory(&self) -> &Option { - &self.working_directory.0 + &self.working_directory } #[inline] pub fn set_working_directory(&mut self, working_directory: Option) { - self.working_directory.0 = working_directory; - } -} - -#[derive(Default, Debug, PartialEq, Eq)] -struct WorkingDirectory(Option); - -impl<'de> Deserialize<'de> for WorkingDirectory { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let value = serde_yaml::Value::deserialize(deserializer)?; - - // Accept `None` to use the default path - if value.as_str().filter(|v| v.to_lowercase() == "none").is_some() { - return Ok(WorkingDirectory(None)); - } - - Ok(match PathBuf::deserialize(value) { - Ok(path) => WorkingDirectory(Some(path)), - Err(err) => { - error!("Problem with config: {}; using None", err); - WorkingDirectory(None) - }, - }) + self.working_directory = working_directory; } } @@ -357,6 +334,12 @@ impl<'a> Shell<'a> { } } +impl FromString for Option> { + fn from(input: String) -> Self { + Some(Shell::new(input)) + } +} + /// A delta for a point in a 2 dimensional plane #[serde(default, bound(deserialize = "T: Deserialize<'de> + Default"))] #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)] @@ -418,17 +401,40 @@ impl Default for DefaultTrueBool { } } +fn fallback_default(err: E) -> T + where T: Default, E: Display +{ + error!("Problem with config: {}; using default value", err); + T::default() +} + pub fn failure_default<'a, D, T>(deserializer: D) -> Result where D: Deserializer<'a>, T: Deserialize<'a> + Default, { - let value = serde_yaml::Value::deserialize(deserializer)?; - match T::deserialize(value) { - Ok(value) => Ok(value), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(T::default()) - }, - } + Ok(T::deserialize(Value::deserialize(deserializer)?).unwrap_or_else(fallback_default)) +} + +pub fn option_explicit_none<'de, T, D>(deserializer: D) -> Result, D::Error> + where D: Deserializer<'de>, T: Deserialize<'de> + Default +{ + Ok(match Value::deserialize(deserializer)? { + Value::String(ref value) if value.to_lowercase() == "none" => None, + value => Some(T::deserialize(value).unwrap_or_else(fallback_default)) + }) +} + +pub fn from_string_or_deserialize<'de, T, D>(deserializer: D) -> Result + where D: Deserializer<'de>, T: Deserialize<'de> + FromString + Default +{ + Ok(match Value::deserialize(deserializer)? { + Value::String(value) => T::from(value), + value => T::deserialize(value).unwrap_or_else(fallback_default) + }) +} + +// Used over From, to allow implementation for foreign types +pub trait FromString { + fn from(input: String) -> Self; } diff --git a/alacritty_terminal/src/config/window.rs b/alacritty_terminal/src/config/window.rs index d7f3dcb..351bef1 100644 --- a/alacritty_terminal/src/config/window.rs +++ b/alacritty_terminal/src/config/window.rs @@ -1,5 +1,6 @@ -use crate::config::{failure_default, Delta}; +use crate::config::{failure_default, option_explicit_none, from_string_or_deserialize, Delta, FromString}; use crate::index::{Column, Line}; +use crate::window::DEFAULT_NAME; #[serde(default)] #[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)] @@ -33,8 +34,12 @@ pub struct WindowConfig { pub title: Option, /// Window class - #[serde(deserialize_with = "failure_default")] - pub class: Option, + #[serde(deserialize_with = "from_string_or_deserialize")] + pub class: Class, + + /// GTK theme variant + #[serde(deserialize_with = "option_explicit_none")] + pub gtk_theme_variant: Option, /// TODO: DEPRECATED #[serde(deserialize_with = "failure_default")] @@ -117,3 +122,23 @@ impl Dimensions { self.columns.0 as u32 } } + +/// Window class hint +#[serde(default)] +#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct Class { + pub instance: String, + pub general: String +} + +impl Default for Class { + fn default() -> Self { + Class { instance: DEFAULT_NAME.into(), general: DEFAULT_NAME.into() } + } +} + +impl FromString for Class { + fn from(value: String) -> Self { + Class { instance: value, general: DEFAULT_NAME.into() } + } +} diff --git a/alacritty_terminal/src/window.rs b/alacritty_terminal/src/window.rs index f7eb16c..204a1f7 100644 --- a/alacritty_terminal/src/window.rs +++ b/alacritty_terminal/src/window.rs @@ -151,9 +151,8 @@ impl Window { dimensions: Option, ) -> Result { let title = config.window.title.as_ref().map_or(DEFAULT_NAME, |t| t); - let class = config.window.class.as_ref().map_or(DEFAULT_NAME, |c| c); - let window_builder = Window::get_platform_window(title, class, &config.window); + let window_builder = Window::get_platform_window(title, &config.window); let windowed_context = create_gl_window(window_builder.clone(), &event_loop, false, dimensions) .or_else(|_| create_gl_window(window_builder, &event_loop, true, dimensions))?; @@ -255,7 +254,6 @@ impl Window { #[cfg(not(any(target_os = "macos", windows)))] pub fn get_platform_window( title: &str, - class: &str, window_config: &WindowConfig, ) -> WindowBuilder { use glutin::os::unix::WindowBuilderExt; @@ -267,7 +265,9 @@ impl Window { let icon = Icon::from_bytes_with_format(WINDOW_ICON, ImageFormat::ICO); - WindowBuilder::new() + let class = &window_config.class; + + let mut builder = WindowBuilder::new() .with_title(title) .with_visibility(false) .with_transparency(true) @@ -275,15 +275,20 @@ impl Window { .with_maximized(window_config.startup_mode() == StartupMode::Maximized) .with_window_icon(icon.ok()) // X11 - .with_class(class.into(), DEFAULT_NAME.into()) + .with_class(class.instance.clone(), class.general.clone()) // Wayland - .with_app_id(class.into()) + .with_app_id(class.instance.clone()); + + if let Some(ref val) = window_config.gtk_theme_variant { + builder = builder.with_gtk_theme_variant(val.clone()) + } + + builder } #[cfg(windows)] pub fn get_platform_window( title: &str, - _class: &str, window_config: &WindowConfig, ) -> WindowBuilder { let decorations = match window_config.decorations { @@ -305,7 +310,6 @@ impl Window { #[cfg(target_os = "macos")] pub fn get_platform_window( title: &str, - _class: &str, window_config: &WindowConfig, ) -> WindowBuilder { use glutin::os::macos::WindowBuilderExt; diff --git a/extra/alacritty.man b/extra/alacritty.man index f86589e..dc0f909 100644 --- a/extra/alacritty.man +++ b/extra/alacritty.man @@ -39,8 +39,8 @@ Increases the level of verbosity (the max level is \fB\-vvv\fR) Prints version information .SH "OPTIONS" .TP -\fB\-\-class\fR -Defines the window class on Linux [default: Alacritty] +\fB\-\-class\fR [ | , ] +Defines the window class hint on Linux [default: Alacritty,Alacritty ] .TP \fB\-e\fR, \fB\-\-command\fR ... Command and args to execute (must be last argument)