aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
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
}