[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(" ")
}