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