From f6e99e9aa6f5aeb6b721cb05e4d882a51d995909 Mon Sep 17 00:00:00 2001 From: Billy Messenger Date: Tue, 16 Nov 2021 00:00:22 -0600 Subject: [PATCH] add ability to close window from user code, add HostWindowHandle (#103) * add ability to close window from user code, add HostWindowHandle * fix manual close method for Mac, rename HostWindowHandle to ChildWindowHandle * fix rustfmt.toml and run cargo format * fix merge conflict mistake * fix more merge conflict mistakes * implement requested changes (with a non-broken commit this time) * implement requested changes * slight reordering of impls --- Cargo.toml | 2 +- examples/open_window.rs | 2 +- src/macos/view.rs | 2 +- src/macos/window.rs | 163 ++++++++++++++++++++++++++++++++++++---- src/win/window.rs | 126 +++++++++++++++++++++++++------ src/window.rs | 44 ++++++++++- src/x11/window.rs | 149 +++++++++++++++++++++++++++++++----- 7 files changed, 423 insertions(+), 65 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e9b1aa0..d781e3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,4 +33,4 @@ objc = "0.2.7" uuid = { version = "0.8", features = ["v4"] } [dev-dependencies] -rtrb = "0.1.1" +rtrb = "0.2" diff --git a/examples/open_window.rs b/examples/open_window.rs index 23ce35b..88a9ae0 100644 --- a/examples/open_window.rs +++ b/examples/open_window.rs @@ -38,7 +38,7 @@ fn main() { scale: WindowScalePolicy::SystemScaleFactor, }; - let (mut tx, rx) = RingBuffer::new(128).split(); + let (mut tx, rx) = RingBuffer::new(128); ::std::thread::spawn(move || loop { ::std::thread::sleep(Duration::from_secs(5)); diff --git a/src/macos/view.rs b/src/macos/view.rs index 7d709bb..bbdc641 100644 --- a/src/macos/view.rs +++ b/src/macos/view.rs @@ -178,7 +178,7 @@ extern "C" fn release(this: &mut Object, _sel: Sel) { let retain_count_after_build = WindowState::from_field(this).retain_count_after_build; if retain_count <= retain_count_after_build { - WindowState::from_field(this).remove_timer(); + WindowState::from_field(this).stop(); this.set_ivar(BASEVIEW_STATE_IVAR, ::std::ptr::null() as *const c_void); diff --git a/src/macos/window.rs b/src/macos/window.rs index 2dc8af4..b8ee46c 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -1,4 +1,7 @@ use std::ffi::c_void; +use std::marker::PhantomData; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use cocoa::appkit::{ NSApp, NSApplication, NSApplicationActivationPolicyRegular, NSBackingStoreBuffered, NSWindow, @@ -15,21 +18,94 @@ use objc::{msg_send, runtime::Object, sel, sel_impl}; use raw_window_handle::{macos::MacOSHandle, HasRawWindowHandle, RawWindowHandle}; -use crate::{Event, EventStatus, WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy}; +use crate::{ + Event, EventStatus, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions, + WindowScalePolicy, +}; use super::keyboard::KeyboardState; use super::view::{create_view, BASEVIEW_STATE_IVAR}; +pub struct WindowHandle { + raw_window_handle: Option, + close_requested: Arc, + is_open: Arc, + + // Ensure handle is !Send + _phantom: PhantomData<*mut ()>, +} + +impl WindowHandle { + pub fn close(&mut self) { + if let Some(_) = self.raw_window_handle.take() { + 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::MacOS(MacOSHandle { ..MacOSHandle::empty() }) + } +} + +struct ParentHandle { + _close_requested: Arc, + is_open: Arc, +} + +impl ParentHandle { + pub fn new(raw_window_handle: RawWindowHandle) -> (Self, WindowHandle) { + let close_requested = Arc::new(AtomicBool::new(false)); + let is_open = Arc::new(AtomicBool::new(true)); + + let handle = WindowHandle { + raw_window_handle: Some(raw_window_handle), + close_requested: Arc::clone(&close_requested), + is_open: Arc::clone(&is_open), + _phantom: PhantomData::default(), + }; + + (Self { _close_requested: close_requested, is_open }, handle) + } + + /* + pub fn parent_did_drop(&self) -> bool { + self.close_requested.load(Ordering::Relaxed) + } + */ +} + +impl Drop for ParentHandle { + fn drop(&mut self) { + self.is_open.store(false, Ordering::Relaxed); + } +} + pub struct Window { + /// Only set if we created the parent window, i.e. we are running in + /// parentless mode + ns_app: Option, /// Only set if we created the parent window, i.e. we are running in /// parentless mode ns_window: Option, /// Our subclassed NSView ns_view: id, + close_requested: bool, } impl Window { - pub fn open_parented(parent: &P, options: WindowOpenOptions, build: B) + pub fn open_parented(parent: &P, options: WindowOpenOptions, build: B) -> WindowHandle where P: HasRawWindowHandle, H: WindowHandler + 'static, @@ -46,9 +122,9 @@ impl Window { let ns_view = unsafe { create_view(&options) }; - let window = Window { ns_window: None, ns_view }; + let window = Window { ns_app: None, ns_window: None, ns_view, close_requested: false }; - Self::init(window, build); + let window_handle = Self::init(true, window, build); unsafe { let _: id = msg_send![handle.ns_view as *mut Object, addSubview: ns_view]; @@ -56,9 +132,11 @@ impl Window { let () = msg_send![pool, drain]; } + + window_handle } - pub fn open_as_if_parented(options: WindowOpenOptions, build: B) -> RawWindowHandle + pub fn open_as_if_parented(options: WindowOpenOptions, build: B) -> WindowHandle where H: WindowHandler + 'static, B: FnOnce(&mut crate::Window) -> H, @@ -68,17 +146,15 @@ impl Window { let ns_view = unsafe { create_view(&options) }; - let window = Window { ns_window: None, ns_view }; + let window = Window { ns_app: None, ns_window: None, ns_view, close_requested: false }; - let raw_window_handle = window.raw_window_handle(); - - Self::init(window, build); + let window_handle = Self::init(true, window, build); unsafe { let () = msg_send![pool, drain]; } - raw_window_handle + window_handle } pub fn open_blocking(options: WindowOpenOptions, build: B) @@ -118,7 +194,9 @@ impl Window { let ns_window = unsafe { let ns_window = NSWindow::alloc(nil).initWithContentRect_styleMask_backing_defer_( rect, - NSWindowStyleMask::NSTitledWindowMask, + NSWindowStyleMask::NSTitledWindowMask + | NSWindowStyleMask::NSClosableWindowMask + | NSWindowStyleMask::NSMiniaturizableWindowMask, NSBackingStoreBuffered, NO, ); @@ -134,21 +212,26 @@ impl Window { let ns_view = unsafe { create_view(&options) }; - let window = Window { ns_window: Some(ns_window), ns_view }; + let window = Window { + ns_app: Some(app), + ns_window: Some(ns_window), + ns_view, + close_requested: false, + }; - Self::init(window, build); + let _ = Self::init(false, window, build); unsafe { ns_window.setContentView_(ns_view); - let () = msg_send![ns_view as id, release]; + let () = msg_send![ns_view as id, release]; let () = msg_send![pool, drain]; app.run(); } } - fn init(mut window: Window, build: B) + fn init(parented: bool, mut window: Window, build: B) -> WindowHandle where H: WindowHandler + 'static, B: FnOnce(&mut crate::Window) -> H, @@ -156,6 +239,9 @@ impl Window { { let window_handler = Box::new(build(&mut crate::Window::new(&mut window))); + let (parent_handle, window_handle) = ParentHandle::new(window.raw_window_handle()); + let parent_handle = if parented { Some(parent_handle) } else { None }; + let retain_count_after_build: usize = unsafe { msg_send![window.ns_view, retainCount] }; let window_state_ptr = Box::into_raw(Box::new(WindowState { @@ -164,6 +250,7 @@ impl Window { keyboard_state: KeyboardState::new(), frame_timer: None, retain_count_after_build, + _parent_handle: parent_handle, })); unsafe { @@ -172,6 +259,12 @@ impl Window { WindowState::setup_timer(window_state_ptr); } + + window_handle + } + + pub fn close(&mut self) { + self.close_requested = true; } } @@ -180,6 +273,7 @@ pub(super) struct WindowState { window_handler: Box, keyboard_state: KeyboardState, frame_timer: Option, + _parent_handle: Option, pub retain_count_after_build: usize, } @@ -201,6 +295,36 @@ impl WindowState { pub(super) fn trigger_frame(&mut self) { self.window_handler.on_frame(&mut crate::Window::new(&mut self.window)); + + let mut do_close = false; + + /* FIXME: Is it even necessary to check if the parent dropped the handle + // in MacOS? + // Check if the parent handle was dropped + if let Some(parent_handle) = &self.parent_handle { + if parent_handle.parent_did_drop() { + do_close = true; + self.window.close_requested = false; + } + } + */ + + // Check if the user requested the window to close + if self.window.close_requested { + do_close = true; + self.window.close_requested = false; + } + + if do_close { + unsafe { + if let Some(ns_window) = self.window.ns_window.take() { + ns_window.close(); + } else { + // FIXME: How do we close a non-parented window? Is this even + // possible in a DAW host usecase? + } + } + } } pub(super) fn process_native_key_event(&mut self, event: *mut Object) -> Option { @@ -235,10 +359,17 @@ impl WindowState { } /// Call when freeing view - pub(super) unsafe fn remove_timer(&mut self) { + pub(super) unsafe fn stop(&mut self) { if let Some(frame_timer) = self.frame_timer.take() { CFRunLoop::get_current().remove_timer(&frame_timer, kCFRunLoopDefaultMode); } + + self.trigger_event(Event::Window(WindowEvent::WillClose)); + + // If in non-parented mode, we want to also quit the app altogether + if let Some(app) = self.window.ns_app.take() { + app.stop_(app); + } } } diff --git a/src/win/window.rs b/src/win/window.rs index 8c67ed6..f91ac17 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -4,25 +4,29 @@ use winapi::shared::windef::{HWND, RECT}; use winapi::um::combaseapi::CoCreateGuid; use winapi::um::winuser::{ AdjustWindowRectEx, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, - GetCapture, GetDpiForWindow, GetMessageW, GetWindowLongPtrW, IsWindow, LoadCursorW, - PostMessageW, RegisterClassW, ReleaseCapture, SetCapture, SetProcessDpiAwarenessContext, - SetTimer, SetWindowLongPtrW, SetWindowPos, TranslateMessage, UnregisterClassW, CS_OWNDC, - GET_XBUTTON_WPARAM, GWLP_USERDATA, IDC_ARROW, MSG, SWP_NOMOVE, SWP_NOZORDER, 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_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, WNDCLASSW, WS_CAPTION, WS_CHILD, - WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE, - XBUTTON1, XBUTTON2, + GetDpiForWindow, GetMessageW, GetWindowLongPtrW, LoadCursorW, PostMessageW, RegisterClassW, + ReleaseCapture, SetCapture, SetProcessDpiAwarenessContext, SetTimer, SetWindowLongPtrW, + SetWindowPos, TranslateMessage, UnregisterClassW, CS_OWNDC, GET_XBUTTON_WPARAM, GWLP_USERDATA, + IDC_ARROW, MSG, SWP_NOMOVE, SWP_NOZORDER, 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_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCDESTROY, WM_RBUTTONDOWN, + WM_RBUTTONUP, 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::RefCell; -use std::ffi::{c_void, OsStr}; +use std::ffi::OsStr; +use std::marker::PhantomData; use std::os::windows::ffi::OsStrExt; use std::ptr::null_mut; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use raw_window_handle::{windows::WindowsHandle, HasRawWindowHandle, RawWindowHandle}; +const BV_WINDOW_MUST_CLOSE: UINT = WM_USER + 1; + use crate::{ Event, MouseButton, MouseEvent, PhyPoint, PhySize, ScrollDelta, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, @@ -51,6 +55,65 @@ unsafe fn generate_guid() -> String { const WIN_FRAME_TIMER: usize = 4242; +pub struct WindowHandle { + hwnd: Option, + is_open: Arc, + + // Ensure handle is !Send + _phantom: PhantomData<*mut ()>, +} + +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.load(Ordering::Relaxed) + } +} + +unsafe impl HasRawWindowHandle for WindowHandle { + fn raw_window_handle(&self) -> RawWindowHandle { + if let Some(hwnd) = self.hwnd { + RawWindowHandle::Windows(WindowsHandle { + hwnd: hwnd as *mut std::ffi::c_void, + ..WindowsHandle::empty() + }) + } else { + RawWindowHandle::Windows(WindowsHandle { ..WindowsHandle::empty() }) + } + } +} + +struct ParentHandle { + is_open: Arc, +} + +impl ParentHandle { + pub fn new(hwnd: HWND) -> (Self, WindowHandle) { + let is_open = Arc::new(AtomicBool::new(true)); + + let handle = WindowHandle { + hwnd: Some(hwnd), + is_open: Arc::clone(&is_open), + _phantom: PhantomData::default(), + }; + + (Self { is_open }, handle) + } +} + +impl Drop for ParentHandle { + fn drop(&mut self) { + self.is_open.store(false, Ordering::Relaxed); + } +} + unsafe extern "system" fn wnd_proc( hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM, ) -> LRESULT { @@ -247,7 +310,12 @@ unsafe extern "system" fn wnd_proc( unregister_wnd_class(window_state.borrow().window_class); SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0); } - _ => {} + _ => { + if msg == BV_WINDOW_MUST_CLOSE { + DestroyWindow(hwnd); + return 0; + } + } } } @@ -283,6 +351,7 @@ unsafe fn unregister_wnd_class(wnd_class: ATOM) { struct WindowState { window_class: ATOM, window_info: WindowInfo, + _parent_handle: Option, keyboard_state: KeyboardState, mouse_button_counter: usize, handler: Box, @@ -295,7 +364,7 @@ pub struct Window { } impl Window { - pub fn open_parented(parent: &P, options: WindowOpenOptions, build: B) + pub fn open_parented(parent: &P, options: WindowOpenOptions, build: B) -> WindowHandle where P: HasRawWindowHandle, H: WindowHandler + 'static, @@ -307,21 +376,20 @@ impl Window { h => panic!("unsupported parent handle {:?}", h), }; - Self::open(true, parent, options, build); + let (window_handle, _) = Self::open(true, parent, options, build); + + window_handle } - pub fn open_as_if_parented(options: WindowOpenOptions, build: B) -> RawWindowHandle + pub fn open_as_if_parented(options: WindowOpenOptions, build: B) -> WindowHandle where H: WindowHandler + 'static, B: FnOnce(&mut crate::Window) -> H, B: Send + 'static, { - let hwnd = Self::open(true, null_mut(), options, build); + let (window_handle, _) = Self::open(true, null_mut(), options, build); - RawWindowHandle::Windows(WindowsHandle { - hwnd: hwnd as *mut std::ffi::c_void, - ..WindowsHandle::empty() - }) + window_handle } pub fn open_blocking(options: WindowOpenOptions, build: B) @@ -330,7 +398,7 @@ impl Window { B: FnOnce(&mut crate::Window) -> H, B: Send + 'static, { - let hwnd = Self::open(false, null_mut(), options, build); + let (_, hwnd) = Self::open(false, null_mut(), options, build); unsafe { let mut msg: MSG = std::mem::zeroed(); @@ -348,7 +416,9 @@ impl Window { } } - fn open(parented: bool, parent: HWND, options: WindowOpenOptions, build: B) -> HWND + fn open( + parented: bool, parent: HWND, options: WindowOpenOptions, build: B, + ) -> (WindowHandle, HWND) where H: WindowHandler + 'static, B: FnOnce(&mut crate::Window) -> H, @@ -410,9 +480,13 @@ impl Window { let handler = Box::new(build(&mut crate::Window::new(&mut Window { hwnd }))); + let (parent_handle, window_handle) = ParentHandle::new(hwnd); + let parent_handle = if parented { Some(parent_handle) } else { None }; + let mut window_state = Box::new(RefCell::new(WindowState { window_class, window_info, + _parent_handle: parent_handle, keyboard_state: KeyboardState::new(), mouse_button_counter: 0, handler, @@ -473,7 +547,13 @@ impl Window { ); } - hwnd + (window_handle, hwnd) + } + } + + pub fn close(&mut self) { + unsafe { + PostMessageW(self.hwnd, BV_WINDOW_MUST_CLOSE, 0, 0); } } } diff --git a/src/window.rs b/src/window.rs index 0686b3f..9b5dfbb 100644 --- a/src/window.rs +++ b/src/window.rs @@ -12,6 +12,35 @@ use crate::win as platform; #[cfg(target_os = "linux")] use crate::x11 as platform; +pub struct WindowHandle { + window_handle: platform::WindowHandle, + // so that WindowHandle is !Send on all platforms + phantom: PhantomData<*mut ()>, +} + +impl WindowHandle { + fn new(window_handle: platform::WindowHandle) -> Self { + Self { window_handle, phantom: PhantomData::default() } + } + + /// Close the window + pub fn close(&mut self) { + self.window_handle.close(); + } + + /// Returns `true` if the window is still open, and returns `false` + /// if the window was closed/dropped. + pub fn is_open(&self) -> bool { + self.window_handle.is_open() + } +} + +unsafe impl HasRawWindowHandle for WindowHandle { + fn raw_window_handle(&self) -> RawWindowHandle { + self.window_handle.raw_window_handle() + } +} + pub trait WindowHandler { fn on_frame(&mut self, window: &mut Window); fn on_event(&mut self, window: &mut Window, event: Event) -> EventStatus; @@ -28,23 +57,25 @@ impl<'a> Window<'a> { Window { window, phantom: PhantomData } } - pub fn open_parented(parent: &P, options: WindowOpenOptions, build: B) + pub fn open_parented(parent: &P, options: WindowOpenOptions, build: B) -> WindowHandle where P: HasRawWindowHandle, H: WindowHandler + 'static, B: FnOnce(&mut Window) -> H, B: Send + 'static, { - platform::Window::open_parented::(parent, options, build) + let window_handle = platform::Window::open_parented::(parent, options, build); + WindowHandle::new(window_handle) } - pub fn open_as_if_parented(options: WindowOpenOptions, build: B) -> RawWindowHandle + pub fn open_as_if_parented(options: WindowOpenOptions, build: B) -> WindowHandle where H: WindowHandler + 'static, B: FnOnce(&mut Window) -> H, B: Send + 'static, { - platform::Window::open_as_if_parented::(options, build) + let window_handle = platform::Window::open_as_if_parented::(options, build); + WindowHandle::new(window_handle) } pub fn open_blocking(options: WindowOpenOptions, build: B) @@ -55,6 +86,11 @@ impl<'a> Window<'a> { { platform::Window::open_blocking::(options, build) } + + /// Close the window + pub fn close(&mut self) { + self.window.close(); + } } unsafe impl<'a> HasRawWindowHandle for Window<'a> { diff --git a/src/x11/window.rs b/src/x11/window.rs index 7073df5..5c3b7c7 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -1,5 +1,8 @@ +use std::marker::PhantomData; use std::os::raw::{c_ulong, c_void}; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc; +use std::sync::Arc; use std::thread; use std::time::*; @@ -13,6 +16,74 @@ use crate::{ use super::keyboard::{convert_key_press_event, convert_key_release_event}; +pub struct WindowHandle { + raw_window_handle: Option, + close_requested: Arc, + is_open: Arc, + + // Ensure handle is !Send + _phantom: PhantomData<*mut ()>, +} + +impl WindowHandle { + pub fn close(&mut self) { + if let Some(_) = self.raw_window_handle.take() { + // 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(XlibHandle { ..raw_window_handle::unix::XlibHandle::empty() }) + } +} + +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), + _phantom: PhantomData::default(), + }; + + (Self { close_requested, is_open }, handle) + } + + pub fn parent_did_drop(&self) -> bool { + self.close_requested.load(Ordering::Relaxed) + } +} + +impl Drop for ParentHandle { + fn drop(&mut self) { + self.is_open.store(false, Ordering::Relaxed); + } +} + pub struct Window { xcb_connection: XcbConnection, window_id: u32, @@ -21,8 +92,10 @@ pub struct Window { frame_interval: Duration, event_loop_running: bool, + close_requested: bool, new_physical_size: Option, + parent_handle: Option, } // Hack to allow sending a RawWindowHandle between threads. Do not make public @@ -33,7 +106,7 @@ unsafe impl Send for SendableRwh {} type WindowOpenResult = Result; impl Window { - pub fn open_parented(parent: &P, options: WindowOpenOptions, build: B) + pub fn open_parented(parent: &P, options: WindowOpenOptions, build: B) -> WindowHandle where P: HasRawWindowHandle, H: WindowHandler + 'static, @@ -49,14 +122,19 @@ impl Window { let (tx, rx) = mpsc::sync_channel::(1); + let (parent_handle, mut window_handle) = ParentHandle::new(); + let thread = thread::spawn(move || { - Self::window_thread(Some(parent_id), options, build, tx.clone()); + Self::window_thread(Some(parent_id), options, build, tx.clone(), Some(parent_handle)); }); - let _ = rx.recv().unwrap().unwrap(); + let raw_window_handle = rx.recv().unwrap().unwrap(); + window_handle.raw_window_handle = Some(raw_window_handle.0); + + window_handle } - pub fn open_as_if_parented(options: WindowOpenOptions, build: B) -> RawWindowHandle + pub fn open_as_if_parented(options: WindowOpenOptions, build: B) -> WindowHandle where H: WindowHandler + 'static, B: FnOnce(&mut crate::Window) -> H, @@ -64,11 +142,16 @@ impl Window { { let (tx, rx) = mpsc::sync_channel::(1); + let (parent_handle, mut window_handle) = ParentHandle::new(); + let thread = thread::spawn(move || { - Self::window_thread(None, options, build, tx.clone()); + Self::window_thread(None, options, build, tx.clone(), Some(parent_handle)); }); - rx.recv().unwrap().unwrap().0 + let raw_window_handle = rx.recv().unwrap().unwrap(); + window_handle.raw_window_handle = Some(raw_window_handle.0); + + window_handle } pub fn open_blocking(options: WindowOpenOptions, build: B) @@ -80,7 +163,7 @@ impl Window { let (tx, rx) = mpsc::sync_channel::(1); let thread = thread::spawn(move || { - Self::window_thread(None, options, build, tx.clone()); + Self::window_thread(None, options, build, tx.clone(), None); }); let _ = rx.recv().unwrap().unwrap(); @@ -90,7 +173,7 @@ impl Window { fn window_thread( parent: Option, options: WindowOpenOptions, build: B, - tx: mpsc::SyncSender, + tx: mpsc::SyncSender, parent_handle: Option, ) where H: WindowHandler + 'static, B: FnOnce(&mut crate::Window) -> H, @@ -182,8 +265,10 @@ impl Window { frame_interval: Duration::from_millis(15), event_loop_running: false, + close_requested: false, new_physical_size: None, + parent_handle, }; let mut handler = build(&mut crate::Window::new(&mut window)); @@ -200,10 +285,6 @@ impl Window { window.run_event_loop(&mut handler); } - pub fn window_info(&self) -> &WindowInfo { - &self.window_info - } - pub fn set_mouse_cursor(&mut self, mouse_cursor: MouseCursor) { if self.mouse_cursor == mouse_cursor { return; @@ -224,6 +305,10 @@ impl Window { self.mouse_cursor = mouse_cursor; } + pub fn close(&mut self) { + self.close_requested = true; + } + #[inline] fn drain_xcb_events(&mut self, handler: &mut dyn WindowHandler) { // the X server has a tendency to send spurious/extraneous configure notify events when a @@ -291,9 +376,41 @@ impl Window { self.drain_xcb_events(handler); } } + + // Check if the parents's handle was dropped (such as when the host + // requested the window to close) + // + // 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). + if let Some(parent_handle) = &self.parent_handle { + if parent_handle.parent_did_drop() { + self.handle_must_close(handler); + self.close_requested = false; + } + } + + // Check if the user has requested the window to close + if self.close_requested { + self.handle_must_close(handler); + self.close_requested = false; + } } } + fn handle_close_requested(&mut self, handler: &mut dyn WindowHandler) { + handler.on_event(&mut crate::Window::new(self), Event::Window(WindowEvent::WillClose)); + + // FIXME: handler should decide whether window stays open or not + self.event_loop_running = false; + } + + fn handle_must_close(&mut self, handler: &mut dyn WindowHandler) { + handler.on_event(&mut crate::Window::new(self), Event::Window(WindowEvent::WillClose)); + + self.event_loop_running = false; + } + fn handle_xcb_event(&mut self, handler: &mut dyn WindowHandler, event: xcb::GenericEvent) { let event_type = event.response_type() & !0x80; @@ -332,13 +449,7 @@ impl Window { self.xcb_connection.atoms.wm_delete_window.unwrap_or(xcb::NONE); if wm_delete_window == data32[0] { - handler.on_event( - &mut crate::Window::new(self), - Event::Window(WindowEvent::WillClose), - ); - - // FIXME: handler should decide whether window stays open or not - self.event_loop_running = false; + self.handle_close_requested(handler); } }