[fuchsia] Minimal changes for building term_model on Fuchsia.

Change-Id: I830f56d8740e0eb4877b639c87e5a5bef7cd808b
diff --git a/alacritty_terminal/BUILD.gn b/alacritty_terminal/BUILD.gn
new file mode 100644
index 0000000..2829d6b
--- /dev/null
+++ b/alacritty_terminal/BUILD.gn
@@ -0,0 +1,64 @@
+# Copyright 2021 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/rust/rustc_library.gni")
+import("//build/rust/rustc_test.gni")
+
+term_sources = [
+  "src/ansi.rs",
+  "src/clipboard.rs",
+  "src/config/colors.rs",
+  "src/config/debug.rs",
+  "src/config/font.rs",
+  "src/config/mod.rs",
+  "src/config/scrolling.rs",
+  "src/config/visual_bell.rs",
+  "src/config/window.rs",
+  "src/event.rs",
+  "src/grid/mod.rs",
+  "src/grid/row.rs",
+  "src/grid/storage.rs",
+  "src/grid/tests.rs",
+  "src/index.rs",
+  "src/lib.rs",
+  "src/message_bar.rs",
+  "src/selection.rs",
+  "src/term/cell.rs",
+  "src/term/color.rs",
+  "src/term/mod.rs",
+]
+
+term_deps = [
+  "//third_party/rust_crates:base64",
+  "//third_party/rust_crates:bitflags",
+  "//third_party/rust_crates:log",
+  "//third_party/rust_crates:serde",
+  "//third_party/rust_crates:serde_json",
+  "//third_party/rust_crates:unicode-width",
+  "//third_party/rust_crates:vte",
+]
+
+rustc_library("term_model") {
+  name = "term_model"
+  version = "0.1.0"
+  edition = "2018"
+
+  # TODO(fxbug.dev/69442) remove this allowance
+  configs += [ "//build/config/rust:allow_legacy_derive_helpers" ]
+
+  deps = term_deps
+  sources = term_sources
+}
+
+rustc_test("term-model_test") {
+  name = "term_model_lib_test"
+  version = "0.1.0"
+  edition = "2018"
+
+  # TODO(fxbug.dev/69442) remove this allowance
+  configs += [ "//build/config/rust:allow_legacy_derive_helpers" ]
+
+  deps = term_deps
+  sources = term_sources
+}
diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs
index 9433199..4452409 100644
--- a/alacritty_terminal/src/ansi.rs
+++ b/alacritty_terminal/src/ansi.rs
@@ -724,7 +724,7 @@
     }
 
     #[inline]
-    fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, _c: char) {
+    fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool) {
         debug!(
             "[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}",
             params, intermediates, ignore
diff --git a/alacritty_terminal/src/clipboard.rs b/alacritty_terminal/src/clipboard.rs
index 6f6a41b..c8ae70b 100644
--- a/alacritty_terminal/src/clipboard.rs
+++ b/alacritty_terminal/src/clipboard.rs
@@ -12,30 +12,35 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#[cfg(not(any(target_os = "macos", target_os = "windows")))]
+#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "fuchsia")))]
 use std::ffi::c_void;
 
+#[cfg(not(target_os = "fuchsia"))]
 use log::{debug, warn};
 
+#[cfg(not(target_os = "fuchsia"))]
 use copypasta::nop_clipboard::NopClipboardContext;
-#[cfg(not(any(target_os = "macos", target_os = "windows")))]
+#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "fuchsia")))]
 use copypasta::wayland_clipboard;
-#[cfg(not(any(target_os = "macos", target_os = "windows")))]
+#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "fuchsia")))]
 use copypasta::x11_clipboard::{Primary as X11SelectionClipboard, X11ClipboardContext};
+#[cfg(not(target_os = "fuchsia"))]
 use copypasta::{ClipboardContext, ClipboardProvider};
 
 pub struct Clipboard {
+    #[cfg(not(target_os = "fuchsia"))]
     clipboard: Box<dyn ClipboardProvider>,
+    #[cfg(not(target_os = "fuchsia"))]
     selection: Option<Box<dyn ClipboardProvider>>,
 }
 
 impl Clipboard {
-    #[cfg(any(target_os = "macos", target_os = "windows"))]
+    #[cfg(any(target_os = "macos", target_os = "windows", target_os = "fuchsia"))]
     pub fn new() -> Self {
         Self::default()
     }
 
-    #[cfg(not(any(target_os = "macos", target_os = "windows")))]
+    #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "fuchsia")))]
     pub fn new(display: Option<*mut c_void>) -> Self {
         if let Some(display) = display {
             let (selection, clipboard) =
@@ -47,18 +52,30 @@
             clipboard: Box::new(ClipboardContext::new().unwrap()),
             selection: Some(Box::new(X11ClipboardContext::<X11SelectionClipboard>::new().unwrap())),
         }
-    }
+     }
 
     // Use for tests and ref-tests
+    #[cfg(not(target_os = "fuchsia"))]
     pub fn new_nop() -> Self {
         Self { clipboard: Box::new(NopClipboardContext::new().unwrap()), selection: None }
     }
+
+    #[cfg(target_os = "fuchsia")]
+    pub fn new_nop() -> Self {
+        Self {}
+    }
 }
 
 impl Default for Clipboard {
+    #[cfg(not(target_os = "fuchsia"))]
     fn default() -> Self {
         Self { clipboard: Box::new(ClipboardContext::new().unwrap()), selection: None }
     }
+
+    #[cfg(target_os = "fuchsia")]
+    fn default() -> Self {
+        Self {}
+    }
 }
 
 #[derive(Debug)]
@@ -67,19 +84,31 @@
     Selection,
 }
 
+//
+// Fuchsia does not currently support clipboards but the Clipboard struct
+// is required for the terminal. For now, we will leave the struct empty until
+// we support this functionality.
+//
+
 impl Clipboard {
+    #[cfg(not(target_os = "fuchsia"))]
     pub fn store(&mut self, ty: ClipboardType, text: impl Into<String>) {
         let clipboard = match (ty, &mut self.selection) {
             (ClipboardType::Selection, Some(provider)) => provider,
             (ClipboardType::Selection, None) => return,
             _ => &mut self.clipboard,
         };
-
         clipboard.set_contents(text.into()).unwrap_or_else(|err| {
             warn!("Unable to store text in clipboard: {}", err);
         });
     }
 
+    #[cfg(target_os = "fuchsia")]
+    pub fn store(&mut self, _ty: ClipboardType, _text: impl Into<String>) {
+        // Intentionally blank
+    }
+
+    #[cfg(not(target_os = "fuchsia"))]
     pub fn load(&mut self, ty: ClipboardType) -> String {
         let clipboard = match (ty, &mut self.selection) {
             (ClipboardType::Selection, Some(provider)) => provider,
@@ -94,4 +123,9 @@
             Ok(text) => text,
         }
     }
+
+    #[cfg(target_os = "fuchsia")]
+    pub fn load(&mut self, _ty: ClipboardType) -> String {
+        String::new()
+    }
 }
diff --git a/alacritty_terminal/src/config/colors.rs b/alacritty_terminal/src/config/colors.rs
index 35c0368..b5b5faf 100644
--- a/alacritty_terminal/src/config/colors.rs
+++ b/alacritty_terminal/src/config/colors.rs
@@ -46,7 +46,10 @@
 where
     D: Deserializer<'a>,
 {
+    #[cfg(not(target_os = "fuchsia"))]
     let value = serde_yaml::Value::deserialize(deserializer)?;
+    #[cfg(target_os = "fuchsia")]
+    let value = serde_json::Value::deserialize(deserializer)?;
     match u8::deserialize(value) {
         Ok(index) => {
             if index < 16 {
diff --git a/alacritty_terminal/src/config/font.rs b/alacritty_terminal/src/config/font.rs
index a8a76c1..dabb395 100644
--- a/alacritty_terminal/src/config/font.rs
+++ b/alacritty_terminal/src/config/font.rs
@@ -1,5 +1,6 @@
 use std::fmt;
 
+#[cfg(not(target_os = "fuchsia"))]
 use font::Size;
 use log::error;
 use serde::de::Visitor;
@@ -9,6 +10,29 @@
 use crate::config::DefaultTrueBool;
 use crate::config::{failure_default, Delta, LOG_TARGET_CONFIG};
 
+#[cfg(target_os = "fuchsia")]
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Size(i16);
+
+#[cfg(target_os = "fuchsia")]
+impl Size {
+    /// Create a new `Size` from a f32 size in points
+    pub fn new(size: f32) -> Size {
+        Size((size * Size::factor()) as i16)
+    }
+
+    /// Scale factor between font "Size" type and point size
+    #[inline]
+    pub fn factor() -> f32 {
+        2.0
+    }
+
+    /// Get the f32 size in points
+    pub fn as_f32_pts(self) -> f32 {
+        f32::from(self.0) / Size::factor()
+    }
+}
+
 /// Font config
 ///
 /// Defaults are provided at the level of this struct per platform, but not per
@@ -193,7 +217,10 @@
             }
         }
 
+        #[cfg(not(target_os = "fuchsia"))]
         let value = serde_yaml::Value::deserialize(deserializer)?;
+        #[cfg(target_os = "fuchsia")]
+        let value = serde_json::Value::deserialize(deserializer)?;
         let size = value
             .deserialize_any(NumVisitor::<D> { _marker: PhantomData })
             .map(|v| Size::new(v as _));
diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs
index fd049af..10adbf1 100644
--- a/alacritty_terminal/src/config/mod.rs
+++ b/alacritty_terminal/src/config/mod.rs
@@ -19,7 +19,11 @@
 
 use log::error;
 use serde::{Deserialize, Deserializer};
+
+#[cfg(not(target_os = "fuchsia"))]
 use serde_yaml::Value;
+#[cfg(target_os = "fuchsia")]
+use serde_json::Value;
 
 mod colors;
 mod debug;
@@ -41,7 +45,7 @@
 pub const LOG_TARGET_CONFIG: &str = "alacritty_config";
 const MAX_SCROLLBACK_LINES: u32 = 100_000;
 
-pub type MockConfig = Config<HashMap<String, serde_yaml::Value>>;
+pub type MockConfig = Config<HashMap<String, Value>>;
 
 /// Top-level config type
 #[derive(Debug, PartialEq, Default, Deserialize)]
diff --git a/alacritty_terminal/src/config/scrolling.rs b/alacritty_terminal/src/config/scrolling.rs
index 358abc3..6e1c84f 100644
--- a/alacritty_terminal/src/config/scrolling.rs
+++ b/alacritty_terminal/src/config/scrolling.rs
@@ -1,7 +1,6 @@
-use log::error;
 use serde::{Deserialize, Deserializer};
 
-use crate::config::{failure_default, LOG_TARGET_CONFIG, MAX_SCROLLBACK_LINES};
+use crate::config::{failure_default, MAX_SCROLLBACK_LINES};
 
 /// Struct for scrolling related settings
 #[serde(default)]
@@ -63,29 +62,16 @@
     where
         D: Deserializer<'de>,
     {
-        let value = serde_yaml::Value::deserialize(deserializer)?;
+        let value = serde_json::Value::deserialize(deserializer)?;
         match u32::deserialize(value) {
             Ok(lines) => {
                 if lines > MAX_SCROLLBACK_LINES {
-                    error!(
-                        target: LOG_TARGET_CONFIG,
-                        "Problem with config: scrollback size is {}, but expected a maximum of \
-                         {}; using {1} instead",
-                        lines,
-                        MAX_SCROLLBACK_LINES,
-                    );
                     Ok(ScrollingHistory(MAX_SCROLLBACK_LINES))
                 } else {
                     Ok(ScrollingHistory(lines))
                 }
-            },
-            Err(err) => {
-                error!(
-                    target: LOG_TARGET_CONFIG,
-                    "Problem with config: {}; using default value", err
-                );
-                Ok(Default::default())
-            },
+            }
+            Err(_err) => Ok(Default::default()),
         }
     }
 }
