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}