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