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::VIRTCON_CLIENT_PRIORITY_VALUE;
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 assert!(
156 self.first_view.is_none(),
157 "A view had been created before. virtcon doesn't support multiple displays yet."
158 );
159 self.first_view = Some(view_key);
160
161 let view_assistant = VirtualConsoleViewAssistant::new(
162 &self.app_sender,
163 view_key,
164 self.args.color_scheme,
165 self.args.rounded_corners,
166 self.args.font_size,
167 self.args.dpi.iter().cloned().collect(),
168 )?;
169
170 self.session_manager.set_has_primary_connected(true);
172
173 for (id, (terminal, make_active)) in &self.terminals {
175 let terminal_clone = terminal.try_clone().expect("failed to clone terminal");
176 self.app_sender.queue_message(
177 MessageTarget::View(view_key),
178 make_message(ViewMessages::AddTerminalMessage(*id, terminal_clone, *make_active)),
179 );
180 }
181
182 Ok(view_assistant)
183 }
184
185 fn outgoing_services_names(&self) -> Vec<&'static str> {
186 [SessionManagerMarker::PROTOCOL_NAME].to_vec()
187 }
188
189 fn handle_service_connection_request(
190 &mut self,
191 _service_name: &str,
192 channel: fasync::Channel,
193 ) -> Result<(), Error> {
194 let app_sender = self.app_sender.clone();
195 let color_scheme = self.args.color_scheme;
196 let scrollback_rows = self.args.scrollback_rows;
197 let client = VirtualConsoleClient { app_sender, color_scheme, scrollback_rows };
198 self.session_manager.bind(&client, channel);
199 Ok(())
200 }
201
202 fn filter_config(&mut self, config: &mut Config) {
203 config.view_mode = carnelian::app::ViewMode::Direct;
204 config.client_priority = Some(VIRTCON_CLIENT_PRIORITY_VALUE);
205 config.keyboard_autorepeat = self.args.keyrepeat;
206 config.display_rotation = self.args.display_rotation;
207 config.keymap_name = Some(self.args.keymap.clone());
208 config.buffer_count = Some(self.args.buffer_count);
209 }
210
211 fn handle_message(&mut self, message: carnelian::Message) {
212 if let Some(message) = message.downcast_ref::<AppMessages>() {
213 match message {
214 AppMessages::AddTerminalMessage(id, terminal, make_active) => {
215 let terminal_clone = terminal.try_clone().expect("failed to clone terminal");
216 self.terminals.insert(*id, (terminal_clone, *make_active));
217 if let Some(view_key) = self.first_view {
218 let terminal_clone =
219 terminal.try_clone().expect("failed to clone terminal");
220 self.app_sender.queue_message(
221 MessageTarget::View(view_key),
222 make_message(ViewMessages::AddTerminalMessage(
223 *id,
224 terminal_clone,
225 *make_active,
226 )),
227 );
228 }
229 }
230 AppMessages::RequestTerminalUpdateMessage(id) => {
231 if let Some(view_key) = self.first_view {
232 self.app_sender.queue_message(
233 MessageTarget::View(view_key),
234 make_message(ViewMessages::RequestTerminalUpdateMessage(*id)),
235 );
236 }
237 }
238 }
239 }
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use fuchsia_async as fasync;
247
248 #[fasync::run_singlethreaded(test)]
249 async fn can_create_app() -> Result<(), Error> {
250 let _ = VirtualConsoleAppAssistant::new_for_test()?;
251 Ok(())
252 }
253
254 #[fasync::run_singlethreaded(test)]
255 async fn can_create_virtual_console_view() -> Result<(), Error> {
256 let mut app = VirtualConsoleAppAssistant::new_for_test()?;
257 app.create_view_assistant(Default::default())?;
258 Ok(())
259 }
260
261 #[fasync::run_singlethreaded(test)]
262 async fn can_handle_service_connection_request_without_view() -> Result<(), Error> {
263 let mut app = VirtualConsoleAppAssistant::new_for_test()?;
264 let (_, server_end) = zx::Channel::create();
265 let channel = fasync::Channel::from_channel(server_end);
266 app.handle_service_connection_request(SessionManagerMarker::PROTOCOL_NAME, channel)?;
267 Ok(())
268 }
269}