diff --git a/examples/open_parented.rs b/examples/open_parented.rs index 280c697..45aa046 100644 --- a/examples/open_parented.rs +++ b/examples/open_parented.rs @@ -14,9 +14,9 @@ struct ParentWindowHandler { } impl ParentWindowHandler { - pub fn new(window: &mut Window) -> Self { - let ctx = unsafe { softbuffer::Context::new(window) }.unwrap(); - let mut surface = unsafe { softbuffer::Surface::new(&ctx, window) }.unwrap(); + pub fn new(window: Window) -> Self { + let ctx = unsafe { softbuffer::Context::new(&window) }.unwrap(); + let mut surface = unsafe { softbuffer::Surface::new(&ctx, &window) }.unwrap(); surface.resize(NonZeroU32::new(512).unwrap(), NonZeroU32::new(512).unwrap()).unwrap(); let window_open_options = baseview::WindowOpenOptions { @@ -28,8 +28,9 @@ impl ParentWindowHandler { #[cfg(feature = "opengl")] gl_config: None, }; + let child_window = - Window::open_parented(window, window_open_options, ChildWindowHandler::new); + Window::open_parented(&window, window_open_options, ChildWindowHandler::new); // TODO: no way to query physical size initially? Self { @@ -43,7 +44,7 @@ impl ParentWindowHandler { } impl WindowHandler for ParentWindowHandler { - fn on_frame(&mut self, _window: &mut Window) { + fn on_frame(&mut self) { let mut buf = self.surface.buffer_mut().unwrap(); if self.damaged { buf.fill(0xFFAAAAAA); @@ -52,7 +53,7 @@ impl WindowHandler for ParentWindowHandler { buf.present().unwrap(); } - fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus { + fn on_event(&mut self, event: Event) -> EventStatus { match event { Event::Window(WindowEvent::Resized(info)) => { println!("Parent Resized: {:?}", info); @@ -83,9 +84,9 @@ struct ChildWindowHandler { } impl ChildWindowHandler { - pub fn new(window: &mut Window) -> Self { - let ctx = unsafe { softbuffer::Context::new(window) }.unwrap(); - let mut surface = unsafe { softbuffer::Surface::new(&ctx, window) }.unwrap(); + pub fn new(window: Window) -> Self { + let ctx = unsafe { softbuffer::Context::new(&window) }.unwrap(); + let mut surface = unsafe { softbuffer::Surface::new(&ctx, &window) }.unwrap(); surface.resize(NonZeroU32::new(512).unwrap(), NonZeroU32::new(512).unwrap()).unwrap(); // TODO: no way to query physical size initially? @@ -94,7 +95,7 @@ impl ChildWindowHandler { } impl WindowHandler for ChildWindowHandler { - fn on_frame(&mut self, _window: &mut Window) { + fn on_frame(&mut self) { let mut buf = self.surface.buffer_mut().unwrap(); if self.damaged { buf.fill(0xFFAA0000); @@ -103,7 +104,7 @@ impl WindowHandler for ChildWindowHandler { buf.present().unwrap(); } - fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus { + fn on_event(&mut self, event: Event) -> EventStatus { match event { Event::Window(WindowEvent::Resized(info)) => { println!("Child Resized: {:?}", info); diff --git a/examples/open_window.rs b/examples/open_window.rs index cf2a90a..681e4ba 100644 --- a/examples/open_window.rs +++ b/examples/open_window.rs @@ -24,7 +24,7 @@ struct OpenWindowExample { } impl WindowHandler for OpenWindowExample { - fn on_frame(&mut self, _window: &mut Window) { + fn on_frame(&mut self) { let mut buf = self.surface.buffer_mut().unwrap(); if self.damaged { buf.fill(0xFFAAAAAA); @@ -37,7 +37,7 @@ impl WindowHandler for OpenWindowExample { } } - fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus { + fn on_event(&mut self, event: Event) -> EventStatus { match &event { #[cfg(target_os = "macos")] Event::Mouse(MouseEvent::ButtonPressed { .. }) => copy_to_clipboard("This is a test!"), @@ -84,8 +84,8 @@ fn main() { }); Window::open_blocking(window_open_options, |window| { - let ctx = unsafe { softbuffer::Context::new(window) }.unwrap(); - let mut surface = unsafe { softbuffer::Surface::new(&ctx, window) }.unwrap(); + let ctx = unsafe { softbuffer::Context::new(&window) }.unwrap(); + let mut surface = unsafe { softbuffer::Surface::new(&ctx, &window) }.unwrap(); surface.resize(NonZeroU32::new(512).unwrap(), NonZeroU32::new(512).unwrap()).unwrap(); OpenWindowExample { diff --git a/examples/render_femtovg/src/main.rs b/examples/render_femtovg/src/main.rs index 0f5504b..860adce 100644 --- a/examples/render_femtovg/src/main.rs +++ b/examples/render_femtovg/src/main.rs @@ -1,4 +1,4 @@ -use baseview::gl::GlConfig; +use baseview::gl::{GlConfig, GlContext}; use baseview::{ Event, EventStatus, MouseEvent, PhyPoint, Size, Window, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, @@ -7,6 +7,8 @@ use femtovg::renderer::OpenGl; use femtovg::{Canvas, Color}; struct FemtovgExample { + _window: Window, + gl_context: GlContext, canvas: Canvas, current_size: WindowInfo, current_mouse_position: PhyPoint, @@ -14,35 +16,36 @@ struct FemtovgExample { } impl FemtovgExample { - fn new(window: &mut Window) -> Self { - let context = window.gl_context().unwrap(); - unsafe { context.make_current() }; + fn new(window: Window) -> Self { + let gl_context = window.gl_context().unwrap(); + unsafe { gl_context.make_current() }; let renderer = - unsafe { OpenGl::new_from_function(|s| context.get_proc_address(s)) }.unwrap(); + unsafe { OpenGl::new_from_function(|s| gl_context.get_proc_address(s)) }.unwrap(); let mut canvas = Canvas::new(renderer).unwrap(); // TODO: get actual window width canvas.set_size(512, 512, 1.0); - unsafe { context.make_not_current() }; + unsafe { gl_context.make_not_current() }; Self { canvas, current_size: WindowInfo::from_logical_size(Size { width: 512.0, height: 512.0 }, 1.0), current_mouse_position: PhyPoint { x: 256, y: 256 }, damaged: true, + _window: window, + gl_context, } } } impl WindowHandler for FemtovgExample { - fn on_frame(&mut self, window: &mut Window) { + fn on_frame(&mut self) { if !self.damaged { return; } - let context = window.gl_context().unwrap(); - unsafe { context.make_current() }; + unsafe { self.gl_context.make_current() }; let screen_height = self.canvas.height(); let screen_width = self.canvas.width(); @@ -70,12 +73,12 @@ impl WindowHandler for FemtovgExample { // Tell renderer to execute all drawing commands self.canvas.flush(); - context.swap_buffers(); - unsafe { context.make_not_current() }; + self.gl_context.swap_buffers(); + unsafe { self.gl_context.make_not_current() }; self.damaged = false; } - fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus { + fn on_event(&mut self, event: Event) -> EventStatus { match event { Event::Window(WindowEvent::Resized(size)) => { let phy_size = size.physical_size(); diff --git a/src/gl/mod.rs b/src/gl/mod.rs index 488cfd7..44c5962 100644 --- a/src/gl/mod.rs +++ b/src/gl/mod.rs @@ -1,25 +1,20 @@ use std::ffi::c_void; -use std::marker::PhantomData; - -// On X11 creating the context is a two step process -#[cfg(not(target_os = "linux"))] -use raw_window_handle::RawWindowHandle; +use std::rc::{Rc, Weak}; #[cfg(target_os = "windows")] -mod win; +pub(crate) mod win; #[cfg(target_os = "windows")] -use win as platform; +pub(crate) use win as platform; -// We need to use this directly within the X11 window creation to negotiate the correct visual #[cfg(target_os = "linux")] pub(crate) mod x11; #[cfg(target_os = "linux")] -pub(crate) use self::x11 as platform; +pub(crate) use x11 as platform; #[cfg(target_os = "macos")] -mod macos; +pub(crate) mod macos; #[cfg(target_os = "macos")] -use macos as platform; +pub(crate) use macos as platform; #[derive(Clone, Debug)] pub struct GlConfig { @@ -70,46 +65,37 @@ pub enum GlError { } pub struct GlContext { - context: platform::GlContext, - phantom: PhantomData<*mut ()>, + context: Weak, } impl GlContext { - #[cfg(not(target_os = "linux"))] - pub(crate) unsafe fn create( - parent: &RawWindowHandle, config: GlConfig, - ) -> Result { - platform::GlContext::create(parent, config) - .map(|context| GlContext { context, phantom: PhantomData }) + pub(crate) fn new(context: Weak) -> GlContext { + GlContext { context } } - /// The X11 version needs to be set up in a different way compared to the Windows and macOS - /// versions. So the platform-specific versions should be used to construct the context within - /// baseview, and then this object can be passed to the user. - #[cfg(target_os = "linux")] - pub(crate) fn new(context: platform::GlContext) -> GlContext { - GlContext { context, phantom: PhantomData } + fn context(&self) -> Rc { + self.context.upgrade().expect("GL context has been destroyed") } pub unsafe fn make_current(&self) { - self.context.make_current(); + self.context().make_current(); } pub unsafe fn make_not_current(&self) { - self.context.make_not_current(); + self.context().make_not_current(); } pub fn get_proc_address(&self, symbol: &str) -> *const c_void { - self.context.get_proc_address(symbol) + self.context().get_proc_address(symbol) } pub fn swap_buffers(&self) { - self.context.swap_buffers(); + self.context().swap_buffers(); } /// On macOS the `NSOpenGLView` needs to be resized separtely from our main view. #[cfg(target_os = "macos")] pub(crate) fn resize(&self, size: cocoa::foundation::NSSize) { - self.context.resize(size); + self.context().resize(size); } } diff --git a/src/gl/win.rs b/src/gl/win.rs index 655a807..23575fb 100644 --- a/src/gl/win.rs +++ b/src/gl/win.rs @@ -1,7 +1,7 @@ use std::ffi::{c_void, CString, OsStr}; use std::os::windows::ffi::OsStrExt; -use raw_window_handle::RawWindowHandle; +use raw_window_handle::Win32WindowHandle; use winapi::shared::minwindef::{HINSTANCE, HMODULE}; use winapi::shared::ntdef::WCHAR; @@ -77,13 +77,9 @@ extern "C" { } impl GlContext { - pub unsafe fn create(parent: &RawWindowHandle, config: GlConfig) -> Result { - let handle = if let RawWindowHandle::Win32(handle) = parent { - handle - } else { - return Err(GlError::InvalidWindowHandle); - }; - + pub unsafe fn create( + handle: &Win32WindowHandle, config: GlConfig, + ) -> Result { if handle.hwnd.is_null() { return Err(GlError::InvalidWindowHandle); } diff --git a/src/macos/window.rs b/src/macos/window.rs index f945518..69c86af 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -1,7 +1,7 @@ use std::cell::{Cell, RefCell}; use std::ffi::c_void; use std::ptr; -use std::rc::Rc; +use std::rc::{Rc, Weak}; use cocoa::appkit::{ NSApp, NSApplication, NSApplicationActivationPolicyRegular, NSBackingStoreBuffered, @@ -16,8 +16,7 @@ use keyboard_types::KeyboardEvent; use objc::class; use objc::{msg_send, runtime::Object, sel, sel_impl}; use raw_window_handle::{ - AppKitDisplayHandle, AppKitWindowHandle, HasRawDisplayHandle, HasRawWindowHandle, - RawDisplayHandle, RawWindowHandle, + AppKitDisplayHandle, AppKitWindowHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, }; use crate::{ @@ -29,7 +28,7 @@ use super::keyboard::KeyboardState; use super::view::{create_view, BASEVIEW_STATE_IVAR}; #[cfg(feature = "opengl")] -use crate::gl::{GlConfig, GlContext}; +use crate::gl::{platform::GlContext, GlConfig}; pub struct WindowHandle { state: Rc, @@ -51,7 +50,7 @@ unsafe impl HasRawWindowHandle for WindowHandle { } } -pub(super) struct WindowInner { +pub struct Window { open: Cell, /// Only set if we created the parent window, i.e. we are running in @@ -64,11 +63,11 @@ pub(super) struct WindowInner { ns_view: id, #[cfg(feature = "opengl")] - gl_context: Option, + gl_context: Option>, } -impl WindowInner { - pub(super) fn close(&self) { +impl Window { + pub fn close(&self) { if self.open.get() { self.open.set(false); @@ -107,7 +106,7 @@ impl WindowInner { } } - fn raw_window_handle(&self) -> RawWindowHandle { + pub fn raw_window_handle(&self) -> RawWindowHandle { if self.open.get() { let ns_window = self.ns_window.get().unwrap_or(ptr::null_mut()) as *mut c_void; @@ -120,20 +119,11 @@ impl WindowInner { RawWindowHandle::AppKit(AppKitWindowHandle::empty()) } -} - -pub struct Window<'a> { - inner: &'a WindowInner, -} -impl<'a> Window<'a> { - pub fn open_parented(parent: &P, options: WindowOpenOptions, build: B) -> WindowHandle - where - P: HasRawWindowHandle, - H: WindowHandler + 'static, - B: FnOnce(&mut crate::Window) -> H, - B: Send + 'static, - { + pub fn open_parented( + parent: &impl HasRawWindowHandle, options: WindowOpenOptions, + build: impl FnOnce(crate::Window) -> H, + ) -> WindowHandle { let pool = unsafe { NSAutoreleasePool::new(nil) }; let scaling = match options.scale { @@ -151,7 +141,7 @@ impl<'a> Window<'a> { let ns_view = unsafe { create_view(&options) }; - let window_inner = WindowInner { + let window_inner = Window { open: Cell::new(true), ns_app: Cell::new(None), ns_window: Cell::new(None), @@ -160,7 +150,7 @@ impl<'a> Window<'a> { #[cfg(feature = "opengl")] gl_context: options .gl_config - .map(|gl_config| Self::create_gl_context(None, ns_view, gl_config)), + .map(|gl_config| Rc::new(Self::create_gl_context(None, ns_view, gl_config))), }; let window_handle = Self::init(window_inner, window_info, build); @@ -174,12 +164,9 @@ impl<'a> Window<'a> { window_handle } - pub fn open_blocking(options: WindowOpenOptions, build: B) - where - H: WindowHandler + 'static, - B: FnOnce(&mut crate::Window) -> H, - B: Send + 'static, - { + pub fn open_blocking( + options: WindowOpenOptions, build: impl FnOnce(crate::Window) -> H, + ) { let pool = unsafe { NSAutoreleasePool::new(nil) }; // It seems prudent to run NSApp() here before doing other @@ -226,16 +213,16 @@ impl<'a> Window<'a> { let ns_view = unsafe { create_view(&options) }; - let window_inner = WindowInner { + let window_inner = Window { open: Cell::new(true), ns_app: Cell::new(Some(app)), ns_window: Cell::new(Some(ns_window)), ns_view, #[cfg(feature = "opengl")] - gl_context: options - .gl_config - .map(|gl_config| Self::create_gl_context(Some(ns_window), ns_view, gl_config)), + gl_context: options.gl_config.map(|gl_config| { + Rc::new(Self::create_gl_context(Some(ns_window), ns_view, gl_config)) + }), }; let _ = Self::init(window_inner, window_info, build); @@ -250,14 +237,11 @@ impl<'a> Window<'a> { } } - fn init(window_inner: WindowInner, window_info: WindowInfo, build: B) -> WindowHandle - where - H: WindowHandler + 'static, - B: FnOnce(&mut crate::Window) -> H, - B: Send + 'static, - { - let mut window = crate::Window::new(Window { inner: &window_inner }); - let window_handler = Box::new(build(&mut window)); + fn init( + window_inner: Window, window_info: WindowInfo, build: impl FnOnce(crate::Window) -> H, + ) -> WindowHandle { + let window_inner = Rc::new(window_inner); + let window_handler = Box::new(build(crate::Window::new(Rc::downgrade(&window_inner)))); let ns_view = window_inner.ns_view; @@ -280,13 +264,9 @@ impl<'a> Window<'a> { WindowHandle { state: window_state } } - pub fn close(&mut self) { - self.inner.close(); - } - - pub fn has_focus(&mut self) -> bool { + pub fn has_focus(&self) -> bool { unsafe { - let view = self.inner.ns_view.as_mut().unwrap(); + let view = self.ns_view; let window: id = msg_send![view, window]; if window == nil { return false; @@ -298,9 +278,9 @@ impl<'a> Window<'a> { } } - pub fn focus(&mut self) { + pub fn focus(&self) { unsafe { - let view = self.inner.ns_view.as_mut().unwrap(); + let view = self.ns_view; let window: id = msg_send![view, window]; if window != nil { msg_send![window, makeFirstResponder:view] @@ -308,38 +288,38 @@ impl<'a> Window<'a> { } } - pub fn resize(&mut self, size: Size) { - if self.inner.open.get() { + pub fn resize(&self, size: Size) { + if self.open.get() { // NOTE: macOS gives you a personal rave if you pass in fractional pixels here. Even // though the size is in fractional pixels. let size = NSSize::new(size.width.round(), size.height.round()); - unsafe { NSView::setFrameSize(self.inner.ns_view, size) }; + unsafe { NSView::setFrameSize(self.ns_view, size) }; unsafe { - let _: () = msg_send![self.inner.ns_view, setNeedsDisplay: YES]; + let _: () = msg_send![self.ns_view, setNeedsDisplay: YES]; } // When using OpenGL the `NSOpenGLView` needs to be resized separately? Why? Because // macOS. #[cfg(feature = "opengl")] - if let Some(gl_context) = &self.inner.gl_context { + if let Some(gl_context) = &self.gl_context { gl_context.resize(size); } // If this is a standalone window then we'll also need to resize the window itself - if let Some(ns_window) = self.inner.ns_window.get() { + if let Some(ns_window) = self.ns_window.get() { unsafe { NSWindow::setContentSize_(ns_window, size) }; } } } - pub fn set_mouse_cursor(&mut self, _mouse_cursor: MouseCursor) { + pub fn set_mouse_cursor(&self, _mouse_cursor: MouseCursor) { todo!() } #[cfg(feature = "opengl")] - pub fn gl_context(&self) -> Option<&GlContext> { - self.inner.gl_context.as_ref() + pub fn gl_context(&self) -> Option> { + self.gl_context.as_ref().map(Rc::downgrade) } #[cfg(feature = "opengl")] @@ -351,10 +331,14 @@ impl<'a> Window<'a> { unsafe { GlContext::create(&handle, config).expect("Could not create OpenGL context") } } + + pub fn raw_display_handle(&self) -> RawDisplayHandle { + RawDisplayHandle::AppKit(AppKitDisplayHandle::empty()) + } } pub(super) struct WindowState { - pub(super) window_inner: WindowInner, + pub(super) window_inner: Rc, window_handler: RefCell>, keyboard_state: KeyboardState, frame_timer: Cell>, @@ -379,13 +363,11 @@ impl WindowState { } pub(super) fn trigger_event(&self, event: Event) -> EventStatus { - let mut window = crate::Window::new(Window { inner: &self.window_inner }); - self.window_handler.borrow_mut().on_event(&mut window, event) + self.window_handler.borrow_mut().on_event(event) } pub(super) fn trigger_frame(&self) { - let mut window = crate::Window::new(Window { inner: &self.window_inner }); - self.window_handler.borrow_mut().on_frame(&mut window); + self.window_handler.borrow_mut().on_frame(); } pub(super) fn keyboard_state(&self) -> &KeyboardState { @@ -421,18 +403,6 @@ impl WindowState { } } -unsafe impl<'a> HasRawWindowHandle for Window<'a> { - fn raw_window_handle(&self) -> RawWindowHandle { - self.inner.raw_window_handle() - } -} - -unsafe impl<'a> HasRawDisplayHandle for Window<'a> { - fn raw_display_handle(&self) -> RawDisplayHandle { - RawDisplayHandle::AppKit(AppKitDisplayHandle::empty()) - } -} - pub fn copy_to_clipboard(string: &str) { unsafe { let pb = NSPasteboard::generalPasteboard(nil); diff --git a/src/win/drop_target.rs b/src/win/drop_target.rs index 0ec0f8c..6ccf669 100644 --- a/src/win/drop_target.rs +++ b/src/win/drop_target.rs @@ -11,19 +11,20 @@ use winapi::shared::windef::POINTL; use winapi::shared::winerror::{E_NOINTERFACE, E_UNEXPECTED, S_OK}; use winapi::shared::wtypes::DVASPECT_CONTENT; use winapi::um::objidl::{IDataObject, FORMATETC, STGMEDIUM, TYMED_HGLOBAL}; +use winapi::um::ole2::RegisterDragDrop; use winapi::um::oleidl::{ IDropTarget, IDropTargetVtbl, DROPEFFECT_COPY, DROPEFFECT_LINK, DROPEFFECT_MOVE, - DROPEFFECT_NONE, DROPEFFECT_SCROLL, + DROPEFFECT_NONE, DROPEFFECT_SCROLL, LPDROPTARGET, }; use winapi::um::shellapi::{DragQueryFileW, HDROP}; use winapi::um::unknwnbase::{IUnknown, IUnknownVtbl}; use winapi::um::winuser::CF_HDROP; use winapi::Interface; +use crate::win::proc::ProcState; +use crate::win::win32_window::Win32Window; use crate::{DropData, DropEffect, Event, EventStatus, MouseEvent, PhyPoint, Point}; -use super::WindowState; - // These function pointers have to be stored in a (const) variable before they can be transmuted // Transmuting is needed because winapi has a bug where the pt parameter has an incorrect // type `*const POINTL` @@ -63,7 +64,7 @@ const DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl { pub(super) struct DropTarget { base: IDropTarget, - window_state: Weak, + proc_state: Weak, // These are cached since DragOver and DragLeave callbacks don't provide them, // and handling drag move events gets awkward on the client end otherwise @@ -71,30 +72,33 @@ pub(super) struct DropTarget { drop_data: DropData, } +// FIXME: properly handle unwinding impl DropTarget { - pub(super) fn new(window_state: Weak) -> Self { - Self { + pub(super) fn register(proc_state: Weak, window: &Win32Window) -> Rc { + let drop_target = Rc::new(Self { base: IDropTarget { lpVtbl: &DROP_TARGET_VTBL }, - - window_state, - + proc_state, drag_position: Point::new(0.0, 0.0), drop_data: DropData::None, + }); + + let handle = window.handle(); + unsafe { + RegisterDragDrop(handle, Rc::as_ptr(&drop_target) as LPDROPTARGET); } + + drop_target } #[allow(non_snake_case)] fn on_event(&self, pdwEffect: Option<*mut DWORD>, event: MouseEvent) { - let Some(window_state) = self.window_state.upgrade() else { + let Some(window_state) = self.proc_state.upgrade() else { return; }; unsafe { - let mut window = crate::Window::new(window_state.create_window()); - let event = Event::Mouse(event); - let event_status = - window_state.handler_mut().as_mut().unwrap().on_event(&mut window, event); + let event_status = window_state.handler_mut().on_event(event); if let Some(pdwEffect) = pdwEffect { match event_status { @@ -109,12 +113,13 @@ impl DropTarget { } fn parse_coordinates(&mut self, pt: POINTL) { - let Some(window_state) = self.window_state.upgrade() else { + let Some(window_state) = self.proc_state.upgrade() else { return; }; let phy_point = PhyPoint::new(pt.x, pt.y); - self.drag_position = phy_point.to_logical(&window_state.window_info()); + let current_window_size = window_state.window.win32_window.current_size(); + self.drag_position = phy_point.to_logical(¤t_window_size); } fn parse_drop_data(&mut self, data_object: &IDataObject) { @@ -204,12 +209,14 @@ impl DropTarget { pdwEffect: *mut DWORD, ) -> HRESULT { let drop_target = &mut *(this as *mut DropTarget); - let Some(window_state) = drop_target.window_state.upgrade() else { + let Some(window_state) = drop_target.proc_state.upgrade() else { return E_UNEXPECTED; }; - let modifiers = - window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); + let modifiers = window_state + .keyboard_state + .borrow() + .get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); drop_target.parse_coordinates(pt); drop_target.parse_drop_data(&*pDataObj); @@ -229,12 +236,14 @@ impl DropTarget { this: *mut IDropTarget, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD, ) -> HRESULT { let drop_target = &mut *(this as *mut DropTarget); - let Some(window_state) = drop_target.window_state.upgrade() else { + let Some(window_state) = drop_target.proc_state.upgrade() else { return E_UNEXPECTED; }; - let modifiers = - window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); + let modifiers = window_state + .keyboard_state + .borrow() + .get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); drop_target.parse_coordinates(pt); @@ -260,12 +269,14 @@ impl DropTarget { pdwEffect: *mut DWORD, ) -> HRESULT { let drop_target = &mut *(this as *mut DropTarget); - let Some(window_state) = drop_target.window_state.upgrade() else { + let Some(window_state) = drop_target.proc_state.upgrade() else { return E_UNEXPECTED; }; - let modifiers = - window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); + let modifiers = window_state + .keyboard_state + .borrow() + .get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); drop_target.parse_coordinates(pt); drop_target.parse_drop_data(&*pDataObj); diff --git a/src/win/handle.rs b/src/win/handle.rs new file mode 100644 index 0000000..4614f58 --- /dev/null +++ b/src/win/handle.rs @@ -0,0 +1,100 @@ +use crate::win::win32_window::Win32Window; +use raw_window_handle::{RawWindowHandle, Win32WindowHandle}; +use std::cell::Cell; +use std::ffi::c_void; +use std::mem::MaybeUninit; +use std::rc::Rc; +use winapi::shared::windef::HWND; +use winapi::um::winuser::{DispatchMessageW, GetMessageW, TranslateMessage}; + +struct HandleShared { + is_open: Cell, +} + +pub struct WindowHandleTransmitter { + shared: Rc, +} + +impl WindowHandleTransmitter { + pub unsafe fn new(handle: HWND) -> (WindowHandleTransmitter, WindowHandle) { + let shared = Rc::new(HandleShared { is_open: Cell::new(true) }); + + ( + WindowHandleTransmitter { shared: shared.clone() }, + WindowHandle { shared, inner: Some(handle) }, + ) + } + + pub fn notify_closed(&self) { + self.shared.is_open.set(false); + } +} + +impl Drop for WindowHandleTransmitter { + // Note: this is never guaranteed to be called. + fn drop(&mut self) { + self.notify_closed() + } +} + +pub struct WindowHandle { + inner: Option, + shared: Rc, +} + +impl WindowHandle { + pub(crate) fn block_on_window(mut self) { + // SAFETY: we keep the handle valid + unsafe { block_on_running_window(self.inner.take().unwrap()) } + } + + pub fn close(&mut self) { + if !self.is_open() { + return; + } + + if let Some(hwnd) = self.inner.take() { + unsafe { + Win32Window::request_close(hwnd); + } + } + } + + pub fn is_open(&self) -> bool { + self.shared.is_open.get() + } + + pub fn raw_window_handle(&self) -> RawWindowHandle { + let mut handle = Win32WindowHandle::empty(); + // TODO: add hinstance + + if self.is_open() { + if let Some(hwnd) = self.inner { + handle.hwnd = hwnd as *mut c_void; + } + } + + handle.into() + } +} + +/// # Safety +/// The handle must be valid. +unsafe fn block_on_running_window(hwnd: HWND) { + let mut msg = MaybeUninit::zeroed(); + + loop { + let status = unsafe { GetMessageW(msg.as_mut_ptr(), hwnd, 0, 0) }; + + if status == -1 { + break; + } + + let msg = msg.assume_init_ref(); + + unsafe { + TranslateMessage(msg); + DispatchMessageW(msg); + } + } +} diff --git a/src/win/mod.rs b/src/win/mod.rs index 00effa4..3e60bb0 100644 --- a/src/win/mod.rs +++ b/src/win/mod.rs @@ -1,6 +1,11 @@ mod cursor; mod drop_target; +mod handle; mod keyboard; +mod proc; +mod util; +mod win32_window; mod window; -pub use window::*; +pub(crate) use handle::WindowHandle; +pub(crate) use window::{copy_to_clipboard, Window}; diff --git a/src/win/proc.rs b/src/win/proc.rs new file mode 100644 index 0000000..4f25000 --- /dev/null +++ b/src/win/proc.rs @@ -0,0 +1,307 @@ +use crate::win::handle::WindowHandleTransmitter; +use crate::win::win32_window::Win32Window; +use crate::win::Window; +use crate::{ + Event, MouseButton, MouseEvent, PhyPoint, PhySize, ScrollDelta, WindowEvent, WindowHandler, +}; + +use crate::win::drop_target::DropTarget; +use crate::win::keyboard::KeyboardState; +use std::cell::{Cell, RefCell, RefMut}; +use std::panic::{catch_unwind, AssertUnwindSafe}; +use std::rc::Rc; +use winapi::shared::minwindef::{LPARAM, LRESULT, UINT, WPARAM}; +use winapi::shared::windef::HWND; +use winapi::um::ole2::RevokeDragDrop; +use winapi::um::winuser::{ + DefWindowProcW, DestroyWindow, GetWindowLongPtrW, PostMessageW, ReleaseCapture, SetCapture, + SetWindowLongPtrW, TrackMouseEvent, GET_XBUTTON_WPARAM, GWLP_USERDATA, TRACKMOUSEEVENT, + WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DPICHANGED, WM_INPUTLANGCHANGE, WM_KEYDOWN, + WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, + WM_MOUSELEAVE, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCDESTROY, WM_RBUTTONDOWN, WM_RBUTTONUP, + WM_SHOWWINDOW, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TIMER, WM_XBUTTONDOWN, + WM_XBUTTONUP, XBUTTON1, XBUTTON2, +}; + +pub(crate) struct ProcState { + pub(crate) window: Rc, + // FIXME: do not expose this, expose handle_event/frame methods instead to ensure + // the borrows aren't kept for too long in callers + pub(crate) handler: RefCell>, + handle_transmitter: WindowHandleTransmitter, + _drop_target: Rc, + + // Internals for proc event handling + // TODO: refactor KeyboardState to use interior mutability + pub(crate) keyboard_state: RefCell, + mouse_button_counter: Cell, + mouse_was_outside_window: Cell, +} + +impl ProcState { + pub fn new( + window: Rc, handle_transmitter: WindowHandleTransmitter, + handler: impl WindowHandler, + ) -> Rc { + Rc::new_cyclic(move |proc_state| Self { + _drop_target: DropTarget::register(proc_state.clone(), &window.win32_window), + window, + handler: RefCell::new(Box::new(handler)), + handle_transmitter, + keyboard_state: RefCell::new(KeyboardState::new()), + mouse_button_counter: Cell::new(0), + mouse_was_outside_window: Cell::new(true), + }) + } + + pub fn move_to_proc(self: Rc) { + let handle = self.window.win32_window.handle(); + let proc_data_ptr = Rc::into_raw(self); + + unsafe { + SetWindowLongPtrW(handle, GWLP_USERDATA, proc_data_ptr as _); + } + } + + pub fn handler_mut(&self) -> RefMut> { + self.handler.borrow_mut() + } + + unsafe fn destroy(ptr: *const Self) { + { + let state = &*ptr; + state.handle_transmitter.notify_closed(); + + let handle = state.window.win32_window.handle(); + RevokeDragDrop(handle); + SetWindowLongPtrW(handle, GWLP_USERDATA, 0); + } + + drop(Rc::from_raw(ptr)); + } +} + +pub(crate) unsafe extern "system" fn wnd_proc( + hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM, +) -> LRESULT { + if msg == WM_CREATE { + PostMessageW(hwnd, WM_SHOWWINDOW, 0, 0); + return 0; + } + + let state_ptr = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *const ProcState; + if state_ptr.is_null() { + return DefWindowProcW(hwnd, msg, wparam, lparam); + } + + let result = catch_unwind(AssertUnwindSafe(move || { + let state = &*state_ptr; + let result = wnd_proc_inner(hwnd, msg, wparam, lparam, state); + + // If any of the above event handlers caused tasks to be pushed to the deferred tasks list, + // then we'll try to handle them now + state.window.handle_deferred_tasks(); + + // NOTE: This is not handled in `wnd_proc_inner` because of the deferred task loop above + if msg == WM_NCDESTROY { + ProcState::destroy(state_ptr) + } + + result + })); + + // The actual custom window proc has been moved to another function so we can always handle + // the deferred tasks regardless of whether the custom window proc returns early or not + match result { + Ok(Some(result)) => result, + Ok(None) => DefWindowProcW(hwnd, msg, wparam, lparam), + Err(_) => 0, // TODO: handle panics? + } +} + +/// Our custom `wnd_proc` handler. If the result contains a value, then this is returned after +/// handling any deferred tasks. otherwise the default window procedure is invoked. +unsafe fn wnd_proc_inner( + hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM, state: &ProcState, +) -> Option { + match msg { + WM_MOUSEMOVE => { + // FIXME: use TrackMouseEvent to generate the WM_MOUSEHOVER events instead of this + if state.mouse_was_outside_window.get() { + // this makes Windows track whether the mouse leaves the window. + // When the mouse leaves it results in a `WM_MOUSELEAVE` event. + let mut track_mouse = TRACKMOUSEEVENT { + cbSize: std::mem::size_of::() as u32, + dwFlags: winapi::um::winuser::TME_LEAVE, + hwndTrack: hwnd, + dwHoverTime: winapi::um::winuser::HOVER_DEFAULT, + }; + // Couldn't find a good way to track whether the mouse enters, + // but if `WM_MOUSEMOVE` happens, the mouse must have entered. + TrackMouseEvent(&mut track_mouse); + state.mouse_was_outside_window.set(false); + + let enter_event = Event::Mouse(MouseEvent::CursorEntered); + state.handler.borrow_mut().on_event(enter_event); + } + + let x = (lparam & 0xFFFF) as i16 as i32; + let y = ((lparam >> 16) & 0xFFFF) as i16 as i32; + + let physical_pos = PhyPoint { x, y }; + let logical_pos = physical_pos.to_logical(&state.window.win32_window.current_size()); + let move_event = Event::Mouse(MouseEvent::CursorMoved { + position: logical_pos, + modifiers: state.keyboard_state.borrow().get_modifiers_from_mouse_wparam(wparam), + }); + state.handler.borrow_mut().on_event(move_event); + Some(0) + } + + WM_MOUSELEAVE => { + let event = Event::Mouse(MouseEvent::CursorLeft); + state.handler.borrow_mut().on_event(event); + + state.mouse_was_outside_window.set(true); + Some(0) + } + WM_MOUSEWHEEL | WM_MOUSEHWHEEL => { + let value = (wparam >> 16) as i16; + let value = value as i32; + let value = value as f32 / WHEEL_DELTA as f32; + + let event = Event::Mouse(MouseEvent::WheelScrolled { + delta: if msg == WM_MOUSEWHEEL { + ScrollDelta::Lines { x: 0.0, y: value } + } else { + ScrollDelta::Lines { x: value, y: 0.0 } + }, + modifiers: state.keyboard_state.borrow().get_modifiers_from_mouse_wparam(wparam), + }); + + state.handler.borrow_mut().on_event(event); + + Some(0) + } + WM_LBUTTONDOWN | WM_LBUTTONUP | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_RBUTTONDOWN + | WM_RBUTTONUP | WM_XBUTTONDOWN | WM_XBUTTONUP => { + let mut mouse_button_counter = state.mouse_button_counter.get(); + + let button = match msg { + WM_LBUTTONDOWN | WM_LBUTTONUP => Some(MouseButton::Left), + WM_MBUTTONDOWN | WM_MBUTTONUP => Some(MouseButton::Middle), + WM_RBUTTONDOWN | WM_RBUTTONUP => Some(MouseButton::Right), + WM_XBUTTONDOWN | WM_XBUTTONUP => match GET_XBUTTON_WPARAM(wparam) { + XBUTTON1 => Some(MouseButton::Back), + XBUTTON2 => Some(MouseButton::Forward), + _ => None, + }, + _ => None, + }; + + if let Some(button) = button { + let event = match msg { + WM_LBUTTONDOWN | WM_MBUTTONDOWN | WM_RBUTTONDOWN | WM_XBUTTONDOWN => { + // Capture the mouse cursor on button down + mouse_button_counter = mouse_button_counter.saturating_add(1); + SetCapture(hwnd); + MouseEvent::ButtonPressed { + button, + modifiers: state + .keyboard_state + .borrow() + .get_modifiers_from_mouse_wparam(wparam), + } + } + WM_LBUTTONUP | WM_MBUTTONUP | WM_RBUTTONUP | WM_XBUTTONUP => { + // Release the mouse cursor capture when all buttons are released + mouse_button_counter = mouse_button_counter.saturating_sub(1); + if mouse_button_counter == 0 { + ReleaseCapture(); + } + + MouseEvent::ButtonReleased { + button, + modifiers: state + .keyboard_state + .borrow() + .get_modifiers_from_mouse_wparam(wparam), + } + } + _ => { + unreachable!() + } + }; + + state.mouse_button_counter.set(mouse_button_counter); + + state.handler.borrow_mut().on_event(Event::Mouse(event)); + } + + None + } + WM_TIMER => { + if wparam == Win32Window::WIN_FRAME_TIMER { + state.handler.borrow_mut().on_frame(); + } + + Some(0) + } + WM_CLOSE => { + // Make sure to release the borrow before the DefWindowProc call + { + state.handler.borrow_mut().on_event(Event::Window(WindowEvent::WillClose)); + } + + // DestroyWindow(hwnd); + // Some(0) + Some(DefWindowProcW(hwnd, msg, wparam, lparam)) + } + WM_CHAR | WM_SYSCHAR | WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP + | WM_INPUTLANGCHANGE => { + let opt_event = + state.keyboard_state.borrow_mut().process_message(hwnd, msg, wparam, lparam); + + if let Some(event) = opt_event { + state.handler.borrow_mut().on_event(Event::Keyboard(event)); + } + + if msg != WM_SYSKEYDOWN { + Some(0) + } else { + None + } + } + WM_SIZE => { + let new_size = PhySize { + width: (lparam & 0xFFFF) as u16 as u32, + height: ((lparam >> 16) & 0xFFFF) as u16 as u32, + }; + + // Only send the event if anything changed + if let Some(new_window_info) = state.window.win32_window.resized(new_size) { + state + .handler + .borrow_mut() + .on_event(Event::Window(WindowEvent::Resized(new_window_info))); + }; + + None + } + WM_DPICHANGED => { + let dpi = (wparam & 0xFFFF) as u16 as u32; + let scale_factor = dpi as f64 / 96.0; + + state.window.win32_window.update_scale_factor(scale_factor); + + None + } + // NOTE: `WM_NCDESTROY` is handled in the outer function because this deallocates the window + // state + Win32Window::BV_WINDOW_MUST_CLOSE => { + DestroyWindow(hwnd); + Some(0) + } + _ => None, + } +} diff --git a/src/win/util.rs b/src/win/util.rs new file mode 100644 index 0000000..a2a0129 --- /dev/null +++ b/src/win/util.rs @@ -0,0 +1,8 @@ +use std::ffi::OsStr; +use std::os::windows::ffi::OsStrExt; + +pub fn to_wstr(str: &str) -> Vec { + let mut title: Vec = OsStr::new(str).encode_wide().collect(); + title.push(0); + title +} diff --git a/src/win/win32_window.rs b/src/win/win32_window.rs new file mode 100644 index 0000000..ecfba13 --- /dev/null +++ b/src/win/win32_window.rs @@ -0,0 +1,278 @@ +use crate::win::util::to_wstr; +use crate::{PhySize, Size, WindowInfo, WindowOpenOptions, WindowScalePolicy}; +use raw_window_handle::Win32WindowHandle; +use std::cell::Cell; +use std::ffi::c_void; +use std::ptr::null_mut; +use winapi::shared::minwindef::{DWORD, UINT}; +use winapi::shared::windef::{HWND, RECT}; +use winapi::um::winuser::{ + AdjustWindowRectEx, CreateWindowExW, GetDpiForWindow, GetFocus, KillTimer, PostMessageW, + SetFocus, SetProcessDpiAwarenessContext, SetTimer, SetWindowPos, SWP_NOMOVE, SWP_NOZORDER, + WM_USER, WS_CAPTION, WS_CHILD, WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_POPUPWINDOW, + WS_SIZEBOX, WS_VISIBLE, +}; + +mod class; +use class::*; + +pub(crate) struct Win32Window { + _class: WndClass, + handle: HWND, + style_flags: DWORD, + + current_size: Cell, + scale_policy: WindowScalePolicy, + + frame_timer_started: Cell, + + #[cfg(feature = "opengl")] + pub(crate) gl_context: Option>, +} + +impl Win32Window { + // TODO: manage errors + pub fn create(parent: Option, options: &WindowOpenOptions) -> Self { + // FIXME: try not to re-register a new class on every window open + let class = WndClass::register(); + + let initial_scaling = match options.scale { + WindowScalePolicy::SystemScaleFactor => 1.0, + WindowScalePolicy::ScaleFactor(scale) => scale, + }; + + let initial_size = WindowInfo::from_logical_size(options.size, initial_scaling); + + let parented = parent.is_some(); + + let style_flags = if parented { + WS_CHILD | WS_VISIBLE + } else { + WS_POPUPWINDOW + | WS_CAPTION + | WS_VISIBLE + | WS_SIZEBOX + | WS_MINIMIZEBOX + | WS_MAXIMIZEBOX + | WS_CLIPSIBLINGS + }; + + let window_size = if parented { + initial_size.physical_size() + } else { + client_size_to_window_size(initial_size.physical_size(), style_flags) + }; + + let title = to_wstr(&options.title); + let handle = unsafe { + CreateWindowExW( + 0, + class.atom() as _, + title.as_ptr(), + style_flags, + 0, // TODO: initial position + 0, + window_size.width as i32, + window_size.height as i32, + parent.unwrap_or(null_mut()) as *mut _, + null_mut(), + null_mut(), + null_mut(), + ) + }; + + // TODO: GL context + let mut window = Self { + _class: class, + handle, + style_flags, + current_size: Cell::new(initial_size), + scale_policy: options.scale, + frame_timer_started: Cell::new(false), + #[cfg(feature = "opengl")] + gl_context: None, + }; + + // FIXME: this should NOT be changed if the window is part of an host + // Only works on Windows 10. + unsafe { + SetProcessDpiAwarenessContext( + winapi::shared::windef::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, + ); + } + + // Now we can get the actual dpi of the window. + window.check_for_dpi_changes(); + + #[cfg(feature = "opengl")] + window.create_gl_context(options); + window.start_frame_timer(); + + window + } + + fn current_system_scale_factor(&self) -> f64 { + // FIXME: Only works on Windows 10. + let dpi = unsafe { GetDpiForWindow(self.handle) }; + dpi as f64 / 96.0 + } + + pub fn raw_window_handle(&self) -> Win32WindowHandle { + let mut handle = Win32WindowHandle::empty(); + handle.hwnd = self.handle() as *mut c_void; + handle + } + + #[cfg(feature = "opengl")] + fn create_gl_context(&mut self, options: &WindowOpenOptions) { + self.gl_context = options.gl_config.as_ref().map(|gl_config| { + let ctx = + // SAFETY: TODO + unsafe { crate::gl::win::GlContext::create(&self.raw_window_handle(), gl_config.clone()) } + .expect("Could not create OpenGL context"); + std::rc::Rc::new(ctx) + }); + } + + fn resize(&self, size: PhySize) { + let window_size = client_size_to_window_size(size, self.style_flags); + + // Windows makes us resize the window manually. This will trigger another `WM_SIZE` event, + // which we can then send the user the new scale factor. + unsafe { + SetWindowPos( + self.handle, + self.handle, + 0, + 0, + window_size.width as i32, + window_size.height as i32, + SWP_NOZORDER | SWP_NOMOVE, + ); + } + } + + fn check_for_dpi_changes(&self) { + // Do not do anything if the scale factor is forced by the user + if self.scale_policy != WindowScalePolicy::SystemScaleFactor { + return; + } + let current_size = self.current_size.get(); + + let current_scale_factor = self.current_system_scale_factor(); + if current_scale_factor == current_size.scale() { + return; + } + + let new_size = + WindowInfo::from_logical_size(current_size.logical_size(), current_scale_factor); + self.resize(new_size.physical_size()); + self.current_size.set(new_size); + } + + pub fn has_focus(&self) -> bool { + let focused_window = unsafe { GetFocus() }; + focused_window == self.handle + } + + pub fn focus(&self) { + unsafe { + SetFocus(self.handle); + } + } + + pub fn handle(&self) -> HWND { + self.handle + } + + pub fn resize_logical(&self, size: Size) { + let current_size = self.current_size.get(); + // TODO: use updated current scale instead? + let new_size = WindowInfo::from_logical_size(size, current_size.scale()); + self.resize(new_size.physical_size()) + } + + /// Called when the size has been changed from an external event. + /// Returns None if the size didn't actually change. + pub fn resized(&self, new_size: PhySize) -> Option { + let current_size = self.current_size.get(); + + if current_size.physical_size() == new_size { + return None; + } + + let new_size = WindowInfo::from_physical_size(new_size, current_size.scale()); + self.current_size.set(new_size); + + Some(new_size) + } + + pub fn update_scale_factor(&self, new_scale_factor: f64) { + if self.scale_policy != WindowScalePolicy::SystemScaleFactor { + // We don't care about DPI updates if the scale factor is forced by the user. + return; + } + + let current_size = self.current_size.get(); + let new_size = WindowInfo::from_logical_size(current_size.logical_size(), new_scale_factor); + self.resize(new_size.physical_size()); + self.current_size.set(new_size); + } + + pub fn current_size(&self) -> WindowInfo { + self.current_size.get() + } + + pub const WIN_FRAME_TIMER: usize = 4242; + pub fn start_frame_timer(&self) { + if self.frame_timer_started.get() { + return; + } + + unsafe { SetTimer(self.handle, Self::WIN_FRAME_TIMER, 15, None) }; + + self.frame_timer_started.set(true) + } + + pub fn stop_frame_timer(&self) { + if !self.frame_timer_started.get() { + return; + } + + unsafe { KillTimer(self.handle, Self::WIN_FRAME_TIMER) }; + self.frame_timer_started.set(false) + } + + pub const BV_WINDOW_MUST_CLOSE: UINT = WM_USER + 1; + + pub unsafe fn request_close(handle: HWND) { + PostMessageW(handle, Self::BV_WINDOW_MUST_CLOSE, 0, 0); + } + + pub fn close(&self) { + unsafe { Self::request_close(self.handle) } + } +} + +impl Drop for Win32Window { + fn drop(&mut self) { + self.stop_frame_timer() + } +} + +pub fn client_size_to_window_size(size: PhySize, window_flags: DWORD) -> PhySize { + let mut rect = RECT { + left: 0, + top: 0, + // todo: check if usize fits into i32 + right: size.width as i32, + bottom: size.height as i32, + }; + + unsafe { + AdjustWindowRectEx(&mut rect, window_flags, 0, 0); + } + + // TODO: saturating operations? + PhySize { width: (rect.right - rect.left) as u32, height: (rect.bottom - rect.top) as u32 } +} diff --git a/src/win/win32_window/class.rs b/src/win/win32_window/class.rs new file mode 100644 index 0000000..eaddf34 --- /dev/null +++ b/src/win/win32_window/class.rs @@ -0,0 +1,72 @@ +use std::ffi::OsStr; +use std::mem::MaybeUninit; +use std::os::windows::ffi::OsStrExt; +use std::ptr::null_mut; +use winapi::shared::minwindef::ATOM; +use winapi::um::combaseapi::CoCreateGuid; +use winapi::um::winuser::{ + LoadCursorW, RegisterClassW, UnregisterClassW, CS_OWNDC, IDC_ARROW, WNDCLASSW, +}; + +pub struct WndClass(ATOM); + +impl WndClass { + // TODO: manage error + pub fn register() -> Self { + let proc = crate::win::proc::wnd_proc; + // We generate a unique name for the new window class to prevent name collisions + let class_name_str = format!("Baseview-{}", generate_guid()); + let mut class_name: Vec = OsStr::new(&class_name_str).encode_wide().collect(); + class_name.push(0); + + let wnd_class = WNDCLASSW { + style: CS_OWNDC, + lpfnWndProc: Some(proc), + hInstance: null_mut(), + lpszClassName: class_name.as_ptr(), + cbClsExtra: 0, + cbWndExtra: 0, + hIcon: null_mut(), + hCursor: unsafe { LoadCursorW(null_mut(), IDC_ARROW) }, + hbrBackground: null_mut(), + lpszMenuName: null_mut(), + }; + + Self(unsafe { RegisterClassW(&wnd_class) }) + } + + pub fn atom(&self) -> ATOM { + self.0 + } +} + +impl Drop for WndClass { + fn drop(&mut self) { + unsafe { + UnregisterClassW(self.0 as _, null_mut()); + } + } +} + +fn generate_guid() -> String { + let mut guid = MaybeUninit::zeroed(); + // SAFETY: the given output pointer is valid + unsafe { CoCreateGuid(guid.as_mut_ptr()) }; + // SAFETY: CoCreateGuid should have initialized the GUID. + // In the worst case, a GUID is all just numbers, so it's still safe to read even when zeroed. + let guid = unsafe { guid.assume_init_ref() }; + format!( + "{:0X}-{:0X}-{:0X}-{:0X}{:0X}-{:0X}{:0X}{:0X}{:0X}{:0X}{:0X}\0", + guid.Data1, + guid.Data2, + guid.Data3, + guid.Data4[0], + guid.Data4[1], + guid.Data4[2], + guid.Data4[3], + guid.Data4[4], + guid.Data4[5], + guid.Data4[6], + guid.Data4[7] + ) +} diff --git a/src/win/window.rs b/src/win/window.rs index ac7824b..770d1a3 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -1,593 +1,49 @@ -use winapi::shared::guiddef::GUID; -use winapi::shared::minwindef::{ATOM, FALSE, LOWORD, LPARAM, LRESULT, UINT, WPARAM}; -use winapi::shared::windef::{HWND, RECT}; -use winapi::um::combaseapi::CoCreateGuid; -use winapi::um::ole2::{OleInitialize, RegisterDragDrop, RevokeDragDrop}; -use winapi::um::oleidl::LPDROPTARGET; -use winapi::um::winuser::{ - AdjustWindowRectEx, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, - GetDpiForWindow, GetFocus, GetMessageW, GetWindowLongPtrW, LoadCursorW, PostMessageW, - RegisterClassW, ReleaseCapture, SetCapture, SetCursor, SetFocus, SetProcessDpiAwarenessContext, - SetTimer, SetWindowLongPtrW, SetWindowPos, TrackMouseEvent, TranslateMessage, UnregisterClassW, - CS_OWNDC, GET_XBUTTON_WPARAM, GWLP_USERDATA, HTCLIENT, IDC_ARROW, MSG, SWP_NOMOVE, - SWP_NOZORDER, TRACKMOUSEEVENT, WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DPICHANGED, - WM_INPUTLANGCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, - WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSELEAVE, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCDESTROY, - WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SHOWWINDOW, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, - WM_SYSKEYUP, WM_TIMER, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_CAPTION, WS_CHILD, - WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE, - XBUTTON1, XBUTTON2, -}; - -use std::cell::{Cell, Ref, RefCell, RefMut}; +use std::cell::{Cell, RefCell}; use std::collections::VecDeque; -use std::ffi::{c_void, OsStr}; -use std::os::windows::ffi::OsStrExt; use std::ptr::null_mut; use std::rc::Rc; +use winapi::shared::windef::HWND; +use winapi::um::ole2::OleInitialize; use raw_window_handle::{ - HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, Win32WindowHandle, - WindowsDisplayHandle, -}; - -const BV_WINDOW_MUST_CLOSE: UINT = WM_USER + 1; - -use crate::{ - Event, MouseButton, MouseCursor, MouseEvent, PhyPoint, PhySize, ScrollDelta, Size, WindowEvent, - WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, + HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, WindowsDisplayHandle, }; - -use super::cursor::cursor_to_lpcwstr; -use super::drop_target::DropTarget; -use super::keyboard::KeyboardState; +use winapi::um::winuser::{LoadCursorW, SetCursor}; #[cfg(feature = "opengl")] -use crate::gl::GlContext; - -unsafe fn generate_guid() -> String { - let mut guid: GUID = std::mem::zeroed(); - CoCreateGuid(&mut guid); - format!( - "{:0X}-{:0X}-{:0X}-{:0X}{:0X}-{:0X}{:0X}{:0X}{:0X}{:0X}{:0X}\0", - guid.Data1, - guid.Data2, - guid.Data3, - guid.Data4[0], - guid.Data4[1], - guid.Data4[2], - guid.Data4[3], - guid.Data4[4], - guid.Data4[5], - guid.Data4[6], - guid.Data4[7] - ) -} - -const WIN_FRAME_TIMER: usize = 4242; - -pub struct WindowHandle { - hwnd: Option, - is_open: Rc>, -} - -impl WindowHandle { - pub fn close(&mut self) { - if let Some(hwnd) = self.hwnd.take() { - unsafe { - PostMessageW(hwnd, BV_WINDOW_MUST_CLOSE, 0, 0); - } - } - } - - pub fn is_open(&self) -> bool { - self.is_open.get() - } -} - -unsafe impl HasRawWindowHandle for WindowHandle { - fn raw_window_handle(&self) -> RawWindowHandle { - if let Some(hwnd) = self.hwnd { - let mut handle = Win32WindowHandle::empty(); - handle.hwnd = hwnd as *mut c_void; - - RawWindowHandle::Win32(handle) - } else { - RawWindowHandle::Win32(Win32WindowHandle::empty()) - } - } -} - -struct ParentHandle { - is_open: Rc>, -} - -impl ParentHandle { - pub fn new(hwnd: HWND) -> (Self, WindowHandle) { - let is_open = Rc::new(Cell::new(true)); - - let handle = WindowHandle { hwnd: Some(hwnd), is_open: Rc::clone(&is_open) }; - - (Self { is_open }, handle) - } -} - -impl Drop for ParentHandle { - fn drop(&mut self) { - self.is_open.set(false); - } -} - -unsafe extern "system" fn wnd_proc( - hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM, -) -> LRESULT { - if msg == WM_CREATE { - PostMessageW(hwnd, WM_SHOWWINDOW, 0, 0); - return 0; - } - - let window_state_ptr = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut WindowState; - if !window_state_ptr.is_null() { - let result = wnd_proc_inner(hwnd, msg, wparam, lparam, &*window_state_ptr); - - // If any of the above event handlers caused tasks to be pushed to the deferred tasks list, - // then we'll try to handle them now - loop { - // NOTE: This is written like this instead of using a `while let` loop to avoid exending - // the borrow of `window_state.deferred_tasks` into the call of - // `window_state.handle_deferred_task()` since that may also generate additional - // messages. - let task = match (*window_state_ptr).deferred_tasks.borrow_mut().pop_front() { - Some(task) => task, - None => break, - }; - - (*window_state_ptr).handle_deferred_task(task); - } - - // NOTE: This is not handled in `wnd_proc_inner` because of the deferred task loop above - if msg == WM_NCDESTROY { - RevokeDragDrop(hwnd); - unregister_wnd_class((*window_state_ptr).window_class); - SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0); - drop(Rc::from_raw(window_state_ptr)); - } - - // The actual custom window proc has been moved to another function so we can always handle - // the deferred tasks regardless of whether the custom window proc returns early or not - if let Some(result) = result { - return result; - } - } - - DefWindowProcW(hwnd, msg, wparam, lparam) -} - -/// Our custom `wnd_proc` handler. If the result contains a value, then this is returned after -/// handling any deferred tasks. otherwise the default window procedure is invoked. -unsafe fn wnd_proc_inner( - hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM, window_state: &WindowState, -) -> Option { - match msg { - WM_MOUSEMOVE => { - let mut window = crate::Window::new(window_state.create_window()); - - let mut mouse_was_outside_window = window_state.mouse_was_outside_window.borrow_mut(); - if *mouse_was_outside_window { - // this makes Windows track whether the mouse leaves the window. - // When the mouse leaves it results in a `WM_MOUSELEAVE` event. - let mut track_mouse = TRACKMOUSEEVENT { - cbSize: std::mem::size_of::() as u32, - dwFlags: winapi::um::winuser::TME_LEAVE, - hwndTrack: hwnd, - dwHoverTime: winapi::um::winuser::HOVER_DEFAULT, - }; - // Couldn't find a good way to track whether the mouse enters, - // but if `WM_MOUSEMOVE` happens, the mouse must have entered. - TrackMouseEvent(&mut track_mouse); - *mouse_was_outside_window = false; - - let enter_event = Event::Mouse(MouseEvent::CursorEntered); - window_state - .handler - .borrow_mut() - .as_mut() - .unwrap() - .on_event(&mut window, enter_event); - } - - let x = (lparam & 0xFFFF) as i16 as i32; - let y = ((lparam >> 16) & 0xFFFF) as i16 as i32; - - let physical_pos = PhyPoint { x, y }; - let logical_pos = physical_pos.to_logical(&window_state.window_info.borrow()); - let move_event = Event::Mouse(MouseEvent::CursorMoved { - position: logical_pos, - modifiers: window_state - .keyboard_state - .borrow() - .get_modifiers_from_mouse_wparam(wparam), - }); - window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, move_event); - Some(0) - } - - WM_MOUSELEAVE => { - let mut window = crate::Window::new(window_state.create_window()); - let event = Event::Mouse(MouseEvent::CursorLeft); - window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); - - *window_state.mouse_was_outside_window.borrow_mut() = true; - Some(0) - } - WM_MOUSEWHEEL | WM_MOUSEHWHEEL => { - let mut window = crate::Window::new(window_state.create_window()); - - let value = (wparam >> 16) as i16; - let value = value as i32; - let value = value as f32 / WHEEL_DELTA as f32; - - let event = Event::Mouse(MouseEvent::WheelScrolled { - delta: if msg == WM_MOUSEWHEEL { - ScrollDelta::Lines { x: 0.0, y: value } - } else { - ScrollDelta::Lines { x: value, y: 0.0 } - }, - modifiers: window_state - .keyboard_state - .borrow() - .get_modifiers_from_mouse_wparam(wparam), - }); - - window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); - - Some(0) - } - WM_LBUTTONDOWN | WM_LBUTTONUP | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_RBUTTONDOWN - | WM_RBUTTONUP | WM_XBUTTONDOWN | WM_XBUTTONUP => { - let mut window = crate::Window::new(window_state.create_window()); - - let mut mouse_button_counter = window_state.mouse_button_counter.get(); - - let button = match msg { - WM_LBUTTONDOWN | WM_LBUTTONUP => Some(MouseButton::Left), - WM_MBUTTONDOWN | WM_MBUTTONUP => Some(MouseButton::Middle), - WM_RBUTTONDOWN | WM_RBUTTONUP => Some(MouseButton::Right), - WM_XBUTTONDOWN | WM_XBUTTONUP => match GET_XBUTTON_WPARAM(wparam) { - XBUTTON1 => Some(MouseButton::Back), - XBUTTON2 => Some(MouseButton::Forward), - _ => None, - }, - _ => None, - }; - - if let Some(button) = button { - let event = match msg { - WM_LBUTTONDOWN | WM_MBUTTONDOWN | WM_RBUTTONDOWN | WM_XBUTTONDOWN => { - // Capture the mouse cursor on button down - mouse_button_counter = mouse_button_counter.saturating_add(1); - SetCapture(hwnd); - MouseEvent::ButtonPressed { - button, - modifiers: window_state - .keyboard_state - .borrow() - .get_modifiers_from_mouse_wparam(wparam), - } - } - WM_LBUTTONUP | WM_MBUTTONUP | WM_RBUTTONUP | WM_XBUTTONUP => { - // Release the mouse cursor capture when all buttons are released - mouse_button_counter = mouse_button_counter.saturating_sub(1); - if mouse_button_counter == 0 { - ReleaseCapture(); - } - - MouseEvent::ButtonReleased { - button, - modifiers: window_state - .keyboard_state - .borrow() - .get_modifiers_from_mouse_wparam(wparam), - } - } - _ => { - unreachable!() - } - }; - - window_state.mouse_button_counter.set(mouse_button_counter); - - window_state - .handler - .borrow_mut() - .as_mut() - .unwrap() - .on_event(&mut window, Event::Mouse(event)); - } - - None - } - WM_TIMER => { - let mut window = crate::Window::new(window_state.create_window()); - - if wparam == WIN_FRAME_TIMER { - window_state.handler.borrow_mut().as_mut().unwrap().on_frame(&mut window); - } - - Some(0) - } - WM_CLOSE => { - // Make sure to release the borrow before the DefWindowProc call - { - let mut window = crate::Window::new(window_state.create_window()); - - window_state - .handler - .borrow_mut() - .as_mut() - .unwrap() - .on_event(&mut window, Event::Window(WindowEvent::WillClose)); - } - - // DestroyWindow(hwnd); - // Some(0) - Some(DefWindowProcW(hwnd, msg, wparam, lparam)) - } - WM_CHAR | WM_SYSCHAR | WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP - | WM_INPUTLANGCHANGE => { - let mut window = crate::Window::new(window_state.create_window()); - - let opt_event = - window_state.keyboard_state.borrow_mut().process_message(hwnd, msg, wparam, lparam); - - if let Some(event) = opt_event { - window_state - .handler - .borrow_mut() - .as_mut() - .unwrap() - .on_event(&mut window, Event::Keyboard(event)); - } - - if msg != WM_SYSKEYDOWN { - Some(0) - } else { - None - } - } - WM_SIZE => { - let mut window = crate::Window::new(window_state.create_window()); - - let width = (lparam & 0xFFFF) as u16 as u32; - let height = ((lparam >> 16) & 0xFFFF) as u16 as u32; - - let new_window_info = { - let mut window_info = window_state.window_info.borrow_mut(); - let new_window_info = - WindowInfo::from_physical_size(PhySize { width, height }, window_info.scale()); - - // Only send the event if anything changed - if window_info.physical_size() == new_window_info.physical_size() { - return None; - } - - *window_info = new_window_info; - - new_window_info - }; - - window_state - .handler - .borrow_mut() - .as_mut() - .unwrap() - .on_event(&mut window, Event::Window(WindowEvent::Resized(new_window_info))); - - None - } - WM_DPICHANGED => { - // To avoid weirdness with the realtime borrow checker. - let new_rect = { - if let WindowScalePolicy::SystemScaleFactor = window_state.scale_policy { - let dpi = (wparam & 0xFFFF) as u16 as u32; - let scale_factor = dpi as f64 / 96.0; - - let mut window_info = window_state.window_info.borrow_mut(); - *window_info = - WindowInfo::from_logical_size(window_info.logical_size(), scale_factor); - - Some(( - RECT { - left: 0, - top: 0, - // todo: check if usize fits into i32 - right: window_info.physical_size().width as i32, - bottom: window_info.physical_size().height as i32, - }, - window_state.dw_style, - )) - } else { - None - } - }; - if let Some((mut new_rect, dw_style)) = new_rect { - // Convert this desired "client rectangle" size to the actual "window rectangle" - // size (Because of course you have to do that). - AdjustWindowRectEx(&mut new_rect, dw_style, 0, 0); - - // Windows makes us resize the window manually. This will trigger another `WM_SIZE` event, - // which we can then send the user the new scale factor. - SetWindowPos( - hwnd, - hwnd, - new_rect.left, - new_rect.top, - new_rect.right - new_rect.left, - new_rect.bottom - new_rect.top, - SWP_NOZORDER | SWP_NOMOVE, - ); - } - - None - } - // If WM_SETCURSOR returns `None`, WM_SETCURSOR continues to get handled by the outer window(s), - // If it returns `Some(1)`, the current window decides what the cursor is - WM_SETCURSOR => { - let low_word = LOWORD(lparam as u32) as isize; - let mouse_in_window = low_word == HTCLIENT; - if mouse_in_window { - // Here we need to set the cursor back to what the state says, since it can have changed when outside the window - let cursor = - LoadCursorW(null_mut(), cursor_to_lpcwstr(window_state.cursor_icon.get())); - unsafe { - SetCursor(cursor); - } - Some(1) - } else { - // Cursor is being changed by some other window, e.g. when having mouse on the borders to resize it - None - } - } - // NOTE: `WM_NCDESTROY` is handled in the outer function because this deallocates the window - // state - BV_WINDOW_MUST_CLOSE => { - DestroyWindow(hwnd); - Some(0) - } - _ => None, - } -} - -unsafe fn register_wnd_class() -> ATOM { - // We generate a unique name for the new window class to prevent name collisions - let class_name_str = format!("Baseview-{}", generate_guid()); - let mut class_name: Vec = OsStr::new(&class_name_str).encode_wide().collect(); - class_name.push(0); - - let wnd_class = WNDCLASSW { - style: CS_OWNDC, - lpfnWndProc: Some(wnd_proc), - hInstance: null_mut(), - lpszClassName: class_name.as_ptr(), - cbClsExtra: 0, - cbWndExtra: 0, - hIcon: null_mut(), - hCursor: LoadCursorW(null_mut(), IDC_ARROW), - hbrBackground: null_mut(), - lpszMenuName: null_mut(), - }; - - RegisterClassW(&wnd_class) -} - -unsafe fn unregister_wnd_class(wnd_class: ATOM) { - UnregisterClassW(wnd_class as _, null_mut()); -} - -/// All data associated with the window. This uses internal mutability so the outer struct doesn't -/// need to be mutably borrowed. Mutably borrowing the entire `WindowState` can be problematic -/// because of the Windows message loops' reentrant nature. Care still needs to be taken to prevent -/// `handler` from indirectly triggering other events that would also need to be handled using -/// `handler`. -pub(super) struct WindowState { - /// The HWND belonging to this window. The window's actual state is stored in the `WindowState` - /// struct associated with this HWND through `unsafe { GetWindowLongPtrW(self.hwnd, - /// GWLP_USERDATA) } as *const WindowState`. - pub hwnd: HWND, - window_class: ATOM, - window_info: RefCell, - _parent_handle: Option, - keyboard_state: RefCell, - mouse_button_counter: Cell, - mouse_was_outside_window: RefCell, - cursor_icon: Cell, - // Initialized late so the `Window` can hold a reference to this `WindowState` - handler: RefCell>>, - _drop_target: RefCell>>, - scale_policy: WindowScalePolicy, - dw_style: u32, - - /// Tasks that should be executed at the end of `wnd_proc`. This is needed to avoid mutably - /// borrowing the fields from `WindowState` more than once. For instance, when the window - /// handler requests a resize in response to a keyboard event, the window state will already be - /// borrowed in `wnd_proc`. So the `resize()` function below cannot also mutably borrow that - /// window state at the same time. - pub deferred_tasks: RefCell>, - - #[cfg(feature = "opengl")] - pub gl_context: Option, -} - -impl WindowState { - pub(super) fn create_window(&self) -> Window { - Window { state: self } - } - - pub(super) fn window_info(&self) -> Ref { - self.window_info.borrow() - } - - pub(super) fn keyboard_state(&self) -> Ref { - self.keyboard_state.borrow() - } - - pub(super) fn handler_mut(&self) -> RefMut>> { - self.handler.borrow_mut() - } - - /// Handle a deferred task as described in [`Self::deferred_tasks`]. - pub(self) fn handle_deferred_task(&self, task: WindowTask) { - match task { - WindowTask::Resize(size) => { - // `self.window_info` will be modified in response to the `WM_SIZE` event that - // follows the `SetWindowPos()` call - let scaling = self.window_info.borrow().scale(); - let window_info = WindowInfo::from_logical_size(size, scaling); - - // If the window is a standalone window then the size needs to include the window - // decorations - let mut rect = RECT { - left: 0, - top: 0, - right: window_info.physical_size().width as i32, - bottom: window_info.physical_size().height as i32, - }; - unsafe { - AdjustWindowRectEx(&mut rect, self.dw_style, 0, 0); - SetWindowPos( - self.hwnd, - self.hwnd, - 0, - 0, - rect.right - rect.left, - rect.bottom - rect.top, - SWP_NOZORDER | SWP_NOMOVE, - ) - }; - } - } - } -} +use crate::gl::win::GlContext; +use crate::win::cursor::cursor_to_lpcwstr; +use crate::win::handle::{WindowHandle, WindowHandleTransmitter}; +use crate::win::proc::ProcState; +use crate::win::win32_window::Win32Window; +use crate::{MouseCursor, Size, WindowHandler, WindowOpenOptions}; /// Tasks that must be deferred until the end of [`wnd_proc()`] to avoid reentrant `WindowState` -/// borrows. See the docstring on [`WindowState::deferred_tasks`] for more information. +/// borrows. See the docstring on [`Window::deferred_tasks`] for more information. #[derive(Debug, Clone)] -pub(super) enum WindowTask { +enum WindowTask { /// Resize the window to the given size. The size is in logical pixels. DPI scaling is applied /// automatically. Resize(Size), + Close, } -pub struct Window<'a> { - state: &'a WindowState, +pub struct Window { + pub(crate) win32_window: Win32Window, + cursor_icon: Cell, + + /// Tasks that should be executed at the end of `wnd_proc`. + /// This is needed to avoid re-entrant calls into the `WindowHandler`. + deferred_tasks: RefCell>, } -impl Window<'_> { - pub fn open_parented(parent: &P, options: WindowOpenOptions, build: B) -> WindowHandle +impl Window { + pub fn open_parented( + parent: &impl HasRawWindowHandle, options: WindowOpenOptions, build: B, + ) -> WindowHandle where - P: HasRawWindowHandle, H: WindowHandler + 'static, - B: FnOnce(&mut crate::Window) -> H, + B: FnOnce(crate::Window) -> H, B: Send + 'static, { let parent = match parent.raw_window_handle() { @@ -595,225 +51,66 @@ impl Window<'_> { h => panic!("unsupported parent handle {:?}", h), }; - let (window_handle, _) = Self::open(true, parent, options, build); + let window_handle = Self::open(Some(parent), options, build); window_handle } - pub fn open_blocking(options: WindowOpenOptions, build: B) - where - H: WindowHandler + 'static, - B: FnOnce(&mut crate::Window) -> H, - B: Send + 'static, - { - let (_, hwnd) = Self::open(false, null_mut(), options, build); - - unsafe { - let mut msg: MSG = std::mem::zeroed(); - - loop { - let status = GetMessageW(&mut msg, hwnd, 0, 0); - - if status == -1 { - break; - } - - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - } + pub fn open_blocking( + options: WindowOpenOptions, build_handler: impl FnOnce(crate::Window) -> H, + ) { + let handle = Self::open(None, options, build_handler); + handle.block_on_window() } - fn open( - parented: bool, parent: HWND, options: WindowOpenOptions, build: B, - ) -> (WindowHandle, HWND) - where - H: WindowHandler + 'static, - B: FnOnce(&mut crate::Window) -> H, - B: Send + 'static, - { + fn open( + parent: Option, options: WindowOpenOptions, + build_handler: impl FnOnce(crate::Window) -> H, + ) -> WindowHandle { + // TODO: ? unsafe { - let mut title: Vec = OsStr::new(&options.title[..]).encode_wide().collect(); - title.push(0); - - let window_class = register_wnd_class(); - // todo: manage error ^ - - let scaling = match options.scale { - WindowScalePolicy::SystemScaleFactor => 1.0, - WindowScalePolicy::ScaleFactor(scale) => scale, - }; - - let window_info = WindowInfo::from_logical_size(options.size, scaling); - - let mut rect = RECT { - left: 0, - top: 0, - // todo: check if usize fits into i32 - right: window_info.physical_size().width as i32, - bottom: window_info.physical_size().height as i32, - }; - - let flags = if parented { - WS_CHILD | WS_VISIBLE - } else { - WS_POPUPWINDOW - | WS_CAPTION - | WS_VISIBLE - | WS_SIZEBOX - | WS_MINIMIZEBOX - | WS_MAXIMIZEBOX - | WS_CLIPSIBLINGS - }; - - if !parented { - AdjustWindowRectEx(&mut rect, flags, FALSE, 0); - } - - let hwnd = CreateWindowExW( - 0, - window_class as _, - title.as_ptr(), - flags, - 0, - 0, - rect.right - rect.left, - rect.bottom - rect.top, - parent as *mut _, - null_mut(), - null_mut(), - null_mut(), - ); - // todo: manage error ^ - - #[cfg(feature = "opengl")] - let gl_context: Option = options.gl_config.map(|gl_config| { - let mut handle = Win32WindowHandle::empty(); - handle.hwnd = hwnd as *mut c_void; - let handle = RawWindowHandle::Win32(handle); - - GlContext::create(&handle, gl_config).expect("Could not create OpenGL context") - }); - - let (parent_handle, window_handle) = ParentHandle::new(hwnd); - let parent_handle = if parented { Some(parent_handle) } else { None }; - - let window_state = Rc::new(WindowState { - hwnd, - window_class, - window_info: RefCell::new(window_info), - _parent_handle: parent_handle, - keyboard_state: RefCell::new(KeyboardState::new()), - mouse_button_counter: Cell::new(0), - mouse_was_outside_window: RefCell::new(true), - cursor_icon: Cell::new(MouseCursor::Default), - // The Window refers to this `WindowState`, so this `handler` needs to be - // initialized later - handler: RefCell::new(None), - _drop_target: RefCell::new(None), - scale_policy: options.scale, - dw_style: flags, - - deferred_tasks: RefCell::new(VecDeque::with_capacity(4)), - - #[cfg(feature = "opengl")] - gl_context, - }); - - let handler = { - let mut window = crate::Window::new(window_state.create_window()); - - build(&mut window) - }; - *window_state.handler.borrow_mut() = Some(Box::new(handler)); - - // Only works on Windows 10 unfortunately. - SetProcessDpiAwarenessContext( - winapi::shared::windef::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, - ); - - // Now we can get the actual dpi of the window. - let new_rect = if let WindowScalePolicy::SystemScaleFactor = options.scale { - // Only works on Windows 10 unfortunately. - let dpi = GetDpiForWindow(hwnd); - let scale_factor = dpi as f64 / 96.0; - - let mut window_info = window_state.window_info.borrow_mut(); - if window_info.scale() != scale_factor { - *window_info = - WindowInfo::from_logical_size(window_info.logical_size(), scale_factor); - - Some(RECT { - left: 0, - top: 0, - // todo: check if usize fits into i32 - right: window_info.physical_size().width as i32, - bottom: window_info.physical_size().height as i32, - }) - } else { - None - } - } else { - None - }; - - let drop_target = Rc::new(DropTarget::new(Rc::downgrade(&window_state))); - *window_state._drop_target.borrow_mut() = Some(drop_target.clone()); - OleInitialize(null_mut()); - RegisterDragDrop(hwnd, Rc::as_ptr(&drop_target) as LPDROPTARGET); + } - SetWindowLongPtrW(hwnd, GWLP_USERDATA, Rc::into_raw(window_state) as *const _ as _); - SetTimer(hwnd, WIN_FRAME_TIMER, 15, None); + let win32_window = Win32Window::create(parent, &options); + let window = Rc::new(Window { + win32_window, + cursor_icon: Cell::new(MouseCursor::Default), + deferred_tasks: RefCell::new(VecDeque::with_capacity(4)), + }); + let handler = build_handler(crate::Window::new(Rc::downgrade(&window))); - if let Some(mut new_rect) = new_rect { - // Convert this desired"client rectangle" size to the actual "window rectangle" - // size (Because of course you have to do that). - AdjustWindowRectEx(&mut new_rect, flags, 0, 0); + let (tx, handle) = unsafe { WindowHandleTransmitter::new(window.win32_window.handle()) }; - // Windows makes us resize the window manually. This will trigger another `WM_SIZE` event, - // which we can then send the user the new scale factor. - SetWindowPos( - hwnd, - hwnd, - new_rect.left, - new_rect.top, - new_rect.right - new_rect.left, - new_rect.bottom - new_rect.top, - SWP_NOZORDER | SWP_NOMOVE, - ); - } + ProcState::new(window, tx, handler).move_to_proc(); - (window_handle, hwnd) - } + handle } - pub fn close(&mut self) { - unsafe { - PostMessageW(self.state.hwnd, BV_WINDOW_MUST_CLOSE, 0, 0); - } + fn defer_task(&self, task: WindowTask) { + self.deferred_tasks.borrow_mut().push_back(task) } - pub fn has_focus(&mut self) -> bool { - let focused_window = unsafe { GetFocus() }; - focused_window == self.state.hwnd + pub fn close(&self) { + self.defer_task(WindowTask::Close) } - pub fn focus(&mut self) { - unsafe { - SetFocus(self.state.hwnd); - } - } - - pub fn resize(&mut self, size: Size) { + pub fn resize(&self, size: Size) { // To avoid reentrant event handler calls we'll defer the actual resizing until after the // event has been handled - let task = WindowTask::Resize(size); - self.state.deferred_tasks.borrow_mut().push_back(task); + self.defer_task(WindowTask::Resize(size)) + } + + pub fn has_focus(&self) -> bool { + self.win32_window.has_focus() } - pub fn set_mouse_cursor(&mut self, mouse_cursor: MouseCursor) { - self.state.cursor_icon.set(mouse_cursor); + pub fn focus(&self) { + self.win32_window.focus() + } + + pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) { + self.cursor_icon.set(mouse_cursor); unsafe { let cursor = LoadCursorW(null_mut(), cursor_to_lpcwstr(mouse_cursor)); SetCursor(cursor); @@ -821,23 +118,37 @@ impl Window<'_> { } #[cfg(feature = "opengl")] - pub fn gl_context(&self) -> Option<&GlContext> { - self.state.gl_context.as_ref() + pub fn gl_context(&self) -> Option> { + self.win32_window.gl_context.as_ref().map(Rc::downgrade) } -} -unsafe impl HasRawWindowHandle for Window<'_> { - fn raw_window_handle(&self) -> RawWindowHandle { - let mut handle = Win32WindowHandle::empty(); - handle.hwnd = self.state.hwnd as *mut c_void; + pub fn raw_window_handle(&self) -> RawWindowHandle { + self.win32_window.raw_window_handle().into() + } + pub fn raw_display_handle(&self) -> RawDisplayHandle { + WindowsDisplayHandle::empty().into() + } - RawWindowHandle::Win32(handle) + pub(crate) fn handle_deferred_tasks(&self) { + // NOTE: This is written like this instead of using a `for` loop to avoid extending + // the borrow of `window_state.deferred_tasks` into the call of + // `window_state.handle_deferred_task()` since that may also generate additional + // messages. + while let Some(task) = self.pop_deferred_task() { + self.handle_deferred_task(task); + } + } + + fn pop_deferred_task(&self) -> Option { + self.deferred_tasks.borrow_mut().pop_front() } -} -unsafe impl HasRawDisplayHandle for Window<'_> { - fn raw_display_handle(&self) -> RawDisplayHandle { - RawDisplayHandle::Windows(WindowsDisplayHandle::empty()) + /// Handle a deferred task as described in `Window::deferred_tasks` + fn handle_deferred_task(&self, task: WindowTask) { + match task { + WindowTask::Resize(size) => self.win32_window.resize_logical(size), + WindowTask::Close => self.win32_window.close(), + } } } diff --git a/src/window.rs b/src/window.rs index 25b53d1..ef1481f 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,4 +1,5 @@ use std::marker::PhantomData; +use std::rc::{Rc, Weak}; use raw_window_handle::{ HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, @@ -44,88 +45,88 @@ unsafe impl HasRawWindowHandle for WindowHandle { } } -pub trait WindowHandler { - fn on_frame(&mut self, window: &mut Window); - fn on_event(&mut self, window: &mut Window, event: Event) -> EventStatus; +pub trait WindowHandler: 'static { + fn on_frame(&mut self); + fn on_event(&mut self, event: Event) -> EventStatus; } -pub struct Window<'a> { - window: platform::Window<'a>, - - // so that Window is !Send on all platforms - phantom: PhantomData<*mut ()>, +#[derive(Clone)] +pub struct Window { + window: Weak, } -impl<'a> Window<'a> { - #[cfg(target_os = "windows")] - pub(crate) fn new(window: platform::Window<'a>) -> Window<'a> { - Window { window, phantom: PhantomData } - } - - #[cfg(not(target_os = "windows"))] - pub(crate) fn new(window: platform::Window) -> Window { - Window { window, phantom: PhantomData } +impl Window { + #[inline] + pub(crate) fn new(window: Weak) -> Window { + Window { window } } pub fn open_parented(parent: &P, options: WindowOpenOptions, build: B) -> WindowHandle where P: HasRawWindowHandle, H: WindowHandler + 'static, - B: FnOnce(&mut Window) -> H, + B: FnOnce(Window) -> H, B: Send + 'static, { - let window_handle = platform::Window::open_parented::(parent, options, build); + let window_handle = platform::Window::open_parented(parent, options, build); WindowHandle::new(window_handle) } pub fn open_blocking(options: WindowOpenOptions, build: B) where H: WindowHandler + 'static, - B: FnOnce(&mut Window) -> H, + B: FnOnce(Window) -> H, B: Send + 'static, { - platform::Window::open_blocking::(options, build) + platform::Window::open_blocking(options, build) + } + + fn inner(&self) -> Rc { + self.window.upgrade().expect("Window has already been destroyed") } /// Close the window - pub fn close(&mut self) { - self.window.close(); + pub fn close(&self) { + // This can be a no-op if the window is already closed. + if let Some(window) = self.window.upgrade() { + window.close() + } } /// Resize the window to the given size. The size is always in logical pixels. DPI scaling will /// automatically be accounted for. - pub fn resize(&mut self, size: Size) { - self.window.resize(size); + pub fn resize(&self, size: Size) { + self.inner().resize(size); } - pub fn set_mouse_cursor(&mut self, cursor: MouseCursor) { - self.window.set_mouse_cursor(cursor); + pub fn set_mouse_cursor(&self, cursor: MouseCursor) { + self.inner().set_mouse_cursor(cursor); } - pub fn has_focus(&mut self) -> bool { - self.window.has_focus() + pub fn has_focus(&self) -> bool { + self.inner().has_focus() } - pub fn focus(&mut self) { - self.window.focus() + pub fn focus(&self) { + self.inner().focus() } /// If provided, then an OpenGL context will be created for this window. You'll be able to /// access this context through [crate::Window::gl_context]. #[cfg(feature = "opengl")] - pub fn gl_context(&self) -> Option<&crate::gl::GlContext> { - self.window.gl_context() + pub fn gl_context(&self) -> Option { + Some(crate::gl::GlContext::new(self.inner().gl_context()?)) } } -unsafe impl<'a> HasRawWindowHandle for Window<'a> { +unsafe impl HasRawWindowHandle for Window { fn raw_window_handle(&self) -> RawWindowHandle { - self.window.raw_window_handle() + self.inner().raw_window_handle() } } -unsafe impl<'a> HasRawDisplayHandle for Window<'a> { +unsafe impl HasRawDisplayHandle for Window { fn raw_display_handle(&self) -> RawDisplayHandle { - self.window.raw_display_handle() + self.inner().raw_display_handle() } } diff --git a/src/window_info.rs b/src/window_info.rs index edb5701..bcaa9e6 100644 --- a/src/window_info.rs +++ b/src/window_info.rs @@ -95,6 +95,11 @@ impl PhyPoint { y: f64::from(self.y) * window_info.scale_recip(), } } + + #[inline] + pub(crate) fn with_scale_factor(&self, scale_factor: f64) -> Point { + Point { x: f64::from(self.x) / scale_factor, y: f64::from(self.y) / scale_factor } + } } /// A size in logical coordinates diff --git a/src/x11/event_loop.rs b/src/x11/event_loop.rs index 6b6ecd3..e548277 100644 --- a/src/x11/event_loop.rs +++ b/src/x11/event_loop.rs @@ -1,18 +1,21 @@ +use crate::x11::handle::ParentHandle; use crate::x11::keyboard::{convert_key_press_event, convert_key_release_event, key_mods}; -use crate::x11::{ParentHandle, Window, WindowInner}; +use crate::x11::Window; use crate::{ Event, MouseButton, MouseEvent, PhyPoint, PhySize, ScrollDelta, WindowEvent, WindowHandler, WindowInfo, }; use std::error::Error; use std::os::fd::AsRawFd; +use std::rc::Rc; use std::time::{Duration, Instant}; use x11rb::connection::Connection; use x11rb::protocol::Event as XEvent; pub(super) struct EventLoop { handler: Box, - window: WindowInner, + window: Rc, + parent_handle: Option, new_physical_size: Option, @@ -22,8 +25,7 @@ pub(super) struct EventLoop { impl EventLoop { pub fn new( - window: WindowInner, handler: impl WindowHandler + 'static, - parent_handle: Option, + window: Rc, handler: impl WindowHandler, parent_handle: Option, ) -> Self { Self { window, @@ -47,15 +49,10 @@ impl EventLoop { } if let Some(size) = self.new_physical_size.take() { - self.window.window_info = - WindowInfo::from_physical_size(size, self.window.window_info.scale()); - - let window_info = self.window.window_info; + let window_info = + WindowInfo::from_physical_size(size, self.window.x11_window.dpi_scale_factor); - self.handler.on_event( - &mut crate::Window::new(Window { inner: &self.window }), - Event::Window(WindowEvent::Resized(window_info)), - ); + self.handler.on_event(Event::Window(WindowEvent::Resized(window_info))); } Ok(()) @@ -82,7 +79,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(&mut crate::Window::new(Window { inner: &self.window })); + self.handler.on_frame(); last_frame = Instant::max(next_frame, Instant::now() - self.frame_interval); } @@ -166,10 +163,12 @@ impl EventLoop { XEvent::ConfigureNotify(event) => { let new_physical_size = PhySize::new(event.width as u32, event.height as u32); - if self.new_physical_size.is_some() - || new_physical_size != self.window.window_info.physical_size() - { - self.new_physical_size = Some(new_physical_size); + match self.new_physical_size { + None => self.new_physical_size = Some(new_physical_size), + Some(s) if s != new_physical_size => { + self.new_physical_size = Some(new_physical_size) + } + _ => {} } } @@ -178,80 +177,62 @@ impl EventLoop { //// XEvent::MotionNotify(event) => { let physical_pos = PhyPoint::new(event.event_x as i32, event.event_y as i32); - let logical_pos = physical_pos.to_logical(&self.window.window_info); - self.handler.on_event( - &mut crate::Window::new(Window { inner: &self.window }), - Event::Mouse(MouseEvent::CursorMoved { - position: logical_pos, - modifiers: key_mods(event.state), - }), - ); + let logical_pos = + physical_pos.with_scale_factor(self.window.x11_window.dpi_scale_factor); + + self.handler.on_event(Event::Mouse(MouseEvent::CursorMoved { + position: logical_pos, + modifiers: key_mods(event.state), + })); } XEvent::EnterNotify(event) => { - self.handler.on_event( - &mut crate::Window::new(Window { inner: &self.window }), - Event::Mouse(MouseEvent::CursorEntered), - ); + self.handler.on_event(Event::Mouse(MouseEvent::CursorEntered)); // since no `MOTION_NOTIFY` event is generated when `ENTER_NOTIFY` is generated, // we generate a CursorMoved as well, so the mouse position from here isn't lost let physical_pos = PhyPoint::new(event.event_x as i32, event.event_y as i32); - let logical_pos = physical_pos.to_logical(&self.window.window_info); - self.handler.on_event( - &mut crate::Window::new(Window { inner: &self.window }), - Event::Mouse(MouseEvent::CursorMoved { - position: logical_pos, - modifiers: key_mods(event.state), - }), - ); + let logical_pos = + physical_pos.with_scale_factor(self.window.x11_window.dpi_scale_factor); + self.handler.on_event(Event::Mouse(MouseEvent::CursorMoved { + position: logical_pos, + modifiers: key_mods(event.state), + })); } XEvent::LeaveNotify(_) => { - self.handler.on_event( - &mut crate::Window::new(Window { inner: &self.window }), - Event::Mouse(MouseEvent::CursorLeft), - ); + self.handler.on_event(Event::Mouse(MouseEvent::CursorLeft)); } XEvent::ButtonPress(event) => match event.detail { 4..=7 => { - self.handler.on_event( - &mut crate::Window::new(Window { inner: &self.window }), - Event::Mouse(MouseEvent::WheelScrolled { - delta: match event.detail { - 4 => ScrollDelta::Lines { x: 0.0, y: 1.0 }, - 5 => ScrollDelta::Lines { x: 0.0, y: -1.0 }, - 6 => ScrollDelta::Lines { x: -1.0, y: 0.0 }, - 7 => ScrollDelta::Lines { x: 1.0, y: 0.0 }, - _ => unreachable!(), - }, - modifiers: key_mods(event.state), - }), - ); + self.handler.on_event(Event::Mouse(MouseEvent::WheelScrolled { + delta: match event.detail { + 4 => ScrollDelta::Lines { x: 0.0, y: 1.0 }, + 5 => ScrollDelta::Lines { x: 0.0, y: -1.0 }, + 6 => ScrollDelta::Lines { x: -1.0, y: 0.0 }, + 7 => ScrollDelta::Lines { x: 1.0, y: 0.0 }, + _ => unreachable!(), + }, + modifiers: key_mods(event.state), + })); } detail => { let button_id = mouse_id(detail); - self.handler.on_event( - &mut crate::Window::new(Window { inner: &self.window }), - Event::Mouse(MouseEvent::ButtonPressed { - button: button_id, - modifiers: key_mods(event.state), - }), - ); + self.handler.on_event(Event::Mouse(MouseEvent::ButtonPressed { + button: button_id, + modifiers: key_mods(event.state), + })); } }, XEvent::ButtonRelease(event) => { if !(4..=7).contains(&event.detail) { let button_id = mouse_id(event.detail); - self.handler.on_event( - &mut crate::Window::new(Window { inner: &self.window }), - Event::Mouse(MouseEvent::ButtonReleased { - button: button_id, - modifiers: key_mods(event.state), - }), - ); + self.handler.on_event(Event::Mouse(MouseEvent::ButtonReleased { + button: button_id, + modifiers: key_mods(event.state), + })); } } @@ -259,17 +240,11 @@ impl EventLoop { // keys //// XEvent::KeyPress(event) => { - self.handler.on_event( - &mut crate::Window::new(Window { inner: &self.window }), - Event::Keyboard(convert_key_press_event(&event)), - ); + self.handler.on_event(Event::Keyboard(convert_key_press_event(&event))); } XEvent::KeyRelease(event) => { - self.handler.on_event( - &mut crate::Window::new(Window { inner: &self.window }), - Event::Keyboard(convert_key_release_event(&event)), - ); + self.handler.on_event(Event::Keyboard(convert_key_release_event(&event))); } _ => {} @@ -282,10 +257,7 @@ impl EventLoop { } fn handle_must_close(&mut self) { - self.handler.on_event( - &mut crate::Window::new(Window { inner: &self.window }), - Event::Window(WindowEvent::WillClose), - ); + self.handler.on_event(Event::Window(WindowEvent::WillClose)); self.event_loop_running = false; } diff --git a/src/x11/handle.rs b/src/x11/handle.rs new file mode 100644 index 0000000..58eb9f6 --- /dev/null +++ b/src/x11/handle.rs @@ -0,0 +1,72 @@ +use raw_window_handle::{RawWindowHandle, XcbWindowHandle}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +struct HandleShared { + close_requested: AtomicBool, + is_open: AtomicBool, +} + +pub struct UninitializedWindowHandle { + shared: Arc, +} + +impl UninitializedWindowHandle { + pub fn window_opened(self, raw_window_handle: XcbWindowHandle) -> WindowHandle { + WindowHandle { raw_window_handle, shared: self.shared } + } +} + +pub struct WindowHandle { + raw_window_handle: XcbWindowHandle, + shared: Arc, +} + +impl WindowHandle { + pub fn close(&self) { + // FIXME: This will need to be changed from just setting an atomic to somehow + // synchronizing with the window being closed (using a synchronous channel, or + // by joining on the event loop thread). + + self.shared.close_requested.store(true, Ordering::Relaxed); + } + + pub fn is_open(&self) -> bool { + self.shared.is_open.load(Ordering::Relaxed) + } + + pub fn raw_window_handle(&self) -> RawWindowHandle { + if self.is_open() { + return self.raw_window_handle.into(); + } + + XcbWindowHandle::empty().into() + } +} + +/// Receives the requests sent from the [`WindowHandle`] +pub struct ParentHandle { + shared: Arc, +} + +impl ParentHandle { + pub fn new() -> (Self, UninitializedWindowHandle) { + let shared = Arc::new(HandleShared { + close_requested: AtomicBool::new(false), + is_open: AtomicBool::new(true), // This isn't observable until WindowHandle is created + }); + + (Self { shared: shared.clone() }, UninitializedWindowHandle { shared }) + } + + pub fn parent_did_drop(&self) -> bool { + self.shared.close_requested.load(Ordering::Relaxed) + } +} + +// Notify the external handles that the window has been closed +impl Drop for ParentHandle { + fn drop(&mut self) { + self.shared.is_open.store(false, Ordering::Relaxed); + } +} diff --git a/src/x11/mod.rs b/src/x11/mod.rs index 149df0b..b5947b8 100644 --- a/src/x11/mod.rs +++ b/src/x11/mod.rs @@ -1,10 +1,11 @@ -mod xcb_connection; -use xcb_connection::XcbConnection; - -mod window; -pub use window::*; - mod cursor; mod event_loop; +mod handle; mod keyboard; mod visual_info; +mod window; +mod x11_window; +mod xcb_connection; + +pub(crate) use handle::WindowHandle; +pub(crate) use window::{copy_to_clipboard, Window}; diff --git a/src/x11/window.rs b/src/x11/window.rs index 5b801ec..c2369b3 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -1,128 +1,35 @@ +#![deny(unsafe_code)] + use std::cell::Cell; use std::error::Error; -use std::ffi::c_void; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::rc::Rc; use std::sync::mpsc; -use std::sync::Arc; use std::thread; -use raw_window_handle::{ - HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, XlibDisplayHandle, - XlibWindowHandle, -}; - -use x11rb::connection::Connection; -use x11rb::protocol::xproto::{ - AtomEnum, ChangeWindowAttributesAux, ConfigureWindowAux, ConnectionExt as _, CreateGCAux, - CreateWindowAux, EventMask, PropMode, Visualid, Window as XWindow, WindowClass, -}; -use x11rb::wrapper::ConnectionExt as _; - -use super::XcbConnection; -use crate::{ - Event, MouseCursor, Size, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions, - WindowScalePolicy, -}; - -#[cfg(feature = "opengl")] -use crate::gl::{platform, GlContext}; -use crate::x11::event_loop::EventLoop; -use crate::x11::visual_info::WindowVisualConfig; - -pub struct WindowHandle { - raw_window_handle: Option, - close_requested: Arc, - is_open: Arc, -} - -impl WindowHandle { - pub fn close(&mut self) { - if self.raw_window_handle.take().is_some() { - // FIXME: This will need to be changed from just setting an atomic to somehow - // synchronizing with the window being closed (using a synchronous channel, or - // by joining on the event loop thread). - - self.close_requested.store(true, Ordering::Relaxed); - } - } - - pub fn is_open(&self) -> bool { - self.is_open.load(Ordering::Relaxed) - } -} - -unsafe impl HasRawWindowHandle for WindowHandle { - fn raw_window_handle(&self) -> RawWindowHandle { - if let Some(raw_window_handle) = self.raw_window_handle { - if self.is_open.load(Ordering::Relaxed) { - return raw_window_handle; - } - } - - RawWindowHandle::Xlib(XlibWindowHandle::empty()) - } -} - -pub(crate) struct ParentHandle { - close_requested: Arc, - is_open: Arc, -} - -impl ParentHandle { - pub fn new() -> (Self, WindowHandle) { - let close_requested = Arc::new(AtomicBool::new(false)); - let is_open = Arc::new(AtomicBool::new(true)); - - let handle = WindowHandle { - raw_window_handle: None, - close_requested: Arc::clone(&close_requested), - is_open: Arc::clone(&is_open), - }; - - (Self { close_requested, is_open }, handle) - } +use raw_window_handle::{HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, XcbWindowHandle}; - pub fn parent_did_drop(&self) -> bool { - self.close_requested.load(Ordering::Relaxed) - } -} +use crate::x11::event_loop::EventLoop; +use crate::x11::handle::{ParentHandle, WindowHandle}; +use crate::{Event, MouseCursor, Size, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions}; -impl Drop for ParentHandle { - fn drop(&mut self) { - self.is_open.store(false, Ordering::Relaxed); - } -} +use crate::x11::x11_window::X11Window; +use crate::x11::xcb_connection::XcbConnection; -pub(crate) struct WindowInner { +pub(crate) struct Window { pub(crate) xcb_connection: XcbConnection, - window_id: XWindow, - pub(crate) window_info: WindowInfo, - visual_id: Visualid, - mouse_cursor: Cell, - - pub(crate) close_requested: Cell, + pub(crate) x11_window: X11Window, - #[cfg(feature = "opengl")] - gl_context: Option, -} + pub close_requested: Cell, -pub struct Window<'a> { - pub(crate) inner: &'a WindowInner, + mouse_cursor: Cell, } -// Hack to allow sending a RawWindowHandle between threads. Do not make public -struct SendableRwh(RawWindowHandle); - -unsafe impl Send for SendableRwh {} - -type WindowOpenResult = Result; - -impl<'a> Window<'a> { +impl Window { pub fn open_parented(parent: &P, options: WindowOpenOptions, build: B) -> WindowHandle where P: HasRawWindowHandle, - H: WindowHandler + 'static, - B: FnOnce(&mut crate::Window) -> H, + H: WindowHandler, + B: FnOnce(crate::Window) -> H, B: Send + 'static, { // Convert parent into something that X understands @@ -132,245 +39,107 @@ impl<'a> Window<'a> { h => panic!("unsupported parent handle type {:?}", h), }; - let (tx, rx) = mpsc::sync_channel::(1); + let (tx, rx) = mpsc::sync_channel::(1); - let (parent_handle, mut window_handle) = ParentHandle::new(); + let (parent_handle, window_handle) = ParentHandle::new(); + // TODO: handle window creation errors thread::spawn(move || { - Self::window_thread(Some(parent_id), options, build, tx.clone(), Some(parent_handle)) + Self::window_thread(Some(parent_id), options, build, Some(tx), Some(parent_handle)) .unwrap(); }); - let raw_window_handle = rx.recv().unwrap().unwrap(); - window_handle.raw_window_handle = Some(raw_window_handle.0); - - window_handle + let raw_window_handle = rx.recv().unwrap(); + window_handle.window_opened(raw_window_handle) } pub fn open_blocking(options: WindowOpenOptions, build: B) where - H: WindowHandler + 'static, - B: FnOnce(&mut crate::Window) -> H, + H: WindowHandler, + B: FnOnce(crate::Window) -> H, B: Send + 'static, { - let (tx, rx) = mpsc::sync_channel::(1); - - let thread = thread::spawn(move || { - Self::window_thread(None, options, build, tx, None).unwrap(); - }); - - let _ = rx.recv().unwrap().unwrap(); - - thread.join().unwrap_or_else(|err| { - eprintln!("Window thread panicked: {:#?}", err); - }); + Self::window_thread(None, options, build, None, None).unwrap(); } fn window_thread( parent: Option, options: WindowOpenOptions, build: B, - tx: mpsc::SyncSender, parent_handle: Option, + tx: Option>, handle_receiver: Option, ) -> Result<(), Box> where - H: WindowHandler + 'static, - B: FnOnce(&mut crate::Window) -> H, + H: WindowHandler, + B: FnOnce(crate::Window) -> H, B: Send + 'static, { // Connect to the X server // FIXME: baseview error type instead of unwrap() - let xcb_connection = XcbConnection::new()?; - - // Get screen information - let screen = xcb_connection.screen(); - let parent_id = parent.unwrap_or(screen.root); - - let gc_id = xcb_connection.conn.generate_id()?; - xcb_connection.conn.create_gc( - gc_id, - parent_id, - &CreateGCAux::new().foreground(screen.black_pixel).graphics_exposures(0), - )?; - - let scaling = match options.scale { - WindowScalePolicy::SystemScaleFactor => xcb_connection.get_scaling().unwrap_or(1.0), - WindowScalePolicy::ScaleFactor(scale) => scale, - }; + let xcb_connection = XcbConnection::new().unwrap(); - let window_info = WindowInfo::from_logical_size(options.size, scaling); - - #[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 window_id = xcb_connection.conn.generate_id()?; - xcb_connection.conn.create_window( - visual_info.visual_depth, - window_id, - parent_id, - 0, // x coordinate of the new window - 0, // y coordinate of the new window - window_info.physical_size().width as u16, // window width - window_info.physical_size().height as u16, // window height - 0, // window border - WindowClass::INPUT_OUTPUT, - visual_info.visual_id, - &CreateWindowAux::new() - .event_mask( - EventMask::EXPOSURE - | EventMask::POINTER_MOTION - | EventMask::BUTTON_PRESS - | EventMask::BUTTON_RELEASE - | EventMask::KEY_PRESS - | EventMask::KEY_RELEASE - | EventMask::STRUCTURE_NOTIFY - | EventMask::ENTER_WINDOW - | EventMask::LEAVE_WINDOW, - ) - // As mentioned above, these two values are needed to be able to create a window - // with a depth of 32-bits when the parent window has a different depth - .colormap(visual_info.color_map) - .border_pixel(0), - )?; - xcb_connection.conn.map_window(window_id)?; - - // Change window title - let title = options.title; - xcb_connection.conn.change_property8( - PropMode::REPLACE, - window_id, - AtomEnum::WM_NAME, - AtomEnum::STRING, - title.as_bytes(), - )?; - - xcb_connection.conn.change_property32( - PropMode::REPLACE, - window_id, - xcb_connection.atoms.WM_PROTOCOLS, - AtomEnum::ATOM, - &[xcb_connection.atoms.WM_DELETE_WINDOW], - )?; - - xcb_connection.conn.flush()?; - - // TODO: These APIs could use a couple tweaks now that everything is internal and there is - // no error handling anymore at this point. Everything is more or less unchanged - // compared to when raw-gl-context was a separate crate. - #[cfg(feature = "opengl")] - let gl_context = visual_info.fb_config.map(|fb_config| { - use std::ffi::c_ulong; - - let window = window_id as c_ulong; - let display = xcb_connection.dpy; - - // Because of the visual negotation we had to take some extra steps to create this context - let context = unsafe { platform::GlContext::create(window, display, fb_config) } - .expect("Could not create OpenGL context"); - GlContext::new(context) - }); + let initial_size = options.size; + let x11_window = X11Window::new(&xcb_connection, parent, options)?; - let mut inner = WindowInner { + let window_shared = Rc::new(Window { xcb_connection, - window_id, - window_info, - visual_id: visual_info.visual_id, + x11_window, mouse_cursor: Cell::new(MouseCursor::default()), - close_requested: Cell::new(false), + }); - #[cfg(feature = "opengl")] - gl_context, - }; - - let mut window = crate::Window::new(Window { inner: &mut inner }); - - let mut handler = build(&mut window); + let mut handler = build(crate::Window::new(Rc::downgrade(&window_shared))); // Send an initial window resized event so the user is alerted of // the correct dpi scaling. - handler.on_event(&mut window, Event::Window(WindowEvent::Resized(window_info))); + let window_info = + WindowInfo::from_logical_size(initial_size, window_shared.x11_window.dpi_scale_factor); + handler.on_event(Event::Window(WindowEvent::Resized(window_info))); - let _ = tx.send(Ok(SendableRwh(window.raw_window_handle()))); + if let Some(tx) = tx { + let _ = tx.send(window_shared.x11_window.raw_window_handle()); + } - EventLoop::new(inner, handler, parent_handle).run()?; + window_shared.x11_window.show(&window_shared.xcb_connection)?; + EventLoop::new(window_shared, handler, handle_receiver).run()?; Ok(()) } pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) { - if self.inner.mouse_cursor.get() == mouse_cursor { + if self.mouse_cursor.get() == mouse_cursor { return; } - let xid = self.inner.xcb_connection.get_cursor(mouse_cursor).unwrap(); - - if xid != 0 { - let _ = self.inner.xcb_connection.conn.change_window_attributes( - self.inner.window_id, - &ChangeWindowAttributesAux::new().cursor(xid), - ); - let _ = self.inner.xcb_connection.conn.flush(); - } + self.x11_window.set_mouse_cursor(&self.xcb_connection, mouse_cursor); - self.inner.mouse_cursor.set(mouse_cursor); + self.mouse_cursor.set(mouse_cursor); } - pub fn close(&mut self) { - self.inner.close_requested.set(true); + pub fn close(&self) { + self.close_requested.set(true); } - pub fn has_focus(&mut self) -> bool { + pub fn has_focus(&self) -> bool { unimplemented!() } - pub fn focus(&mut self) { + pub fn focus(&self) { unimplemented!() } - pub fn resize(&mut self, size: Size) { - let scaling = self.inner.window_info.scale(); - let new_window_info = WindowInfo::from_logical_size(size, scaling); - - let _ = self.inner.xcb_connection.conn.configure_window( - self.inner.window_id, - &ConfigureWindowAux::new() - .width(new_window_info.physical_size().width) - .height(new_window_info.physical_size().height), - ); - let _ = self.inner.xcb_connection.conn.flush(); - - // This will trigger a `ConfigureNotify` event which will in turn change `self.window_info` - // and notify the window handler about it + pub fn resize(&self, size: Size) { + self.x11_window.resize(&self.xcb_connection, size) } #[cfg(feature = "opengl")] - pub fn gl_context(&self) -> Option<&crate::gl::GlContext> { - self.inner.gl_context.as_ref() + pub fn gl_context(&self) -> Option> { + self.x11_window.gl_context() } -} - -unsafe impl<'a> HasRawWindowHandle for Window<'a> { - fn raw_window_handle(&self) -> RawWindowHandle { - let mut handle = XlibWindowHandle::empty(); - - handle.window = self.inner.window_id.into(); - handle.visual_id = self.inner.visual_id.into(); - RawWindowHandle::Xlib(handle) + pub fn raw_window_handle(&self) -> RawWindowHandle { + self.x11_window.raw_window_handle().into() } -} - -unsafe impl<'a> HasRawDisplayHandle for Window<'a> { - fn raw_display_handle(&self) -> RawDisplayHandle { - let display = self.inner.xcb_connection.dpy; - let mut handle = XlibDisplayHandle::empty(); - - handle.display = display as *mut c_void; - handle.screen = unsafe { x11::xlib::XDefaultScreen(display) }; - RawDisplayHandle::Xlib(handle) + pub fn raw_display_handle(&self) -> RawDisplayHandle { + self.xcb_connection.raw_display_handle() } } diff --git a/src/x11/x11_window.rs b/src/x11/x11_window.rs new file mode 100644 index 0000000..5b18e71 --- /dev/null +++ b/src/x11/x11_window.rs @@ -0,0 +1,207 @@ +use crate::x11::visual_info::WindowVisualConfig; +use crate::x11::xcb_connection::XcbConnection; +use crate::{MouseCursor, Size, WindowInfo, WindowOpenOptions, WindowScalePolicy}; +use raw_window_handle::XcbWindowHandle; +use std::error::Error; +use x11rb::connection::Connection; +use x11rb::protocol::xproto::{ + AtomEnum, ChangeWindowAttributesAux, ConfigureWindowAux, ConnectionExt, CreateGCAux, + CreateWindowAux, Drawable, EventMask, Gcontext, PropMode, Visualid, Window, WindowClass, +}; +use x11rb::wrapper::ConnectionExt as _; + +/// Represents an actual X11 window (as opposed to the [`crate::x11::Window`] which also handles the +/// event loop for now). +pub(crate) struct X11Window { + pub window_id: Window, + pub dpi_scale_factor: f64, + visual_id: Visualid, + _graphics_context: Gcontext, + + #[cfg(feature = "opengl")] + gl_context: Option>, +} + +impl X11Window { + pub fn new( + connection: &XcbConnection, parent: Option, options: WindowOpenOptions, + ) -> Result> { + let parent = parent.unwrap_or_else(|| connection.screen().root); + let _graphics_context = create_graphics_context(connection, parent)?; + + let scaling = match options.scale { + WindowScalePolicy::SystemScaleFactor => connection.get_scaling().unwrap_or(1.0), + WindowScalePolicy::ScaleFactor(scale) => scale, + }; + + let window_info = WindowInfo::from_logical_size(options.size, scaling); + let physical_size = window_info.physical_size(); + + #[cfg(feature = "opengl")] + let visual_info = + WindowVisualConfig::find_best_visual_config_for_gl(connection, options.gl_config)?; + + #[cfg(not(feature = "opengl"))] + let visual_info = WindowVisualConfig::find_best_visual_config(connection)?; + + let window_id = connection.conn.generate_id()?; + connection.conn.create_window( + visual_info.visual_depth, + window_id, + parent, + 0, // x coordinate of the new window + 0, // y coordinate of the new window + physical_size.width as u16, // window width + physical_size.height as u16, // window height + 0, // window border + WindowClass::INPUT_OUTPUT, + visual_info.visual_id, + &CreateWindowAux::new() + .event_mask( + EventMask::EXPOSURE + | EventMask::POINTER_MOTION + | EventMask::BUTTON_PRESS + | EventMask::BUTTON_RELEASE + | EventMask::KEY_PRESS + | EventMask::KEY_RELEASE + | EventMask::STRUCTURE_NOTIFY + | EventMask::ENTER_WINDOW + | EventMask::LEAVE_WINDOW, + ) + // As mentioned above, these two values are needed to be able to create a window + // with a depth of 32-bits when the parent window has a different depth + .colormap(visual_info.color_map) + .border_pixel(0), + )?; + + let mut created_window = Self { + window_id, + visual_id: visual_info.visual_id, + dpi_scale_factor: window_info.scale(), + _graphics_context, + #[cfg(feature = "opengl")] + gl_context: None, + }; + + created_window.set_title(connection, &options.title)?; + + // Register protocols + connection.conn.change_property32( + PropMode::REPLACE, + window_id, + connection.atoms.WM_PROTOCOLS, + AtomEnum::ATOM, + &[connection.atoms.WM_DELETE_WINDOW], + )?; + + connection.conn.flush()?; + + #[cfg(feature = "opengl")] + if let Some(config) = visual_info.fb_config { + created_window.create_gl_context(connection, config); + connection.conn.flush()?; + } + + Ok(created_window) + } + + pub fn set_title(&self, connection: &XcbConnection, title: &str) -> Result<(), Box> { + connection.conn.change_property8( + PropMode::REPLACE, + self.window_id, + AtomEnum::WM_NAME, + AtomEnum::STRING, + title.as_bytes(), + )?; + Ok(()) + } + + pub fn show(&self, connection: &XcbConnection) -> Result<(), Box> { + connection.conn.map_window(self.window_id)?; + connection.conn.flush()?; + Ok(()) + } + + pub fn set_mouse_cursor(&self, connection: &XcbConnection, mouse_cursor: MouseCursor) { + let xid = connection.get_cursor(mouse_cursor).unwrap(); + + if xid != 0 { + let _ = connection.conn.change_window_attributes( + self.window_id, + &ChangeWindowAttributesAux::new().cursor(xid), + ); + let _ = connection.conn.flush(); + } + } + + pub fn resize(&self, connection: &XcbConnection, size: Size) { + let new_window_info = WindowInfo::from_logical_size(size, self.dpi_scale_factor); + + let _ = connection.conn.configure_window( + self.window_id, + &ConfigureWindowAux::new() + .width(new_window_info.physical_size().width) + .height(new_window_info.physical_size().height), + ); + let _ = connection.conn.flush(); + + // This will trigger a `ConfigureNotify` event which will in turn change `self.window_info` + // and notify the window handler about it + } + + pub fn raw_window_handle(&self) -> XcbWindowHandle { + let mut handle = XcbWindowHandle::empty(); + + handle.window = self.window_id; + handle.visual_id = self.visual_id; + + handle + } +} + +fn create_graphics_context( + connection: &XcbConnection, parent: Drawable, +) -> Result> { + let context_id = connection.conn.generate_id()?; + let screen = connection.screen(); + + connection.conn.create_gc( + context_id, + parent, + &CreateGCAux::new().foreground(screen.black_pixel).graphics_exposures(0), + )?; + + Ok(context_id) +} + +// OpenGL stuff +#[cfg(feature = "opengl")] +const _: () = { + use crate::gl::platform::GlContext; + use std::rc::{Rc, Weak}; + + use std::ffi::c_ulong; + + impl X11Window { + // TODO: These APIs could use a couple tweaks now that everything is internal and there is + // no error handling anymore at this point. Everything is more or less unchanged + // compared to when raw-gl-context was a separate crate. + #[cfg(feature = "opengl")] + fn create_gl_context( + &mut self, connection: &XcbConnection, config: crate::gl::x11::FbConfig, + ) { + let window = self.window_id as c_ulong; + let display = connection.dpy; + + // Because of the visual negotiation we had to take some extra steps to create this context + let context = unsafe { GlContext::create(window, display, config) } + .expect("Could not create OpenGL context"); + + self.gl_context = Some(Rc::new(context)) + } + + pub fn gl_context(&self) -> Option> { + self.gl_context.as_ref().map(Rc::downgrade) + } + } +}; diff --git a/src/x11/xcb_connection.rs b/src/x11/xcb_connection.rs index a5ea06d..cdbf659 100644 --- a/src/x11/xcb_connection.rs +++ b/src/x11/xcb_connection.rs @@ -1,6 +1,8 @@ +use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle}; use std::cell::RefCell; use std::collections::hash_map::{Entry, HashMap}; use std::error::Error; +use std::ffi::{c_int, c_void}; use x11::{xlib, xlib::Display, xlib_xcb}; @@ -121,6 +123,15 @@ impl XcbConnection { pub fn screen(&self) -> &Screen { &self.conn.setup().roots[self.screen] } + + pub fn raw_display_handle(&self) -> RawDisplayHandle { + let mut handle = XlibDisplayHandle::empty(); + + handle.display = self.dpy as *mut c_void; + handle.screen = self.screen as c_int; + + RawDisplayHandle::Xlib(handle) + } } impl Drop for XcbConnection {