diff --git a/alacritty_terminal/src/event_loop.rs b/alacritty_terminal/src/event_loop.rs
index 2d55815..b0676e4 100644
--- a/alacritty_terminal/src/event_loop.rs
+++ b/alacritty_terminal/src/event_loop.rs
@@ -1,10 +1,13 @@
-//! The main event loop which performs I/O on the pseudoterminal
+//! The main event loop which performs I/O on the pseudoterminal.
+
 use std::borrow::Cow;
 use std::collections::VecDeque;
 use std::fs::File;
 use std::io::{self, ErrorKind, Read, Write};
 use std::marker::Send;
 use std::sync::Arc;
+use std::thread::JoinHandle;
+use std::time::Instant;
 
 use log::error;
 #[cfg(not(windows))]
@@ -13,32 +16,34 @@
 use mio_extras::channel::{self, Receiver, Sender};
 
 use crate::ansi;
-use crate::config::Config;
 use crate::event::{self, Event, EventListener};
 use crate::sync::FairMutex;
 use crate::term::{SizeInfo, Term};
+use crate::thread;
 use crate::tty;
-use crate::util::thread;
 
-/// Max bytes to read from the PTY
-const MAX_READ: usize = 0x10_000;
+/// Max bytes to read from the PTY before forced terminal synchronization.
+const READ_BUFFER_SIZE: usize = 0x10_0000;
 
-/// Messages that may be sent to the `EventLoop`
+/// Max bytes to read from the PTY while the terminal is locked.
+const MAX_LOCKED_READ: usize = u16::max_value() as usize;
+
+/// Messages that may be sent to the `EventLoop`.
 #[derive(Debug)]
 pub enum Msg {
-    /// Data that should be written to the pty
+    /// Data that should be written to the PTY.
     Input(Cow<'static, [u8]>),
 
-    /// Indicates that the `EventLoop` should shut down, as Alacritty is shutting down
+    /// Indicates that the `EventLoop` should shut down, as Alacritty is shutting down.
     Shutdown,
 
-    /// Instruction to resize the pty
+    /// Instruction to resize the PTY.
     Resize(SizeInfo),
 }
 
 /// The main event!.. loop.
 ///
-/// Handles all the pty I/O and runs the pty parser which updates terminal
+/// Handles all the PTY I/O and runs the PTY parser which updates terminal
 /// state.
 pub struct EventLoop<T: tty::EventedPty, U: EventListener> {
     poll: mio::Poll,
@@ -57,20 +62,10 @@
     written: usize,
 }
 
-/// All of the mutable state needed to run the event loop
-///
-/// Contains list of items to write, current write state, etc. Anything that
-/// would otherwise be mutated on the `EventLoop` goes here.
-pub struct State {
-    write_list: VecDeque<Cow<'static, [u8]>>,
-    writing: Option<Writing>,
-    parser: ansi::Processor,
-}
-
 pub struct Notifier(pub Sender<Msg>);
 
 impl event::Notify for Notifier {
-    fn notify<B>(&mut self, bytes: B)
+    fn notify<B>(&self, bytes: B)
     where
         B: Into<Cow<'static, [u8]>>,
     {
@@ -90,10 +85,15 @@
     }
 }
 
-impl Default for State {
-    fn default() -> State {
-        State { write_list: VecDeque::new(), parser: ansi::Processor::new(), writing: None }
-    }
+/// All of the mutable state needed to run the event loop.
+///
+/// Contains list of items to write, current write state, etc. Anything that
+/// would otherwise be mutated on the `EventLoop` goes here.
+#[derive(Default)]
+pub struct State {
+    write_list: VecDeque<Cow<'static, [u8]>>,
+    writing: Option<Writing>,
+    parser: ansi::Processor,
 }
 
 impl State {
@@ -152,12 +152,13 @@
     T: tty::EventedPty + event::OnResize + Send + 'static,
     U: EventListener + Send + 'static,
 {
-    /// Create a new event loop
-    pub fn new<V>(
+    /// Create a new event loop.
+    pub fn new(
         terminal: Arc<FairMutex<Term<U>>>,
         event_proxy: U,
         pty: T,
-        config: &Config<V>,
+        hold: bool,
+        ref_test: bool,
     ) -> EventLoop<T, U> {
         let (tx, rx) = channel::channel();
         EventLoop {
@@ -167,8 +168,8 @@
             rx,
             terminal,
             event_proxy,
-            hold: config.hold,
-            ref_test: config.debug.ref_test,
+            hold,
+            ref_test,
         }
     }
 
@@ -176,9 +177,9 @@
         self.tx.clone()
     }
 
-    // Drain the channel
-    //
-    // Returns `false` when a shutdown message was received.
+    /// Drain the channel.
+    ///
+    /// Returns `false` when a shutdown message was received.
     fn drain_recv_channel(&mut self, state: &mut State) -> bool {
         while let Ok(msg) = self.rx.try_recv() {
             match msg {
@@ -191,7 +192,7 @@
         true
     }
 
-    // Returns a `bool` indicating whether or not the event loop should continue running
+    /// Returns a `bool` indicating whether or not the event loop should continue running.
     #[inline]
     fn channel_event(&mut self, token: mio::Token, state: &mut State) -> bool {
         if !self.drain_recv_channel(state) {
@@ -215,52 +216,62 @@
     where
         X: Write,
     {
+        let mut unprocessed = 0;
         let mut processed = 0;
+
+        // Reserve the next terminal lock for PTY reading.
+        let _terminal_lease = Some(self.terminal.lease());
         let mut terminal = None;
 
         loop {
-            match self.pty.reader().read(&mut buf[..]) {
-                Ok(0) => break,
-                Ok(got) => {
-                    // Record bytes read; used to limit time spent in pty_read.
-                    processed += got;
-
-                    // Send a copy of bytes read to a subscriber. Used for
-                    // example with ref test recording.
-                    writer = writer.map(|w| {
-                        w.write_all(&buf[..got]).unwrap();
-                        w
-                    });
-
-                    // Get reference to terminal. Lock is acquired on initial
-                    // iteration and held until there's no bytes left to parse
-                    // or we've reached MAX_READ.
-                    if terminal.is_none() {
-                        terminal = Some(self.terminal.lock());
-                    }
-                    let terminal = terminal.as_mut().unwrap();
-
-                    // Run the parser
-                    for byte in &buf[..got] {
-                        state.parser.advance(&mut **terminal, *byte, &mut self.pty.writer());
-                    }
-
-                    // Exit if we've processed enough bytes
-                    if processed > MAX_READ {
-                        break;
-                    }
-                },
+            // Read from the PTY.
+            match self.pty.reader().read(&mut buf[unprocessed..]) {
+                // This is received on Windows/macOS when no more data is readable from the PTY.
+                Ok(0) if unprocessed == 0 => break,
+                Ok(got) => unprocessed += got,
                 Err(err) => match err.kind() {
                     ErrorKind::Interrupted | ErrorKind::WouldBlock => {
-                        break;
+                        // Go back to mio if we're caught up on parsing and the PTY would block.
+                        if unprocessed == 0 {
+                            break;
+                        }
                     },
                     _ => return Err(err),
                 },
             }
+
+            // Attempt to lock the terminal.
+            let terminal = match &mut terminal {
+                Some(terminal) => terminal,
+                None => terminal.insert(match self.terminal.try_lock_unfair() {
+                    // Force block if we are at the buffer size limit.
+                    None if unprocessed >= READ_BUFFER_SIZE => self.terminal.lock_unfair(),
+                    None => continue,
+                    Some(terminal) => terminal,
+                }),
+            };
+
+            // Write a copy of the bytes to the ref test file.
+            if let Some(writer) = &mut writer {
+                writer.write_all(&buf[..unprocessed]).unwrap();
+            }
+
+            // Parse the incoming bytes.
+            for byte in &buf[..unprocessed] {
+                state.parser.advance(&mut **terminal, *byte);
+            }
+
+            processed += unprocessed;
+            unprocessed = 0;
+
+            // Assure we're not blocking the terminal too long unnecessarily.
+            if processed >= MAX_LOCKED_READ {
+                break;
+            }
         }
 
-        if processed > 0 {
-            // Queue terminal redraw
+        // Queue terminal redraw unless all processed bytes were synchronized.
+        if state.parser.sync_bytes_count() < processed && processed > 0 {
             self.event_proxy.send_event(Event::Wakeup);
         }
 
@@ -299,10 +310,10 @@
         Ok(())
     }
 
-    pub fn spawn(mut self) -> thread::JoinHandle<(Self, State)> {
-        thread::spawn_named("pty reader", move || {
+    pub fn spawn(mut self) -> JoinHandle<(Self, State)> {
+        thread::spawn_named("PTY reader", move || {
             let mut state = State::default();
-            let mut buf = [0u8; MAX_READ];
+            let mut buf = [0u8; READ_BUFFER_SIZE];
 
             let mut tokens = (0..).map(Into::into);
 
@@ -311,7 +322,7 @@
             let channel_token = tokens.next().unwrap();
             self.poll.register(&self.rx, channel_token, Ready::readable(), poll_opts).unwrap();
 
-            // Register TTY through EventedRW interface
+            // Register TTY through EventedRW interface.
             self.pty.register(&self.poll, &mut tokens, Ready::readable(), poll_opts).unwrap();
 
             let mut events = Events::with_capacity(1024);
@@ -323,13 +334,24 @@
             };
 
             'event_loop: loop {
-                if let Err(err) = self.poll.poll(&mut events, None) {
+                // Wakeup the event loop when a synchronized update timeout was reached.
+                let sync_timeout = state.parser.sync_timeout();
+                let timeout = sync_timeout.map(|st| st.saturating_duration_since(Instant::now()));
+
+                if let Err(err) = self.poll.poll(&mut events, timeout) {
                     match err.kind() {
                         ErrorKind::Interrupted => continue,
                         _ => panic!("EventLoop polling error: {:?}", err),
                     }
                 }
 
+                // Handle synchronized update timeout.
+                if events.is_empty() {
+                    state.parser.stop_sync(&mut *self.terminal.lock());
+                    self.event_proxy.send_event(Event::Wakeup);
+                    continue;
+                }
+
                 for event in events.iter() {
                     match event.token() {
                         token if token == channel_token => {
@@ -340,9 +362,14 @@
 
                         token if token == self.pty.child_event_token() => {
                             if let Some(tty::ChildEvent::Exited) = self.pty.next_child_event() {
-                                if !self.hold {
+                                if self.hold {
+                                    // With hold enabled, make sure the PTY is drained.
+                                    let _ = self.pty_read(&mut state, &mut buf, pipe.as_mut());
+                                } else {
+                                    // Without hold, shutdown the terminal.
                                     self.terminal.lock().exit();
                                 }
+
                                 self.event_proxy.send_event(Event::Wakeup);
                                 break 'event_loop;
                             }
@@ -353,35 +380,32 @@
                                 || token == self.pty.write_token() =>
                         {
                             #[cfg(unix)]
-                            {
-                                if UnixReady::from(event.readiness()).is_hup() {
-                                    // don't try to do I/O on a dead PTY
-                                    continue;
-                                }
+                            if UnixReady::from(event.readiness()).is_hup() {
+                                // Don't try to do I/O on a dead PTY.
+                                continue;
                             }
 
                             if event.readiness().is_readable() {
-                                if let Err(e) = self.pty_read(&mut state, &mut buf, pipe.as_mut()) {
+                                if let Err(err) = self.pty_read(&mut state, &mut buf, pipe.as_mut())
+                                {
+                                    // On Linux, a `read` on the master side of a PTY can fail
+                                    // with `EIO` if the client side hangs up.  In that case,
+                                    // just loop back round for the inevitable `Exited` event.
+                                    // This sucks, but checking the process is either racy or
+                                    // blocking.
                                     #[cfg(target_os = "linux")]
-                                    {
-                                        // On Linux, a `read` on the master side of a PTY can fail
-                                        // with `EIO` if the client side hangs up.  In that case,
-                                        // just loop back round for the inevitable `Exited` event.
-                                        // This sucks, but checking the process is either racy or
-                                        // blocking.
-                                        if e.kind() == ErrorKind::Other {
-                                            continue;
-                                        }
+                                    if err.kind() == ErrorKind::Other {
+                                        continue;
                                     }
 
-                                    error!("Error reading from PTY in event loop: {}", e);
+                                    error!("Error reading from PTY in event loop: {}", err);
                                     break 'event_loop;
                                 }
                             }
 
                             if event.readiness().is_writable() {
-                                if let Err(e) = self.pty_write(&mut state) {
-                                    error!("Error writing to PTY in event loop: {}", e);
+                                if let Err(err) = self.pty_write(&mut state) {
+                                    error!("Error writing to PTY in event loop: {}", err);
                                     break 'event_loop;
                                 }
                             }
@@ -390,16 +414,16 @@
                     }
                 }
 
-                // Register write interest if necessary
+                // Register write interest if necessary.
                 let mut interest = Ready::readable();
                 if state.needs_write() {
                     interest.insert(Ready::writable());
                 }
-                // Reregister with new interest
+                // Reregister with new interest.
                 self.pty.reregister(&self.poll, interest, poll_opts).unwrap();
             }
 
-            // The evented instances are not dropped here so deregister them explicitly
+            // The evented instances are not dropped here so deregister them explicitly.
             let _ = self.poll.deregister(&self.rx);
             let _ = self.pty.deregister(&self.poll);
 
@@ -407,3 +431,32 @@
         })
     }
 }
+
+trait OptionInsert {
+    type T;
+    fn insert(&mut self, value: Self::T) -> &mut Self::T;
+}
+
+// TODO: Remove when MSRV is >= 1.53.0.
+//
+/// Trait implementation to support Rust version < 1.53.0.
+///
+/// This is taken [from STD], further license information can be found in the [rust-lang/rust
+/// repository].
+///
+///
+/// [from STD]: https://github.com/rust-lang/rust/blob/6e0b554619a3bb7e75b3334e97f191af20ef5d76/library/core/src/option.rs#L829-L858
+/// [rust-lang/rust repository]: https://github.com/rust-lang/rust/blob/master/LICENSE-MIT
+impl<T> OptionInsert for Option<T> {
+    type T = T;
+
+    fn insert(&mut self, value: T) -> &mut T {
+        *self = Some(value);
+
+        match self {
+            Some(v) => v,
+            // SAFETY: the code above just filled the option
+            None => unsafe { std::hint::unreachable_unchecked() },
+        }
+    }
+}
diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs
index d1ccd32..2ac7d9d 100644
--- a/alacritty_terminal/src/grid/mod.rs
+++ b/alacritty_terminal/src/grid/mod.rs
@@ -182,13 +182,13 @@
                     max((self.display_offset as isize) + count, 0isize) as usize,
                     self.history_size(),
                 );
-            },
+            }
             Scroll::PageUp => {
                 self.display_offset = min(self.display_offset + self.lines.0, self.history_size());
-            },
+            }
             Scroll::PageDown => {
                 self.display_offset -= min(self.display_offset, self.lines.0);
-            },
+            }
             Scroll::Top => self.display_offset = self.history_size(),
             Scroll::Bottom => self.display_offset = 0,
         }
@@ -275,7 +275,7 @@
                 _ => {
                     reversed.push(row);
                     continue;
-                },
+                }
             };
 
             // Remove wrap flag before appending additional cells
@@ -367,7 +367,7 @@
                     _ => {
                         new_raw.push(row);
                         break;
-                    },
+                    }
                 };
 
                 // Insert spacer if a wide char would be wrapped into the last column
@@ -708,11 +708,11 @@
                 self.cur.line -= 1;
                 self.cur.col = Column(0);
                 Some(&self.grid[self.cur.line][self.cur.col])
-            },
+            }
             _ => {
                 self.cur.col += Column(1);
                 Some(&self.grid[self.cur.line][self.cur.col])
-            },
+            }
         }
     }
 }
@@ -727,11 +727,11 @@
                 self.cur.line += 1;
                 self.cur.col = num_cols - Column(1);
                 Some(&self.grid[self.cur.line][self.cur.col])
-            },
+            }
             _ => {
                 self.cur.col -= Column(1);
                 Some(&self.grid[self.cur.line][self.cur.col])
-            },
+            }
         }
     }
 }
