diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/config.rs | 48 | ||||
-rw-r--r-- | src/display.rs | 46 | ||||
-rw-r--r-- | src/event_loop.rs | 186 | ||||
-rw-r--r-- | src/input.rs | 14 | ||||
-rw-r--r-- | src/lib.rs | 18 | ||||
-rw-r--r-- | src/main.rs | 65 | ||||
-rw-r--r-- | src/renderer/mod.rs | 318 | ||||
-rw-r--r-- | src/tty/mod.rs | 45 | ||||
-rw-r--r-- | src/tty/unix.rs (renamed from src/tty.rs) | 148 | ||||
-rw-r--r-- | src/tty/windows.rs | 284 | ||||
-rw-r--r-- | src/window.rs | 51 |
11 files changed, 902 insertions, 321 deletions
diff --git a/src/config.rs b/src/config.rs index 0f250522..c7d7ea58 100644 --- a/src/config.rs +++ b/src/config.rs @@ -555,10 +555,12 @@ fn failure_default<'a, D, T>(deserializer: D) } } -#[cfg(not(target_os="macos"))] +#[cfg(not(any(windows, target_os="macos")))] static DEFAULT_ALACRITTY_CONFIG: &'static str = include_str!("../alacritty.yml"); #[cfg(target_os="macos")] static DEFAULT_ALACRITTY_CONFIG: &'static str = include_str!("../alacritty_macos.yml"); +#[cfg(windows)] +static DEFAULT_ALACRITTY_CONFIG: &'static str = include_str!("../alacritty_windows.yml"); impl Default for Config { fn default() -> Self { @@ -1444,6 +1446,7 @@ impl Config { /// 2. $XDG_CONFIG_HOME/alacritty.yml /// 3. $HOME/.config/alacritty/alacritty.yml /// 4. $HOME/.alacritty.yml + #[cfg(not(windows))] pub fn installed_config<'a>() -> Option<Cow<'a, Path>> { // Try using XDG location by default ::xdg::BaseDirectories::with_prefix("alacritty") @@ -1472,6 +1475,19 @@ impl Config { .map(|path| path.into()) } + #[cfg(windows)] + pub fn installed_config() -> Option<Cow<'static, Path>> { + if let Some(mut path) = ::std::env::home_dir() { + path.push("alacritty"); + path.set_extension("yml"); + if path.exists() { + return Some(path.into()); + } + } + None + } + + #[cfg(not(windows))] pub fn write_defaults() -> io::Result<Cow<'static, Path>> { let path = ::xdg::BaseDirectories::with_prefix("alacritty") .map_err(|err| io::Error::new(io::ErrorKind::NotFound, ::std::error::Error::description(&err))) @@ -1480,6 +1496,15 @@ impl Config { Ok(path.into()) } + #[cfg(windows)] + pub fn write_defaults() -> io::Result<Cow<'static, Path>> { + let path = ::std::env::home_dir() + .ok_or(io::Error::new(io::ErrorKind::NotFound, "could not find profile directory")) + .and_then(|mut p| {p.push("alacritty"); p.set_extension("yml"); Ok(p)})?; + File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?; + Ok(path.into()) + } + /// Get list of colors /// /// The ordering returned here is expected by the terminal. Colors are simply indexed in this @@ -1894,6 +1919,22 @@ impl Default for Font { } } +#[cfg(windows)] +impl Default for Font { + fn default() -> Font { + Font { + normal: FontDescription::new_with_family("Consolas"), + bold: FontDescription::new_with_family("Consolas"), + italic: FontDescription::new_with_family("Consolas"), + size: Size::new(11.0), + use_thin_strokes: false, + offset: Default::default(), + glyph_offset: Default::default(), + scale_with_dpi: false, + } + } +} + pub struct Monitor { _thread: ::std::thread::JoinHandle<()>, rx: mpsc::Receiver<Config>, @@ -1977,7 +2018,10 @@ mod tests { #[cfg(target_os="macos")] static ALACRITTY_YML: &'static str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty_macos.yml")); - #[cfg(not(target_os="macos"))] + #[cfg(windows)] + static ALACRITTY_YML: &'static str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty_windows.yml")); + #[cfg(not(any(target_os="macos", windows)))] static ALACRITTY_YML: &'static str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty.yml")); diff --git a/src/display.rs b/src/display.rs index d806b33a..d5a2ea0a 100644 --- a/src/display.rs +++ b/src/display.rs @@ -16,7 +16,7 @@ //! GPU drawing. use std::sync::mpsc; -use parking_lot::{MutexGuard}; +use parking_lot::MutexGuard; use Rgb; use cli; @@ -27,7 +27,7 @@ use renderer::{self, GlyphCache, QuadRenderer}; use term::{Term, SizeInfo, RenderableCell}; use sync::FairMutex; -use window::{self, Size, Pixels, Window, SetInnerSize}; +use window::{self, Pixels, SetInnerSize, Size, Window}; #[derive(Debug)] pub enum Error { @@ -128,10 +128,7 @@ impl Display { &self.size_info } - pub fn new( - config: &Config, - options: &cli::Options, - ) -> Result<Display, Error> { + pub fn new(config: &Config, options: &cli::Options) -> Result<Display, Error> { // Extract some properties from config let render_timer = config.render_timer(); @@ -196,9 +193,14 @@ impl Display { // Clear screen let background_color = config.colors().primary.background; - renderer.with_api(config, &size_info, 0. /* visual bell intensity */, |api| { - api.clear(background_color); - }); + renderer.with_api( + config, + &size_info, + 0., /* visual bell intensity */ + |api| { + api.clear(background_color); + }, + ); Ok(Display { window, @@ -224,9 +226,8 @@ impl Display { info!("Initializing glyph cache"); let init_start = ::std::time::Instant::now(); - let cache = renderer.with_loader(|mut api| { - GlyphCache::new(rasterizer, &font, &mut api) - })?; + let cache = + renderer.with_loader(|mut api| GlyphCache::new(rasterizer, &font, &mut api))?; let stop = init_start.elapsed(); let stop_f = stop.as_secs() as f64 + f64::from(stop.subsec_nanos()) / 1_000_000_000f64; @@ -276,7 +277,7 @@ impl Display { &mut self, terminal: &mut MutexGuard<Term>, config: &Config, - items: &mut [&mut OnResize] + items: &mut [&mut OnResize], ) { // Resize events new_size and are handled outside the poll_events // iterator. This has the effect of coalescing multiple resize @@ -295,8 +296,7 @@ impl Display { if new_size == None { // Force a resize to refresh things - new_size = Some((self.size_info.width as u32, - self.size_info.height as u32)); + new_size = Some((self.size_info.width as u32, self.size_info.height as u32)); } } @@ -316,7 +316,6 @@ impl Display { self.window.resize(w, h); self.renderer.resize(w as i32, h as i32); } - } /// Draw the screen @@ -385,10 +384,15 @@ impl Display { // Draw render timer if self.render_timer { let timing = format!("{:.3} usec", self.meter.average()); - let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 }; - self.renderer.with_api(config, &size_info, visual_bell_intensity, |mut api| { - api.render_string(&timing[..], glyph_cache, color); - }); + let color = Rgb { + r: 0xd5, + g: 0x4e, + b: 0x53, + }; + self.renderer + .with_api(config, &size_info, visual_bell_intensity, |mut api| { + api.render_string(&timing[..], glyph_cache, color); + }); } } @@ -403,7 +407,7 @@ impl Display { /// Adjust the IME editor position according to the new location of the cursor pub fn update_ime_position(&mut self, terminal: &Term) { - use index::{Point, Line, Column}; + use index::{Column, Line, Point}; use term::SizeInfo; let Point{line: Line(row), col: Column(col)} = terminal.cursor().point; let SizeInfo{cell_width: cw, diff --git a/src/event_loop.rs b/src/event_loop.rs index d7d27243..8cc094f6 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -1,20 +1,21 @@ //! The main event loop which performs I/O on the pseudoterminal use std::borrow::Cow; use std::collections::VecDeque; -use std::io::{self, ErrorKind, Write}; +use std::io::{self, ErrorKind, Read, Write}; use std::fs::File; -use std::os::unix::io::AsRawFd; use std::sync::Arc; +use std::marker::Send; use mio::{self, Events, PollOpt, Ready}; -#[cfg(unix)] +use mio_more::channel::{self, Receiver, Sender}; + +#[cfg(not(windows))] use mio::unix::UnixReady; -use mio::unix::EventedFd; -use mio_more::channel::{self, Sender, Receiver}; use ansi; use display; use event; +use tty; use term::Term; use util::thread; use sync::FairMutex; @@ -26,16 +27,16 @@ pub enum Msg { Input(Cow<'static, [u8]>), /// Indicates that the `EventLoop` should shut down, as Alacritty is shutting down - Shutdown + Shutdown, } /// The main event!.. loop. /// /// Handles all the pty I/O and runs the pty parser which updates terminal /// state. -pub struct EventLoop<Io> { +pub struct EventLoop<T: tty::EventedReadWrite> { poll: mio::Poll, - pty: Io, + pty: T, rx: Receiver<Msg>, tx: Sender<Msg>, terminal: Arc<FairMutex<Term>>, @@ -83,7 +84,7 @@ pub struct Notifier(pub Sender<Msg>); impl event::Notify for Notifier { fn notify<B>(&mut self, bytes: B) - where B: Into<Cow<'static, [u8]>> + where B: Into<Cow<'static, [u8]>>, { let bytes = bytes.into(); // terminal hangs if we send 0 bytes through. @@ -96,7 +97,6 @@ impl event::Notify for Notifier { } } - impl Default for State { fn default() -> State { State { @@ -163,19 +163,17 @@ impl Writing { /// `mio::Token` for the event loop channel const CHANNEL: mio::Token = mio::Token(0); -/// `mio::Token` for the pty file descriptor -const PTY: mio::Token = mio::Token(1); - -impl<Io> EventLoop<Io> - where Io: io::Read + io::Write + Send + AsRawFd + 'static +impl<T> EventLoop<T> + where + T: tty::EventedReadWrite + Send + 'static, { /// Create a new event loop pub fn new( terminal: Arc<FairMutex<Term>>, display: display::Notifier, - pty: Io, + pty: T, ref_test: bool, - ) -> EventLoop<Io> { + ) -> EventLoop<T> { let (tx, rx) = channel::channel(); EventLoop { poll: mio::Poll::new().expect("create mio Poll"), @@ -203,7 +201,7 @@ impl<Io> EventLoop<Io> match msg { Msg::Input(input) => { state.write_list.push_back(input); - }, + } Msg::Shutdown => { return DrainResult::Shutdown; } @@ -224,32 +222,22 @@ impl<Io> EventLoop<Io> return false; } - self.poll.reregister( - &self.rx, CHANNEL, - Ready::readable(), - PollOpt::edge() | PollOpt::oneshot() - ).expect("reregister channel"); - - if state.needs_write() { - self.poll.reregister( - &EventedFd(&self.pty.as_raw_fd()), - PTY, - Ready::readable() | Ready::writable(), - PollOpt::edge() | PollOpt::oneshot() - ).expect("reregister fd after channel recv"); - } + self.poll + .reregister(&self.rx, CHANNEL, Ready::readable(), PollOpt::edge() | PollOpt::oneshot()) + .unwrap(); true } #[inline] - fn pty_read<W>( + fn pty_read<X>( &mut self, state: &mut State, buf: &mut [u8], - mut writer: Option<&mut W> + mut writer: Option<&mut X>, ) -> io::Result<()> - where W: Write + where + X: Write, { const MAX_READ: usize = 0x1_0000; let mut processed = 0; @@ -259,7 +247,7 @@ impl<Io> EventLoop<Io> let mut send_wakeup = false; loop { - match self.pty.read(&mut buf[..]) { + match self.pty.reader().read(&mut buf[..]) { Ok(0) => break, Ok(got) => { // Record bytes read; used to limit time spent in pty_read. @@ -286,23 +274,22 @@ impl<Io> EventLoop<Io> // Run the parser for byte in &buf[..got] { - state.parser.advance(&mut **terminal, *byte, &mut self.pty); + state + .parser + .advance(&mut **terminal, *byte, &mut self.pty.writer()); } // Exit if we've processed enough bytes if processed > MAX_READ { break; } - }, - Err(err) => { - match err.kind() { - ErrorKind::Interrupted | - ErrorKind::WouldBlock => { - break; - }, - _ => return Err(err), - } } + Err(err) => match err.kind() { + ErrorKind::Interrupted | ErrorKind::WouldBlock => { + break; + } + _ => return Err(err), + }, } } @@ -323,56 +310,53 @@ impl<Io> EventLoop<Io> 'write_many: while let Some(mut current) = state.take_current() { 'write_one: loop { - match self.pty.write(current.remaining_bytes()) { + match self.pty.writer().write(current.remaining_bytes()) { Ok(0) => { state.set_current(Some(current)); break 'write_many; - }, + } Ok(n) => { current.advance(n); if current.finished() { state.goto_next(); break 'write_one; } - }, + } Err(err) => { state.set_current(Some(current)); match err.kind() { - ErrorKind::Interrupted | - ErrorKind::WouldBlock => break 'write_many, + ErrorKind::Interrupted | ErrorKind::WouldBlock => break 'write_many, _ => return Err(err), } } } - } } Ok(()) } - pub fn spawn( - mut self, - state: Option<State> - ) -> thread::JoinHandle<(EventLoop<Io>, State)> { + pub fn spawn(mut self, state: Option<State>) -> thread::JoinHandle<(Self, State)> { thread::spawn_named("pty reader", move || { let mut state = state.unwrap_or_else(Default::default); let mut buf = [0u8; 0x1000]; - let fd = self.pty.as_raw_fd(); - let fd = EventedFd(&fd); - let poll_opts = PollOpt::edge() | PollOpt::oneshot(); - self.poll.register(&self.rx, CHANNEL, Ready::readable(), poll_opts).unwrap(); - self.poll.register(&fd, PTY, Ready::readable(), poll_opts).unwrap(); + let tokens = [1, 2]; + + self.poll + .register(&self.rx, CHANNEL, Ready::readable(), poll_opts) + .unwrap(); + + // Register TTY through EventedRW interface + self.pty + .register(&self.poll, &mut tokens.iter(), Ready::readable(), poll_opts).unwrap(); let mut events = Events::with_capacity(1024); let mut pipe = if self.ref_test { - let file = File::create("./alacritty.recording") - .expect("create alacritty recording"); - Some(file) + Some(File::create("./alacritty.recording").expect("create alacritty recording")) } else { None }; @@ -381,64 +365,68 @@ impl<Io> EventLoop<Io> if let Err(err) = self.poll.poll(&mut events, None) { match err.kind() { ErrorKind::Interrupted => continue, - _ => panic!("EventLoop polling error: {:?}", err) + _ => panic!("EventLoop polling error: {:?}", err), } } for event in events.iter() { match event.token() { - CHANNEL => { - if !self.channel_event(&mut state) { - break 'event_loop; - } + CHANNEL => if !self.channel_event(&mut state) { + break 'event_loop; }, - PTY => { - let ready = event.readiness(); - - #[cfg(unix)] { - if UnixReady::from(ready).is_hup() { - break 'event_loop; - } - } - - if ready.is_readable() { - if let Err(err) = self.pty_read(&mut state, &mut buf, pipe.as_mut()) { - error!("Event loop exiting due to error: {} [{}:{}]", - err, file!(), line!()); - break 'event_loop; + token if token == self.pty.read_token() || token == self.pty.write_token() => { + #[cfg(unix)] + { + if UnixReady::from(event.readiness()).is_hup() { + break 'event_loop; + } } + if event.readiness().is_readable() { + if let Err(err) = self.pty_read(&mut state, &mut buf, pipe.as_mut()) + { + error!( + "Event loop exitting due to error: {} [{}:{}]", + err, + file!(), + line!() + ); + break 'event_loop; + } if ::tty::process_should_exit() { break 'event_loop; } } - if ready.is_writable() { + if event.readiness().is_writable() { if let Err(err) = self.pty_write(&mut state) { - error!("Event loop exiting due to error: {} [{}:{}]", - err, file!(), line!()); + error!( + "Event loop exitting due to error: {} [{}:{}]", + err, + file!(), + line!() + ); break 'event_loop; } } - - // Figure out pty interest - let mut interest = Ready::readable(); - if state.needs_write() { - interest.insert(Ready::writable()); - } - - // Reregister pty - self.poll - .reregister(&fd, PTY, interest, poll_opts) - .expect("register fd after read/write"); - }, + } _ => (), } } + + // Register write interest if necessary + let mut interest = Ready::readable(); + if state.needs_write() { + interest.insert(Ready::writable()); + } + // Reregister with new interest + self.pty.reregister(&self.poll, interest, poll_opts).unwrap(); } + // The evented instances are not dropped here so deregister them explicitly + // TODO: Is this still necessary? let _ = self.poll.deregister(&self.rx); - let _ = self.poll.deregister(&fd); + self.pty.deregister(&self.poll).unwrap(); (self, state) }) diff --git a/src/input.rs b/src/input.rs index cc3f13df..6d3b407a 100644 --- a/src/input.rs +++ b/src/input.rs @@ -22,6 +22,7 @@ use std::borrow::Cow; use std::mem; use std::process::Command; use std::time::Instant; +#[cfg(not(windows))] use std::os::unix::process::CommandExt; use copypasta::{Clipboard, Load, Buffer as ClipboardBuffer}; @@ -232,7 +233,9 @@ impl Action { }, Action::Command(ref program, ref args) => { trace!("running command: {} {:?}", program, args); - match Command::new(program) + + #[cfg(not(windows))] + let spawned = Command::new(program) .args(args) .before_exec(|| { // Detach forked process from Alacritty. This will cause @@ -240,7 +243,14 @@ impl Action { unsafe { ::libc::daemon(1, 0); } Ok(()) }) - .spawn() + .spawn(); + + #[cfg(windows)] + let spawned = Command::new(program) + .args(args) + .spawn(); + + match spawned { Ok(child) => { debug!("spawned new proc with pid: {}", child.id()); @@ -23,9 +23,23 @@ #[macro_use] extern crate serde_derive; #[macro_use] extern crate static_assertions; -#[cfg(any(target_os = "linux", target_os = "freebsd", target_os="dragonfly", target_os="openbsd"))] +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", + target_os = "openbsd"))] extern crate x11_dl; +#[cfg(windows)] +extern crate mio_named_pipes; +#[cfg(windows)] +extern crate winapi; +#[cfg(windows)] +extern crate winpty; +#[cfg(windows)] +extern crate dunce; +#[cfg(windows)] +extern crate winit; +#[cfg(windows)] +extern crate image; + #[cfg(target_os = "macos")] #[macro_use] extern crate objc; @@ -33,8 +47,8 @@ extern crate objc; extern crate arraydeque; extern crate cgmath; extern crate copypasta; -extern crate errno; extern crate env_logger; +extern crate errno; extern crate fnv; extern crate font; extern crate glutin; diff --git a/src/main.rs b/src/main.rs index 03a372df..651c5abd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,12 @@ #![cfg_attr(feature = "nightly", feature(core_intrinsics))] #![cfg_attr(all(test, feature = "bench"), feature(test))] +// With the default subsystem, 'console', windows creates an additional console +// window for the program. +// This is silently ignored on non-windows systems. +// See https://msdn.microsoft.com/en-us/library/4cc7ya5b.aspx for more details. +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + #[macro_use] extern crate alacritty; @@ -29,6 +35,8 @@ use std::error::Error; use std::sync::Arc; #[cfg(target_os = "macos")] use std::env; +#[cfg(not(windows))] +use std::os::unix::io::AsRawFd; use alacritty::cli; use alacritty::config::{self, Config}; @@ -39,7 +47,7 @@ use alacritty::event_loop::{self, EventLoop, Msg}; use alacritty::locale; use alacritty::logging; use alacritty::sync::FairMutex; -use alacritty::term::{Term}; +use alacritty::term::Term; use alacritty::tty::{self, process_should_exit}; use alacritty::util::fmt::Red; @@ -67,7 +75,8 @@ fn main() { /// /// If a configuration file is given as a command line argument we don't /// generate a default file. If an empty configuration file is given, i.e. -/// /dev/null, we load the compiled-in defaults. +/// /dev/null, we load the compiled-in defaults.) +#[cfg(not(windows))] fn load_config(options: &cli::Options) -> Config { let config_path = options.config_path() .or_else(Config::installed_config) @@ -81,6 +90,27 @@ fn load_config(options: &cli::Options) -> Config { Config::default() }) } +#[cfg(windows)] +fn load_config(options: &cli::Options) -> Config { + let config_path = options + .config_path() + .or_else(|| Config::installed_config()) + .unwrap_or_else(|| { + Config::write_defaults() + .unwrap_or_else(|err| die!("Write defaults config failure: {}", err)) + }); + + Config::load_from(&*config_path).unwrap_or_else(|err| match err { + config::Error::NotFound => { + die!("Config file not found after writing: {}", config_path.display()); + } + config::Error::Empty => { + eprintln!("Empty config; Loading defaults"); + Config::default() + } + _ => die!("{}", err), + }) +} /// Run Alacritty /// @@ -122,7 +152,17 @@ fn run(mut config: Config, options: &cli::Options) -> Result<(), Box<Error>> { // 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 mut pty = tty::new(&config, options, &display.size(), window_id); + let pty = tty::new(&config, options, &display.size(), window_id); + + // Get a reference to something that we can resize + // + // This exists because rust doesn't know the interface is thread-safe + // and we need to be able to resize the PTY from the main thread while the IO + // thread owns the EventedRW object. + #[cfg(windows)] + let resize_handle = unsafe { &mut *pty.winpty.get() }; + #[cfg(not(windows))] + let mut resize_handle = pty.fd.as_raw_fd(); // Create the pseudoterminal I/O loop // @@ -133,7 +173,7 @@ fn run(mut config: Config, options: &cli::Options) -> Result<(), Box<Error>> { let event_loop = EventLoop::new( Arc::clone(&terminal), display.notifier(), - pty.reader(), + pty, options.ref_test, ); @@ -168,7 +208,9 @@ fn run(mut config: Config, options: &cli::Options) -> Result<(), Box<Error>> { }; // Kick off the I/O thread - let io_thread = event_loop.spawn(None); + let _io_thread = event_loop.spawn(None); + + info!("Initialisation complete"); // Main display loop loop { @@ -195,7 +237,11 @@ fn run(mut config: Config, options: &cli::Options) -> Result<(), Box<Error>> { // // The second argument is a list of types that want to be notified // of display size changes. - display.handle_resize(&mut terminal_lock, &config, &mut [&mut pty, &mut processor]); + #[cfg(windows)] + display.handle_resize(&mut terminal_lock, &config, &mut [resize_handle, &mut processor]); + #[cfg(not(windows))] + display.handle_resize(&mut terminal_lock, &config, &mut [&mut resize_handle, &mut processor]); + drop(terminal_lock); // Draw the current state of the terminal @@ -208,13 +254,12 @@ fn run(mut config: Config, options: &cli::Options) -> Result<(), Box<Error>> { } } - loop_tx.send(Msg::Shutdown).expect("Error sending shutdown to event loop"); + loop_tx + .send(Msg::Shutdown) + .expect("Error sending shutdown to event loop"); // FIXME patch notify library to have a shutdown method // config_reloader.join().ok(); - // Wait for the I/O thread thread to finish - let _ = io_thread.join(); - Ok(()) } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index bcc896ef..28b44633 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -16,22 +16,22 @@ use std::fs::File; use std::hash::BuildHasherDefault; use std::io::{self, Read}; use std::mem::size_of; -use std::path::{PathBuf}; +use std::path::PathBuf; use std::ptr; use std::sync::mpsc; use std::time::Duration; use cgmath; use fnv::FnvHasher; -use font::{self, Rasterizer, Rasterize, RasterizedGlyph, FontDesc, GlyphKey, FontKey}; +use font::{self, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer}; use gl::types::*; use gl; -use index::{Line, Column, RangeInclusive}; -use notify::{Watcher, watcher, RecursiveMode, DebouncedEvent}; +use index::{Column, Line, RangeInclusive}; +use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use config::{self, Config, Delta}; use term::{self, cell, RenderableCell}; -use window::{Size, Pixels}; +use window::{Pixels, Size}; use Rgb; @@ -40,12 +40,10 @@ static TEXT_SHADER_F_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/ static TEXT_SHADER_V_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.v.glsl"); // Shader source which is used when live-shader-reload feature is disable -static TEXT_SHADER_F: &'static str = include_str!( - concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.f.glsl") -); -static TEXT_SHADER_V: &'static str = include_str!( - concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.v.glsl") -); +static TEXT_SHADER_F: &'static str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.f.glsl")); +static TEXT_SHADER_V: &'static str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.v.glsl")); /// `LoadGlyph` allows for copying a rasterized glyph into graphics memory pub trait LoadGlyph { @@ -97,7 +95,6 @@ impl From<ShaderCreationError> for Error { } } - /// Text drawing program /// /// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a". @@ -127,7 +124,6 @@ pub struct ShaderProgram { padding_y: u8, } - #[derive(Debug, Clone)] pub struct Glyph { tex_id: GLuint, @@ -174,9 +170,10 @@ impl GlyphCache { pub fn new<L>( mut rasterizer: Rasterizer, font: &config::Font, - loader: &mut L + loader: &mut L, ) -> Result<GlyphCache, font::Error> - where L: LoadGlyph + where + L: LoadGlyph, { let (regular, bold, italic) = Self::compute_font_keys(font, &mut rasterizer)?; @@ -184,7 +181,8 @@ impl GlyphCache { // The glyph requested here ('m' at the time of writing) has no special // meaning. rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; - let metrics = rasterizer.metrics(regular)?; + + let metrics = rasterizer.metrics(regular, font.size())?; let mut cache = GlyphCache { cache: HashMap::default(), @@ -204,11 +202,7 @@ impl GlyphCache { Ok(cache) } - fn load_glyphs_for_font<L: LoadGlyph>( - &mut self, - font: FontKey, - loader: &mut L, - ) { + fn load_glyphs_for_font<L: LoadGlyph>(&mut self, font: FontKey, loader: &mut L) { let size = self.font_size; for i in RangeInclusive::new(32u8, 128u8) { self.get(GlyphKey { @@ -222,22 +216,23 @@ impl GlyphCache { /// Computes font keys for (Regular, Bold, Italic) fn compute_font_keys( font: &config::Font, - rasterizer: &mut Rasterizer + rasterizer: &mut Rasterizer, ) -> Result<(FontKey, FontKey, FontKey), font::Error> { let size = font.size(); // Load regular font let regular_desc = Self::make_desc(&font.normal, font::Slant::Normal, font::Weight::Normal); - let regular = rasterizer - .load_font(®ular_desc, size)?; + let regular = rasterizer.load_font(®ular_desc, size)?; // helper to load a description if it is not the regular_desc - let mut load_or_regular = |desc:FontDesc| { + let mut load_or_regular = |desc: FontDesc| { if desc == regular_desc { regular } else { - rasterizer.load_font(&desc, size).unwrap_or_else(|_| regular) + rasterizer + .load_font(&desc, size) + .unwrap_or_else(|_| regular) } }; @@ -269,7 +264,7 @@ impl GlyphCache { pub fn font_metrics(&self) -> font::Metrics { self.rasterizer - .metrics(self.font_key) + .metrics(self.font_key, self.font_size) .expect("metrics load since font is loaded at glyph cache creation") } @@ -290,7 +285,7 @@ impl GlyphCache { rasterized.top -= metrics.descent as i32; loader.load_glyph(&rasterized) - }) + }) } pub fn update_font_size<L: LoadGlyph>( &mut self, @@ -306,8 +301,9 @@ impl GlyphCache { let font = font.to_owned().with_size(size); info!("Font size changed: {:?}", font.size); let (regular, bold, italic) = Self::compute_font_keys(&font, &mut self.rasterizer)?; + self.rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; - let metrics = self.rasterizer.metrics(regular)?; + let metrics = self.rasterizer.metrics(regular, size)?; self.font_size = font.size; self.font_key = regular; @@ -405,11 +401,7 @@ impl Batch { } } - pub fn add_item( - &mut self, - cell: &RenderableCell, - glyph: &Glyph, - ) { + pub fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph) { if self.is_empty() { self.tex = glyph.tex_id; } @@ -509,69 +501,99 @@ impl QuadRenderer { gl::BindBuffer(gl::ARRAY_BUFFER, vbo); - gl::VertexAttribPointer(0, 2, - gl::FLOAT, gl::FALSE, - size_of::<PackedVertex>() as i32, - ptr::null()); + gl::VertexAttribPointer( + 0, + 2, + gl::FLOAT, + gl::FALSE, + size_of::<PackedVertex>() as i32, + ptr::null(), + ); gl::EnableVertexAttribArray(0); - gl::BufferData(gl::ARRAY_BUFFER, - (size_of::<PackedVertex>() * vertices.len()) as GLsizeiptr, - vertices.as_ptr() as *const _, - gl::STATIC_DRAW); + gl::BufferData( + gl::ARRAY_BUFFER, + (size_of::<PackedVertex>() * vertices.len()) as GLsizeiptr, + vertices.as_ptr() as *const _, + gl::STATIC_DRAW, + ); // --------------------- // Set up element buffer // --------------------- - let indices: [u32; 6] = [0, 1, 3, - 1, 2, 3]; + let indices: [u32; 6] = [0, 1, 3, 1, 2, 3]; gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo); - gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, - (6 * size_of::<u32>()) as isize, - indices.as_ptr() as *const _, - gl::STATIC_DRAW); + gl::BufferData( + gl::ELEMENT_ARRAY_BUFFER, + (6 * size_of::<u32>()) as isize, + indices.as_ptr() as *const _, + gl::STATIC_DRAW, + ); // ---------------------------- // Setup vertex instance buffer // ---------------------------- gl::BindBuffer(gl::ARRAY_BUFFER, vbo_instance); - gl::BufferData(gl::ARRAY_BUFFER, - (BATCH_MAX * size_of::<InstanceData>()) as isize, - ptr::null(), gl::STREAM_DRAW); + gl::BufferData( + gl::ARRAY_BUFFER, + (BATCH_MAX * size_of::<InstanceData>()) as isize, + ptr::null(), + gl::STREAM_DRAW, + ); // coords - gl::VertexAttribPointer(1, 2, - gl::FLOAT, gl::FALSE, - size_of::<InstanceData>() as i32, - ptr::null()); + gl::VertexAttribPointer( + 1, + 2, + gl::FLOAT, + gl::FALSE, + size_of::<InstanceData>() as i32, + ptr::null(), + ); gl::EnableVertexAttribArray(1); gl::VertexAttribDivisor(1, 1); // glyphoffset - gl::VertexAttribPointer(2, 4, - gl::FLOAT, gl::FALSE, - size_of::<InstanceData>() as i32, - (2 * size_of::<f32>()) as *const _); + gl::VertexAttribPointer( + 2, + 4, + gl::FLOAT, + gl::FALSE, + size_of::<InstanceData>() as i32, + (2 * size_of::<f32>()) as *const _, + ); gl::EnableVertexAttribArray(2); gl::VertexAttribDivisor(2, 1); // uv - gl::VertexAttribPointer(3, 4, - gl::FLOAT, gl::FALSE, - size_of::<InstanceData>() as i32, - (6 * size_of::<f32>()) as *const _); + gl::VertexAttribPointer( + 3, + 4, + gl::FLOAT, + gl::FALSE, + size_of::<InstanceData>() as i32, + (6 * size_of::<f32>()) as *const _, + ); gl::EnableVertexAttribArray(3); gl::VertexAttribDivisor(3, 1); // color - gl::VertexAttribPointer(4, 3, - gl::FLOAT, gl::FALSE, - size_of::<InstanceData>() as i32, - (10 * size_of::<f32>()) as *const _); + gl::VertexAttribPointer( + 4, + 3, + gl::FLOAT, + gl::FALSE, + size_of::<InstanceData>() as i32, + (10 * size_of::<f32>()) as *const _, + ); gl::EnableVertexAttribArray(4); gl::VertexAttribDivisor(4, 1); // color - gl::VertexAttribPointer(5, 4, - gl::FLOAT, gl::FALSE, - size_of::<InstanceData>() as i32, - (13 * size_of::<f32>()) as *const _); + gl::VertexAttribPointer( + 5, + 4, + gl::FLOAT, + gl::FALSE, + size_of::<InstanceData>() as i32, + (13 * size_of::<f32>()) as *const _, + ); gl::EnableVertexAttribArray(5); gl::VertexAttribDivisor(5, 1); @@ -585,18 +607,23 @@ impl QuadRenderer { ::std::thread::spawn(move || { let (tx, rx) = ::std::sync::mpsc::channel(); // The Duration argument is a debouncing period. - let mut watcher = watcher(tx, Duration::from_millis(10)).expect("create file watcher"); - watcher.watch(TEXT_SHADER_F_PATH, RecursiveMode::NonRecursive) - .expect("watch fragment shader"); - watcher.watch(TEXT_SHADER_V_PATH, RecursiveMode::NonRecursive) - .expect("watch vertex shader"); + let mut watcher = + watcher(tx, Duration::from_millis(10)).expect("create file watcher"); + watcher + .watch(TEXT_SHADER_F_PATH, RecursiveMode::NonRecursive) + .expect("watch fragment shader"); + watcher + .watch(TEXT_SHADER_V_PATH, RecursiveMode::NonRecursive) + .expect("watch vertex shader"); loop { let event = rx.recv().expect("watcher event"); match event { DebouncedEvent::Rename(_, _) => continue, - DebouncedEvent::Create(_) | DebouncedEvent::Write(_) | DebouncedEvent::Chmod(_) => { + DebouncedEvent::Create(_) + | DebouncedEvent::Write(_) + | DebouncedEvent::Chmod(_) => { msg_tx.send(Msg::ShaderReload).expect("msg send ok"); } _ => {} @@ -629,17 +656,21 @@ impl QuadRenderer { config: &Config, props: &term::SizeInfo, visual_bell_intensity: f64, - func: F + func: F, ) -> T - where F: FnOnce(RenderApi) -> T + where + F: FnOnce(RenderApi) -> T, { while let Ok(msg) = self.rx.try_recv() { match msg { Msg::ShaderReload => { - self.reload_shaders(config, Size { - width: Pixels(props.width as u32), - height: Pixels(props.height as u32) - }); + self.reload_shaders( + config, + Size { + width: Pixels(props.width as u32), + height: Pixels(props.height as u32), + }, + ); } } } @@ -677,7 +708,8 @@ impl QuadRenderer { } pub fn with_loader<F, T>(&mut self, func: F) -> T - where F: FnOnce(LoaderApi) -> T + where + F: FnOnce(LoaderApi) -> T, { unsafe { gl::ActiveTexture(gl::TEXTURE0); @@ -696,12 +728,12 @@ impl QuadRenderer { Ok(program) => { warn!(" ... OK"); program - }, + } Err(err) => { match err { ShaderCreationError::Io(err) => { error!("Error reading shader file: {}", err); - }, + } ShaderCreationError::Compile(path, log) => { error!("Error compiling shader at {:?}\n{}", path, log); } @@ -724,7 +756,12 @@ impl QuadRenderer { // viewport unsafe { - gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y); + gl::Viewport( + padding_x, + padding_y, + width - 2 * padding_x, + height - 2 * padding_y, + ); } // update projection @@ -750,8 +787,12 @@ impl<'a> RenderApi<'a> { fn render_batch(&mut self) { unsafe { - gl::BufferSubData(gl::ARRAY_BUFFER, 0, self.batch.size() as isize, - self.batch.instances.as_ptr() as *const _); + gl::BufferSubData( + gl::ARRAY_BUFFER, + 0, + self.batch.size() as isize, + self.batch.instances.as_ptr() as *const _, + ); } // Bind texture if necessary @@ -764,29 +805,33 @@ impl<'a> RenderApi<'a> { unsafe { self.program.set_background_pass(true); - gl::DrawElementsInstanced(gl::TRIANGLES, - 6, gl::UNSIGNED_INT, ptr::null(), - self.batch.len() as GLsizei); + gl::DrawElementsInstanced( + gl::TRIANGLES, + 6, + gl::UNSIGNED_INT, + ptr::null(), + self.batch.len() as GLsizei, + ); self.program.set_background_pass(false); - gl::DrawElementsInstanced(gl::TRIANGLES, - 6, gl::UNSIGNED_INT, ptr::null(), - self.batch.len() as GLsizei); + gl::DrawElementsInstanced( + gl::TRIANGLES, + 6, + gl::UNSIGNED_INT, + ptr::null(), + self.batch.len() as GLsizei, + ); } self.batch.clear(); } /// Render a string in a predefined location. Used for printing render time for profiling and /// optimization. - pub fn render_string( - &mut self, - string: &str, - glyph_cache: &mut GlyphCache, - color: Rgb, - ) { + pub fn render_string(&mut self, string: &str, glyph_cache: &mut GlyphCache, color: Rgb) { let line = Line(23); let col = Column(0); - let cells = string.chars() + let cells = string + .chars() .enumerate() .map(|(i, c)| RenderableCell { line, @@ -795,7 +840,7 @@ impl<'a> RenderApi<'a> { bg: color, fg: Rgb { r: 0, g: 0, b: 0 }, flags: cell::Flags::empty(), - bg_alpha: 1.0 + bg_alpha: 1.0, }) .collect::<Vec<_>>(); @@ -838,7 +883,7 @@ impl<'a> RenderApi<'a> { let mut glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, - c: cell.c + c: cell.c, }; // Don't render text of HIDDEN cells @@ -859,7 +904,7 @@ impl<'a> RenderApi<'a> { let glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, - c: '_' + c: '_', }; let underscore = glyph_cache.get(glyph_key, self); @@ -959,28 +1004,22 @@ impl ShaderProgram { pub fn new( config: &Config, - size: Size<Pixels<u32>> + size: Size<Pixels<u32>>, ) -> Result<ShaderProgram, ShaderCreationError> { let vertex_source = if cfg!(feature = "live-shader-reload") { None } else { Some(TEXT_SHADER_V) }; - let vertex_shader = ShaderProgram::create_shader( - TEXT_SHADER_V_PATH, - gl::VERTEX_SHADER, - vertex_source - )?; + let vertex_shader = + ShaderProgram::create_shader(TEXT_SHADER_V_PATH, gl::VERTEX_SHADER, vertex_source)?; let frag_source = if cfg!(feature = "live-shader-reload") { None } else { Some(TEXT_SHADER_F) }; - let fragment_shader = ShaderProgram::create_shader( - TEXT_SHADER_F_PATH, - gl::FRAGMENT_SHADER, - frag_source - )?; + let fragment_shader = + ShaderProgram::create_shader(TEXT_SHADER_F_PATH, gl::FRAGMENT_SHADER, frag_source)?; let program = ShaderProgram::create_program(vertex_shader, fragment_shader)?; unsafe { @@ -1060,10 +1099,13 @@ impl ShaderProgram { info!("width: {}, height: {}", width, height); unsafe { - gl::UniformMatrix4fv(self.u_projection, - 1, gl::FALSE, projection.as_ptr() as *const _); + gl::UniformMatrix4fv( + self.u_projection, + 1, + gl::FALSE, + projection.as_ptr() as *const _, + ); } - } fn set_term_uniforms(&self, props: &term::SizeInfo) { @@ -1080,11 +1122,7 @@ impl ShaderProgram { } fn set_background_pass(&self, background_pass: bool) { - let value = if background_pass { - 1 - } else { - 0 - }; + let value = if background_pass { 1 } else { 0 }; unsafe { gl::Uniform1i(self.u_background, value); @@ -1109,11 +1147,10 @@ impl ShaderProgram { } } - fn create_shader( path: &str, kind: GLenum, - source: Option<&'static str> + source: Option<&'static str>, ) -> Result<GLuint, ShaderCreationError> { let from_disk; let source = if let Some(src) = source { @@ -1144,7 +1181,9 @@ impl ShaderProgram { let log = get_shader_info_log(shader); // Cleanup - unsafe { gl::DeleteShader(shader); } + unsafe { + gl::DeleteShader(shader); + } Err(ShaderCreationError::Compile(PathBuf::from(path), log)) } @@ -1170,7 +1209,12 @@ fn get_program_info_log(program: GLuint) -> String { let mut actual_length: GLint = 0; let mut buf: Vec<u8> = Vec::with_capacity(max_length as usize); unsafe { - gl::GetProgramInfoLog(program, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _); + gl::GetProgramInfoLog( + program, + max_length, + &mut actual_length, + buf.as_mut_ptr() as *mut _, + ); } // Build a string @@ -1193,7 +1237,12 @@ fn get_shader_info_log(shader: GLuint) -> String { let mut actual_length: GLint = 0; let mut buf: Vec<u8> = Vec::with_capacity(max_length as usize); unsafe { - gl::GetShaderInfoLog(shader, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _); + gl::GetShaderInfoLog( + shader, + max_length, + &mut actual_length, + buf.as_mut_ptr() as *mut _, + ); } // Build a string @@ -1248,10 +1297,8 @@ impl ::std::fmt::Display for ShaderCreationError { ShaderCreationError::Io(ref err) => write!(f, "couldn't read shader: {}", err), ShaderCreationError::Compile(ref _path, ref s) => { write!(f, "failed compiling shader: {}", s) - }, - ShaderCreationError::Link(ref s) => { - write!(f, "failed linking shader: {}", s) - }, + } + ShaderCreationError::Link(ref s) => write!(f, "failed linking shader: {}", s), } } } @@ -1262,7 +1309,6 @@ impl From<io::Error> for ShaderCreationError { } } - /// Manages a single texture atlas /// /// The strategy for filling an atlas looks roughly like this: @@ -1332,7 +1378,7 @@ impl Atlas { 0, gl::RGB, gl::UNSIGNED_BYTE, - ptr::null() + ptr::null(), ); gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32); @@ -1407,7 +1453,7 @@ impl Atlas { height, gl::RGB, gl::UNSIGNED_BYTE, - glyph.buf.as_ptr() as *const _ + glyph.buf.as_ptr() as *const _, ); gl::BindTexture(gl::TEXTURE_2D, 0); diff --git a/src/tty/mod.rs b/src/tty/mod.rs new file mode 100644 index 00000000..5657b0fd --- /dev/null +++ b/src/tty/mod.rs @@ -0,0 +1,45 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//! tty related functionality + +use mio; +use std::io; + +#[cfg(not(windows))] +mod unix; +#[cfg(not(windows))] +pub use self::unix::*; + +#[cfg(windows)] +mod windows; +#[cfg(windows)] +pub use self::windows::*; + +/// This trait defines the behaviour needed to read and/or write to a stream. +/// It defines an abstraction over mio's interface in order to allow either one +/// read/write object or a seperate read and write object. +pub trait EventedReadWrite { + type Reader: io::Read; + type Writer: io::Write; + + fn register(&mut self, &mio::Poll, &mut Iterator<Item = &usize>, mio::Ready, mio::PollOpt) -> io::Result<()>; + fn reregister(&mut self, &mio::Poll, mio::Ready, mio::PollOpt) -> io::Result<()>; + fn deregister(&mut self, &mio::Poll) -> io::Result<()>; + + fn reader(&mut self) -> &mut Self::Reader; + fn read_token(&self) -> mio::Token; + fn writer(&mut self) -> &mut Self::Writer; + fn write_token(&self) -> mio::Token; +} diff --git a/src/tty.rs b/src/tty/unix.rs index b4a8e316..08a2c4f3 100644 --- a/src/tty.rs +++ b/src/tty/unix.rs @@ -14,20 +14,26 @@ // //! tty related functionality //! -use std::ffi::CStr; -use std::fs::File; -use std::os::unix::io::FromRawFd; -use std::os::unix::process::CommandExt; -use std::ptr; -use std::process::{Command, Stdio}; - -use libc::{self, winsize, c_int, pid_t, WNOHANG, SIGCHLD, TIOCSCTTY}; -use terminfo::Database; +use tty::EventedReadWrite; use term::SizeInfo; use display::OnResize; use config::{Config, Shell}; use cli::Options; +use mio; + +use libc::{self, c_int, pid_t, winsize, SIGCHLD, TIOCSCTTY, WNOHANG}; +use terminfo::Database; + +use std::os::unix::io::{FromRawFd, RawFd}; +use std::fs::File; +use std::os::unix::process::CommandExt; +use std::process::{Command, Stdio}; +use std::ffi::CStr; +use std::ptr; +use mio::unix::EventedFd; +use std::io; +use std::os::unix::io::AsRawFd; /// Process ID of child process @@ -164,7 +170,6 @@ fn get_pw_entry(buf: &mut [i8; 1024]) -> Passwd { die!("pw not found"); } - // sanity check assert_eq!(entry.pw_uid, uid); @@ -180,8 +185,37 @@ fn get_pw_entry(buf: &mut [i8; 1024]) -> Passwd { } } +pub struct Pty { + pub fd: File, + pub raw_fd: RawFd, + token: mio::Token, +} + +impl Pty { + /// Resize the pty + /// + /// Tells the kernel that the window size changed with the new pixel + /// dimensions and line/column counts. + pub fn resize<T: ToWinsize>(&self, size: &T) { + let win = size.to_winsize(); + + let res = unsafe { + libc::ioctl(self.fd.as_raw_fd(), libc::TIOCSWINSZ, &win as *const _) + }; + + if res < 0 { + die!("ioctl TIOCSWINSZ failed: {}", errno()); + } + } +} + /// Create a new tty and return a handle to interact with it. -pub fn new<T: ToWinsize>(config: &Config, options: &Options, size: &T, window_id: Option<usize>) -> Pty { +pub fn new<T: ToWinsize>( + config: &Config, + options: &Options, + size: &T, + window_id: Option<usize>, +) -> Pty { let win = size.to_winsize(); let mut buf = [0; 1024]; let pw = get_pw_entry(&mut buf); @@ -279,7 +313,11 @@ pub fn new<T: ToWinsize>(config: &Config, options: &Options, size: &T, window_id set_nonblocking(master); } - let pty = Pty { fd: master }; + let pty = Pty { + fd: unsafe {File::from_raw_fd(master) }, + raw_fd: master, + token: mio::Token::from(0) + }; pty.resize(size); pty }, @@ -289,34 +327,60 @@ pub fn new<T: ToWinsize>(config: &Config, options: &Options, size: &T, window_id } } -pub struct Pty { - fd: c_int, -} +impl EventedReadWrite for Pty { + type Reader = File; + type Writer = File; + + #[inline] + fn register( + &mut self, + poll: &mio::Poll, + token: &mut Iterator<Item = &usize>, + interest: mio::Ready, + poll_opts: mio::PollOpt, + ) -> io::Result<()> { + self.token = (*token.next().unwrap()).into(); + poll.register( + &EventedFd(&self.raw_fd), + self.token, + interest, + poll_opts + ) + } -impl Pty { - /// Get reader for the TTY - /// - /// XXX File is a bad abstraction here; it closes the fd on drop - pub fn reader(&self) -> File { - unsafe { - File::from_raw_fd(self.fd) - } + #[inline] + fn reregister(&mut self, poll: &mio::Poll, interest: mio::Ready, poll_opts: mio::PollOpt) -> io::Result<()> { + poll.reregister( + &EventedFd(&self.raw_fd), + self.token, + interest, + poll_opts + ) } - /// Resize the pty - /// - /// Tells the kernel that the window size changed with the new pixel - /// dimensions and line/column counts. - pub fn resize<T: ToWinsize>(&self, size: &T) { - let win = size.to_winsize(); + #[inline] + fn deregister(&mut self, poll: &mio::Poll) -> io::Result<()> { + poll.deregister(&EventedFd(&self.raw_fd)) + } - let res = unsafe { - libc::ioctl(self.fd, libc::TIOCSWINSZ, &win as *const _) - }; + #[inline] + fn reader(&mut self) -> &mut File { + &mut self.fd + } - if res < 0 { - die!("ioctl TIOCSWINSZ failed: {}", errno()); - } + #[inline] + fn read_token(&self) -> mio::Token { + self.token + } + + #[inline] + fn writer(&mut self) -> &mut File { + &mut self.fd + } + + #[inline] + fn write_token(&self) -> mio::Token { + self.token } } @@ -337,14 +401,22 @@ impl<'a> ToWinsize for &'a SizeInfo { } } -impl OnResize for Pty { +impl OnResize for i32 { fn on_resize(&mut self, size: &SizeInfo) { - self.resize(&size); + let win = size.to_winsize(); + + let res = unsafe { + libc::ioctl(*self, libc::TIOCSWINSZ, &win as *const _) + }; + + if res < 0 { + die!("ioctl TIOCSWINSZ failed: {}", errno()); + } } } unsafe fn set_nonblocking(fd: c_int) { - use libc::{fcntl, F_SETFL, F_GETFL, O_NONBLOCK}; + use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK}; let res = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); assert_eq!(res, 0); diff --git a/src/tty/windows.rs b/src/tty/windows.rs new file mode 100644 index 00000000..9a452955 --- /dev/null +++ b/src/tty/windows.rs @@ -0,0 +1,284 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::io; +use std::fs::OpenOptions; +use std::os::raw::c_void; +use std::os::windows::io::{FromRawHandle, IntoRawHandle}; +use std::os::windows::fs::OpenOptionsExt; +use std::env; +use std::cell::UnsafeCell; + +use dunce::canonicalize; +use mio; +use mio::Evented; +use mio_named_pipes::NamedPipe; +use winapi::um::synchapi::WaitForSingleObject; +use winapi::um::winbase::{WAIT_OBJECT_0, FILE_FLAG_OVERLAPPED}; +use winapi::shared::winerror::WAIT_TIMEOUT; +use winpty::{ConfigFlags, MouseMode, SpawnConfig, SpawnFlags, Winpty}; +use winpty::Config as WinptyConfig; + +use config::{Config, Shell}; +use display::OnResize; +use cli::Options; +use tty::EventedReadWrite; +use term::SizeInfo; + +/// Handle to the winpty agent process. Required so we know when it closes. +static mut HANDLE: *mut c_void = 0usize as *mut c_void; + +/// How long the winpty agent should wait for any RPC request +/// This is a placeholder value until we see how often long responses happen +const AGENT_TIMEOUT: u32 = 10000; + +pub fn process_should_exit() -> bool { + unsafe { + match WaitForSingleObject(HANDLE, 0) { + // Process has exited + WAIT_OBJECT_0 => { + info!("wait_object_0"); + true + } + // Reached timeout of 0, process has not exited + WAIT_TIMEOUT => false, + // Error checking process, winpty gave us a bad agent handle? + _ => { + info!("Bad exit: {}", ::std::io::Error::last_os_error()); + true + } + } + } +} + +pub struct Pty<'a, R: io::Read + Evented + Send, W: io::Write + Evented + Send> { + // TODO: Provide methods for accessing this safely + pub winpty: UnsafeCell<Winpty<'a>>, + + conout: R, + conin: W, + read_token: mio::Token, + write_token: mio::Token, +} + +pub fn new<'a>( + config: &Config, + options: &Options, + size: &SizeInfo, + _window_id: Option<usize>, +) -> Pty<'a, NamedPipe, NamedPipe> { + // Create config + let mut wconfig = WinptyConfig::new(ConfigFlags::empty()).unwrap(); + + wconfig.set_initial_size(size.cols().0 as i32, size.lines().0 as i32); + wconfig.set_mouse_mode(&MouseMode::Auto); + wconfig.set_agent_timeout(AGENT_TIMEOUT); + + // Start agent + let mut winpty = Winpty::open(&wconfig).unwrap(); + let (conin, conout) = (winpty.conin_name(), winpty.conout_name()); + + // Get process commandline + let default_shell = &Shell::new(env::var("COMSPEC").unwrap_or_else(|_| "cmd".into())); + let shell = config.shell().unwrap_or(default_shell); + let initial_command = options.command().unwrap_or(shell); + let mut cmdline = initial_command.args().to_vec(); + cmdline.insert(0, initial_command.program().into()); + + // Warning, here be borrow hell + let cwd = options.working_dir.as_ref().map(|dir| canonicalize(dir).unwrap()); + let cwd = cwd.as_ref().map(|dir| dir.to_str().unwrap()); + + // Spawn process + let spawnconfig = SpawnConfig::new( + SpawnFlags::AUTO_SHUTDOWN | SpawnFlags::EXIT_AFTER_SHUTDOWN, + None, // appname + Some(&cmdline.join(" ")), + cwd, + None, // Env + ).unwrap(); + + let default_opts = &mut OpenOptions::new(); + default_opts + .share_mode(0) + .custom_flags(FILE_FLAG_OVERLAPPED); + + let (conout_pipe, conin_pipe); + unsafe { + conout_pipe = NamedPipe::from_raw_handle( + default_opts + .clone() + .read(true) + .open(conout) + .unwrap() + .into_raw_handle(), + ); + conin_pipe = NamedPipe::from_raw_handle( + default_opts + .clone() + .write(true) + .open(conin) + .unwrap() + .into_raw_handle(), + ); + }; + + if let Some(err) = conout_pipe.connect().err() { + if err.kind() != io::ErrorKind::WouldBlock { + panic!(err); + } + } + assert!(conout_pipe.take_error().unwrap().is_none()); + + if let Some(err) = conin_pipe.connect().err() { + if err.kind() != io::ErrorKind::WouldBlock { + panic!(err); + } + } + assert!(conin_pipe.take_error().unwrap().is_none()); + + winpty.spawn(&spawnconfig).unwrap(); + + unsafe { + HANDLE = winpty.raw_handle(); + } + + Pty { + winpty: UnsafeCell::new(winpty), + conout: conout_pipe, + conin: conin_pipe, + // Placeholder tokens that are overwritten + read_token: 0.into(), + write_token: 0.into(), + } +} + +impl<'a> EventedReadWrite for Pty<'a, NamedPipe, NamedPipe> { + type Reader = NamedPipe; + type Writer = NamedPipe; + + #[inline] + fn register( + &mut self, + poll: &mio::Poll, + token: &mut Iterator<Item = &usize>, + interest: mio::Ready, + poll_opts: mio::PollOpt, + ) -> io::Result<()> { + self.read_token = (*token.next().unwrap()).into(); + self.write_token = (*token.next().unwrap()).into(); + if interest.is_readable() { + poll.register( + &self.conout, + self.read_token, + mio::Ready::readable(), + poll_opts, + )? + } else { + poll.register( + &self.conout, + self.read_token, + mio::Ready::empty(), + poll_opts, + )? + } + if interest.is_writable() { + poll.register( + &self.conin, + self.write_token, + mio::Ready::writable(), + poll_opts, + )? + } else { + poll.register( + &self.conin, + self.write_token, + mio::Ready::empty(), + poll_opts, + )? + } + Ok(()) + } + + #[inline] + fn reregister(&mut self, poll: &mio::Poll, interest: mio::Ready, poll_opts: mio::PollOpt) -> io::Result<()> { + if interest.is_readable() { + poll.reregister( + &self.conout, + self.read_token, + mio::Ready::readable(), + poll_opts, + )?; + } else { + poll.reregister( + &self.conout, + self.read_token, + mio::Ready::empty(), + poll_opts, + )?; + } + if interest.is_writable() { + poll.reregister( + &self.conin, + self.write_token, + mio::Ready::writable(), + poll_opts, + )?; + } else { + poll.reregister( + &self.conin, + self.write_token, + mio::Ready::empty(), + poll_opts, + )?; + } + Ok(()) + } + + #[inline] + fn deregister(&mut self, poll: &mio::Poll) -> io::Result<()> { + poll.deregister(&self.conout)?; + poll.deregister(&self.conin)?; + Ok(()) + } + + #[inline] + fn reader(&mut self) -> &mut NamedPipe { + &mut self.conout + } + + #[inline] + fn read_token(&self) -> mio::Token { + self.read_token + } + + #[inline] + fn writer(&mut self) -> &mut NamedPipe { + &mut self.conin + } + + #[inline] + fn write_token(&self) -> mio::Token { + self.write_token + } +} + +impl<'a> OnResize for Winpty<'a> { + fn on_resize(&mut self, sizeinfo: &SizeInfo) { + if sizeinfo.cols().0 > 0 && sizeinfo.lines().0 > 0 { + self.set_size(sizeinfo.cols().0, sizeinfo.lines().0) + .unwrap_or_else(|_| info!("Unable to set winpty size, did it die?")); + } + } +} diff --git a/src/window.rs b/src/window.rs index eac4c014..2c1bf7b2 100644 --- a/src/window.rs +++ b/src/window.rs @@ -17,6 +17,10 @@ use std::ops::Deref; use gl; use glutin::GlContext; +#[cfg(windows)] +use winit::Icon; +#[cfg(windows)] +use image::ImageFormat; use glutin::{ self, ContextBuilder, ControlFlow, CursorState, Event, EventsLoop, MouseCursor as GlutinMouseCursor, WindowBuilder, @@ -27,6 +31,9 @@ use MouseCursor; use cli::Options; use config::{Decorations, WindowConfig}; +#[cfg(windows)] +static WINDOW_ICON: &'static [u8] = include_bytes!("../assets/windows/alacritty.ico"); + /// Default text for the window's title bar, if not overriden. /// /// In X11, this the default value for the `WM_NAME` property. @@ -215,7 +222,7 @@ impl Window { let event_loop = EventsLoop::new(); let title = options.title.as_ref().map_or(DEFAULT_TITLE, |t| t); - let class = options.class.as_ref().map_or(DEFAULT_CLASS, |c| c); + let class = options.class.as_ref().map_or(DEFAULT_TITLE, |c| c); let window_builder = Window::get_platform_window(title, window_config); let window_builder = Window::platform_builder_ext(window_builder, &class); let window = create_gl_window(window_builder.clone(), &event_loop, false) @@ -225,14 +232,14 @@ impl Window { // Text cursor window.set_cursor(GlutinMouseCursor::Text); - // Set OpenGL symbol loader - gl::load_with(|symbol| window.get_proc_address(symbol) as *const _); - // Make the context current so OpenGL operations can run unsafe { window.make_current()?; } + // Set OpenGL symbol loader. This call MUST be after window.make_current on windows. + gl::load_with(|symbol| window.get_proc_address(symbol) as *const _); + let window = Window { event_loop, window, @@ -304,8 +311,11 @@ impl Window { /// Set the window title #[inline] - pub fn set_title(&self, title: &str) { - self.window.set_title(title); + pub fn set_title(&self, _title: &str) { + // Because winpty doesn't know anything about OSC escapes this gets set to an empty + // string on windows + #[cfg(not(windows))] + self.window.set_title(_title); } #[inline] @@ -357,7 +367,7 @@ impl Window { window_builder } - #[cfg(not(target_os = "macos"))] + #[cfg(not(any(target_os = "macos", windows)))] pub fn get_platform_window(title: &str, window_config: &WindowConfig) -> WindowBuilder { let decorations = match window_config.decorations() { Decorations::None => false, @@ -371,6 +381,23 @@ impl Window { .with_decorations(decorations) } + #[cfg(windows)] + pub fn get_platform_window(title: &str, window_config: &WindowConfig) -> WindowBuilder { + let icon = Icon::from_bytes_with_format(WINDOW_ICON, ImageFormat::ICO).unwrap(); + + let decorations = match window_config.decorations() { + Decorations::None => false, + _ => true, + }; + + WindowBuilder::new() + .with_title(title) + .with_visibility(cfg!(windows)) + .with_decorations(decorations) + .with_transparency(true) + .with_window_icon(Some(icon)) + } + #[cfg(target_os = "macos")] pub fn get_platform_window(title: &str, window_config: &WindowConfig) -> WindowBuilder { use glutin::os::macos::WindowBuilderExt; @@ -421,11 +448,13 @@ impl Window { )] pub fn set_urgent(&self, _is_urgent: bool) {} - pub fn set_ime_spot(&self, x: i32, y: i32) { - self.window.set_ime_spot(x, y); + pub fn set_ime_spot(&self, _x: i32, _y: i32) { + // This is not implemented on windows as of winit 0.15.1 + #[cfg(not(windows))] + self.window.set_ime_spot(_x, _y); } - #[cfg(not(target_os = "macos"))] + #[cfg(not(any(target_os = "macos", target_os = "windows")))] pub fn get_window_id(&self) -> Option<usize> { use glutin::os::unix::WindowExt; @@ -435,7 +464,7 @@ impl Window { } } - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", target_os = "windows"))] pub fn get_window_id(&self) -> Option<usize> { None } |