Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions winit-uikit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ objc2-ui-kit = { workspace = true, features = [
"UIPinchGestureRecognizer",
"UIResponder",
"UIRotationGestureRecognizer",
"UIScene",
"UIScreen",
"UIScreenMode",
"UITapGestureRecognizer",
Expand All @@ -68,6 +69,7 @@ objc2-ui-kit = { workspace = true, features = [
"UIView",
"UIViewController",
"UIWindow",
"UIWindowScene",
] }
winit-common = { workspace = true, features = ["core-foundation", "event-handler", "foundation"] }

Expand Down
21 changes: 19 additions & 2 deletions winit-uikit/src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use std::{fmt, ptr};

use dispatch2::MainThreadBound;
use dpi::PhysicalSize;
use objc2::MainThreadMarker;
use objc2::rc::Retained;
use objc2::{MainThreadMarker, available};
use objc2_core_foundation::{
CFAbsoluteTimeGetCurrent, CFRetained, CFRunLoop, CFRunLoopTimer, CGRect, CGSize,
kCFRunLoopCommonModes,
Expand Down Expand Up @@ -83,6 +83,7 @@ pub(crate) struct AppState {
event_loop_proxy: Arc<EventLoopProxy>,
queued_events: Cell<Vec<EventWrapper>>,
queued_gpu_redraws: Cell<HashSet<Retained<WinitUIWindow>>>,
awaiting_scene: Cell<bool>,
}

impl fmt::Debug for AppState {
Expand Down Expand Up @@ -116,6 +117,7 @@ impl AppState {
event_loop_proxy,
queued_events: Cell::new(Vec::new()),
queued_gpu_redraws: Cell::new(HashSet::new()),
awaiting_scene: Cell::new(false),
})
.is_ok()
}
Expand Down Expand Up @@ -268,10 +270,25 @@ pub fn did_finish_launching(mtm: MainThreadMarker) {
this.did_finish_launching_transition();

get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, StartCause::Init));
get_handler(mtm).handle(|app| app.can_create_surfaces(&ActiveEventLoop { mtm }));
// Apps that adopt the scene life cycle get their first scene after launching, and windows
// need one to be shown.
if available!(ios = 13.0, tvos = 13.0, visionos = 1.0)
&& UIApplication::sharedApplication(mtm).connectedScenes().is_empty()
{
this.awaiting_scene.set(true);
} else {
get_handler(mtm).handle(|app| app.can_create_surfaces(&ActiveEventLoop { mtm }));
}
handle_nonuser_events(mtm, []);
}

pub(crate) fn scene_will_connect(mtm: MainThreadMarker) {
if AppState::get(mtm).awaiting_scene.replace(false) {
get_handler(mtm).handle(|app| app.can_create_surfaces(&ActiveEventLoop { mtm }));
handle_nonuser_events(mtm, []);
}
}