diff --git a/alacritty_terminal/src/index.rs b/alacritty_terminal/src/index.rs
index fb21baa..4a97aac 100644
--- a/alacritty_terminal/src/index.rs
+++ b/alacritty_terminal/src/index.rs
@@ -31,7 +31,7 @@
 }
 
 /// Index in the grid using row, column notation
-#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize, PartialOrd)]
+#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd)]
 pub struct Point<L = Line> {
     pub line: L,
     pub col: Column,
diff --git a/alacritty_terminal/src/lib.rs b/alacritty_terminal/src/lib.rs
index 039f2b8..828b930 100644
--- a/alacritty_terminal/src/lib.rs
+++ b/alacritty_terminal/src/lib.rs
@@ -1,3 +1,4 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
 // Copyright 2016 Joe Wilm, The Alacritty Project Contributors
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -11,32 +12,16 @@
 // 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.
-//
-//! Alacritty - The GPU Enhanced Terminal
-#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)]
-#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
-#![cfg_attr(all(test, feature = "bench"), feature(test))]
-
-#[cfg(target_os = "macos")]
-#[macro_use]
-extern crate objc;
 
 pub mod ansi;
 pub mod clipboard;
 pub mod config;
 pub mod event;
-pub mod event_loop;
 pub mod grid;
 pub mod index;
-pub mod locale;
 pub mod message_bar;
-pub mod meter;
-pub mod panic;
 pub mod selection;
-pub mod sync;
 pub mod term;
-pub mod tty;
-pub mod util;
 
 pub use crate::grid::Grid;
 pub use crate::term::Term;
diff --git a/alacritty_terminal/src/message_bar.rs b/alacritty_terminal/src/message_bar.rs
index 1382684..5082d64 100644
--- a/alacritty_terminal/src/message_bar.rs
+++ b/alacritty_terminal/src/message_bar.rs
@@ -294,10 +294,10 @@
 
         let lines = message_buffer.message().unwrap().text(&size);
 
-        assert_eq!(lines, vec![
-            String::from("hahahahahahahahaha [X]"),
-            String::from("[MESSAGE TRUNCATED]   ")
-        ]);
+        assert_eq!(
+            lines,
+            vec![String::from("hahahahahahahahaha [X]"), String::from("[MESSAGE TRUNCATED]   ")]
+        );
     }
 
     #[test]
@@ -415,11 +415,10 @@
 
         let lines = message_buffer.message().unwrap().text(&size);
 
-        assert_eq!(lines, vec![
-            String::from("a [X]"),
-            String::from("bc   "),
-            String::from("defg ")
-        ]);
+        assert_eq!(
+            lines,
+            vec![String::from("a [X]"), String::from("bc   "), String::from("defg ")]
+        );
     }
 
     #[test]
diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs
index 92335e1..9b8494c 100644
--- a/alacritty_terminal/src/selection.rs
+++ b/alacritty_terminal/src/selection.rs
@@ -215,7 +215,7 @@
                         && end.side == Side::Left
                         && (start.point.line == end.point.line)
                         && start.point.col + 1 == end.point.col)
-            },
+            }
             SelectionType::Block => {
                 let (start, end) = (self.region.start, self.region.end);
 
@@ -229,7 +229,7 @@
                     || (end.point.col + 1 == start.point.col
                         && start.side == Side::Left
                         && end.side == Side::Right)
-            },
+            }
             SelectionType::Semantic | SelectionType::Lines => false,
         }
     }
@@ -483,11 +483,10 @@
         let mut selection = Selection::simple(location, Side::Left);
         selection.update(location, Side::Right);
 
