1mod frame;
7mod input_events;
8mod ownership;
9mod window_state;
10
11use dear_imgui_rs::{
12 ContextAttachmentError, ContextBindingError, ContextPlatformAttachmentReleaseError,
13 ContextPlatformWindowTeardownError,
14};
15use thiserror::Error;
16
17pub use frame::HiDpiMode;
18pub use ownership::WinitPlatform;
19#[cfg(feature = "multi-viewport")]
20pub(crate) use ownership::{WINIT_VIEWPORT_FLAGS, WinitPlatformControl};
21
22#[derive(Clone, Debug, Eq, Error, PartialEq)]
24#[non_exhaustive]
25pub enum WinitPlatformError {
26 #[error(transparent)]
28 Attachment(#[from] ContextAttachmentError),
29 #[error(transparent)]
31 PlatformAttachmentRelease(#[from] ContextPlatformAttachmentReleaseError),
32 #[error(transparent)]
34 Context(#[from] ContextBindingError),
35 #[error(transparent)]
37 PlatformWindowTeardown(#[from] ContextPlatformWindowTeardownError),
38 #[error("the Winit platform backend belongs to a different Dear ImGui context")]
40 ContextMismatch,
41 #[error("Dear ImGui platform state `{field}` is already owned")]
43 PlatformStateOccupied { field: &'static str },
44 #[error("Dear ImGui platform state `{field}` changed while Winit was attached")]
46 PlatformStateReplaced { field: &'static str },
47 #[error("attach a main Winit window before using this operation")]
49 WindowNotAttached,
50 #[error("the Winit window does not match the platform backend's attached main window")]
52 WindowMismatch,
53 #[error("Winit multi-viewport support is already attached")]
55 RuntimeAlreadyAttached,
56 #[error("Winit platform configuration is locked while multi-viewport support is attached")]
58 RuntimeConfigurationLocked,
59 #[error("dear-imgui-sys was built without PlatformIO aggregate ABI hooks")]
61 AggregateCallbackHooksUnavailable,
62 #[error("ImGuiPlatformIO callback `{callback}` is already owned by another platform backend")]
64 PlatformCallbackOccupied { callback: &'static str },
65 #[error("Dear ImGui backend capability `{flag}` is already owned by another platform backend")]
67 PlatformCapabilityOccupied { flag: &'static str },
68 #[error(
70 "Winit platform callback table slot `{callback}` changed while the runtime was attached"
71 )]
72 PlatformCallbackReplaced { callback: &'static str },
73 #[error("renderer state `{field}` is still installed; shut down the renderer before Winit")]
75 RendererShutdownRequired { field: &'static str },
76 #[error("viewport platform data or handle is already owned by another platform backend")]
78 ForeignPlatformUserData,
79 #[error("Winit lost ownership of viewport {viewport_id} field `{field}`")]
81 ViewportOwnershipLost {
82 viewport_id: u32,
84 field: &'static str,
86 },
87 #[error("Winit did not expose any monitor geometry")]
89 NoMonitors,
90 #[cfg(feature = "multi-viewport")]
92 #[error("Winit native monitor collection is unavailable: {reason}")]
93 MonitorCollectionUnavailable {
94 reason: crate::multi_viewport::WinitMonitorCollectionFailure,
95 },
96 #[error("Winit monitor {monitor} is invalid: {reason}")]
98 InvalidMonitorGeometry {
99 monitor: usize,
100 reason: &'static str,
101 },
102 #[error("Dear ImGui viewport geometry is invalid during {operation}: {reason}")]
104 InvalidViewportGeometry {
105 operation: &'static str,
106 reason: &'static str,
107 },
108 #[error("Winit multi-viewport requires HiDpiMode::Default")]
110 CustomHiDpiModeUnsupported,
111 #[error("Wayland is unsupported by the Winit multi-viewport backend; use X11 on Linux")]
113 WaylandUnsupported,
114 #[error("the Winit multi-viewport backend does not support target `{target}`")]
116 UnsupportedWindowSystem { target: &'static str },
117 #[error("Winit cannot honor viewport flag `{flag}` during {operation}")]
119 UnsupportedViewportFlag {
120 flag: &'static str,
121 operation: &'static str,
122 },
123 #[error("the Winit monitor count exceeds i32::MAX")]
125 MonitorCountOverflow,
126 #[error("Dear ImGui failed to allocate Winit monitor storage")]
128 MonitorStorageAllocationFailed,
129 #[error("Winit viewport creation requires WinitPlatform::with_event_loop")]
131 EventLoopUnavailable,
132 #[error("Winit failed to create a secondary viewport window: {message}")]
134 WindowCreation { message: String },
135 #[error("Winit viewport operation `{operation}` failed: {message}")]
137 WindowOperation {
138 operation: &'static str,
139 message: String,
140 },
141 #[error("Winit platform callback `{callback}` panicked")]
143 CallbackPanicked { callback: &'static str },
144 #[error("the Winit platform runtime is no longer attached")]
146 RuntimeDetached,
147 #[cfg(test)]
148 #[error("injected Winit construction failure after {stage}")]
149 InjectedConstructionFailure { stage: &'static str },
150}
151
152#[cfg(test)]
153mod tests {
154 use std::ffi::{CStr, CString};
155 use std::rc::Rc;
156
157 use dear_imgui_rs::{
158 BackendFlags, Context, ContextAttachment, ContextAttachmentRole,
159 ContextPlatformAttachmentReleaseError,
160 };
161 use winit::event::{Event, WindowEvent};
162
163 use super::input_events::{event_targets_window, rescale_mouse_pos_for_hidpi_change};
164 use super::ownership::{
165 PlatformOwnerToken, PlatformState, WINIT_BASE_FLAGS, WINIT_RESERVED_FLAGS,
166 winit_backend_name_ptr,
167 };
168 use super::window_state::ime_callback_eq;
169 use super::{HiDpiMode, WinitPlatform, WinitPlatformError};
170 use crate::test_util::test_sync::lock_context;
171
172 struct ActiveRendererMarker;
173 struct ActiveRendererAttachment;
174
175 impl ContextAttachment for ActiveRendererAttachment {}
176
177 unsafe extern "C" fn foreign_ime_callback(
178 _context: *mut dear_imgui_rs::sys::ImGuiContext,
179 _viewport: *mut dear_imgui_rs::sys::ImGuiViewport,
180 _data: *mut dear_imgui_rs::sys::ImGuiPlatformImeData,
181 ) {
182 }
183
184 #[test]
185 fn test_hidpi_mode_default() {
186 assert_eq!(HiDpiMode::default(), HiDpiMode::Default);
187 }
188
189 #[test]
190 fn test_platform_creation() {
191 let _guard = lock_context();
192 let mut ctx = Context::create();
193 let platform = WinitPlatform::new(&mut ctx).unwrap();
194
195 assert_eq!(platform.hidpi_mode, HiDpiMode::Default);
196 assert_eq!(platform.hidpi_factor, 1.0);
197 assert_eq!(platform.cursor_cache, None);
198 assert!(!platform.ime_enabled);
199 }
200
201 #[test]
202 fn platform_shutdown_rejects_an_active_renderer_before_releasing_base_state() {
203 let _guard = lock_context();
204 let mut context = Context::create();
205 let mut platform = WinitPlatform::new(&mut context).unwrap();
206 let mut renderer = context
207 .register_attachment::<ActiveRendererMarker>(
208 ContextAttachmentRole::Renderer,
209 Rc::new(ActiveRendererAttachment),
210 )
211 .unwrap();
212 let io = unsafe { dear_imgui_rs::sys::igGetIO_ContextPtr(context.as_raw()) };
213
214 assert!(matches!(
215 platform.shutdown(&mut context),
216 Err(WinitPlatformError::PlatformAttachmentRelease(
217 ContextPlatformAttachmentReleaseError::RendererActive
218 ))
219 ));
220 assert_eq!(
221 unsafe { (*io).BackendPlatformUserData },
222 platform.control.token_ptr()
223 );
224 assert!(platform.control.attachment_handle().unwrap().is_attached());
225
226 assert_eq!(renderer.detach(), Ok(true));
227 platform.shutdown(&mut context).unwrap();
228 }
229
230 #[test]
231 fn platform_drop_defers_base_release_while_a_renderer_attachment_is_active() {
232 let _guard = lock_context();
233 let mut context = Context::create();
234 let platform = WinitPlatform::new(&mut context).unwrap();
235 let control = platform.control();
236 let mut renderer = context
237 .register_attachment::<ActiveRendererMarker>(
238 ContextAttachmentRole::Renderer,
239 Rc::new(ActiveRendererAttachment),
240 )
241 .unwrap();
242 let io = unsafe { dear_imgui_rs::sys::igGetIO_ContextPtr(context.as_raw()) };
243
244 drop(platform);
245
246 assert_eq!(
247 unsafe { (*io).BackendPlatformUserData },
248 control.token_ptr()
249 );
250 assert!(control.attachment_handle().unwrap().is_attached());
251 assert_eq!(renderer.detach(), Ok(true));
252 drop(context);
253 assert_eq!(control.state.get(), PlatformState::ContextDestroyed);
254 }
255
256 #[test]
257 fn platform_claim_publishes_stable_identity_and_cleans_up_exact_ownership() {
258 let _guard = lock_context();
259 let mut context = Context::create();
260 let platform_io =
261 unsafe { dear_imgui_rs::sys::igGetPlatformIO_ContextPtr(context.as_raw()) };
262 let baseline_ime_callback = unsafe { (*platform_io).Platform_SetImeDataFn };
263 let baseline_ime_user_data = unsafe { (*platform_io).Platform_ImeUserData };
264
265 let platform = WinitPlatform::new(&mut context).unwrap();
266 let control = platform.control();
267 let io = unsafe { dear_imgui_rs::sys::igGetIO_ContextPtr(context.as_raw()) };
268
269 assert_ne!(std::mem::size_of::<PlatformOwnerToken>(), 0);
270 assert_eq!(
271 unsafe { (*io).BackendPlatformName },
272 winit_backend_name_ptr()
273 );
274 assert_eq!(
275 unsafe { (*io).BackendPlatformUserData },
276 control.token_ptr()
277 );
278 assert_eq!(
279 unsafe { CStr::from_ptr((*io).BackendPlatformName) },
280 unsafe { CStr::from_ptr(winit_backend_name_ptr()) }
281 );
282 assert_eq!(
283 BackendFlags::from_bits_retain(unsafe { (*io).BackendFlags }) & WINIT_RESERVED_FLAGS,
284 WINIT_BASE_FLAGS
285 );
286 assert!(ime_callback_eq(
287 unsafe { (*platform_io).Platform_SetImeDataFn },
288 baseline_ime_callback
289 ));
290 assert_eq!(
291 unsafe { (*platform_io).Platform_ImeUserData },
292 baseline_ime_user_data
293 );
294
295 drop(platform);
296
297 assert!(unsafe { (*io).BackendPlatformName.is_null() });
298 assert!(unsafe { (*io).BackendPlatformUserData.is_null() });
299 assert!(
300 (BackendFlags::from_bits_retain(unsafe { (*io).BackendFlags }) & WINIT_RESERVED_FLAGS)
301 .is_empty()
302 );
303 assert!(ime_callback_eq(
304 unsafe { (*platform_io).Platform_SetImeDataFn },
305 baseline_ime_callback
306 ));
307 assert_eq!(
308 unsafe { (*platform_io).Platform_ImeUserData },
309 baseline_ime_user_data
310 );
311 }
312
313 #[test]
314 fn platform_attachment_is_unique_per_context_and_reusable_after_release() {
315 let _guard = lock_context();
316 let mut context = Context::create();
317 let platform = WinitPlatform::new(&mut context).unwrap();
318
319 let error = match WinitPlatform::new(&mut context) {
320 Ok(_) => panic!("a Context cannot have two Winit platform owners"),
321 Err(error) => error,
322 };
323 assert_eq!(
324 error,
325 WinitPlatformError::PlatformStateOccupied {
326 field: "BackendPlatformName"
327 }
328 );
329
330 drop(platform);
331 drop(WinitPlatform::new(&mut context).unwrap());
332 }
333
334 #[test]
335 fn base_contract_reports_each_replaced_owned_field() {
336 let _guard = lock_context();
337 let mut context = Context::create();
338 let mut platform = WinitPlatform::new(&mut context).unwrap();
339 let control = platform.control();
340 let io = unsafe { dear_imgui_rs::sys::igGetIO_ContextPtr(context.as_raw()) };
341 let platform_io =
342 unsafe { dear_imgui_rs::sys::igGetPlatformIO_ContextPtr(context.as_raw()) };
343 let baseline_ime_callback = unsafe { (*platform_io).Platform_SetImeDataFn };
344
345 let validate = || {
346 control
347 .binding()
348 .with_bound_context(|| control.validate_complete_contract_in_current_context())
349 };
350
351 unsafe { (*io).BackendPlatformName = std::ptr::null() };
352 assert_eq!(
353 validate(),
354 Err(WinitPlatformError::PlatformStateReplaced {
355 field: "BackendPlatformName"
356 })
357 );
358 unsafe { (*io).BackendPlatformName = winit_backend_name_ptr() };
359
360 unsafe { (*io).BackendPlatformUserData = std::ptr::null_mut() };
361 assert_eq!(
362 validate(),
363 Err(WinitPlatformError::PlatformStateReplaced {
364 field: "BackendPlatformUserData"
365 })
366 );
367 unsafe { (*io).BackendPlatformUserData = control.token_ptr() };
368
369 unsafe { (*io).BackendFlags &= !WINIT_BASE_FLAGS.bits() };
370 assert_eq!(
371 validate(),
372 Err(WinitPlatformError::PlatformStateReplaced {
373 field: "BackendFlags"
374 })
375 );
376 unsafe { (*io).BackendFlags |= WINIT_BASE_FLAGS.bits() };
377
378 unsafe { (*platform_io).Platform_SetImeDataFn = Some(foreign_ime_callback) };
379 assert_eq!(
380 validate(),
381 Err(WinitPlatformError::PlatformStateReplaced {
382 field: "Platform_SetImeDataFn"
383 })
384 );
385 unsafe { (*platform_io).Platform_SetImeDataFn = baseline_ime_callback };
386
387 let foreign_ime_user_data = std::ptr::dangling_mut::<u8>().cast();
388 unsafe { (*platform_io).Platform_ImeUserData = foreign_ime_user_data };
389 assert_eq!(
390 validate(),
391 Err(WinitPlatformError::PlatformStateReplaced {
392 field: "Platform_ImeUserData"
393 })
394 );
395 unsafe { (*platform_io).Platform_ImeUserData = std::ptr::null_mut() };
396
397 platform.shutdown(&mut context).unwrap();
398 }
399
400 #[test]
401 fn public_base_entry_latches_contract_drift_until_ordered_shutdown() {
402 let _guard = lock_context();
403 let mut context = Context::create();
404 let mut platform = WinitPlatform::new(&mut context).unwrap();
405 let io = unsafe { dear_imgui_rs::sys::igGetIO_ContextPtr(context.as_raw()) };
406 unsafe { (*io).BackendPlatformName = std::ptr::null() };
407
408 let expected = WinitPlatformError::PlatformStateReplaced {
409 field: "BackendPlatformName",
410 };
411 assert_eq!(
412 platform.set_software_cursor_enabled(&mut context, true),
413 Err(expected.clone())
414 );
415 assert!(
416 !BackendFlags::from_bits_retain(unsafe { (*io).BackendFlags })
417 .contains(WINIT_BASE_FLAGS)
418 );
419
420 unsafe { (*io).BackendPlatformName = winit_backend_name_ptr() };
421 assert_eq!(
422 platform.set_software_cursor_enabled(&mut context, false),
423 Err(expected.clone())
424 );
425 assert_eq!(platform.shutdown(&mut context), Err(expected));
426 assert!(unsafe { (*io).BackendPlatformName.is_null() });
427 assert!(unsafe { (*io).BackendPlatformUserData.is_null() });
428 assert_eq!(platform.shutdown(&mut context), Ok(()));
429 }
430
431 #[test]
432 fn shutdown_preserves_a_same_text_foreign_backend_name_pointer() {
433 let _guard = lock_context();
434 let mut context = Context::create();
435 let mut platform = WinitPlatform::new(&mut context).unwrap();
436 let io = unsafe { dear_imgui_rs::sys::igGetIO_ContextPtr(context.as_raw()) };
437 let foreign_name = CString::new(
438 unsafe { CStr::from_ptr(winit_backend_name_ptr()) }
439 .to_bytes()
440 .to_vec(),
441 )
442 .unwrap();
443 assert_ne!(foreign_name.as_ptr(), winit_backend_name_ptr());
444 unsafe { (*io).BackendPlatformName = foreign_name.as_ptr() };
445
446 assert_eq!(
447 platform.shutdown(&mut context),
448 Err(WinitPlatformError::PlatformStateReplaced {
449 field: "BackendPlatformName"
450 })
451 );
452 assert_eq!(unsafe { (*io).BackendPlatformName }, foreign_name.as_ptr());
453 assert_eq!(
454 unsafe { CStr::from_ptr((*io).BackendPlatformName) },
455 unsafe { CStr::from_ptr(winit_backend_name_ptr()) }
456 );
457 assert!(unsafe { (*io).BackendPlatformUserData.is_null() });
458
459 unsafe { (*io).BackendPlatformName = std::ptr::null() };
460 drop(WinitPlatform::new(&mut context).unwrap());
461 }
462
463 #[test]
464 fn explicit_shutdown_preserves_complete_foreign_base_takeover_and_flags() {
465 let _guard = lock_context();
466 let mut context = Context::create();
467 let mut platform = WinitPlatform::new(&mut context).unwrap();
468 let io = unsafe { dear_imgui_rs::sys::igGetIO_ContextPtr(context.as_raw()) };
469 let foreign_name = CString::new("foreign-platform").unwrap();
470 let foreign_token = std::ptr::dangling_mut::<u8>().cast();
471 unsafe {
472 (*io).BackendPlatformName = foreign_name.as_ptr();
473 (*io).BackendPlatformUserData = foreign_token;
474 }
475
476 assert_eq!(
477 platform.shutdown(&mut context),
478 Err(WinitPlatformError::PlatformStateReplaced {
479 field: "BackendPlatformUserData"
480 })
481 );
482 assert_eq!(unsafe { (*io).BackendPlatformName }, foreign_name.as_ptr());
483 assert_eq!(unsafe { (*io).BackendPlatformUserData }, foreign_token);
484 assert!(
485 BackendFlags::from_bits_retain(unsafe { (*io).BackendFlags })
486 .contains(WINIT_BASE_FLAGS)
487 );
488
489 unsafe {
490 (*io).BackendPlatformName = std::ptr::null();
491 (*io).BackendPlatformUserData = std::ptr::null_mut();
492 (*io).BackendFlags &= !WINIT_BASE_FLAGS.bits();
493 }
494 }
495
496 #[test]
497 fn drop_preserves_complete_foreign_base_takeover_and_flags() {
498 let _guard = lock_context();
499 let mut context = Context::create();
500 let io = unsafe { dear_imgui_rs::sys::igGetIO_ContextPtr(context.as_raw()) };
501 let foreign_name = CString::new("foreign-platform").unwrap();
502 let foreign_token = std::ptr::dangling_mut::<u8>().cast();
503 let platform = WinitPlatform::new(&mut context).unwrap();
504 unsafe {
505 (*io).BackendPlatformName = foreign_name.as_ptr();
506 (*io).BackendPlatformUserData = foreign_token;
507 }
508
509 drop(platform);
510
511 assert_eq!(unsafe { (*io).BackendPlatformName }, foreign_name.as_ptr());
512 assert_eq!(unsafe { (*io).BackendPlatformUserData }, foreign_token);
513 assert!(
514 BackendFlags::from_bits_retain(unsafe { (*io).BackendFlags })
515 .contains(WINIT_BASE_FLAGS)
516 );
517
518 unsafe {
519 (*io).BackendPlatformName = std::ptr::null();
520 (*io).BackendPlatformUserData = std::ptr::null_mut();
521 (*io).BackendFlags &= !WINIT_BASE_FLAGS.bits();
522 }
523 }
524
525 #[test]
526 fn complete_foreign_takeover_does_not_revoke_foreign_flags_on_contract_fault() {
527 let _guard = lock_context();
528 let mut context = Context::create();
529 let mut platform = WinitPlatform::new(&mut context).unwrap();
530 let io = unsafe { dear_imgui_rs::sys::igGetIO_ContextPtr(context.as_raw()) };
531 let foreign_name = CString::new("foreign-platform").unwrap();
532 let foreign_token = std::ptr::dangling_mut::<u8>().cast();
533 unsafe {
534 (*io).BackendPlatformName = foreign_name.as_ptr();
535 (*io).BackendPlatformUserData = foreign_token;
536 }
537
538 let expected = WinitPlatformError::PlatformStateReplaced {
539 field: "BackendPlatformName",
540 };
541 assert_eq!(
542 platform.set_software_cursor_enabled(&mut context, true),
543 Err(expected.clone())
544 );
545 assert!(
546 BackendFlags::from_bits_retain(unsafe { (*io).BackendFlags })
547 .contains(WINIT_BASE_FLAGS)
548 );
549 assert_eq!(platform.shutdown(&mut context), Err(expected));
550 assert_eq!(unsafe { (*io).BackendPlatformName }, foreign_name.as_ptr());
551 assert_eq!(unsafe { (*io).BackendPlatformUserData }, foreign_token);
552 assert!(
553 BackendFlags::from_bits_retain(unsafe { (*io).BackendFlags })
554 .contains(WINIT_BASE_FLAGS)
555 );
556
557 unsafe {
558 (*io).BackendPlatformName = std::ptr::null();
559 (*io).BackendPlatformUserData = std::ptr::null_mut();
560 (*io).BackendFlags &= !WINIT_BASE_FLAGS.bits();
561 }
562 }
563
564 #[test]
565 fn test_hidpi_mode_setting() {
566 let _guard = lock_context();
567 let mut ctx = Context::create();
568 let mut platform = WinitPlatform::new(&mut ctx).unwrap();
569
570 platform.set_hidpi_mode(HiDpiMode::Locked(2.0)).unwrap();
571 assert_eq!(platform.hidpi_mode, HiDpiMode::Locked(2.0));
572
573 platform.set_hidpi_mode(HiDpiMode::Rounded).unwrap();
574 assert_eq!(platform.hidpi_mode, HiDpiMode::Rounded);
575 }
576
577 #[test]
578 fn full_window_events_are_filtered_by_window_id_before_dispatch() {
579 let target = winit::window::WindowId::from(41_u64);
580 let foreign = winit::window::WindowId::from(42_u64);
581 let foreign_event = Event::<()>::WindowEvent {
582 window_id: foreign,
583 event: WindowEvent::Focused(true),
584 };
585 let target_event = Event::<()>::WindowEvent {
586 window_id: target,
587 event: WindowEvent::Focused(true),
588 };
589
590 assert!(!event_targets_window(target, &foreign_event));
591 assert!(event_targets_window(target, &target_event));
592 assert!(event_targets_window(target, &Event::<()>::AboutToWait));
593 }
594
595 #[test]
596 fn rescale_mouse_pos_for_hidpi_change_rejects_non_finite_results() {
597 assert_eq!(
598 rescale_mouse_pos_for_hidpi_change([10.0, 20.0], 1.0, 2.0),
599 Some([20.0, 40.0])
600 );
601 assert_eq!(
602 rescale_mouse_pos_for_hidpi_change([f32::NAN, 20.0], 1.0, 2.0),
603 None
604 );
605 assert_eq!(
606 rescale_mouse_pos_for_hidpi_change([10.0, 20.0], 0.0, 2.0),
607 Some([20.0, 40.0])
608 );
609 assert_eq!(
610 rescale_mouse_pos_for_hidpi_change([f32::MAX, 20.0], 1.0, f64::MAX),
611 None
612 );
613 }
614
615 #[test]
616 fn test_window_attributes_creation() {
617 let attrs = WinitPlatform::create_window_attributes();
618 let _ = attrs;
620 }
621}