aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/event.rs29
-rw-r--r--src/input.rs28
-rw-r--r--src/main.rs256
-rw-r--r--src/tty.rs13
4 files changed, 272 insertions, 54 deletions
diff --git a/src/event.rs b/src/event.rs
index 303c6d8e..f44fd86c 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -1,4 +1,5 @@
//! Process window events
+use std::io;
use std::sync::{Arc, mpsc};
use std;
@@ -9,27 +10,25 @@ use sync::FairMutex;
use term::Term;
/// The event processor
-pub struct Processor<'a, W: 'a> {
- writer: &'a mut W,
+pub struct Processor<N> {
+ notifier: N,
input_processor: input::Processor,
terminal: Arc<FairMutex<Term>>,
resize_tx: mpsc::Sender<(u32, u32)>,
}
-impl<'a, W> Processor<'a, W>
- where W: std::io::Write
-{
+impl<N: input::Notify> Processor<N> {
/// Create a new event processor
///
/// Takes a writer which is expected to be hooked up to the write end of a
/// pty.
- pub fn new(writer: &mut W,
- terminal: Arc<FairMutex<Term>>,
- resize_tx: mpsc::Sender<(u32, u32)>)
- -> Processor<W>
- {
+ pub fn new(
+ notifier: N,
+ terminal: Arc<FairMutex<Term>>,
+ resize_tx: mpsc::Sender<(u32, u32)>
+ ) -> Processor<N> {
Processor {
- writer: writer,
+ notifier: notifier,
terminal: terminal,
input_processor: input::Processor::new(),
resize_tx: resize_tx,
@@ -47,7 +46,7 @@ impl<'a, W> Processor<'a, W>
'\u{f700}' | '\u{f701}' | '\u{f702}' | '\u{f703}' => (),
_ => {
let encoded = c.encode_utf8();
- self.writer.write(encoded.as_slice()).unwrap();
+ self.notifier.notify(encoded.as_slice().to_vec());
}
}
},
@@ -60,10 +59,10 @@ impl<'a, W> Processor<'a, W>
glutin::Event::KeyboardInput(state, _code, key, mods) => {
// Acquire term lock
let terminal = self.terminal.lock();
+ let processor = &mut self.input_processor;
+ let notifier = &mut self.notifier;
- self.input_processor.process(state, key, mods,
- &mut input::WriteNotifier(self.writer),
- *terminal.mode());
+ processor.process(state, key, mods, notifier, *terminal.mode());
},
_ => (),
}
diff --git a/src/input.rs b/src/input.rs
index 757256d2..829312e7 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -23,6 +23,7 @@
//! APIs
//!
//! TODO handling xmodmap would be good
+use std::borrow::Cow;
use std::io::Write;
use glutin::{ElementState, VirtualKeyCode};
@@ -42,15 +43,34 @@ pub struct Processor;
/// Types that are notified of escape sequences from the input::Processor.
pub trait Notify {
/// Notify that an escape sequence should be written to the pty
- fn notify(&mut self, &str);
+ ///
+ /// TODO this needs to be able to error somehow
+ fn notify<B: Into<Cow<'static, [u8]>>>(&mut self, B);
}
/// A notifier type that simply writes bytes to the provided `Write` type
pub struct WriteNotifier<'a, W: Write + 'a>(pub &'a mut W);
impl<'a, W: Write> Notify for WriteNotifier<'a, W> {
- fn notify(&mut self, message: &str) {
- self.0.write_all(message.as_bytes()).unwrap();
+ fn notify<B>(&mut self, bytes: B)
+ where B: Into<Cow<'static, [u8]>>
+ {
+ let message = bytes.into();
+ self.0.write_all(&message[..]).unwrap();
+ }
+}
+
+pub struct LoopNotifier(pub ::mio::channel::Sender<::EventLoopMessage>);
+
+impl Notify for LoopNotifier {
+ fn notify<B>(&mut self, bytes: B)
+ where B: Into<Cow<'static, [u8]>>
+ {
+ let bytes = bytes.into();
+ match self.0.send(::EventLoopMessage::Input(bytes)) {
+ Ok(_) => (),
+ Err(_) => panic!("expected send event loop msg"),
+ }
}
}
@@ -277,7 +297,7 @@ impl Processor {
// Modifier keys
if binding.mods.is_all() || mods.intersects(binding.mods) {
// everything matches
- notifier.notify(binding.send);
+ notifier.notify(binding.send.as_bytes());
break;
}
}
diff --git a/src/main.rs b/src/main.rs
index e520b08d..c6694ce9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -28,6 +28,7 @@ extern crate errno;
extern crate font;
extern crate glutin;
extern crate libc;
+extern crate mio;
extern crate notify;
extern crate parking_lot;
extern crate serde;
@@ -54,6 +55,7 @@ mod util;
mod sync;
use std::sync::{mpsc, Arc};
+use std::fs::File;
use std::sync::atomic::{AtomicBool, Ordering};
use parking_lot::{MutexGuard};
@@ -172,9 +174,7 @@ fn main() {
println!("Cell Size: ({} x {})", cell_width, cell_height);
let terminal = Term::new(width as f32, height as f32, cell_width as f32, cell_height as f32);
-
- let reader = terminal.tty().reader();
- let mut writer = terminal.tty().writer();
+ let pty_io = terminal.tty().reader();
let (tx, rx) = mpsc::channel();
unsafe {
@@ -183,16 +183,20 @@ fn main() {
let signal_flag = Flag::new(false);
+
let terminal = Arc::new(FairMutex::new(terminal));
let window = Arc::new(window);
- let pty_reader = PtyReader::spawn(
+ let event_loop = EventLoop::new(
terminal.clone(),
- reader,
window.create_window_proxy(),
- signal_flag.clone()
+ signal_flag.clone(),
+ pty_io,
);
+ let loop_tx = event_loop.channel();
+ let event_loop_handle = event_loop.spawn();
+
// Wraps a renderer and gives simple draw() api.
let mut display = Display::new(
window.clone(),
@@ -203,7 +207,11 @@ fn main() {
);
// Event processor
- let mut processor = event::Processor::new(&mut writer, terminal.clone(), tx);
+ let mut processor = event::Processor::new(
+ input::LoopNotifier(loop_tx),
+ terminal.clone(),
+ tx
+ );
// Main loop
loop {
@@ -223,43 +231,221 @@ fn main() {
}
// shutdown
- pty_reader.join().ok();
+ event_loop_handle.join().ok();
println!("Goodbye");
}
-struct PtyReader;
+struct EventLoop {
+ poll: mio::Poll,
+ pty: File,
+ rx: mio::channel::Receiver<EventLoopMessage>,
+ tx: mio::channel::Sender<EventLoopMessage>,
+ terminal: Arc<FairMutex<Term>>,
+ proxy: ::glutin::WindowProxy,
+ signal_flag: Flag,
+}
-impl PtyReader {
- pub fn spawn<R>(terminal: Arc<FairMutex<Term>>,
- mut pty: R,
- proxy: ::glutin::WindowProxy,
- signal_flag: Flag)
- -> std::thread::JoinHandle<()>
- where R: std::io::Read + Send + 'static
- {
- thread::spawn_named("pty reader", move || {
- let mut buf = [0u8; 4096];
- let mut pty_parser = ansi::Processor::new();
+const CHANNEL: mio::Token = mio::Token(0);
+const PTY: mio::Token = mio::Token(1);
- loop {
- if let Ok(got) = pty.read(&mut buf[..]) {
- let mut terminal = terminal.lock();
+#[derive(Debug)]
+pub enum EventLoopMessage {
+ Input(::std::borrow::Cow<'static, [u8]>),
+}
- for byte in &buf[..got] {
- pty_parser.advance(&mut *terminal, *byte);
- }
+impl EventLoop {
+ pub fn new(
+ terminal: Arc<FairMutex<Term>>,
+ proxy: ::glutin::WindowProxy,
+ signal_flag: Flag,
+ pty: File,
+ ) -> EventLoop {
+ let (tx, rx) = ::mio::channel::channel();
+ EventLoop {
+ poll: mio::Poll::new().expect("create mio Poll"),
+ pty: pty,
+ tx: tx,
+ rx: rx,
+ terminal: terminal,
+ proxy: proxy,
+ signal_flag: signal_flag
+ }
+ }
+
+ pub fn channel(&self) -> mio::channel::Sender<EventLoopMessage> {
+ self.tx.clone()
+ }
- terminal.dirty = true;
+ pub fn spawn(mut self) -> std::thread::JoinHandle<()> {
+ use mio::{Events, PollOpt, Ready};
+ use mio::unix::EventedFd;
+ use std::borrow::Cow;
+ use std::os::unix::io::AsRawFd;
+ use std::io::{Read, Write, ErrorKind};
- // Only wake up the event loop if it hasn't already been signaled. This is a
- // really important optimization because waking up the event loop redundantly
- // burns *a lot* of cycles.
- if !signal_flag.get() {
- proxy.wakeup_event_loop();
- signal_flag.set(true);
+ struct Writing {
+ source: Cow<'static, [u8]>,
+ written: usize,
+ }
+
+ impl Writing {
+ #[inline]
+ fn new(c: Cow<'static, [u8]>) -> Writing {
+ Writing { source: c, written: 0 }
+ }
+
+ #[inline]
+ fn advance(&mut self, n: usize) {
+ self.written += n;
+ }
+
+ #[inline]
+ fn remaining_bytes(&self) -> &[u8] {
+ &self.source[self.written..]
+ }
+
+ #[inline]
+ fn finished(&self) -> bool {
+ self.written >= self.source.len()
+ }
+ }
+
+ thread::spawn_named("pty reader", move || {
+
+ let EventLoop { mut poll, mut pty, rx, terminal, proxy, signal_flag, .. } = self;
+
+
+ let mut buf = [0u8; 4096];
+ let mut pty_parser = ansi::Processor::new();
+ let fd = pty.as_raw_fd();
+ let fd = EventedFd(&fd);
+
+ poll.register(&rx, CHANNEL, Ready::readable(), PollOpt::edge() | PollOpt::oneshot())
+ .unwrap();
+ poll.register(&fd, PTY, Ready::readable(), PollOpt::edge() | PollOpt::oneshot())
+ .unwrap();
+
+ let mut events = Events::with_capacity(1024);
+ let mut write_list = ::std::collections::VecDeque::new();
+ let mut writing = None;
+
+ 'event_loop: loop {
+ poll.poll(&mut events, None).expect("poll ok");
+
+ for event in events.iter() {
+ match event.token() {
+ CHANNEL => {
+ while let Ok(msg) = rx.try_recv() {
+ match msg {
+ EventLoopMessage::Input(input) => {
+ write_list.push_back(input);
+ }
+ }
+ }
+
+ poll.reregister(
+ &rx, CHANNEL,
+ Ready::readable(),
+ PollOpt::edge() | PollOpt::oneshot()
+ ).expect("reregister channel");
+
+ if writing.is_some() || !write_list.is_empty() {
+ poll.reregister(
+ &fd,
+ PTY,
+ Ready::readable() | Ready::writable(),
+ PollOpt::edge() | PollOpt::oneshot()
+ ).expect("reregister fd after channel recv");
+ }
+ },
+ PTY => {
+ let kind = event.kind();
+
+ if kind.is_readable() {
+ loop {
+ match pty.read(&mut buf[..]) {
+ Ok(0) => break,
+ Ok(got) => {
+ let mut terminal = terminal.lock();
+ for byte in &buf[..got] {
+ pty_parser.advance(&mut *terminal, *byte);
+ }
+
+ terminal.dirty = true;
+
+ // Only wake up the event loop if it hasn't already been
+ // signaled. This is a really important optimization
+ // because waking up the event loop redundantly burns *a
+ // lot* of cycles.
+ if !signal_flag.get() {
+ proxy.wakeup_event_loop();
+ signal_flag.set(true);
+ }
+ },
+ Err(err) => {
+ match err.kind() {
+ ErrorKind::WouldBlock => break,
+ _ => panic!("unexpected read err: {:?}", err),
+ }
+ }
+ }
+ }
+ }
+
+ if kind.is_writable() {
+ if writing.is_none() {
+ writing = write_list
+ .pop_front()
+ .map(|c| Writing::new(c));
+ }
+
+ 'write_list_loop: while let Some(mut write_now) = writing.take() {
+ loop {
+ let start = write_now.written;
+ match pty.write(write_now.remaining_bytes()) {
+ Ok(0) => {
+ writing = Some(write_now);
+ break 'write_list_loop;
+ },
+ Ok(n) => {
+ write_now.advance(n);
+ if write_now.finished() {
+ writing = write_list
+ .pop_front()
+ .map(|next| Writing::new(next));
+
+ break;
+ } else {
+ }
+ },
+ Err(err) => {
+ writing = Some(write_now);
+ match err.kind() {
+ ErrorKind::WouldBlock => break 'write_list_loop,
+ // TODO
+ _ => panic!("unexpected err: {:?}", err),
+ }
+ }
+ }
+
+ }
+ }
+ }
+
+ if kind.is_hup() {
+ break 'event_loop;
+ }
+
+ let mut interest = Ready::readable();
+ if writing.is_some() || !write_list.is_empty() {
+ interest.insert(Ready::writable());
+ }
+
+ poll.reregister(&fd, PTY, interest, PollOpt::edge() | PollOpt::oneshot())
+ .expect("register fd after read/write");
+ },
+ _ => (),
}
- } else {
- break;
}
}
diff --git a/src/tty.rs b/src/tty.rs
index accf0226..988db37e 100644
--- a/src/tty.rs
+++ b/src/tty.rs
@@ -274,6 +274,12 @@ pub fn new(rows: u8, cols: u8) -> Tty {
libc::close(slave);
}
+ unsafe {
+ // Maybe this should be done outside of this function so nonblocking
+ // isn't forced upon consumers. Although maybe it should be?
+ set_nonblocking(master);
+ }
+
Tty { fd: master }
}
}
@@ -320,6 +326,13 @@ impl Tty {
}
}
+unsafe fn set_nonblocking(fd: c_int) {
+ use libc::{fcntl, F_SETFL, F_GETFL, O_NONBLOCK};
+
+ let res = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
+ assert_eq!(res, 0);
+}
+
#[test]
fn test_get_pw_entry() {
let mut buf: [i8; 1024] = [0; 1024];