Skip to content

Commit

Permalink
tty: Handle terminal window resizing
Browse files Browse the repository at this point in the history
A TTY can be automatically resized if a SIGWINCH signal handler is
provided. The TtyClient::update_winsize() can manually do the trick.

Closes #2
  • Loading branch information
l0kod committed Oct 16, 2016
1 parent 9a0148d commit 8800491
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 10 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ license = "LGPL-3.0"
exclude = [".gitignore"]

[dependencies]
chan = "0.1"
chan-signal = "0.1.7"
fd = "0.2.2"
libc = "0.2.*"
termios = "0.2.*"
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,3 @@ You need to use a nightly Rust channel >= 1.8.0-dev to build this crate (because

This library is a work in progress.
The API may change.

This library does not yet support signal handling (e.g. terminal resize, Ctrl-C).
9 changes: 7 additions & 2 deletions examples/spawn.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2014 Mickaël Salaün
// Copyright (C) 2014-2016 Mickaël Salaün
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
Expand All @@ -12,21 +12,26 @@
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

extern crate chan_signal;
extern crate libc;
extern crate tty;

use chan_signal::Signal;
use std::process::Command;
use tty::FileDesc;
use tty::TtyServer;

fn main() {
// Get notifications for terminal resizing before any and all other threads!
let signal = chan_signal::notify(&[Signal::WINCH]);

let stdin = FileDesc::new(libc::STDIN_FILENO, false);
let mut server = match TtyServer::new(Some(&stdin)) {
Ok(s) => s,
Err(e) => panic!("Error TTY server: {}", e),
};
println!("Got PTY {}", server.as_ref().display());
let proxy = match server.new_client(stdin) {
let proxy = match server.new_client(stdin, Some(signal)) {
Ok(p) => p,
Err(e) => panic!("Error TTY client: {}", e),
};
Expand Down
2 changes: 1 addition & 1 deletion src/ffi.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2014-2015 Mickaël Salaün
// Copyright (C) 2014-2016 Mickaël Salaün
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
Expand Down
66 changes: 61 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@

#![feature(process_exec)]

#[macro_use]
extern crate chan;

extern crate chan_signal;
extern crate fd;
extern crate libc;
extern crate termios;

use chan_signal::Signal;
use fd::{Pipe, set_flags, splice_loop, unset_append_flag};
use ffi::{get_winsize, openpty};
use ffi::{get_winsize, openpty, set_winsize};
use libc::c_int;
use std::fs::File;
use std::io;
Expand Down Expand Up @@ -54,6 +59,8 @@ pub struct TtyClient {
termios_orig: Termios,
do_flush: Arc<AtomicBool>,
flush_event: Receiver<()>,
// Automatically send an event when dropped
_stop: chan::Sender<()>,
}

impl TtyServer {
Expand All @@ -73,9 +80,16 @@ impl TtyServer {
}

/// Bind the peer TTY with the server TTY
pub fn new_client<T>(&self, peer: T) -> io::Result<TtyClient> where T: AsRawFd + IntoRawFd {
///
/// The sigwinch_handler must handle the SIGWINCH signal to update the TTY window size.
/// This handler can be created with `chan_signal::notify(&[Signal::WINCH])` from the
/// chan_signal crate.
///
/// Any and all threads spawned must come after the first call to chan_signal::notify!
pub fn new_client<T>(&self, peer: T, sigwinch_handler: Option<chan::Receiver<Signal>>) ->
io::Result<TtyClient> where T: AsRawFd + IntoRawFd {
let master = FileDesc::new(self.master.as_raw_fd(), false);
TtyClient::new(master, peer)
TtyClient::new(master, peer, sigwinch_handler)
}

/// Get the TTY master file descriptor usable by a `TtyClient`
Expand Down Expand Up @@ -116,12 +130,25 @@ impl AsRef<Path> for TtyServer {
}
}

// Ignore errors
fn copy_winsize<T, U>(src: &T, dst: &U) where T: AsRawFd, U: AsRawFd {
if let Ok(ws) = get_winsize(src) {
let _ = set_winsize(dst, &ws);
}
}

// TODO: Handle SIGWINCH to dynamically update WinSize
// TODO: Replace `spawn` with `scoped` and share variables
impl TtyClient {
/// Setup the peer TTY client (e.g. stdio) and bind it to the master TTY server
pub fn new<T, U>(master: T, peer: U) -> io::Result<TtyClient>
where T: AsRawFd + IntoRawFd, U: AsRawFd + IntoRawFd {
///
/// The sigwinch_handler must handle the SIGWINCH signal to update the TTY window size.
/// This handler can be created with `chan_signal::notify(&[Signal::WINCH])` from the
/// chan_signal crate.
///
/// Any and all threads spawned must come after the first call to chan_signal::notify!
pub fn new<T, U>(master: T, peer: U, sigwinch_handler: Option<chan::Receiver<Signal>>) ->
io::Result<TtyClient> where T: AsRawFd + IntoRawFd, U: AsRawFd + IntoRawFd {
// Setup peer terminal configuration
let termios_orig = try!(Termios::from_fd(peer.as_raw_fd()));
let mut termios_peer = try!(Termios::from_fd(peer.as_raw_fd()));
Expand Down Expand Up @@ -165,6 +192,29 @@ impl TtyClient {
let master_status = try!(unset_append_flag(master_fd));
thread::spawn(move || splice_loop(do_flush, Some(event_tx), p2m_rx.as_raw_fd(), master_fd));

// Handle terminal resizing
let (stop_tx, stop_rx) = chan::sync(0);
if let Some(signal) = sigwinch_handler {
// master and peer FD will be close by TtyClient::drop()
let master2 = FileDesc::new(master.as_raw_fd(), false);
let peer2 = FileDesc::new(peer.as_raw_fd(), false);
thread::spawn(move || {
'select: loop {
chan_select! {
signal.recv() -> signal => {
if signal != Some(Signal::WINCH) {
continue 'select;
}
copy_winsize(&peer2, &master2);
},
stop_rx.recv() => {
break;
}
}
}
});
}

Ok(TtyClient {
master: FileDesc::new(master.into_raw_fd(), true),
master_status: master_status,
Expand All @@ -173,6 +223,7 @@ impl TtyClient {
termios_orig: termios_orig,
do_flush: do_flush_main,
flush_event: event_rx,
_stop: stop_tx,
})
}

Expand All @@ -182,6 +233,11 @@ impl TtyClient {
let _ = self.flush_event.recv();
}
}

/// Update the terminal window size according to the peer
pub fn update_winsize(&mut self) {
copy_winsize(&self.peer, &self.master);
}
}

impl Drop for TtyClient {
Expand Down

0 comments on commit 8800491

Please sign in to comment.