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