From 13839b5d5aa1dd54ee1413afbdd0511b925b2363 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:13:52 +0200 Subject: [PATCH 1/3] Implement set_parent --- examples/plugin_clack/Cargo.toml | 1 + examples/plugin_clack/src/gui.rs | 50 ++++++++++++++++--------------- src/platform/win/gl.rs | 2 +- src/platform/x11/event_loop.rs | 9 ++++-- src/platform/x11/window_thread.rs | 24 +++++++++------ src/platform/x11/xcb_window.rs | 11 +++++++ src/window.rs | 6 ++++ src/window_open_options.rs | 6 ++++ 8 files changed, 73 insertions(+), 36 deletions(-) diff --git a/examples/plugin_clack/Cargo.toml b/examples/plugin_clack/Cargo.toml index 1402c072..71546862 100644 --- a/examples/plugin_clack/Cargo.toml +++ b/examples/plugin_clack/Cargo.toml @@ -12,3 +12,4 @@ clack-extensions = { version = "0.1.0", features = ["gui", "clack-plugin", "raw- baseview = { path = "../..", features = ["opengl"] } softbuffer = "0.4.8" raw-window-handle = "0.6.2" + diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 1e4403a0..d485c9ce 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -31,7 +31,23 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { } fn create(&mut self, _configuration: GuiConfiguration) -> Result<(), PluginError> { - // Delay creation until set_parent + let options = WindowOpenOptions::new() + .with_size(PhysicalSize::new(400, 200)) + .with_gl_config(GlConfig::default()); + + let mut host = Host::new().with_main_thread(unsafe { + MainThreadHandler { host: self.host.shared().with_arbitrary_lifetime() } + }); + + if let Some(gui) = self.host_gui { + host = host.with_callbacks(unsafe { + HostGuiCallbacks { ext: gui, host: self.host.with_arbitrary_lifetime() } + }); + } + + let window = baseview::create_window_with_host(options, OpenWindowExample::new, host)?; + + self.gui = Some(ExamplePluginGui { handle: window }); Ok(()) } @@ -51,11 +67,11 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { } fn get_size(&mut self) -> Option { - let Some(gui) = self.gui.as_ref() else { - // Because we delayed the window creation, this will get called without a GUI active. - // During that time, return the default UI size. - return Some(GuiSize { width: 400, height: 200 }); + let Some(gui) = &self.gui else { + eprintln!("get_size called without a GUI active"); + return None; }; + Some(window_size_to_gui_size(gui.handle.size())) } @@ -90,27 +106,14 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { #[allow(deprecated)] fn set_parent(&mut self, window: ClapWindow) -> Result<(), PluginError> { + let Some(gui) = &self.gui else { + return Err(PluginError::Message("set_parent called without a GUI active")); + }; + let parent = window.raw_window_handle()?; let parent = unsafe { raw_window_handle::WindowHandle::borrow_raw(parent) }; - let options = WindowOpenOptions::new() - .with_size(PhysicalSize::new(400, 200)) - .with_gl_config(GlConfig::default()) - .with_parent(&parent); - - let mut host = Host::new().with_main_thread(unsafe { - MainThreadHandler { host: self.host.shared().with_arbitrary_lifetime() } - }); - - if let Some(gui) = self.host_gui { - host = host.with_callbacks(unsafe { - HostGuiCallbacks { ext: gui, host: self.host.with_arbitrary_lifetime() } - }); - } - - let window = baseview::create_window_with_host(options, OpenWindowExample::new, host)?; - - self.gui = Some(ExamplePluginGui { handle: window }); + gui.handle.set_parent(&parent)?; Ok(()) } @@ -163,7 +166,6 @@ struct MainThreadHandler { impl HostMainThreadCaller for MainThreadHandler { fn call_main_thread(&mut self) { - eprintln!("Requesting main thread"); self.host.request_callback(); } } diff --git a/src/platform/win/gl.rs b/src/platform/win/gl.rs index b38d74a2..5d125d1a 100644 --- a/src/platform/win/gl.rs +++ b/src/platform/win/gl.rs @@ -101,7 +101,7 @@ fn find_wgl_pixel_format( Ok(None) => {} }; - eprintln!("Warning: sRGB framebuffer not supported, falling back to non-sRGB"); + crate::warn!("Warning: sRGB framebuffer not supported, falling back to non-sRGB"); format_attribs.set_without_srgb_ext(); match extra.choose_pixel_format_from_attribs(&format_attribs, dc) { diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index a093ac7e..df0f6ea9 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -6,7 +6,7 @@ use std::result::Result; use crate::host::HostMainThreadCaller; use crate::platform::x11::error::FatalError; use crate::platform::x11::window_thread::{ - HostCallback, WindowThreadRequest, WindowThreadResponse, WindowThreadResponseMessage, + HostCallback, WindowThreadRequest, WindowThreadResponseMessage, }; use crate::warn; use crate::wrappers::xkbcommon::XkbcommonState; @@ -156,7 +156,7 @@ impl EventLoop { self.stop_now(); } calloop::channel::Event::Msg(req) => match self.handle_request(req) { - Ok(()) => self.send_response(Ok(WindowThreadResponse::Ok)), + Ok(()) => self.send_response(Ok(())), Err(e) => self.send_response(Err(e.to_string())), }, } @@ -199,6 +199,11 @@ impl EventLoop { self.window.resize_immediately(new_physical_size, &*self.handler)?; + Ok(()) + } + WindowThreadRequest::SetParent(new_parent) => { + self.window.xcb_window.reparent(Some(new_parent.window_id))?; + Ok(()) } } diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index a4d179ba..f6e4b785 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -34,6 +34,11 @@ impl WindowThreadShared { } } + fn init(&self, window: &WindowInner) { + self.set_size(window.get_size()); + self.set_scaling_factor(window.scale_factor()); + } + pub fn get_size(&self) -> PhysicalSize { let bytes = self.size.load(Ordering::Relaxed); let low = (bytes & u16::MAX as u32) as u16; @@ -64,13 +69,10 @@ impl WindowThreadShared { pub enum WindowThreadRequest { SuggestScaleFactor(f64), Resize(Size), + SetParent(ParentWindowHandle), } -pub enum WindowThreadResponse { - Ok, -} - -pub type WindowThreadResponseMessage = core::result::Result; +pub type WindowThreadResponseMessage = core::result::Result<(), String>; pub enum HostCallback { Resized { new_size: WindowSize, previous: WindowSize }, @@ -152,10 +154,7 @@ impl WindowThreadHandle { self.request_sender.send(req).map_err(|_| RequestFailed::Send)?; let result = self.response_receiver.recv().map_err(|_| RequestFailed::Recv)?; - match result { - Ok(WindowThreadResponse::Ok) => Ok(()), - Err(e) => Err(RequestFailed::Response(e).into()), - } + result.map_err(|e| RequestFailed::Response(e).into()) } pub fn run_until_closed(&self) -> Result<()> { @@ -185,6 +184,10 @@ impl WindowThreadHandle { } } + pub fn set_parent(&self, new_parent: ParentWindowHandle) -> Result<()> { + self.request(WindowThreadRequest::SetParent(new_parent)) + } + fn handle_main_thread_message(&mut self, msg: HostCallback) { let Some(host_callbacks) = self.host_callbacks.as_mut() else { return }; @@ -255,6 +258,9 @@ impl WindowThread { ) -> Result { let mut ev_loop = calloop::EventLoop::try_new()?; let inner = WindowInner::create(options, &ev_loop, shared.clone())?; + + shared.init(&inner); + let handler = handler.build(WindowContext::new(Rc::clone(&inner)))?; let event_loop = EventLoop::new(inner, handler, receiver, sender, main_thread_caller, &mut ev_loop)?; diff --git a/src/platform/x11/xcb_window.rs b/src/platform/x11/xcb_window.rs index d07560bf..2b40592d 100644 --- a/src/platform/x11/xcb_window.rs +++ b/src/platform/x11/xcb_window.rs @@ -74,6 +74,17 @@ impl XcbWindow { ) } + pub fn reparent( + &self, parent: Option, + ) -> Result, ConnectionError> { + self.connection.conn.reparent_window( + self.id().get(), + parent.map(|i| i.get()).unwrap_or(0), + 0, + 0, + ) + } + pub fn set_title(&self, title: &str) -> Result, ReplyOrIdError> { Ok(self.connection.conn.change_property8( PropMode::REPLACE, diff --git a/src/window.rs b/src/window.rs index 3dab4905..97d58c5e 100644 --- a/src/window.rs +++ b/src/window.rs @@ -91,6 +91,12 @@ impl WindowHandle { pub fn host_main_thread_callback(&mut self) { self.window_handle.handle_main_thread_callback() } + + #[inline] + pub fn set_parent(&self, parent: impl Into) -> Result<(), Error> { + self.window_handle.set_parent(parent.into().inner)?; + Ok(()) + } } #[inline] diff --git a/src/window_open_options.rs b/src/window_open_options.rs index 80b87542..c5bca625 100644 --- a/src/window_open_options.rs +++ b/src/window_open_options.rs @@ -93,3 +93,9 @@ impl ParentWindowHandle { Self { inner } } } + +impl From<&W> for ParentWindowHandle { + fn from(window: &W) -> Self { + Self::from_window(window) + } +} From 79094f991d08064cf29568e5205b574838da6869 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:46:21 +0200 Subject: [PATCH 2/3] Implement set_parent --- src/platform/macos/view.rs | 74 +++++++++++++++++++---------- src/platform/macos/window.rs | 26 +++++++--- src/platform/win/window.rs | 17 ++++++- src/platform/win/window_state.rs | 6 ++- src/wrappers/win32/window/handle.rs | 41 ++++++++++++++-- 5 files changed, 129 insertions(+), 35 deletions(-) diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 115c6fff..cf182f8c 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -27,7 +27,35 @@ use std::rc::Rc; pub enum ViewParentingType { Parented { parent_view: Weak }, - Windowed { owned_window: Weak, running_app: Weak }, + Windowed { owned_window: Weak }, + Uninitialized, +} + +impl ViewParentingType { + fn setup(&self, child: &View) { + match self { + ViewParentingType::Parented { parent_view } => { + if let Some(parent_view) = parent_view.load() { + parent_view.addSubview(child); + } + } + ViewParentingType::Windowed { owned_window, .. } => { + if let Some(owned_window) = owned_window.load() { + owned_window.setContentView(Some(child)); + set_delegate(&owned_window, child); + } + } + _ => {} + } + } + + fn teardown(self) { + if let ViewParentingType::Windowed { owned_window: parent_window } = self { + if let Some(parent_window) = parent_window.load() { + parent_window.close(); + } + } + } } pub(crate) struct BaseviewView { @@ -40,7 +68,8 @@ pub(crate) struct BaseviewView { keyboard_state: KeyboardState, - parenting: ViewParentingType, + parenting: RefCell, + pub(crate) lifetime_tied_to_app: Cell>>, host: Host, @@ -66,8 +95,9 @@ impl BaseviewView { frame_timer: None.into(), window_handler: WindowHandlerContainer::new(), notification_center_observer: None.into(), - parenting, + parenting: ViewParentingType::Uninitialized.into(), host, + lifetime_tied_to_app: None.into(), #[cfg(feature = "opengl")] gl_context: std::cell::OnceCell::new(), @@ -75,19 +105,8 @@ impl BaseviewView { let view = View::new(view_rect, inner, |view| { // Set up parenting before handler setup - match &view.parenting { - ViewParentingType::Parented { parent_view } => { - if let Some(parent_view) = parent_view.load() { - parent_view.addSubview(view.view); - } - } - ViewParentingType::Windowed { owned_window, .. } => { - if let Some(owned_window) = owned_window.load() { - owned_window.setContentView(Some(view.view)); - set_delegate(&owned_window, view.view); - } - } - } + parenting.setup(&view.view); + view.parenting.replace(parenting); view.state.scale_factor.set(view.view.backing_scale_factor()); view.state.size.set(view.view.size()); @@ -142,14 +161,11 @@ impl BaseviewView { this.frame_timer.take(); this.window_handler.destroy(); - if let ViewParentingType::Windowed { owned_window: parent_window, running_app } = - &this.parenting - { - if let Some(parent_window) = parent_window.load() { - parent_window.close(); - } + let parenting = this.parenting.replace(ViewParentingType::Uninitialized); + parenting.teardown(); - if let Some(app) = running_app.load() { + if let Some(app) = this.lifetime_tied_to_app.take() { + if let Some(app) = app.load() { app.stop(Some(&app)); } } @@ -159,6 +175,16 @@ impl BaseviewView { } } + pub fn set_parent(this: ViewRef, new_parent: Retained) { + let previous_parenting = this.parenting.replace(ViewParentingType::Uninitialized); + previous_parenting.teardown(); + + let parenting = ViewParentingType::Parented { parent_view: Weak::from(new_parent) }; + parenting.setup(this.view); + + this.parenting.replace(parenting); + } + pub fn resize(this: ViewRef, size: Size, notify_host: bool) { let size = size.to_logical::(this.view.backing_scale_factor()); // NOTE: macOS gives you a personal rave if you pass in fractional pixels here. Even @@ -176,7 +202,7 @@ impl BaseviewView { } // If this is a standalone window then we'll also need to resize the window itself - if let ViewParentingType::Windowed { owned_window, .. } = &this.parenting { + if let ViewParentingType::Windowed { owned_window } = &*this.parenting.borrow() { if let Some(owned_window) = owned_window.load() { owned_window.setContentSize(size); } diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index 3996cf91..055e4063 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -9,6 +9,7 @@ use std::rc::Rc; use crate::handler::WindowHandlerBuilder; use crate::host::Host; use crate::platform::macos::view::{BaseviewView, ViewParentingType}; +use crate::platform::ParentWindowHandle; use crate::platform::Result; use crate::wrappers::appkit::{create_window, View}; use crate::*; @@ -76,16 +77,12 @@ impl WindowHandle { builder: WindowOpenOptions, handler: WindowHandlerBuilder, host: Host, mtm: MainThreadMarker, ) -> Result { - let app = NSApplication::sharedApplication(mtm); let window = create_window_with_options(&builder, mtm); let final_size = window.contentRectForFrameRect(window.frame()).size; let final_size = LogicalSize::new(final_size.width, final_size.height); - let parenting = ViewParentingType::Windowed { - running_app: Weak::from_retained(&app), - owned_window: Weak::from_retained(&window), - }; + let parenting = ViewParentingType::Windowed { owned_window: Weak::from_retained(&window) }; let (view, state) = BaseviewView::new(builder, handler, parenting, host, final_size, mtm)?; @@ -93,7 +90,15 @@ impl WindowHandle { } pub fn run_until_closed(self) -> Result<()> { - NSApplication::sharedApplication(self.mtm).run(); + let Some(view) = self.view.load() else { return Ok(()) }; + let Some(view) = view.inner_ref() else { return Ok(()) }; + + let app = NSApplication::sharedApplication(self.mtm); + + view.lifetime_tied_to_app.set(Some(Weak::from_retained(&app))); + app.run(); + view.lifetime_tied_to_app.set(None); + Ok(()) } @@ -123,6 +128,15 @@ impl WindowHandle { // This does not do anything on macOS: all coordinates are already logical Ok(()) } + + pub fn set_parent(&self, new_parent: ParentWindowHandle) -> Result<()> { + let Some(view) = self.view.load() else { return Ok(()) }; + let Some(view) = view.inner_ref() else { return Ok(()) }; + + BaseviewView::set_parent(view, new_parent.view); + + Ok(()) + } } fn create_window_with_options( diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 4743cebf..a44272f9 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -117,6 +117,20 @@ impl WindowHandle { } } + pub fn set_parent(&self, new_parent: ParentWindowHandle) -> Result<()> { + let Some(hwnd) = self.hwnd.get() else { return Ok(()) }; + + hwnd.set_parent(&new_parent.handle)?; + + if !self.state.parented.get() { + self.state.parented.set(true); + + hwnd.set_style(WindowStyle::parented())?; + } + + Ok(()) + } + #[inline] pub fn handle_main_thread_callback(&self) { // No-op @@ -545,7 +559,8 @@ impl WindowHandle { WindowStyle::embedded() }; let dpi_ctx = DpiAwarenessContext::new(&extended_user_32)?; - let shared_state = WindowSharedState::new(window_size, extended_user_32.clone()); + let shared_state = + WindowSharedState::new(window_size, extended_user_32.clone(), options.parent.is_some()); let initializer = { let extended_user_32 = extended_user_32.clone(); diff --git a/src/platform/win/window_state.rs b/src/platform/win/window_state.rs index 8dd84f23..e80bec5b 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -142,6 +142,7 @@ impl WindowState { } pub struct WindowSharedState { + pub parented: Cell, pub is_alive: Cell, pub current_size: Cell>, pub current_dpi: Cell>, // None if Win32 HiDPI isn't supported @@ -153,8 +154,11 @@ pub struct WindowSharedState { } impl WindowSharedState { - pub fn new(current_size: PhysicalSize, user32: ExtendedUser32) -> Rc { + pub fn new( + current_size: PhysicalSize, user32: ExtendedUser32, parented: bool, + ) -> Rc { Self { + parented: parented.into(), is_alive: true.into(), current_dpi: None.into(), current_size: current_size.into(), diff --git a/src/wrappers/win32/window/handle.rs b/src/wrappers/win32/window/handle.rs index eed49cbc..ce94ae91 100644 --- a/src/wrappers/win32/window/handle.rs +++ b/src/wrappers/win32/window/handle.rs @@ -14,9 +14,9 @@ use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ GetFocus, ReleaseCapture, SetCapture, SetFocus, TrackMouseEvent, TME_LEAVE, TRACKMOUSEEVENT, }; use windows_sys::Win32::UI::WindowsAndMessaging::{ - DestroyWindow, GetWindowLongPtrW, GetWindowLongW, SetTimer, SetWindowLongPtrW, SetWindowPos, - ShowWindow, GWLP_USERDATA, GWL_EXSTYLE, GWL_STYLE, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOZORDER, - SW_SHOW, WINDOW_LONG_PTR_INDEX, + DestroyWindow, GetWindowLongPtrW, GetWindowLongW, SetParent, SetTimer, SetWindowLongPtrW, + SetWindowLongW, SetWindowPos, ShowWindow, GWLP_USERDATA, GWL_EXSTYLE, GWL_STYLE, + SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOZORDER, SW_SHOW, WINDOW_LONG_PTR_INDEX, }; /// A simple wrapper around a HWND. @@ -80,6 +80,25 @@ impl HWnd { Err(error) } + pub fn set_long(&self, index: WINDOW_LONG_PTR_INDEX, value: i32) -> Result { + // SAFETY: This function is always safe to call + unsafe { SetLastError(0) }; + // SAFETY: This type guarantees the HWND is still valid. + let result = unsafe { SetWindowLongW(self.as_raw(), index, value) }; + if result != 0 { + return Ok(result); + } + + // We can't know if a return value of 0 is indicative of an error, or if it's just because the + // value was actually 0. So we check GetLastError instead (called by Error::from_thread). + let error = Error::from_thread(); + if error.code() == HRESULT(0) { + return Ok(result); + } + + Err(error) + } + pub fn get_style(&self) -> Result { Ok(WindowStyle { style: self.get_long(GWL_STYLE)? as _, @@ -87,6 +106,22 @@ impl HWnd { }) } + pub fn set_style(&self, style: WindowStyle) -> Result<()> { + self.set_long(GWL_STYLE, style.style as _)?; + self.set_long(GWL_EXSTYLE, style.style_ex as _)?; + Ok(()) + } + + pub fn set_parent(&self, parent: &HWnd) -> Result<()> { + let result = unsafe { SetParent(self.as_raw(), parent.as_raw()) }; + + if result.is_null() { + return Err(Error::from_thread()); + } + + Ok(()) + } + pub fn get_dpi(&self, extended_user32: &ExtendedUser32) -> Result> { let Some(get_dpi_for_window) = extended_user32.get_dpi_for_window else { return Ok(None); From e19eb93a501cb3c3b045ea695f3b2b37004e55cf Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:41:09 +0200 Subject: [PATCH 3/3] Clippy fix --- src/platform/macos/view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index cf182f8c..3e297975 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -105,7 +105,7 @@ impl BaseviewView { let view = View::new(view_rect, inner, |view| { // Set up parenting before handler setup - parenting.setup(&view.view); + parenting.setup(view.view); view.parenting.replace(parenting); view.state.scale_factor.set(view.view.backing_scale_factor());