winit/platform/macos.rs
1//! # macOS / AppKit
2//!
3//! Winit has an OS requirement of macOS 10.11 or higher (same as Rust
4//! itself), and is regularly tested on macOS 10.14.
5//!
6//! ## Window initialization
7//!
8//! A lot of functionality expects the application to be ready before you
9//! start doing anything; this includes creating windows, fetching monitors,
10//! drawing, and so on, see issues [#2238], [#2051] and [#2087].
11//!
12//! If you encounter problems, you should try doing your initialization inside
13//! [`ApplicationHandler::resumed`].
14//!
15//! [#2238]: https://github.com/rust-windowing/winit/issues/2238
16//! [#2051]: https://github.com/rust-windowing/winit/issues/2051
17//! [#2087]: https://github.com/rust-windowing/winit/issues/2087
18//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed
19//!
20//! ## Custom `NSApplicationDelegate`
21//!
22//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
23//! though, you might want to do more niche stuff, such as [handle when the user re-activates the
24//! application][reopen]. Such functionality is not exposed directly in Winit, since it would
25//! increase the API surface by quite a lot.
26//!
27//! [reopen]: https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428638-applicationshouldhandlereopen?language=objc
28//!
29//! Instead, Winit guarantees that it will not register an application delegate, so the solution is
30//! to register your own application delegate, as outlined in the following example (see
31//! `objc2-app-kit` for more detailed information).
32#![cfg_attr(target_os = "macos", doc = "```")]
33#![cfg_attr(not(target_os = "macos"), doc = "```ignore")]
34//! use objc2::rc::Retained;
35//! use objc2::runtime::ProtocolObject;
36//! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
37//! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
38//! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol};
39//! use winit::event_loop::EventLoop;
40//!
41//! declare_class!(
42//! struct AppDelegate;
43//!
44//! unsafe impl ClassType for AppDelegate {
45//! type Super = NSObject;
46//! type Mutability = mutability::MainThreadOnly;
47//! const NAME: &'static str = "MyAppDelegate";
48//! }
49//!
50//! impl DeclaredClass for AppDelegate {}
51//!
52//! unsafe impl NSObjectProtocol for AppDelegate {}
53//!
54//! unsafe impl NSApplicationDelegate for AppDelegate {
55//! #[method(application:openURLs:)]
56//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) {
57//! // Note: To specifically get `application:openURLs:` to work, you _might_
58//! // have to bundle your application. This is not done in this example.
59//! println!("open urls: {application:?}, {urls:?}");
60//! }
61//! }
62//! );
63//!
64//! impl AppDelegate {
65//! fn new(mtm: MainThreadMarker) -> Retained<Self> {
66//! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] }
67//! }
68//! }
69//!
70//! fn main() -> Result<(), Box<dyn std::error::Error>> {
71//! let event_loop = EventLoop::new()?;
72//!
73//! let mtm = MainThreadMarker::new().unwrap();
74//! let delegate = AppDelegate::new(mtm);
75//! // Important: Call `sharedApplication` after `EventLoop::new`,
76//! // doing it before is not yet supported.
77//! let app = NSApplication::sharedApplication(mtm);
78//! app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
79//!
80//! // event_loop.run_app(&mut my_app);
81//! Ok(())
82//! }
83//! ```
84
85use std::os::raw::c_void;
86
87#[cfg(feature = "serde")]
88use serde::{Deserialize, Serialize};
89
90use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
91use crate::monitor::MonitorHandle;
92use crate::window::{Window, WindowAttributes};
93
94/// Additional methods on [`Window`] that are specific to MacOS.
95pub trait WindowExtMacOS {
96 /// Returns whether or not the window is in simple fullscreen mode.
97 fn simple_fullscreen(&self) -> bool;
98
99 /// Toggles a fullscreen mode that doesn't require a new macOS space.
100 /// Returns a boolean indicating whether the transition was successful (this
101 /// won't work if the window was already in the native fullscreen).
102 ///
103 /// This is how fullscreen used to work on macOS in versions before Lion.
104 /// And allows the user to have a fullscreen window without using another
105 /// space or taking control over the entire monitor.
106 fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
107
108 /// Returns whether or not the window has shadow.
109 fn has_shadow(&self) -> bool;
110
111 /// Sets whether or not the window has shadow.
112 fn set_has_shadow(&self, has_shadow: bool);
113
114 /// Group windows together by using the same tabbing identifier.
115 ///
116 /// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
117 fn set_tabbing_identifier(&self, identifier: &str);
118
119 /// Returns the window's tabbing identifier.
120 fn tabbing_identifier(&self) -> String;
121
122 /// Select next tab.
123 fn select_next_tab(&self);
124
125 /// Select previous tab.
126 fn select_previous_tab(&self);
127
128 /// Select the tab with the given index.
129 ///
130 /// Will no-op when the index is out of bounds.
131 fn select_tab_at_index(&self, index: usize);
132
133 /// Get the number of tabs in the window tab group.
134 fn num_tabs(&self) -> usize;
135
136 /// Get the window's edit state.
137 ///
138 /// # Examples
139 ///
140 /// ```ignore
141 /// WindowEvent::CloseRequested => {
142 /// if window.is_document_edited() {
143 /// // Show the user a save pop-up or similar
144 /// } else {
145 /// // Close the window
146 /// drop(window);
147 /// }
148 /// }
149 /// ```
150 fn is_document_edited(&self) -> bool;
151
152 /// Put the window in a state which indicates a file save is required.
153 fn set_document_edited(&self, edited: bool);
154
155 /// Set option as alt behavior as described in [`OptionAsAlt`].
156 ///
157 /// This will ignore diacritical marks and accent characters from
158 /// being processed as received characters. Instead, the input
159 /// device's raw character will be placed in event queues with the
160 /// Alt modifier set.
161 fn set_option_as_alt(&self, option_as_alt: OptionAsAlt);
162
163 /// Getter for the [`WindowExtMacOS::set_option_as_alt`].
164 fn option_as_alt(&self) -> OptionAsAlt;
165
166 /// Disable the Menu Bar and Dock in Simple or Borderless Fullscreen mode. Useful for games.
167 /// The effect is applied when [`WindowExtMacOS::set_simple_fullscreen`] or
168 /// [`Window::set_fullscreen`] is called.
169 fn set_borderless_game(&self, borderless_game: bool);
170
171 /// Getter for the [`WindowExtMacOS::set_borderless_game`].
172 fn is_borderless_game(&self) -> bool;
173}
174
175impl WindowExtMacOS for Window {
176 #[inline]
177 fn simple_fullscreen(&self) -> bool {
178 self.window.maybe_wait_on_main(|w| w.simple_fullscreen())
179 }
180
181 #[inline]
182 fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
183 self.window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
184 }
185
186 #[inline]
187 fn has_shadow(&self) -> bool {
188 self.window.maybe_wait_on_main(|w| w.has_shadow())
189 }
190
191 #[inline]
192 fn set_has_shadow(&self, has_shadow: bool) {
193 self.window.maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
194 }
195
196 #[inline]
197 fn set_tabbing_identifier(&self, identifier: &str) {
198 self.window.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
199 }
200
201 #[inline]
202 fn tabbing_identifier(&self) -> String {
203 self.window.maybe_wait_on_main(|w| w.tabbing_identifier())
204 }
205
206 #[inline]
207 fn select_next_tab(&self) {
208 self.window.maybe_queue_on_main(|w| w.select_next_tab())
209 }
210
211 #[inline]
212 fn select_previous_tab(&self) {
213 self.window.maybe_queue_on_main(|w| w.select_previous_tab())
214 }
215
216 #[inline]
217 fn select_tab_at_index(&self, index: usize) {
218 self.window.maybe_queue_on_main(move |w| w.select_tab_at_index(index))
219 }
220
221 #[inline]
222 fn num_tabs(&self) -> usize {
223 self.window.maybe_wait_on_main(|w| w.num_tabs())
224 }
225
226 #[inline]
227 fn is_document_edited(&self) -> bool {
228 self.window.maybe_wait_on_main(|w| w.is_document_edited())
229 }
230
231 #[inline]
232 fn set_document_edited(&self, edited: bool) {
233 self.window.maybe_queue_on_main(move |w| w.set_document_edited(edited))
234 }
235
236 #[inline]
237 fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
238 self.window.maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
239 }
240
241 #[inline]
242 fn option_as_alt(&self) -> OptionAsAlt {
243 self.window.maybe_wait_on_main(|w| w.option_as_alt())
244 }
245
246 #[inline]
247 fn set_borderless_game(&self, borderless_game: bool) {
248 self.window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game))
249 }
250
251 #[inline]
252 fn is_borderless_game(&self) -> bool {
253 self.window.maybe_wait_on_main(|w| w.is_borderless_game())
254 }
255}
256
257/// Corresponds to `NSApplicationActivationPolicy`.
258#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
259pub enum ActivationPolicy {
260 /// Corresponds to `NSApplicationActivationPolicyRegular`.
261 #[default]
262 Regular,
263
264 /// Corresponds to `NSApplicationActivationPolicyAccessory`.
265 Accessory,
266
267 /// Corresponds to `NSApplicationActivationPolicyProhibited`.
268 Prohibited,
269}
270
271/// Additional methods on [`WindowAttributes`] that are specific to MacOS.
272///
273/// **Note:** Properties dealing with the titlebar will be overwritten by the
274/// [`WindowAttributes::with_decorations`] method:
275/// - `with_titlebar_transparent`
276/// - `with_title_hidden`
277/// - `with_titlebar_hidden`
278/// - `with_titlebar_buttons_hidden`
279/// - `with_fullsize_content_view`
280pub trait WindowAttributesExtMacOS {
281 /// Enables click-and-drag behavior for the entire window, not just the titlebar.
282 fn with_movable_by_window_background(self, movable_by_window_background: bool) -> Self;
283 /// Makes the titlebar transparent and allows the content to appear behind it.
284 fn with_titlebar_transparent(self, titlebar_transparent: bool) -> Self;
285 /// Hides the window title.
286 fn with_title_hidden(self, title_hidden: bool) -> Self;
287 /// Hides the window titlebar.
288 fn with_titlebar_hidden(self, titlebar_hidden: bool) -> Self;
289 /// Hides the window titlebar buttons.
290 fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> Self;
291 /// Makes the window content appear behind the titlebar.
292 fn with_fullsize_content_view(self, fullsize_content_view: bool) -> Self;
293 fn with_disallow_hidpi(self, disallow_hidpi: bool) -> Self;
294 fn with_has_shadow(self, has_shadow: bool) -> Self;
295 /// Window accepts click-through mouse events.
296 fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> Self;
297 /// Defines the window tabbing identifier.
298 ///
299 /// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
300 fn with_tabbing_identifier(self, identifier: &str) -> Self;
301 /// Set how the <kbd>Option</kbd> keys are interpreted.
302 ///
303 /// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
304 fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
305 /// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set.
306 fn with_borderless_game(self, borderless_game: bool) -> Self;
307}
308
309impl WindowAttributesExtMacOS for WindowAttributes {
310 #[inline]
311 fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
312 self.platform_specific.movable_by_window_background = movable_by_window_background;
313 self
314 }
315
316 #[inline]
317 fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
318 self.platform_specific.titlebar_transparent = titlebar_transparent;
319 self
320 }
321
322 #[inline]
323 fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
324 self.platform_specific.titlebar_hidden = titlebar_hidden;
325 self
326 }
327
328 #[inline]
329 fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
330 self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
331 self
332 }
333
334 #[inline]
335 fn with_title_hidden(mut self, title_hidden: bool) -> Self {
336 self.platform_specific.title_hidden = title_hidden;
337 self
338 }
339
340 #[inline]
341 fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
342 self.platform_specific.fullsize_content_view = fullsize_content_view;
343 self
344 }
345
346 #[inline]
347 fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
348 self.platform_specific.disallow_hidpi = disallow_hidpi;
349 self
350 }
351
352 #[inline]
353 fn with_has_shadow(mut self, has_shadow: bool) -> Self {
354 self.platform_specific.has_shadow = has_shadow;
355 self
356 }
357
358 #[inline]
359 fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
360 self.platform_specific.accepts_first_mouse = accepts_first_mouse;
361 self
362 }
363
364 #[inline]
365 fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
366 self.platform_specific.tabbing_identifier.replace(tabbing_identifier.to_string());
367 self
368 }
369
370 #[inline]
371 fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
372 self.platform_specific.option_as_alt = option_as_alt;
373 self
374 }
375
376 #[inline]
377 fn with_borderless_game(mut self, borderless_game: bool) -> Self {
378 self.platform_specific.borderless_game = borderless_game;
379 self
380 }
381}
382
383pub trait EventLoopBuilderExtMacOS {
384 /// Sets the activation policy for the application. If used, this will override
385 /// any relevant settings provided in the package manifest.
386 /// For instance, `with_activation_policy(ActivationPolicy::Regular)` will prevent
387 /// the application from running as an "agent", even if LSUIElement is set to true.
388 ///
389 /// If unused, the Winit will honor the package manifest.
390 ///
391 /// # Example
392 ///
393 /// Set the activation policy to "accessory".
394 ///
395 /// ```
396 /// use winit::event_loop::EventLoopBuilder;
397 /// #[cfg(target_os = "macos")]
398 /// use winit::platform::macos::{ActivationPolicy, EventLoopBuilderExtMacOS};
399 ///
400 /// let mut builder = EventLoopBuilder::new();
401 /// #[cfg(target_os = "macos")]
402 /// builder.with_activation_policy(ActivationPolicy::Accessory);
403 /// # if false { // We can't test this part
404 /// let event_loop = builder.build();
405 /// # }
406 /// ```
407 fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self;
408
409 /// Used to control whether a default menubar menu is created.
410 ///
411 /// Menu creation is enabled by default.
412 ///
413 /// # Example
414 ///
415 /// Disable creating a default menubar.
416 ///
417 /// ```
418 /// use winit::event_loop::EventLoopBuilder;
419 /// #[cfg(target_os = "macos")]
420 /// use winit::platform::macos::EventLoopBuilderExtMacOS;
421 ///
422 /// let mut builder = EventLoopBuilder::new();
423 /// #[cfg(target_os = "macos")]
424 /// builder.with_default_menu(false);
425 /// # if false { // We can't test this part
426 /// let event_loop = builder.build();
427 /// # }
428 /// ```
429 fn with_default_menu(&mut self, enable: bool) -> &mut Self;
430
431 /// Used to prevent the application from automatically activating when launched if
432 /// another application is already active.
433 ///
434 /// The default behavior is to ignore other applications and activate when launched.
435 fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self;
436}
437
438impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
439 #[inline]
440 fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
441 self.platform_specific.activation_policy = Some(activation_policy);
442 self
443 }
444
445 #[inline]
446 fn with_default_menu(&mut self, enable: bool) -> &mut Self {
447 self.platform_specific.default_menu = enable;
448 self
449 }
450
451 #[inline]
452 fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self {
453 self.platform_specific.activate_ignoring_other_apps = ignore;
454 self
455 }
456}
457
458/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
459pub trait MonitorHandleExtMacOS {
460 /// Returns the identifier of the monitor for Cocoa.
461 fn native_id(&self) -> u32;
462 /// Returns a pointer to the NSScreen representing this monitor.
463 fn ns_screen(&self) -> Option<*mut c_void>;
464}
465
466impl MonitorHandleExtMacOS for MonitorHandle {
467 #[inline]
468 fn native_id(&self) -> u32 {
469 self.inner.native_identifier()
470 }
471
472 fn ns_screen(&self) -> Option<*mut c_void> {
473 // SAFETY: We only use the marker to get a pointer
474 let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
475 self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
476 }
477}
478
479/// Additional methods on [`ActiveEventLoop`] that are specific to macOS.
480pub trait ActiveEventLoopExtMacOS {
481 /// Hide the entire application. In most applications this is typically triggered with
482 /// Command-H.
483 fn hide_application(&self);
484 /// Hide the other applications. In most applications this is typically triggered with
485 /// Command+Option-H.
486 fn hide_other_applications(&self);
487 /// Set whether the system can automatically organize windows into tabs.
488 ///
489 /// <https://developer.apple.com/documentation/appkit/nswindow/1646657-allowsautomaticwindowtabbing>
490 fn set_allows_automatic_window_tabbing(&self, enabled: bool);
491 /// Returns whether the system can automatically organize windows into tabs.
492 fn allows_automatic_window_tabbing(&self) -> bool;
493}
494
495impl ActiveEventLoopExtMacOS for ActiveEventLoop {
496 fn hide_application(&self) {
497 self.p.hide_application()
498 }
499
500 fn hide_other_applications(&self) {
501 self.p.hide_other_applications()
502 }
503
504 fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
505 self.p.set_allows_automatic_window_tabbing(enabled);
506 }
507
508 fn allows_automatic_window_tabbing(&self) -> bool {
509 self.p.allows_automatic_window_tabbing()
510 }
511}
512
513/// Option as alt behavior.
514///
515/// The default is `None`.
516#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
517#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
518pub enum OptionAsAlt {
519 /// The left `Option` key is treated as `Alt`.
520 OnlyLeft,
521
522 /// The right `Option` key is treated as `Alt`.
523 OnlyRight,
524
525 /// Both `Option` keys are treated as `Alt`.
526 Both,
527
528 /// No special handling is applied for `Option` key.
529 #[default]
530 None,
531}