1use serde::{Deserialize, Serialize};
10
11use crate::SessionSlot;
12
13pub const TERMINAL_OUTPUT_TAG: u8 = 4;
16
17pub const TERMINAL_INPUT_TAG: u8 = 5;
19
20pub const MAX_COLS: u16 = 500;
22pub const MAX_ROWS: u16 = 300;
23pub const MIN_COLS: u16 = 2;
24pub const MIN_ROWS: u16 = 2;
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(rename_all = "snake_case")]
29pub enum TerminalSource {
30 Exact,
32 Heuristic,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "snake_case")]
39pub enum TerminalInputKind {
40 Password,
41 Confirm,
42 Prompt,
43}
44
45#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
47pub struct TerminalInputExpected {
48 pub kind: TerminalInputKind,
49 pub evidence: String,
50}
51
52#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
54pub struct TerminalExit {
55 pub code: Option<i32>,
56 pub signal: Option<i32>,
57}
58
59#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
61pub struct TerminalState {
62 pub running: bool,
63 pub input_expected: Option<TerminalInputExpected>,
64 pub exit: Option<TerminalExit>,
65 pub source: TerminalSource,
66}
67
68#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
70pub struct TerminalSummary {
71 pub id: u16,
72 pub seat: SessionSlot,
73 pub shell: String,
74 pub cwd: String,
75 pub cols: u16,
76 pub rows: u16,
77 pub alive: bool,
79 pub state: TerminalState,
80 pub title: String,
82 pub held: bool,
84}
85
86#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
89pub struct TerminalHandOff {
90 pub id: uuid::Uuid,
91 pub reason: String,
93 pub terminal: u16,
95 pub secret: bool,
98}
99
100pub type TerminalHandOffAnswer = crate::browser::HandOffAnswer;
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
111 fn the_binary_tags_are_the_next_two() {
112 assert_eq!(TERMINAL_OUTPUT_TAG, 4);
113 assert_eq!(TERMINAL_INPUT_TAG, 5);
114 assert_ne!(TERMINAL_OUTPUT_TAG, TERMINAL_INPUT_TAG);
115 }
116
117 #[test]
120 fn a_hand_off_crosses_with_its_reason_and_secret() {
121 let handoff = TerminalHandOff {
122 id: uuid::Uuid::from_u128(7),
123 reason: "sudo password".to_owned(),
124 terminal: 1,
125 secret: true,
126 };
127 let encoded = serde_json::to_value(&handoff).expect("a hand-off encodes");
128 assert_eq!(
129 encoded,
130 serde_json::json!({
131 "id": "00000000-0000-0000-0000-000000000007",
132 "reason": "sudo password",
133 "terminal": 1,
134 "secret": true,
135 }),
136 );
137 assert_eq!(serde_json::from_value::<TerminalHandOff>(encoded).expect("decodes"), handoff);
138 }
139
140 #[test]
143 fn a_state_crosses_with_its_source() {
144 let state = TerminalState {
145 running: false,
146 input_expected: Some(TerminalInputExpected {
147 kind: TerminalInputKind::Password,
148 evidence: "ECHO off".to_owned(),
149 }),
150 exit: Some(TerminalExit { code: Some(7), signal: None }),
151 source: TerminalSource::Exact,
152 };
153 let encoded = serde_json::to_value(&state).expect("a state encodes");
154 assert_eq!(encoded["source"], "exact");
155 assert_eq!(encoded["input_expected"]["kind"], "password");
156 assert_eq!(encoded["exit"]["code"], 7);
157 assert_eq!(serde_json::from_value::<TerminalState>(encoded).expect("decodes"), state);
158 }
159
160 #[test]
163 fn a_summary_carries_its_seat_and_shell() {
164 let summary = TerminalSummary {
165 id: 1,
166 seat: SessionSlot::worker("O", "p", "w"),
167 shell: "/bin/zsh".to_owned(),
168 cwd: "/tmp".to_owned(),
169 cols: 80,
170 rows: 24,
171 alive: true,
172 state: TerminalState {
173 running: false,
174 input_expected: None,
175 exit: None,
176 source: TerminalSource::Heuristic,
177 },
178 title: String::new(),
179 held: false,
180 };
181 let encoded = serde_json::to_value(&summary).expect("a summary encodes");
182 assert_eq!(encoded["shell"], "/bin/zsh");
183 assert_eq!(encoded["seat"]["label"], "w");
184 assert_eq!(serde_json::from_value::<TerminalSummary>(encoded).expect("decodes"), summary);
185 }
186}