From 074bd2bce4f55f2ffbfc136fe0c8f58717f9c622 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 23 Jun 2026 05:06:43 +0200 Subject: [PATCH 1/7] Implement platform_handle on X11 --- src/context.rs | 34 +++++++++++++++++++++++++++ src/platform/x11/mod.rs | 35 ++++++++++++++++++++++++++++ src/platform/x11/window_shared.rs | 14 ++++++++--- src/platform/x11/xcb_connection.rs | 15 +++--------- src/wrappers/xlib/xlib_connection.rs | 24 +++++++++++++++++++ src/wrappers/xlib/xlib_xcb.rs | 11 ++++++++- 6 files changed, 117 insertions(+), 16 deletions(-) diff --git a/src/context.rs b/src/context.rs index 1687476e..75c1d15d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -3,6 +3,7 @@ use dpi::{Pixel, Size}; use raw_window_handle::{ DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle, }; +use std::fmt::Debug; #[derive(Clone)] pub struct WindowContext { @@ -42,6 +43,10 @@ impl WindowContext { self.inner.size() } + pub fn platform_handle(&self) -> PlatformHandle { + PlatformHandle { inner: self.inner.platform_handle() } + } + #[cfg(feature = "opengl")] pub fn gl_context(&self) -> Option { self.inner.gl_context() @@ -59,3 +64,32 @@ impl HasDisplayHandle for WindowContext { Ok(self.inner.display_handle()) } } + +#[derive(Clone)] +pub struct PlatformHandle { + inner: platform::PlatformHandle, +} + +// Assert that PlatformHandle implements both Send & Sync on all platforms +const _: () = { + const fn assert_impl_all() {} + let _: fn() = assert_impl_all::; +}; + +impl HasWindowHandle for PlatformHandle { + fn window_handle(&self) -> Result, HandleError> { + self.inner.window_handle().ok_or(HandleError::Unavailable) + } +} + +impl HasDisplayHandle for PlatformHandle { + fn display_handle(&self) -> Result, HandleError> { + Ok(self.inner.display_handle()) + } +} + +impl Debug for PlatformHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.inner.fmt(f) + } +} diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index 4f9efc95..7a4e3b08 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -1,6 +1,10 @@ mod xcb_connection; +use raw_window_handle::{DisplayHandle, XcbWindowHandle}; +use std::fmt::Formatter; +use std::num::NonZero; use std::rc::Rc; +use std::sync::Arc; pub(crate) use xcb_connection::X11Connection; mod window; @@ -15,7 +19,38 @@ mod visual_info; mod window_shared; use crate::platform::x11::window_shared::WindowInner; +use crate::wrappers::xlib::XlibXcbConnection; + pub type WindowContext = Rc; #[cfg(feature = "opengl")] pub mod gl; + +#[derive(Clone)] +pub struct PlatformHandle { + connection: Arc, + window_id: NonZero, +} + +impl PlatformHandle { + pub fn window_handle(&self) -> Option> { + let handle = XcbWindowHandle::new(self.window_id); + Some(unsafe { raw_window_handle::WindowHandle::borrow_raw(handle.into()) }) + } + + pub fn display_handle(&self) -> DisplayHandle<'_> { + self.connection.display_handle() + } +} + +impl std::fmt::Debug for PlatformHandle { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let display_string = self.connection.xlib_connection().display_string(); + let display_string: &str = &display_string.to_string_lossy(); + + f.debug_struct("PlatformHandle (X11)") + .field("connection", &display_string) + .field("window_id", &self.window_id.get()) + .finish() + } +} diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index e2c5b7fe..b731ad5e 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -1,10 +1,11 @@ use crate::platform::X11Connection; use crate::{MouseCursor, WindowSize}; use dpi::{PhysicalSize, Size}; -use raw_window_handle::{DisplayHandle, XcbWindowHandle}; +use raw_window_handle::{DisplayHandle, XlibWindowHandle}; use std::cell::Cell; use std::num::NonZero; use std::rc::Rc; +use std::sync::Arc; use x11rb::connection::Connection; use x11rb::protocol::xproto::{ ChangeWindowAttributesAux, ConfigureWindowAux, ConnectionExt, InputFocus, Window as XWindow, @@ -97,12 +98,19 @@ impl WindowInner { } pub fn window_handle(&self) -> Option> { - let handle = XcbWindowHandle::new(self.window_id); + let handle = XlibWindowHandle::new(self.window_id.get() as _); Some(unsafe { raw_window_handle::WindowHandle::borrow_raw(handle.into()) }) } pub fn display_handle(&self) -> DisplayHandle<'_> { - self.connection.display_handle() + self.connection.conn.display_handle() + } + + pub fn platform_handle(&self) -> super::PlatformHandle { + super::PlatformHandle { + connection: Arc::clone(&self.connection.conn), + window_id: self.window_id, + } } #[cfg(feature = "opengl")] diff --git a/src/platform/x11/xcb_connection.rs b/src/platform/x11/xcb_connection.rs index b0c8b805..55e9da13 100644 --- a/src/platform/x11/xcb_connection.rs +++ b/src/platform/x11/xcb_connection.rs @@ -1,8 +1,7 @@ -use raw_window_handle::{DisplayHandle, XcbDisplayHandle}; use std::cell::RefCell; use std::collections::hash_map::{Entry, HashMap}; use std::error::Error; -use std::ptr::NonNull; +use std::sync::Arc; use x11rb::connection::Connection; use x11rb::cursor::Handle as CursorHandle; use x11rb::protocol::xproto::{self, Cursor, Screen}; @@ -44,7 +43,7 @@ x11rb::atom_manager! { /// /// Keeps track of the xcb connection itself and the xlib display ID that was used to connect. pub struct X11Connection { - pub(crate) conn: XlibXcbConnection, + pub(crate) conn: Arc, pub(crate) atoms: Atoms, pub(crate) resources: resource_manager::Database, pub(crate) cursor_handle: CursorHandle, @@ -62,7 +61,7 @@ impl X11Connection { let cursor_handle = CursorHandle::new(xcb_conn, screen as usize, &resources)?.reply()?; Ok(Self { - conn, + conn: Arc::new(conn), atoms, resources, cursor_handle, @@ -116,12 +115,4 @@ impl X11Connection { ) -> Result, GetPropertyError> { self::get_property::get_property(window, property, property_type, &self.conn) } - - pub fn display_handle(&self) -> DisplayHandle<'_> { - let raw_connection = self.conn.xcb_connection().get_raw_xcb_connection(); - let Some(raw_connection) = NonNull::new(raw_connection) else { unreachable!() }; - let handle = XcbDisplayHandle::new(Some(raw_connection), self.conn.default_screen()); - - unsafe { DisplayHandle::borrow_raw(handle.into()) } - } } diff --git a/src/wrappers/xlib/xlib_connection.rs b/src/wrappers/xlib/xlib_connection.rs index b43a7c53..b284a513 100644 --- a/src/wrappers/xlib/xlib_connection.rs +++ b/src/wrappers/xlib/xlib_connection.rs @@ -1,4 +1,5 @@ use std::error::Error; +use std::ffi::CStr; use std::fmt::Formatter; use std::os::raw::c_int; use std::ptr::NonNull; @@ -17,10 +18,19 @@ pub struct XlibConnection { default_screen: c_int, } +// SAFETY: Xlib functions should be thread-safe, since we initialize it with XInitThreads below +unsafe impl Send for XlibConnection {} +// SAFETY: same as above +unsafe impl Sync for XlibConnection {} + impl XlibConnection { pub fn open() -> Result> { let xlib = Box::new(Xlib::open()?); + if unsafe { (xlib.XInitThreads)() } == 0 { + return Err(InitThreadsFailedError.into()); + } + // SAFETY: It's always safe to call XOpenDisplay with a NULL display_name let ptr = unsafe { (xlib.XOpenDisplay)(core::ptr::null()) }; @@ -48,6 +58,11 @@ impl XlibConnection { self.default_screen } + pub fn display_string(&self) -> &CStr { + let ptr = unsafe { (self.xlib.XDisplayString)(self.display.as_ptr()) }; + unsafe { CStr::from_ptr(ptr) } + } + /// Safe wrapper for XDefaultScreen fn fetch_default_screen(&self) -> c_int { // SAFETY: This type ensures the display pointer is always valid. @@ -131,3 +146,12 @@ impl std::fmt::Display for DisplayOpenFailedError { } } impl Error for DisplayOpenFailedError {} + +#[derive(Debug)] +struct InitThreadsFailedError; +impl std::fmt::Display for InitThreadsFailedError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str("Failed to open X11 display connection: XOpenDisplay() failed") + } +} +impl Error for InitThreadsFailedError {} diff --git a/src/wrappers/xlib/xlib_xcb.rs b/src/wrappers/xlib/xlib_xcb.rs index 27d83b56..cadbc709 100644 --- a/src/wrappers/xlib/xlib_xcb.rs +++ b/src/wrappers/xlib/xlib_xcb.rs @@ -1,8 +1,10 @@ use crate::wrappers::xlib::xlib_connection::XlibConnection; +use raw_window_handle::{DisplayHandle, XlibDisplayHandle}; use std::error::Error; use std::ops::Deref; use std::os::fd::{AsFd, BorrowedFd}; use std::os::raw::c_int; +use std::ptr::NonNull; use x11_dl::xlib_xcb::Xlib_xcb; use x11rb::xcb_ffi::XCBConnection; @@ -54,10 +56,17 @@ impl XlibXcbConnection { &self.xcb_connection } - #[cfg(feature = "opengl")] pub fn xlib_connection(&self) -> &XlibConnection { &self.xlib_connection } + + pub fn display_handle(&self) -> DisplayHandle<'_> { + let raw_connection = self.xlib_connection.as_raw().cast(); + let Some(raw_connection) = NonNull::new(raw_connection) else { unreachable!() }; + let handle = XlibDisplayHandle::new(Some(raw_connection), self.default_screen()); + + unsafe { DisplayHandle::borrow_raw(handle.into()) } + } } // For convenience From b648bd2b3642ca179c18eab72f9ebd9aa09d1dae Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 23 Jun 2026 05:55:51 +0200 Subject: [PATCH 2/7] wip (abort) --- Cargo.toml | 2 +- examples/render_wgpu/Cargo.toml | 10 ++ examples/render_wgpu/src/main.rs | 219 +++++++++++++++++++++++++++++++ src/platform/macos/context.rs | 9 +- src/platform/macos/mod.rs | 38 ++++++ src/wrappers/appkit.rs | 2 + src/wrappers/appkit/rwh_view.rs | 38 ++++++ src/wrappers/appkit/view.rs | 5 + 8 files changed, 321 insertions(+), 2 deletions(-) create mode 100644 examples/render_wgpu/Cargo.toml create mode 100644 examples/render_wgpu/src/main.rs create mode 100644 src/wrappers/appkit/rwh_view.rs diff --git a/Cargo.toml b/Cargo.toml index 1fe37e01..85733ef8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,7 +93,7 @@ objc2-app-kit = { version = "0.3.2", default-features = false, features = [ ] } [workspace] -members = ["examples/open_parented", "examples/open_window", "examples/render_femtovg"] +members = ["examples/open_parented", "examples/open_window", "examples/render_femtovg", "examples/render_wgpu"] [lints.clippy] missing-safety-doc = "allow" diff --git a/examples/render_wgpu/Cargo.toml b/examples/render_wgpu/Cargo.toml new file mode 100644 index 00000000..dd968d33 --- /dev/null +++ b/examples/render_wgpu/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "render_wgpu" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +baseview = { path = "../..", features = ["opengl"] } +wgpu = "29.0.3" +pollster = "0.4.0" diff --git a/examples/render_wgpu/src/main.rs b/examples/render_wgpu/src/main.rs new file mode 100644 index 00000000..5f1746d7 --- /dev/null +++ b/examples/render_wgpu/src/main.rs @@ -0,0 +1,219 @@ +use baseview::dpi::{LogicalSize, PhysicalSize}; +use baseview::{ + Event, EventStatus, Window, WindowContext, WindowHandler, WindowOpenOptions, WindowSize, +}; + +use std::cell::RefCell; + +struct WgpuExample { + window_context: WindowContext, + + instance: wgpu::Instance, + device: wgpu::Device, + pipeline: wgpu::RenderPipeline, + queue: wgpu::Queue, + surface: RefCell>, + surface_config: RefCell, +} + +impl WgpuExample { + async fn new(context: WindowContext) -> Self { + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_with_display_handle( + Box::new(context.platform_handle()), + )); + + let surface = instance.create_surface(context.platform_handle()).unwrap(); + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::LowPower, + force_fallback_adapter: false, + // Request an adapter which can render to our surface + compatible_surface: Some(&surface), + }) + .await + .expect("Failed to find an appropriate adapter"); + + // Create the logical device and command queue + let (device, queue) = adapter + .request_device(&wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::downlevel_webgl2_defaults() + .using_resolution(adapter.limits()), + memory_hints: wgpu::MemoryHints::MemoryUsage, + ..Default::default() + }) + .await + .expect("Failed to create device"); + + const SHADER: &str = " + const VERTS = array( + vec2(0.5, 1.0), + vec2(0.0, 0.0), + vec2(1.0, 0.0) + ); + + struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) position: vec2, + }; + + @vertex + fn vs_main( + @builtin(vertex_index) in_vertex_index: u32, + ) -> VertexOutput { + var out: VertexOutput; + out.position = VERTS[in_vertex_index]; + out.clip_position = vec4(out.position - 0.5, 0.0, 1.0); + return out; + } + + @fragment + fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return vec4(in.position, 0.5, 1.0); + } + "; + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(SHADER.into()), + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + immediate_size: 0, + }); + + let swapchain_capabilities = surface.get_capabilities(&adapter); + let swapchain_format = swapchain_capabilities.formats[0]; + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + compilation_options: Default::default(), + targets: &[Some(swapchain_format.into())], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview_mask: None, + cache: None, + }); + + let PhysicalSize { width, height } = context.size().physical; + + let surface_config = surface.get_default_config(&adapter, width, height).unwrap(); + surface.configure(&device, &surface_config); + + Self { + window_context: context, + + instance, + device, + pipeline, + queue, + surface: surface.into(), + surface_config: surface_config.into(), + } + } +} + +impl WindowHandler for WgpuExample { + fn on_frame(&self) { + let mut surface = self.surface.borrow_mut(); + + let surface_texture = match surface.get_current_texture() { + wgpu::CurrentSurfaceTexture::Success(texture) => texture, + wgpu::CurrentSurfaceTexture::Occluded | wgpu::CurrentSurfaceTexture::Timeout => return, + wgpu::CurrentSurfaceTexture::Suboptimal(_) | wgpu::CurrentSurfaceTexture::Outdated => { + surface.configure(&self.device, &self.surface_config.borrow()); + // We'll retry next frame + return; + } + wgpu::CurrentSurfaceTexture::Lost => { + *surface = + self.instance.create_surface(self.window_context.platform_handle()).unwrap(); + surface.configure(&self.device, &self.surface_config.borrow()); + + // We'll retry next frame + return; + } + wgpu::CurrentSurfaceTexture::Validation => { + unreachable!("No error scope registered, so validation errors will panic") + } + }; + + let view = surface_texture.texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = + self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), + store: wgpu::StoreOp::Store, + }, + depth_slice: None, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + multiview_mask: None, + }); + + rpass.set_pipeline(&self.pipeline); + rpass.draw(0..3, 0..1); + } + + self.queue.submit(Some(encoder.finish())); + surface_texture.present(); + } + + fn resized(&self, new_size: WindowSize) { + let mut surface_config = self.surface_config.borrow_mut(); + + surface_config.width = new_size.physical.width; + surface_config.height = new_size.physical.height; + + let surface = self.surface.borrow(); + surface.configure(&self.device, &surface_config); + } + + fn on_event(&self, event: Event) -> EventStatus { + log_event(&event); + + EventStatus::Ignored + } +} + +fn main() { + let window_open_options = WindowOpenOptions::new() + .with_title("WGPU on Baseview") + .with_size(LogicalSize::new(512, 512)); + + Window::open_blocking(window_open_options, |c| pollster::block_on(WgpuExample::new(c))); +} + +fn log_event(event: &Event) { + match event { + Event::Mouse(e) => println!("Mouse event: {:?}", e), + Event::Keyboard(e) => println!("Keyboard event: {:?}", e), + Event::Window(e) => println!("Window event: {:?}", e), + } +} diff --git a/src/platform/macos/context.rs b/src/platform/macos/context.rs index bbd772aa..e54e1961 100644 --- a/src/platform/macos/context.rs +++ b/src/platform/macos/context.rs @@ -1,6 +1,6 @@ use crate::platform::macos::cursor::Cursor; use crate::platform::macos::view::BaseviewView; -use crate::platform::WindowSharedState; +use crate::platform::{PlatformHandle, WindowSharedState}; use crate::wrappers::appkit::{View, ViewRef}; use crate::{MouseCursor, WindowSize}; use dpi::Size; @@ -86,4 +86,11 @@ impl WindowContext { pub fn display_handle(&self) -> DisplayHandle<'_> { DisplayHandle::appkit() } + + pub fn platform_handle(&self) -> PlatformHandle { + Weak::from_retained(self.) + PlatformHandle { + view: + } + } } diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs index 614b8f9f..890659b7 100644 --- a/src/platform/macos/mod.rs +++ b/src/platform/macos/mod.rs @@ -4,8 +4,46 @@ mod keyboard; mod view; mod window; +use crate::wrappers::appkit::RwhPinkyPromiseNSView; pub use context::WindowContext; +use objc2::rc::Weak; +use raw_window_handle::{AppKitWindowHandle, DisplayHandle}; +use std::fmt; +use std::fmt::Formatter; +use std::ptr::NonNull; pub use window::*; #[cfg(feature = "opengl")] pub mod gl; + +#[derive(Clone)] +pub struct PlatformHandle { + view: Weak, +} + +impl PlatformHandle { + pub fn window_handle(&self) -> Option> { + let view = self.view.load()?; + let ns_view = NonNull::from(&view as &RwhPinkyPromiseNSView).cast(); + let handle = AppKitWindowHandle::new(ns_view); + + Some(unsafe { raw_window_handle::WindowHandle::borrow_raw(handle.into()) }) + } + + pub fn display_handle(&self) -> DisplayHandle<'_> { + DisplayHandle::appkit() + } +} + +impl fmt::Debug for PlatformHandle { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + struct PtrFmt(T); + impl fmt::Debug for PtrFmt { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + fmt::Pointer::fmt(&self.0, f) + } + } + + f.debug_struct("PlatformHandle (AppKit)").field("ns_view", &PtrFmt(&self.view)).finish() + } +} diff --git a/src/wrappers/appkit.rs b/src/wrappers/appkit.rs index 08f032e5..cd5b5bd1 100644 --- a/src/wrappers/appkit.rs +++ b/src/wrappers/appkit.rs @@ -1,4 +1,5 @@ mod notification_center; +mod rwh_view; mod timer; mod view; mod window; @@ -9,6 +10,7 @@ use objc2_core_foundation::CFUUID; use std::ffi::CString; pub use notification_center::*; +pub use rwh_view::*; pub use timer::TimerHandle; pub use view::*; pub use window::*; diff --git a/src/wrappers/appkit/rwh_view.rs b/src/wrappers/appkit/rwh_view.rs new file mode 100644 index 00000000..dd87ac4e --- /dev/null +++ b/src/wrappers/appkit/rwh_view.rs @@ -0,0 +1,38 @@ +use objc2::__framework_prelude::Retained; +use objc2::rc::Weak; +use objc2::{Encoding, Message, RefEncode}; +use objc2_app_kit::NSView; +use raw_window_handle::AppKitWindowHandle; +use std::ptr::NonNull; + +#[repr(C)] +pub struct RwhPinkyPromiseNSView { + must_protecc: NSView, +} + +impl RwhPinkyPromiseNSView { + pub fn new(view: Retained) -> Retained { + // SAFETY: Safe due to #[repr(C)] and just wrapping an NSView + unsafe { Retained::cast_unchecked(view) } + } + + pub fn window_handle(&self) -> raw_window_handle::WindowHandle { + let handle = AppKitWindowHandle::new(NonNull::from(&self.must_protecc).cast()); + + // SAFETY: This is safe, as this type guarantees we're an actual NSView and is borrowed by &self + unsafe { raw_window_handle::WindowHandle::borrow_raw(handle.into()) } + } +} + +// SAFETY: Safe due to #[repr(C)] and just wrapping an NSView +unsafe impl RefEncode for RwhPinkyPromiseNSView { + const ENCODING_REF: Encoding = NSView::ENCODING_REF; +} + +// SAFETY: same as above +unsafe impl Message for RwhPinkyPromiseNSView {} + +// SAFETY: RWH's Pinky promise! +unsafe impl Send for RwhPinkyPromiseNSView {} +// SAFETY: RWH's Pinky promise! +unsafe impl Sync for RwhPinkyPromiseNSView {} diff --git a/src/wrappers/appkit/view.rs b/src/wrappers/appkit/view.rs index c5a27e5b..cd085d14 100644 --- a/src/wrappers/appkit/view.rs +++ b/src/wrappers/appkit/view.rs @@ -104,6 +104,11 @@ impl View { let Some(ns_window) = self.window() else { return 1.0 }; ns_window.backingScaleFactor() } + + pub fn retained_to_nsview(this: Retained) -> Retained { + // SAFETY: Safe due to #[repr(C)] and just wrapping an NSView + unsafe { Retained::cast_unchecked(this) } + } } pub struct ViewInner { From e9f66a27ee29ec97137b3cd189d1f4091b3f66cf Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 23 Jun 2026 09:58:45 +0200 Subject: [PATCH 3/7] wip --- Cargo.toml | 1 + src/platform/macos/context.rs | 15 +++++++----- src/platform/macos/mod.rs | 42 +++++++++++++++++++++++---------- src/platform/macos/view.rs | 6 +++-- src/platform/macos/window.rs | 8 +++++-- src/wrappers/appkit.rs | 2 -- src/wrappers/appkit/rwh_view.rs | 38 ----------------------------- 7 files changed, 49 insertions(+), 63 deletions(-) delete mode 100644 src/wrappers/appkit/rwh_view.rs diff --git a/Cargo.toml b/Cargo.toml index 85733ef8..737fc97e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,7 @@ windows-core = { version = "=0.61.2" } objc2 = "0.6.4" objc2-core-foundation = { version = "0.3.2", default-features = false, features = ["std", "CFString", "CFUUID", "block2", "objc2"] } block2 = "0.6.2" +dispatch2 = "0.3.1" objc2-foundation = { version = "0.3.2", default-features = false, features = ["std", "NSEnumerator", "block2", "NSOperation"] } objc2-app-kit = { version = "0.3.2", default-features = false, features = [ "NSApplication", diff --git a/src/platform/macos/context.rs b/src/platform/macos/context.rs index e54e1961..22333cda 100644 --- a/src/platform/macos/context.rs +++ b/src/platform/macos/context.rs @@ -3,22 +3,28 @@ use crate::platform::macos::view::BaseviewView; use crate::platform::{PlatformHandle, WindowSharedState}; use crate::wrappers::appkit::{View, ViewRef}; use crate::{MouseCursor, WindowSize}; +use dispatch2::MainThreadBound; use dpi::Size; use objc2::rc::Weak; use objc2::runtime::NSObjectProtocol; -use objc2::Message; +use objc2::{MainThreadMarker, Message}; use raw_window_handle::DisplayHandle; use std::rc::Rc; #[derive(Clone)] pub struct WindowContext { + mtm: MainThreadMarker, view: Weak>, state: Rc, } impl WindowContext { pub(crate) fn new(view: ViewRef<'_, BaseviewView>) -> Self { - Self { view: Weak::from_retained(&view.view.retain()), state: Rc::clone(&view.state) } + Self { + view: Weak::from_retained(&view.view.retain()), + state: Rc::clone(&view.state), + mtm: view.mtm, + } } pub fn close(&self) { @@ -88,9 +94,6 @@ impl WindowContext { } pub fn platform_handle(&self) -> PlatformHandle { - Weak::from_retained(self.) - PlatformHandle { - view: - } + PlatformHandle { inner: MainThreadBound::new(self.view.clone(), self.mtm) } } } diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs index 890659b7..0c72e4f3 100644 --- a/src/platform/macos/mod.rs +++ b/src/platform/macos/mod.rs @@ -4,30 +4,30 @@ mod keyboard; mod view; mod window; -use crate::wrappers::appkit::RwhPinkyPromiseNSView; +use crate::platform::macos::view::BaseviewView; +use crate::wrappers::appkit::View; pub use context::WindowContext; +use dispatch2::MainThreadBound; use objc2::rc::Weak; -use raw_window_handle::{AppKitWindowHandle, DisplayHandle}; +use objc2::MainThreadMarker; +use raw_window_handle::DisplayHandle; use std::fmt; use std::fmt::Formatter; -use std::ptr::NonNull; pub use window::*; #[cfg(feature = "opengl")] pub mod gl; -#[derive(Clone)] pub struct PlatformHandle { - view: Weak, + inner: MainThreadBound>>, } impl PlatformHandle { pub fn window_handle(&self) -> Option> { - let view = self.view.load()?; - let ns_view = NonNull::from(&view as &RwhPinkyPromiseNSView).cast(); - let handle = AppKitWindowHandle::new(ns_view); + let mtm = MainThreadMarker::new()?; + let view = self.inner.get(mtm); - Some(unsafe { raw_window_handle::WindowHandle::borrow_raw(handle.into()) }) + View::window_handle_from_weak(view) } pub fn display_handle(&self) -> DisplayHandle<'_> { @@ -35,15 +35,31 @@ impl PlatformHandle { } } +impl Clone for PlatformHandle { + fn clone(&self) -> Self { + // upstream this in objc2/dispatch2 someday + // SAFETY: The use of this marker is only to access the thread-safe Retained impl + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + let view = self.inner.get(mtm); + Self { inner: MainThreadBound::new(view.clone(), mtm) } + } +} + impl fmt::Debug for PlatformHandle { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - struct PtrFmt(T); - impl fmt::Debug for PtrFmt { + struct PtrFmt<'a>(&'a PlatformHandle); + impl fmt::Debug for PtrFmt<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - fmt::Pointer::fmt(&self.0, f) + // upstream this in objc2/dispatch2 someday + // SAFETY: The use of this marker is only to access the thread-safe Retained impl + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + match self.0.inner.get(mtm).load() { + Some(retained) => fmt::Pointer::fmt(&retained, f), + _ => f.write_str("(gone)"), + } } } - f.debug_struct("PlatformHandle (AppKit)").field("ns_view", &PtrFmt(&self.view)).finish() + f.debug_struct("PlatformHandle (AppKit)").field("ns_view", &PtrFmt(&self)).finish() } } diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 498b2a51..f7a21b4e 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -13,7 +13,7 @@ use dpi::{LogicalPosition, LogicalSize, Size}; use objc2::__framework_prelude::Retained; use objc2::rc::Weak; use objc2::runtime::{NSObjectProtocol, ProtocolObject}; -use objc2::{msg_send, AllocAnyThread}; +use objc2::{msg_send, AllocAnyThread, MainThreadMarker}; use objc2_app_kit::{ NSApplication, NSDragOperation, NSDraggingInfo, NSEvent, NSFilenamesPboardType, NSTrackingArea, NSTrackingAreaOptions, NSView, NSWindow, @@ -29,6 +29,7 @@ pub enum ViewParentingType { pub(crate) struct BaseviewView { pub(crate) state: Rc, + pub(crate) mtm: MainThreadMarker, window_handler: OnceCell>, frame_timer: Cell>, @@ -46,7 +47,7 @@ impl BaseviewView { pub fn new( _options: WindowOpenOptions, builder: impl FnOnce(crate::WindowContext) -> H + Send + 'static, - parenting: ViewParentingType, final_size: LogicalSize, + parenting: ViewParentingType, final_size: LogicalSize, mtm: MainThreadMarker, ) -> (Retained>, Rc) { let view_rect = NSRect::new(NSPoint::ZERO, NSSize::new(final_size.width, final_size.height)); @@ -54,6 +55,7 @@ impl BaseviewView { let state = Rc::new(WindowSharedState::new(final_size, 1.0)); let inner = BaseviewView { + mtm, state: state.clone(), keyboard_state: KeyboardState::new(), diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index 8961e14f..e8805eaa 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -45,6 +45,10 @@ impl Window { panic!("Invalid window handle: ns_view is NULL"); }; + let Some(mtm) = MainThreadMarker::new() else { + panic!("macOS: open_blocking can only be called on the main thread!") + }; + let parenting = ViewParentingType::Parented { parent_view: Weak::from_retained(&parent_view) }; @@ -52,7 +56,7 @@ impl Window { parent_view.window().map(|w| w.backingScaleFactor()).unwrap_or(1.0); let final_size = options.size.to_logical(backing_scale_factor); - let (ns_view, state) = BaseviewView::new(options, build, parenting, final_size); + let (ns_view, state) = BaseviewView::new(options, build, parenting, final_size, mtm); WindowHandle { view: Some(Weak::from_retained(&ns_view)).into(), state } }) @@ -89,7 +93,7 @@ impl Window { owned_window: Weak::from_retained(&window), }; - let _ = BaseviewView::new(options, build, parenting, final_size); + let _ = BaseviewView::new(options, build, parenting, final_size, mtm); app.run(); }) diff --git a/src/wrappers/appkit.rs b/src/wrappers/appkit.rs index cd5b5bd1..08f032e5 100644 --- a/src/wrappers/appkit.rs +++ b/src/wrappers/appkit.rs @@ -1,5 +1,4 @@ mod notification_center; -mod rwh_view; mod timer; mod view; mod window; @@ -10,7 +9,6 @@ use objc2_core_foundation::CFUUID; use std::ffi::CString; pub use notification_center::*; -pub use rwh_view::*; pub use timer::TimerHandle; pub use view::*; pub use window::*; diff --git a/src/wrappers/appkit/rwh_view.rs b/src/wrappers/appkit/rwh_view.rs deleted file mode 100644 index dd87ac4e..00000000 --- a/src/wrappers/appkit/rwh_view.rs +++ /dev/null @@ -1,38 +0,0 @@ -use objc2::__framework_prelude::Retained; -use objc2::rc::Weak; -use objc2::{Encoding, Message, RefEncode}; -use objc2_app_kit::NSView; -use raw_window_handle::AppKitWindowHandle; -use std::ptr::NonNull; - -#[repr(C)] -pub struct RwhPinkyPromiseNSView { - must_protecc: NSView, -} - -impl RwhPinkyPromiseNSView { - pub fn new(view: Retained) -> Retained { - // SAFETY: Safe due to #[repr(C)] and just wrapping an NSView - unsafe { Retained::cast_unchecked(view) } - } - - pub fn window_handle(&self) -> raw_window_handle::WindowHandle { - let handle = AppKitWindowHandle::new(NonNull::from(&self.must_protecc).cast()); - - // SAFETY: This is safe, as this type guarantees we're an actual NSView and is borrowed by &self - unsafe { raw_window_handle::WindowHandle::borrow_raw(handle.into()) } - } -} - -// SAFETY: Safe due to #[repr(C)] and just wrapping an NSView -unsafe impl RefEncode for RwhPinkyPromiseNSView { - const ENCODING_REF: Encoding = NSView::ENCODING_REF; -} - -// SAFETY: same as above -unsafe impl Message for RwhPinkyPromiseNSView {} - -// SAFETY: RWH's Pinky promise! -unsafe impl Send for RwhPinkyPromiseNSView {} -// SAFETY: RWH's Pinky promise! -unsafe impl Sync for RwhPinkyPromiseNSView {} From 136f4b027fffb988ead442906ec699a49f77505f Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 23 Jun 2026 10:03:54 +0200 Subject: [PATCH 4/7] macOS fix --- src/wrappers/appkit/view.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/wrappers/appkit/view.rs b/src/wrappers/appkit/view.rs index cd085d14..c5a27e5b 100644 --- a/src/wrappers/appkit/view.rs +++ b/src/wrappers/appkit/view.rs @@ -104,11 +104,6 @@ impl View { let Some(ns_window) = self.window() else { return 1.0 }; ns_window.backingScaleFactor() } - - pub fn retained_to_nsview(this: Retained) -> Retained { - // SAFETY: Safe due to #[repr(C)] and just wrapping an NSView - unsafe { Retained::cast_unchecked(this) } - } } pub struct ViewInner { From e66835f1c8441455c1ccd3464890a7c20203a1ac Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 23 Jun 2026 10:13:07 +0200 Subject: [PATCH 5/7] Win32 impl --- src/platform/win/mod.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/platform/win/mod.rs b/src/platform/win/mod.rs index ca6b84a6..61889721 100644 --- a/src/platform/win/mod.rs +++ b/src/platform/win/mod.rs @@ -4,6 +4,9 @@ mod keyboard; mod window; mod window_state; +use raw_window_handle::{DisplayHandle, Win32WindowHandle}; +use std::fmt::Debug; +use std::num::NonZeroIsize; use std::rc::Rc; pub use window::*; @@ -11,3 +14,26 @@ pub use window::*; pub mod gl; pub type WindowContext = Rc; + +#[derive(Clone)] +pub struct PlatformHandle { + hwnd: NonZeroIsize, +} + +impl PlatformHandle { + pub fn window_handle(&self) -> Option> { + let handle = Win32WindowHandle::new(self.hwnd); + // TODO: add HINSTANCE + Some(unsafe { raw_window_handle::WindowHandle::borrow_raw(handle.into()) }) + } + + pub fn display_handle(&self) -> DisplayHandle<'_> { + DisplayHandle::windows() + } +} + +impl Debug for PlatformHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PlatformHandle (Win32)").field("hwnd", &self.hwnd.get()).finish() + } +} From 4bec87b689b3a4cc6cd00b2e45dd967e9c8c1bec Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 23 Jun 2026 10:16:25 +0200 Subject: [PATCH 6/7] Win32 fix --- src/platform/win/window_state.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/platform/win/window_state.rs b/src/platform/win/window_state.rs index 2a230f4f..11b735da 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -1,4 +1,5 @@ use crate::platform::win::keyboard::KeyboardState; +use crate::platform::PlatformHandle; use crate::wrappers::win32::cursor::SystemCursor; use crate::wrappers::win32::window::HWnd; use crate::wrappers::win32::{Dpi, ExtendedUser32}; @@ -139,4 +140,9 @@ impl WindowState { pub fn display_handle(&self) -> DisplayHandle<'_> { DisplayHandle::windows() } + + pub fn platform_handle(&self) -> PlatformHandle { + let Some(hwnd) = NonZeroIsize::new(self.hwnd as _) else { unreachable!() }; + PlatformHandle { hwnd } + } } From 3da7425fe97d1fb64003a32768e9f6c0746a68f8 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 23 Jun 2026 10:22:07 +0200 Subject: [PATCH 7/7] Clippy fix --- src/platform/macos/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs index 0c72e4f3..556526e9 100644 --- a/src/platform/macos/mod.rs +++ b/src/platform/macos/mod.rs @@ -60,6 +60,6 @@ impl fmt::Debug for PlatformHandle { } } - f.debug_struct("PlatformHandle (AppKit)").field("ns_view", &PtrFmt(&self)).finish() + f.debug_struct("PlatformHandle (AppKit)").field("ns_view", &PtrFmt(self)).finish() } }