diff --git a/Cargo.toml b/Cargo.toml index 520f72ec..461eb535 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ opengl = [ keyboard-types = { version = "0.8.3" } raw-window-handle = "0.6.2" dpi = "0.1.2" +tracing = { version = "0.1", optional = true } [target.'cfg(target_os="linux")'.dependencies] x11rb = { version = "0.13.2", features = ["cursor", "resource_manager", "allow-unsafe-code", "dl-libxcb"], default-features = false } diff --git a/examples/open_parented/src/main.rs b/examples/open_parented/src/main.rs index 8eedd262..ef2def04 100644 --- a/examples/open_parented/src/main.rs +++ b/examples/open_parented/src/main.rs @@ -1,6 +1,7 @@ use baseview::dpi::LogicalSize; use baseview::{ - Event, EventStatus, WindowContext, WindowHandle, WindowHandler, WindowOpenOptions, WindowSize, + Event, EventStatus, HandlerError, WindowContext, WindowHandle, WindowHandler, + WindowOpenOptions, WindowSize, }; use std::cell::{Cell, RefCell}; use std::num::NonZeroU32; @@ -13,45 +14,51 @@ struct ParentWindowHandler { } impl ParentWindowHandler { - pub fn new(window: WindowContext) -> Self { - let ctx = softbuffer::Context::new(window.clone()).unwrap(); - let mut surface = softbuffer::Surface::new(&ctx, window.clone()).unwrap(); + pub fn new(window: WindowContext) -> Result { + let ctx = softbuffer::Context::new(window.clone())?; + let mut surface = softbuffer::Surface::new(&ctx, window.clone())?; let size = window.size().physical; - surface - .resize(NonZeroU32::new(size.width).unwrap(), NonZeroU32::new(size.height).unwrap()) - .unwrap(); + surface.resize(size.width.try_into()?, size.height.try_into()?)?; let window_open_options = WindowOpenOptions::new() .with_size(LogicalSize::new(256, 256)) .with_parent(&window) .with_title("baseview child"); - let child_window = baseview::create_window(window_open_options, ChildWindowHandler::new); + let child_window = baseview::create_window(window_open_options, ChildWindowHandler::new)?; - Self { surface: surface.into(), damaged: true.into(), _child_window: Some(child_window) } + Ok(Self { + surface: surface.into(), + damaged: true.into(), + _child_window: Some(child_window), + }) } } impl WindowHandler for ParentWindowHandler { - fn on_frame(&self) { + fn on_frame(&self) -> Result<(), HandlerError> { let mut surface = self.surface.borrow_mut(); - let mut buf = surface.buffer_mut().unwrap(); + let mut buf = surface.buffer_mut()?; if self.damaged.get() { buf.fill(0xFFAAAAAA); self.damaged.set(false); } - buf.present().unwrap(); + buf.present()?; + + Ok(()) } - fn resized(&self, new_size: WindowSize) { + fn resized(&self, new_size: WindowSize) -> Result<(), HandlerError> { println!("Parent Resized: {new_size:?}"); if let (Some(width), Some(height)) = (NonZeroU32::new(new_size.physical.width), NonZeroU32::new(new_size.physical.height)) { - self.surface.borrow_mut().resize(width, height).unwrap(); + self.surface.borrow_mut().resize(width, height)?; self.damaged.set(true); } + + Ok(()) } fn on_event(&self, event: Event) -> EventStatus { @@ -72,38 +79,40 @@ struct ChildWindowHandler { } impl ChildWindowHandler { - pub fn new(window: WindowContext) -> Self { - let ctx = softbuffer::Context::new(window.clone()).unwrap(); - let mut surface = softbuffer::Surface::new(&ctx, window.clone()).unwrap(); + pub fn new(window: WindowContext) -> Result { + let ctx = softbuffer::Context::new(window.clone())?; + let mut surface = softbuffer::Surface::new(&ctx, window.clone())?; let size = window.size().physical; - surface - .resize(NonZeroU32::new(size.width).unwrap(), NonZeroU32::new(size.height).unwrap()) - .unwrap(); + surface.resize(size.width.try_into()?, size.height.try_into()?)?; - Self { surface: surface.into(), damaged: true.into() } + Ok(Self { surface: surface.into(), damaged: true.into() }) } } impl WindowHandler for ChildWindowHandler { - fn on_frame(&self) { + fn on_frame(&self) -> Result<(), HandlerError> { let mut surface = self.surface.borrow_mut(); - let mut buf = surface.buffer_mut().unwrap(); + let mut buf = surface.buffer_mut()?; if self.damaged.get() { buf.fill(0xFFAA0000); self.damaged.set(false); } - buf.present().unwrap(); + buf.present()?; + + Ok(()) } - fn resized(&self, new_size: WindowSize) { + fn resized(&self, new_size: WindowSize) -> Result<(), HandlerError> { println!("Child Resized: {new_size:?}"); if let (Some(width), Some(height)) = (NonZeroU32::new(new_size.physical.width), NonZeroU32::new(new_size.physical.height)) { - self.surface.borrow_mut().resize(width, height).unwrap(); + self.surface.borrow_mut().resize(width, height)?; self.damaged.set(true); } + + Ok(()) } fn on_event(&self, event: Event) -> EventStatus { @@ -118,8 +127,10 @@ impl WindowHandler for ChildWindowHandler { } } -fn main() { +fn main() -> Result<(), baseview::Error> { let window_open_options = WindowOpenOptions::new().with_size(LogicalSize::new(512.0, 512.0)); - baseview::create_window(window_open_options, ParentWindowHandler::new).run_until_closed(); + baseview::create_window(window_open_options, ParentWindowHandler::new)?.run_until_closed(); + + Ok(()) } diff --git a/examples/open_window/src/main.rs b/examples/open_window/src/main.rs index adae5fe5..fed1ecd5 100644 --- a/examples/open_window/src/main.rs +++ b/examples/open_window/src/main.rs @@ -8,7 +8,8 @@ use rtrb::{Consumer, RingBuffer}; use baseview::copy_to_clipboard; use baseview::dpi::{LogicalSize, PhysicalPosition}; use baseview::{ - Event, EventStatus, MouseEvent, WindowContext, WindowHandler, WindowOpenOptions, WindowSize, + Event, EventStatus, HandlerError, MouseEvent, WindowContext, WindowHandler, WindowOpenOptions, + WindowSize, }; #[derive(Debug, Clone)] @@ -27,24 +28,26 @@ struct OpenWindowExample { } impl WindowHandler for OpenWindowExample { - fn resized(&self, new_size: WindowSize) { + fn resized(&self, new_size: WindowSize) -> Result<(), HandlerError> { println!("Resized: {new_size:?}"); if let (Some(width), Some(height)) = (NonZeroU32::new(new_size.physical.width), NonZeroU32::new(new_size.physical.height)) { - self.surface.borrow_mut().resize(width, height).unwrap(); + self.surface.borrow_mut().resize(width, height)?; self.damaged.set(true); } + + Ok(()) } - fn on_frame(&self) { + fn on_frame(&self) -> Result<(), HandlerError> { if !self.damaged.get() { - return; + return Ok(()); } let mut surface = self.surface.borrow_mut(); - let mut pixels = surface.buffer_mut().unwrap(); + let mut pixels = surface.buffer_mut()?; let size = self.window_context.size(); let scale_factor = self.window_context.scale_factor(); let (width, height) = (size.physical.width, size.physical.height); @@ -101,12 +104,14 @@ impl WindowHandler for OpenWindowExample { } } - pixels.present().unwrap(); + pixels.present()?; self.damaged.set(false); while let Ok(message) = self.rx.borrow_mut().pop() { println!("Message: {:?}", message); } + + Ok(()) } fn on_event(&self, event: Event) -> EventStatus { @@ -134,7 +139,7 @@ impl WindowHandler for OpenWindowExample { } } -fn main() { +fn main() -> Result<(), baseview::Error> { let window_open_options = WindowOpenOptions::new().with_size(LogicalSize::new(512.0, 512.0)); let (mut tx, rx) = RingBuffer::new(128); @@ -148,23 +153,23 @@ fn main() { }); baseview::create_window(window_open_options, |window| { - let ctx = softbuffer::Context::new(window.clone()).unwrap(); - let mut surface = softbuffer::Surface::new(&ctx, window.clone()).unwrap(); + let ctx = softbuffer::Context::new(window.clone())?; + let mut surface = softbuffer::Surface::new(&ctx, window.clone())?; let size = window.size().physical; - surface - .resize(NonZeroU32::new(size.width).unwrap(), NonZeroU32::new(size.height).unwrap()) - .unwrap(); + surface.resize(size.width.try_into()?, size.height.try_into()?)?; - OpenWindowExample { + Ok(OpenWindowExample { window_context: window, surface: surface.into(), rx: rx.into(), mouse_pos: PhysicalPosition::new(0., 0.).into(), is_cursor_inside: false.into(), damaged: true.into(), - } - }) + }) + })? .run_until_closed(); + + Ok(()) } fn log_event(event: &Event) { diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 62fa72e2..4430d244 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -2,7 +2,7 @@ use crate::window_handler::OpenWindowExample; use crate::ExamplePluginMainThread; use baseview::dpi::PhysicalSize; use baseview::gl::GlConfig; -use baseview::{Window, WindowHandle, WindowOpenOptions}; +use baseview::{WindowHandle, WindowOpenOptions}; use clack_extensions::gui::{ GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, PluginGuiImpl, Window as ClapWindow, }; @@ -72,9 +72,10 @@ impl PluginGuiImpl for ExamplePluginMainThread { let options = WindowOpenOptions::new() .with_size(PhysicalSize::new(400, 200)) - .with_gl_config(GlConfig::default()); + .with_gl_config(GlConfig::default()) + .with_parent(&parent); - let window = Window::open_parented(&parent, options, OpenWindowExample::new); + let window = baseview::create_window(options, OpenWindowExample::new)?; self.gui = Some(ExamplePluginGui { handle: window }); diff --git a/examples/plugin_clack/src/window_handler.rs b/examples/plugin_clack/src/window_handler.rs index 726b2bab..ca498d62 100644 --- a/examples/plugin_clack/src/window_handler.rs +++ b/examples/plugin_clack/src/window_handler.rs @@ -1,5 +1,7 @@ use baseview::dpi::PhysicalPosition; -use baseview::{Event, EventStatus, MouseEvent, WindowContext, WindowHandler, WindowSize}; +use baseview::{ + Event, EventStatus, HandlerError, MouseEvent, WindowContext, WindowHandler, WindowSize, +}; use std::cell::{Cell, RefCell}; use std::num::NonZeroU32; @@ -13,24 +15,26 @@ pub struct OpenWindowExample { } impl WindowHandler for OpenWindowExample { - fn resized(&self, new_size: WindowSize) { + fn resized(&self, new_size: WindowSize) -> Result<(), HandlerError> { println!("Resized: {new_size:?}"); if let (Some(width), Some(height)) = (NonZeroU32::new(new_size.physical.width), NonZeroU32::new(new_size.physical.height)) { - self.surface.borrow_mut().resize(width, height).unwrap(); + self.surface.borrow_mut().resize(width, height)?; self.damaged.set(true); } + + Ok(()) } - fn on_frame(&self) { + fn on_frame(&self) -> Result<(), HandlerError> { if !self.damaged.get() { - return; + return Ok(()); } let mut surface = self.surface.borrow_mut(); - let mut pixels = surface.buffer_mut().unwrap(); + let mut pixels = surface.buffer_mut()?; let size = self.window_context.size(); let scale_factor = self.window_context.scale_factor(); let (width, height) = (size.physical.width, size.physical.height); @@ -87,8 +91,10 @@ impl WindowHandler for OpenWindowExample { } } - pixels.present().unwrap(); + pixels.present()?; self.damaged.set(false); + + Ok(()) } fn on_event(&self, event: Event) -> EventStatus { @@ -115,21 +121,19 @@ impl WindowHandler for OpenWindowExample { } impl OpenWindowExample { - pub fn new(window: WindowContext) -> Self { - let ctx = softbuffer::Context::new(window.clone()).unwrap(); - let mut surface = softbuffer::Surface::new(&ctx, window.clone()).unwrap(); + pub fn new(window: WindowContext) -> Result { + let ctx = softbuffer::Context::new(window.clone())?; + let mut surface = softbuffer::Surface::new(&ctx, window.clone())?; let size = window.size().physical; - surface - .resize(NonZeroU32::new(size.width).unwrap(), NonZeroU32::new(size.height).unwrap()) - .unwrap(); + surface.resize(size.width.try_into()?, size.height.try_into()?)?; - OpenWindowExample { + Ok(OpenWindowExample { window_context: window, surface: surface.into(), mouse_pos: PhysicalPosition::new(0., 0.).into(), is_cursor_inside: false.into(), damaged: true.into(), - } + }) } } diff --git a/examples/render_femtovg/src/main.rs b/examples/render_femtovg/src/main.rs index 102b1f4c..c609f829 100644 --- a/examples/render_femtovg/src/main.rs +++ b/examples/render_femtovg/src/main.rs @@ -1,12 +1,12 @@ use baseview::dpi::{LogicalSize, PhysicalPosition}; use baseview::gl::{GlConfig, GlContext}; use baseview::{ - Event, EventStatus, MouseEvent, WindowContext, WindowHandler, WindowOpenOptions, WindowSize, + Event, EventStatus, HandlerError, MouseEvent, WindowContext, WindowHandler, WindowOpenOptions, + WindowSize, }; use femtovg::renderer::OpenGl; use femtovg::{Canvas, Color}; use std::cell::{Cell, RefCell}; -use std::ffi::CString; struct FemtovgExample { window_context: WindowContext, @@ -17,39 +17,37 @@ struct FemtovgExample { } impl FemtovgExample { - fn new(window_context: WindowContext) -> Self { - let gl_context = window_context.gl_context().unwrap(); - unsafe { gl_context.make_current() }; + fn new(window_context: WindowContext) -> Result { + let Some(gl_context) = window_context.gl_context() else { unreachable!() }; + unsafe { gl_context.make_current()? }; - let renderer = unsafe { - OpenGl::new_from_function(|s| gl_context.get_proc_address(&CString::new(s).unwrap())) - } - .unwrap(); + let renderer = + unsafe { OpenGl::new_from_function(|s| gl_context.get_proc_address_from_str(s)) }?; - let mut canvas = Canvas::new(renderer).unwrap(); + let mut canvas = Canvas::new(renderer)?; let size = window_context.size(); canvas.set_size(size.physical.width, size.physical.height, size.scale_factor as f32); - unsafe { gl_context.make_not_current() }; - Self { + unsafe { gl_context.make_not_current()? }; + Ok(Self { gl_context, window_context, canvas: canvas.into(), damaged: true.into(), current_mouse_position: Cell::new(PhysicalPosition::default()), - } + }) } } impl WindowHandler for FemtovgExample { - fn on_frame(&self) { + fn on_frame(&self) -> Result<(), HandlerError> { if !self.damaged.get() { - return; + return Ok(()); } let context = &self.gl_context; - unsafe { context.make_current() }; + unsafe { context.make_current()? }; let mut canvas = self.canvas.borrow_mut(); @@ -81,15 +79,19 @@ impl WindowHandler for FemtovgExample { // Tell renderer to execute all drawing commands canvas.flush(); - context.swap_buffers(); - unsafe { context.make_not_current() }; + context.swap_buffers()?; + unsafe { context.make_not_current()? }; self.damaged.set(false); + + Ok(()) } - fn resized(&self, new_size: WindowSize) { + fn resized(&self, new_size: WindowSize) -> Result<(), HandlerError> { let size = new_size.physical; self.canvas.borrow_mut().set_size(size.width, size.height, new_size.scale_factor as f32); self.damaged.set(true); + + Ok(()) } fn on_event(&self, event: Event) -> EventStatus { @@ -102,7 +104,7 @@ impl WindowHandler for FemtovgExample { ) => { self.current_mouse_position.set(position); if position.y > 400. && !self.window_context.has_focus() { - self.window_context.focus() + let _ = self.window_context.focus(); } self.damaged.set(true); } @@ -113,13 +115,14 @@ impl WindowHandler for FemtovgExample { } } -fn main() { +fn main() -> Result<(), baseview::Error> { let window_open_options = WindowOpenOptions::new() .with_title("Femtovg on Baseview") .with_size(LogicalSize::new(512, 512)) .with_gl_config(GlConfig { alpha_bits: 8, ..GlConfig::default() }); - baseview::create_window(window_open_options, FemtovgExample::new).run_until_closed(); + baseview::create_window(window_open_options, FemtovgExample::new)?.run_until_closed(); + Ok(()) } fn log_event(event: &Event) { diff --git a/examples/render_wgpu/src/main.rs b/examples/render_wgpu/src/main.rs index 0b5ca20a..1828e1cb 100644 --- a/examples/render_wgpu/src/main.rs +++ b/examples/render_wgpu/src/main.rs @@ -1,5 +1,7 @@ use baseview::dpi::{LogicalSize, PhysicalSize}; -use baseview::{Event, EventStatus, WindowContext, WindowHandler, WindowOpenOptions, WindowSize}; +use baseview::{ + Event, EventStatus, HandlerError, WindowContext, WindowHandler, WindowOpenOptions, WindowSize, +}; use log::LevelFilter; use std::cell::RefCell; @@ -16,12 +18,12 @@ struct WgpuExample { } impl WgpuExample { - async fn new(context: WindowContext) -> Self { + async fn new(context: WindowContext) -> Result { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_with_display_handle( Box::new(context.platform_handle()), )); - let surface = instance.create_surface(context.platform_handle()).unwrap(); + let surface = instance.create_surface(context.platform_handle())?; let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { @@ -31,8 +33,7 @@ impl WgpuExample { compatible_surface: Some(&surface), ..Default::default() }) - .await - .expect("Failed to find an appropriate adapter"); + .await?; // Create the logical device and command queue let (device, queue) = adapter @@ -44,8 +45,7 @@ impl WgpuExample { memory_hints: wgpu::MemoryHints::MemoryUsage, ..Default::default() }) - .await - .expect("Failed to create device"); + .await?; const SHADER: &str = " const VERTS = array( @@ -116,7 +116,7 @@ impl WgpuExample { let surface_config = surface.get_default_config(&adapter, width, height).unwrap(); surface.configure(&device, &surface_config); - Self { + Ok(Self { window_context: context, instance, @@ -125,29 +125,30 @@ impl WgpuExample { queue, surface: surface.into(), surface_config: surface_config.into(), - } + }) } } impl WindowHandler for WgpuExample { - fn on_frame(&self) { + fn on_frame(&self) -> Result<(), HandlerError> { let mut surface = self.surface.borrow_mut(); let surface_texture = match surface.get_current_texture() { wgpu::CurrentSurfaceTexture::Success(texture) => texture, - wgpu::CurrentSurfaceTexture::Occluded | wgpu::CurrentSurfaceTexture::Timeout => return, + wgpu::CurrentSurfaceTexture::Occluded | wgpu::CurrentSurfaceTexture::Timeout => { + return Ok(()) + } wgpu::CurrentSurfaceTexture::Suboptimal(_) | wgpu::CurrentSurfaceTexture::Outdated => { surface.configure(&self.device, &self.surface_config.borrow()); // We'll retry next frame - return; + return Ok(()); } wgpu::CurrentSurfaceTexture::Lost => { - *surface = - self.instance.create_surface(self.window_context.platform_handle()).unwrap(); + *surface = self.instance.create_surface(self.window_context.platform_handle())?; surface.configure(&self.device, &self.surface_config.borrow()); // We'll retry next frame - return; + return Ok(()); } wgpu::CurrentSurfaceTexture::Validation => { unreachable!("No error scope registered, so validation errors will panic") @@ -183,9 +184,11 @@ impl WindowHandler for WgpuExample { self.queue.submit(Some(encoder.finish())); self.queue.present(surface_texture); + + Ok(()) } - fn resized(&self, new_size: WindowSize) { + fn resized(&self, new_size: WindowSize) -> Result<(), HandlerError> { let mut surface_config = self.surface_config.borrow_mut(); surface_config.width = new_size.physical.width; @@ -193,6 +196,8 @@ impl WindowHandler for WgpuExample { let surface = self.surface.borrow(); surface.configure(&self.device, &surface_config); + + Ok(()) } fn on_event(&self, event: Event) -> EventStatus { @@ -202,14 +207,16 @@ impl WindowHandler for WgpuExample { } } -fn main() { +fn main() -> Result<(), baseview::Error> { env_logger::builder().filter_level(LevelFilter::Debug).init(); let window_open_options = WindowOpenOptions::new() .with_title("WGPU on Baseview") .with_size(LogicalSize::new(512, 512)); - baseview::create_window(window_open_options, |c| pollster::block_on(WgpuExample::new(c))) + baseview::create_window(window_open_options, |c| pollster::block_on(WgpuExample::new(c)))? .run_until_closed(); + + Ok(()) } fn log_event(event: &Event) { diff --git a/src/context.rs b/src/context.rs index d99d9ff7..18bdf3e2 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,3 +1,4 @@ +use super::*; use crate::{platform, MouseCursor, WindowSize}; use dpi::Size; use raw_window_handle::{ @@ -18,8 +19,9 @@ impl WindowContext { } /// Sets the [`MouseCursor`] icon to be displayed when the mouse cursor hovers this window. - pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) { - self.inner.set_mouse_cursor(mouse_cursor); + pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) -> Result<()> { + self.inner.set_mouse_cursor(mouse_cursor)?; + Ok(()) } /// Requests the window to be closed. @@ -38,16 +40,18 @@ impl WindowContext { } /// Focuses this window. - pub fn focus(&self) { - self.inner.focus(); + pub fn focus(&self) -> Result<()> { + self.inner.focus()?; + Ok(()) } /// Resizes this window to the given `size`. /// /// The given `size` can either be in [physical](dpi::PhysicalSize) or /// [logical](dpi::LogicalSize) pixels. - pub fn resize(&self, size: impl Into) { - self.inner.resize(size.into()); + pub fn resize(&self, size: impl Into) -> Result<()> { + self.inner.resize(size.into())?; + Ok(()) } /// Returns the current scale factor of this window. @@ -78,13 +82,13 @@ impl WindowContext { } impl HasWindowHandle for WindowContext { - fn window_handle(&self) -> Result, HandleError> { + fn window_handle(&self) -> core::result::Result, HandleError> { self.inner.window_handle().ok_or(HandleError::Unavailable) } } impl HasDisplayHandle for WindowContext { - fn display_handle(&self) -> Result, HandleError> { + fn display_handle(&self) -> core::result::Result, HandleError> { Ok(self.inner.display_handle()) } } @@ -124,13 +128,13 @@ const _: () = { }; impl HasWindowHandle for PlatformHandle { - fn window_handle(&self) -> Result, HandleError> { + fn window_handle(&self) -> core::result::Result, HandleError> { self.inner.window_handle().ok_or(HandleError::Unavailable) } } impl HasDisplayHandle for PlatformHandle { - fn display_handle(&self) -> Result, HandleError> { + fn display_handle(&self) -> core::result::Result, HandleError> { Ok(self.inner.display_handle()) } } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..80ea5fb4 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,62 @@ +use std::fmt::{Debug, Display, Formatter}; + +pub type Result = std::result::Result; + +pub struct Error { + inner: crate::platform::Error, +} + +impl From for Error { + fn from(inner: crate::platform::Error) -> Error { + Error { inner } + } +} + +impl Debug for Error { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.inner, f) + } +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.inner, f) + } +} + +impl std::error::Error for Error {} + +pub struct HandlerError { + inner: Box, +} + +impl HandlerError { + #[inline] + pub fn cause(&self) -> &(dyn std::error::Error + 'static) { + self.inner.as_ref() + } + + #[inline] + pub fn into_inner(self) -> Box { + self.inner + } +} + +impl Debug for HandlerError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.inner, f) + } +} + +impl Display for HandlerError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.inner, f) + } +} + +impl From for HandlerError { + fn from(value: E) -> Self { + Self { inner: Box::new(value) } + } +} diff --git a/src/gl.rs b/src/gl.rs index edfc89f7..431421d6 100644 --- a/src/gl.rs +++ b/src/gl.rs @@ -1,5 +1,4 @@ -use crate::platform::gl::*; -use std::ffi::{c_void, CStr}; +use std::ffi::{c_void, CStr, CString}; use std::marker::PhantomData; #[derive(Clone, Debug, PartialEq)] @@ -43,13 +42,6 @@ pub enum Profile { Core, } -#[derive(Debug)] -#[non_exhaustive] -pub enum GlError { - VersionNotSupported, - CreationFailed(CreationFailedError), -} - #[derive(Clone)] pub struct GlContext { inner: crate::platform::gl::GlContext, @@ -62,19 +54,28 @@ impl GlContext { GlContext { inner: context, phantom: PhantomData } } - pub unsafe fn make_current(&self) { - self.inner.make_current(); + pub unsafe fn make_current(&self) -> Result<(), crate::Error> { + self.inner.make_current()?; + Ok(()) } - pub unsafe fn make_not_current(&self) { - self.inner.make_not_current(); + pub unsafe fn make_not_current(&self) -> Result<(), crate::Error> { + self.inner.make_not_current()?; + Ok(()) + } + + pub fn get_proc_address_from_str(&self, symbol: impl Into>) -> *const c_void { + let Ok(symbol) = CString::new(symbol) else { return std::ptr::null() }; + + self.get_proc_address(&symbol) } pub fn get_proc_address(&self, symbol: &CStr) -> *const c_void { self.inner.get_proc_address(symbol) } - pub fn swap_buffers(&self) { - self.inner.swap_buffers(); + pub fn swap_buffers(&self) -> Result<(), crate::Error> { + self.inner.swap_buffers()?; + Ok(()) } } diff --git a/src/handler.rs b/src/handler.rs index 39b12d0b..afa29f4f 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,24 +1,30 @@ use super::*; +use crate::platform::Result; pub trait WindowHandler: 'static { - fn on_frame(&self); - fn resized(&self, new_size: WindowSize); + fn on_frame(&self) -> core::result::Result<(), HandlerError>; + fn resized(&self, new_size: WindowSize) -> core::result::Result<(), HandlerError>; fn on_event(&self, event: Event) -> EventStatus; } +type DynBuilderResult = core::result::Result, HandlerError>; + #[allow(unused)] pub struct WindowHandlerBuilder { - inner: Box Box + Send + 'static>, + inner: Box DynBuilderResult + Send + 'static>, } impl WindowHandlerBuilder { pub fn new( - f: impl FnOnce(WindowContext) -> H + Send + 'static, + f: impl FnOnce(WindowContext) -> core::result::Result + Send + 'static, ) -> WindowHandlerBuilder { - Self { inner: Box::new(|c| Box::new(f(c))) } + Self { inner: Box::new(|c| Ok(Box::new(f(c)?))) } } - pub fn build(self, ctx: WindowContext) -> Box { - (self.inner)(ctx) + pub fn build(self, ctx: WindowContext) -> Result> { + match (self.inner)(ctx) { + Ok(handle) => Ok(handle), + Err(e) => Err(platform::Error::Handler(e)), + } } } diff --git a/src/lib.rs b/src/lib.rs index e611d188..a781de81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,11 @@ mod clipboard; mod context; +mod error; mod event; mod handler; mod keyboard; mod mouse_cursor; +mod tracing; mod window; mod window_open_options; @@ -15,10 +17,14 @@ pub mod gl; pub use clipboard::*; pub use context::{PlatformHandle, WindowContext}; pub use dpi; +pub use error::*; pub use event::*; pub use handler::WindowHandler; pub use mouse_cursor::MouseCursor; pub use window::*; pub use window_open_options::*; +#[allow(unused)] +pub(crate) use tracing::warn; + pub(crate) mod wrappers; diff --git a/src/platform/macos/context.rs b/src/platform/macos/context.rs index a8cfe27a..4535b439 100644 --- a/src/platform/macos/context.rs +++ b/src/platform/macos/context.rs @@ -2,7 +2,7 @@ use crate::platform::macos::cursor::Cursor; use crate::platform::macos::view::BaseviewView; use crate::platform::{PlatformHandle, WindowSharedState}; use crate::wrappers::appkit::{View, ViewRef}; -use crate::{MouseCursor, WindowSize}; +use crate::*; use dispatch2::MainThreadBound; use dpi::Size; use objc2::rc::Weak; @@ -50,27 +50,32 @@ impl WindowContext { view.isEqual(Some(&*first_responder)) } - pub fn focus(&self) { - let Some(view) = self.view.load() else { return }; + pub fn focus(&self) -> Result<()> { + let Some(view) = self.view.load() else { return Ok(()) }; if let Some(window) = view.window() { window.makeFirstResponder(Some(&view)); } + + Ok(()) } - pub fn resize(&self, size: Size) { - let Some(view) = self.view.load() else { return }; - let Some(view) = view.inner_ref() else { return }; + pub fn resize(&self, size: Size) -> Result<()> { + let Some(view) = self.view.load() else { return Ok(()) }; + let Some(view) = view.inner_ref() else { return Ok(()) }; if view.inner.state.closed.get() { - return; + return Ok(()); } BaseviewView::resize(view, size); + + Ok(()) } - pub fn set_mouse_cursor(&self, cursor: MouseCursor) { - let Some(view) = self.view.load() else { return }; + pub fn set_mouse_cursor(&self, cursor: MouseCursor) -> Result<()> { + let Some(view) = self.view.load() else { return Ok(()) }; let native_cursor = Cursor::from(cursor); view.addCursorRect_cursor(view.bounds(), &native_cursor.load()); + Ok(()) } pub fn size(&self) -> WindowSize { diff --git a/src/platform/macos/error.rs b/src/platform/macos/error.rs new file mode 100644 index 00000000..8501e438 --- /dev/null +++ b/src/platform/macos/error.rs @@ -0,0 +1,19 @@ +use crate::HandlerError; +use std::fmt::Display; + +#[derive(Debug)] +pub enum Error { + Handler(HandlerError), + #[cfg(feature = "opengl")] + GlError(super::gl::GlError), +} + +impl Display for Error { + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + #[cfg(feature = "opengl")] + Error::GlError(e) => e.fmt(fmt), + Error::Handler(e) => e.fmt(fmt), + } + } +} diff --git a/src/platform/macos/gl.rs b/src/platform/macos/gl.rs index 4d3a5f63..1ee7e670 100644 --- a/src/platform/macos/gl.rs +++ b/src/platform/macos/gl.rs @@ -1,6 +1,8 @@ #![allow(deprecated)] // OpenGL is deprecated on macOS -use crate::gl::{GlConfig, GlError, Profile}; +use crate::gl::{GlConfig, Profile}; +use crate::platform::*; +use crate::warn; use objc2::rc::Retained; use objc2::AllocAnyThread; use objc2::{MainThreadMarker, MainThreadOnly}; @@ -11,20 +13,54 @@ use objc2_app_kit::{ NSOpenGLPixelFormat, NSOpenGLProfileVersion3_2Core, NSOpenGLProfileVersion4_1Core, NSOpenGLProfileVersionLegacy, NSOpenGLView, NSView, }; -use objc2_core_foundation::{CFBundle, CFString, CFStringBuiltInEncodings}; +use objc2_core_foundation::{CFBundle, CFRetained, CFString, CFStringBuiltInEncodings}; use objc2_foundation::NSSize; use std::ffi::{c_void, CStr}; +use std::fmt::Display; use std::ptr::NonNull; -pub type CreationFailedError = (); +#[derive(Debug)] +pub enum GlError { + GlVersionNotSupported { version: (u8, u8), profile: Profile }, + NSOpenGLPixelFormatInitFailed, + NSOpenGLViewInitFailed, + OpenGlBundleNotFound, +} + +impl From for Error { + fn from(value: GlError) -> Self { + Self::GlError(value) + } +} + +impl Display for GlError { + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + GlError::GlVersionNotSupported { version: (maj, min), profile } => { + write!(fmt, "GL version {maj}.{min} is not supported for profile {profile:?}") + } + GlError::NSOpenGLPixelFormatInitFailed => { + fmt.write_str("NSOpenGLPixelFormat initialization failed") + } + GlError::NSOpenGLViewInitFailed => fmt.write_str("NSOpenGLView initialization failed"), + GlError::OpenGlBundleNotFound => { + fmt.write_str("Could not find bundle com.apple.opengl") + } + } + } +} + #[derive(Clone)] pub struct GlContext { pub(crate) view: Retained, context: Retained, + gl_bundle: CFRetained, } impl GlContext { - pub(crate) fn create(parent_view: &NSView, config: GlConfig) -> Result { + pub(crate) fn create( + parent_view: &NSView, config: GlConfig, marker: MainThreadMarker, + ) -> Result { let version = if config.version < (3, 2) && config.profile == Profile::Compatibility { NSOpenGLProfileVersionLegacy } else if config.version == (3, 2) && config.profile == Profile::Core { @@ -32,7 +68,11 @@ impl GlContext { } else if config.version > (3, 2) && config.profile == Profile::Core { NSOpenGLProfileVersion4_1Core } else { - return Err(GlError::VersionNotSupported); + return Err(GlError::GlVersionNotSupported { + version: config.version, + profile: config.profile, + } + .into()); }; #[rustfmt::skip] @@ -60,30 +100,30 @@ impl GlContext { attrs.push(0); + let Some(attrs) = NonNull::new(attrs.as_mut_ptr()) else { + // PANIC: This cannot panic, as the pointer comes from the vec + unreachable!() + }; + // SAFETY: Attribs pointer is valid (coming from the above vec) and null-terminated - let pixel_format = unsafe { - NSOpenGLPixelFormat::initWithAttributes( - NSOpenGLPixelFormat::alloc(), - // PANIC: This cannot panic, as the pointer comes from the vec - NonNull::new(attrs.as_mut_ptr()).unwrap(), - ) - } - .ok_or(GlError::CreationFailed(()))?; + let pixel_format = + unsafe { NSOpenGLPixelFormat::initWithAttributes(NSOpenGLPixelFormat::alloc(), attrs) } + .ok_or(GlError::NSOpenGLPixelFormatInitFailed)?; let view = NSOpenGLView::initWithFrame_pixelFormat( - // PANIC: This cannot panic, as the pointer comes from the vec - NSOpenGLView::alloc(MainThreadMarker::new().unwrap()), + NSOpenGLView::alloc(marker), parent_view.frame(), Some(&pixel_format), ) - .ok_or(GlError::CreationFailed(()))?; + .ok_or(GlError::NSOpenGLViewInitFailed)?; view.setWantsBestResolutionOpenGLSurface(true); view.display(); parent_view.addSubview(&view); - let context = view.openGLContext().ok_or(GlError::CreationFailed(()))?; + // NSOpenGlView::openGLContext is not documented to possibly return NULL. + let Some(context) = view.openGLContext() else { unreachable!() }; let value = config.vsync as i32; @@ -92,39 +132,50 @@ impl GlContext { context.setValues_forParameter((&value).into(), NSOpenGLContextParameter::SwapInterval); } - Ok(GlContext { view, context }) + let framework_name = CFString::from_static_str("com.apple.opengl"); + let gl_bundle = CFBundle::bundle_with_identifier(Some(&framework_name)) + .ok_or(GlError::OpenGlBundleNotFound)?; + + Ok(GlContext { view, context, gl_bundle }) } - pub unsafe fn make_current(&self) { + pub unsafe fn make_current(&self) -> Result<()> { self.context.makeCurrentContext(); + Ok(()) } - pub unsafe fn make_not_current(&self) { + pub unsafe fn make_not_current(&self) -> Result<()> { NSOpenGLContext::clearCurrentContext(); + Ok(()) } pub fn get_proc_address(&self, symbol: &CStr) -> *const c_void { + // PANIC: CStr alloc can not be longer than isize + let Ok(bytes_count) = symbol.count_bytes().try_into() else { unreachable!() }; + // SAFETY: The string pointer is valid let symbol_name = unsafe { CFString::with_bytes( None, symbol.as_ptr().cast(), - symbol.count_bytes().try_into().unwrap(), + bytes_count, CFStringBuiltInEncodings::EncodingUTF8.0, false, ) - } - .unwrap(); + }; - let framework_name = CFString::from_static_str("com.apple.opengl"); - let framework = CFBundle::bundle_with_identifier(Some(&framework_name)).unwrap(); + let Some(symbol_name) = symbol_name else { + warn!("Failed to create CFString for symbol {:?}", symbol); + return core::ptr::null(); + }; - CFBundle::function_pointer_for_name(&framework, Some(&symbol_name)) + self.gl_bundle.function_pointer_for_name(Some(&symbol_name)) } - pub fn swap_buffers(&self) { + pub fn swap_buffers(&self) -> Result<()> { self.context.flushBuffer(); self.view.setNeedsDisplay(true); + Ok(()) } /// On macOS the `NSOpenGLView` needs to be resized separtely from our main view. diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs index 84af49f0..67f2a2fd 100644 --- a/src/platform/macos/mod.rs +++ b/src/platform/macos/mod.rs @@ -1,13 +1,15 @@ mod context; mod cursor; +mod error; mod keyboard; mod view; mod window; use crate::platform::macos::view::BaseviewView; -use crate::wrappers::appkit::{extract_raw_window_handle, View}; +use crate::wrappers::appkit::{extract_raw_window_handle, ParentWindowHandleError, View}; pub use context::WindowContext; use dispatch2::MainThreadBound; +pub use error::Error; use objc2::__framework_prelude::Retained; use objc2::rc::Weak; use objc2::MainThreadMarker; @@ -16,6 +18,7 @@ use raw_window_handle::{DisplayHandle, HasWindowHandle}; use std::fmt; use std::fmt::Formatter; pub use window::*; +pub(crate) type Result = std::result::Result; #[cfg(feature = "opengl")] pub mod gl; @@ -72,7 +75,11 @@ pub struct ParentWindowHandle { } impl ParentWindowHandle { - pub fn extract(window: &impl HasWindowHandle) -> Self { - Self { view: extract_raw_window_handle(window.window_handle().unwrap()).unwrap() } + pub fn extract( + window: &impl HasWindowHandle, + ) -> core::result::Result { + let view = extract_raw_window_handle(window.window_handle()?)?; + + Ok(Self { view }) } } diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index b13f601d..836515db 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -3,7 +3,8 @@ use super::keyboard::{make_modifiers, KeyboardState}; use super::window::WindowSharedState; use crate::handler::WindowHandlerBuilder; -use crate::platform::macos::context::WindowContext; +use crate::platform::*; +use crate::tracing::warn; use crate::wrappers::appkit::*; use crate::MouseEvent::{ButtonPressed, ButtonReleased}; use crate::{ @@ -48,7 +49,7 @@ impl BaseviewView { pub fn new( _options: WindowOpenOptions, builder: WindowHandlerBuilder, parenting: ViewParentingType, final_size: LogicalSize, mtm: MainThreadMarker, - ) -> (Retained>, Rc) { + ) -> Result<(Retained>, Rc)> { let view_rect = NSRect::new(NSPoint::ZERO, NSSize::new(final_size.width, final_size.height)); @@ -72,13 +73,15 @@ impl BaseviewView { // Set up parenting before handler setup match &view.parenting { ViewParentingType::Parented { parent_view } => { - let parent_view = parent_view.load().unwrap(); - parent_view.addSubview(view.view); + if let Some(parent_view) = parent_view.load() { + parent_view.addSubview(view.view); + } } ViewParentingType::Windowed { owned_window, .. } => { - let owned_window = owned_window.load().unwrap(); - owned_window.setContentView(Some(view.view)); - set_delegate(&owned_window, view.view); + if let Some(owned_window) = owned_window.load() { + owned_window.setContentView(Some(view.view)); + set_delegate(&owned_window, view.view); + } } } @@ -87,12 +90,12 @@ impl BaseviewView { #[cfg(feature = "opengl")] if let Some(gl_config) = _options.gl_config { - let gl_context = super::gl::GlContext::create(view.view, gl_config).unwrap(); + let gl_context = super::gl::GlContext::create(view.view, gl_config, view.mtm)?; let Ok(()) = view.gl_context.set(gl_context) else { unreachable!() }; } let context = WindowContext::new(view); - let handler = builder.build(crate::WindowContext::new(context)); + let handler = builder.build(crate::WindowContext::new(context))?; // Initialize handler view.window_handler.set(handler); @@ -121,9 +124,11 @@ impl BaseviewView { } }); view.notification_center_observer.set(Some(observer)); - }); - (view, state) + Ok(()) + })?; + + Ok((view, state)) } pub fn close(this: ViewRef) { @@ -178,7 +183,10 @@ impl BaseviewView { } fn trigger_frame(this: ViewRef) { - this.window_handler.use_handler(|h| h.on_frame()); + if let Some(Err(e)) = this.window_handler.use_handler(|h| h.on_frame()) { + warn!("Error while rendering frame: {}", e); + Self::close(this); + } } } @@ -222,12 +230,18 @@ impl ViewImpl for BaseviewView { if this.state.scale_factor.get() != current_scale_factor || this.state.size.get() != current_size { - this.state.size.set(current_size); + let previous = this.state.size.replace(current_size); this.state.scale_factor.set(current_scale_factor); - this.window_handler.use_handler(|h| { + let result = this.window_handler.use_handler(|h| { h.resized(WindowSize::from_logical(current_size, current_scale_factor)) }); + + if let Some(Err(e)) = result { + warn!("Window Handler failed to resize: {}", e); + this.state.size.set(previous); + Self::resize(this, previous.into()) + } } } diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index 6d343005..55590df9 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -28,7 +28,9 @@ impl Drop for WindowHandle { } impl WindowHandle { - pub fn create_window(mut options: WindowOpenOptions, handler: WindowHandlerBuilder) -> Self { + pub fn create_window( + mut options: WindowOpenOptions, handler: WindowHandlerBuilder, + ) -> Result { autoreleasepool(|_| { let Some(mtm) = MainThreadMarker::new() else { panic!("macOS: Windows can only be created on the main thread!") @@ -48,7 +50,7 @@ impl WindowHandle { pub fn create_window_parented( builder: WindowOpenOptions, handler: WindowHandlerBuilder, parent_view: Retained, mtm: MainThreadMarker, - ) -> Self { + ) -> Result { let parenting = ViewParentingType::Parented { parent_view: Weak::from_retained(&parent_view) }; @@ -56,14 +58,14 @@ impl WindowHandle { parent_view.window().map(|w| w.backingScaleFactor()).unwrap_or(1.0); let final_size = builder.size.to_logical(backing_scale_factor); - let (ns_view, state) = BaseviewView::new(builder, handler, parenting, final_size, mtm); + let (ns_view, state) = BaseviewView::new(builder, handler, parenting, final_size, mtm)?; - Self { mtm, state, _window: None, view: Weak::from_retained(&ns_view) } + Ok(Self { mtm, state, _window: None, view: Weak::from_retained(&ns_view) }) } pub fn create_window_standalone( builder: WindowOpenOptions, handler: WindowHandlerBuilder, mtm: MainThreadMarker, - ) -> Self { + ) -> Result { let app = NSApplication::sharedApplication(mtm); let window = create_window_with_options(&builder, mtm); @@ -75,9 +77,9 @@ impl WindowHandle { owned_window: Weak::from_retained(&window), }; - let (view, state) = BaseviewView::new(builder, handler, parenting, final_size, mtm); + let (view, state) = BaseviewView::new(builder, handler, parenting, final_size, mtm)?; - Self { mtm, state, view: Weak::from_retained(&view), _window: Some(window) } + Ok(Self { mtm, state, view: Weak::from_retained(&view), _window: Some(window) }) } pub fn run_until_closed(self) { diff --git a/src/platform/win/error.rs b/src/platform/win/error.rs new file mode 100644 index 00000000..e8d33297 --- /dev/null +++ b/src/platform/win/error.rs @@ -0,0 +1,31 @@ +use crate::HandlerError; +use std::fmt::Display; + +pub type Result = std::result::Result; + +#[derive(Debug)] +pub enum Error { + Win32(windows_core::Error), + Handler(HandlerError), +} + +impl From for Error { + fn from(value: windows_core::Error) -> Self { + Error::Win32(value) + } +} + +impl From for Error { + fn from(value: HandlerError) -> Self { + Error::Handler(value) + } +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Win32(e) => Display::fmt(e, f), + Error::Handler(e) => Display::fmt(e, f), + } + } +} diff --git a/src/platform/win/gl.rs b/src/platform/win/gl.rs index 10fddf0a..b38d74a2 100644 --- a/src/platform/win/gl.rs +++ b/src/platform/win/gl.rs @@ -5,38 +5,15 @@ use windows_core::{s, PCSTR}; use windows_sys::Win32::Graphics::OpenGL::wglGetProcAddress; use crate::gl::*; +use crate::warn; use crate::wrappers::win32::window::{ - with_dummy_window, HWnd, MissingExtensionFunctionError, OwnDeviceContext, PixelFormat, - PixelFormatAttribs, WglContext, WglExtra, + with_dummy_window, HWnd, OwnDeviceContext, PixelFormat, PixelFormatAttribs, WglContext, + WglExtra, }; use crate::wrappers::win32::LibraryModule; -#[derive(Debug)] -pub enum CreationFailedError { - Win32(windows_core::Error), - MissingWglExtension(MissingExtensionFunctionError), -} - -impl From for CreationFailedError { - fn from(err: windows_core::Error) -> Self { - CreationFailedError::Win32(err) - } -} - -impl From for CreationFailedError { - fn from(err: MissingExtensionFunctionError) -> Self { - CreationFailedError::MissingWglExtension(err) - } -} - pub type GlContext = Rc; -impl From for GlError { - fn from(e: CreationFailedError) -> Self { - GlError::CreationFailed(e) - } -} - pub struct GlContextInner { hdc: OwnDeviceContext, wgl_ctx: WglContext, @@ -44,7 +21,7 @@ pub struct GlContextInner { } impl GlContextInner { - pub fn create(window: HWnd, config: GlConfig) -> Result { + pub fn create(window: HWnd, config: GlConfig) -> Result { let gl_library = unsafe { LibraryModule::load(s!("opengl32.dll"))? }; // Create temporary window and context to load function pointers @@ -54,7 +31,7 @@ impl GlContextInner { let wgl_ctx = hdc.create_wgl_context()?; wgl_ctx.with_current(&hdc, WglExtra::load) - })??; + })?; // Create actual context let hdc = window.get_own_dc()?; @@ -64,19 +41,29 @@ impl GlContextInner { None => hdc.set_pixel_format(&PixelFormat::from_config(&config))?, } - let wgl_ctx = extra.create_context_for_config(&hdc, &config)?; + let wgl_ctx = match extra.create_context_for_config(&hdc, &config) { + Ok(wgl_ctx) => wgl_ctx, + Err(e) => { + warn!("Could not create OpenGL context from OpenGL config attributes: {}. Attempting fallback.", e); + hdc.create_wgl_context()? + } + }; - wgl_ctx.with_current(&hdc, || extra.set_vsync(config.vsync))??; + if let Err(e) | Ok(Err(e)) = wgl_ctx.with_current(&hdc, || extra.set_vsync(config.vsync)) { + warn!("Could not set vsync: {}", e); + } Ok(Self { hdc, wgl_ctx, gl_library }) } - pub unsafe fn make_current(&self) { - let _ = self.wgl_ctx.make_current(&self.hdc); + pub unsafe fn make_current(&self) -> Result<(), super::Error> { + self.wgl_ctx.make_current(&self.hdc)?; + Ok(()) } - pub unsafe fn make_not_current(&self) { - let _ = self.wgl_ctx.make_not_current(); + pub unsafe fn make_not_current(&self) -> Result<(), super::Error> { + self.wgl_ctx.make_not_current()?; + Ok(()) } pub fn get_proc_address(&self, symbol: &CStr) -> *const c_void { @@ -94,8 +81,9 @@ impl GlContextInner { core::ptr::null() } - pub fn swap_buffers(&self) { - let _ = self.hdc.swap_buffers(); + pub fn swap_buffers(&self) -> Result<(), super::Error> { + self.hdc.swap_buffers()?; + Ok(()) } } @@ -104,16 +92,25 @@ fn find_wgl_pixel_format( ) -> Option { let mut format_attribs = PixelFormatAttribs::from_config(config); - // TODO: log errors - if let Ok(Some(format)) = extra.choose_pixel_format_from_attribs(&format_attribs, dc) { - return Some(format); + match extra.choose_pixel_format_from_attribs(&format_attribs, dc) { + Ok(Some(format)) => return Some(format), + Err(e) => { + warn!("Could not choose optimal pixel format from GL configuration: {}", e); + return None; + } + Ok(None) => {} }; eprintln!("Warning: sRGB framebuffer not supported, falling back to non-sRGB"); format_attribs.set_without_srgb_ext(); - if let Ok(Some(format)) = extra.choose_pixel_format_from_attribs(&format_attribs, dc) { - return Some(format); + match extra.choose_pixel_format_from_attribs(&format_attribs, dc) { + Ok(Some(format)) => return Some(format), + Err(e) => { + warn!("Could not choose optimal pixel format from GL configuration: {}", e); + return None; + } + Ok(None) => {} }; None diff --git a/src/platform/win/mod.rs b/src/platform/win/mod.rs index f75b0d70..0580927c 100644 --- a/src/platform/win/mod.rs +++ b/src/platform/win/mod.rs @@ -1,4 +1,5 @@ mod drop_target; +mod error; mod hook; mod keyboard; mod window; @@ -6,9 +7,13 @@ mod window_state; use crate::wrappers::win32::h_instance::HInstance; use crate::wrappers::win32::window::HWnd; -use raw_window_handle::{DisplayHandle, HasWindowHandle, RawWindowHandle, Win32WindowHandle}; -use std::fmt::Debug; +pub use error::{Error, Result}; +use raw_window_handle::{ + DisplayHandle, HandleError, HasWindowHandle, RawWindowHandle, Win32WindowHandle, +}; +use std::fmt::{Debug, Display, Formatter}; use std::num::NonZeroIsize; +use std::ptr::NonNull; use std::rc::Rc; pub use window::*; @@ -50,12 +55,38 @@ pub struct ParentWindowHandle { } impl ParentWindowHandle { - pub fn extract(parent: &impl HasWindowHandle) -> Self { - let parent = match parent.window_handle().unwrap().as_raw() { + pub fn extract( + parent: &impl HasWindowHandle, + ) -> core::result::Result { + let parent = match parent.window_handle()?.as_raw() { RawWindowHandle::Win32(h) => h.hwnd, - h => panic!("unsupported parent handle {:?}", h), + h => return Err(ParentWindowHandleError::UnsupportedWindowHandleType(h)), }; - Self { handle: unsafe { HWnd::from_raw(parent.get() as _) } } + let parent = NonNull::new(parent.get() as _).unwrap(); + + Ok(Self { handle: unsafe { HWnd::from_raw(parent) } }) + } +} + +pub enum ParentWindowHandleError { + HandleError(HandleError), + UnsupportedWindowHandleType(RawWindowHandle), +} + +impl From for ParentWindowHandleError { + fn from(err: HandleError) -> Self { + Self::HandleError(err) + } +} + +impl Display for ParentWindowHandleError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ParentWindowHandleError::HandleError(e) => Display::fmt(e, f), + ParentWindowHandleError::UnsupportedWindowHandleType(h) => { + write!(f, "Unsupported window handle type on Win32: {h:?}") + } + } } } diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 2f119adf..8f5abbee 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -1,4 +1,4 @@ -use windows_core::{ComObject, Result, HSTRING}; +use windows_core::{ComObject, HSTRING}; use windows_sys::Win32::{ Foundation::{LPARAM, LRESULT, RECT, WPARAM}, UI::{ @@ -13,6 +13,7 @@ use windows_sys::Win32::{ }, }; +use crate::warn; use dpi::{PhysicalPosition, PhysicalSize, Size}; use std::cell::Cell; use std::num::NonZeroUsize; @@ -23,6 +24,7 @@ use super::drop_target::DropTarget; use super::*; use crate::handler::WindowHandlerBuilder; use crate::platform::win::window_state::WindowState; +use crate::platform::Error; use crate::wrappers::win32::cursor::SystemCursor; use crate::wrappers::win32::window::*; use crate::wrappers::win32::{ @@ -98,7 +100,7 @@ pub struct BaseviewWindow { } impl WindowImpl for BaseviewWindow { - fn after_create(&self, window: HWnd) -> Result<()> { + fn after_create(&self, window: HWnd) -> core::result::Result<(), Error> { let hwnd = window.as_raw(); let window_state = &self.window_state; @@ -106,11 +108,9 @@ impl WindowImpl for BaseviewWindow { // Now we can get the actual dpi of the window. let dpi = window.get_dpi(&self.window_state.user32)?; - let mut dpi_changed = false; if dpi != window_state.current_dpi.get() { window_state.current_dpi.set(dpi); - dpi_changed = true; // If the user's requested initial size was in system-scaled logical pixels if let WindowScalePolicy::SystemScaleFactor = self.window_state.scale_policy { @@ -145,15 +145,10 @@ impl WindowImpl for BaseviewWindow { let handler = { let context = crate::WindowContext::new(Rc::clone(&self.window_state)); - self.handler_builder.take().unwrap().build(context) + self.handler_builder.take().unwrap().build(context)? }; let Ok(()) = window_state.handler.set(handler) else { unreachable!() }; - if dpi_changed { - // Send an initial Resized event so users get the correct scale factor and physical size. - self.window_state.send_resized(); - } - Ok(()) } @@ -345,10 +340,17 @@ unsafe fn wnd_proc_inner( return None; } - window_state.current_size.set(new_size); + let previous = window_state.current_size.replace(new_size); + let new_size = WindowSize::from_physical(new_size, window_state.scale_factor()); + + let handler = window_state.handler.get()?; + if let Err(e) = handler.resized(new_size) { + warn!("Window Handler failed to resize: {}", e); + window_state.current_size.set(previous); - if let Some(handler) = window_state.handler.get() { - handler.resized(WindowSize::from_physical(new_size, window_state.scale_factor())) + if let Err(e) = window_state.resize(previous.into()) { + warn!("Failed to resize back to previous window size: {}", e); + } } None @@ -367,15 +369,25 @@ unsafe fn wnd_proc_inner( let changed = window_state.current_size.get() != new_size || window_state.current_dpi.get() != dpi; - window_state.current_dpi.set(dpi); - window_state.current_size.set(new_size); + window_state.current_dpi.replace(dpi); + let previous_size = window_state.current_size.replace(new_size); // Windows makes us resize the window manually. This however will not send a WM_SIZE event, // hence why we are notifying the window handler manually below. let _ = window.set_nc_rect(suggested_nc_rect); if changed { - window_state.send_resized(); + let handler = window_state.handler.get()?; + let new_size = WindowSize::from_physical(new_size, dpi.scale_factor()); + + if let Err(e) = handler.resized(new_size) { + warn!("Window Handler failed to resize: {}", e); + window_state.current_size.set(previous_size); + + if let Err(e) = window_state.resize(previous_size.into()) { + warn!("Failed to resize back to previous window size: {}", e); + } + } } None @@ -407,8 +419,10 @@ unsafe fn wnd_proc_inner( } impl WindowHandle { - pub fn create_window(options: WindowOpenOptions, build: WindowHandlerBuilder) -> WindowHandle { - let extended_user_32 = ExtendedUser32::load().unwrap(); + pub fn create_window( + options: WindowOpenOptions, build: WindowHandlerBuilder, + ) -> Result { + let extended_user_32 = ExtendedUser32::load()?; let title = HSTRING::from(options.title); let scaling_factor = match options.scale { @@ -423,10 +437,9 @@ impl WindowHandle { } else { WindowStyle::embedded() }; - let dpi_ctx = DpiAwarenessContext::new(&extended_user_32).unwrap(); + let dpi_ctx = DpiAwarenessContext::new(&extended_user_32)?; - let rect = - dpi_ctx.client_area_to_nc_area(window_size.into(), style, Dpi::default()).unwrap(); + let rect = dpi_ctx.client_area_to_nc_area(window_size.into(), style, Dpi::default())?; let is_open = Rc::new(Cell::new(true)); @@ -455,19 +468,18 @@ impl WindowHandle { let parent = options.parent.map(|p| p.handle); - let window = - create_window(&title, style, rect.size(), parent, &dpi_ctx, initializer).unwrap(); + let window = create_window(&title, style, rect.size(), parent, &dpi_ctx, initializer)?; // FIXME: this SetTimer call could be in after_create, but for some reason it changes the ordering // for a parent+child window situation, which results in the parent drawing over the child. // This timer should be replaced by proper window redrawing/damage/vsync handling, but this // would be a breaking change, so we'll do that later. // TODO: create a new timer instead of hard-coding a specific ID - window.set_timer(WIN_FRAME_TIMER, 15).unwrap(); + window.set_timer(WIN_FRAME_TIMER, 15)?; window.show_and_activate(); - WindowHandle { hwnd: Some(window).into(), is_open: Rc::clone(&is_open) } + Ok(WindowHandle { hwnd: Some(window).into(), is_open: Rc::clone(&is_open) }) } } diff --git a/src/platform/win/window_state.rs b/src/platform/win/window_state.rs index 14f4fecd..2e56ab1c 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -1,5 +1,6 @@ use crate::platform::win::keyboard::KeyboardState; use crate::platform::PlatformHandle; +use crate::warn; use crate::wrappers::win32::cursor::SystemCursor; use crate::wrappers::win32::h_instance::HInstance; use crate::wrappers::win32::window::HWnd; @@ -56,7 +57,10 @@ impl WindowState { pub(crate) fn handle_on_frame(&self) { let Some(handler) = self.handler.get() else { return }; - handler.on_frame() + if let Err(e) = handler.on_frame() { + warn!("Error while rendering frame: {}", e); + self.request_close(); + } } pub(crate) fn handle_event(&self, event: Event) -> EventStatus { @@ -82,13 +86,6 @@ impl WindowState { self.keyboard_state.borrow() } - pub fn send_resized(&self) { - if let Some(handler) = self.handler.get() { - handler - .resized(WindowSize::from_physical(self.current_size.get(), self.scale_factor())); - } - } - pub fn request_close(&self) { unsafe { PostMessageW( @@ -104,24 +101,28 @@ impl WindowState { HWnd::get_focused_window() == self.hwnd.as_raw() } - pub fn focus(&self) { - self.hwnd.set_focus().unwrap() + pub fn focus(&self) -> Result<(), super::Error> { + self.hwnd.set_focus()?; + Ok(()) } - pub fn resize(&self, size: Size) { + pub fn resize(&self, size: Size) -> Result<(), super::Error> { // `self.window_info` will be modified in response to the `WM_SIZE` event that // follows the `SetWindowPos()` call let dpi = self.current_dpi.get(); let new_size = size.to_physical(dpi.scale_factor()); - self.hwnd.resize_and_activate(new_size, dpi, &self.user32).unwrap(); + self.hwnd.resize_and_activate(new_size, dpi, &self.user32)?; + Ok(()) } - pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) { + pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) -> Result<(), super::Error> { self.cursor_icon.set(mouse_cursor); if let Ok(cursor) = SystemCursor::load(mouse_cursor) { cursor.set() } + + Ok(()) } #[cfg(feature = "opengl")] diff --git a/src/platform/x11/cursor.rs b/src/platform/x11/cursor.rs index 4c620c94..035ee36b 100644 --- a/src/platform/x11/cursor.rs +++ b/src/platform/x11/cursor.rs @@ -1,13 +1,12 @@ -use std::error::Error; - use x11rb::connection::Connection; use x11rb::cursor::Handle as CursorHandle; use x11rb::protocol::xproto::{ConnectionExt as _, Cursor}; use x11rb::xcb_ffi::XCBConnection; +use crate::platform::*; use crate::MouseCursor; -fn create_empty_cursor(conn: &XCBConnection, screen: usize) -> Result> { +fn create_empty_cursor(conn: &XCBConnection, screen: usize) -> Result { let cursor_id = conn.generate_id()?; let pixmap_id = conn.generate_id()?; let root_window = conn.setup().roots[screen].root; @@ -20,7 +19,7 @@ fn create_empty_cursor(conn: &XCBConnection, screen: usize) -> Result Result, Box> { +) -> Result> { let cursor = cursor_handle.load_cursor(conn, name)?; if cursor != x11rb::NONE { Ok(Some(cursor)) @@ -31,7 +30,7 @@ fn load_cursor( fn load_first_existing_cursor( conn: &XCBConnection, cursor_handle: &CursorHandle, names: &[&str], -) -> Result, Box> { +) -> Result> { for name in names { let cursor = load_cursor(conn, cursor_handle, name)?; if cursor.is_some() { @@ -44,7 +43,7 @@ fn load_first_existing_cursor( pub(crate) fn get_xcursor( conn: &XCBConnection, screen: usize, cursor_handle: &CursorHandle, cursor: MouseCursor, -) -> Result> { +) -> Result { let load = |name: &str| load_cursor(conn, cursor_handle, name); let loadn = |names: &[&str]| load_first_existing_cursor(conn, cursor_handle, names); diff --git a/src/platform/x11/drag_n_drop.rs b/src/platform/x11/drag_n_drop.rs index 7e16ad0f..892d4246 100644 --- a/src/platform/x11/drag_n_drop.rs +++ b/src/platform/x11/drag_n_drop.rs @@ -1,3 +1,10 @@ +use super::xcb_connection::{Atoms, GetPropertyError}; +use super::*; +use crate::handler::WindowHandler; +use crate::platform::x11::error::ReplyExt; +use crate::warn; +use crate::{DropData, Event, MouseEvent}; +use core::result::Result; use dpi::PhysicalPosition; use keyboard_types::Modifiers; use percent_encoding::percent_decode; @@ -9,18 +16,14 @@ use std::{ str::Utf8Error, }; use x11rb::connection::Connection; -use x11rb::errors::ReplyError; +use x11rb::cookie::VoidCookie; +use x11rb::errors::ConnectionError; use x11rb::protocol::xproto::{Atom, ClientMessageEvent, SelectionNotifyEvent, Timestamp}; +use x11rb::xcb_ffi::XCBConnection; use x11rb::{ - errors::ConnectionError, protocol::xproto::{self, ConnectionExt}, x11_utils::Serialize, }; - -use super::xcb_connection::{Atoms, GetPropertyError}; -use super::*; -use crate::handler::WindowHandler; -use crate::{DropData, Event, MouseEvent}; use DragNDropState::*; /// The Drag-N-Drop session state of a `baseview` X11 window, for which it is the target. @@ -88,11 +91,13 @@ pub(crate) enum DragNDropState { }, } +// Note: For all X11 operations on this type, only ConnectionErrors are considered fatal. +// Other errors (protocol errors, transfer errors) should be dealt with as gracefully as possible. impl DragNDropState { pub fn handle_enter_event( &mut self, window: &WindowInner, handler: &mut dyn WindowHandler, event: &ClientMessageEvent, - ) -> Result<(), GetPropertyError> { + ) -> Result<(), ConnectionError> { let data = event.data.as_data32(); let source_window = data[0] as xproto::Window; @@ -107,13 +112,23 @@ impl DragNDropState { let supported_types = if !has_more_types { &data[2..5] } else { - extra_types = window.connection.get_property( + let result = window.connection.get_property( source_window, window.connection.atoms.XdndTypeList, xproto::Atom::from(xproto::AtomEnum::ATOM), - )?; + ); - &extra_types + match result { + Err(GetPropertyError::ConnectionError(e)) => return Err(e), + Ok(v) => { + extra_types = v; + &extra_types + } + Err(e) => { + warn!("XdndEnter: Failed to fetch XdndTypeList from source window {source_window}: {}", e); + &data[2..5] + } + } }; // We only support the TextUriList type @@ -142,7 +157,7 @@ impl DragNDropState { pub fn handle_position_event( &mut self, window: &WindowInner, handler: &mut dyn WindowHandler, event: &ClientMessageEvent, - ) -> Result<(), ReplyError> { + ) -> Result<(), ConnectionError> { let event_data = event.data.as_data32(); let event_source_window = event_data[0] as xproto::Window; @@ -171,20 +186,29 @@ impl DragNDropState { // This is the position event we were waiting for. Now we can request the selection data. // The code above already checks that source_window == event_source_window. - WaitingForPosition { protocol_version, source_window: _ } => { + WaitingForPosition { protocol_version, source_window } => { + let protocol_version = *protocol_version; + let source_window = *source_window; + // In version 0, time isn't specified - let timestamp = (*protocol_version >= 1).then_some(event_data[3] as Timestamp); + let timestamp = (protocol_version >= 1).then_some(event_data[3] as Timestamp); // In version <2, action isn't specified - let requested_action = (*protocol_version >= 2).then_some(event_data[4] as Atom); + let requested_action = (protocol_version >= 2).then_some(event_data[4] as Atom); let requested_action = requested_action.map(|a| { DndAction::from_atom(a, &window.connection.atoms).unwrap_or(DndAction::Private) }); - request_convert_selection(window, timestamp)?; + if let Err(e) = request_convert_selection(window, timestamp)?.check() { + warn!("XdndPosition: ConvertSelection request failed: {}", e); + + // Permanently reject the drop if we couldn't convert selection + *self = PermanentlyRejected { source_window }; + send_status_rejected(event_source_window, window)?; + } // We set our state before translating position data, in case that fails. *self = WaitingForData { - protocol_version: *protocol_version, + protocol_version, requested_at: timestamp, source_window: event_source_window, position: PhysicalPosition::new(0, 0), @@ -193,14 +217,19 @@ impl DragNDropState { }; let WaitingForData { position, .. } = self else { unreachable!() }; - *position = translate_root_coordinates(window, event_x, event_y)?; + + if let Some(result) = translate_root_coordinates(window, event_x, event_y)? { + *position = result; + } Ok(()) } // We are still waiting for the data. So we'll just update the position in the meantime. WaitingForData { position, .. } => { - *position = translate_root_coordinates(window, event_x, event_y)?; + if let Some(result) = translate_root_coordinates(window, event_x, event_y)? { + *position = result; + } Ok(()) } @@ -208,13 +237,11 @@ impl DragNDropState { // We have already received the data. We can update the position and notify the handler Ready { protocol_version, position, data, requested_action, .. } => { // Inform the source that we are still accepting the drop. - // Do this first, in case translate_root_coordinates fails, or the handler panics. - // Do not return right away on failure though, we can still inform the handler about - // the new position. - let status_result = - send_status_event(event_source_window, window, *requested_action); + send_status_event(event_source_window, window, *requested_action)?; - *position = translate_root_coordinates(window, event_x, event_y)?; + if let Some(result) = translate_root_coordinates(window, event_x, event_y)? { + *position = result; + } // In version <2, action isn't specified *requested_action = if *protocol_version < 2 { @@ -230,7 +257,6 @@ impl DragNDropState { modifiers: Modifiers::empty(), })); - status_result?; Ok(()) } } @@ -279,7 +305,7 @@ impl DragNDropState { match self { // Someone sent us a position event without first sending an enter event. // Weird, but we'll still politely tell them we reject the drop. - NoCurrentSession => send_finished_rejected(event_source_window, window), + NoCurrentSession => send_finished_rejected(event_source_window, window)?, // The current session's source window does not match the given event. // This means it can either be from a stale session, or a misbehaving app. @@ -290,7 +316,7 @@ impl DragNDropState { | Ready { source_window, .. } if *source_window != event_source_window => { - send_finished_rejected(event_source_window, window) + send_finished_rejected(event_source_window, window)?; } // We decided to permanently reject this drop. @@ -298,13 +324,13 @@ impl DragNDropState { PermanentlyRejected { .. } => { *self = NoCurrentSession; - send_finished_rejected(event_source_window, window) + send_finished_rejected(event_source_window, window)? } // We received a drop event without any position event. That's very weird, but not // irrecoverable: the drop event provides enough data as it is. // The code above already checks that source_window == event_source_window. - WaitingForPosition { protocol_version, source_window: _ } => { + WaitingForPosition { protocol_version, source_window } => { // In version 0, time isn't specified let timestamp = (*protocol_version >= 1).then_some(data[2] as Timestamp); @@ -313,15 +339,12 @@ impl DragNDropState { // If we fail to send the request when the drop has completed, we can't do anything. // Just cancel the drop. - if let Err(e) = request_convert_selection(window, timestamp) { - *self = NoCurrentSession; + if let Err(e) = request_convert_selection(window, timestamp)?.check() { + warn!("XdndDrop: ConvertSelection request failed: {}", e); + *self = PermanentlyRejected { source_window: *source_window }; // Try to inform the source that we ended up rejecting the drop. - // If the initial request failed, this is likely to fail too, so we'll ignore - // it if it errors, so we can focus on the original error. - let _ = send_finished_rejected(event_source_window, window); - - return Err(e); + return send_finished_rejected(event_source_window, window); }; *self = WaitingForData { @@ -334,8 +357,6 @@ impl DragNDropState { requested_action: Some(DndAction::Private), dropped: true, }; - - Ok(()) } // We are still waiting to receive the data. @@ -354,8 +375,6 @@ impl DragNDropState { // Indicate to the selection_notified handler that the user has performed the drop. // Now it should complete the drop instead of just waiting for more events. *dropped = true; - - Ok(()) } // The normal case. @@ -366,10 +385,7 @@ impl DragNDropState { unreachable!() }; - // Don't return immediately if sending the reply fails, we can still notify the window - // handler about the drop. - let reply_result = - send_finished_event(event_source_window, window, requested_action); + send_finished_event(event_source_window, window, requested_action)?.check_warn(); handler.on_event(Event::Mouse(MouseEvent::DragDropped { position: position.cast(), @@ -377,10 +393,10 @@ impl DragNDropState { // We don't get modifiers for drag n drop events. modifiers: Modifiers::empty(), })); - - reply_result } - } + }; + + Ok(()) } pub fn handle_selection_notify_event( @@ -413,19 +429,17 @@ impl DragNDropState { } // The sender should have set the data on our window, let's fetch it. - match fetch_dnd_data(window) { - Err(_e) => { + match fetch_dnd_data(window)? { + None => { *self = PermanentlyRejected { source_window }; if dropped { - send_finished_rejected(source_window, window) + send_finished_rejected(source_window, window)?; } else { - send_status_rejected(source_window, window) + send_status_rejected(source_window, window)?; } - - // TODO: Log warning } - Ok(data) => { + Some(data) => { // Inform the source that we are (still) accepting the drop. // Handle the case where the user already dropped, but we only received the data later. @@ -449,7 +463,7 @@ impl DragNDropState { modifiers: Modifiers::empty(), })); - reply_result + reply_result?.check_warn(); } else { // Save the data, now that we finally have it! *self = Ready { @@ -470,10 +484,12 @@ impl DragNDropState { modifiers: Modifiers::empty(), })); - reply_result + reply_result?; } } } + + Ok(()) } } @@ -491,9 +507,11 @@ fn send_status_rejected( type_: conn.atoms.XdndStatus, }; - conn.conn.send_event(false, source_window, xproto::EventMask::NO_EVENT, event.serialize())?; + conn.conn + .send_event(false, source_window, xproto::EventMask::NO_EVENT, event.serialize())? + .check_warn(); - conn.conn.flush() + Ok(()) } fn send_status_event( @@ -515,7 +533,8 @@ fn send_status_event( conn.conn.send_event(false, source_window, xproto::EventMask::NO_EVENT, event.serialize())?; - conn.conn.flush() + conn.conn.flush()?; + Ok(()) } pub fn send_finished_rejected( @@ -532,14 +551,16 @@ pub fn send_finished_rejected( type_: conn.atoms.XdndFinished as _, }; - conn.conn.send_event(false, source_window, xproto::EventMask::NO_EVENT, event.serialize())?; + conn.conn + .send_event(false, source_window, xproto::EventMask::NO_EVENT, event.serialize())? + .check_warn(); - conn.conn.flush() + Ok(()) } fn send_finished_event( source_window: xproto::Window, window: &WindowInner, action: Option, -) -> Result<(), ConnectionError> { +) -> Result, ConnectionError> { let conn = &window.connection; let action = action.map(|a| a.to_atom(&window.connection.atoms)).unwrap_or(window.connection.atoms.None); @@ -553,25 +574,19 @@ fn send_finished_event( type_: conn.atoms.XdndFinished as _, }; - conn.conn.send_event(false, source_window, xproto::EventMask::NO_EVENT, event.serialize())?; - - conn.conn.flush() + conn.conn.send_event(false, source_window, xproto::EventMask::NO_EVENT, event.serialize()) } fn request_convert_selection( window: &WindowInner, timestamp: Option, -) -> Result<(), ConnectionError> { - let conn = &window.connection; - - conn.conn.convert_selection( +) -> Result, ConnectionError> { + window.connection.conn.convert_selection( window.xcb_window.id().get(), - conn.atoms.XdndSelection, - conn.atoms.TextUriList, - conn.atoms.XdndSelection, + window.connection.atoms.XdndSelection, + window.connection.atoms.TextUriList, + window.connection.atoms.XdndSelection, timestamp.unwrap_or(x11rb::CURRENT_TIME), - )?; - - conn.conn.flush() + ) } fn decode_xy(data: u32) -> (u16, u16) { @@ -580,36 +595,49 @@ fn decode_xy(data: u32) -> (u16, u16) { fn translate_root_coordinates( window: &WindowInner, x: u16, y: u16, -) -> Result, ReplyError> { +) -> Result>, ConnectionError> { let root_id = window.connection.screen().root; let x = x.try_into().unwrap_or(i16::MAX); let y = y.try_into().unwrap_or(i16::MAX); if root_id == window.xcb_window.id().get() { - return Ok(PhysicalPosition::new(x, y)); + return Ok(Some(PhysicalPosition::new(x, y))); } let reply = window .connection .conn .translate_coordinates(root_id, window.xcb_window.id().get(), x, y)? - .reply()?; + .reply_or_warn(); + + let Some(reply) = reply else { return Ok(None) }; - Ok(PhysicalPosition::new(reply.dst_x, reply.dst_y)) + Ok(Some(PhysicalPosition::new(reply.dst_x, reply.dst_y))) } -fn fetch_dnd_data(window: &WindowInner) -> Result> { +fn fetch_dnd_data(window: &WindowInner) -> Result, ConnectionError> { let conn = &window.connection; - let data: Vec = conn.get_property( + let data: Vec = match conn.get_property( window.xcb_window.id().get(), conn.atoms.XdndSelection, conn.atoms.TextUriList, - )?; - - let path_list = parse_data(&data)?; + ) { + Ok(data) => data, + Err(GetPropertyError::ConnectionError(e)) => return Err(e), + Err(e) => { + warn!("{}", e); + return Ok(None); + } + }; - Ok(DropData::Files(path_list)) + match parse_data(&data) { + Ok(path_list) => Ok(Some(DropData::Files(path_list))), + Err(e) => { + warn!("{}", e); + Ok(None) + } + } } // See: https://edeproject.org/spec/file-uri-spec.txt @@ -650,7 +678,7 @@ fn parse_data(data: &[u8]) -> Result, ParseError> { } #[derive(Debug)] -enum ParseError { +pub enum ParseError { EmptyData, InvalidUtf8(Utf8Error), UnsupportedHostname(String), diff --git a/src/platform/x11/error.rs b/src/platform/x11/error.rs new file mode 100644 index 00000000..e8e64c17 --- /dev/null +++ b/src/platform/x11/error.rs @@ -0,0 +1,167 @@ +use crate::platform::x11::drag_n_drop::ParseError; +use crate::platform::x11::xcb_connection::GetPropertyError; +use crate::warn; +use crate::wrappers::xlib::{DisplayOpenFailedError, InitThreadsFailedError}; +use crate::HandlerError; +use std::sync::mpsc::RecvError; +use x11_dl::error::OpenError; +use x11rb::connection::RequestConnection; +use x11rb::cookie::{Cookie, VoidCookie}; +use x11rb::errors::{ConnectError, ConnectionError, ReplyError, ReplyOrIdError}; +use x11rb::x11_utils::{TryParse, X11Error}; + +#[derive(Debug)] +pub enum Error { + CreationFailed(String), + Io(std::io::Error), + DylibOpen(OpenError), + InitThreadsFailed(InitThreadsFailedError), + X11(X11Error), + Connection(ConnectionError), + IdsExhausted, + Parse(ParseError), + GetProperty(GetPropertyError), + Connect(ConnectError), + DisplayOpenFailed(DisplayOpenFailedError), + Handler(HandlerError), + Channel(RecvError), + #[cfg(feature = "opengl")] + XLib(crate::wrappers::xlib::XLibError), + #[cfg(feature = "opengl")] + Gl(super::gl::CreationFailedError), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Io(e) => e.fmt(f), + Self::IdsExhausted => f.write_str("X11 IDs have been exhausted"), + _ => todo!(), + } + } +} + +impl std::error::Error for Error {} + +impl From for Error { + fn from(value: std::io::Error) -> Self { + Self::Io(value) + } +} + +impl From for Error { + fn from(value: OpenError) -> Self { + Self::DylibOpen(value) + } +} + +impl From for Error { + fn from(value: InitThreadsFailedError) -> Self { + Self::InitThreadsFailed(value) + } +} + +impl From for Error { + fn from(value: DisplayOpenFailedError) -> Self { + Self::DisplayOpenFailed(value) + } +} + +impl From for Error { + fn from(value: ConnectionError) -> Self { + Self::Connection(value) + } +} + +impl From for Error { + fn from(value: X11Error) -> Self { + Self::X11(value) + } +} + +impl From for Error { + fn from(value: HandlerError) -> Self { + Self::Handler(value) + } +} + +#[cfg(feature = "opengl")] +impl From for Error { + fn from(value: crate::wrappers::xlib::XLibError) -> Self { + Self::XLib(value) + } +} + +impl From for Error { + fn from(value: ParseError) -> Self { + Self::Parse(value) + } +} + +impl From for Error { + fn from(value: GetPropertyError) -> Self { + Self::GetProperty(value) + } +} + +impl From for Error { + fn from(value: ConnectError) -> Self { + Self::Connect(value) + } +} + +// X11rb aggregate error types + +impl From for Error { + fn from(value: ReplyOrIdError) -> Self { + match value { + ReplyOrIdError::IdsExhausted => Self::IdsExhausted, + ReplyOrIdError::ConnectionError(e) => Self::Connection(e), + ReplyOrIdError::X11Error(e) => Self::X11(e), + } + } +} + +impl From for Error { + fn from(value: ReplyError) -> Self { + match value { + ReplyError::ConnectionError(e) => Self::Connection(e), + ReplyError::X11Error(e) => Self::X11(e), + } + } +} + +#[cfg(feature = "opengl")] +impl From for Error { + fn from(value: super::gl::CreationFailedError) -> Self { + Self::Gl(value) + } +} + +pub trait CookieExt { + fn check_warn(self); +} + +impl CookieExt for VoidCookie<'_, T> { + fn check_warn(self) { + if let Err(e) = self.check() { + warn!("{}", e); + } + } +} + +pub trait ReplyExt { + fn reply_or_warn(self) -> Option; +} + +impl ReplyExt for Cookie<'_, C, R> { + fn reply_or_warn(self) -> Option { + match self.reply() { + Ok(r) => Some(r), + Err(e) => { + warn!("{}", e); + None + } + } + } +} diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 34139729..e5ce2edc 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -2,14 +2,15 @@ use super::drag_n_drop::DragNDropState; use super::keyboard::{convert_key_press_event, convert_key_release_event, key_mods}; use super::*; +use crate::warn; use crate::wrappers::connection_poller::{ConnectionPoller, PollStatus}; use crate::wrappers::xkbcommon::XkbcommonState; use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowHandler, WindowSize}; use dpi::{PhysicalPosition, PhysicalSize}; -use std::error::Error; use std::rc::Rc; use std::time::{Duration, Instant}; use x11rb::connection::Connection; +use x11rb::errors::ConnectionError; use x11rb::protocol::Event as XEvent; pub(crate) struct EventLoop { @@ -29,9 +30,10 @@ pub(crate) struct EventLoop { impl EventLoop { pub fn new( window: Rc, handler: Box, - parent_handle: Option, xkb_state: Option, + parent_handle: Option, ) -> Self { Self { + xkb_state: XkbcommonState::new(&window.connection), window, handler, parent_handle, @@ -39,12 +41,15 @@ impl EventLoop { event_loop_running: false, new_physical_size: None, drag_n_drop: DragNDropState::NoCurrentSession, - xkb_state, } } + pub fn window_id(&self) -> NonZeroU32 { + self.window.xcb_window.id() + } + #[inline] - fn drain_xcb_events(&mut self) -> Result<(), Box> { + fn drain_xcb_events(&mut self) -> core::result::Result<(), ConnectionError> { // the X server has a tendency to send spurious/extraneous configure notify events when a // window is resized, and we need to batch those together and just send one resize event // when they've all been coalesced. @@ -55,18 +60,23 @@ impl EventLoop { } if let Some(size) = self.new_physical_size.take() { - self.window.window_size.set(size); + let previous = self.window.window_size.replace(size); let scale_factor = self.window.scaling_factor.get(); - - self.handler.resized(WindowSize::from_physical(size.cast(), scale_factor)); + if let Err(e) = + self.handler.resized(WindowSize::from_physical(size.cast(), scale_factor)) + { + warn!("Window Handler failed to resize: {}", e); + self.window.window_size.set(previous); + self.window.xcb_window.resize(previous.cast())?.check_warn(); + } } Ok(()) } // Event loop - pub fn run(&mut self) -> Result<(), Box> { + pub fn run(mut self) -> Result<()> { let connection = Rc::clone(&self.window.connection); let mut poller = ConnectionPoller::new(&connection.conn)?; @@ -82,7 +92,7 @@ impl EventLoop { // if it's already time to draw a new frame. let next_frame = last_frame + self.frame_interval; if Instant::now() >= next_frame { - self.handler.on_frame(); + self.handler.on_frame()?; last_frame = Instant::max(next_frame, Instant::now() - self.frame_interval); } @@ -91,7 +101,7 @@ impl EventLoop { self.drain_xcb_events()?; // FIXME: handle errors - if let PollStatus::ReadAvailable = poller.wait(next_frame).unwrap() { + if let PollStatus::ReadAvailable = poller.wait(next_frame)? { self.drain_xcb_events()?; } diff --git a/src/platform/x11/gl.rs b/src/platform/x11/gl.rs index 11b7adaa..29b73a36 100644 --- a/src/platform/x11/gl.rs +++ b/src/platform/x11/gl.rs @@ -20,18 +20,6 @@ pub enum CreationFailedError { OpenError(OpenError), } -impl From for GlError { - fn from(e: XLibError) -> Self { - GlError::CreationFailed(CreationFailedError::X11Error(e)) - } -} - -impl From for GlError { - fn from(e: OpenError) -> Self { - GlError::CreationFailed(CreationFailedError::OpenError(e)) - } -} - pub type GlContext = Rc; pub struct GlContextInner { @@ -66,18 +54,18 @@ impl GlContextInner { /// Use [Self::get_fb_config_and_visual] to create both of these things. pub fn create( window: &XcbWindow, connection: Rc, config: FbConfig, - ) -> Result, GlError> { + ) -> Result> { let glx = Glx::open()?; let xlib_connection = connection.conn.xlib_connection(); XErrorHandler::handle(xlib_connection, |error_handler| { let Some(create_context) = glx.get_glx_create_context_attribs_arb() else { - return Err(GlError::CreationFailed(CreationFailedError::GetProcAddressFailed)); + return Err(CreationFailedError::GetProcAddressFailed.into()); }; let Some(swap_interval) = glx.get_glx_swap_interval_ext() else { - return Err(GlError::CreationFailed(CreationFailedError::GetProcAddressFailed)); + return Err(CreationFailedError::GetProcAddressFailed.into()); }; let context = create_context.call( @@ -122,7 +110,7 @@ impl GlContextInner { /// using the visual also returned from this function. pub fn get_fb_config_and_visual( connection: &X11Connection, config: GlConfig, - ) -> Result<(FbConfig, WindowConfig), GlError> { + ) -> Result<(FbConfig, WindowConfig)> { let glx = Glx::open()?; let xlib_connection = connection.conn.xlib_connection(); @@ -142,22 +130,20 @@ impl GlContextInner { }) } - pub unsafe fn make_current(&self) { + pub unsafe fn make_current(&self) -> Result<()> { XErrorHandler::handle(self.connection.conn.xlib_connection(), |error_handler| { - self.glx - .make_current( - self.connection.conn.xlib_connection(), - self.window_id(), - self.context, - error_handler, - ) - .unwrap(); + self.glx.make_current( + self.connection.conn.xlib_connection(), + self.window_id(), + self.context, + error_handler, + ) }) } - pub unsafe fn make_not_current(&self) { + pub unsafe fn make_not_current(&self) -> Result<()> { XErrorHandler::handle(self.connection.conn.xlib_connection(), |error_handler| { - self.glx.clear_current(self.connection.conn.xlib_connection(), error_handler).unwrap(); + self.glx.clear_current(self.connection.conn.xlib_connection(), error_handler) }) } @@ -172,15 +158,13 @@ impl GlContextInner { } } - pub fn swap_buffers(&self) { + pub fn swap_buffers(&self) -> Result<()> { XErrorHandler::handle(self.connection.conn.xlib_connection(), |error_handler| { - self.glx - .swap_buffers( - self.connection.conn.xlib_connection(), - self.window_id(), - error_handler, - ) - .unwrap() + self.glx.swap_buffers( + self.connection.conn.xlib_connection(), + self.window_id(), + error_handler, + ) }) } } diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index 9fe96882..858915e8 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -1,8 +1,10 @@ mod xcb_connection; -use raw_window_handle::{DisplayHandle, HasWindowHandle, RawWindowHandle, XcbWindowHandle}; -use std::fmt::Formatter; -use std::num::{NonZero, NonZeroU32}; +use raw_window_handle::{ + DisplayHandle, HandleError, HasWindowHandle, RawWindowHandle, XcbWindowHandle, +}; +use std::fmt::{Display, Formatter}; +use std::num::{NonZero, NonZeroU32, TryFromIntError}; use std::rc::Rc; use std::sync::Arc; pub(crate) use xcb_connection::X11Connection; @@ -12,6 +14,7 @@ pub use window::*; mod cursor; mod drag_n_drop; +mod error; mod event_loop; mod keyboard; mod visual_info; @@ -19,6 +22,9 @@ mod xcb_window; mod window_shared; +pub use error::{CookieExt as _, Error}; +pub(crate) type Result = std::result::Result; + use crate::platform::x11::window_shared::WindowInner; use crate::wrappers::xlib::XlibXcbConnection; @@ -31,13 +37,13 @@ pub mod gl; pub struct PlatformHandle { connection: Arc, window_id: NonZero, - visual_id: NonZero, + visual_id: x11rb::protocol::xproto::Visualid, } impl PlatformHandle { pub fn window_handle(&self) -> Option> { let mut handle = XcbWindowHandle::new(self.window_id); - handle.visual_id = Some(self.visual_id); + handle.visual_id = NonZero::new(self.visual_id); Some(unsafe { raw_window_handle::WindowHandle::borrow_raw(handle.into()) }) } @@ -64,13 +70,49 @@ pub struct ParentWindowHandle { } impl ParentWindowHandle { - pub fn extract(window: &impl HasWindowHandle) -> Self { - let window_id = match window.window_handle().unwrap().as_raw() { - RawWindowHandle::Xlib(h) => NonZeroU32::new(h.window.try_into().unwrap()).unwrap(), + pub fn extract( + window: &impl HasWindowHandle, + ) -> core::result::Result { + let window_id = match window.window_handle()?.as_raw() { + RawWindowHandle::Xlib(h) => { + NonZeroU32::new(h.window.try_into()?).ok_or(ParentWindowHandleError::NullId)? + } RawWindowHandle::Xcb(h) => h.window, - h => panic!("unsupported parent handle type {:?}", h), + h => Err(ParentWindowHandleError::UnsupportedWindowHandleType(h))?, }; - Self { window_id } + Ok(Self { window_id }) + } +} + +pub enum ParentWindowHandleError { + HandleError(HandleError), + UnsupportedWindowHandleType(RawWindowHandle), + InvalidU32(TryFromIntError), + NullId, +} + +impl From for ParentWindowHandleError { + fn from(value: HandleError) -> Self { + Self::HandleError(value) + } +} + +impl From for ParentWindowHandleError { + fn from(value: TryFromIntError) -> Self { + Self::InvalidU32(value) + } +} + +impl Display for ParentWindowHandleError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ParentWindowHandleError::HandleError(e) => e.fmt(f), + ParentWindowHandleError::UnsupportedWindowHandleType(h) => { + write!(f, "Unsupported window handle type on X11: {h:?}") + } + ParentWindowHandleError::InvalidU32(e) => write!(f, "Invalid window XID: {e}"), + ParentWindowHandleError::NullId => f.write_str("Window XID is zero"), + } } } diff --git a/src/platform/x11/visual_info.rs b/src/platform/x11/visual_info.rs index f45fb8d3..439958f0 100644 --- a/src/platform/x11/visual_info.rs +++ b/src/platform/x11/visual_info.rs @@ -1,5 +1,5 @@ use super::xcb_connection::X11Connection; -use std::error::Error; +use crate::platform::*; use x11rb::connection::Connection; use x11rb::protocol::xproto::{ Colormap, ColormapAlloc, ConnectionExt, Screen, VisualClass, Visualid, @@ -20,12 +20,11 @@ impl WindowVisualConfig { #[cfg(feature = "opengl")] pub fn find_best_visual_config_for_gl( connection: &X11Connection, gl_config: Option, - ) -> Result> { + ) -> Result { let Some(gl_config) = gl_config else { return Self::find_best_visual_config(connection) }; let (fb_config, window_config) = - super::gl::GlContextInner::get_fb_config_and_visual(connection, gl_config) - .expect("Could not fetch framebuffer config"); + super::gl::GlContextInner::get_fb_config_and_visual(connection, gl_config)?; Ok(Self { fb_config: Some(fb_config), @@ -35,7 +34,7 @@ impl WindowVisualConfig { }) } - pub fn find_best_visual_config(connection: &X11Connection) -> Result> { + pub fn find_best_visual_config(connection: &X11Connection) -> Result { match find_visual_for_depth(connection.screen(), 32) { None => Ok(Self::copy_from_parent()), Some(visual_id) => Ok(Self { @@ -61,9 +60,7 @@ impl WindowVisualConfig { // For this 32-bit depth to work, you also need to define a color map and set a border // pixel: https://cgit.freedesktop.org/xorg/xserver/tree/dix/window.c#n818 -fn create_color_map( - connection: &X11Connection, visual_id: Visualid, -) -> Result> { +fn create_color_map(connection: &X11Connection, visual_id: Visualid) -> Result { let colormap = connection.conn.generate_id()?; connection.conn.create_colormap( ColormapAlloc::NONE, diff --git a/src/platform/x11/window.rs b/src/platform/x11/window.rs index d359a5b7..cd99e990 100644 --- a/src/platform/x11/window.rs +++ b/src/platform/x11/window.rs @@ -1,5 +1,4 @@ use std::cell::Cell; -use std::error::Error; use std::num::NonZero; use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; @@ -7,14 +6,12 @@ use std::sync::mpsc; use std::sync::Arc; use std::thread::{self, JoinHandle}; -use x11rb::connection::Connection; - use super::X11Connection; use super::{event_loop::EventLoop, visual_info::WindowVisualConfig}; use crate::handler::WindowHandlerBuilder; use crate::platform::x11::window_shared::WindowInner; use crate::platform::x11::xcb_window::XcbWindow; -use crate::wrappers::xkbcommon::XkbcommonState; +use crate::platform::Result; use crate::*; pub struct WindowHandle { @@ -27,17 +24,30 @@ pub struct WindowHandle { impl WindowHandle { pub fn create_window( options: WindowOpenOptions, handler: WindowHandlerBuilder, - ) -> WindowHandle { + ) -> Result { let (tx, rx) = mpsc::sync_channel::(1); let (parent_handle, mut window_handle) = ParentHandle::new(); - let join_handle = thread::spawn(move || { - Window::window_thread(options, handler, tx.clone(), Some(parent_handle)).unwrap(); - }); - let raw_window_handle = rx.recv().unwrap().unwrap(); - window_handle.window_id = Some(raw_window_handle); + let join_handle = + thread::spawn(move || match create_window(options, handler, Some(parent_handle)) { + Ok(ev_loop) => { + tx.send(Ok(ev_loop.window_id())).unwrap(); + ev_loop.run().unwrap(); + } + Err(e) => { + tx.send(Err(format!("{}", e))).unwrap(); + } + }); + + let id = match rx.recv() { + Ok(Ok(id)) => id, + Err(e) => return Err(super::Error::Channel(e)), + Ok(Err(s)) => return Err(super::error::Error::CreationFailed(s)), + }; + + window_handle.window_id = Some(id); window_handle.event_loop_handle = Some(join_handle).into(); - window_handle + Ok(window_handle) } pub fn run_until_closed(self) { @@ -92,77 +102,74 @@ impl Drop for ParentHandle { } } -pub struct Window; +type WindowOpenResult = core::result::Result, String>; -type WindowOpenResult = Result, ()>; +fn create_window( + options: WindowOpenOptions, build: WindowHandlerBuilder, parent_handle: Option, +) -> Result { + // Connect to the X server + let xcb_connection = X11Connection::new()?; -impl Window { - fn window_thread( - options: WindowOpenOptions, build: WindowHandlerBuilder, - tx: mpsc::SyncSender, parent_handle: Option, - ) -> Result<(), Box> { - // Connect to the X server - let xcb_connection = X11Connection::new()?; + let scaling = match options.scale { + WindowScalePolicy::SystemScaleFactor => xcb_connection.get_scaling(), + WindowScalePolicy::ScaleFactor(scale) => scale, + }; - // Setup xkbcommon - let xkb_state = XkbcommonState::new(&xcb_connection); + let physical_size = options.size.to_physical(scaling); - let scaling = match options.scale { - WindowScalePolicy::SystemScaleFactor => xcb_connection.get_scaling(), - WindowScalePolicy::ScaleFactor(scale) => scale, - }; + #[cfg(feature = "opengl")] + let visual_info = + WindowVisualConfig::find_best_visual_config_for_gl(&xcb_connection, options.gl_config)?; - let physical_size = options.size.to_physical(scaling); + #[cfg(not(feature = "opengl"))] + let visual_info = WindowVisualConfig::find_best_visual_config(&xcb_connection)?; - #[cfg(feature = "opengl")] - let visual_info = - WindowVisualConfig::find_best_visual_config_for_gl(&xcb_connection, options.gl_config)?; - - #[cfg(not(feature = "opengl"))] - let visual_info = WindowVisualConfig::find_best_visual_config(&xcb_connection)?; + let xcb_connection = Rc::new(xcb_connection); - let xcb_connection = Rc::new(xcb_connection); + let x_window = XcbWindow::new( + Rc::clone(&xcb_connection), + physical_size, + &visual_info, + options.parent.map(|p| p.window_id), + )?; - let x_window = XcbWindow::new( - Rc::clone(&xcb_connection), - physical_size, - &visual_info, - options.parent.map(|p| p.window_id), - )?; + let cookies = [ + x_window.map_window()?, + x_window.set_title(&options.title)?, + x_window.enable_wm_protocols()?, + x_window.enable_dnd_protocols()?, + ]; - x_window.map_window()?; - x_window.set_title(&options.title)?; - x_window.enable_wm_protocols()?; - x_window.enable_dnd_protocols()?; - - xcb_connection.conn.flush()?; + for cookie in cookies { + cookie.check()?; + } - #[cfg(feature = "opengl")] - let gl_context = visual_info.fb_config.map(|fb_config| { + #[cfg(feature = "opengl")] + let gl_context = match visual_info.fb_config { + None => None, + Some(fb_config) => { // Because of the visual negotation we had to take some extra steps to create this context - super::gl::GlContextInner::create(&x_window, Rc::clone(&xcb_connection), fb_config) - .expect("Could not create OpenGL context") - }); - - let window_id = x_window.id(); - let inner = Rc::new(WindowInner::new( - xcb_connection, - x_window, - physical_size, - scaling, - visual_info.visual_id.try_into()?, - #[cfg(feature = "opengl")] - gl_context, - )); - - let handler = build.build(WindowContext::new(Rc::clone(&inner))); - - let _ = tx.send(Ok(window_id)); + Some(super::gl::GlContextInner::create( + &x_window, + Rc::clone(&xcb_connection), + fb_config, + )?) + } + }; + + let inner = Rc::new(WindowInner::new( + xcb_connection, + x_window, + physical_size, + scaling, + visual_info.visual_id, + #[cfg(feature = "opengl")] + gl_context, + )); - EventLoop::new(inner, handler, parent_handle, xkb_state).run()?; + let handler = build.build(WindowContext::new(Rc::clone(&inner)))?; - Ok(()) - } + Ok(EventLoop::new(inner, handler, parent_handle)) } pub fn copy_to_clipboard(_data: &str) { diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 965f4599..8b69cd0b 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -1,16 +1,12 @@ use crate::platform::x11::xcb_window::XcbWindow; -use crate::platform::X11Connection; +use crate::platform::*; use crate::{MouseCursor, WindowSize}; use dpi::{PhysicalSize, Size}; use raw_window_handle::{DisplayHandle, XlibWindowHandle}; use std::cell::Cell; -use std::num::NonZero; use std::rc::Rc; use std::sync::Arc; -use x11rb::connection::Connection; -use x11rb::protocol::xproto::{ - ChangeWindowAttributesAux, ConfigureWindowAux, ConnectionExt, InputFocus, Visualid, -}; +use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt, InputFocus, Visualid}; use x11rb::CURRENT_TIME; pub(crate) struct WindowInner { @@ -23,7 +19,7 @@ pub(crate) struct WindowInner { pub(crate) scaling_factor: Cell, pub(crate) window_size: Cell>, mouse_cursor: Cell, - pub(crate) visual_id: NonZero, + pub(crate) visual_id: Visualid, pub(crate) close_requested: Cell, pub(crate) is_focused: Cell, @@ -32,7 +28,7 @@ pub(crate) struct WindowInner { impl WindowInner { pub(crate) fn new( connection: Rc, xcb_window: XcbWindow, window_size: PhysicalSize, - scale_factor: f64, visual_id: NonZero, + scale_factor: f64, visual_id: Visualid, #[cfg(feature = "opengl")] gl_context: Option, ) -> Self { Self { @@ -51,22 +47,26 @@ impl WindowInner { } } - pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) { + pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) -> Result<()> { if self.mouse_cursor.get() == mouse_cursor { - return; + return Ok(()); } - let xid = self.connection.get_cursor(mouse_cursor).unwrap(); + let xid = self.connection.get_cursor(mouse_cursor)?; if xid != 0 { - let _ = self.connection.conn.change_window_attributes( - self.xcb_window.id().get(), - &ChangeWindowAttributesAux::new().cursor(xid), - ); - let _ = self.connection.conn.flush(); + self.connection + .conn + .change_window_attributes( + self.xcb_window.id().get(), + &ChangeWindowAttributesAux::new().cursor(xid), + )? + .check()?; } self.mouse_cursor.set(mouse_cursor); + + Ok(()) } pub fn request_close(&self) { @@ -77,33 +77,28 @@ impl WindowInner { self.is_focused.get() } - pub fn focus(&self) { - let _ = self.connection.conn.set_input_focus( - InputFocus::POINTER_ROOT, - self.xcb_window.id(), - CURRENT_TIME, - ); - let _ = self.connection.conn.flush(); + pub fn focus(&self) -> Result<()> { + self.connection + .conn + .set_input_focus(InputFocus::POINTER_ROOT, self.xcb_window.id(), CURRENT_TIME)? + .check()?; + + Ok(()) } - pub fn resize(&self, size: Size) { + pub fn resize(&self, size: Size) -> Result<()> { let new_physical_size = size.to_physical::(self.scaling_factor.get()); - - let _ = self.connection.conn.configure_window( - self.xcb_window.id().get(), - &ConfigureWindowAux::new() - .width(new_physical_size.width) - .height(new_physical_size.height), - ); - let _ = self.connection.conn.flush(); + self.xcb_window.resize(new_physical_size)?; // This will trigger a `ConfigureNotify` event which will in turn change `self.window_info` // and notify the window handler about it + + Ok(()) } pub fn window_handle(&self) -> Option> { let mut handle = XlibWindowHandle::new(self.xcb_window.id().get() as _); - handle.visual_id = self.visual_id.get().into(); + handle.visual_id = self.visual_id.into(); Some(unsafe { raw_window_handle::WindowHandle::borrow_raw(handle.into()) }) } @@ -111,8 +106,8 @@ impl WindowInner { self.connection.conn.xlib_display_handle() } - pub fn platform_handle(&self) -> super::PlatformHandle { - super::PlatformHandle { + pub fn platform_handle(&self) -> PlatformHandle { + PlatformHandle { connection: Arc::clone(&self.connection.conn), window_id: self.xcb_window.id(), visual_id: self.visual_id, diff --git a/src/platform/x11/xcb_connection.rs b/src/platform/x11/xcb_connection.rs index f80e8a78..7a5608f4 100644 --- a/src/platform/x11/xcb_connection.rs +++ b/src/platform/x11/xcb_connection.rs @@ -1,6 +1,5 @@ use std::cell::RefCell; use std::collections::hash_map::{Entry, HashMap}; -use std::error::Error; use std::sync::Arc; use x11rb::connection::Connection; use x11rb::cursor::Handle as CursorHandle; @@ -8,6 +7,7 @@ use x11rb::protocol::xproto::{self, Cursor, Screen}; use x11rb::resource_manager; use super::cursor; +use crate::platform::*; use crate::wrappers::xlib::XlibXcbConnection; use crate::MouseCursor; @@ -51,7 +51,7 @@ pub struct X11Connection { } impl X11Connection { - pub fn new() -> Result> { + pub fn new() -> Result { let conn = XlibXcbConnection::open()?; let screen = conn.default_screen(); let xcb_conn = conn.xcb_connection(); @@ -79,7 +79,7 @@ impl X11Connection { } #[inline] - pub fn get_cursor(&self, cursor: MouseCursor) -> Result> { + pub fn get_cursor(&self, cursor: MouseCursor) -> Result { // PANIC: this function is the only point where we access the cache, and we never call // external functions that may make a reentrant call to this function let mut cursor_cache = self.cursor_cache.borrow_mut(); @@ -105,7 +105,7 @@ impl X11Connection { pub fn get_property( &self, window: xproto::Window, property: xproto::Atom, property_type: xproto::Atom, - ) -> Result, GetPropertyError> { - self::get_property::get_property(window, property, property_type, &self.conn) + ) -> core::result::Result, GetPropertyError> { + get_property::get_property(window, property, property_type, &self.conn) } } diff --git a/src/platform/x11/xcb_connection/get_property.rs b/src/platform/x11/xcb_connection/get_property.rs index 0da440e5..6bd487ff 100644 --- a/src/platform/x11/xcb_connection/get_property.rs +++ b/src/platform/x11/xcb_connection/get_property.rs @@ -41,37 +41,43 @@ use std::error::Error; use std::ffi::c_int; use std::fmt; use std::mem; -use std::sync::Arc; use bytemuck::Pod; - -use x11rb::errors::ReplyError; +use x11rb::errors::{ConnectionError, ReplyError}; use x11rb::protocol::xproto::{self, ConnectionExt}; use x11rb::xcb_ffi::XCBConnection; -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum GetPropertyError { - X11rbError(Arc), + ConnectionError(ConnectionError), + ReplyError(ReplyError), TypeMismatch(xproto::Atom), FormatMismatch(c_int), } -impl> From for GetPropertyError { - fn from(e: T) -> Self { - Self::X11rbError(Arc::new(e.into())) - } -} - impl fmt::Display for GetPropertyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - GetPropertyError::X11rbError(err) => err.fmt(f), GetPropertyError::TypeMismatch(err) => write!(f, "type mismatch: {err}"), GetPropertyError::FormatMismatch(err) => write!(f, "format mismatch: {err}"), + GetPropertyError::ConnectionError(e) => e.fmt(f), + GetPropertyError::ReplyError(e) => e.fmt(f), } } } +impl From for GetPropertyError { + fn from(e: ConnectionError) -> Self { + GetPropertyError::ConnectionError(e) + } +} + +impl From for GetPropertyError { + fn from(e: ReplyError) -> Self { + GetPropertyError::ReplyError(e) + } +} + impl Error for GetPropertyError {} // Number of 32-bit chunks to retrieve per iteration of get_property's inner loop. diff --git a/src/platform/x11/xcb_window.rs b/src/platform/x11/xcb_window.rs index 7ff1d85b..7e390d9a 100644 --- a/src/platform/x11/xcb_window.rs +++ b/src/platform/x11/xcb_window.rs @@ -5,9 +5,10 @@ use std::num::{NonZero, NonZeroU32}; use std::rc::Rc; use x11rb::connection::Connection; use x11rb::cookie::VoidCookie; -use x11rb::errors::ReplyOrIdError; +use x11rb::errors::{ConnectionError, ReplyOrIdError}; use x11rb::protocol::xproto::{ - AtomEnum, ConnectionExt as _, CreateWindowAux, EventMask, PropMode, WindowClass, + AtomEnum, ConfigureWindowAux, ConnectionExt as _, CreateWindowAux, EventMask, PropMode, + WindowClass, }; use x11rb::wrapper::ConnectionExt as _; use x11rb::xcb_ffi::XCBConnection; @@ -63,6 +64,15 @@ impl XcbWindow { Ok(self.connection.conn.map_window(self.window_id.get())?) } + pub fn resize( + &self, size: PhysicalSize, + ) -> Result, ConnectionError> { + self.connection.conn.configure_window( + self.id().get(), + &ConfigureWindowAux::new().width(size.width).height(size.height), + ) + } + pub fn set_title(&self, title: &str) -> Result, ReplyOrIdError> { Ok(self.connection.conn.change_property8( PropMode::REPLACE, diff --git a/src/tracing.rs b/src/tracing.rs new file mode 100644 index 00000000..bc23b156 --- /dev/null +++ b/src/tracing.rs @@ -0,0 +1,19 @@ +#[cfg(feature = "tracing")] +pub use tracing::*; + +#[cfg(not(feature = "tracing"))] +mod tracing_impl { + macro_rules! __warn { + ($($f:tt)*) => { + #[allow(unused, dead_code)] + { + let _ = ($($f)*); + } + }; + } + + pub(crate) use __warn as warn; +} + +#[cfg(not(feature = "tracing"))] +pub(crate) use tracing_impl::*; diff --git a/src/window.rs b/src/window.rs index fc44bdc3..27e47724 100644 --- a/src/window.rs +++ b/src/window.rs @@ -32,12 +32,13 @@ impl WindowHandle { } pub fn create_window( - builder: WindowOpenOptions, handler: impl FnOnce(WindowContext) -> H + Send + 'static, -) -> WindowHandle { - WindowHandle::new(platform::WindowHandle::create_window( + builder: WindowOpenOptions, + handler: impl FnOnce(WindowContext) -> core::result::Result + Send + 'static, +) -> Result { + Ok(WindowHandle::new(platform::WindowHandle::create_window( builder, WindowHandlerBuilder::new(handler), - )) + )?)) } /// A window's size, which can be read in either logical or physical pixels. diff --git a/src/window_open_options.rs b/src/window_open_options.rs index 5d32db91..501427c7 100644 --- a/src/window_open_options.rs +++ b/src/window_open_options.rs @@ -16,7 +16,6 @@ pub enum WindowScalePolicy { /// The options for opening a new window #[derive(Debug, Clone, PartialEq)] -#[non_exhaustive] pub struct WindowOpenOptions { pub title: String, @@ -62,7 +61,14 @@ impl WindowOpenOptions { #[inline] pub fn with_parent(mut self, parent: &impl HasWindowHandle) -> Self { - self.parent = Some(ParentWindowHandle::extract(parent)); + let parent = match ParentWindowHandle::extract(parent) { + Ok(parent) => parent, + Err(e) => { + panic!("Invalid parent window handle: {e}") + } + }; + + self.parent = Some(parent); self } diff --git a/src/wrappers/appkit.rs b/src/wrappers/appkit.rs index 08f032e5..b6a8c3cc 100644 --- a/src/wrappers/appkit.rs +++ b/src/wrappers/appkit.rs @@ -3,33 +3,62 @@ mod timer; mod view; mod window; +pub use notification_center::*; use objc2::rc::Retained; use objc2_app_kit::NSView; use objc2_core_foundation::CFUUID; use std::ffi::CString; - -pub use notification_center::*; +use std::fmt::{Display, Formatter}; pub use timer::TimerHandle; pub use view::*; pub use window::*; -use raw_window_handle::{RawWindowHandle, WindowHandle}; +use raw_window_handle::{HandleError, RawWindowHandle, WindowHandle}; fn new_class_name(prefix: &str) -> CString { // PANIC: CFUUIDCreate is not documented to return NULL. - let uuid = CFUUID::new(None).unwrap(); + let Some(uuid) = CFUUID::new(None) else { unreachable!() }; // PANIC: CFUUIDCreateString is not documented to return NULL. - let uuid_str = CFUUID::new_string(None, Some(&uuid)).unwrap(); + let Some(uuid_str) = CFUUID::new_string(None, Some(&uuid)) else { unreachable!() }; let class_name = format!("{prefix}{uuid_str}"); // PANIC: This cannot have any NULL bytes - CString::new(class_name).unwrap() + let Ok(class_name) = CString::new(class_name) else { unreachable!() }; + class_name } -pub fn extract_raw_window_handle(handle: WindowHandle) -> Option> { - let RawWindowHandle::AppKit(handle) = handle.as_raw() else { - panic!("Not a macOS window"); +pub fn extract_raw_window_handle( + raw_handle: WindowHandle, +) -> Result, ParentWindowHandleError> { + let raw_handle = raw_handle.as_raw(); + let RawWindowHandle::AppKit(handle) = raw_handle else { + return Err(ParentWindowHandleError::UnsupportedWindowHandleType(raw_handle)); }; - unsafe { Retained::retain(handle.ns_view.as_ptr() as *mut NSView) } + (unsafe { Retained::retain(handle.ns_view.as_ptr() as *mut NSView) }) + .ok_or(ParentWindowHandleError::NullViewPtr) +} + +pub enum ParentWindowHandleError { + HandleError(HandleError), + UnsupportedWindowHandleType(RawWindowHandle), + NullViewPtr, +} + +impl From for ParentWindowHandleError { + fn from(value: HandleError) -> Self { + Self::HandleError(value) + } +} + +impl Display for ParentWindowHandleError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ParentWindowHandleError::HandleError(e) => e.fmt(f), + ParentWindowHandleError::UnsupportedWindowHandleType(h) => { + write!(f, "Unsupported window handle type on macOS (AppKit): {h:?}") + } + ParentWindowHandleError::NullViewPtr => f.write_str("NSView pointer is NULL"), + } + } } diff --git a/src/wrappers/appkit/view.rs b/src/wrappers/appkit/view.rs index 4394b51e..0ee889b5 100644 --- a/src/wrappers/appkit/view.rs +++ b/src/wrappers/appkit/view.rs @@ -1,7 +1,7 @@ use dpi::LogicalSize; use objc2::__framework_prelude::{Allocated, AnyClass, ProtocolObject, Retained}; use objc2::rc::Weak; -use objc2::runtime::AnyObject; +use objc2::runtime::{AnyObject, Ivar}; use objc2::{msg_send, Encoding, Message, RefEncode}; use objc2_app_kit::{NSDragOperation, NSDraggingInfo, NSEvent, NSView, NSWindow}; use objc2_core_foundation::CGRect; @@ -40,7 +40,10 @@ impl Deref for View { } impl View { - pub fn new(frame: CGRect, inner: V, init: impl FnOnce(ViewRef)) -> Retained> { + pub fn new( + frame: CGRect, inner: V, + init: impl FnOnce(ViewRef) -> Result<(), crate::platform::Error>, + ) -> Result>, crate::platform::Error> { // SAFETY: We don't access this reference after this function let class = unsafe { implementation::create_view_class::() }; @@ -51,21 +54,28 @@ impl View { let view: Retained> = unsafe { msg_send![view, initWithFrame: frame] }; - init(view.inner_ref().unwrap()); + let Some(inner_ref) = view.inner_ref() else { unreachable!() }; - view + init(inner_ref)?; + + Ok(view) + } + + fn get_ivar(class: &AnyClass) -> &Ivar { + let Some(ivar) = class.instance_variable(BASEVIEW_STATE_IVAR) else { unreachable!() }; + ivar } fn set_inner(view: &Allocated>, class: &AnyClass, inner: ViewInner) { let inner = Box::new(inner); - let ivar = class.instance_variable(BASEVIEW_STATE_IVAR).unwrap(); + let ivar = Self::get_ivar(class); let ivar_target = unsafe { &*Allocated::as_ptr(view).cast() }; let ivar = unsafe { ivar.load_ptr::<*mut c_void>(ivar_target) }; unsafe { ivar.write(Box::into_raw(inner).cast()) }; } fn free_inner(this: &AnyObject, class: &AnyClass) { - let ivar = class.instance_variable(BASEVIEW_STATE_IVAR).unwrap(); + let ivar = Self::get_ivar(class); let ivar = unsafe { ivar.load_ptr::<*mut c_void>(this) }; let raw = unsafe { ivar.read() }; @@ -79,7 +89,7 @@ impl View { } fn get_inner(&self) -> Option<&ViewInner> { - let ivar = self.class().instance_variable(BASEVIEW_STATE_IVAR).unwrap(); + let ivar = Self::get_ivar(self.class()); let ivar = unsafe { ivar.load::<*mut c_void>(self) }; unsafe { ivar.cast::>().as_ref() } } diff --git a/src/wrappers/appkit/view/implementation.rs b/src/wrappers/appkit/view/implementation.rs index 96026124..823a4c1d 100644 --- a/src/wrappers/appkit/view/implementation.rs +++ b/src/wrappers/appkit/view/implementation.rs @@ -20,7 +20,12 @@ pub unsafe fn create_view_class() -> &'static AnyClass { // any class definitions lying around when the plugin is closed. let class_name = new_class_name("BaseviewNSView_"); - let mut class = ClassBuilder::new(&class_name, NSView::class()).unwrap(); + let Some(mut class) = ClassBuilder::new(&class_name, NSView::class()) else { + panic!( + "Failed to construct new class '{}' using ClassBuilder", + class_name.to_string_lossy() + ); + }; // SAFETY: All of these function signatures are correct unsafe { diff --git a/src/wrappers/glx.rs b/src/wrappers/glx.rs index 06b6800c..7f16d13f 100644 --- a/src/wrappers/glx.rs +++ b/src/wrappers/glx.rs @@ -1,6 +1,7 @@ use super::xlib::*; -use crate::gl::{GlConfig, GlError, Profile}; +use crate::gl::{GlConfig, Profile}; use crate::platform::gl::CreationFailedError; +use crate::platform::*; use std::ffi::{c_ulong, c_void, CStr}; use std::os::raw::c_int; @@ -30,7 +31,7 @@ pub struct Glx { } impl Glx { - pub fn open() -> Result { + pub fn open() -> Result { Ok(Self { inner: x11_dl::glx::Glx::open()? }) } @@ -59,7 +60,7 @@ impl Glx { pub fn choose_best_fb_config( &self, connection: &XlibConnection, config: &GlConfig, error_handler: &XErrorHandler, - ) -> Result { + ) -> Result { let fb_attribs = Self::get_fb_attribs(config); let mut nelements = 0; @@ -76,7 +77,7 @@ impl Glx { error_handler.check()?; if nelements == 0 || result.is_null() { - return Err(GlError::CreationFailed(CreationFailedError::NoValidFBConfig)); + return Err(CreationFailedError::NoValidFBConfig.into()); } // SAFETY: If nelements != 0, the result pointer is non-null, and no Xlib error occured, then @@ -92,14 +93,14 @@ impl Glx { pub fn get_visual_from_fb_config( &self, connection: &XlibConnection, fb_config: GlxFbConfig, error_handler: &XErrorHandler, - ) -> Result { + ) -> Result { // SAFETY: XlibConnection guarantees the inner dpy is valid. let result = unsafe { (self.inner.glXGetVisualFromFBConfig)(connection.as_raw(), fb_config.0) }; error_handler.check()?; if result.is_null() { - return Err(GlError::CreationFailed(CreationFailedError::NoVisual)); + return Err(CreationFailedError::NoVisual.into()); } // SAFETY: If the result pointer is non-null, and no Xlib error occured, then @@ -114,7 +115,7 @@ impl Glx { pub fn swap_buffers( &self, connection: &XlibConnection, window_id: c_ulong, error_handler: &XErrorHandler, - ) -> Result<(), GlError> { + ) -> Result<()> { // SAFETY: XlibConnection guarantees the inner dpy is valid. unsafe { (self.inner.glXSwapBuffers)(connection.as_raw(), window_id) }; @@ -151,13 +152,13 @@ impl Glx { pub unsafe fn make_current( &self, connection: &XlibConnection, window_id: c_ulong, context: GLXContext, error_handler: &XErrorHandler, - ) -> Result<(), GlError> { + ) -> Result<()> { // SAFETY: XlibConnection guarantees the inner dpy is valid. let res = unsafe { (self.inner.glXMakeCurrent)(connection.as_raw(), window_id, context) }; error_handler.check()?; if res == 0 { - return Err(GlError::CreationFailed(CreationFailedError::MakeCurrentFailed)); + return Err(CreationFailedError::MakeCurrentFailed.into()); } Ok(()) @@ -165,14 +166,14 @@ impl Glx { pub unsafe fn clear_current( &self, connection: &XlibConnection, error_handler: &XErrorHandler, - ) -> Result<(), GlError> { + ) -> Result<()> { self.make_current(connection, 0, core::ptr::null_mut(), error_handler) } pub unsafe fn with_current_context( &self, connection: &XlibConnection, window_id: c_ulong, context: GLXContext, error_handler: &XErrorHandler, closure: impl FnOnce() -> T, - ) -> Result { + ) -> Result { self.make_current(connection, window_id, context, error_handler)?; // Using a "drop" allows us to clear the GL context even if the given closure panics @@ -221,7 +222,7 @@ impl GlxCreateContextAttribsARB { pub fn call( &self, connection: &XlibConnection, gl_config: &GlConfig, glx_fb_config: GlxFbConfig, error_handler: &XErrorHandler, - ) -> Result { + ) -> Result { let ctx_attribs = Self::get_ctx_attribs(gl_config); let context = unsafe { @@ -237,7 +238,7 @@ impl GlxCreateContextAttribsARB { error_handler.check()?; if context.is_null() { - return Err(GlError::CreationFailed(CreationFailedError::ContextCreationFailed)); + return Err(CreationFailedError::ContextCreationFailed.into()); } Ok(context) diff --git a/src/wrappers/win32/user32.rs b/src/wrappers/win32/user32.rs index e4b789ae..93add62d 100644 --- a/src/wrappers/win32/user32.rs +++ b/src/wrappers/win32/user32.rs @@ -50,7 +50,11 @@ impl ExtendedUser32 { impl Clone for ExtendedUser32 { fn clone(&self) -> Self { - let library = unsafe { LibraryModule::load(s!("user32.dll")).unwrap() }; + let library = unsafe { LibraryModule::load(s!("user32.dll")) }; + + // PANIC: This should not be able to happen, since we already loaded it once and it's still loaded in Clone + let Ok(library) = library else { unreachable!() }; + Self { _library: library, diff --git a/src/wrappers/win32/window.rs b/src/wrappers/win32/window.rs index ad104c60..1b7816ec 100644 --- a/src/wrappers/win32/window.rs +++ b/src/wrappers/win32/window.rs @@ -24,7 +24,7 @@ use windows_core::{Error, Result, HSTRING}; use crate::wrappers::win32::h_instance::HInstance; use crate::wrappers::win32::style::WindowStyle; use crate::wrappers::win32::DpiAwarenessContext; -use windows_sys::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM}; +use windows_sys::Win32::Foundation::{LPARAM, LRESULT, WPARAM}; use windows_sys::Win32::UI::WindowsAndMessaging::CreateWindowExW; pub trait WindowImpl: 'static { @@ -35,7 +35,7 @@ pub trait WindowImpl: 'static { /// [`handle_message`] to be called immediately. Implementations must be ready for that. /// /// If this returns an error, the window creation is canceled. - fn after_create(&self, window: HWnd) -> Result<()>; + fn after_create(&self, window: HWnd) -> core::result::Result<(), crate::platform::Error>; unsafe fn handle_message( &self, window: HWnd, message_code: u32, w_param: WPARAM, l_param: LPARAM, ) -> Option; diff --git a/src/wrappers/win32/window/data.rs b/src/wrappers/win32/window/data.rs index 171838d8..fd5d6a86 100644 --- a/src/wrappers/win32/window/data.rs +++ b/src/wrappers/win32/window/data.rs @@ -32,7 +32,7 @@ impl WindowData { Rc::clone(&this) } - pub fn initialize(&self, window: HWnd) -> Result<()> { + pub fn initialize(&self, window: HWnd) -> core::result::Result<(), crate::platform::Error> { let Some(initializer) = self.initializer.take() else { panic!("WindowData is already initialized"); }; diff --git a/src/wrappers/win32/window/wgl.rs b/src/wrappers/win32/window/wgl.rs index a82d28e4..90e4c612 100644 --- a/src/wrappers/win32/window/wgl.rs +++ b/src/wrappers/win32/window/wgl.rs @@ -1,11 +1,16 @@ +#![allow(non_snake_case)] + use crate::gl::{GlConfig, Profile}; +use crate::warn; use crate::wrappers::win32::window::OwnDeviceContext; -use std::ffi::{c_void, CStr}; +use std::ffi::c_void; use std::fmt::{Display, Formatter}; use std::mem::transmute; use std::num::NonZeroI32; use std::ptr::{null_mut, NonNull}; use windows_core::Error; +use windows_sys::s; +use windows_sys::Win32::Foundation::PROC; use windows_sys::Win32::Graphics::Gdi::HDC; use windows_sys::Win32::Graphics::OpenGL::{ wglDeleteContext, wglGetCurrentContext, wglGetProcAddress, wglMakeCurrent, HGLRC, @@ -74,8 +79,14 @@ impl WglContext { impl Drop for WglContext { fn drop(&mut self) { - let _ = unsafe { self.make_not_current() }; - unsafe { wglDeleteContext(self.inner.as_ptr()) }; // TODO: warn on error + if let Err(e) = unsafe { self.make_not_current() } { + warn!("Could not unset current GL context {}", e); + } + + let result = unsafe { wglDeleteContext(self.inner.as_ptr()) }; + if result == 0 { + warn!("Could not delete GL context: {}", Error::from_thread()); + } } } @@ -89,42 +100,35 @@ type WglChoosePixelFormatARB = // See https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_create_context.txt type WglCreateContextAttribsARB = unsafe extern "system" fn(HDC, HGLRC, *const i32) -> HGLRC; -#[allow(non_snake_case)] pub struct WglExtra { - wglCreateContextAttribsARB: WglCreateContextAttribsARB, - wglChoosePixelFormatARB: WglChoosePixelFormatARB, - wglSwapIntervalEXT: WglSwapIntervalEXT, + wglCreateContextAttribsARB: Option, + wglChoosePixelFormatARB: Option, + wglSwapIntervalEXT: Option, } impl WglExtra { - pub fn load() -> Result { + pub fn load() -> Self { unsafe { - Ok(Self { - wglCreateContextAttribsARB: transmute::<*const c_void, WglCreateContextAttribsARB>( - Self::load_fn(c"wglCreateContextAttribsARB")?, + Self { + wglCreateContextAttribsARB: transmute::>( + wglGetProcAddress(s!("wglCreateContextAttribsARB")), ), - wglChoosePixelFormatARB: transmute::<*const c_void, WglChoosePixelFormatARB>( - Self::load_fn(c"wglChoosePixelFormatARB")?, + wglChoosePixelFormatARB: transmute::>( + wglGetProcAddress(s!("wglChoosePixelFormatARB")), ), - wglSwapIntervalEXT: transmute::<*const c_void, WglSwapIntervalEXT>(Self::load_fn( - c"wglSwapIntervalEXT", - )?), - }) - } - } - - fn load_fn(name: &'static CStr) -> Result<*const c_void, MissingExtensionFunctionError> { - let ptr = unsafe { wglGetProcAddress(name.as_ptr() as *const u8) }; - - match ptr { - Some(ptr) => Ok(ptr as *const c_void), - None => Err(MissingExtensionFunctionError { name }), + wglSwapIntervalEXT: transmute::>( + wglGetProcAddress(s!("wglSwapIntervalEXT")), + ), + } } } pub fn create_context_for_config( &self, dc: &OwnDeviceContext, config: &GlConfig, - ) -> windows_core::Result { + ) -> Result { + let Some(wglCreateContextAttribsARB) = self.wglCreateContextAttribsARB else { + return Err(CreateContextError::Unavailable); + }; let profile_mask = match config.profile { Profile::Core => WGL_CONTEXT_CORE_PROFILE_BIT_ARB, Profile::Compatibility => WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, @@ -138,17 +142,22 @@ impl WglExtra { 0 ]; - let ctx = unsafe { - (self.wglCreateContextAttribsARB)(dc.as_raw(), null_mut(), ctx_attribs.as_ptr()) - }; + let ctx = + unsafe { wglCreateContextAttribsARB(dc.as_raw(), null_mut(), ctx_attribs.as_ptr()) }; - let ctx = NonNull::new(ctx).ok_or_else(Error::from_thread)?; + let ctx = + NonNull::new(ctx).ok_or_else(|| CreateContextError::Win32(Error::from_thread()))?; Ok(WglContext { inner: ctx }) } pub fn set_vsync(&self, vsync: bool) -> windows_core::Result<()> { - let result = unsafe { (self.wglSwapIntervalEXT)(vsync.into()) }; + let Some(wglSwapIntervalEXT) = self.wglSwapIntervalEXT else { + warn!("Could not set vsync: wglSwapIntervalEXT is not available"); + return Ok(()); + }; + + let result = unsafe { wglSwapIntervalEXT(vsync.into()) }; if result == 0 { return Err(Error::from_thread()); } @@ -158,11 +167,15 @@ impl WglExtra { pub fn choose_pixel_format_from_attribs( &self, attribs: &PixelFormatAttribs, dc: &OwnDeviceContext, - ) -> windows_core::Result> { + ) -> Result, ChoosePixelFormatFromAttribsError> { + let Some(wglChoosePixelFormatARB) = self.wglChoosePixelFormatARB else { + return Err(ChoosePixelFormatFromAttribsError::Unavailable); + }; + let mut pixel_formats = [0]; let mut num_formats = 0; let result = unsafe { - (self.wglChoosePixelFormatARB)( + wglChoosePixelFormatARB( dc.as_raw(), attribs.inner.as_ptr(), std::ptr::null(), @@ -173,7 +186,7 @@ impl WglExtra { }; if result == 0 { - return Err(Error::from_thread()); + return Err(ChoosePixelFormatFromAttribsError::Win32(Error::from_thread())); } if num_formats == 0 { @@ -184,6 +197,38 @@ impl WglExtra { } } +pub enum CreateContextError { + Win32(Error), + Unavailable, +} + +impl Display for CreateContextError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + CreateContextError::Win32(err) => Display::fmt(err, f), + CreateContextError::Unavailable => { + f.write_str("wglCreateContextAttribsARB is not available") + } + } + } +} + +pub enum ChoosePixelFormatFromAttribsError { + Win32(Error), + Unavailable, +} + +impl Display for ChoosePixelFormatFromAttribsError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ChoosePixelFormatFromAttribsError::Win32(e) => Display::fmt(e, f), + ChoosePixelFormatFromAttribsError::Unavailable => { + f.write_str("wglChoosePixelFormatARB is not available") + } + } + } +} + const WGL_DRAW_TO_WINDOW_ARB: i32 = 0x2001; const WGL_ACCELERATION_ARB: i32 = 0x2003; const WGL_SUPPORT_OPENGL_ARB: i32 = 0x2010; @@ -241,14 +286,3 @@ impl PixelFormatAttribs { self.inner[27] = 0; } } - -#[derive(Debug)] -pub struct MissingExtensionFunctionError { - name: &'static CStr, -} - -impl Display for MissingExtensionFunctionError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "Missing WGL function extension: {}", self.name.to_string_lossy()) - } -} diff --git a/src/wrappers/xlib.rs b/src/wrappers/xlib.rs index e8db61d3..ebfb3ebf 100644 --- a/src/wrappers/xlib.rs +++ b/src/wrappers/xlib.rs @@ -3,7 +3,8 @@ mod error_handler; mod xlib_connection; mod xlib_xcb; -pub use xlib_xcb::XlibXcbConnection; +pub use xlib_connection::*; +pub use xlib_xcb::*; #[cfg(feature = "opengl")] pub use self::{ diff --git a/src/wrappers/xlib/xlib_connection.rs b/src/wrappers/xlib/xlib_connection.rs index b284a513..95fcabb2 100644 --- a/src/wrappers/xlib/xlib_connection.rs +++ b/src/wrappers/xlib/xlib_connection.rs @@ -1,3 +1,4 @@ +use crate::platform::*; use std::error::Error; use std::ffi::CStr; use std::fmt::Formatter; @@ -24,7 +25,7 @@ unsafe impl Send for XlibConnection {} unsafe impl Sync for XlibConnection {} impl XlibConnection { - pub fn open() -> Result> { + pub fn open() -> Result { let xlib = Box::new(Xlib::open()?); if unsafe { (xlib.XInitThreads)() } == 0 { @@ -86,9 +87,7 @@ impl XlibConnection { unsafe { (self.xlib.XSync)(self.display.as_ptr(), 0) }; } - pub fn get_error_text( - &self, buf: &mut [u8], error_code: core::ffi::c_uchar, - ) -> &core::ffi::CStr { + pub fn get_error_text(&self, buf: &mut [u8], error_code: core::ffi::c_uchar) -> &CStr { if buf.is_empty() { return c""; } @@ -115,7 +114,7 @@ impl XlibConnection { *buf.last_mut().unwrap() = 0; // SAFETY: whatever XGetErrorText did or not, we guaranteed there is a nul byte at the end of the buffer - unsafe { std::ffi::CStr::from_ptr(buf.as_mut_ptr().cast()) } + unsafe { CStr::from_ptr(buf.as_mut_ptr().cast()) } } pub fn set_error_handler( @@ -139,7 +138,7 @@ impl Drop for XlibConnection { } #[derive(Debug)] -struct DisplayOpenFailedError; +pub struct DisplayOpenFailedError; impl std::fmt::Display for DisplayOpenFailedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str("Failed to open X11 display connection: XOpenDisplay() failed") @@ -148,7 +147,7 @@ impl std::fmt::Display for DisplayOpenFailedError { impl Error for DisplayOpenFailedError {} #[derive(Debug)] -struct InitThreadsFailedError; +pub struct InitThreadsFailedError; impl std::fmt::Display for InitThreadsFailedError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str("Failed to open X11 display connection: XOpenDisplay() failed") diff --git a/src/wrappers/xlib/xlib_xcb.rs b/src/wrappers/xlib/xlib_xcb.rs index 019acf64..e50015f4 100644 --- a/src/wrappers/xlib/xlib_xcb.rs +++ b/src/wrappers/xlib/xlib_xcb.rs @@ -1,6 +1,6 @@ +use crate::platform::*; use crate::wrappers::xlib::xlib_connection::XlibConnection; use raw_window_handle::{DisplayHandle, XcbDisplayHandle, XlibDisplayHandle}; -use std::error::Error; use std::ops::Deref; use std::os::fd::{AsFd, BorrowedFd}; use std::os::raw::c_int; @@ -24,7 +24,7 @@ pub struct XlibXcbConnection { } impl XlibXcbConnection { - pub fn open() -> Result> { + pub fn open() -> Result { let xlib_xcb = Xlib_xcb::open()?; // Open the connection to the X11 server as a Xlib/XCB connection object let xlib_connection = XlibConnection::open()?;