distant_protocol/common/
pty.rs

1use std::fmt;
2use std::num::ParseIntError;
3use std::str::FromStr;
4
5use derive_more::{Display, Error};
6use serde::{Deserialize, Serialize};
7
8/// Represents the size associated with a remote PTY
9#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
10pub struct PtySize {
11    /// Number of lines of text
12    pub rows: u16,
13
14    /// Number of columns of text
15    pub cols: u16,
16
17    /// Width of a cell in pixels. Note that some systems never fill this value and ignore it.
18    #[serde(default)]
19    pub pixel_width: u16,
20
21    /// Height of a cell in pixels. Note that some systems never fill this value and ignore it.
22    #[serde(default)]
23    pub pixel_height: u16,
24}
25
26impl PtySize {
27    /// Creates new size using just rows and columns
28    pub fn from_rows_and_cols(rows: u16, cols: u16) -> Self {
29        Self {
30            rows,
31            cols,
32            ..Default::default()
33        }
34    }
35}
36
37impl fmt::Display for PtySize {
38    /// Prints out `rows,cols[,pixel_width,pixel_height]` where the
39    /// pixel width and pixel height are only included if either
40    /// one of them is not zero
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        write!(f, "{},{}", self.rows, self.cols)?;
43        if self.pixel_width > 0 || self.pixel_height > 0 {
44            write!(f, ",{},{}", self.pixel_width, self.pixel_height)?;
45        }
46
47        Ok(())
48    }
49}
50
51impl Default for PtySize {
52    fn default() -> Self {
53        PtySize {
54            rows: 24,
55            cols: 80,
56            pixel_width: 0,
57            pixel_height: 0,
58        }
59    }
60}
61
62#[derive(Clone, Debug, PartialEq, Eq, Display, Error)]
63pub enum PtySizeParseError {
64    MissingRows,
65    MissingColumns,
66    InvalidRows(ParseIntError),
67    InvalidColumns(ParseIntError),
68    InvalidPixelWidth(ParseIntError),
69    InvalidPixelHeight(ParseIntError),
70}
71
72impl FromStr for PtySize {
73    type Err = PtySizeParseError;
74
75    /// Attempts to parse a str into PtySize using one of the following formats:
76    ///
77    /// * rows,cols (defaults to 0 for pixel_width & pixel_height)
78    /// * rows,cols,pixel_width,pixel_height
79    fn from_str(s: &str) -> Result<Self, Self::Err> {
80        let mut tokens = s.split(',');
81
82        Ok(Self {
83            rows: tokens
84                .next()
85                .ok_or(PtySizeParseError::MissingRows)?
86                .trim()
87                .parse()
88                .map_err(PtySizeParseError::InvalidRows)?,
89            cols: tokens
90                .next()
91                .ok_or(PtySizeParseError::MissingColumns)?
92                .trim()
93                .parse()
94                .map_err(PtySizeParseError::InvalidColumns)?,
95            pixel_width: tokens
96                .next()
97                .map(|s| s.trim().parse())
98                .transpose()
99                .map_err(PtySizeParseError::InvalidPixelWidth)?
100                .unwrap_or(0),
101            pixel_height: tokens
102                .next()
103                .map(|s| s.trim().parse())
104                .transpose()
105                .map_err(PtySizeParseError::InvalidPixelHeight)?
106                .unwrap_or(0),
107        })
108    }
109}
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn should_be_able_to_serialize_to_json() {
116        let size = PtySize {
117            rows: 10,
118            cols: 20,
119            pixel_width: 30,
120            pixel_height: 40,
121        };
122
123        let value = serde_json::to_value(size).unwrap();
124        assert_eq!(
125            value,
126            serde_json::json!({
127                "rows": 10,
128                "cols": 20,
129                "pixel_width": 30,
130                "pixel_height": 40,
131            })
132        );
133    }
134
135    #[test]
136    fn should_be_able_to_deserialize_minimal_size_from_json() {
137        let value = serde_json::json!({
138            "rows": 10,
139            "cols": 20,
140        });
141
142        let size: PtySize = serde_json::from_value(value).unwrap();
143        assert_eq!(
144            size,
145            PtySize {
146                rows: 10,
147                cols: 20,
148                pixel_width: 0,
149                pixel_height: 0,
150            }
151        );
152    }
153
154    #[test]
155    fn should_be_able_to_deserialize_full_size_from_json() {
156        let value = serde_json::json!({
157            "rows": 10,
158            "cols": 20,
159            "pixel_width": 30,
160            "pixel_height": 40,
161        });
162
163        let size: PtySize = serde_json::from_value(value).unwrap();
164        assert_eq!(
165            size,
166            PtySize {
167                rows: 10,
168                cols: 20,
169                pixel_width: 30,
170                pixel_height: 40,
171            }
172        );
173    }
174
175    #[test]
176    fn should_be_able_to_serialize_to_msgpack() {
177        let size = PtySize {
178            rows: 10,
179            cols: 20,
180            pixel_width: 30,
181            pixel_height: 40,
182        };
183
184        // NOTE: We don't actually check the output here because it's an implementation detail
185        // and could change as we change how serialization is done. This is merely to verify
186        // that we can serialize since there are times when serde fails to serialize at
187        // runtime.
188        let _ = rmp_serde::encode::to_vec_named(&size).unwrap();
189    }
190
191    #[test]
192    fn should_be_able_to_deserialize_minimal_size_from_msgpack() {
193        // NOTE: It may seem odd that we are serializing just to deserialize, but this is to
194        // verify that we are not corrupting or causing issues when serializing on a
195        // client/server and then trying to deserialize on the other side. This has happened
196        // enough times with minor changes that we need tests to verify.
197        #[derive(Serialize)]
198        struct PartialSize {
199            rows: u16,
200            cols: u16,
201        }
202        let buf = rmp_serde::encode::to_vec_named(&PartialSize { rows: 10, cols: 20 }).unwrap();
203
204        let size: PtySize = rmp_serde::decode::from_slice(&buf).unwrap();
205        assert_eq!(
206            size,
207            PtySize {
208                rows: 10,
209                cols: 20,
210                pixel_width: 0,
211                pixel_height: 0,
212            }
213        );
214    }
215
216    #[test]
217    fn should_be_able_to_deserialize_full_size_from_msgpack() {
218        // NOTE: It may seem odd that we are serializing just to deserialize, but this is to
219        // verify that we are not corrupting or causing issues when serializing on a
220        // client/server and then trying to deserialize on the other side. This has happened
221        // enough times with minor changes that we need tests to verify.
222        let buf = rmp_serde::encode::to_vec_named(&PtySize {
223            rows: 10,
224            cols: 20,
225            pixel_width: 30,
226            pixel_height: 40,
227        })
228        .unwrap();
229
230        let size: PtySize = rmp_serde::decode::from_slice(&buf).unwrap();
231        assert_eq!(
232            size,
233            PtySize {
234                rows: 10,
235                cols: 20,
236                pixel_width: 30,
237                pixel_height: 40,
238            }
239        );
240    }
241}