1use crate::args::VirtualConsoleArgs;
6use crate::colors::ColorScheme;
7use crate::log::{Log, LogClient};
8use crate::logo::Logo;
9use crate::session_manager::{SessionManager, SessionManagerClient};
10use crate::terminal::Terminal;
11use crate::view::{ViewMessages, VirtualConsoleViewAssistant};
12use anyhow::Error;
13use carnelian::app::Config;
14use carnelian::{AppAssistant, AppSender, MessageTarget, ViewAssistantPtr, ViewKey, make_message};
15use fidl::prelude::*;
16use fidl_fuchsia_hardware_display::VirtconMode;
17use fidl_fuchsia_virtualconsole::SessionManagerMarker;
18use fuchsia_async as fasync;
19use pty::ServerPty;
20use std::collections::BTreeMap;
21
22const DEBUGLOG_ID: u32 = 0;
23const LOGO_ID: u32 = 1;
24const FIRST_SESSION_ID: u32 = 2;
25
26enum AppMessages {
27 AddTerminalMessage(u32, Terminal, bool),
28 RequestTerminalUpdateMessage(u32),
29}
30
31#[derive(Clone)]
32struct VirtualConsoleClient {
33 app_sender: AppSender,
34 color_scheme: ColorScheme,
35 scrollback_rows: u32,
36}
37
38impl VirtualConsoleClient {
39 fn create_terminal(
40 &self,
41 id: u32,
42 title: String,
43 make_active: bool,
44 pty: Option<ServerPty>,
45 ) -> Result<Terminal, Error> {
46 let terminal = Terminal::new(title, self.color_scheme, self.scrollback_rows, pty);
47 let terminal_clone = terminal.try_clone()?;
48 self.app_sender.queue_message(
49 MessageTarget::Application,
50 make_message(AppMessages::AddTerminalMessage(id, terminal_clone, make_active)),
51 );
52 Ok(terminal)
53 }
54
55 fn request_update(&self, id: u32) {
56 self.app_sender.queue_message(
57 MessageTarget::Application,
58 make_message(AppMessages::RequestTerminalUpdateMessage(id)),
59 );
60 }
61}
62
63impl LogClient for VirtualConsoleClient {
64 fn create_terminal(&self, id: u32, title: String) -> Result<Terminal, Error> {
65 VirtualConsoleClient::create_terminal(self, id, title, true, None)
66 }
67
68 fn request_update(&self, id: u32) {
69 VirtualConsoleClient::request_update(self, id)
70 }
71}
72
73impl SessionManagerClient for VirtualConsoleClient {
74 fn create_terminal(
75 &self,
76 id: u32,
77 title: String,
78 make_active: bool,
79 pty: ServerPty,
80 ) -> Result<Terminal, Error> {
81 VirtualConsoleClient::create_terminal(self, id, title, make_active, Some(pty))
82 }
83
84 fn request_update(&self, id: u32) {
85 VirtualConsoleClient::request_update(self, id)
86 }
87}
88
89pub struct VirtualConsoleAppAssistant {
90 app_sender: AppSender,
91 args: VirtualConsoleArgs,
92 read_only_debuglog: Option<zx::DebugLog>,
93 session_manager: SessionManager,
94 terminals: BTreeMap<u32, (Terminal, bool)>,
95 first_view: Option<ViewKey>,
96}
97
98impl VirtualConsoleAppAssistant {
99 pub fn new(
100 app_sender: &AppSender,
101 args: VirtualConsoleArgs,
102 read_only_debuglog: Option<zx::DebugLog>,
103 ) -> Result<VirtualConsoleAppAssistant, Error> {
104 let app_sender = app_sender.clone();
105 let session_manager =
106 SessionManager::new(args.keep_log_visible || args.show_logo, FIRST_SESSION_ID);
107 let terminals = BTreeMap::new();
108
109 Ok(VirtualConsoleAppAssistant {
110 app_sender,
111 args,
112 read_only_debuglog,
113 session_manager,
114 terminals,
115 first_view: None,
116 })
117 }
118
119 fn start_log(&self, read_only_debuglog: zx::DebugLog) -> Result<(), Error> {
120 let app_sender = self.app_sender.clone();
121 let color_scheme = self.args.color_scheme;
122 let scrollback_rows = self.args.scrollback_rows;
123 let client = VirtualConsoleClient { app_sender, color_scheme, scrollback_rows };
124 Log::start(read_only_debuglog, &client, DEBUGLOG_ID)
125 }
126
127 fn start_logo(&self) {
128 let app_sender = self.app_sender.clone();
129 let color_scheme = self.args.color_scheme;
130 let scrollback_rows = self.args.scrollback_rows;
131 let client = VirtualConsoleClient { app_sender, color_scheme, scrollback_rows };
132 Logo::start(&client, LOGO_ID).unwrap();
133 }
134
135 #[cfg(test)]
136 fn new_for_test() -> Result<VirtualConsoleAppAssistant, Error> {
137 let app_sender = AppSender::new_for_testing_purposes_only();
138 Self::new(&app_sender, VirtualConsoleArgs::default(), None)
139 }
140}
141
142impl AppAssistant for VirtualConsoleAppAssistant {
143 fn setup(&mut self) -> Result<(), Error> {
144 if let Some(read_only_debuglog) = self.read_only_debuglog.take() {
145 self.start_log(read_only_debuglog).expect("failed to start debuglog");
146 }
147 if self.args.show_logo {
148 self.start_logo();
149 }
150
151 Ok(())
152 }
153
154 fn create_view_assistant(&mut self, view_key: ViewKey) -> Result<ViewAssistantPtr, Error> {
155 let is_primary = if self.first_view.is_none() {
157 self.first_view = Some(view_key);
159 true
160 } else {
161 false
162 };
163
164 let view_assistant = VirtualConsoleViewAssistant::new(
165 &self.app_sender,
166 view_key,
167 self.args.color_scheme,
168 self.args.rounded_corners,
169 self.args.font_size,
170 self.args.dpi.iter().cloned().collect(),
171 is_primary,
172 )?;
173
174 if !is_primary {
177 return Ok(view_assistant);
178 }
179
180 self.session_manager.set_has_primary_connected(true);
182
183 for (id, (terminal, make_active)) in &self.terminals {
185 let terminal_clone = terminal.try_clone().expect("failed to clone terminal");
186 self.app_sender.queue_message(
187 MessageTarget::View(view_key),
188 make_message(ViewMessages::AddTerminalMessage(*id, terminal_clone, *make_active)),
189 );
190 }
191
192 Ok(view_assistant)
193 }
194
195 fn outgoing_services_names(&self) -> Vec<&'static str> {
196 [SessionManagerMarker::PROTOCOL_NAME].to_vec()
197 }
198
199 fn handle_service_connection_request(
200 &mut self,
201 _service_name: &str,
202 channel: fasync::Channel,
203 ) -> Result<(), Error> {
204 let app_sender = self.app_sender.clone();
205 let color_scheme = self.args.color_scheme;
206 let scrollback_rows = self.args.scrollback_rows;
207 let client = VirtualConsoleClient { app_sender, color_scheme, scrollback_rows };
208 self.session_manager.bind(&client, channel);
209 Ok(())
210 }
211
212 fn filter_config(&mut self, config: &mut Config) {
213 config.view_mode = carnelian::app::ViewMode::Direct;
214 config.virtcon_mode = Some(VirtconMode::Forced);
215 config.keyboard_autorepeat = self.args.keyrepeat;
216 config.display_rotation = self.args.display_rotation;
217 config.keymap_name = Some(self.args.keymap.clone());
218 config.buffer_count = Some(self.args.buffer_count);
219 }
220
221 fn handle_message(&mut self, message: carnelian::Message) {
222 if let Some(message) = message.downcast_ref::<AppMessages>() {
223 match message {
224 AppMessages::AddTerminalMessage(id, terminal, make_active) => {
225 let terminal_clone = terminal.try_clone().expect("failed to clone terminal");
226 self.terminals.insert(*id, (terminal_clone, *make_active));
227 if let Some(view_key) = self.first_view {
228 let terminal_clone =
229 terminal.try_clone().expect("failed to clone terminal");
230 self.app_sender.queue_message(
231 MessageTarget::View(view_key),
232 make_message(ViewMessages::AddTerminalMessage(
233 *id,
234 terminal_clone,
235 *make_active,
236 )),
237 );
238 }
239 }
240 AppMessages::RequestTerminalUpdateMessage(id) => {
241 if let Some(view_key) = self.first_view {
242 self.app_sender.queue_message(
243 MessageTarget::View(view_key),
244 make_message(ViewMessages::RequestTerminalUpdateMessage(*id)),
245 );
246 }
247 }
248 }
249 }
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256 use fuchsia_async as fasync;
257
258 #[fasync::run_singlethreaded(test)]
259 async fn can_create_app() -> Result<(), Error> {
260 let _ = VirtualConsoleAppAssistant::new_for_test()?;
261 Ok(())
262 }
263
264 #[fasync::run_singlethreaded(test)]
265 async fn can_create_virtual_console_view() -> Result<(), Error> {
266 let mut app = VirtualConsoleAppAssistant::new_for_test()?;
267 app.create_view_assistant(Default::default())?;
268 Ok(())
269 }
270
271 #[fasync::run_singlethreaded(test)]
272 async fn can_handle_service_connection_request_without_view() -> Result<(), Error> {
273 let mut app = VirtualConsoleAppAssistant::new_for_test()?;
274 let (_, server_end) = zx::Channel::create();
275 let channel = fasync::Channel::from_channel(server_end);
276 app.handle_service_connection_request(SessionManagerMarker::PROTOCOL_NAME, channel)?;
277 Ok(())
278 }
279}