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::{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        // The first view created will take the role as primary output.
188        let is_primary = if self.first_view.is_none() {
189            // Terminal messages will be routed to this view from this point forward.
190            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        // Early out if terminals are already associated with a view.
208        // TODO(reveman): Improve this when we have multi-display support.
209        if !is_primary {
210            return Ok(view_assistant);
211        }
212
213        // Primary display has connected when this is called.
214        self.session_manager.set_has_primary_connected(true);
215
216        // Add all terminals to this view.
217        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}