// needed since RLS won't accept #[repr(C)] #![allow(improper_ctypes)] use anyhow::Error; use sdl2; use sdl2::Sdl; use sdl2::clipboard::ClipboardUtil; use sdl2::messagebox::{MessageBoxFlag, show_simple_message_box}; use sdl2::mouse::Cursor; use sdl2::surface::Surface as SDL_Surface; use sdl2::sys::SDL_Window; use sdl2::video::{FullscreenType, WindowPos}; use crate::Result; use vulkan_rs::prelude::*; use std::mem::MaybeUninit; use std::ops::Deref; use std::path::Path; use std::sync::{ Arc, Mutex, atomic::{AtomicI32, AtomicU32, Ordering::SeqCst}, }; const SDL_SYSWM_WINDOWS: u32 = 0x1; const SDL_SYSWM_X11: u32 = 0x2; const SDL_SYSWM_COCOA: u32 = 0x4; const SDL_SYSWM_WAYLAND: u32 = 0x6; const SDL_SYSWM_ANDROID: u32 = 0x9; #[repr(C)] struct SdlSysWmInfo { version: sdl2::version::Version, subsystem: u32, info: [u64; 32], } unsafe extern "C" { fn SDL_GetWindowWMInfo(window: *const sdl2::sys::SDL_Window, info: *mut SdlSysWmInfo) -> bool; } #[derive(Default, Debug, Clone)] pub struct WindowCreateInfo { pub title: String, pub width: u32, pub height: u32, pub fullscreen: bool, pub requested_display: Option, } #[derive(Debug)] pub struct Display { pub name: String, // bounds pub x: i32, pub y: i32, pub w: u32, pub h: u32, pub dpi: [f32; 3], } #[derive(Default, Debug)] struct CellRect { x: AtomicI32, y: AtomicI32, width: AtomicU32, height: AtomicU32, } impl CellRect { fn update_from_window(&self, window: &sdl2::video::Window) { let (w, h) = window.size(); let (x, y) = window.position(); self.x.store(x, SeqCst); self.y.store(y, SeqCst); self.width.store(w, SeqCst); self.height.store(h, SeqCst); } fn update_to_window(&self, window: &mut sdl2::video::Window) -> Result<()> { set_window_size(window, self.width.load(SeqCst), self.height.load(SeqCst))?; window.set_position( WindowPos::Positioned(self.x.load(SeqCst)), WindowPos::Positioned(self.y.load(SeqCst)), ); Ok(()) } } pub struct WindowSystemIntegration { // sdl _video_subsystem: Mutex, window: Mutex, clipboard_util: ClipboardUtil, cursor: Mutex>, displays: Vec, _enabled_display_index: usize, pre_fullscreen_rect: CellRect, surface: Mutex>>, } unsafe impl Send for WindowSystemIntegration {} unsafe impl Sync for WindowSystemIntegration {} impl WindowSystemIntegration { pub(crate) fn new<'a>( create_info: &WindowCreateInfo, context: &Sdl, ) -> Result> { // create video subsystem let video_subsystem = context.video().map_err(|s| anyhow::Error::msg(s))?; // query display count let display_count = match video_subsystem.num_video_displays() { Ok(num_displays) => num_displays, Err(_) => 0, }; if display_count == 0 { return Err(anyhow::Error::msg("No display where detected!")); } // query display information let mut displays = Vec::with_capacity(display_count as usize); for i in 0..display_count { let rect = video_subsystem .display_bounds(i) .map_err(|s| anyhow::Error::msg(s))?; let name = video_subsystem .display_name(i) .map_err(|s| anyhow::Error::msg(s))?; let (dpi0, dpi1, dpi2) = match video_subsystem.display_dpi(i) { Ok(dpis) => dpis, Err(msg) => { println!("failed getting dpi for display {} ({}): {}", i, name, msg); (0.0, 0.0, 0.0) } }; let display = Display { name, x: rect.x(), y: rect.y(), w: rect.width(), h: rect.height(), dpi: [dpi0, dpi1, dpi2], }; displays.push(display); } // check if there is an preferred display let mut display_index = None; if let Some(requested_display) = &create_info.requested_display { match displays .iter() .position(|display| display.name == *requested_display) { Some(index) => display_index = Some(index), None => { println!("could not find display: {}", requested_display); println!("defaulting to display 0 ({})", displays[0].name); } } } // build window let mut window_builder = video_subsystem.window(&create_info.title, create_info.width, create_info.height); window_builder.resizable().vulkan(); match display_index { Some(index) => { let display = &displays[index]; window_builder.position( display.x + display.w as i32 / 2, display.y + display.h as i32 / 2, ); } None => { window_builder.position_centered(); } } let window = window_builder.build()?; display_index = Some(window.display_index().map_err(|s| anyhow::Error::msg(s))? as usize); let rect = CellRect::default(); rect.update_from_window(&window); let wsi = WindowSystemIntegration { clipboard_util: video_subsystem.clipboard(), _video_subsystem: Mutex::new(video_subsystem), window: Mutex::new(window), cursor: Mutex::new(None), displays, _enabled_display_index: display_index.expect("display index not set"), pre_fullscreen_rect: rect, surface: Mutex::new(None), }; if create_info.fullscreen { wsi.set_fullscreen(true)?; } Ok(Arc::new(wsi)) } pub fn window_size(&self) -> (u32, u32) { self.window.lock().unwrap().size() } pub fn is_fullscreen(&self) -> bool { match self.window.lock().unwrap().fullscreen_state() { FullscreenType::Desktop => false, FullscreenType::True => true, FullscreenType::Off => false, } } pub fn set_fullscreen(&self, fullscreen: bool) -> Result<()> { let mut window = self.window.lock().unwrap(); if fullscreen { // store window information self.pre_fullscreen_rect.update_from_window(&window); // set fullscreen size to fit display let display = &self.displays[window.display_index().map_err(|s| anyhow::Error::msg(s))? as usize]; set_window_size(&mut window, display.w, display.h)?; window.set_bordered(false); // change fullscreen mode window .set_fullscreen(FullscreenType::True) .map_err(|s| anyhow::Error::msg(s))?; } else { // change fullscreen mode window .set_fullscreen(FullscreenType::Off) .map_err(|s| anyhow::Error::msg(s))?; // force window borders window.set_bordered(true); // update window values self.pre_fullscreen_rect.update_to_window(&mut window)?; } Ok(()) } pub fn show_simple_info_box(&self, title: &str, message: &str) -> Result<()> { self.show_simple_message_box(MessageBoxFlag::INFORMATION, title, message) } pub fn show_simple_warning_box(&self, title: &str, message: &str) -> Result<()> { self.show_simple_message_box(MessageBoxFlag::WARNING, title, message) } pub fn show_simple_error_box(&self, title: &str, message: &str) -> Result<()> { self.show_simple_message_box(MessageBoxFlag::ERROR, title, message) } pub fn clipboard_content(&self) -> Result> { Ok(if self.clipboard_util.has_clipboard_text() { Some( self.clipboard_util .clipboard_text() .map_err(|err| Error::msg(err))?, ) } else { None }) } #[inline] fn show_simple_message_box( &self, flags: MessageBoxFlag, title: &str, message: &str, ) -> Result<()> { let window = self.window.lock().unwrap(); show_simple_message_box(flags, title, message, Some(window.deref()))?; Ok(()) } pub fn set_opacity(&self, opacity: f32) -> Result<()> { self.window .lock() .unwrap() .set_opacity(opacity) .map_err(|s| anyhow::Error::msg(s))?; Ok(()) } pub fn set_icon>(&self, path: T) -> Result<()> { // let texture = match image::open(path) { // Ok(tex) => tex.to_rgba(), // Err(err) => create_error!(format!("error loading image: {}", err)), // }; // let (width, height) = texture.dimensions(); // let mut texture_data = texture.into_raw(); // let surface = SDL_Surface::from_data( // &mut texture_data, // width, // height, // width * 4, // PixelFormatEnum::RGBA8888, // )?; let surface = SDL_Surface::load_bmp(path).map_err(|s| anyhow::Error::msg(s))?; self.window.lock().unwrap().set_icon(surface); Ok(()) } pub fn set_cursor>(&self, path: T) -> Result<()> { // let texture = match image::open(path) { // Ok(tex) => tex.to_rgba(), // Err(err) => create_error!(format!("error loading image: {}", err)), // }; // let (width, height) = texture.dimensions(); // let mut texture_data = texture.into_raw(); // let surface = SDL_Surface::from_data( // &mut texture_data, // width, // height, // width * 4, // PixelFormatEnum::RGBA8888, // )?; let surface = SDL_Surface::load_bmp(path).map_err(|s| anyhow::Error::msg(s))?; let cursor = Cursor::from_surface(surface, 0, 0).map_err(|s| anyhow::Error::msg(s))?; cursor.set(); *self.cursor.lock().unwrap() = Some(cursor); Ok(()) } fn raw_sdl2_window(&self) -> *mut SDL_Window { self.window.lock().unwrap().raw() } pub fn displays(&self) -> &[Display] { &self.displays } pub fn create_vulkan_surface(&self, instance: &Arc) -> Result<()> { let vk_surface = self .window .lock() .unwrap() .vulkan_create_surface(instance.vk_handle().raw()) .map_err(|s| anyhow::Error::msg(s))? .into(); *self.surface.lock().unwrap() = Some(Surface::from_vk_surface(vk_surface, instance)); Ok(()) } pub fn surface(&self) -> Arc { self.surface.lock().unwrap().as_ref().unwrap().clone() } pub(crate) fn activate_vulkan_instance_extensions( &self, extensions: &mut InstanceExtensions, ) -> Result<()> { let sys_wm_info: SdlSysWmInfo = unsafe { let tmp = MaybeUninit::zeroed(); let mut ret: SdlSysWmInfo = tmp.assume_init(); ret.version = sdl2::version::version(); SDL_GetWindowWMInfo(self.raw_sdl2_window(), &mut ret); ret }; match sys_wm_info.subsystem { SDL_SYSWM_ANDROID => extensions.android_surface = true, SDL_SYSWM_COCOA => extensions.macos_surface = true, SDL_SYSWM_WAYLAND => extensions.wayland_surface = true, SDL_SYSWM_WINDOWS => extensions.win32_surface = true, SDL_SYSWM_X11 => extensions.xlib_surface = true, _ => { return Err(anyhow::Error::msg(format!( "Unsupported window subsystem flag {}", sys_wm_info.subsystem ))); } } extensions.surface = true; Ok(()) } pub(crate) fn activate_vulkan_device_extensions( &self, extensions: &mut DeviceExtensions, ) -> Result<()> { extensions.swapchain = true; Ok(()) } } impl std::fmt::Debug for WindowSystemIntegration { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "WindowSystemIntegration {{ }}") } } /// helper function to wrap SDL2 error types #[inline] fn set_window_size(window: &mut sdl2::video::Window, width: u32, height: u32) -> Result<()> { window.set_size(width, height)?; Ok(()) }