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
1617use std::os::raw::c_void;
1819#[cfg(feature = "serde")]
20use serde::{Deserialize, Serialize};
2122use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
23use crate::monitor::MonitorHandle;
24use crate::window::{Window, WindowAttributes};
2526/// Additional methods on [`Window`] that are specific to MacOS.
27pub trait WindowExtMacOS {
28/// Returns whether or not the window is in simple fullscreen mode.
29fn simple_fullscreen(&self) -> bool;
3031/// 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.
38fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
3940/// Returns whether or not the window has shadow.
41fn has_shadow(&self) -> bool;
4243/// Sets whether or not the window has shadow.
44fn set_has_shadow(&self, has_shadow: bool);
4546/// Group windows together by using the same tabbing identifier.
47 ///
48 /// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
49fn set_tabbing_identifier(&self, identifier: &str);
5051/// Returns the window's tabbing identifier.
52fn tabbing_identifier(&self) -> String;
5354/// Select next tab.
55fn select_next_tab(&self);
5657/// Select previous tab.
58fn select_previous_tab(&self);
5960/// Select the tab with the given index.
61 ///
62 /// Will no-op when the index is out of bounds.
63fn select_tab_at_index(&self, index: usize);
6465/// Get the number of tabs in the window tab group.
66fn num_tabs(&self) -> usize;
6768/// 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 /// ```
82fn is_document_edited(&self) -> bool;
8384/// Put the window in a state which indicates a file save is required.
85fn set_document_edited(&self, edited: bool);
8687/// 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.
93fn set_option_as_alt(&self, option_as_alt: OptionAsAlt);
9495/// Getter for the [`WindowExtMacOS::set_option_as_alt`].
96fn option_as_alt(&self) -> OptionAsAlt;
9798/// Disable the Menu Bar and Dock in Borderless Fullscreen mode. Useful for games.
99fn set_borderless_game(&self, borderless_game: bool);
100101/// Getter for the [`WindowExtMacOS::set_borderless_game`].
102fn is_borderless_game(&self) -> bool;
103}
104105impl WindowExtMacOS for Window {
106#[inline]
107fn simple_fullscreen(&self) -> bool {
108self.window.maybe_wait_on_main(|w| w.simple_fullscreen())
109 }
110111#[inline]
112fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
113self.window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
114 }
115116#[inline]
117fn has_shadow(&self) -> bool {
118self.window.maybe_wait_on_main(|w| w.has_shadow())
119 }
120121#[inline]
122fn set_has_shadow(&self, has_shadow: bool) {
123self.window.maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
124 }
125126#[inline]
127fn set_tabbing_identifier(&self, identifier: &str) {
128self.window.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
129 }
130131#[inline]
132fn tabbing_identifier(&self) -> String {
133self.window.maybe_wait_on_main(|w| w.tabbing_identifier())
134 }
135136#[inline]
137fn select_next_tab(&self) {
138self.window.maybe_queue_on_main(|w| w.select_next_tab())
139 }
140141#[inline]
142fn select_previous_tab(&self) {
143self.window.maybe_queue_on_main(|w| w.select_previous_tab())
144 }
145146#[inline]
147fn select_tab_at_index(&self, index: usize) {
148self.window.maybe_queue_on_main(move |w| w.select_tab_at_index(index))
149 }
150151#[inline]
152fn num_tabs(&self) -> usize {
153self.window.maybe_wait_on_main(|w| w.num_tabs())
154 }
155156#[inline]
157fn is_document_edited(&self) -> bool {
158self.window.maybe_wait_on_main(|w| w.is_document_edited())
159 }
160161#[inline]
162fn set_document_edited(&self, edited: bool) {
163self.window.maybe_queue_on_main(move |w| w.set_document_edited(edited))
164 }
165166#[inline]
167fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
168self.window.maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
169 }
170171#[inline]
172fn option_as_alt(&self) -> OptionAsAlt {
173self.window.maybe_wait_on_main(|w| w.option_as_alt())
174 }
175176#[inline]
177fn set_borderless_game(&self, borderless_game: bool) {
178self.window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game))
179 }
180181#[inline]
182fn is_borderless_game(&self) -> bool {
183self.window.maybe_wait_on_main(|w| w.is_borderless_game())
184 }
185}
186187/// Corresponds to `NSApplicationActivationPolicy`.
188#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
189pub enum ActivationPolicy {
190/// Corresponds to `NSApplicationActivationPolicyRegular`.
191#[default]
192Regular,
193194/// Corresponds to `NSApplicationActivationPolicyAccessory`.
195Accessory,
196197/// Corresponds to `NSApplicationActivationPolicyProhibited`.
198Prohibited,
199}
200201/// 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.
212fn 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.
214fn with_titlebar_transparent(self, titlebar_transparent: bool) -> Self;
215/// Hides the window title.
216fn with_title_hidden(self, title_hidden: bool) -> Self;
217/// Hides the window titlebar.
218fn with_titlebar_hidden(self, titlebar_hidden: bool) -> Self;
219/// Hides the window titlebar buttons.
220fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> Self;
221/// Makes the window content appear behind the titlebar.
222fn with_fullsize_content_view(self, fullsize_content_view: bool) -> Self;
223fn with_disallow_hidpi(self, disallow_hidpi: bool) -> Self;
224fn with_has_shadow(self, has_shadow: bool) -> Self;
225/// Window accepts click-through mouse events.
226fn 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>
230fn 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.
234fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
235/// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set.
236fn with_borderless_game(self, borderless_game: bool) -> Self;
237}
238239impl WindowAttributesExtMacOS for WindowAttributes {
240#[inline]
241fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
242self.platform_specific.movable_by_window_background = movable_by_window_background;
243self
244}
245246#[inline]
247fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
248self.platform_specific.titlebar_transparent = titlebar_transparent;
249self
250}
251252#[inline]
253fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
254self.platform_specific.titlebar_hidden = titlebar_hidden;
255self
256}
257258#[inline]
259fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
260self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
261self
262}
263264#[inline]
265fn with_title_hidden(mut self, title_hidden: bool) -> Self {
266self.platform_specific.title_hidden = title_hidden;
267self
268}
269270#[inline]
271fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
272self.platform_specific.fullsize_content_view = fullsize_content_view;
273self
274}
275276#[inline]
277fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
278self.platform_specific.disallow_hidpi = disallow_hidpi;
279self
280}
281282#[inline]
283fn with_has_shadow(mut self, has_shadow: bool) -> Self {
284self.platform_specific.has_shadow = has_shadow;
285self
286}
287288#[inline]
289fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
290self.platform_specific.accepts_first_mouse = accepts_first_mouse;
291self
292}
293294#[inline]
295fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
296self.platform_specific.tabbing_identifier.replace(tabbing_identifier.to_string());
297self
298}
299300#[inline]
301fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
302self.platform_specific.option_as_alt = option_as_alt;
303self
304}
305306#[inline]
307fn with_borderless_game(mut self, borderless_game: bool) -> Self {
308self.platform_specific.borderless_game = borderless_game;
309self
310}
311}
312313pub 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 /// ```
337fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self;
338339/// 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 /// ```
359fn with_default_menu(&mut self, enable: bool) -> &mut Self;
360361/// 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.
365fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self;
366}
367368impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
369#[inline]
370fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
371self.platform_specific.activation_policy = Some(activation_policy);
372self
373}
374375#[inline]
376fn with_default_menu(&mut self, enable: bool) -> &mut Self {
377self.platform_specific.default_menu = enable;
378self
379}
380381#[inline]
382fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self {
383self.platform_specific.activate_ignoring_other_apps = ignore;
384self
385}
386}
387388/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
389pub trait MonitorHandleExtMacOS {
390/// Returns the identifier of the monitor for Cocoa.
391fn native_id(&self) -> u32;
392/// Returns a pointer to the NSScreen representing this monitor.
393fn ns_screen(&self) -> Option<*mut c_void>;
394}
395396impl MonitorHandleExtMacOS for MonitorHandle {
397#[inline]
398fn native_id(&self) -> u32 {
399self.inner.native_identifier()
400 }
401402fn ns_screen(&self) -> Option<*mut c_void> {
403// SAFETY: We only use the marker to get a pointer
404let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
405self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
406 }
407}
408409/// 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.
413fn hide_application(&self);
414/// Hide the other applications. In most applications this is typically triggered with
415 /// Command+Option-H.
416fn 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>
420fn set_allows_automatic_window_tabbing(&self, enabled: bool);
421/// Returns whether the system can automatically organize windows into tabs.
422fn allows_automatic_window_tabbing(&self) -> bool;
423}
424425impl ActiveEventLoopExtMacOS for ActiveEventLoop {
426fn hide_application(&self) {
427self.p.hide_application()
428 }
429430fn hide_other_applications(&self) {
431self.p.hide_other_applications()
432 }
433434fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
435self.p.set_allows_automatic_window_tabbing(enabled);
436 }
437438fn allows_automatic_window_tabbing(&self) -> bool {
439self.p.allows_automatic_window_tabbing()
440 }
441}
442443/// 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`.
450OnlyLeft,
451452/// The right `Option` key is treated as `Alt`.
453OnlyRight,
454455/// Both `Option` keys are treated as `Alt`.
456Both,
457458/// No special handling is applied for `Option` key.
459#[default]
460None,
461}