-        assert_eq!(selection.to_range(&term(1, 1)).unwrap(), SelectionRange {
-            start: location,
-            end: location,
-            is_block: false
-        });
+        assert_eq!(
+            selection.to_range(&term(1, 1)).unwrap(),
+            SelectionRange { start: location, end: location, is_block: false }
+        );
     }
 
     /// Test case of single cell selection.
@@ -501,11 +500,10 @@
         let mut selection = Selection::simple(location, Side::Right);
         selection.update(location, Side::Left);
 
-        assert_eq!(selection.to_range(&term(1, 1)).unwrap(), SelectionRange {
-            start: location,
-            end: location,
-            is_block: false
-        });
+        assert_eq!(
+            selection.to_range(&term(1, 1)).unwrap(),
+            SelectionRange { start: location, end: location, is_block: false }
+        );
     }
 
     /// Test adjacent cell selection from left to right.
@@ -547,11 +545,14 @@
         let mut selection = Selection::simple(Point::new(1, Column(1)), Side::Right);
         selection.update(Point::new(0, Column(1)), Side::Right);
 
-        assert_eq!(selection.to_range(&term(5, 2)).unwrap(), SelectionRange {
-            start: Point::new(1, Column(2)),
-            end: Point::new(0, Column(1)),
-            is_block: false,
-        });
+        assert_eq!(
+            selection.to_range(&term(5, 2)).unwrap(),
+            SelectionRange {
+                start: Point::new(1, Column(2)),
+                end: Point::new(0, Column(1)),
+                is_block: false,
+            }
+        );
     }
 
     /// Test selection across adjacent lines.
@@ -570,11 +571,14 @@
         selection.update(Point::new(1, Column(1)), Side::Right);
         selection.update(Point::new(1, Column(0)), Side::Right);
 
-        assert_eq!(selection.to_range(&term(5, 2)).unwrap(), SelectionRange {
-            start: Point::new(1, Column(1)),
-            end: Point::new(0, Column(1)),
-            is_block: false,
-        });
+        assert_eq!(
+            selection.to_range(&term(5, 2)).unwrap(),
+            SelectionRange {
+                start: Point::new(1, Column(1)),
+                end: Point::new(0, Column(1)),
+                is_block: false,
+            }
+        );
     }
 
     #[test]
@@ -585,11 +589,14 @@
         selection.update(Point::new(5, Column(1)), Side::Right);
         selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap();
 
-        assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
-            start: Point::new(9, Column(0)),
-            end: Point::new(7, Column(4)),
-            is_block: false,
-        });
+        assert_eq!(
+            selection.to_range(&term(num_cols, num_lines)).unwrap(),
+            SelectionRange {
+                start: Point::new(9, Column(0)),
+                end: Point::new(7, Column(4)),
+                is_block: false,
+            }
+        );
     }
 
     #[test]
@@ -600,11 +607,14 @@
         selection.update(Point::new(5, Column(1)), Side::Right);
         selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap();
 
-        assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
-            start: Point::new(9, Column(0)),
-            end: Point::new(7, Column(3)),
-            is_block: false,
-        });
+        assert_eq!(
+            selection.to_range(&term(num_cols, num_lines)).unwrap(),
+            SelectionRange {
+                start: Point::new(9, Column(0)),
+                end: Point::new(7, Column(3)),
+                is_block: false,
+            }
+        );
     }
 
     #[test]
@@ -615,11 +625,14 @@
         selection.update(Point::new(5, Column(1)), Side::Right);
         selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap();
 
-        assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
-            start: Point::new(9, Column(0)),
-            end: Point::new(7, Column(3)),
-            is_block: false,
-        });
+        assert_eq!(
+            selection.to_range(&term(num_cols, num_lines)).unwrap(),
+            SelectionRange {
+                start: Point::new(9, Column(0)),
+                end: Point::new(7, Column(3)),
+                is_block: false,
+            }
+        );
     }
 
     #[test]
@@ -630,11 +643,14 @@
         selection.update(Point::new(5, Column(1)), Side::Right);
         selection = selection.rotate(num_lines, num_cols, &(Line(0)..Line(num_lines)), 7).unwrap();
 
-        assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
-            start: Point::new(9, Column(2)),
-            end: Point::new(7, Column(3)),
-            is_block: true
-        });
+        assert_eq!(
+            selection.to_range(&term(num_cols, num_lines)).unwrap(),
+            SelectionRange {
+                start: Point::new(9, Column(2)),
+                end: Point::new(7, Column(3)),
+                is_block: true
+            }
+        );
     }
 
     #[test]
@@ -650,11 +666,14 @@
         let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Left);
         selection.update(Point::new(0, Column(8)), Side::Right);
 
-        assert_eq!(selection.to_range(&term).unwrap(), SelectionRange {
-            start: Point::new(0, Column(0)),
-            end: Point::new(0, Column(9)),
-            is_block: false,
-        });
+        assert_eq!(
+            selection.to_range(&term).unwrap(),
+            SelectionRange {
+                start: Point::new(0, Column(0)),
+                end: Point::new(0, Column(9)),
+                is_block: false,
+            }
+        );
     }
 
     #[test]
@@ -692,11 +711,14 @@
         selection =
             selection.rotate(num_lines, num_cols, &(Line(1)..Line(num_lines - 1)), 4).unwrap();
 
-        assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
-            start: Point::new(8, Column(0)),
-            end: Point::new(6, Column(3)),
-            is_block: false,
-        });
+        assert_eq!(
+            selection.to_range(&term(num_cols, num_lines)).unwrap(),
+            SelectionRange {
+                start: Point::new(8, Column(0)),
+                end: Point::new(6, Column(3)),
+                is_block: false,
+            }
+        );
     }
 
     #[test]
@@ -708,11 +730,14 @@
         selection =
             selection.rotate(num_lines, num_cols, &(Line(1)..Line(num_lines - 1)), -5).unwrap();
 
-        assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
-            start: Point::new(3, Column(1)),
-            end: Point::new(1, Column(num_cols - 1)),
-            is_block: false,
-        });
+        assert_eq!(
+            selection.to_range(&term(num_cols, num_lines)).unwrap(),
+            SelectionRange {
+                start: Point::new(3, Column(1)),
+                end: Point::new(1, Column(num_cols - 1)),
+                is_block: false,
+            }
+        );
     }
 
     #[test]
@@ -724,10 +749,13 @@
         selection =
             selection.rotate(num_lines, num_cols, &(Line(1)..Line(num_lines - 1)), 4).unwrap();
 
-        assert_eq!(selection.to_range(&term(num_cols, num_lines)).unwrap(), SelectionRange {
-            start: Point::new(8, Column(2)),
-            end: Point::new(6, Column(3)),
-            is_block: true,
-        });
+        assert_eq!(
+            selection.to_range(&term(num_cols, num_lines)).unwrap(),
+            SelectionRange {
+                start: Point::new(8, Column(2)),
+                end: Point::new(6, Column(3)),
+                is_block: true,
+            }
+        );
     }
 }
diff --git a/alacritty_terminal/src/sync.rs b/alacritty_terminal/src/sync.rs
index 0fcd086..848bab6 100644
--- a/alacritty_terminal/src/sync.rs
+++ b/alacritty_terminal/src/sync.rs
@@ -1,44 +1,49 @@
-// 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.
-
-//! Synchronization types
+//! Synchronization types.
 //!
-//! Most importantly, a fair mutex is included
+//! Most importantly, a fair mutex is included.
+
 use parking_lot::{Mutex, MutexGuard};
 
-/// A fair mutex
+/// A fair mutex.
 ///
 /// Uses an extra lock to ensure that if one thread is waiting that it will get
 /// the lock before a single thread can re-lock it.
 pub struct FairMutex<T> {
-    /// Data
+    /// Data.
     data: Mutex<T>,
-    /// Next-to-access
+    /// Next-to-access.
     next: Mutex<()>,
 }
 
 impl<T> FairMutex<T> {
-    /// Create a new fair mutex
+    /// Create a new fair mutex.
     pub fn new(data: T) -> FairMutex<T> {
         FairMutex { data: Mutex::new(data), next: Mutex::new(()) }
     }
 
-    /// Lock the mutex
+    /// Acquire a lease to reserve the mutex lock.
+    ///
+    /// This will prevent others from acquiring a terminal lock, but block if anyone else is
+    /// already holding a lease.
+    pub fn lease(&self) -> MutexGuard<'_, ()> {
+        self.next.lock()
+    }
+
+    /// Lock the mutex.
     pub fn lock(&self) -> MutexGuard<'_, T> {
         // Must bind to a temporary or the lock will be freed before going
-        // into data.lock()
+        // into data.lock().
         let _next = self.next.lock();
         self.data.lock()
     }
+
+    /// Unfairly lock the mutex.
+    pub fn lock_unfair(&self) -> MutexGuard<'_, T> {
+        self.data.lock()
+    }
+
+    /// Unfairly try to lock the mutex.
+    pub fn try_lock_unfair(&self) -> Option<MutexGuard<'_, T>> {
+        self.data.try_lock()
+    }
 }
diff --git a/alacritty_terminal/src/term/color.rs b/alacritty_terminal/src/term/color.rs
index e9f0a26..ad5234f 100644
--- a/alacritty_terminal/src/term/color.rs
+++ b/alacritty_terminal/src/term/color.rs
@@ -74,7 +74,7 @@
         }
 
         // Return an error if the syntax is incorrect
-        let value = serde_yaml::Value::deserialize(deserializer)?;
+        let value = serde_json::Value::deserialize(deserializer)?;
 
         // Attempt to deserialize from struct form
         if let Ok(RgbDerivedDeser { r, g, b }) = RgbDerivedDeser::deserialize(value.clone()) {
@@ -87,7 +87,7 @@
             Err(err) => {
                 error!("Problem with config: {}; using color #000000", err);
                 Ok(Rgb::default())
-            },
+            }
         }
     }
 }
@@ -120,7 +120,7 @@
                 if chars.next() != Some('x') {
                     return Err(());
                 }
-            },
+            }
             Some('#') => (),
             _ => return Err(()),
         }
@@ -199,7 +199,7 @@
                 self[ansi::NamedColor::DimMagenta] = dim.magenta;
                 self[ansi::NamedColor::DimCyan] = dim.cyan;
                 self[ansi::NamedColor::DimWhite] = dim.white;
