rio_winit_fork/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
99impl WindowExtMacOS for Window {
100    #[inline]
101    fn simple_fullscreen(&self) -> bool {
102        self.window.maybe_wait_on_main(|w| w.simple_fullscreen())
103    }
104
105    #[inline]
106    fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
107        self.window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
108    }
109
110    #[inline]
111    fn has_shadow(&self) -> bool {
112        self.window.maybe_wait_on_main(|w| w.has_shadow())
113    }
114
115    #[inline]
116    fn set_has_shadow(&self, has_shadow: bool) {
117        self.window.maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
118    }
119
120    #[inline]
121    fn set_tabbing_identifier(&self, identifier: &str) {
122        self.window.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
123    }
124
125    #[inline]
126    fn tabbing_identifier(&self) -> String {
127        self.window.maybe_wait_on_main(|w| w.tabbing_identifier())
128    }
129
130    #[inline]
131    fn select_next_tab(&self) {
132        self.window.maybe_queue_on_main(|w| w.select_next_tab())
133    }
134
135    #[inline]
136    fn select_previous_tab(&self) {
137        self.window.maybe_queue_on_main(|w| w.select_previous_tab())
138    }
139
140    #[inline]
141    fn select_tab_at_index(&self, index: usize) {
142        self.window.maybe_queue_on_main(move |w| w.select_tab_at_index(index))
143    }
144
145    #[inline]
146    fn num_tabs(&self) -> usize {
147        self.window.maybe_wait_on_main(|w| w.num_tabs())
148    }
149
150    #[inline]
151    fn is_document_edited(&self) -> bool {
152        self.window.maybe_wait_on_main(|w| w.is_document_edited())
153    }
154
155    #[inline]
156    fn set_document_edited(&self, edited: bool) {
157        self.window.maybe_queue_on_main(move |w| w.set_document_edited(edited))
158    }
159
160    #[inline]
161    fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
162        self.window.maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
163    }
164
165    #[inline]
166    fn option_as_alt(&self) -> OptionAsAlt {
167        self.window.maybe_wait_on_main(|w| w.option_as_alt())
168    }
169}
170
171/// Corresponds to `NSApplicationActivationPolicy`.
172#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
173pub enum ActivationPolicy {
174    /// Corresponds to `NSApplicationActivationPolicyRegular`.
175    #[default]
176    Regular,
177
178    /// Corresponds to `NSApplicationActivationPolicyAccessory`.
179    Accessory,
180
181    /// Corresponds to `NSApplicationActivationPolicyProhibited`.
182    Prohibited,
183}
184
185/// Additional methods on [`WindowAttributes`] that are specific to MacOS.
186///
187/// **Note:** Properties dealing with the titlebar will be overwritten by the
188/// [`WindowAttributes::with_decorations`] method:
189/// - `with_titlebar_transparent`
190/// - `with_title_hidden`
191/// - `with_titlebar_hidden`
192/// - `with_titlebar_buttons_hidden`
193/// - `with_fullsize_content_view`
194pub trait WindowAttributesExtMacOS {
195    /// Enables click-and-drag behavior for the entire window, not just the titlebar.
196    fn with_movable_by_window_background(self, movable_by_window_background: bool) -> Self;
197    /// Makes the titlebar transparent and allows the content to appear behind it.
198    fn with_titlebar_transparent(self, titlebar_transparent: bool) -> Self;
199    /// Hides the window title.
200    fn with_title_hidden(self, title_hidden: bool) -> Self;
201    /// Hides the window titlebar.
202    fn with_titlebar_hidden(self, titlebar_hidden: bool) -> Self;
203    /// Hides the window titlebar buttons.
204    fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> Self;
205    /// Makes the window content appear behind the titlebar.
206    fn with_fullsize_content_view(self, fullsize_content_view: bool) -> Self;
207    fn with_disallow_hidpi(self, disallow_hidpi: bool) -> Self;
208    fn with_has_shadow(self, has_shadow: bool) -> Self;
209    /// Window accepts click-through mouse events.
210    fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> Self;
211    /// Defines the window tabbing identifier.
212    ///
213    /// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
214    fn with_tabbing_identifier(self, identifier: &str) -> Self;
215    /// Set how the <kbd>Option</kbd> keys are interpreted.
216    ///
217    /// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
218    fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
219}
220
221impl WindowAttributesExtMacOS for WindowAttributes {
222    #[inline]
223    fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
224        self.platform_specific.movable_by_window_background = movable_by_window_background;
225        self
226    }
227
228    #[inline]
229    fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
230        self.platform_specific.titlebar_transparent = titlebar_transparent;
231        self
232    }
233
234    #[inline]
235    fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
236        self.platform_specific.titlebar_hidden = titlebar_hidden;
237        self
238    }
239
240    #[inline]
241    fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
242        self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
243        self
244    }
245
246    #[inline]
247    fn with_title_hidden(mut self, title_hidden: bool) -> Self {
248        self.platform_specific.title_hidden = title_hidden;
249        self
250    }
251
252    #[inline]
253    fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
254        self.platform_specific.fullsize_content_view = fullsize_content_view;
255        self
256    }
257
258    #[inline]
259    fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
260        self.platform_specific.disallow_hidpi = disallow_hidpi;
261        self
262    }
263
264    #[inline]
265    fn with_has_shadow(mut self, has_shadow: bool) -> Self {
266        self.platform_specific.has_shadow = has_shadow;
267        self
268    }
269
270    #[inline]
271    fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
272        self.platform_specific.accepts_first_mouse = accepts_first_mouse;
273        self
274    }
275
276    #[inline]
277    fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
278        self.platform_specific.tabbing_identifier.replace(tabbing_identifier.to_string());
279        self
280    }
281
282    #[inline]
283    fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
284        self.platform_specific.option_as_alt = option_as_alt;
285        self
286    }
287}
288
289pub trait EventLoopBuilderExtMacOS {
290    /// Sets the activation policy for the application.
291    ///
292    /// It is set to [`ActivationPolicy::Regular`] by default.
293    ///
294    /// # Example
295    ///
296    /// Set the activation policy to "accessory".
297    ///
298    /// ```
299    /// use rio_winit_fork::event_loop::EventLoopBuilder;
300    /// #[cfg(target_os = "macos")]
301    /// use rio_winit_fork::platform::macos::{ActivationPolicy, EventLoopBuilderExtMacOS};
302    ///
303    /// let mut builder = EventLoopBuilder::new();
304    /// #[cfg(target_os = "macos")]
305    /// builder.with_activation_policy(ActivationPolicy::Accessory);
306    /// # if false { // We can't test this part
307    /// let event_loop = builder.build();
308    /// # }
309    /// ```
310    fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self;
311
312    /// Used to control whether a default menubar menu is created.
313    ///
314    /// Menu creation is enabled by default.
315    ///
316    /// # Example
317    ///
318    /// Disable creating a default menubar.
319    ///
320    /// ```
321    /// use rio_winit_fork::event_loop::EventLoopBuilder;
322    /// #[cfg(target_os = "macos")]
323    /// use rio_winit_fork::platform::macos::EventLoopBuilderExtMacOS;
324    ///
325    /// let mut builder = EventLoopBuilder::new();
326    /// #[cfg(target_os = "macos")]
327    /// builder.with_default_menu(false);
328    /// # if false { // We can't test this part
329    /// let event_loop = builder.build();
330    /// # }
331    /// ```
332    fn with_default_menu(&mut self, enable: bool) -> &mut Self;
333
334    /// Used to prevent the application from automatically activating when launched if
335    /// another application is already active.
336    ///
337    /// The default behavior is to ignore other applications and activate when launched.
338    fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self;
339}
340
341impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
342    #[inline]
343    fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
344        self.platform_specific.activation_policy = activation_policy;
345        self
346    }
347
348    #[inline]
349    fn with_default_menu(&mut self, enable: bool) -> &mut Self {
350        self.platform_specific.default_menu = enable;
351        self
352    }
353
354    #[inline]
355    fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self {
356        self.platform_specific.activate_ignoring_other_apps = ignore;
357        self
358    }
359}
360
361/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
362pub trait MonitorHandleExtMacOS {
363    /// Returns the identifier of the monitor for Cocoa.
364    fn native_id(&self) -> u32;
365    /// Returns a pointer to the NSScreen representing this monitor.
366    fn ns_screen(&self) -> Option<*mut c_void>;
367}
368
369impl MonitorHandleExtMacOS for MonitorHandle {
370    #[inline]
371    fn native_id(&self) -> u32 {
372        self.inner.native_identifier()
373    }
374
375    fn ns_screen(&self) -> Option<*mut c_void> {
376        // SAFETY: We only use the marker to get a pointer
377        let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
378        self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
379    }
380}
381
382/// Additional methods on [`ActiveEventLoop`] that are specific to macOS.
383pub trait ActiveEventLoopExtMacOS {
384    /// Hide the entire application. In most applications this is typically triggered with
385    /// Command-H.
386    fn hide_application(&self);
387    /// Hide the other applications. In most applications this is typically triggered with
388    /// Command+Option-H.
389    fn hide_other_applications(&self);
390    /// Set whether the system can automatically organize windows into tabs.
391    ///
392    /// <https://developer.apple.com/documentation/appkit/nswindow/1646657-allowsautomaticwindowtabbing>
393    fn set_allows_automatic_window_tabbing(&self, enabled: bool);
394    /// Returns whether the system can automatically organize windows into tabs.
395    fn allows_automatic_window_tabbing(&self) -> bool;
396}
397
398impl ActiveEventLoopExtMacOS for ActiveEventLoop {
399    fn hide_application(&self) {
400        self.p.hide_application()
401    }
402
403    fn hide_other_applications(&self) {
404        self.p.hide_other_applications()
405    }
406
407    fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
408        self.p.set_allows_automatic_window_tabbing(enabled);
409    }
410
411    fn allows_automatic_window_tabbing(&self) -> bool {
412        self.p.allows_automatic_window_tabbing()
413    }
414}
415
416/// Option as alt behavior.
417///
418/// The default is `None`.
419#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
420#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
421pub enum OptionAsAlt {
422    /// The left `Option` key is treated as `Alt`.
423    OnlyLeft,
424
425    /// The right `Option` key is treated as `Alt`.
426    OnlyRight,
427
428    /// Both `Option` keys are treated as `Alt`.
429    Both,
430
431    /// No special handling is applied for `Option` key.
432    #[default]
433    None,
434}