// Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. // // StratoVirt is licensed under Mulan PSL v2. // You can use this software according to the terms and conditions of the Mulan // PSL v2. // You may obtain a copy of Mulan PSL v2 at: // http://license.coscl.org.cn/MulanPSL2 // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. use std::{ cmp, mem::size_of, ptr, sync::{Arc, Mutex, Weak}, time::Duration, }; use anyhow::Result; use log::error; use once_cell::sync::Lazy; use crate::pixman::{ create_pixman_image, get_image_data, get_image_height, get_image_stride, get_image_width, pixman_glyph_from_vgafont, pixman_glyph_render, unref_pixman_image, ColorNames, COLOR_TABLE_RGB, }; use machine_manager::event_loop::EventLoop; use util::pixman::{pixman_format_code_t, pixman_image_t}; static CONSOLES: Lazy<Arc<Mutex<ConsoleList>>> = Lazy::new(|| Arc::new(Mutex::new(ConsoleList::new()))); static DISPLAY_STATE: Lazy<Arc<Mutex<DisplayState>>> = Lazy::new(|| Arc::new(Mutex::new(DisplayState::new()))); /// Width of font. const FONT_WIDTH: i32 = 8; /// Height of font. const FONT_HEIGHT: i32 = 16; /// Width of image in surface. pub const DEFAULT_SURFACE_WIDTH: i32 = 800; /// Height of image in surface. pub const DEFAULT_SURFACE_HEIGHT: i32 = 600; /// Maximum default window width. pub const MAX_WINDOW_WIDTH: u16 = 2560; /// Maximum default window height. pub const MAX_WINDOW_HEIGHT: u16 = 2048; /// Minimum refresh interval in ms. pub const DISPLAY_UPDATE_INTERVAL_DEFAULT: u64 = 30; /// Update time interval dynamically. pub const DISPLAY_UPDATE_INTERVAL_INC: u64 = 50; /// Maximum refresh interval in ms. pub const DISPLAY_UPDATE_INTERVAL_MAX: u64 = 3_000; pub enum ConsoleType { Graphic, Text, } /// Run stage of virtual machine. #[derive(Clone, Copy, PartialEq, Eq)] pub enum VmRunningStage { Init, Bios, Os, } #[derive(Default)] struct UiInfo { last_width: u32, last_height: u32, } /// Image data defined in display. #[derive(Clone, Copy)] pub struct DisplaySurface { /// Image format. pub format: pixman_format_code_t, /// Pointer to image pub image: *mut pixman_image_t, } impl Default for DisplaySurface { fn default() -> Self { DisplaySurface { format: pixman_format_code_t::PIXMAN_a8r8g8b8, image: ptr::null_mut(), } } } impl DisplaySurface { pub fn width(&self) -> i32 { get_image_width(self.image) } pub fn height(&self) -> i32 { get_image_height(self.image) } pub fn stride(&self) -> i32 { get_image_stride(self.image) } pub fn data(&self) -> *mut u32 { get_image_data(self.image) } } /// Cursor data defined in Display. /// hot_x and hot_y indicate the hotspot of the cursor. /// width and height indicate the width of the cursor in pixel. /// The data consists of the primary and secondary colours for /// the cursor, followed by one bitmap for the colour and /// one bitmask for the transparency. #[derive(Clone, Default)] pub struct DisplayMouse { pub width: u32, pub height: u32, pub hot_x: u32, pub hot_y: u32, pub data: Vec<u8>, } impl DisplayMouse { pub fn new(width: u32, height: u32, hot_x: u32, hot_y: u32) -> Self { let data_size = (width * height) as usize * size_of::<u32>(); DisplayMouse { width, height, hot_x, hot_y, data: vec![0_u8; data_size], } } } /// UIs (such as VNC) can register interfaces related to image display. /// After the graphic hardware processes images, these interfaces can be /// called to display images on the user's desktop. pub trait DisplayChangeListenerOperations { /// Switch the image in display surface. fn dpy_switch(&self, _surface: &DisplaySurface) -> Result<()>; /// Refresh the image. fn dpy_refresh(&self, _dcl: &Arc<Mutex<DisplayChangeListener>>) -> Result<()>; /// Update image. fn dpy_image_update(&self, _x: i32, _y: i32, _w: i32, _h: i32) -> Result<()>; /// Update the cursor data. fn dpy_cursor_update(&self, _cursor: &DisplayMouse) -> Result<()>; /// Set the current display as major. fn dpy_set_major(&self) -> Result<()> { Ok(()) } } /// Callback functions registered by graphic hardware. pub trait HardWareOperations { /// Update image. fn hw_update(&self, _con: Arc<Mutex<DisplayConsole>>) {} /// Ui configuration changed. fn hw_ui_info(&self, _con: Arc<Mutex<DisplayConsole>>, _width: u32, _height: u32) {} } /// Listen to the change of image and call the related /// interface to update the image on user's desktop. pub struct DisplayChangeListener { pub con_id: Option<usize>, pub dcl_id: Option<usize>, pub active: bool, pub update_interval: u64, pub dpy_opts: Arc<dyn DisplayChangeListenerOperations>, } impl DisplayChangeListener { pub fn new(con_id: Option<usize>, dpy_opts: Arc<dyn DisplayChangeListenerOperations>) -> Self { Self { con_id, dcl_id: None, active: false, update_interval: 0, dpy_opts, } } } /// Graphic hardware can register a console during initialization /// and store the information of images in this structure. pub struct DisplayConsole { pub con_id: usize, pub dev_name: String, pub con_type: ConsoleType, pub width: i32, pub height: i32, ui_info: UiInfo, pub surface: Option<DisplaySurface>, pub console_list: Weak<Mutex<ConsoleList>>, pub dev_opts: Arc<dyn HardWareOperations>, pub timer_id: Option<u64>, pub active: bool, } impl DisplayConsole { pub fn new( con_id: usize, dev_name: String, con_type: ConsoleType, console_list: Weak<Mutex<ConsoleList>>, dev_opts: Arc<dyn HardWareOperations>, ) -> Self { Self { con_id, dev_name, con_type, width: 0, height: 0, ui_info: UiInfo::default(), console_list, surface: None, dev_opts, timer_id: None, active: true, } } } /// The state of console layer. struct DisplayState { /// Running stage. run_stage: VmRunningStage, /// Refresh interval, which can be dynamic changed. interval: u64, /// Whether there is a refresh task. is_refresh: bool, /// A list of DisplayChangeListeners. listeners: Vec<Option<Arc<Mutex<DisplayChangeListener>>>>, /// Total number of refresh task. refresh_num: i32, } // SAFETY: The Arc<dyn ...> in rust doesn't impl Send, it will be delivered only once during // initialization process, and only be saved in the single thread. So implement Send is safe. unsafe impl Send for DisplayState {} impl DisplayState { fn new() -> Self { Self { run_stage: VmRunningStage::Init, interval: DISPLAY_UPDATE_INTERVAL_DEFAULT, is_refresh: false, listeners: Vec::new(), refresh_num: 0, } } // Get all related display by con_id. fn get_related_display(&self, con_id: usize) -> Result<Vec<Arc<Mutex<DisplayChangeListener>>>> { let mut related_dpys: Vec<Arc<Mutex<DisplayChangeListener>>> = vec![]; let active_id = CONSOLES.lock().unwrap().activate_id; for dcl in self.listeners.iter().flatten() { match dcl.lock().unwrap().con_id { Some(id) if con_id == id => { related_dpys.push(dcl.clone()); } None if Some(con_id) == active_id => { related_dpys.push(dcl.clone()); } _ => {} } } Ok(related_dpys) } } /// The registered console will be inserted in the console list. /// If no console is specified, the activate console will be used. pub struct ConsoleList { pub activate_id: Option<usize>, pub console_list: Vec<Option<Arc<Mutex<DisplayConsole>>>>, } // SAFETY: // 1. The raw pointer in rust doesn't impl Send, the target thread can only read the memory of image // by this pointer. // 2. The Arc<dyn ...> in rust doesn't impl Send, it will be delivered only once during // initialization process, // and only be saved in the single thread. // So implement Send is safe. unsafe impl Send for ConsoleList {} impl ConsoleList { fn new() -> Self { Self { activate_id: None, console_list: Vec::new(), } } // Get console by device name. fn get_console_by_dev_name(&mut self, dev_name: String) -> Option<Arc<Mutex<DisplayConsole>>> { let mut target: Option<Arc<Mutex<DisplayConsole>>> = None; for con in self.console_list.iter().flatten() { let locked_con = con.lock().unwrap(); if locked_con.dev_name == dev_name { target = Some(con.clone()); break; } } target } /// Get the console by id. fn get_console_by_id(&mut self, con_id: Option<usize>) -> Option<Arc<Mutex<DisplayConsole>>> { if con_id.is_none() && self.activate_id.is_none() { return None; } let mut target_id: usize = 0; if let Some(id) = con_id { target_id = id; } else if let Some(id) = self.activate_id { target_id = id; } self.console_list.get(target_id)?.clone() } } /// Set currently running stage for virtual machine. pub fn set_run_stage(run_stage: VmRunningStage) { DISPLAY_STATE.lock().unwrap().run_stage = run_stage } /// Get currently running stage. pub fn get_run_stage() -> VmRunningStage { DISPLAY_STATE.lock().unwrap().run_stage } /// Refresh display image. fn display_refresh() { let mut dcl_interval: u64; let mut interval: u64 = DISPLAY_UPDATE_INTERVAL_MAX; let mut locked_state = DISPLAY_STATE.lock().unwrap(); let mut related_listeners: Vec<Arc<Mutex<DisplayChangeListener>>> = vec![]; for dcl in &mut locked_state.listeners.iter_mut().flatten() { related_listeners.push(dcl.clone()); } drop(locked_state); for dcl in &mut related_listeners.iter() { let dcl_opts = dcl.lock().unwrap().dpy_opts.clone(); if let Err(e) = (*dcl_opts).dpy_refresh(dcl) { error!("{:?}", e); return; } // Update refresh interval. dcl_interval = dcl.lock().unwrap().update_interval; if dcl_interval == 0 { dcl_interval = DISPLAY_UPDATE_INTERVAL_MAX; } if interval > dcl_interval { interval = dcl_interval } } trace::console_dpy_refresh(&interval); let mut locked_state = DISPLAY_STATE.lock().unwrap(); locked_state.interval = interval; if locked_state.interval != 0 { locked_state.is_refresh = true; setup_refresh(interval); } } /// Register the timer to execute the scheduled /// refresh task. fn setup_refresh(update_interval: u64) { let func = Box::new(move || { display_refresh(); }); if update_interval != 0 { EventLoop::get_ctx(None) .unwrap() .timer_add(func, Duration::from_millis(update_interval)); } } /// Switch the image of surface in display. pub fn display_replace_surface( console: &Option<Weak<Mutex<DisplayConsole>>>, surface: Option<DisplaySurface>, ) -> Result<()> { let con = match console.as_ref().and_then(|c| c.upgrade()) { Some(c) => c, None => return Ok(()), }; let mut locked_con = con.lock().unwrap(); let old_surface = locked_con.surface; if surface.is_none() { // Create a place holder message. locked_con.surface = create_msg_surface( DEFAULT_SURFACE_WIDTH, DEFAULT_SURFACE_HEIGHT, "Display is not active.".to_string(), ); } else { locked_con.surface = surface; } if let Some(s) = locked_con.surface { locked_con.width = get_image_width(s.image); locked_con.height = get_image_height(s.image); } let con_id = locked_con.con_id; if let Some(s) = old_surface { unref_pixman_image(s.image); } drop(locked_con); let related_listeners = DISPLAY_STATE.lock().unwrap().get_related_display(con_id)?; for dcl in related_listeners.iter() { let dcl_opts = dcl.lock().unwrap().dpy_opts.clone(); if let Some(s) = &con.lock().unwrap().surface.clone() { (*dcl_opts).dpy_switch(s)?; } } Ok(()) } /// Update area of the image. /// `x` `y` `w` `h` marke the area of image. pub fn display_graphic_update( console: &Option<Weak<Mutex<DisplayConsole>>>, x: i32, y: i32, w: i32, h: i32, ) -> Result<()> { let con = match console.as_ref().and_then(|c| c.upgrade()) { Some(c) => c, None => return Ok(()), }; let mut width: i32 = w; let mut height: i32 = h; let locked_con = con.lock().unwrap(); if let Some(s) = locked_con.surface { width = get_image_width(s.image); height = get_image_height(s.image); } let mut x = cmp::max(x, 0); let mut y = cmp::max(y, 0); x = cmp::min(x, width); y = cmp::min(y, height); let w = cmp::min(w, width - x); let h = cmp::min(h, height - y); let con_id = locked_con.con_id; drop(locked_con); let related_listeners = DISPLAY_STATE.lock().unwrap().get_related_display(con_id)?; for dcl in related_listeners.iter() { let dcl_opts = dcl.lock().unwrap().dpy_opts.clone(); (*dcl_opts).dpy_image_update(x, y, w, h)?; } Ok(()) } /// Update cursor data in display. /// /// # Arguments /// /// * `con_id` - console id in console list. /// * `cursor` - data of curosr image. pub fn display_cursor_define( console: &Option<Weak<Mutex<DisplayConsole>>>, cursor: &DisplayMouse, ) -> Result<()> { let con = match console.as_ref().and_then(|c| c.upgrade()) { Some(c) => c, None => return Ok(()), }; let con_id = con.lock().unwrap().con_id; let related_listeners = DISPLAY_STATE.lock().unwrap().get_related_display(con_id)?; for dcl in related_listeners.iter() { let dcl_opts = dcl.lock().unwrap().dpy_opts.clone(); (*dcl_opts).dpy_cursor_update(cursor)?; } Ok(()) } /// Set specific screen as the main display screen. pub fn display_set_major_screen(dev_name: &str) -> Result<()> { let con = match CONSOLES .lock() .unwrap() .get_console_by_dev_name(dev_name.to_string()) { Some(c) => c, None => return Ok(()), }; let con_id = con.lock().unwrap().con_id; console_select(Some(con_id))?; let related_listeners = DISPLAY_STATE.lock().unwrap().get_related_display(con_id)?; for dcl in related_listeners.iter() { let dcl_opts = dcl.lock().unwrap().dpy_opts.clone(); (*dcl_opts).dpy_set_major()?; } Ok(()) } pub fn graphic_hardware_update(con_id: Option<usize>) { let console = CONSOLES.lock().unwrap().get_console_by_id(con_id); if let Some(con) = console { let con_opts = con.lock().unwrap().dev_opts.clone(); (*con_opts).hw_update(con); } } pub fn graphic_hardware_ui_info( con: Arc<Mutex<DisplayConsole>>, width: u32, height: u32, ) -> Result<()> { let mut locked_con = con.lock().unwrap(); trace::console_dpy_ui_info( &locked_con.dev_name, &width, &height, &locked_con.ui_info.last_width, &locked_con.ui_info.last_height, ); if locked_con.ui_info.last_width == width && locked_con.ui_info.last_height == height { return Ok(()); } locked_con.ui_info.last_width = width; locked_con.ui_info.last_height = height; let clone_con = con.clone(); let con_opts = locked_con.dev_opts.clone(); let func = Box::new(move || { (*con_opts).hw_ui_info(clone_con.clone(), width, height); }); let ctx = EventLoop::get_ctx(None).unwrap(); if let Some(timer_id) = locked_con.timer_id { ctx.timer_del(timer_id); } locked_con.timer_id = Some(ctx.timer_add(func, Duration::from_millis(500))); Ok(()) } /// Get the weak reference of all active consoles from the console lists. pub fn get_active_console() -> Vec<Weak<Mutex<DisplayConsole>>> { let mut res: Vec<Weak<Mutex<DisplayConsole>>> = vec![]; let locked_cons = CONSOLES.lock().unwrap(); for con in locked_cons.console_list.iter().flatten() { if con.lock().unwrap().active { res.push(Arc::downgrade(con)); } } res } /// Register a dcl and return the id. pub fn register_display(dcl: &Arc<Mutex<DisplayChangeListener>>) -> Result<()> { let mut dcl_id = 0; let mut locked_state = DISPLAY_STATE.lock().unwrap(); let len = locked_state.listeners.len(); for dcl in &mut locked_state.listeners.iter() { if dcl.is_none() { break; } dcl_id += 1; } if dcl_id < len { locked_state.listeners[dcl_id] = Some(dcl.clone()); } else { locked_state.listeners.push(Some(dcl.clone())); } locked_state.refresh_num += 1; // Register the clock and execute the scheduled refresh event. if !locked_state.is_refresh && locked_state.interval != 0 { locked_state.is_refresh = true; setup_refresh(locked_state.interval); } drop(locked_state); dcl.lock().unwrap().dcl_id = Some(dcl_id); let dcl_opts = dcl.lock().unwrap().dpy_opts.clone(); let con_id = dcl.lock().unwrap().con_id; let console = CONSOLES.lock().unwrap().get_console_by_id(con_id); if let Some(con) = console { if let Some(surface) = &mut con.lock().unwrap().surface.clone() { (*dcl_opts).dpy_switch(surface)?; } } else { let mut place_holder_image = create_msg_surface( DEFAULT_SURFACE_WIDTH, DEFAULT_SURFACE_HEIGHT, "This VM has no graphic display device.".to_string(), ); if let Some(surface) = &mut place_holder_image { (*dcl_opts).dpy_switch(surface)?; } } Ok(()) } /// Unregister display change listener. pub fn unregister_display(dcl: &Option<Weak<Mutex<DisplayChangeListener>>>) -> Result<()> { let dcl = match dcl.as_ref().and_then(|d| d.upgrade()) { Some(d) => d, None => return Ok(()), }; let dcl_id = dcl.lock().unwrap().dcl_id; let mut locked_state = DISPLAY_STATE.lock().unwrap(); let len = locked_state.listeners.len(); let id = dcl_id.unwrap_or(len); if id >= len { return Ok(()); } locked_state.listeners[id] = None; // Stop refreshing if the current refreshing num is 0 locked_state.refresh_num -= 1; if locked_state.refresh_num <= 0 { locked_state.is_refresh = false; } drop(locked_state); Ok(()) } /// Create a console and add into a global list. Then returen a console id /// for later finding the assigned console. pub fn console_init( dev_name: String, con_type: ConsoleType, dev_opts: Arc<dyn HardWareOperations>, ) -> Option<Weak<Mutex<DisplayConsole>>> { let mut locked_consoles = CONSOLES.lock().unwrap(); for con in locked_consoles.console_list.iter().flatten() { let mut locked_con = con.lock().unwrap(); if locked_con.dev_name == dev_name { locked_con.active = true; locked_con.dev_opts = dev_opts; return Some(Arc::downgrade(con)); } } let con_id = locked_consoles.console_list.len(); let new_con = DisplayConsole::new( con_id, dev_name, con_type, Arc::downgrade(&CONSOLES), dev_opts, ); let con = Arc::new(Mutex::new(new_con)); locked_consoles.console_list.push(Some(con.clone())); if locked_consoles.activate_id.is_none() { locked_consoles.activate_id = Some(con_id); } drop(locked_consoles); let con = Arc::downgrade(&con); let surface = create_msg_surface( DEFAULT_SURFACE_WIDTH, DEFAULT_SURFACE_HEIGHT, "Guest has not initialized the display yet.".to_string(), ); display_replace_surface(&Some(con.clone()), surface) .unwrap_or_else(|e| error!("Error occurs during surface switching: {:?}", e)); set_run_stage(VmRunningStage::Bios); Some(con) } /// Close a console. pub fn console_close(console: &Option<Weak<Mutex<DisplayConsole>>>) -> Result<()> { let con = match console.as_ref().and_then(|c| c.upgrade()) { Some(c) => c, None => return Ok(()), }; let mut locked_con = con.lock().unwrap(); if let Some(surface) = locked_con.surface { unref_pixman_image(surface.image); } locked_con.active = false; locked_con.surface = create_msg_surface( DEFAULT_SURFACE_WIDTH, DEFAULT_SURFACE_HEIGHT, "Display is not active.".to_string(), ); let con_id = locked_con.con_id; drop(locked_con); // If the active console is closed, reset the active console. let mut locked_consoles = CONSOLES.lock().unwrap(); match locked_consoles.activate_id { Some(active_con) if active_con == con_id => { let mut active_id: Option<usize> = None; for con in locked_consoles.console_list.iter().flatten() { let locked_con = con.lock().unwrap(); if locked_con.active { active_id = Some(locked_con.con_id); break; } } locked_consoles.activate_id = active_id; } _ => {} } Ok(()) } /// Select the default display device. /// If con_id is none, then do nothing. pub fn console_select(con_id: Option<usize>) -> Result<()> { trace::console_select(&con_id); let mut locked_consoles = CONSOLES.lock().unwrap(); if locked_consoles.activate_id == con_id { return Ok(()); } let activate_console: Option<Arc<Mutex<DisplayConsole>>> = match con_id { Some(id) if locked_consoles.console_list.get(id).is_some() => { locked_consoles.activate_id = Some(id); locked_consoles.console_list[id].clone() } _ => return Ok(()), }; drop(locked_consoles); let mut related_listeners: Vec<Arc<Mutex<DisplayChangeListener>>> = vec![]; let mut locked_state = DISPLAY_STATE.lock().unwrap(); for dcl in locked_state.listeners.iter_mut().flatten() { if dcl.lock().unwrap().con_id.is_some() { continue; } related_listeners.push(dcl.clone()); } drop(locked_state); let con = match activate_console { Some(c) => c, None => return Ok(()), }; let width = con.lock().unwrap().width; let height = con.lock().unwrap().height; for dcl in related_listeners { let dpy_opts = dcl.lock().unwrap().dpy_opts.clone(); if let Some(s) = &mut con.lock().unwrap().surface { (*dpy_opts).dpy_switch(s)?; } } display_graphic_update(&Some(Arc::downgrade(&con)), 0, 0, width, height) } /// Create a default image to display messages. /// /// # Arguments /// /// * `width` - width of image. /// * `height` - height of image. /// * `msg` - test messages showed in display. pub fn create_msg_surface(width: i32, height: i32, msg: String) -> Option<DisplaySurface> { if !(0..MAX_WINDOW_WIDTH as i32).contains(&width) || !(0..MAX_WINDOW_HEIGHT as i32).contains(&height) { error!("The size of image is invalid!"); return None; } let mut surface = DisplaySurface::default(); // One pixel occupies four bytes. surface.image = create_pixman_image(surface.format, width, height, ptr::null_mut(), width * 4); if surface.image.is_null() { error!("create default surface failed!"); return None; } let fg = COLOR_TABLE_RGB[0][ColorNames::ColorWhite as usize]; let bg = COLOR_TABLE_RGB[0][ColorNames::ColorBlack as usize]; let x = (width / FONT_WIDTH - msg.len() as i32) / 2; let y = (height / FONT_HEIGHT - 1) / 2; for (index, ch) in msg.chars().enumerate() { let glyph = pixman_glyph_from_vgafont(FONT_HEIGHT as u32, ch as u32); if glyph.is_null() { continue; } pixman_glyph_render( glyph, surface.image, &fg, &bg, (x + index as i32, y), FONT_WIDTH, FONT_HEIGHT, ); unref_pixman_image(glyph); } Some(surface) } #[cfg(test)] mod tests { use super::*; use machine_manager::config::VmConfig; pub struct DclOpts {} impl DisplayChangeListenerOperations for DclOpts { fn dpy_switch(&self, _surface: &DisplaySurface) -> Result<()> { Ok(()) } fn dpy_refresh(&self, _dcl: &Arc<Mutex<DisplayChangeListener>>) -> Result<()> { Ok(()) } fn dpy_image_update(&self, _x: i32, _y: i32, _w: i32, _h: i32) -> Result<()> { Ok(()) } fn dpy_cursor_update(&self, _cursor: &DisplayMouse) -> Result<()> { Ok(()) } } struct HwOpts {} impl HardWareOperations for HwOpts {} #[test] fn test_console_select() { let con_opts = Arc::new(HwOpts {}); let dev_name0 = format!("test_device0"); let con_0 = console_init(dev_name0, ConsoleType::Graphic, con_opts.clone()); let clone_con = con_0.clone(); assert_eq!( clone_con.unwrap().upgrade().unwrap().lock().unwrap().con_id, 0 ); let dev_name1 = format!("test_device1"); let con_1 = console_init(dev_name1, ConsoleType::Graphic, con_opts.clone()); assert_eq!(con_1.unwrap().upgrade().unwrap().lock().unwrap().con_id, 1); let dev_name2 = format!("test_device2"); let con_2 = console_init(dev_name2, ConsoleType::Graphic, con_opts.clone()); assert_eq!(con_2.unwrap().upgrade().unwrap().lock().unwrap().con_id, 2); assert!(console_close(&con_0).is_ok()); assert_eq!(CONSOLES.lock().unwrap().activate_id, Some(1)); let dev_name3 = format!("test_device3"); let con_3 = console_init(dev_name3, ConsoleType::Graphic, con_opts.clone()); assert_eq!(con_3.unwrap().upgrade().unwrap().lock().unwrap().con_id, 3); assert!(console_select(Some(0)).is_ok()); assert_eq!(CONSOLES.lock().unwrap().activate_id, Some(0)); assert!(console_select(Some(1)).is_ok()); assert_eq!(CONSOLES.lock().unwrap().activate_id, Some(1)); assert!(console_select(Some(2)).is_ok()); assert_eq!(CONSOLES.lock().unwrap().activate_id, Some(2)); assert!(console_select(Some(3)).is_ok()); assert_eq!(CONSOLES.lock().unwrap().activate_id, Some(3)); assert!(console_select(None).is_ok()); assert_eq!(CONSOLES.lock().unwrap().activate_id, Some(3)); } #[test] fn test_register_display() { let vm_config = VmConfig::default(); assert!(EventLoop::object_init(&vm_config.iothreads).is_ok()); let dcl_opts = Arc::new(DclOpts {}); let dcl_0 = Arc::new(Mutex::new(DisplayChangeListener::new( None, dcl_opts.clone(), ))); let dcl_1 = Arc::new(Mutex::new(DisplayChangeListener::new( None, dcl_opts.clone(), ))); let dcl_2 = Arc::new(Mutex::new(DisplayChangeListener::new( None, dcl_opts.clone(), ))); let dcl_3 = Arc::new(Mutex::new(DisplayChangeListener::new( None, dcl_opts.clone(), ))); assert!(register_display(&dcl_0).is_ok()); assert_eq!(dcl_0.lock().unwrap().dcl_id, Some(0)); assert!(register_display(&dcl_1).is_ok()); assert_eq!(dcl_1.lock().unwrap().dcl_id, Some(1)); assert!(register_display(&dcl_2).is_ok()); assert_eq!(dcl_2.lock().unwrap().dcl_id, Some(2)); assert!(unregister_display(&Some(Arc::downgrade(&dcl_0))).is_ok()); assert!(register_display(&dcl_3).is_ok()); assert_eq!(dcl_3.lock().unwrap().dcl_id, Some(0)); } }