-            },
+            }
             None => {
                 trace!("Deriving dim colors from normal colors");
                 self[ansi::NamedColor::DimBlack] = colors.normal().black * 0.66;
@@ -210,7 +210,7 @@
                 self[ansi::NamedColor::DimMagenta] = colors.normal().magenta * 0.66;
                 self[ansi::NamedColor::DimCyan] = colors.normal().cyan * 0.66;
                 self[ansi::NamedColor::DimWhite] = colors.normal().white * 0.66;
-            },
+            }
         }
     }
 
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs
index eeacbf7..02d4dd3 100644
--- a/alacritty_terminal/src/term/mod.rs
+++ b/alacritty_terminal/src/term/mod.rs
@@ -35,8 +35,6 @@
 use crate::selection::{Selection, SelectionRange};
 use crate::term::cell::{Cell, Flags, LineLength};
 use crate::term::color::Rgb;
-#[cfg(windows)]
-use crate::tty;
 
 pub mod cell;
 pub mod color;
@@ -241,11 +239,11 @@
                 (Some(start), None) => {
                     let end = Point::new(num_lines.0 - 1, num_cols - 1);
                     (start, end)
-                },
+                }
                 (None, Some(end)) => {
                     let start = Point::new(0, Column(0));
                     (start, end)
-                },
+                }
                 (None, None) => return None,
             };
 
@@ -358,7 +356,7 @@
                             && config.colors.primary.bright_foreground.is_none() =>
                     {
                         colors[NamedColor::DimForeground]
-                    },
+                    }
                     // Draw bold text in bright colors *and* contains bold flag.
                     (true, Flags::BOLD) => colors[ansi.to_bright()],
                     // Cell is marked as dim and not bold
@@ -366,7 +364,7 @@
                     // None of the above, keep original color.
                     _ => colors[ansi],
                 }
-            },
+            }
             Color::Indexed(idx) => {
                 let idx = match (
                     config.draw_bold_text_with_bright_colors(),
@@ -380,7 +378,7 @@
                 };
 
                 colors[idx]
-            },
+            }
         }
     }
 
@@ -637,7 +635,7 @@
                     self.start_time = None;
                 }
                 false
-            },
+            }
             None => true,
         }
     }
@@ -682,12 +680,12 @@
                 let inverse_intensity = match self.animation {
                     VisualBellAnimation::Ease | VisualBellAnimation::EaseOut => {
                         cubic_bezier(0.25, 0.1, 0.25, 1.0, time)
-                    },
+                    }
                     VisualBellAnimation::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),
                     VisualBellAnimation::EaseOutCubic => {
                         cubic_bezier(0.215, 0.61, 0.355, 1.0, time)
-                    },
+                    }
                     VisualBellAnimation::EaseOutQuart => cubic_bezier(0.165, 0.84, 0.44, 1.0, time),
                     VisualBellAnimation::EaseOutQuint => cubic_bezier(0.23, 1.0, 0.32, 1.0, time),
                     VisualBellAnimation::EaseOutExpo => cubic_bezier(0.19, 1.0, 0.22, 1.0, time),
@@ -698,7 +696,7 @@
                 // Since we want the `intensity` of the VisualBell to decay over
                 // `time`, we subtract the `inverse_intensity` from 1.0.
                 1.0 - inverse_intensity
-            },
+            }
         }
     }
 
@@ -1455,12 +1453,12 @@
         match arg {
             5 => {
                 let _ = writer.write_all(b"\x1b[0n");
-            },
+            }
             6 => {
                 let pos = self.cursor.point;
                 let response = format!("\x1b[{};{}R", pos.line + 1, pos.col + 1);
                 let _ = writer.write_all(response.as_bytes());
-            },
+            }
             _ => debug!("unknown device status query: {}", arg),
         };
     }
@@ -1713,19 +1711,19 @@
                 for cell in &mut row[col..] {
                     cell.reset(&self.cursor.template);
                 }
-            },
+            }
             ansi::LineClearMode::Left => {
                 let row = &mut self.grid[self.cursor.point.line];
                 for cell in &mut row[..=col] {
                     cell.reset(&self.cursor.template);
                 }
-            },
+            }
             ansi::LineClearMode::All => {
                 let row = &mut self.grid[self.cursor.point.line];
                 for cell in &mut row[..] {
                     cell.reset(&self.cursor.template);
                 }
-            },
+            }
         }
     }
 
@@ -1810,7 +1808,7 @@
                 for cell in &mut self.grid[self.cursor.point.line][..end] {
                     cell.reset(&template);
                 }
-            },
+            }
             ansi::ClearMode::Below => {
                 for cell in &mut self.grid[self.cursor.point.line][self.cursor.point.col..] {
                     cell.reset(&template);
@@ -1820,7 +1818,7 @@
                         .region_mut((self.cursor.point.line + 1)..)
                         .each(|cell| cell.reset(&template));
                 }
-            },
+            }
             ansi::ClearMode::All => {
                 if self.mode.contains(TermMode::ALT_SCREEN) {
                     self.grid.region_mut(..).each(|c| c.reset(&template));
@@ -1828,7 +1826,7 @@
                     let template = Cell { bg: template.bg, ..Cell::default() };
                     self.grid.clear_viewport(&template);
                 }
-            },
+            }
             ansi::ClearMode::Saved => self.grid.clear_history(),
         }
     }
@@ -1840,10 +1838,10 @@
             ansi::TabulationClearMode::Current => {
                 let column = self.cursor.point.col;
                 self.tabs[column] = false;
-            },
+            }
             ansi::TabulationClearMode::All => {
                 self.tabs.clear_all();
-            },
+            }
         }
     }
 
@@ -1891,7 +1889,7 @@
                 self.cursor.template.fg = Color::Named(NamedColor::Foreground);
                 self.cursor.template.bg = Color::Named(NamedColor::Background);
                 self.cursor.template.flags = Flags::empty();
-            },
+            }
             Attr::Reverse => self.cursor.template.flags.insert(Flags::INVERSE),
             Attr::CancelReverse => self.cursor.template.flags.remove(Flags::INVERSE),
             Attr::Bold => self.cursor.template.flags.insert(Flags::BOLD),
@@ -1908,7 +1906,7 @@
             Attr::CancelStrike => self.cursor.template.flags.remove(Flags::STRIKEOUT),
             _ => {
                 debug!("Term got unhandled attr: {:?}", attr);
-            },
+            }
         }
     }
 
@@ -1923,7 +1921,7 @@
                     self.swap_alt();
                     self.save_cursor_position();
                 }
-            },
+            }
             ansi::Mode::ShowCursor => self.mode.insert(TermMode::SHOW_CURSOR),
             ansi::Mode::CursorKeys => self.mode.insert(TermMode::APP_CURSOR),
             // Mouse protocols are mutually exlusive
@@ -1931,28 +1929,28 @@
                 self.mode.remove(TermMode::MOUSE_MODE);
                 self.mode.insert(TermMode::MOUSE_REPORT_CLICK);
                 self.event_proxy.send_event(Event::MouseCursorDirty);
-            },
+            }
             ansi::Mode::ReportCellMouseMotion => {
                 self.mode.remove(TermMode::MOUSE_MODE);
                 self.mode.insert(TermMode::MOUSE_DRAG);
                 self.event_proxy.send_event(Event::MouseCursorDirty);
-            },
+            }
             ansi::Mode::ReportAllMouseMotion => {
                 self.mode.remove(TermMode::MOUSE_MODE);
                 self.mode.insert(TermMode::MOUSE_MOTION);
                 self.event_proxy.send_event(Event::MouseCursorDirty);
-            },
+            }
             ansi::Mode::ReportFocusInOut => self.mode.insert(TermMode::FOCUS_IN_OUT),
             ansi::Mode::BracketedPaste => self.mode.insert(TermMode::BRACKETED_PASTE),
             // Mouse encodings are mutually exlusive
             ansi::Mode::SgrMouse => {
                 self.mode.remove(TermMode::UTF8_MOUSE);
                 self.mode.insert(TermMode::SGR_MOUSE);
-            },
+            }
             ansi::Mode::Utf8Mouse => {
                 self.mode.remove(TermMode::SGR_MOUSE);
                 self.mode.insert(TermMode::UTF8_MOUSE);
-            },
+            }
             ansi::Mode::AlternateScroll => self.mode.insert(TermMode::ALTERNATE_SCROLL),
             ansi::Mode::LineWrap => self.mode.insert(TermMode::LINE_WRAP),
             ansi::Mode::LineFeedNewLine => self.mode.insert(TermMode::LINE_FEED_NEW_LINE),
@@ -1961,7 +1959,7 @@
             ansi::Mode::Insert => self.mode.insert(TermMode::INSERT), // heh
             ansi::Mode::BlinkingCursor => {
                 trace!("... unimplemented mode");
-            },
+            }
         }
     }
 
@@ -1976,21 +1974,21 @@
                     self.swap_alt();
                     self.restore_cursor_position();
                 }
-            },
+            }
             ansi::Mode::ShowCursor => self.mode.remove(TermMode::SHOW_CURSOR),
             ansi::Mode::CursorKeys => self.mode.remove(TermMode::APP_CURSOR),
             ansi::Mode::ReportMouseClicks => {
                 self.mode.remove(TermMode::MOUSE_REPORT_CLICK);
                 self.event_proxy.send_event(Event::MouseCursorDirty);
-            },
+            }
             ansi::Mode::ReportCellMouseMotion => {
                 self.mode.remove(TermMode::MOUSE_DRAG);
                 self.event_proxy.send_event(Event::MouseCursorDirty);
-            },
+            }
             ansi::Mode::ReportAllMouseMotion => {
                 self.mode.remove(TermMode::MOUSE_MOTION);
                 self.event_proxy.send_event(Event::MouseCursorDirty);
-            },
+            }
             ansi::Mode::ReportFocusInOut => self.mode.remove(TermMode::FOCUS_IN_OUT),
             ansi::Mode::BracketedPaste => self.mode.remove(TermMode::BRACKETED_PASTE),
             ansi::Mode::SgrMouse => self.mode.remove(TermMode::SGR_MOUSE),
@@ -2003,7 +2001,7 @@
             ansi::Mode::Insert => self.mode.remove(TermMode::INSERT),
             ansi::Mode::BlinkingCursor => {
                 trace!("... unimplemented mode");
-            },
+            }
         }
     }
 
diff --git a/alacritty_terminal/src/tty/mod.rs b/alacritty_terminal/src/tty/mod.rs
index 425ec4b..a1c8c0c 100644
--- a/alacritty_terminal/src/tty/mod.rs
+++ b/alacritty_terminal/src/tty/mod.rs
@@ -1,22 +1,7 @@
-// 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::{env, io};
+//! TTY related functionality.
 
-use terminfo::Database;
+use std::path::PathBuf;
+use std::{env, io};
 
 use crate::config::Config;
 
@@ -53,14 +38,14 @@
     fn write_token(&self) -> mio::Token;
 }
 
