Skip to main content

rio_window/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
19use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
20use crate::monitor::MonitorHandle;
21use crate::window::{Window, WindowAttributes};
22
23/// Colorspace options for macOS windows.
24#[derive(Clone, Copy, Debug, PartialEq)]
25pub enum Colorspace {
26    /// Standard sRGB colorspace
27    Srgb,
28    /// Display P3 wide color gamut
29    DisplayP3,
30    /// Rec. 2020 ultra-wide color gamut
31    Rec2020,
32}
33
34/// Additional methods on [`Window`] that are specific to MacOS.
35pub trait WindowExtMacOS {
36    /// Returns whether or not the window is in simple fullscreen mode.
37    fn simple_fullscreen(&self) -> bool;
38
39    /// Toggles a fullscreen mode that doesn't require a new macOS space.
40    /// Returns a boolean indicating whether the transition was successful (this
41    /// won't work if the window was already in the native fullscreen).
42    ///
43    /// This is how fullscreen used to work on macOS in versions before Lion.
44    /// And allows the user to have a fullscreen window without using another
45    /// space or taking control over the entire monitor.
46    fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
47
48    /// Returns whether or not the window has shadow.
49    fn has_shadow(&self) -> bool;
50
51    /// Sets whether or not the window has shadow.
52    fn set_has_shadow(&self, has_shadow: bool);
53
54    /// Sets background color.
55    fn set_background_color(&self, _r: f64, _g: f64, _b: f64, _a: f64);
56
57    /// Group windows together by using the same tabbing identifier.
58    ///
59    /// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
60    fn set_tabbing_identifier(&self, identifier: &str);
61
62    /// Returns the window's tabbing identifier.
63    fn tabbing_identifier(&self) -> String;
64
65    /// Select next tab.
66    fn select_next_tab(&self);
67
68    /// Select previous tab.
69    fn select_previous_tab(&self);
70
71    /// Select the tab with the given index.
72    ///
73    /// Will no-op when the index is out of bounds.
74    fn select_tab_at_index(&self, index: usize);
75
76    /// Get the number of tabs in the window tab group.
77    fn num_tabs(&self) -> usize;
78
79    /// Get the window's edit state.
80    ///
81    /// # Examples
82    ///
83    /// ```ignore
84    /// WindowEvent::CloseRequested => {
85    ///     if window.is_document_edited() {
86    ///         // Show the user a save pop-up or similar
87    ///     } else {
88    ///         // Close the window
89    ///         drop(window);
90    ///     }
91    /// }
92    /// ```
93    fn is_document_edited(&self) -> bool;
94
95    /// Put the window in a state which indicates a file save is required.
96    fn set_document_edited(&self, edited: bool);
97
98    /// Set option as alt behavior as described in [`OptionAsAlt`].
99    ///
100    /// This will ignore diacritical marks and accent characters from
101    /// being processed as received characters. Instead, the input
102    /// device's raw character will be placed in event queues with the
103    /// Alt modifier set.
104    fn set_option_as_alt(&self, option_as_alt: OptionAsAlt);
105
106    /// Getter for the [`WindowExtMacOS::set_option_as_alt`].
107    fn option_as_alt(&self) -> OptionAsAlt;
108
109    /// Makes the titlebar bigger, effectively adding more space around the
110    /// window controls if the titlebar is invisible.
111    fn set_unified_titlebar(&self, unified_titlebar: bool);
112    /// Getter for the [`WindowExtMacOS::set_unified_titlebar`].
113    fn unified_titlebar(&self) -> bool;
114
115    /// Sets the window's colorspace for wide color gamut support.
116    fn set_colorspace(&self, colorspace: Colorspace);
117
118    /// Sets the position of the traffic light buttons (close, minimize, maximize).
119    /// The position is specified as (x, y) coordinates in points from the top-left corner.
120    /// Pass None to reset to the default position.
121    fn set_traffic_light_position(&self, position: Option<(f64, f64)>);
122
123    /// Push the configured `window.opacity` into the liquid-glass
124    /// tint computation. Effective only when
125    /// [`crate::window::BlurStyle::MacosGlassRegular`] /
126    /// [`crate::window::BlurStyle::MacosGlassClear`] is active and
127    /// `NSGlassEffectView` is available at runtime (macOS 26+).
128    /// Otherwise stored for the next time glass installs. Mirrors
129    /// the `tintColor = bg.withAlphaComponent(opacity)` step in
130    /// AppKit-native glass setups.
131    fn set_glass_opacity(&self, opacity: f64);
132}
133
134impl WindowExtMacOS for Window {
135    #[inline]
136    fn simple_fullscreen(&self) -> bool {
137        self.window.maybe_wait_on_main(|w| w.simple_fullscreen())
138    }
139
140    #[inline]
141    fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
142        self.window
143            .maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
144    }
145
146    #[inline]
147    fn has_shadow(&self) -> bool {
148        self.window.maybe_wait_on_main(|w| w.has_shadow())
149    }
150
151    #[inline]
152    fn set_has_shadow(&self, has_shadow: bool) {
153        self.window
154            .maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
155    }
156
157    #[inline]
158    fn set_background_color(&self, r: f64, g: f64, b: f64, a: f64) {
159        self.window
160            .maybe_queue_on_main(move |w| w.set_background_color(r, g, b, a))
161    }
162
163    #[inline]
164    fn set_glass_opacity(&self, opacity: f64) {
165        self.window
166            .maybe_queue_on_main(move |w| w.set_glass_opacity(opacity))
167    }
168
169    #[inline]
170    fn set_tabbing_identifier(&self, identifier: &str) {
171        self.window
172            .maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
173    }
174
175    #[inline]
176    fn tabbing_identifier(&self) -> String {
177        self.window.maybe_wait_on_main(|w| w.tabbing_identifier())
178    }
179
180    #[inline]
181    fn select_next_tab(&self) {
182        self.window.maybe_queue_on_main(|w| w.select_next_tab())
183    }
184
185    #[inline]
186    fn select_previous_tab(&self) {
187        self.window.maybe_queue_on_main(|w| w.select_previous_tab())
188    }
189
190    #[inline]
191    fn select_tab_at_index(&self, index: usize) {
192        self.window
193            .maybe_queue_on_main(move |w| w.select_tab_at_index(index))
194    }
195
196    #[inline]
197    fn num_tabs(&self) -> usize {
198        self.window.maybe_wait_on_main(|w| w.num_tabs())
199    }
200
201    #[inline]
202    fn is_document_edited(&self) -> bool {
203        self.window.maybe_wait_on_main(|w| w.is_document_edited())
204    }
205
206    #[inline]
207    fn set_document_edited(&self, edited: bool) {
208        self.window
209            .maybe_queue_on_main(move |w| w.set_document_edited(edited))
210    }
211
212    #[inline]
213    fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
214        self.window
215            .maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
216    }
217
218    #[inline]
219    fn option_as_alt(&self) -> OptionAsAlt {
220        self.window.maybe_wait_on_main(|w| w.option_as_alt())
221    }
222
223    #[inline]
224    fn set_unified_titlebar(&self, unified_titlebar: bool) {
225        self.window
226            .maybe_wait_on_main(|w| w.set_unified_titlebar(unified_titlebar))
227    }
228
229    #[inline]
230    fn unified_titlebar(&self) -> bool {
231        self.window.maybe_wait_on_main(|w| w.unified_titlebar())
232    }
233
234    #[inline]
235    fn set_colorspace(&self, colorspace: Colorspace) {
236        self.window
237            .maybe_queue_on_main(move |w| w.set_colorspace(colorspace))
238    }
239
240    #[inline]
241    fn set_traffic_light_position(&self, position: Option<(f64, f64)>) {
242        self.window
243            .maybe_queue_on_main(move |w| w.set_traffic_light_position(position))
244    }
245}
246
247/// Corresponds to `NSApplicationActivationPolicy`.
248#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
249pub enum ActivationPolicy {
250    /// Corresponds to `NSApplicationActivationPolicyRegular`.
251    #[default]
252    Regular,
253
254    /// Corresponds to `NSApplicationActivationPolicyAccessory`.
255    Accessory,
256
257    /// Corresponds to `NSApplicationActivationPolicyProhibited`.
258    Prohibited,
259}
260
261/// Additional methods on [`WindowAttributes`] that are specific to MacOS.
262///
263/// **Note:** Properties dealing with the titlebar will be overwritten by the
264/// [`WindowAttributes::with_decorations`] method:
265/// - `with_titlebar_transparent`
266/// - `with_title_hidden`
267/// - `with_titlebar_hidden`
268/// - `with_titlebar_buttons_hidden`
269/// - `with_fullsize_content_view`
270pub trait WindowAttributesExtMacOS {
271    /// Enables click-and-drag behavior for the entire window, not just the titlebar.
272    fn with_movable_by_window_background(
273        self,
274        movable_by_window_background: bool,
275    ) -> Self;
276    /// Makes the titlebar transparent and allows the content to appear behind it.
277    fn with_titlebar_transparent(self, titlebar_transparent: bool) -> Self;
278    /// Hides the window title.
279    fn with_title_hidden(self, title_hidden: bool) -> Self;
280    /// Hides the window titlebar.
281    fn with_titlebar_hidden(self, titlebar_hidden: bool) -> Self;
282    /// Hides the window titlebar buttons.
283    fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> Self;
284    /// Makes the window content appear behind the titlebar.
285    fn with_fullsize_content_view(self, fullsize_content_view: bool) -> Self;
286    fn with_disallow_hidpi(self, disallow_hidpi: bool) -> Self;
287    fn with_has_shadow(self, has_shadow: bool) -> Self;
288    /// Window accepts click-through mouse events.
289    fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> Self;
290    /// Defines the window tabbing identifier.
291    ///
292    /// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
293    fn with_tabbing_identifier(self, identifier: &str) -> Self;
294    /// Set how the <kbd>Option</kbd> keys are interpreted.
295    ///
296    /// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
297    fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
298    /// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set.
299    fn with_unified_titlebar(self, unified_titlebar: bool) -> Self;
300    /// Sets the window's colorspace for wide color gamut support.
301    fn with_colorspace(self, colorspace: Colorspace) -> Self;
302    /// Sets the position of the traffic light buttons (close, minimize, maximize).
303    /// The position is specified as (x, y) coordinates in points from the top-left corner.
304    fn with_traffic_light_position(self, x: f64, y: f64) -> Self;
305    fn with_mouse_down_can_move_window(self, can_move: bool) -> Self;
306}
307
308impl WindowAttributesExtMacOS for WindowAttributes {
309    #[inline]
310    fn with_movable_by_window_background(
311        mut self,
312        movable_by_window_background: bool,
313    ) -> Self {
314        self.platform_specific.movable_by_window_background =
315            movable_by_window_background;
316        self
317    }
318
319    #[inline]
320    fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
321        self.platform_specific.titlebar_transparent = titlebar_transparent;
322        self
323    }
324
325    #[inline]
326    fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
327        self.platform_specific.titlebar_hidden = titlebar_hidden;
328        self
329    }
330
331    #[inline]
332    fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
333        self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
334        self
335    }
336
337    #[inline]
338    fn with_title_hidden(mut self, title_hidden: bool) -> Self {
339        self.platform_specific.title_hidden = title_hidden;
340        self
341    }
342
343    #[inline]
344    fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
345        self.platform_specific.fullsize_content_view = fullsize_content_view;
346        self
347    }
348
349    #[inline]
350    fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
351        self.platform_specific.disallow_hidpi = disallow_hidpi;
352        self
353    }
354
355    #[inline]
356    fn with_has_shadow(mut self, has_shadow: bool) -> Self {
357        self.platform_specific.has_shadow = has_shadow;
358        self
359    }
360
361    #[inline]
362    fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
363        self.platform_specific.accepts_first_mouse = accepts_first_mouse;
364        self
365    }
366
367    #[inline]
368    fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
369        self.platform_specific
370            .tabbing_identifier
371            .replace(tabbing_identifier.to_string());
372        self
373    }
374
375    #[inline]
376    fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
377        self.platform_specific.option_as_alt = option_as_alt;
378        self
379    }
380
381    #[inline]
382    fn with_unified_titlebar(mut self, unified_titlebar: bool) -> Self {
383        self.platform_specific.unified_titlebar = unified_titlebar;
384        self
385    }
386
387    #[inline]
388    fn with_colorspace(mut self, colorspace: Colorspace) -> Self {
389        self.platform_specific.colorspace = Some(colorspace);
390        self
391    }
392
393    #[inline]
394    fn with_traffic_light_position(mut self, x: f64, y: f64) -> Self {
395        self.platform_specific.traffic_light_position = Some((x, y));
396        self
397    }
398
399    #[inline]
400    fn with_mouse_down_can_move_window(mut self, can_move: bool) -> Self {
401        self.platform_specific.mouse_down_can_move_window = can_move;
402        self
403    }
404}
405
406pub trait EventLoopBuilderExtMacOS {
407    /// Sets the activation policy for the application.
408    ///
409    /// It is set to [`ActivationPolicy::Regular`] by default.
410    ///
411    /// # Example
412    ///
413    /// Set the activation policy to "accessory".
414    ///
415    /// ```
416    /// use rio_window::event_loop::EventLoopBuilder;
417    /// #[cfg(target_os = "macos")]
418    /// use rio_window::platform::macos::{ActivationPolicy, EventLoopBuilderExtMacOS};
419    ///
420    /// let mut builder = EventLoopBuilder::new();
421    /// #[cfg(target_os = "macos")]
422    /// builder.with_activation_policy(ActivationPolicy::Accessory);
423    /// # if false { // We can't test this part
424    /// let event_loop = builder.build();
425    /// # }
426    /// ```
427    fn with_activation_policy(
428        &mut self,
429        activation_policy: ActivationPolicy,
430    ) -> &mut Self;
431
432    /// Used to control whether a default menubar menu is created.
433    ///
434    /// Menu creation is enabled by default.
435    ///
436    /// # Example
437    ///
438    /// Disable creating a default menubar.
439    ///
440    /// ```
441    /// use rio_window::event_loop::EventLoopBuilder;
442    /// #[cfg(target_os = "macos")]
443    /// use rio_window::platform::macos::EventLoopBuilderExtMacOS;
444    ///
445    /// let mut builder = EventLoopBuilder::new();
446    /// #[cfg(target_os = "macos")]
447    /// builder.with_default_menu(false);
448    /// # if false { // We can't test this part
449    /// let event_loop = builder.build();
450    /// # }
451    /// ```
452    fn with_default_menu(&mut self, enable: bool) -> &mut Self;
453
454    /// Used to prevent the application from automatically activating when launched if
455    /// another application is already active.
456    ///
457    /// The default behavior is to ignore other applications and activate when launched.
458    fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self;
459}
460
461impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
462    #[inline]
463    fn with_activation_policy(
464        &mut self,
465        activation_policy: ActivationPolicy,
466    ) -> &mut Self {
467        self.platform_specific.activation_policy = activation_policy;
468        self
469    }
470
471    #[inline]
472    fn with_default_menu(&mut self, enable: bool) -> &mut Self {
473        self.platform_specific.default_menu = enable;
474        self
475    }
476
477    #[inline]
478    fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self {
479        self.platform_specific.activate_ignoring_other_apps = ignore;
480        self
481    }
482}
483
484/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
485pub trait MonitorHandleExtMacOS {
486    /// Returns the identifier of the monitor for Cocoa.
487    fn native_id(&self) -> u32;
488    /// Returns a pointer to the NSScreen representing this monitor.
489    fn ns_screen(&self) -> Option<*mut c_void>;
490}
491
492impl MonitorHandleExtMacOS for MonitorHandle {
493    #[inline]
494    fn native_id(&self) -> u32 {
495        self.inner.native_identifier()
496    }
497
498    fn ns_screen(&self) -> Option<*mut c_void> {
499        // SAFETY: We only use the marker to get a pointer
500        let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
501        self.inner
502            .ns_screen(mtm)
503            .map(|s| objc2::rc::Retained::as_ptr(&s) as _)
504    }
505}
506
507/// Additional methods on [`ActiveEventLoop`] that are specific to macOS.
508pub trait ActiveEventLoopExtMacOS {
509    /// Hide the entire application. In most applications this is typically triggered with
510    /// Command-H.
511    fn hide_application(&self);
512    /// Hide the other applications. In most applications this is typically triggered with
513    /// Command+Option-H.
514    fn hide_other_applications(&self);
515    /// Set whether the system can automatically organize windows into tabs.
516    ///
517    /// <https://developer.apple.com/documentation/appkit/nswindow/1646657-allowsautomaticwindowtabbing>
518    fn set_allows_automatic_window_tabbing(&self, enabled: bool);
519    /// Returns whether the system can automatically organize windows into tabs.
520    fn allows_automatic_window_tabbing(&self) -> bool;
521}
522
523impl ActiveEventLoopExtMacOS for ActiveEventLoop {
524    fn hide_application(&self) {
525        self.p.hide_application()
526    }
527
528    fn hide_other_applications(&self) {
529        self.p.hide_other_applications()
530    }
531
532    fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
533        self.p.set_allows_automatic_window_tabbing(enabled);
534    }
535
536    fn allows_automatic_window_tabbing(&self) -> bool {
537        self.p.allows_automatic_window_tabbing()
538    }
539}
540
541/// Option as alt behavior.
542///
543/// The default is `None`.
544#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
545pub enum OptionAsAlt {
546    /// The left `Option` key is treated as `Alt`.
547    OnlyLeft,
548
549    /// The right `Option` key is treated as `Alt`.
550    OnlyRight,
551
552    /// Both `Option` keys are treated as `Alt`.
553    Both,
554
555    /// No special handling is applied for `Option` key.
556    #[default]
557    None,
558}