Skip to main content

virtual_console_lib/
app.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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        // The first view created will take the role as primary output.
156        let is_primary = if self.first_view.is_none() {
157            // Terminal messages will be routed to this view from this point forward.
158            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        // Early out if terminals are already associated with a view.
175        // TODO(reveman): Improve this when we have multi-display support.
176        if !is_primary {
177            return Ok(view_assistant);
178        }
179
180        // Primary display has connected when this is called.
181        self.session_manager.set_has_primary_connected(true);
182
183        // Add all terminals to this view.
184        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}