1
0
Fork 0
mirror of https://github.com/alacritty/alacritty.git synced 2025-09-04 22:43:53 -04:00

Add option to run command on bell

Fixes #1528.
This commit is contained in:
Kirill Chibisov 2020-07-10 22:32:44 +03:00 committed by GitHub
parent b78f3d1339
commit 8bd2c13490
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 297 additions and 207 deletions

View file

@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Expanding existing selections using the right mouse button - Expanding existing selections using the right mouse button
- Support for `gopher` and `gemini` URLs - Support for `gopher` and `gemini` URLs
- Unicode 13 support - Unicode 13 support
- Option to run command on bell which can be set in `bell.command`
### Changed ### Changed
@ -48,6 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- To use the cell's text color for selection with a modified background, the `color.selection.text` - To use the cell's text color for selection with a modified background, the `color.selection.text`
variable must now be set to `CellForeground` instead of omitting it variable must now be set to `CellForeground` instead of omitting it
- URLs are no longer highlighted without a clearly delimited scheme - URLs are no longer highlighted without a clearly delimited scheme
- Renamed `visual_bell` to `bell`
### Fixed ### Fixed

1
Cargo.lock generated
View file

@ -32,6 +32,7 @@ dependencies = [
"gl_generator 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", "gl_generator 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
"glutin 0.24.1 (registry+https://github.com/rust-lang/crates.io-index)", "glutin 0.24.1 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.23.6 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.23.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"notify 4.0.15 (registry+https://github.com/rust-lang/crates.io-index)", "notify 4.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -265,31 +265,46 @@
# #
#indexed_colors: [] #indexed_colors: []
# Visual Bell # Bell
# #
# Any time the BEL code is received, Alacritty "rings" the visual bell. Once # The bell is rung every time the BEL control character is received.
# rung, the terminal background will be set to white and transition back to the #bell:
# default background color. You can control the rate of this transition by # Visual Bell Animation
# setting the `duration` property (represented in milliseconds). You can also #
# configure the transition function by setting the `animation` property. # Animation effect for flashing the screen when the visual bell is rung.
# #
# Values for `animation`: # Values for `animation`:
# - Ease # - Ease
# - EaseOut # - EaseOut
# - EaseOutSine # - EaseOutSine
# - EaseOutQuad # - EaseOutQuad
# - EaseOutCubic # - EaseOutCubic
# - EaseOutQuart # - EaseOutQuart
# - EaseOutQuint # - EaseOutQuint
# - EaseOutExpo # - EaseOutExpo
# - EaseOutCirc # - EaseOutCirc
# - Linear # - Linear
# #animation: EaseOutExpo
# Specifying a `duration` of `0` will disable the visual bell.
#visual_bell: # Duration of the visual bell flash. A `duration` of `0` will disable the
# animation: EaseOutExpo # visual bell animation.
# duration: 0 #duration: 0
# color: '#ffffff'
# Visual bell animation color.
#color: '#ffffff'
# Bell Command
#
# This program is executed whenever the bell is rung.
#
# When set to `command: None`, no command will be executed.
#
# Example:
# command:
# program: notify-send
# args: ["Hello, World!"]
#
#command: None
# Background opacity # Background opacity
# #
@ -358,7 +373,7 @@
# #
# Alacritty defaults to using the newer ConPTY backend if it is available, # Alacritty defaults to using the newer ConPTY backend if it is available,
# since it resolves a lot of bugs and is quite a bit faster. If it is not # since it resolves a lot of bugs and is quite a bit faster. If it is not
# available, the the WinPTY backend will be used instead. # available, the WinPTY backend will be used instead.
# #
# Setting this option to `true` makes Alacritty use the legacy WinPTY backend, # Setting this option to `true` makes Alacritty use the legacy WinPTY backend,
# even if the ConPTY backend is available. # even if the ConPTY backend is available.

View file

@ -23,6 +23,7 @@ parking_lot = "0.10.2"
font = { path = "../font", features = ["force_system_fontconfig"] } font = { path = "../font", features = ["force_system_fontconfig"] }
urlocator = "0.1.3" urlocator = "0.1.3"
copypasta = { version = "0.7.0", default-features = false } copypasta = { version = "0.7.0", default-features = false }
libc = "0.2"
unicode-width = "0.1" unicode-width = "0.1"
[build-dependencies] [build-dependencies]

View file

@ -217,6 +217,13 @@ fn print_deprecation_warnings(config: &Config) {
the config" the config"
); );
} }
if config.visual_bell.is_some() {
warn!(
target: LOG_TARGET_CONFIG,
"Config visual_bell has been deprecated; please use bell instead"
)
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -4,7 +4,7 @@ use std::time::Duration;
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use alacritty_terminal::util; use alacritty_terminal::thread;
use crate::event::{Event, EventProxy}; use crate::event::{Event, EventProxy};
@ -20,7 +20,7 @@ impl Monitor {
let path = path.into(); let path = path.into();
Monitor { Monitor {
_thread: util::thread::spawn_named("config watcher", move || { _thread: thread::spawn_named("config watcher", move || {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
// The Duration argument is a debouncing period. // The Duration argument is a debouncing period.
let mut watcher = let mut watcher =

View file

@ -1,65 +1,33 @@
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fmt::Debug;
use std::io; use std::io;
use std::process::{Command, Stdio};
#[cfg(not(windows))] #[cfg(not(windows))]
use std::os::unix::process::CommandExt; use std::os::unix::process::CommandExt;
#[cfg(windows)] #[cfg(windows)]
use std::os::windows::process::CommandExt; use std::os::windows::process::CommandExt;
use std::process::{Command, Stdio};
use log::{debug, warn};
#[cfg(windows)] #[cfg(windows)]
use winapi::um::winbase::{CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW}; use winapi::um::winbase::{CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW};
/// Threading utilities. /// Start the daemon and log error on failure.
pub mod thread { pub fn start_daemon<I, S>(program: &str, args: I)
/// Like `thread::spawn`, but with a `name` argument.
pub fn spawn_named<F, T, S>(name: S, f: F) -> std::thread::JoinHandle<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
S: Into<String>,
{
std::thread::Builder::new().name(name.into()).spawn(f).expect("thread spawn works")
}
pub use std::thread::*;
}
#[cfg(not(windows))]
pub fn start_daemon<I, S>(program: &str, args: I) -> io::Result<()>
where where
I: IntoIterator<Item = S>, I: IntoIterator<Item = S> + Debug + Copy,
S: AsRef<OsStr>, S: AsRef<OsStr>,
{ {
unsafe { match spawn_daemon(program, args) {
Command::new(program) Ok(_) => debug!("Launched {} with args {:?}", program, args),
.args(args) Err(_) => warn!("Unable to launch {} with args {:?}", program, args),
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.pre_exec(|| {
match ::libc::fork() {
-1 => return Err(io::Error::last_os_error()),
0 => (),
_ => ::libc::_exit(0),
}
if ::libc::setsid() == -1 {
return Err(io::Error::last_os_error());
}
Ok(())
})
.spawn()?
.wait()
.map(|_| ())
} }
} }
#[cfg(windows)] #[cfg(windows)]
pub fn start_daemon<I, S>(program: &str, args: I) -> io::Result<()> fn spawn_daemon<I, S>(program: &str, args: I) -> io::Result<()>
where where
I: IntoIterator<Item = S>, I: IntoIterator<Item = S> + Copy,
S: AsRef<OsStr>, S: AsRef<OsStr>,
{ {
// Setting all the I/O handles to null and setting the // Setting all the I/O handles to null and setting the
@ -75,3 +43,34 @@ where
.spawn() .spawn()
.map(|_| ()) .map(|_| ())
} }
#[cfg(not(windows))]
fn spawn_daemon<I, S>(program: &str, args: I) -> io::Result<()>
where
I: IntoIterator<Item = S> + Copy,
S: AsRef<OsStr>,
{
unsafe {
Command::new(program)
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.pre_exec(|| {
match libc::fork() {
-1 => return Err(io::Error::last_os_error()),
0 => (),
_ => libc::_exit(0),
}
if libc::setsid() == -1 {
return Err(io::Error::last_os_error());
}
Ok(())
})
.spawn()?
.wait()
.map(|_| ())
}
}

View file

@ -540,7 +540,7 @@ impl Display {
0., 0.,
size_info.width, size_info.width,
size_info.height, size_info.height,
config.visual_bell.color, config.bell().color,
visual_bell_intensity as f32, visual_bell_intensity as f32,
); );
rects.push(visual_bell_rect); rects.push(visual_bell_rect);

View file

@ -3,6 +3,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::env; use std::env;
use std::fmt::Debug;
#[cfg(unix)] #[cfg(unix)]
use std::fs; use std::fs;
use std::fs::File; use std::fs::File;
@ -20,7 +21,7 @@ use glutin::event_loop::{ControlFlow, EventLoop, EventLoopProxy, EventLoopWindow
use glutin::platform::desktop::EventLoopExtDesktop; use glutin::platform::desktop::EventLoopExtDesktop;
#[cfg(not(any(target_os = "macos", windows)))] #[cfg(not(any(target_os = "macos", windows)))]
use glutin::platform::unix::EventLoopWindowTargetExtUnix; use glutin::platform::unix::EventLoopWindowTargetExtUnix;
use log::{debug, info, warn}; use log::info;
use serde_json as json; use serde_json as json;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@ -38,12 +39,12 @@ use alacritty_terminal::term::cell::Cell;
use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode}; use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode};
#[cfg(not(windows))] #[cfg(not(windows))]
use alacritty_terminal::tty; use alacritty_terminal::tty;
use alacritty_terminal::util::start_daemon;
use crate::cli::Options; use crate::cli::Options;
use crate::clipboard::Clipboard; use crate::clipboard::Clipboard;
use crate::config; use crate::config;
use crate::config::Config; use crate::config::Config;
use crate::daemon::start_daemon;
use crate::display::{Display, DisplayUpdate}; use crate::display::{Display, DisplayUpdate};
use crate::input::{self, ActionContext as _, FONT_SIZE_STEP}; use crate::input::{self, ActionContext as _, FONT_SIZE_STEP};
use crate::scheduler::{Scheduler, TimerId}; use crate::scheduler::{Scheduler, TimerId};
@ -290,10 +291,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
#[cfg(not(unix))] #[cfg(not(unix))]
let args: Vec<String> = Vec::new(); let args: Vec<String> = Vec::new();
match start_daemon(&alacritty, &args) { start_daemon(&alacritty, &args);
Ok(_) => debug!("Started new Alacritty process: {} {:?}", alacritty, args),
Err(_) => warn!("Unable to start new Alacritty process: {} {:?}", alacritty, args),
}
} }
/// Spawn URL launcher when clicking on URLs. /// Spawn URL launcher when clicking on URLs.
@ -308,10 +306,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
let end = self.terminal.visible_to_buffer(url.end()); let end = self.terminal.visible_to_buffer(url.end());
args.push(self.terminal.bounds_to_string(start, end)); args.push(self.terminal.bounds_to_string(start, end));
match start_daemon(launcher.program(), &args) { start_daemon(launcher.program(), &args);
Ok(_) => debug!("Launched {} with args {:?}", launcher.program(), args),
Err(_) => warn!("Unable to launch {} with args {:?}", launcher.program(), args),
}
} }
} }
@ -839,8 +834,10 @@ impl<N: Notify + OnResize> Processor<N> {
Event::TerminalEvent(event) => match event { Event::TerminalEvent(event) => match event {
TerminalEvent::Title(title) => processor.ctx.window.set_title(&title), TerminalEvent::Title(title) => processor.ctx.window.set_title(&title),
TerminalEvent::Wakeup => processor.ctx.terminal.dirty = true, TerminalEvent::Wakeup => processor.ctx.terminal.dirty = true,
TerminalEvent::Urgent => { TerminalEvent::Bell => {
processor.ctx.window.set_urgent(!processor.ctx.terminal.is_focused) let bell_command = processor.ctx.config.bell().command.as_ref();
let _ = bell_command.map(|cmd| start_daemon(cmd.program(), cmd.args()));
processor.ctx.window.set_urgent(!processor.ctx.terminal.is_focused);
}, },
TerminalEvent::ClipboardStore(clipboard_type, content) => { TerminalEvent::ClipboardStore(clipboard_type, content) => {
processor.ctx.clipboard.store(clipboard_type, content); processor.ctx.clipboard.store(clipboard_type, content);

View file

@ -10,7 +10,7 @@ use std::cmp::{max, min, Ordering};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use log::{debug, trace, warn}; use log::trace;
use glutin::dpi::PhysicalPosition; use glutin::dpi::PhysicalPosition;
use glutin::event::{ use glutin::event::{
@ -30,11 +30,11 @@ use alacritty_terminal::message_bar::{self, Message};
use alacritty_terminal::selection::SelectionType; use alacritty_terminal::selection::SelectionType;
use alacritty_terminal::term::mode::TermMode; use alacritty_terminal::term::mode::TermMode;
use alacritty_terminal::term::{ClipboardType, SizeInfo, Term}; use alacritty_terminal::term::{ClipboardType, SizeInfo, Term};
use alacritty_terminal::util::start_daemon;
use alacritty_terminal::vi_mode::ViMotion; use alacritty_terminal::vi_mode::ViMotion;
use crate::clipboard::Clipboard; use crate::clipboard::Clipboard;
use crate::config::{Action, Binding, Config, Key, ViAction}; use crate::config::{Action, Binding, Config, Key, ViAction};
use crate::daemon::start_daemon;
use crate::event::{ClickState, Event, Mouse, TYPING_SEARCH_DELAY}; use crate::event::{ClickState, Event, Mouse, TYPING_SEARCH_DELAY};
use crate::scheduler::{Scheduler, TimerId}; use crate::scheduler::{Scheduler, TimerId};
use crate::url::{Url, Urls}; use crate::url::{Url, Urls};
@ -160,10 +160,7 @@ impl<T: EventListener> Execute<T> for Action {
let program = program.program(); let program = program.program();
trace!("Running command {} with args {:?}", program, args); trace!("Running command {} with args {:?}", program, args);
match start_daemon(program, args) { start_daemon(program, args);
Ok(_) => debug!("Spawned new proc"),
Err(err) => warn!("Couldn't run command {}", err),
}
}, },
Action::ClearSelection => ctx.clear_selection(), Action::ClearSelection => ctx.clear_selection(),
Action::ToggleViMode => ctx.terminal_mut().toggle_vi_mode(), Action::ToggleViMode => ctx.terminal_mut().toggle_vi_mode(),

View file

@ -36,6 +36,7 @@ mod cli;
mod clipboard; mod clipboard;
mod config; mod config;
mod cursor; mod cursor;
mod daemon;
mod display; mod display;
mod event; mod event;
mod input; mod input;

View file

@ -24,7 +24,7 @@ use alacritty_terminal::index::{Column, Line};
use alacritty_terminal::term::cell::{self, Flags}; use alacritty_terminal::term::cell::{self, Flags};
use alacritty_terminal::term::color::Rgb; use alacritty_terminal::term::color::Rgb;
use alacritty_terminal::term::{self, CursorKey, RenderableCell, RenderableCellContent, SizeInfo}; use alacritty_terminal::term::{self, CursorKey, RenderableCell, RenderableCellContent, SizeInfo};
use alacritty_terminal::util; use alacritty_terminal::thread;
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
pub mod rects; pub mod rects;
@ -660,7 +660,7 @@ impl QuadRenderer {
let (msg_tx, msg_rx) = mpsc::channel(); let (msg_tx, msg_rx) = mpsc::channel();
if cfg!(feature = "live-shader-reload") { if cfg!(feature = "live-shader-reload") {
util::thread::spawn_named("live shader reload", move || { thread::spawn_named("live shader reload", move || {
let (tx, rx) = std::sync::mpsc::channel(); let (tx, rx) = std::sync::mpsc::channel();
// The Duration argument is a debouncing period. // The Duration argument is a debouncing period.
let mut watcher = let mut watcher =

View file

@ -0,0 +1,120 @@
use std::time::Duration;
use log::error;
use serde::{Deserialize, Deserializer};
use serde_yaml::Value;
use crate::config::{failure_default, Program, LOG_TARGET_CONFIG};
use crate::term::color::Rgb;
const DEFAULT_BELL_COLOR: Rgb = Rgb { r: 255, g: 255, b: 255 };
#[serde(default)]
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct BellConfig {
/// Visual bell animation function.
#[serde(deserialize_with = "failure_default")]
pub animation: BellAnimation,
/// Visual bell duration in milliseconds.
#[serde(deserialize_with = "failure_default")]
duration: u16,
/// Visual bell flash color.
#[serde(deserialize_with = "deserialize_bell_color")]
pub color: Rgb,
/// Command to run on bell.
#[serde(deserialize_with = "deserialize_bell_command")]
pub command: Option<Program>,
}
impl Default for BellConfig {
fn default() -> Self {
Self {
animation: Default::default(),
duration: Default::default(),
command: Default::default(),
color: DEFAULT_BELL_COLOR,
}
}
}
impl BellConfig {
/// Visual bell duration in milliseconds.
#[inline]
pub fn duration(&self) -> Duration {
Duration::from_millis(u64::from(self.duration))
}
}
fn deserialize_bell_color<'a, D>(deserializer: D) -> Result<Rgb, D::Error>
where
D: Deserializer<'a>,
{
let value = Value::deserialize(deserializer)?;
match Rgb::deserialize(value) {
Ok(value) => Ok(value),
Err(err) => {
error!(
target: LOG_TARGET_CONFIG,
"Problem with config: {}, using default color value {}", err, DEFAULT_BELL_COLOR
);
Ok(DEFAULT_BELL_COLOR)
},
}
}
fn deserialize_bell_command<'a, D>(deserializer: D) -> Result<Option<Program>, D::Error>
where
D: Deserializer<'a>,
{
// Deserialize to generic value.
let val = Value::deserialize(deserializer)?;
// Accept `None` to disable the bell command.
if val.as_str().filter(|v| v.to_lowercase() == "none").is_some() {
return Ok(None);
}
match Program::deserialize(val) {
Ok(command) => Ok(Some(command)),
Err(err) => {
error!(target: LOG_TARGET_CONFIG, "Problem with config: {}; ignoring field", err);
Ok(None)
},
}
}
/// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert
/// Penner's Easing Functions.
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
pub enum BellAnimation {
// CSS animation.
Ease,
// CSS animation.
EaseOut,
// Penner animation.
EaseOutSine,
// Penner animation.
EaseOutQuad,
// Penner animation.
EaseOutCubic,
// Penner animation.
EaseOutQuart,
// Penner animation.
EaseOutQuint,
// Penner animation.
EaseOutExpo,
// Penner animation.
EaseOutCirc,
// Penner animation.
Linear,
}
impl Default for BellAnimation {
fn default() -> Self {
BellAnimation::EaseOutExpo
}
}

View file

@ -6,20 +6,20 @@ use log::error;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use serde_yaml::Value; use serde_yaml::Value;
mod bell;
mod colors; mod colors;
mod debug; mod debug;
mod font; mod font;
mod scrolling; mod scrolling;
mod visual_bell;
mod window; mod window;
use crate::ansi::CursorStyle; use crate::ansi::CursorStyle;
pub use crate::config::bell::{BellAnimation, BellConfig};
pub use crate::config::colors::Colors; pub use crate::config::colors::Colors;
pub use crate::config::debug::Debug; pub use crate::config::debug::Debug;
pub use crate::config::font::{Font, FontDescription}; pub use crate::config::font::{Font, FontDescription};
pub use crate::config::scrolling::Scrolling; pub use crate::config::scrolling::Scrolling;
pub use crate::config::visual_bell::{VisualBellAnimation, VisualBellConfig};
pub use crate::config::window::{Decorations, Dimensions, StartupMode, WindowConfig, DEFAULT_NAME}; pub use crate::config::window::{Decorations, Dimensions, StartupMode, WindowConfig, DEFAULT_NAME};
pub const LOG_TARGET_CONFIG: &str = "alacritty_config"; pub const LOG_TARGET_CONFIG: &str = "alacritty_config";
@ -69,9 +69,9 @@ pub struct Config<T> {
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
pub config_path: Option<PathBuf>, pub config_path: Option<PathBuf>,
/// Visual bell configuration. /// Bell configuration.
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
pub visual_bell: VisualBellConfig, bell: BellConfig,
/// Use dynamic title. /// Use dynamic title.
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
@ -114,6 +114,10 @@ pub struct Config<T> {
#[serde(skip)] #[serde(skip)]
pub hold: bool, pub hold: bool,
// TODO: DEPRECATED
#[serde(default, deserialize_with = "failure_default")]
pub visual_bell: Option<BellConfig>,
// TODO: REMOVED // TODO: REMOVED
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
pub tabspaces: Option<usize>, pub tabspaces: Option<usize>,
@ -176,6 +180,11 @@ impl<T> Config<T> {
pub fn background_opacity(&self) -> f32 { pub fn background_opacity(&self) -> f32 {
self.background_opacity.0 as f32 self.background_opacity.0 as f32
} }
#[inline]
pub fn bell(&self) -> &BellConfig {
self.visual_bell.as_ref().unwrap_or(&self.bell)
}
} }
#[serde(default)] #[serde(default)]

View file

@ -1,76 +0,0 @@
use std::time::Duration;
use serde::Deserialize;
use crate::config::failure_default;
use crate::term::color::Rgb;
#[serde(default)]
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct VisualBellConfig {
/// Visual bell animation function.
#[serde(deserialize_with = "failure_default")]
pub animation: VisualBellAnimation,
/// Visual bell duration in milliseconds.
#[serde(deserialize_with = "failure_default")]
pub duration: u16,
/// Visual bell flash color.
#[serde(deserialize_with = "failure_default")]
pub color: Rgb,
}
impl Default for VisualBellConfig {
fn default() -> VisualBellConfig {
VisualBellConfig {
animation: Default::default(),
duration: Default::default(),
color: default_visual_bell_color(),
}
}
}
impl VisualBellConfig {
/// Visual bell duration in milliseconds.
#[inline]
pub fn duration(&self) -> Duration {
Duration::from_millis(u64::from(self.duration))
}
}
/// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert
/// Penner's Easing Functions.
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
pub enum VisualBellAnimation {
// CSS animation.
Ease,
// CSS animation.
EaseOut,
// Penner animation.
EaseOutSine,
// Penner animation.
EaseOutQuad,
// Penner animation.
EaseOutCubic,
// Penner animation.
EaseOutQuart,
// Penner animation.
EaseOutQuint,
// Penner animation.
EaseOutExpo,
// Penner animation.
EaseOutCirc,
// Penner animation.
Linear,
}
impl Default for VisualBellAnimation {
fn default() -> Self {
VisualBellAnimation::EaseOutExpo
}
}
fn default_visual_bell_color() -> Rgb {
Rgb { r: 255, g: 255, b: 255 }
}

View file

@ -11,7 +11,7 @@ pub enum Event {
ClipboardStore(ClipboardType, String), ClipboardStore(ClipboardType, String),
ClipboardLoad(ClipboardType, Arc<dyn Fn(&str) -> String + Sync + Send + 'static>), ClipboardLoad(ClipboardType, Arc<dyn Fn(&str) -> String + Sync + Send + 'static>),
Wakeup, Wakeup,
Urgent, Bell,
Exit, Exit,
} }
@ -23,7 +23,7 @@ impl Debug for Event {
Event::ClipboardStore(ty, text) => write!(f, "ClipboardStore({:?}, {})", ty, text), Event::ClipboardStore(ty, text) => write!(f, "ClipboardStore({:?}, {})", ty, text),
Event::ClipboardLoad(ty, _) => write!(f, "ClipboardLoad({:?})", ty), Event::ClipboardLoad(ty, _) => write!(f, "ClipboardLoad({:?})", ty),
Event::Wakeup => write!(f, "Wakeup"), Event::Wakeup => write!(f, "Wakeup"),
Event::Urgent => write!(f, "Urgent"), Event::Bell => write!(f, "Bell"),
Event::Exit => write!(f, "Exit"), Event::Exit => write!(f, "Exit"),
} }
} }

View file

@ -6,6 +6,7 @@ use std::fs::File;
use std::io::{self, ErrorKind, Read, Write}; use std::io::{self, ErrorKind, Read, Write};
use std::marker::Send; use std::marker::Send;
use std::sync::Arc; use std::sync::Arc;
use std::thread::JoinHandle;
use log::error; use log::error;
#[cfg(not(windows))] #[cfg(not(windows))]
@ -18,8 +19,8 @@ use crate::config::Config;
use crate::event::{self, Event, EventListener}; use crate::event::{self, Event, EventListener};
use crate::sync::FairMutex; use crate::sync::FairMutex;
use crate::term::{SizeInfo, Term}; use crate::term::{SizeInfo, Term};
use crate::thread;
use crate::tty; use crate::tty;
use crate::util::thread;
/// Max bytes to read from the PTY. /// Max bytes to read from the PTY.
const MAX_READ: usize = 0x10_000; const MAX_READ: usize = 0x10_000;
@ -300,7 +301,7 @@ where
Ok(()) Ok(())
} }
pub fn spawn(mut self) -> thread::JoinHandle<(Self, State)> { pub fn spawn(mut self) -> JoinHandle<(Self, State)> {
thread::spawn_named("PTY reader", move || { thread::spawn_named("PTY reader", move || {
let mut state = State::default(); let mut state = State::default();
let mut buf = [0u8; MAX_READ]; let mut buf = [0u8; MAX_READ];

View file

@ -22,8 +22,8 @@ pub mod panic;
pub mod selection; pub mod selection;
pub mod sync; pub mod sync;
pub mod term; pub mod term;
pub mod thread;
pub mod tty; pub mod tty;
pub mod util;
pub mod vi_mode; pub mod vi_mode;
pub use crate::grid::Grid; pub use crate::grid::Grid;

View file

@ -1,4 +1,4 @@
use std::fmt; use std::fmt::{self, Display, Formatter};
use std::ops::{Index, IndexMut, Mul}; use std::ops::{Index, IndexMut, Mul};
use std::str::FromStr; use std::str::FromStr;
@ -64,7 +64,7 @@ impl<'de> Deserialize<'de> for Rgb {
impl<'a> Visitor<'a> for RgbVisitor { impl<'a> Visitor<'a> for RgbVisitor {
type Value = Rgb; type Value = Rgb;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("hex color like #ff00ff") f.write_str("hex color like #ff00ff")
} }
@ -94,6 +94,12 @@ impl<'de> Deserialize<'de> for Rgb {
} }
} }
impl Display for Rgb {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
}
}
impl FromStr for Rgb { impl FromStr for Rgb {
type Err = (); type Err = ();

View file

@ -14,7 +14,7 @@ use unicode_width::UnicodeWidthChar;
use crate::ansi::{ use crate::ansi::{
self, Attr, CharsetIndex, Color, CursorStyle, Handler, NamedColor, StandardCharset, self, Attr, CharsetIndex, Color, CursorStyle, Handler, NamedColor, StandardCharset,
}; };
use crate::config::{Config, VisualBellAnimation}; use crate::config::{BellAnimation, BellConfig, Config};
use crate::event::{Event, EventListener}; use crate::event::{Event, EventListener};
use crate::grid::{Dimensions, DisplayIter, Grid, IndexRegion, Indexed, Scroll}; use crate::grid::{Dimensions, DisplayIter, Grid, IndexRegion, Indexed, Scroll};
use crate::index::{self, Boundary, Column, Direction, IndexRange, Line, Point, Side}; use crate::index::{self, Boundary, Column, Direction, IndexRange, Line, Point, Side};
@ -467,7 +467,7 @@ pub use crate::term::mode::TermMode;
pub struct VisualBell { pub struct VisualBell {
/// Visual bell animation. /// Visual bell animation.
animation: VisualBellAnimation, animation: BellAnimation,
/// Visual bell duration. /// Visual bell duration.
duration: Duration, duration: Duration,
@ -484,15 +484,6 @@ fn cubic_bezier(p0: f64, p1: f64, p2: f64, p3: f64, x: f64) -> f64 {
} }
impl VisualBell { impl VisualBell {
pub fn new<C>(config: &Config<C>) -> VisualBell {
let visual_bell_config = &config.visual_bell;
VisualBell {
animation: visual_bell_config.animation,
duration: visual_bell_config.duration(),
start_time: None,
}
}
/// Ring the visual bell, and return its intensity. /// Ring the visual bell, and return its intensity.
pub fn ring(&mut self) -> f64 { pub fn ring(&mut self) -> f64 {
let now = Instant::now(); let now = Instant::now();
@ -557,19 +548,17 @@ impl VisualBell {
// VisualBell. When `time` is 0.0, `inverse_intensity` is 0.0, // VisualBell. When `time` is 0.0, `inverse_intensity` is 0.0,
// and when `time` is 1.0, `inverse_intensity` is 1.0. // and when `time` is 1.0, `inverse_intensity` is 1.0.
let inverse_intensity = match self.animation { let inverse_intensity = match self.animation {
VisualBellAnimation::Ease | VisualBellAnimation::EaseOut => { BellAnimation::Ease | BellAnimation::EaseOut => {
cubic_bezier(0.25, 0.1, 0.25, 1.0, time) cubic_bezier(0.25, 0.1, 0.25, 1.0, time)
}, },
VisualBellAnimation::EaseOutSine => cubic_bezier(0.39, 0.575, 0.565, 1.0, time), BellAnimation::EaseOutSine => cubic_bezier(0.39, 0.575, 0.565, 1.0, time),
VisualBellAnimation::EaseOutQuad => cubic_bezier(0.25, 0.46, 0.45, 0.94, time), BellAnimation::EaseOutQuad => cubic_bezier(0.25, 0.46, 0.45, 0.94, time),
VisualBellAnimation::EaseOutCubic => { BellAnimation::EaseOutCubic => cubic_bezier(0.215, 0.61, 0.355, 1.0, time),
cubic_bezier(0.215, 0.61, 0.355, 1.0, time) BellAnimation::EaseOutQuart => cubic_bezier(0.165, 0.84, 0.44, 1.0, time),
}, BellAnimation::EaseOutQuint => cubic_bezier(0.23, 1.0, 0.32, 1.0, time),
VisualBellAnimation::EaseOutQuart => cubic_bezier(0.165, 0.84, 0.44, 1.0, time), BellAnimation::EaseOutExpo => cubic_bezier(0.19, 1.0, 0.22, 1.0, time),
VisualBellAnimation::EaseOutQuint => cubic_bezier(0.23, 1.0, 0.32, 1.0, time), BellAnimation::EaseOutCirc => cubic_bezier(0.075, 0.82, 0.165, 1.0, time),
VisualBellAnimation::EaseOutExpo => cubic_bezier(0.19, 1.0, 0.22, 1.0, time), BellAnimation::Linear => time,
VisualBellAnimation::EaseOutCirc => cubic_bezier(0.075, 0.82, 0.165, 1.0, time),
VisualBellAnimation::Linear => time,
}; };
// Since we want the `intensity` of the VisualBell to decay over // Since we want the `intensity` of the VisualBell to decay over
@ -580,9 +569,19 @@ impl VisualBell {
} }
pub fn update_config<C>(&mut self, config: &Config<C>) { pub fn update_config<C>(&mut self, config: &Config<C>) {
let visual_bell_config = &config.visual_bell; let bell_config = config.bell();
self.animation = visual_bell_config.animation; self.animation = bell_config.animation;
self.duration = visual_bell_config.duration(); self.duration = bell_config.duration();
}
}
impl From<&BellConfig> for VisualBell {
fn from(bell_config: &BellConfig) -> VisualBell {
VisualBell {
animation: bell_config.animation,
duration: bell_config.duration(),
start_time: None,
}
} }
} }
@ -763,7 +762,7 @@ impl<T> Term<T> {
Term { Term {
dirty: false, dirty: false,
visual_bell: VisualBell::new(config), visual_bell: config.bell().into(),
grid, grid,
inactive_grid: alt, inactive_grid: alt,
active_charset: Default::default(), active_charset: Default::default(),
@ -1617,7 +1616,7 @@ impl<T: EventListener> Handler for Term<T> {
fn bell(&mut self) { fn bell(&mut self) {
trace!("Bell"); trace!("Bell");
self.visual_bell.ring(); self.visual_bell.ring();
self.event_proxy.send_event(Event::Urgent); self.event_proxy.send_event(Event::Bell);
} }
#[inline] #[inline]

View file

@ -0,0 +1,11 @@
use std::thread::{Builder, JoinHandle};
/// Like `thread::spawn`, but with a `name` argument.
pub fn spawn_named<F, T, S>(name: S, f: F) -> JoinHandle<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
S: Into<String>,
{
Builder::new().name(name.into()).spawn(f).expect("thread spawn works")
}