1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
use core::cell::UnsafeCell;
use core::marker::PhantomData;
use objc::rc::{AutoreleasePool, Id, Owned, Shared};
use objc::runtime::{Class, Object, BOOL, NO, YES};
use objc::{class, msg_send, sel};
use super::menu::Menu;
use super::menubar::MenuBar;
/// Helper to make various functions on the global application object safe.
#[doc(alias = "NSApp")]
#[doc(alias = "NSApplication")]
#[repr(C)]
pub struct InitializedApplication {
/// The application contains syncronization primitives that allows mutable
/// access with an immutable reference, and hence need to be `UnsafeCell`.
///
/// TODO: Verify this claim.
_priv: UnsafeCell<[u8; 0]>,
}
unsafe impl objc::RefEncode for InitializedApplication {
const ENCODING_REF: objc::Encoding<'static> = objc::Encoding::Object;
}
unsafe impl objc::Message for InitializedApplication {}
unsafe impl Sync for InitializedApplication {}
impl InitializedApplication {
/// # Safety
///
/// This must not be called before `applicationDidFinishLaunching`.
///
/// In `winit`, this is at or after
/// [`winit::event::StartCause::Init`] has been emitted.
#[doc(alias = "sharedApplication")]
pub unsafe fn new() -> &'static Self {
msg_send![class!(NSApplication), sharedApplication]
}
#[doc(alias = "mainMenu")]
pub fn menubar<'p>(&self, pool: &'p AutoreleasePool) -> Option<&'p Menu> {
unsafe { msg_send![self, mainMenu] }
}
/// Setting the menubar to `null` does not work properly, so we don't allow
/// that functionality here!
#[doc(alias = "setMainMenu")]
#[doc(alias = "setMainMenu:")]
pub fn set_menubar(&self, menubar: MenuBar) -> Id<Menu, Shared> {
let menu = menubar.into_raw();
let _: () = unsafe { msg_send![self, setMainMenu: &*menu] };
menu.into()
}
/// Returns the first menu set with [`set_window_menu`]
#[doc(alias = "windowsMenu")]
pub fn window_menu<'p>(&self, pool: &'p AutoreleasePool) -> Option<&'p Menu> {
unsafe { msg_send![self, windowsMenu] }
}
/// Set the global window menu.
///
/// The "Window: menu has items and keyboard shortcuts for entering
/// fullscreen, managing tabs (e.g. "Show Next Tab") and a list of the
/// application's windows.
///
/// Should be called before [`set_menubar`], otherwise the window menu
/// won't be properly populated.
///
/// Un-setting the window menu (to `null`) does not work properly, so we
/// don't expose that functionality here.
///
/// Additionally, you can have luck setting the window menu more than once,
/// though this is not recommended.
#[doc(alias = "setWindowsMenu")]
#[doc(alias = "setWindowsMenu:")]
pub fn set_window_menu(&self, menu: &Menu) {
// TODO: Is it safe to immutably set this?
unsafe { msg_send![self, setWindowsMenu: menu] }
}
/// Returns the first menu set with [`set_services_menu`]
#[doc(alias = "servicesMenu")]
pub fn services_menu<'p>(&self, pool: &'p AutoreleasePool) -> Option<&'p Menu> {
unsafe { msg_send![self, servicesMenu] }
}
/// Set the global services menu.
///
/// The user can have a number of system configured services and
/// corresponding keyboard shortcuts that can be accessed from this menu.
///
/// Un-setting the services menu (to `null`) does not work properly, so we
/// don't expose that functionality here.
///
/// Additionally, you can sometimes have luck setting the services menu
/// more than once, but this is really flaky.
#[doc(alias = "setServicesMenu")]
#[doc(alias = "setServicesMenu:")]
pub fn set_services_menu(&self, menu: &Menu) {
// TODO: Is it safe to immutably set this?
// TODO: The menu should (must?) not contain any items!
// TODO: Setting this and pressing the close button doesn't work in winit
unsafe { msg_send![self, setServicesMenu: menu] }
}
// TODO: registerServicesMenuSendTypes
/// Get the menu that is currently assigned as the help menu, or `None` if the system is configured to autodetect this.
#[doc(alias = "helpMenu")]
pub fn help_menu<'p>(&self, pool: &'p AutoreleasePool) -> Option<&'p Menu> {
unsafe { msg_send![self, helpMenu] }
}
/// Set the global menu that should have the spotlight Help Search
/// functionality at the top of it.
///
/// If this is set to `None`, the system will place the search bar somewhere
/// else, usually on an item named "Help" (unknown if localization applies).
/// To prevent this, specify a menu that does not appear anywhere.
#[doc(alias = "setHelpMenu")]
#[doc(alias = "setHelpMenu:")]
pub fn set_help_menu(&self, menu: Option<&Menu>) {
// TODO: Is it safe to immutably set this?
unsafe { msg_send![self, setHelpMenu: menu] }
}
// TODO: applicationDockMenu (the application delegate should implement this function)
#[doc(alias = "menuBarVisible")]
pub fn menubar_visible(&self) -> bool {
let visible: BOOL = unsafe { msg_send![class!(NSMenu), menuBarVisible] };
visible != NO
}
/// Hide or show the menubar for the entire application.
/// This also hides or shows the yellow minimize button.
///
/// Might silently fail to set the menubar visible if in fullscreen mode or similar.
#[doc(alias = "setMenuBarVisible")]
#[doc(alias = "setMenuBarVisible:")]
pub fn set_menubar_visible(&self, visible: bool) {
let visible: BOOL = if visible { YES } else { NO };
unsafe { msg_send![class!(NSMenu), setMenuBarVisible: visible] }
}
// Only available on the global menu bar object
// #[doc(alias = "menuBarHeight")]
// pub fn global_height(&self) -> f64 {
// let height: CGFloat = unsafe { msg_send![self, menuBarHeight] };
// height
// }
}
#[cfg(test)]
mod tests {
use super::*;
use objc::rc::autoreleasepool;
fn init_app() -> &'static InitializedApplication {
unimplemented!()
}
fn create_menu() -> Id<Menu, Owned> {
unimplemented!()
}
#[test]
#[ignore = "not implemented"]
fn test_services_menu() {
let app = init_app();
let menu1 = create_menu();
let menu2 = create_menu();
autoreleasepool(|pool| {
assert!(app.services_menu(pool).is_none());
app.set_services_menu(&menu1);
assert_eq!(app.services_menu(pool).unwrap(), &*menu1);
app.set_services_menu(&menu2);
assert_eq!(app.services_menu(pool).unwrap(), &*menu2);
// At this point `menu1` still shows as a services menu...
});
}
}