diff --git a/src/config.rs b/src/config.rs index 27fcf3ca..f3e68dca 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,11 +7,13 @@ use std::env; use std::fs; use std::io::{self, Read}; use std::path::{Path, PathBuf}; +use std::sync::mpsc; use ::Rgb; use font::Size; use serde_yaml; use serde::{self, Error as SerdeError}; +use notify::{Watcher as WatcherApi, RecommendedWatcher as FileWatcher, op}; /// Top-level config type #[derive(Debug, Deserialize, Default)] @@ -232,7 +234,7 @@ impl Config { /// /// 1. `$HOME/.config/alacritty.yml` /// 2. `$HOME/.alacritty.yml` - pub fn load() -> Result { + pub fn load() -> Result<(Config, PathBuf)> { let home = env::var("HOME")?; // First path @@ -240,15 +242,17 @@ impl Config { path.push(".config"); path.push("alacritty.yml"); - // Fallback path - let mut alt_path = PathBuf::from(&home); - alt_path.push(".alacritty.yml"); - - match Config::load_from(&path) { + match Config::load_from(path) { Ok(c) => Ok(c), Err(e) => { match e { - Error::NotFound => Config::load_from(&alt_path), + Error::NotFound => { + // Fallback path + let mut alt_path = PathBuf::from(&home); + alt_path.push(".alacritty.yml"); + + Config::load_from(alt_path) + }, _ => Err(e), } } @@ -315,9 +319,10 @@ impl Config { self.render_timer } - fn load_from>(path: P) -> Result { - let raw = Config::read_file(path)?; - Ok(serde_yaml::from_str(&raw[..])?) + fn load_from>(path: P) -> Result<(Config, PathBuf)> { + let path = path.into(); + let raw = Config::read_file(path.as_path())?; + Ok((serde_yaml::from_str(&raw[..])?, path)) } fn read_file>(path: P) -> Result { @@ -525,3 +530,46 @@ impl Default for Font { } } } + +pub struct Watcher(::std::thread::JoinHandle<()>); + +pub trait OnConfigReload { + fn on_config_reload(&mut self, Config); +} + +impl Watcher { + pub fn new(path: PathBuf, mut handler: H) -> Watcher { + Watcher(::util::thread::spawn_named("config watcher", move || { + let (tx, rx) = mpsc::channel(); + let mut watcher = FileWatcher::new(tx).unwrap(); + watcher.watch(&path).expect("watch alacritty yml"); + + let config_path = path.as_path(); + + loop { + let event = rx.recv().expect("watcher event"); + let ::notify::Event { path, op } = event; + + if let Ok(op) = op { + if op.contains(op::RENAME) { + continue; + } + + if op.contains(op::IGNORED) { + if let Some(path) = path.as_ref() { + if let Err(err) = watcher.watch(&path) { + err_println!("failed to establish watch on {:?}: {:?}", path, err); + } + + if path == config_path { + if let Ok((config, _)) = Config::load() { + handler.on_config_reload(config); + }; + } + } + } + } + } + })) + } +} diff --git a/src/main.rs b/src/main.rs index 01c5f511..8a2159e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -118,14 +118,14 @@ mod gl { fn main() { // Load configuration - let config = match Config::load() { + let (config, config_path) = match Config::load() { Err(err) => match err { // Use default config when not found - config::Error::NotFound => Config::default(), + config::Error::NotFound => (Config::default(), None), // Exit when there's a problem with it _ => die!("{}", err), }, - Ok(config) => config, + Ok((config, path)) => (config, Some(path)), }; let font = config.font(); @@ -225,11 +225,25 @@ fn main() { tx ); + let (config_tx, config_rx) = mpsc::channel(); + + // create a config watcher when config is loaded from disk + let _config_reloader = config_path.map(|config_path| { + config::Watcher::new(config_path, ConfigHandler { + tx: config_tx, + window: window.create_window_proxy(), + }) + }); + // Main loop loop { // Wait for something to happen processor.process_events(&window); + if let Ok(config) = config_rx.try_recv() { + display.update_config(&config); + } + // Maybe draw the terminal let terminal = terminal.lock(); signal_flag.set(false); @@ -242,11 +256,31 @@ fn main() { } } + // FIXME need file watcher to work with custom delegates before + // joining config reloader is possible + // config_reloader.join().ok(); + // shutdown event_loop_handle.join().ok(); println!("Goodbye"); } +struct ConfigHandler { + tx: mpsc::Sender, + window: ::glutin::WindowProxy, +} + +impl config::OnConfigReload for ConfigHandler { + fn on_config_reload(&mut self, config: Config) { + if let Err(..) = self.tx.send(config) { + err_println!("Failed to notify of new config"); + return; + } + + self.window.wakeup_event_loop(); + } +} + struct Display { window: Arc, renderer: QuadRenderer, @@ -257,6 +291,10 @@ struct Display { } impl Display { + pub fn update_config(&mut self, config: &Config) { + self.renderer.update_config(config); + } + pub fn new(window: Arc, renderer: QuadRenderer, glyph_cache: GlyphCache, diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index eccbb8af..bbd07fe5 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -42,37 +42,9 @@ pub trait LoadGlyph { } enum Msg { - ConfigReload(Config), ShaderReload, } -/// Colors! -/// -/// FIXME this is obviously bad; need static for reload logic for now. Hacking something in with -/// minimal effort and will improve later. -/// -/// Only renderer is allowed to access this to prevent race conditions -static mut COLORS: [Rgb; 18] = [ - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, - Rgb { r: 0, g: 0, b: 0 }, -]; - /// Text drawing program /// /// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a". @@ -278,6 +250,7 @@ pub struct RenderApi<'a> { batch: &'a mut Batch, atlas: &'a mut Vec, program: &'a mut ShaderProgram, + colors: &'a [Rgb; 18], } #[derive(Debug)] @@ -500,7 +473,6 @@ impl QuadRenderer { let mut watcher = Watcher::new(tx).unwrap(); watcher.watch(TEXT_SHADER_F_PATH).expect("watch fragment shader"); watcher.watch(TEXT_SHADER_V_PATH).expect("watch vertex shader"); - watcher.watch("/home/jwilm/.alacritty.yml").expect("watch alacritty yml"); loop { let event = rx.recv().expect("watcher event"); @@ -517,15 +489,8 @@ impl QuadRenderer { println!("failed to establish watch on {:?}: {:?}", path, err); } - if path == ::std::path::Path::new("/home/jwilm/.alacritty.yml") { - if let Ok(config) = Config::load() { - msg_tx.send(Msg::ConfigReload(config)) - .expect("msg send ok"); - }; - } else { - msg_tx.send(Msg::ShaderReload) - .expect("msg send ok"); - } + msg_tx.send(Msg::ShaderReload) + .expect("msg send ok"); } } } @@ -545,27 +510,24 @@ impl QuadRenderer { rx: msg_rx, }; - unsafe { - COLORS = config.color_list(); - } - let atlas = Atlas::new(ATLAS_SIZE); renderer.atlas.push(atlas); renderer } + pub fn update_config(&mut self, config: &Config) { + self.colors = config.color_list(); + self.program.activate(); + self.program.set_color_uniforms(&self.colors); + self.program.deactivate(); + } + pub fn with_api(&mut self, props: &term::SizeInfo, func: F) -> T where F: FnOnce(RenderApi) -> T { while let Ok(msg) = self.rx.try_recv() { match msg { - Msg::ConfigReload(config) => { - self.colors = config.color_list(); - self.program.activate(); - self.program.set_color_uniforms(&self.colors); - self.program.deactivate(); - }, Msg::ShaderReload => { self.reload_shaders(props.width as u32, props.height as u32); } @@ -587,6 +549,7 @@ impl QuadRenderer { batch: &mut self.batch, atlas: &mut self.atlas, program: &mut self.program, + colors: &self.colors, }); unsafe { @@ -732,7 +695,7 @@ impl<'a> RenderApi<'a> { glyph_cache: &mut GlyphCache ) { // TODO should be built into renderer - let color = unsafe { COLORS[::ansi::Color::Background as usize] }; + let color = self.colors[::ansi::Color::Background as usize]; unsafe { gl::ClearColor( color.r as f32 / 255.0,