1
0
Fork 0

Compare commits

...

17 commits

Author SHA1 Message Date
Alex Janka 50a5602bca don't disable mouse coalescing on macos 2024-01-25 09:46:59 +11:00
Micah Johnston 2c1b1a7b0f
Fix window cleanup logic on macOS (#164)
Instead of trying to detect when to clean up the window based on the `NSView`'s retain count, require window cleanup to be initiated explicitly via `Window::close`, `WindowHandle::close`, or `[NSWindowDelegate windowShouldClose:]` (in non-parented mode; called when the user clicks the "X" button). This fixes the leaks and use-after-frees that can be caused by the inherent unreliability of the retain count logic.

As discussed in #153, this change essentially means that the `NSView` created by Baseview will not be suitable as the top-level view for an Audio Unit, since the Baseview API now requires that child windows be cleaned up by an explicit call to `WindowHandle::close`, and the only reliable signal for cleaning up an Audio Unit view is a call to `[NSView dealloc]`. However, this does not mean that Baseview cannot be used in the context of an Audio Unit; it just means that plugin frameworks must implement a compatibility layer with a wrapper `NSView` (which is the approach taken by JUCE).

In order to implement this change:

- `WindowState` is stored in an `Rc` rather than a `Box`.
- `WindowHandle` holds an `Rc<WindowState>` so that `WindowHandle::close` can directly invoke window cleanup logic.
- Since the window can be closed during an event handler, `WindowState::from_view` now returns a clone of the `Rc<WindowState>` held by the `NSView` to ensure that it lives until the end of an event handler.
- In the non-parented case, the `NSView` is set as the window delegate, which allows it to receive the `windowShouldClose:` call when the user clicks the "X" button, upon which it will dispatch the `WillClose` event and initiate window cleanup logic.
- `Window::open_parented` and `open_blocking` no longer `release` the `NSView` immediately after attaching it. Instead, the `NSView` is released as part of the cleanup logic in `WindowInner::close`.
- `Window::resize` now checks if the window is open to avoid using the `NSView` after releasing it.
- The overridden `release` method, the `retain_count_after_build` field, the `ParentHandle` struct, and the `close_requested` flag have all been removed.
2023-12-17 22:57:51 -06:00
Micah Johnston fdb43eac11
Unify Window struct across backends (#162)
The public Window struct holds a mutable reference to `platform::Window`, which is a pattern that doesn't make sense for all backends. On Windows, the `platform::Window` struct itself just holds another (immutable) reference, and on macOS the `platform::Window` struct has to be wrapped in a `RefCell` so that a mutable reference to it can be formed.

Change the public `Window` struct to hold `platform::Window` directly, and change `platform::Window` in the macOS and X11 backends so that it simply holds a reference to another `WindowInner` struct similarly to the Windows backend. This allows us to remove the platform conditional in the declaration of the top-level `Window` struct. It also allows us to remove the `RefCell` wrapping `platform::Window` in the macOS backend and replace it with `Cell`s wrapping its individual fields.
2023-12-17 02:21:31 -06:00
Micah Johnston 937ef965b4
Remove unnecessary PhantomData from platform::WindowHandle definitions (#163)
These `PhantomData` fields are redundant, as the public `WindowHandle` struct already contains one.
2023-12-17 02:16:36 -06:00
Micah Johnston 7d14a555e5
Expose public Window::set_mouse_cursor method (#161)
The X11 backend has a full implementation of a `Window::set_mouse_cursor` method, but it isn't exposed via any public interface, which results in a lot of warnings for unused code. Add a public `set_mouse_cursor` method, along with stubbed-out versions in the Windows and macOS backends.
2023-12-17 02:12:31 -06:00
Micah Johnston 0976a9a6a4
macOS: use interior mutability for WindowState (#157)
Forming mutable references to the WindowState is unsound given the possibility
of reentrant calls to NSView methods. Instead, form only immutable references
to the WindowState and wrap mutable fields in Cell and RefCell.

Follow-up work should use try_borrow_mut instead of borrow_mut to avoid
panicking in the case of reentrant calls.
2023-12-16 19:37:21 -06:00
Micah Johnston 1b8c8760c7
Fix warnings on windows (#159) 2023-12-17 01:33:12 +00:00
Micah Johnston b59a67d287
Fix warnings on X11 (#160) 2023-12-17 01:31:06 +00:00
Micah Johnston 92f1a19b81
Fix warnings in example (#158) 2023-12-16 19:29:27 -06:00
Micah Johnston 348bc9fe61
Remove RawWindowHandleWrapper (#156)
RawWindowHandleWrapper is only necessary since GlContext::create takes an `impl
HasRawWindowHandle` argument, but GlContext::create is an internal unsafe API
and it is can just take a RawWindowHandle directly.
2023-12-12 11:47:32 -06:00
Micah Johnston 3ecc88f9fc
Fix warnings on macos (#155) 2023-12-12 17:40:45 +00:00
Micah Johnston df0e3928a2
Remove Window::open_as_if_parented (#153)
The open_as_if_parented method was intended to support the use case of Audio
Unit plugin views. However, the Audio Unit API differs in important ways from
other plugin APIs (such as VST 3 and CLAP) with regard to the lifetime
management of the plugin's NSView, and trying to support both lifetime
management patterns simultaneously has been the source of complexity and bugs.
Thus, rather than implementing Audio Unit NSView management directly in
Baseview, it will be the responsibility of plugin frameworks to implement a
compatibility layer between the Audio Unit API and the Baseview API.
2023-12-12 16:45:12 +00:00
Micah Johnston d08f7afd49
Run rustfmt (#154) 2023-12-12 10:41:03 -06:00
Nick Gideo d8e3b52acd
Merge pull request #149 from ilmai/drag-and-drop-mac
Drag and drop for Mac OS
2023-12-09 16:13:14 -06:00
Nick Gideo dbf4623b6e
Merge branch 'master' into drag-and-drop-mac 2023-12-09 16:12:05 -06:00
Jussi Viiri 8270260382 Working drag and drop for Mac OS 2023-06-20 17:30:30 +03:00
Jussi Viiri 1921d477ff Start implementing drag and drop for Mac 2023-06-19 19:11:39 +03:00
15 changed files with 495 additions and 512 deletions

View file

@ -1,3 +1,3 @@
fn_args_layout = "Compressed"
fn_params_layout = "Compressed"
use_small_heuristics = "Max"
use_field_init_shorthand = true

View file

@ -29,7 +29,7 @@ impl WindowHandler for OpenWindowExample {
#[cfg(target_os = "macos")]
match e {
MouseEvent::ButtonPressed { button, modifiers } => {
MouseEvent::ButtonPressed { .. } => {
copy_to_clipboard(&"This is a test!")
}
_ => (),

View file

@ -157,6 +157,6 @@ pub enum EventStatus {
/// plugin window is in focus.
Ignored,
/// We are prepared to handle the data in the drag and dropping will
/// result in [DropEffect]
/// result in [DropEffect]
AcceptDrop(DropEffect),
}

View file

@ -1,7 +1,7 @@
use std::ffi::c_void;
use std::str::FromStr;
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
use raw_window_handle::RawWindowHandle;
use cocoa::appkit::{
NSOpenGLContext, NSOpenGLContextParameter, NSOpenGLPFAAccelerated, NSOpenGLPFAAlphaSize,
@ -28,10 +28,8 @@ pub struct GlContext {
}
impl GlContext {
pub unsafe fn create(
parent: &impl HasRawWindowHandle, config: GlConfig,
) -> Result<GlContext, GlError> {
let handle = if let RawWindowHandle::AppKit(handle) = parent.raw_window_handle() {
pub unsafe fn create(parent: &RawWindowHandle, config: GlConfig) -> Result<GlContext, GlError> {
let handle = if let RawWindowHandle::AppKit(handle) = parent {
handle
} else {
return Err(GlError::InvalidWindowHandle);

View file

@ -3,7 +3,7 @@ use std::marker::PhantomData;
// On X11 creating the context is a two step process
#[cfg(not(target_os = "linux"))]
use raw_window_handle::HasRawWindowHandle;
use raw_window_handle::RawWindowHandle;
#[cfg(target_os = "windows")]
mod win;
@ -77,7 +77,7 @@ pub struct GlContext {
impl GlContext {
#[cfg(not(target_os = "linux"))]
pub(crate) unsafe fn create(
parent: &impl HasRawWindowHandle, config: GlConfig,
parent: &RawWindowHandle, config: GlConfig,
) -> Result<GlContext, GlError> {
platform::GlContext::create(parent, config)
.map(|context| GlContext { context, phantom: PhantomData })

View file

@ -1,7 +1,7 @@
use std::ffi::{c_void, CString, OsStr};
use std::os::windows::ffi::OsStrExt;
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
use raw_window_handle::RawWindowHandle;
use winapi::shared::minwindef::{HINSTANCE, HMODULE};
use winapi::shared::ntdef::WCHAR;
@ -77,10 +77,8 @@ extern "C" {
}
impl GlContext {
pub unsafe fn create(
parent: &impl HasRawWindowHandle, config: GlConfig,
) -> Result<GlContext, GlError> {
let handle = if let RawWindowHandle::Win32(handle) = parent.raw_window_handle() {
pub unsafe fn create(parent: &RawWindowHandle, config: GlConfig) -> Result<GlContext, GlError> {
let handle = if let RawWindowHandle::Win32(handle) = parent {
handle
} else {
return Err(GlError::InvalidWindowHandle);

View file

@ -18,6 +18,8 @@
//! Conversion of platform keyboard event into cross-platform event.
use std::cell::Cell;
use cocoa::appkit::{NSEvent, NSEventModifierFlags, NSEventType};
use cocoa::base::id;
use cocoa::foundation::NSString;
@ -44,7 +46,7 @@ pub(crate) fn from_nsstring(s: id) -> String {
/// Most of the logic in this module is adapted from Mozilla, and in particular
/// TextInputHandler.mm.
pub(crate) struct KeyboardState {
last_mods: NSEventModifierFlags,
last_mods: Cell<NSEventModifierFlags>,
}
/// Convert a macOS platform key code (keyCode field of NSEvent).
@ -269,11 +271,15 @@ fn is_modifier_code(code: Code) -> bool {
impl KeyboardState {
pub(crate) fn new() -> KeyboardState {
let last_mods = NSEventModifierFlags::empty();
let last_mods = Cell::new(NSEventModifierFlags::empty());
KeyboardState { last_mods }
}
pub(crate) fn process_native_event(&mut self, event: id) -> Option<KeyboardEvent> {
pub(crate) fn last_mods(&self) -> NSEventModifierFlags {
self.last_mods.get()
}
pub(crate) fn process_native_event(&self, event: id) -> Option<KeyboardEvent> {
unsafe {
let event_type = event.eventType();
let key_code = event.keyCode();
@ -288,8 +294,8 @@ impl KeyboardState {
// We use `bits` here because we want to distinguish the
// device dependent bits (when both left and right keys
// may be pressed, for example).
let any_down = raw_mods.bits() & !self.last_mods.bits();
self.last_mods = raw_mods;
let any_down = raw_mods.bits() & !self.last_mods.get().bits();
self.last_mods.set(raw_mods);
if is_modifier_code(code) {
if any_down == 0 {
KeyState::Up

View file

@ -3,3 +3,15 @@ mod view;
mod window;
pub use window::*;
#[allow(non_upper_case_globals)]
mod consts {
use cocoa::foundation::NSUInteger;
pub const NSDragOperationNone: NSUInteger = 0;
pub const NSDragOperationCopy: NSUInteger = 1;
pub const NSDragOperationLink: NSUInteger = 2;
pub const NSDragOperationGeneric: NSUInteger = 4;
pub const NSDragOperationMove: NSUInteger = 16;
}
use consts::*;

View file

@ -1,8 +1,8 @@
use std::ffi::c_void;
use cocoa::appkit::{NSEvent, NSView, NSWindow};
use cocoa::appkit::{NSEvent, NSFilenamesPboardType, NSView, NSWindow};
use cocoa::base::{id, nil, BOOL, NO, YES};
use cocoa::foundation::{NSArray, NSPoint, NSRect, NSSize};
use cocoa::foundation::{NSArray, NSPoint, NSRect, NSSize, NSUInteger};
use objc::{
class,
@ -15,12 +15,16 @@ use uuid::Uuid;
use crate::MouseEvent::{ButtonPressed, ButtonReleased};
use crate::{
Event, EventStatus, MouseButton, MouseEvent, Point, ScrollDelta, Size, WindowEvent, WindowInfo,
WindowOpenOptions,
DropData, DropEffect, Event, EventStatus, MouseButton, MouseEvent, Point, ScrollDelta, Size,
WindowEvent, WindowInfo, WindowOpenOptions,
};
use super::keyboard::make_modifiers;
use super::keyboard::{from_nsstring, make_modifiers};
use super::window::WindowState;
use super::{
NSDragOperationCopy, NSDragOperationGeneric, NSDragOperationLink, NSDragOperationMove,
NSDragOperationNone,
};
/// Name of the field used to store the `WindowState` pointer.
pub(super) const BASEVIEW_STATE_IVAR: &str = "baseview_state";
@ -29,9 +33,7 @@ macro_rules! add_simple_mouse_class_method {
($class:ident, $sel:ident, $event:expr) => {
#[allow(non_snake_case)]
extern "C" fn $sel(this: &Object, _: Sel, _: id){
let state: &mut WindowState = unsafe {
WindowState::from_field(this)
};
let state = unsafe { WindowState::from_view(this) };
state.trigger_event(Event::Mouse($event));
}
@ -49,9 +51,7 @@ macro_rules! add_mouse_button_class_method {
($class:ident, $sel:ident, $event_ty:ident, $button:expr) => {
#[allow(non_snake_case)]
extern "C" fn $sel(this: &Object, _: Sel, event: id){
let state: &mut WindowState = unsafe {
WindowState::from_field(this)
};
let state = unsafe { WindowState::from_view(this) };
let modifiers = unsafe { NSEvent::modifierFlags(event) };
@ -72,9 +72,7 @@ macro_rules! add_simple_keyboard_class_method {
($class:ident, $sel:ident) => {
#[allow(non_snake_case)]
extern "C" fn $sel(this: &Object, _: Sel, event: id){
let state: &mut WindowState = unsafe {
WindowState::from_field(this)
};
let state = unsafe { WindowState::from_view(this) };
if let Some(key_event) = state.process_native_key_event(event){
let status = state.trigger_event(Event::Keyboard(key_event));
@ -105,6 +103,11 @@ pub(super) unsafe fn create_view(window_options: &WindowOpenOptions) -> id {
view.initWithFrame_(NSRect::new(NSPoint::new(0., 0.), NSSize::new(size.width, size.height)));
let _: id = msg_send![
view,
registerForDraggedTypes: NSArray::arrayWithObjects(nil, &[NSFilenamesPboardType])
];
view
}
@ -131,7 +134,10 @@ unsafe fn create_view_class() -> &'static Class {
accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL,
);
class.add_method(sel!(release), release as extern "C" fn(&mut Object, Sel));
class.add_method(
sel!(windowShouldClose:),
window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
);
class.add_method(sel!(dealloc), dealloc as extern "C" fn(&mut Object, Sel));
class.add_method(
sel!(viewWillMoveToWindow:),
@ -154,6 +160,24 @@ unsafe fn create_view_class() -> &'static Class {
view_did_change_backing_properties as extern "C" fn(&Object, Sel, id),
);
class.add_method(
sel!(draggingEntered:),
dragging_entered as extern "C" fn(&Object, Sel, id) -> NSUInteger,
);
class.add_method(
sel!(prepareForDragOperation:),
prepare_for_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL,
);
class.add_method(
sel!(performDragOperation:),
perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL,
);
class.add_method(
sel!(draggingUpdated:),
dragging_updated as extern "C" fn(&Object, Sel, id) -> NSUInteger,
);
class.add_method(sel!(draggingExited:), dragging_exited as extern "C" fn(&Object, Sel, id));
add_mouse_button_class_method!(class, mouseDown, ButtonPressed, MouseButton::Left);
add_mouse_button_class_method!(class, mouseUp, ButtonReleased, MouseButton::Left);
add_mouse_button_class_method!(class, rightMouseDown, ButtonPressed, MouseButton::Right);
@ -184,37 +208,14 @@ extern "C" fn accepts_first_mouse(_this: &Object, _sel: Sel, _event: id) -> BOOL
YES
}
extern "C" fn release(this: &mut Object, _sel: Sel) {
// Hack for breaking circular references. We store the value of retainCount
// after build(), and then when retainCount drops back to that value, we
// drop the WindowState, hoping that any circular references it holds back
// to the NSView (e.g. wgpu surfaces) get released.
//
// This is definitely broken, since it can be thwarted by e.g. creating a
// wgpu surface at some point after build() (which will mean the NSView
// never gets dealloced) or dropping a wgpu surface at some point before
// drop() (which will mean the WindowState gets dropped early).
//
// TODO: Find a better solution for circular references.
extern "C" fn window_should_close(this: &Object, _: Sel, _sender: id) -> BOOL {
let state = unsafe { WindowState::from_view(this) };
unsafe {
let retain_count: usize = msg_send![this, retainCount];
state.trigger_event(Event::Window(WindowEvent::WillClose));
let state_ptr: *mut c_void = *this.get_ivar(BASEVIEW_STATE_IVAR);
state.window_inner.close();
if !state_ptr.is_null() {
let retain_count_after_build = WindowState::from_field(this).retain_count_after_build;
if retain_count <= retain_count_after_build {
WindowState::stop_and_free(this);
}
}
}
unsafe {
let superclass = msg_send![this, superclass];
let () = msg_send![super(this, superclass), release];
}
NO
}
extern "C" fn dealloc(this: &mut Object, _sel: Sel) {
@ -236,7 +237,7 @@ extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel, _: id) {
let scale_factor: f64 =
if ns_window.is_null() { 1.0 } else { NSWindow::backingScaleFactor(ns_window) };
let state: &mut WindowState = WindowState::from_field(this);
let state = WindowState::from_view(this);
let bounds: NSRect = msg_send![this, bounds];
@ -245,10 +246,12 @@ extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel, _: id) {
scale_factor,
);
let window_info = state.window_info.get();
// Only send the event when the window's size has actually changed to be in line with the
// other platform implementations
if new_window_info.physical_size() != state.window_info.physical_size() {
state.window_info = new_window_info;
if new_window_info.physical_size() != window_info.physical_size() {
state.window_info.set(new_window_info);
state.trigger_event(Event::Window(WindowEvent::Resized(new_window_info)));
}
}
@ -292,8 +295,6 @@ extern "C" fn view_will_move_to_window(this: &Object, _self: Sel, new_window: id
let tracking_areas: *mut Object = msg_send![this, trackingAreas];
let tracking_area_count = NSArray::count(tracking_areas);
let _: () = msg_send![class!(NSEvent), setMouseCoalescingEnabled: NO];
if new_window == nil {
if tracking_area_count != 0 {
let tracking_area = NSArray::objectAtIndex(tracking_areas, 0);
@ -334,7 +335,7 @@ extern "C" fn update_tracking_areas(this: &Object, _self: Sel, _: id) {
}
extern "C" fn mouse_moved(this: &Object, _sel: Sel, event: id) {
let state: &mut WindowState = unsafe { WindowState::from_field(this) };
let state = unsafe { WindowState::from_view(this) };
let point: NSPoint = unsafe {
let point = NSEvent::locationInWindow(event);
@ -352,7 +353,7 @@ extern "C" fn mouse_moved(this: &Object, _sel: Sel, event: id) {
}
extern "C" fn scroll_wheel(this: &Object, _: Sel, event: id) {
let state: &mut WindowState = unsafe { WindowState::from_field(this) };
let state = unsafe { WindowState::from_view(this) };
let delta = unsafe {
let x = NSEvent::scrollingDeltaX(event) as f32;
@ -372,3 +373,101 @@ extern "C" fn scroll_wheel(this: &Object, _: Sel, event: id) {
modifiers: make_modifiers(modifiers),
}));
}
fn get_drag_position(sender: id) -> Point {
let point: NSPoint = unsafe { msg_send![sender, draggingLocation] };
Point::new(point.x, point.y)
}
fn get_drop_data(sender: id) -> DropData {
if sender == nil {
return DropData::None;
}
unsafe {
let pasteboard: id = msg_send![sender, draggingPasteboard];
let file_list: id = msg_send![pasteboard, propertyListForType: NSFilenamesPboardType];
if file_list == nil {
return DropData::None;
}
let mut files = vec![];
for i in 0..NSArray::count(file_list) {
let data = NSArray::objectAtIndex(file_list, i);
files.push(from_nsstring(data).into());
}
DropData::Files(files)
}
}
fn on_event(window_state: &WindowState, event: MouseEvent) -> NSUInteger {
let event_status = window_state.trigger_event(Event::Mouse(event));
match event_status {
EventStatus::AcceptDrop(DropEffect::Copy) => NSDragOperationCopy,
EventStatus::AcceptDrop(DropEffect::Move) => NSDragOperationMove,
EventStatus::AcceptDrop(DropEffect::Link) => NSDragOperationLink,
EventStatus::AcceptDrop(DropEffect::Scroll) => NSDragOperationGeneric,
_ => NSDragOperationNone,
}
}
extern "C" fn dragging_entered(this: &Object, _sel: Sel, sender: id) -> NSUInteger {
let state = unsafe { WindowState::from_view(this) };
let modifiers = state.keyboard_state().last_mods();
let drop_data = get_drop_data(sender);
let event = MouseEvent::DragEntered {
position: get_drag_position(sender),
modifiers: make_modifiers(modifiers),
data: drop_data,
};
on_event(&state, event)
}
extern "C" fn dragging_updated(this: &Object, _sel: Sel, sender: id) -> NSUInteger {
let state = unsafe { WindowState::from_view(this) };
let modifiers = state.keyboard_state().last_mods();
let drop_data = get_drop_data(sender);
let event = MouseEvent::DragMoved {
position: get_drag_position(sender),
modifiers: make_modifiers(modifiers),
data: drop_data,
};
on_event(&state, event)
}
extern "C" fn prepare_for_drag_operation(_this: &Object, _sel: Sel, _sender: id) -> BOOL {
// Always accept drag operation if we get this far
// This function won't be called unless dragging_entered/updated
// has returned an acceptable operation
YES
}
extern "C" fn perform_drag_operation(this: &Object, _sel: Sel, sender: id) -> BOOL {
let state = unsafe { WindowState::from_view(this) };
let modifiers = state.keyboard_state().last_mods();
let drop_data = get_drop_data(sender);
let event = MouseEvent::DragDropped {
position: get_drag_position(sender),
modifiers: make_modifiers(modifiers),
data: drop_data,
};
let event_status = state.trigger_event(Event::Mouse(event));
match event_status {
EventStatus::AcceptDrop(_) => YES,
_ => NO,
}
}
extern "C" fn dragging_exited(this: &Object, _sel: Sel, _sender: id) {
let state = unsafe { WindowState::from_view(this) };
on_event(&state, MouseEvent::DragLeft);
}

View file

@ -1,8 +1,7 @@
use std::cell::{Cell, RefCell};
use std::ffi::c_void;
use std::marker::PhantomData;
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::rc::Rc;
use cocoa::appkit::{
NSApp, NSApplication, NSApplicationActivationPolicyRegular, NSBackingStoreBuffered,
@ -23,7 +22,7 @@ use raw_window_handle::{
};
use crate::{
Event, EventStatus, Size, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions,
Event, EventStatus, MouseCursor, Size, WindowHandler, WindowInfo, WindowOpenOptions,
WindowScalePolicy,
};
@ -31,93 +30,99 @@ use super::keyboard::KeyboardState;
use super::view::{create_view, BASEVIEW_STATE_IVAR};
#[cfg(feature = "opengl")]
use crate::{
gl::{GlConfig, GlContext},
window::RawWindowHandleWrapper,
};
use crate::gl::{GlConfig, GlContext};
pub struct WindowHandle {
raw_window_handle: Option<RawWindowHandle>,
close_requested: Arc<AtomicBool>,
is_open: Arc<AtomicBool>,
// Ensure handle is !Send
_phantom: PhantomData<*mut ()>,
state: Rc<WindowState>,
}
impl WindowHandle {
pub fn close(&mut self) {
if self.raw_window_handle.take().is_some() {
self.close_requested.store(true, Ordering::Relaxed);
}
self.state.window_inner.close();
}
pub fn is_open(&self) -> bool {
self.is_open.load(Ordering::Relaxed)
self.state.window_inner.open.get()
}
}
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;
self.state.window_inner.raw_window_handle()
}
}
pub(super) struct WindowInner {
open: Cell<bool>,
/// Only set if we created the parent window, i.e. we are running in
/// parentless mode
ns_app: Cell<Option<id>>,
/// Only set if we created the parent window, i.e. we are running in
/// parentless mode
ns_window: Cell<Option<id>>,
/// Our subclassed NSView
ns_view: id,
#[cfg(feature = "opengl")]
gl_context: Option<GlContext>,
}
impl WindowInner {
pub(super) fn close(&self) {
if self.open.get() {
self.open.set(false);
unsafe {
// Take back ownership of the NSView's Rc<WindowState>
let state_ptr: *const c_void = *(*self.ns_view).get_ivar(BASEVIEW_STATE_IVAR);
let window_state = Rc::from_raw(state_ptr as *mut WindowState);
// Cancel the frame timer
if let Some(frame_timer) = window_state.frame_timer.take() {
CFRunLoop::get_current().remove_timer(&frame_timer, kCFRunLoopDefaultMode);
}
drop(window_state);
// Close the window if in non-parented mode
if let Some(ns_window) = self.ns_window.take() {
ns_window.close();
}
// Ensure that the NSView is detached from the parent window
self.ns_view.removeFromSuperview();
let () = msg_send![self.ns_view as id, release];
// If in non-parented mode, we want to also quit the app altogether
let app = self.ns_app.take();
if let Some(app) = app {
app.stop_(app);
}
}
}
}
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;
let mut handle = AppKitWindowHandle::empty();
handle.ns_window = ns_window;
handle.ns_view = self.ns_view as *mut c_void;
return RawWindowHandle::AppKit(handle);
}
RawWindowHandle::AppKit(AppKitWindowHandle::empty())
}
}
struct ParentHandle {
_close_requested: Arc<AtomicBool>,
is_open: Arc<AtomicBool>,
pub struct Window<'a> {
inner: &'a WindowInner,
}
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<id>,
/// Only set if we created the parent window, i.e. we are running in
/// parentless mode
ns_window: Option<id>,
/// Our subclassed NSView
ns_view: id,
close_requested: bool,
#[cfg(feature = "opengl")]
gl_context: Option<GlContext>,
}
impl Window {
impl<'a> Window<'a> {
pub fn open_parented<P, H, B>(parent: &P, options: WindowOpenOptions, build: B) -> WindowHandle
where
P: HasRawWindowHandle,
@ -142,11 +147,11 @@ impl Window {
let ns_view = unsafe { create_view(&options) };
let window = Window {
ns_app: None,
ns_window: None,
let window_inner = WindowInner {
open: Cell::new(true),
ns_app: Cell::new(None),
ns_window: Cell::new(None),
ns_view,
close_requested: false,
#[cfg(feature = "opengl")]
gl_context: options
@ -154,11 +159,10 @@ impl Window {
.map(|gl_config| Self::create_gl_context(None, ns_view, gl_config)),
};
let window_handle = Self::init(true, window, window_info, build);
let window_handle = Self::init(window_inner, window_info, build);
unsafe {
let _: id = msg_send![handle.ns_view as *mut Object, addSubview: ns_view];
let () = msg_send![ns_view as id, release];
let () = msg_send![pool, drain];
}
@ -166,44 +170,6 @@ impl Window {
window_handle
}
pub fn open_as_if_parented<H, B>(options: WindowOpenOptions, build: B) -> WindowHandle
where
H: WindowHandler + 'static,
B: FnOnce(&mut crate::Window) -> H,
B: Send + 'static,
{
let pool = unsafe { NSAutoreleasePool::new(nil) };
let scaling = match options.scale {
WindowScalePolicy::ScaleFactor(scale) => scale,
WindowScalePolicy::SystemScaleFactor => 1.0,
};
let window_info = WindowInfo::from_logical_size(options.size, scaling);
let ns_view = unsafe { create_view(&options) };
let window = Window {
ns_app: None,
ns_window: None,
ns_view,
close_requested: false,
#[cfg(feature = "opengl")]
gl_context: options
.gl_config
.map(|gl_config| Self::create_gl_context(None, ns_view, gl_config)),
};
let window_handle = Self::init(true, window, window_info, build);
unsafe {
let () = msg_send![pool, drain];
}
window_handle
}
pub fn open_blocking<H, B>(options: WindowOpenOptions, build: B)
where
H: WindowHandler + 'static,
@ -256,11 +222,11 @@ impl Window {
let ns_view = unsafe { create_view(&options) };
let window = Window {
ns_app: Some(app),
ns_window: Some(ns_window),
let window_inner = WindowInner {
open: Cell::new(true),
ns_app: Cell::new(Some(app)),
ns_window: Cell::new(Some(ns_window)),
ns_view,
close_requested: false,
#[cfg(feature = "opengl")]
gl_context: options
@ -268,82 +234,84 @@ impl Window {
.map(|gl_config| Self::create_gl_context(Some(ns_window), ns_view, gl_config)),
};
let _ = Self::init(false, window, window_info, build);
let _ = Self::init(window_inner, window_info, build);
unsafe {
ns_window.setContentView_(ns_view);
ns_window.setDelegate_(ns_view);
let () = msg_send![ns_view as id, release];
let () = msg_send![pool, drain];
app.run();
}
}
fn init<H, B>(
parented: bool, mut window: Window, window_info: WindowInfo, build: B,
) -> WindowHandle
fn init<H, B>(window_inner: WindowInner, window_info: WindowInfo, build: B) -> WindowHandle
where
H: WindowHandler + 'static,
B: FnOnce(&mut crate::Window) -> H,
B: Send + 'static,
{
let window_handler = Box::new(build(&mut crate::Window::new(&mut window)));
let mut window = crate::Window::new(Window { inner: &window_inner });
let window_handler = Box::new(build(&mut window));
let (parent_handle, window_handle) = ParentHandle::new(window.raw_window_handle());
let parent_handle = if parented { Some(parent_handle) } else { None };
let ns_view = window_inner.ns_view;
let retain_count_after_build: usize = unsafe { msg_send![window.ns_view, retainCount] };
let window_state_ptr = Box::into_raw(Box::new(WindowState {
window,
window_handler,
let window_state = Rc::new(WindowState {
window_inner,
window_handler: RefCell::new(window_handler),
keyboard_state: KeyboardState::new(),
frame_timer: None,
retain_count_after_build,
window_info,
_parent_handle: parent_handle,
}));
frame_timer: Cell::new(None),
window_info: Cell::new(window_info),
});
let window_state_ptr = Rc::into_raw(Rc::clone(&window_state));
unsafe {
(*(*window_state_ptr).window.ns_view)
.set_ivar(BASEVIEW_STATE_IVAR, window_state_ptr as *mut c_void);
(*ns_view).set_ivar(BASEVIEW_STATE_IVAR, window_state_ptr as *const c_void);
WindowState::setup_timer(window_state_ptr);
}
window_handle
WindowHandle { state: window_state }
}
pub fn close(&mut self) {
self.close_requested = true;
self.inner.close();
}
pub fn resize(&mut self, size: Size) {
// 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());
if self.inner.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.ns_view, size) };
unsafe {
let _: () = msg_send![self.ns_view, setNeedsDisplay: YES];
}
unsafe { NSView::setFrameSize(self.inner.ns_view, size) };
unsafe {
let _: () = msg_send![self.inner.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.gl_context {
gl_context.resize(size);
}
// 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 {
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.ns_window {
unsafe { NSWindow::setContentSize_(ns_window, 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() {
unsafe { NSWindow::setContentSize_(ns_window, size) };
}
}
}
pub fn set_mouse_cursor(&mut self, _mouse_cursor: MouseCursor) {
todo!()
}
#[cfg(feature = "opengl")]
pub fn gl_context(&self) -> Option<&GlContext> {
self.gl_context.as_ref()
self.inner.gl_context.as_ref()
}
#[cfg(feature = "opengl")]
@ -351,82 +319,59 @@ impl Window {
let mut handle = AppKitWindowHandle::empty();
handle.ns_window = ns_window.unwrap_or(ptr::null_mut()) as *mut c_void;
handle.ns_view = ns_view as *mut c_void;
let handle = RawWindowHandleWrapper { handle: RawWindowHandle::AppKit(handle) };
let handle = RawWindowHandle::AppKit(handle);
unsafe { GlContext::create(&handle, config).expect("Could not create OpenGL context") }
}
}
pub(super) struct WindowState {
window: Window,
window_handler: Box<dyn WindowHandler>,
pub(super) window_inner: WindowInner,
window_handler: RefCell<Box<dyn WindowHandler>>,
keyboard_state: KeyboardState,
frame_timer: Option<CFRunLoopTimer>,
_parent_handle: Option<ParentHandle>,
pub retain_count_after_build: usize,
frame_timer: Cell<Option<CFRunLoopTimer>>,
/// The last known window info for this window.
pub window_info: WindowInfo,
pub window_info: Cell<WindowInfo>,
}
impl WindowState {
/// Returns a mutable reference to a WindowState from an Objective-C field
/// Gets the `WindowState` held by a given `NSView`.
///
/// Don't use this to create two simulataneous references to a single
/// WindowState. Apparently, macOS blocks for the duration of an event,
/// callback, meaning that this shouldn't be a problem in practice.
pub(super) unsafe fn from_field(obj: &Object) -> &mut Self {
let state_ptr: *mut c_void = *obj.get_ivar(BASEVIEW_STATE_IVAR);
/// This method returns a cloned `Rc<WindowState>` rather than just a `&WindowState`, since the
/// original `Rc<WindowState>` owned by the `NSView` can be dropped at any time
/// (including during an event handler).
pub(super) unsafe fn from_view(view: &Object) -> Rc<WindowState> {
let state_ptr: *const c_void = *view.get_ivar(BASEVIEW_STATE_IVAR);
&mut *(state_ptr as *mut Self)
let state_rc = Rc::from_raw(state_ptr as *const WindowState);
let state = Rc::clone(&state_rc);
let _ = Rc::into_raw(state_rc);
state
}
pub(super) fn trigger_event(&mut self, event: Event) -> EventStatus {
self.window_handler.on_event(&mut crate::Window::new(&mut self.window), event)
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)
}
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 trigger_frame(&self) {
let mut window = crate::Window::new(Window { inner: &self.window_inner });
self.window_handler.borrow_mut().on_frame(&mut window);
}
pub(super) fn process_native_key_event(&mut self, event: *mut Object) -> Option<KeyboardEvent> {
pub(super) fn keyboard_state(&self) -> &KeyboardState {
&self.keyboard_state
}
pub(super) fn process_native_key_event(&self, event: *mut Object) -> Option<KeyboardEvent> {
self.keyboard_state.process_native_event(event)
}
/// Don't call until WindowState pointer is stored in view
unsafe fn setup_timer(window_state_ptr: *mut WindowState) {
unsafe fn setup_timer(window_state_ptr: *const WindowState) {
extern "C" fn timer_callback(_: *mut __CFRunLoopTimer, window_state_ptr: *mut c_void) {
unsafe {
let window_state = &mut *(window_state_ptr as *mut WindowState);
let window_state = &*(window_state_ptr as *const WindowState);
window_state.trigger_frame();
}
@ -444,50 +389,17 @@ impl WindowState {
CFRunLoop::get_current().add_timer(&timer, kCFRunLoopDefaultMode);
let window_state = &mut *(window_state_ptr);
window_state.frame_timer = Some(timer);
}
/// Call when freeing view
pub(super) unsafe fn stop_and_free(ns_view_obj: &mut Object) {
let state_ptr: *mut c_void = *ns_view_obj.get_ivar(BASEVIEW_STATE_IVAR);
// Take back ownership of Box<WindowState> so that it gets dropped
// when it goes out of scope
let mut window_state = Box::from_raw(state_ptr as *mut WindowState);
if let Some(frame_timer) = window_state.frame_timer.take() {
CFRunLoop::get_current().remove_timer(&frame_timer, kCFRunLoopDefaultMode);
}
// Clear ivar before triggering WindowEvent::WillClose. Otherwise, if the
// handler of the event causes another call to release, this function could be
// called again, leading to a double free.
ns_view_obj.set_ivar(BASEVIEW_STATE_IVAR, ptr::null() as *const c_void);
window_state.trigger_event(Event::Window(WindowEvent::WillClose));
// If in non-parented mode, we want to also quit the app altogether
if let Some(app) = window_state.window.ns_app.take() {
app.stop_(app);
}
(*window_state_ptr).frame_timer.set(Some(timer));
}
}
unsafe impl HasRawWindowHandle for Window {
unsafe impl<'a> HasRawWindowHandle for Window<'a> {
fn raw_window_handle(&self) -> RawWindowHandle {
let ns_window = self.ns_window.unwrap_or(ptr::null_mut()) as *mut c_void;
let mut handle = AppKitWindowHandle::empty();
handle.ns_window = ns_window;
handle.ns_view = self.ns_view as *mut c_void;
RawWindowHandle::AppKit(handle)
self.inner.raw_window_handle()
}
}
unsafe impl HasRawDisplayHandle for Window {
unsafe impl<'a> HasRawDisplayHandle for Window<'a> {
fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::AppKit(AppKitDisplayHandle::empty())
}

View file

@ -2,31 +2,51 @@ use std::ffi::OsString;
use std::mem::transmute;
use std::os::windows::prelude::OsStringExt;
use std::ptr::null_mut;
use std::rc::{Weak, Rc};
use std::rc::{Rc, Weak};
use winapi::Interface;
use winapi::shared::guiddef::{REFIID, IsEqualIID};
use winapi::shared::guiddef::{IsEqualIID, REFIID};
use winapi::shared::minwindef::{DWORD, WPARAM};
use winapi::shared::ntdef::{HRESULT, ULONG};
use winapi::shared::windef::POINTL;
use winapi::shared::winerror::{S_OK, E_NOINTERFACE, E_UNEXPECTED};
use winapi::shared::winerror::{E_NOINTERFACE, E_UNEXPECTED, S_OK};
use winapi::shared::wtypes::DVASPECT_CONTENT;
use winapi::um::objidl::{IDataObject, FORMATETC, TYMED_HGLOBAL, STGMEDIUM};
use winapi::um::oleidl::{IDropTarget, IDropTargetVtbl, DROPEFFECT_COPY, DROPEFFECT_MOVE, DROPEFFECT_LINK, DROPEFFECT_SCROLL, DROPEFFECT_NONE};
use winapi::um::objidl::{IDataObject, FORMATETC, STGMEDIUM, TYMED_HGLOBAL};
use winapi::um::oleidl::{
IDropTarget, IDropTargetVtbl, DROPEFFECT_COPY, DROPEFFECT_LINK, DROPEFFECT_MOVE,
DROPEFFECT_NONE, DROPEFFECT_SCROLL,
};
use winapi::um::shellapi::DragQueryFileW;
use winapi::um::unknwnbase::{IUnknownVtbl, IUnknown};
use winapi::um::unknwnbase::{IUnknown, IUnknownVtbl};
use winapi::um::winuser::CF_HDROP;
use winapi::Interface;
use crate::{Point, DropData, MouseEvent, Event, EventStatus, DropEffect, PhyPoint};
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`
const DRAG_ENTER_PTR: unsafe extern "system" fn(this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drag_enter;
const DRAG_OVER_PTR: unsafe extern "system" fn(this: *mut IDropTarget, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drag_over;
const DROP_PTR: unsafe extern "system" fn(this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drop;
const DRAG_ENTER_PTR: unsafe extern "system" fn(
this: *mut IDropTarget,
pDataObj: *const IDataObject,
grfKeyState: DWORD,
pt: POINTL,
pdwEffect: *mut DWORD,
) -> HRESULT = DropTarget::drag_enter;
const DRAG_OVER_PTR: unsafe extern "system" fn(
this: *mut IDropTarget,
grfKeyState: DWORD,
pt: POINTL,
pdwEffect: *mut DWORD,
) -> HRESULT = DropTarget::drag_over;
const DROP_PTR: unsafe extern "system" fn(
this: *mut IDropTarget,
pDataObj: *const IDataObject,
grfKeyState: DWORD,
pt: POINTL,
pdwEffect: *mut DWORD,
) -> HRESULT = DropTarget::drop;
const DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl {
parent: IUnknownVtbl {
QueryInterface: DropTarget::query_interface,
@ -63,17 +83,18 @@ impl DropTarget {
}
}
#[allow(non_snake_case)]
fn on_event(&self, pdwEffect: Option<*mut DWORD>, event: MouseEvent) {
let Some(window_state) = self.window_state.upgrade() else {
return;
};
unsafe {
let mut window = window_state.create_window();
let mut window = crate::Window::new(&mut window);
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().as_mut().unwrap().on_event(&mut window, event);
if let Some(pdwEffect) = pdwEffect {
match event_status {
@ -82,8 +103,8 @@ impl DropTarget {
EventStatus::AcceptDrop(DropEffect::Link) => *pdwEffect = DROPEFFECT_LINK,
EventStatus::AcceptDrop(DropEffect::Scroll) => *pdwEffect = DROPEFFECT_SCROLL,
_ => *pdwEffect = DROPEFFECT_NONE,
}
}
}
}
}
}
@ -105,11 +126,7 @@ impl DropTarget {
tymed: TYMED_HGLOBAL,
};
let mut medium = STGMEDIUM {
tymed: 0,
u: null_mut(),
pUnkForRelease: null_mut(),
};
let mut medium = STGMEDIUM { tymed: 0, u: null_mut(), pUnkForRelease: null_mut() };
unsafe {
let hresult = data_object.GetData(&format, &mut medium);
@ -119,13 +136,13 @@ impl DropTarget {
}
let hdrop = transmute((*medium.u).hGlobal());
let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, null_mut(), 0);
if item_count == 0 {
self.drop_data = DropData::None;
return;
}
let mut paths = Vec::with_capacity(item_count as usize);
for i in 0..item_count {
@ -133,7 +150,12 @@ impl DropTarget {
let buffer_size = characters as usize + 1;
let mut buffer = Vec::<u16>::with_capacity(buffer_size);
DragQueryFileW(hdrop, i, transmute(buffer.spare_capacity_mut().as_mut_ptr()), buffer_size as u32);
DragQueryFileW(
hdrop,
i,
transmute(buffer.spare_capacity_mut().as_mut_ptr()),
buffer_size as u32,
);
buffer.set_len(buffer_size);
paths.push(OsString::from_wide(&buffer[..characters as usize]).into())
@ -143,21 +165,19 @@ impl DropTarget {
}
}
#[allow(non_snake_case)]
unsafe extern "system" fn query_interface(
this: *mut IUnknown,
riid: REFIID,
ppvObject: *mut *mut winapi::ctypes::c_void,
) -> HRESULT
{
if IsEqualIID(&*riid, &IUnknown::uuidof()) || IsEqualIID(&*riid, &IDropTarget::uuidof()){
this: *mut IUnknown, riid: REFIID, ppvObject: *mut *mut winapi::ctypes::c_void,
) -> HRESULT {
if IsEqualIID(&*riid, &IUnknown::uuidof()) || IsEqualIID(&*riid, &IDropTarget::uuidof()) {
Self::add_ref(this);
*ppvObject = this as *mut winapi::ctypes::c_void;
return S_OK;
}
return E_NOINTERFACE;
}
unsafe extern "system" fn add_ref(this: *mut IUnknown) -> ULONG {
let arc = Rc::from_raw(this);
let result = Rc::strong_count(&arc) + 1;
@ -167,7 +187,7 @@ impl DropTarget {
result as ULONG
}
unsafe extern "system" fn release(this: *mut IUnknown) -> ULONG {
let arc = Rc::from_raw(this);
let result = Rc::strong_count(&arc) - 1;
@ -177,21 +197,19 @@ impl DropTarget {
result as ULONG
}
#[allow(non_snake_case)]
unsafe extern "system" fn drag_enter(
this: *mut IDropTarget,
pDataObj: *const IDataObject,
grfKeyState: DWORD,
pt: POINTL,
this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, pt: POINTL,
pdwEffect: *mut DWORD,
) -> HRESULT
{
) -> HRESULT {
let drop_target = &mut *(this as *mut DropTarget);
let Some(window_state) = drop_target.window_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().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM);
drop_target.parse_coordinates(pt);
drop_target.parse_drop_data(&*pDataObj);
@ -205,20 +223,18 @@ impl DropTarget {
drop_target.on_event(Some(pdwEffect), event);
S_OK
}
#[allow(non_snake_case)]
unsafe extern "system" fn drag_over(
this: *mut IDropTarget,
grfKeyState: DWORD,
pt: POINTL,
pdwEffect: *mut DWORD,
) -> HRESULT
{
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 {
return E_UNEXPECTED;
};
let modifiers = window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM);
let modifiers =
window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM);
drop_target.parse_coordinates(pt);
@ -231,27 +247,25 @@ impl DropTarget {
drop_target.on_event(Some(pdwEffect), event);
S_OK
}
unsafe extern "system" fn drag_leave(this: *mut IDropTarget) -> HRESULT {
let drop_target = &mut *(this as *mut DropTarget);
drop_target.on_event(None, MouseEvent::DragLeft);
S_OK
}
#[allow(non_snake_case)]
unsafe extern "system" fn drop(
this: *mut IDropTarget,
pDataObj: *const IDataObject,
grfKeyState: DWORD,
pt: POINTL,
this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, pt: POINTL,
pdwEffect: *mut DWORD,
) -> HRESULT
{
) -> HRESULT {
let drop_target = &mut *(this as *mut DropTarget);
let Some(window_state) = drop_target.window_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().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM);
drop_target.parse_coordinates(pt);
drop_target.parse_drop_data(&*pDataObj);

View file

@ -2,7 +2,7 @@ use winapi::shared::guiddef::GUID;
use winapi::shared::minwindef::{ATOM, FALSE, LPARAM, LRESULT, UINT, WPARAM};
use winapi::shared::windef::{HWND, RECT};
use winapi::um::combaseapi::CoCreateGuid;
use winapi::um::ole2::{RegisterDragDrop, OleInitialize, RevokeDragDrop};
use winapi::um::ole2::{OleInitialize, RegisterDragDrop, RevokeDragDrop};
use winapi::um::oleidl::LPDROPTARGET;
use winapi::um::winuser::{
AdjustWindowRectEx, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW,
@ -18,10 +18,9 @@ use winapi::um::winuser::{
XBUTTON1, XBUTTON2,
};
use std::cell::{Cell, RefCell, Ref, RefMut};
use std::cell::{Cell, Ref, RefCell, RefMut};
use std::collections::VecDeque;
use std::ffi::{c_void, OsStr};
use std::marker::PhantomData;
use std::os::windows::ffi::OsStrExt;
use std::ptr::null_mut;
use std::rc::Rc;
@ -34,7 +33,7 @@ use raw_window_handle::{
const BV_WINDOW_MUST_CLOSE: UINT = WM_USER + 1;
use crate::{
Event, MouseButton, MouseEvent, PhyPoint, PhySize, ScrollDelta, Size, WindowEvent,
Event, MouseButton, MouseCursor, MouseEvent, PhyPoint, PhySize, ScrollDelta, Size, WindowEvent,
WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy,
};
@ -42,7 +41,7 @@ use super::drop_target::DropTarget;
use super::keyboard::KeyboardState;
#[cfg(feature = "opengl")]
use crate::{gl::GlContext, window::RawWindowHandleWrapper};
use crate::gl::GlContext;
unsafe fn generate_guid() -> String {
let mut guid: GUID = std::mem::zeroed();
@ -68,9 +67,6 @@ const WIN_FRAME_TIMER: usize = 4242;
pub struct WindowHandle {
hwnd: Option<HWND>,
is_open: Rc<Cell<bool>>,
// Ensure handle is !Send
_phantom: PhantomData<*mut ()>,
}
impl WindowHandle {
@ -108,11 +104,7 @@ 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),
_phantom: PhantomData::default(),
};
let handle = WindowHandle { hwnd: Some(hwnd), is_open: Rc::clone(&is_open) };
(Self { is_open }, handle)
}
@ -176,8 +168,7 @@ unsafe fn wnd_proc_inner(
) -> Option<LRESULT> {
match msg {
WM_MOUSEMOVE => {
let mut window = window_state.create_window();
let mut window = crate::Window::new(&mut window);
let mut window = crate::Window::new(window_state.create_window());
let x = (lparam & 0xFFFF) as i16 as i32;
let y = ((lparam >> 16) & 0xFFFF) as i16 as i32;
@ -197,8 +188,7 @@ unsafe fn wnd_proc_inner(
Some(0)
}
WM_MOUSEWHEEL | WM_MOUSEHWHEEL => {
let mut window = window_state.create_window();
let mut window = crate::Window::new(&mut window);
let mut window = crate::Window::new(window_state.create_window());
let value = (wparam >> 16) as i16;
let value = value as i32;
@ -222,8 +212,7 @@ unsafe fn wnd_proc_inner(
}
WM_LBUTTONDOWN | WM_LBUTTONUP | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_RBUTTONDOWN
| WM_RBUTTONUP | WM_XBUTTONDOWN | WM_XBUTTONUP => {
let mut window = window_state.create_window();
let mut window = crate::Window::new(&mut window);
let mut window = crate::Window::new(window_state.create_window());
let mut mouse_button_counter = window_state.mouse_button_counter.get();
@ -286,8 +275,7 @@ unsafe fn wnd_proc_inner(
None
}
WM_TIMER => {
let mut window = window_state.create_window();
let mut window = crate::Window::new(&mut window);
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);
@ -298,8 +286,7 @@ unsafe fn wnd_proc_inner(
WM_CLOSE => {
// Make sure to release the borrow before the DefWindowProc call
{
let mut window = window_state.create_window();
let mut window = crate::Window::new(&mut window);
let mut window = crate::Window::new(window_state.create_window());
window_state
.handler
@ -315,8 +302,7 @@ unsafe fn wnd_proc_inner(
}
WM_CHAR | WM_SYSCHAR | WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP
| WM_INPUTLANGCHANGE => {
let mut window = window_state.create_window();
let mut window = crate::Window::new(&mut window);
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);
@ -337,8 +323,7 @@ unsafe fn wnd_proc_inner(
}
}
WM_SIZE => {
let mut window = window_state.create_window();
let mut window = crate::Window::new(&mut window);
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;
@ -565,17 +550,6 @@ impl Window<'_> {
window_handle
}
pub fn open_as_if_parented<H, B>(options: WindowOpenOptions, build: B) -> WindowHandle
where
H: WindowHandler + 'static,
B: FnOnce(&mut crate::Window) -> H,
B: Send + 'static,
{
let (window_handle, _) = Self::open(true, null_mut(), options, build);
window_handle
}
pub fn open_blocking<H, B>(options: WindowOpenOptions, build: B)
where
H: WindowHandler + 'static,
@ -666,7 +640,7 @@ impl Window<'_> {
let gl_context: Option<GlContext> = options.gl_config.map(|gl_config| {
let mut handle = Win32WindowHandle::empty();
handle.hwnd = hwnd as *mut c_void;
let handle = RawWindowHandleWrapper { handle: RawWindowHandle::Win32(handle) };
let handle = RawWindowHandle::Win32(handle);
GlContext::create(&handle, gl_config).expect("Could not create OpenGL context")
});
@ -695,8 +669,7 @@ impl Window<'_> {
});
let handler = {
let mut window = window_state.create_window();
let mut window = crate::Window::new(&mut window);
let mut window = crate::Window::new(window_state.create_window());
build(&mut window)
};
@ -776,6 +749,10 @@ impl Window<'_> {
self.state.deferred_tasks.borrow_mut().push_back(task);
}
pub fn set_mouse_cursor(&mut self, _mouse_cursor: MouseCursor) {
todo!()
}
#[cfg(feature = "opengl")]
pub fn gl_context(&self) -> Option<&GlContext> {
self.state.gl_context.as_ref()
@ -797,6 +774,6 @@ unsafe impl HasRawDisplayHandle for Window<'_> {
}
}
pub fn copy_to_clipboard(data: &str) {
pub fn copy_to_clipboard(_data: &str) {
todo!()
}

View file

@ -6,7 +6,7 @@ use raw_window_handle::{
use crate::event::{Event, EventStatus};
use crate::window_open_options::WindowOpenOptions;
use crate::Size;
use crate::{MouseCursor, Size};
#[cfg(target_os = "macos")]
use crate::macos as platform;
@ -21,12 +21,6 @@ pub struct WindowHandle {
phantom: PhantomData<*mut ()>,
}
/// Quick wrapper to satisfy [HasRawWindowHandle], because of course a raw window handle wouldn't
/// have a raw window handle, that would be silly.
pub(crate) struct RawWindowHandleWrapper {
pub handle: RawWindowHandle,
}
impl WindowHandle {
fn new(window_handle: platform::WindowHandle) -> Self {
Self { window_handle, phantom: PhantomData::default() }
@ -56,10 +50,7 @@ pub trait WindowHandler {
}
pub struct Window<'a> {
#[cfg(target_os = "windows")]
window: &'a mut platform::Window<'a>,
#[cfg(not(target_os = "windows"))]
window: &'a mut platform::Window,
window: platform::Window<'a>,
// so that Window is !Send on all platforms
phantom: PhantomData<*mut ()>,
@ -67,12 +58,12 @@ pub struct Window<'a> {
impl<'a> Window<'a> {
#[cfg(target_os = "windows")]
pub(crate) fn new(window: &'a mut platform::Window<'a>) -> Window<'a> {
pub(crate) fn new(window: platform::Window<'a>) -> Window<'a> {
Window { window, phantom: PhantomData }
}
#[cfg(not(target_os = "windows"))]
pub(crate) fn new(window: &mut platform::Window) -> Window {
pub(crate) fn new(window: platform::Window) -> Window {
Window { window, phantom: PhantomData }
}
@ -87,16 +78,6 @@ impl<'a> Window<'a> {
WindowHandle::new(window_handle)
}
pub fn open_as_if_parented<H, B>(options: WindowOpenOptions, build: B) -> WindowHandle
where
H: WindowHandler + 'static,
B: FnOnce(&mut Window) -> H,
B: Send + 'static,
{
let window_handle = platform::Window::open_as_if_parented::<H, B>(options, build);
WindowHandle::new(window_handle)
}
pub fn open_blocking<H, B>(options: WindowOpenOptions, build: B)
where
H: WindowHandler + 'static,
@ -117,6 +98,10 @@ impl<'a> Window<'a> {
self.window.resize(size);
}
pub fn set_mouse_cursor(&mut self, cursor: MouseCursor) {
self.window.set_mouse_cursor(cursor);
}
/// 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")]
@ -136,9 +121,3 @@ unsafe impl<'a> HasRawDisplayHandle for Window<'a> {
self.window.raw_display_handle()
}
}
unsafe impl HasRawWindowHandle for RawWindowHandleWrapper {
fn raw_window_handle(&self) -> RawWindowHandle {
self.handle
}
}

View file

@ -1,5 +1,4 @@
use std::marker::PhantomData;
use std::os::raw::{c_ulong, c_void};
use std::ffi::c_void;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
use std::sync::Arc;
@ -28,9 +27,6 @@ pub struct WindowHandle {
raw_window_handle: Option<RawWindowHandle>,
close_requested: Arc<AtomicBool>,
is_open: Arc<AtomicBool>,
// Ensure handle is !Send
_phantom: PhantomData<*mut ()>,
}
impl WindowHandle {
@ -75,7 +71,6 @@ impl ParentHandle {
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)
@ -92,12 +87,11 @@ impl Drop for ParentHandle {
}
}
pub struct Window {
struct WindowInner {
xcb_connection: XcbConnection,
window_id: u32,
window_info: WindowInfo,
visual_id: u32,
// FIXME: There's all this mouse cursor logic but it's never actually used, is this correct?
mouse_cursor: MouseCursor,
frame_interval: Duration,
@ -111,6 +105,10 @@ pub struct Window {
gl_context: Option<GlContext>,
}
pub struct Window<'a> {
inner: &'a mut WindowInner,
}
// Hack to allow sending a RawWindowHandle between threads. Do not make public
struct SendableRwh(RawWindowHandle);
@ -118,7 +116,7 @@ unsafe impl Send for SendableRwh {}
type WindowOpenResult = Result<SendableRwh, ()>;
impl Window {
impl<'a> Window<'a> {
pub fn open_parented<P, H, B>(parent: &P, options: WindowOpenOptions, build: B) -> WindowHandle
where
P: HasRawWindowHandle,
@ -147,26 +145,6 @@ impl Window {
window_handle
}
pub fn open_as_if_parented<H, B>(options: WindowOpenOptions, build: B) -> WindowHandle
where
H: WindowHandler + 'static,
B: FnOnce(&mut crate::Window) -> H,
B: Send + 'static,
{
let (tx, rx) = mpsc::sync_channel::<WindowOpenResult>(1);
let (parent_handle, mut window_handle) = ParentHandle::new();
thread::spawn(move || {
Self::window_thread(None, options, build, tx.clone(), Some(parent_handle));
});
let raw_window_handle = rx.recv().unwrap().unwrap();
window_handle.raw_window_handle = Some(raw_window_handle.0);
window_handle
}
pub fn open_blocking<H, B>(options: WindowOpenOptions, build: B)
where
H: WindowHandler + 'static,
@ -326,6 +304,8 @@ impl Window {
// compared to when raw-gl-context was a separate crate.
#[cfg(feature = "opengl")]
let gl_context = fb_config.map(|fb_config| {
use std::ffi::c_ulong;
let window = window_id as c_ulong;
let display = xcb_connection.conn.get_raw_dpy();
@ -335,7 +315,7 @@ impl Window {
GlContext::new(context)
});
let mut window = Self {
let mut inner = WindowInner {
xcb_connection,
window_id,
window_info,
@ -353,57 +333,56 @@ impl Window {
gl_context,
};
let mut handler = build(&mut crate::Window::new(&mut window));
let mut window = crate::Window::new(Window { inner: &mut inner });
let mut handler = build(&mut window);
// Send an initial window resized event so the user is alerted of
// the correct dpi scaling.
handler.on_event(
&mut crate::Window::new(&mut window),
Event::Window(WindowEvent::Resized(window_info)),
);
handler.on_event(&mut window, Event::Window(WindowEvent::Resized(window_info)));
let _ = tx.send(Ok(SendableRwh(window.raw_window_handle())));
window.run_event_loop(&mut handler);
inner.run_event_loop(&mut handler);
}
pub fn set_mouse_cursor(&mut self, mouse_cursor: MouseCursor) {
if self.mouse_cursor == mouse_cursor {
if self.inner.mouse_cursor == mouse_cursor {
return;
}
let xid = self.xcb_connection.get_cursor_xid(mouse_cursor);
let xid = self.inner.xcb_connection.get_cursor_xid(mouse_cursor);
if xid != 0 {
xcb::change_window_attributes(
&self.xcb_connection.conn,
self.window_id,
&self.inner.xcb_connection.conn,
self.inner.window_id,
&[(xcb::CW_CURSOR, xid)],
);
self.xcb_connection.conn.flush();
self.inner.xcb_connection.conn.flush();
}
self.mouse_cursor = mouse_cursor;
self.inner.mouse_cursor = mouse_cursor;
}
pub fn close(&mut self) {
self.close_requested = true;
self.inner.close_requested = true;
}
pub fn resize(&mut self, size: Size) {
let scaling = self.window_info.scale();
let scaling = self.inner.window_info.scale();
let new_window_info = WindowInfo::from_logical_size(size, scaling);
xcb::configure_window(
&self.xcb_connection.conn,
self.window_id,
&self.inner.xcb_connection.conn,
self.inner.window_id,
&[
(xcb::CONFIG_WINDOW_WIDTH as u16, new_window_info.physical_size().width),
(xcb::CONFIG_WINDOW_HEIGHT as u16, new_window_info.physical_size().height),
],
);
self.xcb_connection.conn.flush();
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
@ -411,7 +390,7 @@ impl Window {
#[cfg(feature = "opengl")]
pub fn gl_context(&self) -> Option<&crate::gl::GlContext> {
self.gl_context.as_ref()
self.inner.gl_context.as_ref()
}
fn find_visual_for_depth(screen: &StructPtr<xcb_screen_t>, depth: u8) -> Option<u32> {
@ -429,7 +408,9 @@ impl Window {
None
}
}
impl WindowInner {
#[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
@ -447,7 +428,7 @@ impl Window {
let window_info = self.window_info;
handler.on_event(
&mut crate::Window::new(self),
&mut crate::Window::new(Window { inner: self }),
Event::Window(WindowEvent::Resized(window_info)),
);
}
@ -477,7 +458,7 @@ impl Window {
// if it's already time to draw a new frame.
let next_frame = last_frame + self.frame_interval;
if Instant::now() >= next_frame {
handler.on_frame(&mut crate::Window::new(self));
handler.on_frame(&mut crate::Window::new(Window { inner: self }));
last_frame = Instant::max(next_frame, Instant::now() - self.frame_interval);
}
@ -523,14 +504,20 @@ impl Window {
}
fn handle_close_requested(&mut self, handler: &mut dyn WindowHandler) {
handler.on_event(&mut crate::Window::new(self), Event::Window(WindowEvent::WillClose));
handler.on_event(
&mut crate::Window::new(Window { inner: 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));
handler.on_event(
&mut crate::Window::new(Window { inner: self }),
Event::Window(WindowEvent::WillClose),
);
self.event_loop_running = false;
}
@ -602,7 +589,7 @@ impl Window {
let logical_pos = physical_pos.to_logical(&self.window_info);
handler.on_event(
&mut crate::Window::new(self),
&mut crate::Window::new(Window { inner: self }),
Event::Mouse(MouseEvent::CursorMoved {
position: logical_pos,
modifiers: key_mods(event.state()),
@ -613,7 +600,7 @@ impl Window {
xcb::ENTER_NOTIFY => {
handler.on_event(
&mut crate::Window::new(self),
&mut crate::Window::new(Window { inner: self }),
Event::Mouse(MouseEvent::CursorEntered),
);
// since no `MOTION_NOTIFY` event is generated when `ENTER_NOTIFY` is generated,
@ -622,7 +609,7 @@ impl Window {
let physical_pos = PhyPoint::new(event.event_x() as i32, event.event_y() as i32);
let logical_pos = physical_pos.to_logical(&self.window_info);
handler.on_event(
&mut crate::Window::new(self),
&mut crate::Window::new(Window { inner: self }),
Event::Mouse(MouseEvent::CursorMoved {
position: logical_pos,
modifiers: key_mods(event.state()),
@ -631,8 +618,10 @@ impl Window {
}
xcb::LEAVE_NOTIFY => {
handler
.on_event(&mut crate::Window::new(self), Event::Mouse(MouseEvent::CursorLeft));
handler.on_event(
&mut crate::Window::new(Window { inner: self }),
Event::Mouse(MouseEvent::CursorLeft),
);
}
xcb::BUTTON_PRESS => {
@ -642,7 +631,7 @@ impl Window {
match detail {
4..=7 => {
handler.on_event(
&mut crate::Window::new(self),
&mut crate::Window::new(Window { inner: self }),
Event::Mouse(MouseEvent::WheelScrolled {
delta: match detail {
4 => ScrollDelta::Lines { x: 0.0, y: 1.0 },
@ -658,7 +647,7 @@ impl Window {
detail => {
let button_id = mouse_id(detail);
handler.on_event(
&mut crate::Window::new(self),
&mut crate::Window::new(Window { inner: self }),
Event::Mouse(MouseEvent::ButtonPressed {
button: button_id,
modifiers: key_mods(event.state()),
@ -675,7 +664,7 @@ impl Window {
if !(4..=7).contains(&detail) {
let button_id = mouse_id(detail);
handler.on_event(
&mut crate::Window::new(self),
&mut crate::Window::new(Window { inner: self }),
Event::Mouse(MouseEvent::ButtonReleased {
button: button_id,
modifiers: key_mods(event.state()),
@ -691,7 +680,7 @@ impl Window {
let event = unsafe { xcb::cast_event::<xcb::KeyPressEvent>(&event) };
handler.on_event(
&mut crate::Window::new(self),
&mut crate::Window::new(Window { inner: self }),
Event::Keyboard(convert_key_press_event(event)),
);
}
@ -700,7 +689,7 @@ impl Window {
let event = unsafe { xcb::cast_event::<xcb::KeyReleaseEvent>(&event) };
handler.on_event(
&mut crate::Window::new(self),
&mut crate::Window::new(Window { inner: self }),
Event::Keyboard(convert_key_release_event(event)),
);
}
@ -710,20 +699,20 @@ impl Window {
}
}
unsafe impl HasRawWindowHandle for Window {
unsafe impl<'a> HasRawWindowHandle for Window<'a> {
fn raw_window_handle(&self) -> RawWindowHandle {
let mut handle = XlibWindowHandle::empty();
handle.window = self.window_id.into();
handle.visual_id = self.visual_id.into();
handle.window = self.inner.window_id.into();
handle.visual_id = self.inner.visual_id.into();
RawWindowHandle::Xlib(handle)
}
}
unsafe impl HasRawDisplayHandle for Window {
unsafe impl<'a> HasRawDisplayHandle for Window<'a> {
fn raw_display_handle(&self) -> RawDisplayHandle {
let display = self.xcb_connection.conn.get_raw_dpy();
let display = self.inner.xcb_connection.conn.get_raw_dpy();
let mut handle = XlibDisplayHandle::empty();
handle.display = display as *mut c_void;
@ -744,6 +733,6 @@ fn mouse_id(id: u8) -> MouseButton {
}
}
pub fn copy_to_clipboard(data: &str) {
pub fn copy_to_clipboard(_data: &str) {
todo!()
}

View file

@ -19,7 +19,6 @@ pub struct XcbConnection {
pub(crate) atoms: Atoms,
// FIXME: Same here, there's a ton of unused cursor machinery in here
pub(super) cursor_cache: HashMap<MouseCursor, u32>,
}