-/// Events concerning TTY child processes
+/// Events concerning TTY child processes.
 #[derive(Debug, PartialEq)]
 pub enum ChildEvent {
-    /// Indicates the child has exited
+    /// Indicates the child has exited.
     Exited,
 }
 
-/// A pseudoterminal (or PTY)
+/// A pseudoterminal (or PTY).
 ///
 /// This is a refinement of EventedReadWrite that also provides a channel through which we can be
 /// notified if the PTY child process does something we care about (other than writing to the TTY).
@@ -68,30 +53,73 @@
 pub trait EventedPty: EventedReadWrite {
     fn child_event_token(&self) -> mio::Token;
 
-    /// Tries to retrieve an event
+    /// Tries to retrieve an event.
     ///
     /// Returns `Some(event)` on success, or `None` if there are no events to retrieve.
     fn next_child_event(&mut self) -> Option<ChildEvent>;
 }
 
-// Setup environment variables
+/// Setup environment variables.
 pub fn setup_env<C>(config: &Config<C>) {
     // Default to 'alacritty' terminfo if it is available, otherwise
     // default to 'xterm-256color'. May be overridden by user's config
     // below.
-    env::set_var(
-        "TERM",
-        if Database::from_name("alacritty").is_ok() { "alacritty" } else { "xterm-256color" },
-    );
+    let terminfo = if terminfo_exists("alacritty") { "alacritty" } else { "xterm-256color" };
+    env::set_var("TERM", terminfo);
 
-    // Advertise 24-bit color support
+    // Advertise 24-bit color support.
     env::set_var("COLORTERM", "truecolor");
 
-    // Prevent child processes from inheriting startup notification env
+    // Prevent child processes from inheriting startup notification env.
     env::remove_var("DESKTOP_STARTUP_ID");
 
-    // Set env vars from config
+    // Set env vars from config.
     for (key, value) in config.env.iter() {
         env::set_var(key, value);
     }
 }
+
+/// Check if a terminfo entry exists on the system.
+fn terminfo_exists(terminfo: &str) -> bool {
+    // Get first terminfo character for the parent directory.
+    let first = terminfo.get(..1).unwrap_or_default();
+    let first_hex = format!("{:x}", first.chars().next().unwrap_or_default() as usize);
+
+    // Return true if the terminfo file exists at the specified location.
+    macro_rules! check_path {
+        ($path:expr) => {
+            if $path.join(first).join(terminfo).exists()
+                || $path.join(&first_hex).join(terminfo).exists()
+            {
+                return true;
+            }
+        };
+    }
+
+    if let Some(dir) = env::var_os("TERMINFO") {
+        check_path!(PathBuf::from(&dir));
+    } else if let Some(home) = dirs::home_dir() {
+        check_path!(home.join(".terminfo"));
+    }
+
+    if let Ok(dirs) = env::var("TERMINFO_DIRS") {
+        for dir in dirs.split(':') {
+            check_path!(PathBuf::from(dir));
+        }
+    }
+
+    if let Ok(prefix) = env::var("PREFIX") {
+        let path = PathBuf::from(prefix);
+        check_path!(path.join("etc/terminfo"));
+        check_path!(path.join("lib/terminfo"));
+        check_path!(path.join("share/terminfo"));
+    }
+
+    check_path!(PathBuf::from("/etc/terminfo"));
+    check_path!(PathBuf::from("/lib/terminfo"));
+    check_path!(PathBuf::from("/usr/share/terminfo"));
+    check_path!(PathBuf::from("/boot/system/data/terminfo"));
+
+    // No valid terminfo path has been found.
+    false
+}
diff --git a/alacritty_terminal/src/tty/unix.rs b/alacritty_terminal/src/tty/unix.rs
index 1b01af0..ba59bb6 100644
--- a/alacritty_terminal/src/tty/unix.rs
+++ b/alacritty_terminal/src/tty/unix.rs
@@ -1,31 +1,8 @@
-// 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
+//! TTY related functionality.
 
-use crate::config::{Config, Shell};
-use crate::event::OnResize;
-use crate::term::SizeInfo;
-use crate::tty::{ChildEvent, EventedPty, EventedReadWrite};
-use mio;
-
-use libc::{self, c_int, pid_t, winsize, TIOCSCTTY};
-use log::error;
-use nix::pty::openpty;
-use signal_hook::{self as sighook, iterator::Signals};
-
-use mio::unix::EventedFd;
+use std::borrow::Cow;
+#[cfg(not(target_os = "macos"))]
+use std::env;
 use std::ffi::CStr;
 use std::fs::File;
 use std::io;
@@ -36,17 +13,34 @@
 };
 use std::process::{Child, Command, Stdio};
 use std::ptr;
-use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::atomic::{AtomicI32, AtomicUsize, Ordering};
 
-/// Process ID of child process
+use libc::{self, c_int, pid_t, winsize, TIOCSCTTY};
+use log::error;
+use mio::unix::EventedFd;
+use nix::pty::openpty;
+#[cfg(any(target_os = "linux", target_os = "macos"))]
+use nix::sys::termios::{self, InputFlags, SetArg};
+use signal_hook::{self as sighook, iterator::Signals};
+
+use crate::config::{Config, Program};
+use crate::event::OnResize;
+use crate::grid::Dimensions;
+use crate::term::SizeInfo;
+use crate::tty::{ChildEvent, EventedPty, EventedReadWrite};
+
+/// Process ID of child process.
 ///
-/// Necessary to put this in static storage for `sigchld` to have access
+/// Necessary to put this in static storage for `SIGCHLD` to have access.
 static PID: AtomicUsize = AtomicUsize::new(0);
 
+/// File descriptor of terminal master.
+static FD: AtomicI32 = AtomicI32::new(-1);
+
 macro_rules! die {
     ($($arg:tt)*) => {{
         error!($($arg)*);
-        ::std::process::exit(1);
+        std::process::exit(1);
     }}
 }
 
@@ -54,7 +48,11 @@
     PID.load(Ordering::Relaxed) as pid_t
 }
 
-/// Get raw fds for master/slave ends of a new pty
+pub fn master_fd() -> RawFd {
+    FD.load(Ordering::Relaxed) as RawFd
+}
+
+/// Get raw fds for master/slave ends of a new PTY.
 fn make_pty(size: winsize) -> (RawFd, RawFd) {
     let mut win_size = size;
     win_size.ws_xpixel = 0;
@@ -65,7 +63,7 @@
     (ends.master, ends.slave)
 }
 
-/// Really only needed on BSD, but should be fine elsewhere
+/// Really only needed on BSD, but should be fine elsewhere.
 fn set_controlling_terminal(fd: c_int) {
     let res = unsafe {
         // TIOSCTTY changes based on platform and the `ioctl` call is different
@@ -92,13 +90,13 @@
     shell: &'a str,
 }
 
-/// Return a Passwd struct with pointers into the provided buf
+/// Return a Passwd struct with pointers into the provided buf.
 ///
 /// # Unsafety
 ///
 /// If `buf` is changed while `Passwd` is alive, bad thing will almost certainly happen.
 fn get_pw_entry(buf: &mut [i8; 1024]) -> Passwd<'_> {
-    // Create zeroed passwd struct
+    // Create zeroed passwd struct.
     let mut entry: MaybeUninit<libc::passwd> = MaybeUninit::uninit();
 
     let mut res: *mut libc::passwd = ptr::null_mut();
@@ -118,10 +116,10 @@
         die!("pw not found");
     }
 
-    // sanity check
+    // Sanity check.
     assert_eq!(entry.pw_uid, uid);
 
-    // Build a borrowed Passwd struct
+    // Build a borrowed Passwd struct.
     Passwd {
         name: unsafe { CStr::from_ptr(entry.pw_name).to_str().unwrap() },
         passwd: unsafe { CStr::from_ptr(entry.pw_passwd).to_str().unwrap() },
@@ -141,50 +139,67 @@
     signals_token: mio::Token,
 }
 