// AppState::did_finish_launching handles the special transition `Init`
pub fn handle_wakeup_transition(mtm: MainThreadMarker) {
let this = AppState::get(mtm);
Expand Down
33 changes: 31 additions & 2 deletions winit-uikit/src/event_loop.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::ffi::{CStr, c_char, c_void};
use std::ptr;
use std::sync::Arc;

use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2::{ClassType, MainThreadMarker, msg_send};
use objc2::{ClassType, MainThreadMarker, available, msg_send};
use objc2_core_foundation::{CFIndex, CFRunLoopActivity, kCFRunLoopDefaultMode};
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
use objc2_foundation::{NSNotificationCenter, NSNotificationName, NSObjectProtocol};
use objc2_ui_kit::{
UIApplication, UIApplicationDidBecomeActiveNotification,
UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification,
Expand Down Expand Up @@ -134,6 +136,7 @@ pub struct EventLoop {
_did_enter_background_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_did_receive_memory_warning_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_scene_will_connect_observer: Option<Retained<ProtocolObject<dyn NSObjectProtocol>>>,

_tracing_observers: Option<(MainRunLoopObserver, MainRunLoopObserver)>,
_wakeup_observer: MainRunLoopObserver,
Expand Down Expand Up @@ -240,6 +243,19 @@ impl EventLoop {
},
);

let _scene_will_connect_observer = available!(ios = 13.0, tvos = 13.0, visionos = 1.0)
.then(|| {
create_observer(
&center,
// `scene:willConnectToSession:options:`
uikit_notification_name(c"UISceneWillConnectNotification"),
move |_| {
let _entered = debug_span!("UISceneWillConnectNotification").entered();
app_state::scene_will_connect(mtm)
},
)
});

let main_loop = MainRunLoop::get(mtm);
let mode = unsafe { kCFRunLoopDefaultMode }.unwrap();

Expand Down Expand Up @@ -302,6 +318,7 @@ impl EventLoop {
_did_enter_background_observer,
_will_terminate_observer,
_did_receive_memory_warning_observer,
_scene_will_connect_observer,
_tracing_observers,
_wakeup_observer,
_main_events_cleared_observer,
Expand Down Expand Up @@ -334,6 +351,18 @@ impl EventLoop {
}
}

/// Looks up a notification name at runtime. Linking to one that is newer than the app's oldest
/// supported iOS version would stop the app from launching there.
fn uikit_notification_name(symbol: &CStr) -> &'static NSNotificationName {
unsafe extern "C" {
fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
}
const RTLD_DEFAULT: *mut c_void = ptr::without_provenance_mut(-2isize as usize);
let name: *const &'static NSNotificationName =
unsafe { dlsym(RTLD_DEFAULT, symbol.as_ptr()) }.cast();
unsafe { name.as_ref() }.copied().unwrap_or_else(|| panic!("UIKit has no {symbol:?}"))
}

impl EventLoopProvider for EventLoop {
fn run_app<A: ApplicationHandler + 'static>(self, app: A) -> Result<(), EventLoopError> {
self.run_app_never_return(app)
Expand Down
27 changes: 27 additions & 0 deletions winit-uikit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,33 @@
//!
//! Note that an app may not receive the `Drop` event if suspended; it might be SIGKILL'ed.
//!
//! ## Scenes
//!
//! Apps built with the iOS 27 SDK must adopt [the UIScene lifecycle][scenes] by declaring a scene
//! configuration in `Info.plist`. No scene delegate is needed:
//!
//! ```xml
//! <key>UIApplicationSceneManifest</key>
//! <dict>
//! <key>UIApplicationSupportsMultipleScenes</key>
//! <false/>
//! <key>UISceneConfigurations</key>
//! <dict>
//! <key>UIWindowSceneSessionRoleApplication</key>
//! <array>
//! <dict>
//! <key>UISceneConfigurationName</key>
//! <string>Default Configuration</string>
//! </dict>
//! </array>
//! </dict>
//! </dict>
//! ```
//!
//! Winit shows its windows in the first scene that connects; multiple scenes are not supported.
//!
//! [scenes]: https://developer.apple.com/documentation/uikit/specifying-the-scenes-your-app-supports
//!
//! ## Custom `UIApplicationDelegate`
//!
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
Expand Down
11 changes: 10 additions & 1 deletion winit-uikit/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use objc2_core_foundation::{CGFloat, CGPoint, CGRect, CGSize};
use objc2_foundation::{NSObject, NSObjectProtocol};
use objc2_ui_kit::{
UIApplication, UICoordinateSpace, UIEdgeInsets, UIResponder, UIScreen,
UIScreenOverscanCompensation, UIViewController, UIWindow,
UIScreenOverscanCompensation, UIViewController, UIWindow, UIWindowScene,
};
use tracing::{debug, debug_span, warn};
use winit_core::cursor::Cursor;
Expand Down Expand Up @@ -84,6 +84,15 @@ impl WinitUIWindow {

this.setRootViewController(Some(view_controller));

// Windows in apps that adopt the scene life cycle are only shown once attached to a scene.
if available!(ios = 13.0, tvos = 13.0, visionos = 1.0) && this.windowScene().is_none() {
let scene = UIApplication::sharedApplication(mtm)
.connectedScenes()
.into_iter()
.find_map(|scene| scene.downcast::<UIWindowScene>().ok());
this.setWindowScene(scene.as_deref());
}

match window_attributes.fullscreen.clone() {
Some(Fullscreen::Exclusive(monitor, ref video_mode)) => {
let monitor = monitor.cast_ref::<MonitorHandle>().unwrap();
Expand Down
4 changes: 4 additions & 0 deletions winit/src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ changelog entry.

## Unreleased

### Added

- On iOS, support the UIScene lifecycle (required by iOS 27)

### Fixed

- On Windows, fix a crash occurring when trying to create a DXGI surface on a window that is both fullscreen and always on top.
Expand Down
Loading