tokio_terminal_resize/
lib.rs1#![warn(clippy::pedantic)]
27#![warn(clippy::nursery)]
28#![allow(clippy::multiple_crate_versions)]
29
30use futures::future::Future as _;
31use futures::stream::Stream as _;
32use snafu::futures01::FutureExt as _;
33use snafu::futures01::StreamExt as _;
34use snafu::ResultExt as _;
35use std::convert::TryInto as _;
36
37#[derive(Debug, snafu::Snafu)]
39pub enum Error {
40 #[snafu(display("failed to get terminal size"))]
42 GetTerminalSize,
43
44 #[snafu(display("invalid terminal size found: {}", source))]
46 InvalidTerminalSize { source: std::num::TryFromIntError },
47
48 #[snafu(display("SIGWINCH handler failed: {}", source))]
50 SigWinchHandler { source: std::io::Error },
51}
52
53pub fn resizes() -> ResizeFuture {
56 ResizeFuture::default()
57}
58
59pub struct ResizeFuture {
61 stream_fut: Box<
62 dyn futures::future::Future<Item = ResizeStream, Error = Error>
63 + Send,
64 >,
65}
66
67impl Default for ResizeFuture {
68 fn default() -> Self {
69 let stream_fut = tokio_signal::unix::Signal::new(
70 tokio_signal::unix::libc::SIGWINCH,
71 )
72 .context(SigWinchHandler)
73 .and_then(|stream| {
74 futures::future::ok(ResizeStream {
75 winches: Box::new(
76 stream.map(|_| ()).context(SigWinchHandler),
77 ),
78 sent_initial_size: false,
79 })
80 });
81 Self {
82 stream_fut: Box::new(stream_fut),
83 }
84 }
85}
86
87#[must_use = "streams do nothing unless polled"]
88impl futures::future::Future for ResizeFuture {
89 type Item = ResizeStream;
90 type Error = Error;
91
92 fn poll(&mut self) -> futures::Poll<Self::Item, Self::Error> {
93 self.stream_fut.poll()
94 }
95}
96
97pub struct ResizeStream {
99 winches:
100 Box<dyn futures::stream::Stream<Item = (), Error = Error> + Send>,
101 sent_initial_size: bool,
102}
103
104#[must_use = "streams do nothing unless polled"]
105impl futures::stream::Stream for ResizeStream {
106 type Item = (u16, u16);
107 type Error = Error;
108
109 fn poll(&mut self) -> futures::Poll<Option<Self::Item>, Self::Error> {
110 if !self.sent_initial_size {
111 self.sent_initial_size = true;
112 return Ok(futures::Async::Ready(Some(term_size()?)));
113 }
114 futures::try_ready!(self.winches.poll());
115 Ok(futures::Async::Ready(Some(term_size()?)))
116 }
117}
118
119fn term_size() -> Result<(u16, u16), Error> {
120 if let Some((cols, rows)) = term_size::dimensions() {
121 Ok((
122 rows.try_into().context(InvalidTerminalSize)?,
123 cols.try_into().context(InvalidTerminalSize)?,
124 ))
125 } else {
126 Err(Error::GetTerminalSize)
127 }
128}