From 8800491f3b32d050bd79e27d45e1effb84e9a7f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Thu, 14 Jul 2016 15:44:43 +0200 Subject: [PATCH] tty: Handle terminal window resizing A TTY can be automatically resized if a SIGWINCH signal handler is provided. The TtyClient::update_winsize() can manually do the trick. Closes #2 --- Cargo.toml | 2 ++ README.md | 2 -- examples/spawn.rs | 9 +++++-- src/ffi.rs | 2 +- src/lib.rs | 66 +++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 71 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 20b183f..0f9a4be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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.*" diff --git a/README.md b/README.md index 30d3612..07c2cf4 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/examples/spawn.rs b/examples/spawn.rs index 891fd6a..5950a0f 100644 --- a/examples/spawn.rs +++ b/examples/spawn.rs @@ -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 @@ -12,21 +12,26 @@ // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see . +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), }; diff --git a/src/ffi.rs b/src/ffi.rs index bf778af..3dc2648 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -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 diff --git a/src/lib.rs b/src/lib.rs index f0e2969..7e7cd31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; @@ -54,6 +59,8 @@ pub struct TtyClient { termios_orig: Termios, do_flush: Arc, flush_event: Receiver<()>, + // Automatically send an event when dropped + _stop: chan::Sender<()>, } impl TtyServer { @@ -73,9 +80,16 @@ impl TtyServer { } /// Bind the peer TTY with the server TTY - pub fn new_client(&self, peer: T) -> io::Result 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(&self, peer: T, sigwinch_handler: Option>) -> + io::Result 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` @@ -116,12 +130,25 @@ impl AsRef for TtyServer { } } +// Ignore errors +fn copy_winsize(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(master: T, peer: U) -> io::Result - 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(master: T, peer: U, sigwinch_handler: Option>) -> + io::Result 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())); @@ -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, @@ -173,6 +223,7 @@ impl TtyClient { termios_orig: termios_orig, do_flush: do_flush_main, flush_event: event_rx, + _stop: stop_tx, }) } @@ -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 {