-/// Create a new tty and return a handle to interact with it.
+#[cfg(target_os = "macos")]
+fn default_shell(pw: &Passwd<'_>) -> Program {
+    let shell_name = pw.shell.rsplit('/').next().unwrap();
+    let argv = vec![String::from("-c"), format!("exec -a -{} {}", shell_name, pw.shell)];
+
+    Program::WithArgs { program: "/bin/bash".to_owned(), args: argv }
+}
+
+#[cfg(not(target_os = "macos"))]
+fn default_shell(pw: &Passwd<'_>) -> Program {
+    Program::Just(env::var("SHELL").unwrap_or_else(|_| pw.shell.to_owned()))
+}
+
+/// Create a new TTY and return a handle to interact with it.
 pub fn new<C>(config: &Config<C>, size: &SizeInfo, window_id: Option<usize>) -> Pty {
-    let win_size = size.to_winsize();
+    let (master, slave) = make_pty(size.to_winsize());
+
+    #[cfg(any(target_os = "linux", target_os = "macos"))]
+    if let Ok(mut termios) = termios::tcgetattr(master) {
+        // Set character encoding to UTF-8.
+        termios.input_flags.set(InputFlags::IUTF8, true);
+        let _ = termios::tcsetattr(master, SetArg::TCSANOW, &termios);
+    }
+
     let mut buf = [0; 1024];
     let pw = get_pw_entry(&mut buf);
 
-    let (master, slave) = make_pty(win_size);
-
-    let default_shell = if cfg!(target_os = "macos") {
-        let shell_name = pw.shell.rsplit('/').next().unwrap();
-        let argv = vec![String::from("-c"), format!("exec -a -{} {}", shell_name, pw.shell)];
-
-        Shell::new_with_args("/bin/bash", argv)
-    } else {
-        Shell::new(pw.shell)
+    let shell = match config.shell.as_ref() {
+        Some(shell) => Cow::Borrowed(shell),
+        None => Cow::Owned(default_shell(&pw)),
     };
-    let shell = config.shell.as_ref().unwrap_or(&default_shell);
 
-    let mut builder = Command::new(&*shell.program);
-    for arg in &shell.args {
+    let mut builder = Command::new(shell.program());
+    for arg in shell.args() {
         builder.arg(arg);
     }
 
-    // Setup child stdin/stdout/stderr as slave fd of pty
+    // Setup child stdin/stdout/stderr as slave fd of PTY.
     // Ownership of fd is transferred to the Stdio structs and will be closed by them at the end of
     // this scope. (It is not an issue that the fd is closed three times since File::drop ignores
-    // error on libc::close.)
+    // error on libc::close.).
     builder.stdin(unsafe { Stdio::from_raw_fd(slave) });
     builder.stderr(unsafe { Stdio::from_raw_fd(slave) });
     builder.stdout(unsafe { Stdio::from_raw_fd(slave) });
 
-    // Setup shell environment
+    // Setup shell environment.
     builder.env("LOGNAME", pw.name);
     builder.env("USER", pw.name);
-    builder.env("SHELL", pw.shell);
     builder.env("HOME", pw.dir);
 
+    // Set $SHELL environment variable on macOS, since login does not do it for us.
+    #[cfg(target_os = "macos")]
+    builder.env("SHELL", config.shell.as_ref().map(|sh| sh.program()).unwrap_or(pw.shell));
+
     if let Some(window_id) = window_id {
         builder.env("WINDOWID", format!("{}", window_id));
     }
 
     unsafe {
         builder.pre_exec(move || {
-            // Create a new process group
+            // Create a new process group.
             let err = libc::setsid();
             if err == -1 {
                 die!("Failed to set session id: {}", io::Error::last_os_error());
@@ -192,7 +207,7 @@
 
             set_controlling_terminal(slave);
 
-            // No longer need slave/master fds
+            // No longer need slave/master fds.
             libc::close(slave);
             libc::close(master);
 
@@ -207,18 +222,19 @@
         });
     }
 
-    // Handle set working directory option
+    // Handle set working directory option.
     if let Some(dir) = &config.working_directory {
         builder.current_dir(dir);
     }
 
-    // Prepare signal handling before spawning child
+    // Prepare signal handling before spawning child.
     let signals = Signals::new(&[sighook::SIGCHLD]).expect("error preparing signal handling");
 
     match builder.spawn() {
         Ok(child) => {
-            // Remember child PID so other modules can use it
+            // Remember master FD and child PID so other modules can use it.
             PID.store(child.id() as usize, Ordering::Relaxed);
+            FD.store(master, Ordering::Relaxed);
 
             unsafe {
                 // Maybe this should be done outside of this function so nonblocking
@@ -236,7 +252,7 @@
             pty.on_resize(size);
             pty
         },
-        Err(err) => die!("Failed to spawn command '{}': {}", shell.program, err),
+        Err(err) => die!("Failed to spawn command '{}': {}", shell.program(), err),
     }
 }
 
@@ -333,25 +349,25 @@
     }
 }
 
-/// Types that can produce a `libc::winsize`
+/// Types that can produce a `libc::winsize`.
 pub trait ToWinsize {
-    /// Get a `libc::winsize`
+    /// Get a `libc::winsize`.
     fn to_winsize(&self) -> winsize;
 }
 
 impl<'a> ToWinsize for &'a SizeInfo {
     fn to_winsize(&self) -> winsize {
         winsize {
-            ws_row: self.lines().0 as libc::c_ushort,
-            ws_col: self.cols().0 as libc::c_ushort,
-            ws_xpixel: self.width as libc::c_ushort,
-            ws_ypixel: self.height as libc::c_ushort,
+            ws_row: self.screen_lines() as libc::c_ushort,
+            ws_col: self.columns() as libc::c_ushort,
+            ws_xpixel: self.width() as libc::c_ushort,
+            ws_ypixel: self.height() as libc::c_ushort,
         }
     }
 }
 
 impl OnResize for Pty {
-    /// Resize the pty
+    /// Resize the PTY.
     ///
     /// Tells the kernel that the window size changed with the new pixel
     /// dimensions and line/column counts.
diff --git a/alacritty_terminal/src/tty/windows/child.rs b/alacritty_terminal/src/tty/windows/child.rs
index 447b7fb..fc16360 100644
--- a/alacritty_terminal/src/tty/windows/child.rs
+++ b/alacritty_terminal/src/tty/windows/child.rs
@@ -1,15 +1,3 @@
-// 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::ffi::c_void;
 use std::io::Error;
 use std::sync::atomic::{AtomicPtr, Ordering};
@@ -76,7 +64,7 @@
 }
 
 #[cfg(test)]
-mod test {
+mod tests {
     use std::os::windows::io::AsRawHandle;
     use std::process::Command;
     use std::time::Duration;
@@ -106,10 +94,10 @@
 
         child.kill().unwrap();
 
-        // Poll for the event or fail with timeout if nothing has been sent
+        // Poll for the event or fail with timeout if nothing has been sent.
         poll.poll(&mut events, Some(WAIT_TIMEOUT)).unwrap();
         assert_eq!(events.iter().next().unwrap().token(), child_events_token);
-        // Verify that at least one `ChildEvent::Exited` was received
+        // Verify that at least one `ChildEvent::Exited` was received.
         assert_eq!(child_exit_watcher.event_rx().try_recv(), Ok(ChildEvent::Exited));
     }
 }
diff --git a/alacritty_terminal/src/tty/windows/conpty.rs b/alacritty_terminal/src/tty/windows/conpty.rs
index 99d52b0..919bd00 100644
--- a/alacritty_terminal/src/tty/windows/conpty.rs
+++ b/alacritty_terminal/src/tty/windows/conpty.rs
@@ -1,17 +1,3 @@
-// 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::i16;
 use std::io::Error;
 use std::mem;
@@ -19,12 +5,11 @@
 use std::ptr;
 
 use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite};
-use miow;
 use winapi::shared::basetsd::{PSIZE_T, SIZE_T};
-use winapi::shared::minwindef::{BYTE, DWORD};
-use winapi::shared::ntdef::{HANDLE, HRESULT, LPWSTR};
+use winapi::shared::minwindef::BYTE;
+use winapi::shared::ntdef::LPWSTR;
 use winapi::shared::winerror::S_OK;
-use winapi::um::libloaderapi::{GetModuleHandleA, GetProcAddress};
+use winapi::um::consoleapi::{ClosePseudoConsole, CreatePseudoConsole, ResizePseudoConsole};
 use winapi::um::processthreadsapi::{
     CreateProcessW, InitializeProcThreadAttributeList, UpdateProcThreadAttribute,
     PROCESS_INFORMATION, STARTUPINFOW,
@@ -34,56 +19,14 @@
 
 use crate::config::Config;
 use crate::event::OnResize;
+use crate::grid::Dimensions;
 use crate::term::SizeInfo;
 use crate::tty::windows::child::ChildExitWatcher;
 use crate::tty::windows::{cmdline, win32_string, Pty};
 
-// TODO: Replace with winapi's implementation. This cannot be
-//  done until a safety net is in place for versions of Windows
-//  that do not support the ConPTY api, as such versions will
-//  pass unit testing - but fail to actually function.
-/// Dynamically-loaded Pseudoconsole API from kernel32.dll
-///
-/// The field names are deliberately PascalCase as this matches
-/// the defined symbols in kernel32 and also is the convention
-/// that the `winapi` crate follows.
-#[allow(non_snake_case)]
-struct ConptyApi {
-    CreatePseudoConsole:
-        unsafe extern "system" fn(COORD, HANDLE, HANDLE, DWORD, *mut HPCON) -> HRESULT,
-    ResizePseudoConsole: unsafe extern "system" fn(HPCON, COORD) -> HRESULT,
-    ClosePseudoConsole: unsafe extern "system" fn(HPCON),
-}
-
-impl ConptyApi {
-    /// Load the API or None if it cannot be found.
-    pub fn new() -> Option<Self> {
-        // Unsafe because windows API calls
-        unsafe {
-            let hmodule = GetModuleHandleA("kernel32\0".as_ptr() as _);
-            assert!(!hmodule.is_null());
-
-            let cpc = GetProcAddress(hmodule, "CreatePseudoConsole\0".as_ptr() as _);
-            let rpc = GetProcAddress(hmodule, "ResizePseudoConsole\0".as_ptr() as _);
-            let clpc = GetProcAddress(hmodule, "ClosePseudoConsole\0".as_ptr() as _);
-
-            if cpc.is_null() || rpc.is_null() || clpc.is_null() {
-                None
-            } else {
-                Some(Self {
-                    CreatePseudoConsole: mem::transmute(cpc),
-                    ResizePseudoConsole: mem::transmute(rpc),
-                    ClosePseudoConsole: mem::transmute(clpc),
-                })
-            }
-        }
-    }
-}
-
-/// RAII Pseudoconsole
+/// RAII Pseudoconsole.
 pub struct Conpty {
     pub handle: HPCON,
-    api: ConptyApi,
 }
 
 impl Drop for Conpty {
@@ -91,21 +34,15 @@
         // XXX: This will block until the conout pipe is drained. Will cause a deadlock if the
         // conout pipe has already been dropped by this point.
         //
-        // See PR #3084 and https://docs.microsoft.com/en-us/windows/console/closepseudoconsole
-        unsafe { (self.api.ClosePseudoConsole)(self.handle) }
+        // See PR #3084 and https://docs.microsoft.com/en-us/windows/console/closepseudoconsole.
+        unsafe { ClosePseudoConsole(self.handle) }
     }
 }
 
-// The Conpty handle can be sent between threads.
+// The ConPTY handle can be sent between threads.
 unsafe impl Send for Conpty {}
 
-pub fn new<C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>) -> Option<Pty> {
-    if config.winpty_backend {
-        return None;
-    }
-
-    let api = ConptyApi::new()?;
-
+pub fn new<C>(config: &Config<C>, size: &SizeInfo) -> Option<Pty> {
     let mut pty_handle = 0 as HPCON;
 
     // Passing 0 as the size parameter allows the "system default" buffer
@@ -118,9 +55,9 @@
     let coord =
         coord_from_sizeinfo(size).expect("Overflow when creating initial size on pseudoconsole");
 
-    // Create the Pseudo Console, using the pipes
+    // Create the Pseudo Console, using the pipes.
     let result = unsafe {
-        (api.CreatePseudoConsole)(
+        CreatePseudoConsole(
             coord,
             conin_pty_handle.into_raw_handle(),
             conout_pty_handle.into_raw_handle(),
@@ -133,19 +70,18 @@
 
     let mut success;
 
-    // Prepare child process startup info
+    // Prepare child process startup info.
 
     let mut size: SIZE_T = 0;
 
     let mut startup_info_ex: STARTUPINFOEXW = Default::default();
 
-    let title = win32_string(&config.window.title);
-    startup_info_ex.StartupInfo.lpTitle = title.as_ptr() as LPWSTR;
+    startup_info_ex.StartupInfo.lpTitle = std::ptr::null_mut() as LPWSTR;
 
     startup_info_ex.StartupInfo.cb = mem::size_of::<STARTUPINFOEXW>() as u32;
 
     // Setting this flag but leaving all the handles as default (null) ensures the
-    // pty process does not inherit any handles from this Alacritty process.
+    // PTY process does not inherit any handles from this Alacritty process.
     startup_info_ex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
 
     // Create the appropriately sized thread attribute list.
@@ -185,12 +121,12 @@
         }
     }
 
-    // Set thread attribute list's Pseudo Console to the specified ConPTY
+    // Set thread attribute list's Pseudo Console to the specified ConPTY.
     unsafe {
         success = UpdateProcThreadAttribute(
             startup_info_ex.lpAttributeList,
             0,
-            22 | 0x0002_0000, // PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
+            22 | 0x0002_0000, // PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE.
             pty_handle,
             mem::size_of::<HPCON>(),
             ptr::null_mut(),
@@ -229,20 +165,12 @@
     let conout = EventedAnonRead::new(conout);
 
     let child_watcher = ChildExitWatcher::new(proc_info.hProcess).unwrap();
-    let conpty = Conpty { handle: pty_handle, api };
+    let conpty = Conpty { handle: pty_handle };
 
-    Some(Pty {
-        backend: super::PtyBackend::Conpty(conpty),
-        conout: super::EventedReadablePipe::Anonymous(conout),
-        conin: super::EventedWritablePipe::Anonymous(conin),
-        read_token: 0.into(),
-        write_token: 0.into(),
-        child_event_token: 0.into(),
-        child_watcher,
-    })
+    Some(Pty::new(conpty, conout, conin, child_watcher))
 }
 
-// Panic with the last os error as message
+// Panic with the last os error as message.
 fn panic_shell_spawn() {
     panic!("Unable to spawn shell: {}", Error::last_os_error());
 }
@@ -250,19 +178,19 @@
 impl OnResize for Conpty {
     fn on_resize(&mut self, sizeinfo: &SizeInfo) {
         if let Some(coord) = coord_from_sizeinfo(sizeinfo) {
-            let result = unsafe { (self.api.ResizePseudoConsole)(self.handle, coord) };
+            let result = unsafe { ResizePseudoConsole(self.handle, coord) };
             assert_eq!(result, S_OK);
         }
     }
 }
 
 /// Helper to build a COORD from a SizeInfo, returning None in overflow cases.
-fn coord_from_sizeinfo(sizeinfo: &SizeInfo) -> Option<COORD> {
-    let cols = sizeinfo.cols().0;
-    let lines = sizeinfo.lines().0;
+fn coord_from_sizeinfo(size: &SizeInfo) -> Option<COORD> {
+    let lines = size.screen_lines();
+    let columns = size.columns();
 
-    if cols <= i16::MAX as usize && lines <= i16::MAX as usize {
-        Some(COORD { X: sizeinfo.cols().0 as i16, Y: sizeinfo.lines().0 as i16 })
+    if columns <= i16::MAX as usize && lines <= i16::MAX as usize {
+        Some(COORD { X: columns as i16, Y: lines as i16 })
     } else {
         None
     }
diff --git a/alacritty_terminal/src/tty/windows/mod.rs b/alacritty_terminal/src/tty/windows/mod.rs
index 03e2a3c..644253f 100644
--- a/alacritty_terminal/src/tty/windows/mod.rs
+++ b/alacritty_terminal/src/tty/windows/mod.rs
@@ -1,31 +1,10 @@
-// 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::ffi::OsStr;
-use std::io::{self, Read, Write};
+use std::io;
 use std::iter::once;
 use std::os::windows::ffi::OsStrExt;
-use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::mpsc::TryRecvError;
 
-use mio::{self, Evented, Poll, PollOpt, Ready, Token};
-use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite};
-use mio_named_pipes::NamedPipe;
-
-use log::info;
-
-use crate::config::{Config, Shell};
+use crate::config::{Config, Program};
 use crate::event::OnResize;
 use crate::term::SizeInfo;
 use crate::tty::windows::child::ChildExitWatcher;
@@ -33,161 +12,48 @@
 
 mod child;
 mod conpty;
-mod winpty;
 
-static IS_CONPTY: AtomicBool = AtomicBool::new(false);
-
-pub fn is_conpty() -> bool {
-    IS_CONPTY.load(Ordering::Relaxed)
-}
-
-enum PtyBackend {
-    Winpty(winpty::Agent),
-    Conpty(conpty::Conpty),
-}
+use conpty::Conpty as Backend;
+use mio_anonymous_pipes::{EventedAnonRead as ReadPipe, EventedAnonWrite as WritePipe};
 
 pub struct Pty {
     // XXX: Backend is required to be the first field, to ensure correct drop order. Dropping
-    // `conout` before `backend` will cause a deadlock.
-    backend: PtyBackend,
-    // TODO: It's on the roadmap for the Conpty API to support Overlapped I/O.
-    // See https://github.com/Microsoft/console/issues/262
-    // When support for that lands then it should be possible to use
-    // NamedPipe for the conout and conin handles
-    conout: EventedReadablePipe,
-    conin: EventedWritablePipe,
+    // `conout` before `backend` will cause a deadlock (with Conpty).
+    backend: Backend,
+    conout: ReadPipe,
+    conin: WritePipe,
     read_token: mio::Token,
     write_token: mio::Token,
     child_event_token: mio::Token,
     child_watcher: ChildExitWatcher,
 }
 
-pub fn new<C>(config: &Config<C>, size: &SizeInfo, window_id: Option<usize>) -> Pty {
-    if let Some(pty) = conpty::new(config, size, window_id) {
-        info!("Using ConPTY backend");
-        IS_CONPTY.store(true, Ordering::Relaxed);
-        pty
-    } else {
-        info!("Using WinPTY backend");
-        winpty::new(config, size, window_id)
-    }
+pub fn new<C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>) -> Pty {
+    conpty::new(config, size).expect("Failed to create ConPTY backend")
 }
 
-// TODO: The ConPTY API currently must use synchronous pipes as the input
-// and output handles. This has led to the need to support two different
-// types of pipe.
-//
-// When https://github.com/Microsoft/console/issues/262 lands then the
-// Anonymous variant of this enum can be removed from the codebase and
-// everything can just use NamedPipe.
-pub enum EventedReadablePipe {
-    Anonymous(EventedAnonRead),
-    Named(NamedPipe),
-}
-
-pub enum EventedWritablePipe {
-    Anonymous(EventedAnonWrite),
-    Named(NamedPipe),
-}
-
-impl Evented for EventedReadablePipe {
-    fn register(
-        &self,
-        poll: &Poll,
-        token: Token,
-        interest: Ready,
-        opts: PollOpt,
-    ) -> io::Result<()> {
-        match self {
-            EventedReadablePipe::Anonymous(p) => p.register(poll, token, interest, opts),
-            EventedReadablePipe::Named(p) => p.register(poll, token, interest, opts),
-        }
-    }
-
-    fn reregister(
-        &self,
-        poll: &Poll,
-        token: Token,
-        interest: Ready,
-        opts: PollOpt,
-    ) -> io::Result<()> {
-        match self {
-            EventedReadablePipe::Anonymous(p) => p.reregister(poll, token, interest, opts),
-            EventedReadablePipe::Named(p) => p.reregister(poll, token, interest, opts),
-        }
-    }
-
-    fn deregister(&self, poll: &Poll) -> io::Result<()> {
-        match self {
-            EventedReadablePipe::Anonymous(p) => p.deregister(poll),
-            EventedReadablePipe::Named(p) => p.deregister(poll),
-        }
-    }
-}
-
-impl Read for EventedReadablePipe {
-    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
-        match self {
-            EventedReadablePipe::Anonymous(p) => p.read(buf),
-            EventedReadablePipe::Named(p) => p.read(buf),
-        }
-    }
-}
-
-impl Evented for EventedWritablePipe {
-    fn register(
-        &self,
-        poll: &Poll,
-        token: Token,
-        interest: Ready,
-        opts: PollOpt,
-    ) -> io::Result<()> {
-        match self {
-            EventedWritablePipe::Anonymous(p) => p.register(poll, token, interest, opts),
-            EventedWritablePipe::Named(p) => p.register(poll, token, interest, opts),
-        }
-    }
-
-    fn reregister(
-        &self,
-        poll: &Poll,
-        token: Token,
-        interest: Ready,
-        opts: PollOpt,
-    ) -> io::Result<()> {
-        match self {
-            EventedWritablePipe::Anonymous(p) => p.reregister(poll, token, interest, opts),
-            EventedWritablePipe::Named(p) => p.reregister(poll, token, interest, opts),
-        }
-    }
-
-    fn deregister(&self, poll: &Poll) -> io::Result<()> {
-        match self {
-            EventedWritablePipe::Anonymous(p) => p.deregister(poll),
-            EventedWritablePipe::Named(p) => p.deregister(poll),
-        }
-    }
-}
-
-impl Write for EventedWritablePipe {
-    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
-        match self {
-            EventedWritablePipe::Anonymous(p) => p.write(buf),
-            EventedWritablePipe::Named(p) => p.write(buf),
-        }
-    }
-
-    fn flush(&mut self) -> io::Result<()> {
-        match self {
-            EventedWritablePipe::Anonymous(p) => p.flush(),
-            EventedWritablePipe::Named(p) => p.flush(),
+impl Pty {
+    fn new(
+        backend: impl Into<Backend>,
+        conout: impl Into<ReadPipe>,
+        conin: impl Into<WritePipe>,
+        child_watcher: ChildExitWatcher,
+    ) -> Self {
+        Self {
+            backend: backend.into(),
+            conout: conout.into(),
+            conin: conin.into(),
+            read_token: 0.into(),
+            write_token: 0.into(),
+            child_event_token: 0.into(),
+            child_watcher,
         }
     }
 }
 
 impl EventedReadWrite for Pty {
-    type Reader = EventedReadablePipe;
-    type Writer = EventedWritablePipe;
+    type Reader = ReadPipe;
+    type Writer = WritePipe;
 
     #[inline]
     fn register(
@@ -295,19 +161,16 @@
 
 impl OnResize for Pty {
     fn on_resize(&mut self, size: &SizeInfo) {
-        match &mut self.backend {
-            PtyBackend::Winpty(w) => w.on_resize(size),
-            PtyBackend::Conpty(c) => c.on_resize(size),
-        }
+        self.backend.on_resize(size)
     }
 }
 
 fn cmdline<C>(config: &Config<C>) -> String {
-    let default_shell = Shell::new("powershell");
+    let default_shell = Program::Just("powershell".to_owned());
     let shell = config.shell.as_ref().unwrap_or(&default_shell);
 
-    once(shell.program.as_ref())
-        .chain(shell.args.iter().map(|a| a.as_ref()))
+    once(shell.program().as_ref())
+        .chain(shell.args().iter().map(|a| a.as_ref()))
         .collect::<Vec<_>>()
         .join(" ")
 }