gpui_base/input/base/
native.rs1#[cfg(target_os = "macos")]
2mod macos {
3 use std::{cell::RefCell, collections::HashMap, mem, ptr, sync::Once};
4
5 use gpui::Window;
6 use objc2::{
7 ffi, msg_send,
8 rc::Retained,
9 runtime::{AnyObject, AnyProtocol, Imp, Sel},
10 sel,
11 };
12 use objc2_foundation::NSString;
13 use raw_window_handle::{HasWindowHandle, RawWindowHandle};
14
15 static INSTALL_TEXT_CONTENT: Once = Once::new();
16
17 thread_local! {
18 static CONTENT_TYPES: RefCell<HashMap<usize, Retained<NSString>>> =
19 RefCell::new(HashMap::new());
20 }
21
22 pub fn set_text_content_type(window: &Window, content_type: Option<&str>) {
23 let Some(view) = ns_view(window) else {
24 return;
25 };
26
27 INSTALL_TEXT_CONTENT.call_once(|| install_text_content(view));
28 if view
29 .class()
30 .instance_method(sel!(setContentType:))
31 .is_none()
32 {
33 return;
34 }
35
36 let ns_content_type = content_type.map(NSString::from_str);
37 let ns_content_type = ns_content_type.as_ref().map_or(ptr::null_mut(), |value| {
38 Retained::as_ptr(value).cast_mut().cast::<AnyObject>()
39 });
40
41 unsafe {
42 let _: () = msg_send![view, setContentType: ns_content_type];
43 }
44 }
45
46 fn ns_view(window: &Window) -> Option<&AnyObject> {
47 let handle = HasWindowHandle::window_handle(window).ok()?;
48 let RawWindowHandle::AppKit(handle) = handle.as_raw() else {
49 return None;
50 };
51
52 Some(unsafe { &*(handle.ns_view.as_ptr() as *const AnyObject) })
53 }
54
55 fn install_text_content(view: &AnyObject) {
56 let class = view.class();
57 let class = class as *const _ as *mut _;
58
59 unsafe {
60 let protocol = AnyProtocol::get(c"NSTextContent");
61 if let Some(protocol) = protocol {
62 ffi::class_addProtocol(class, protocol);
63 }
64
65 let content_type_imp: Imp = mem::transmute(
66 content_type as unsafe extern "C-unwind" fn(&AnyObject, Sel) -> *mut AnyObject,
67 );
68 let set_content_type_imp: Imp = mem::transmute(
69 set_content_type as unsafe extern "C-unwind" fn(&AnyObject, Sel, *mut AnyObject),
70 );
71
72 ffi::class_addMethod(class, sel!(contentType), content_type_imp, c"@@:".as_ptr());
73 ffi::class_addMethod(
74 class,
75 sel!(setContentType:),
76 set_content_type_imp,
77 c"v@:@".as_ptr(),
78 );
79 }
80 }
81
82 unsafe extern "C-unwind" fn content_type(this: &AnyObject, _: Sel) -> *mut AnyObject {
83 let key = this as *const _ as usize;
84 CONTENT_TYPES.with(|content_types| {
85 content_types
86 .borrow()
87 .get(&key)
88 .map_or(ptr::null_mut(), |value| {
89 Retained::as_ptr(value).cast_mut().cast::<AnyObject>()
90 })
91 })
92 }
93
94 unsafe extern "C-unwind" fn set_content_type(this: &AnyObject, _: Sel, value: *mut AnyObject) {
95 let key = this as *const _ as usize;
96 CONTENT_TYPES.with(|content_types| {
97 let mut content_types = content_types.borrow_mut();
98 if value.is_null() {
99 content_types.remove(&key);
100 } else if let Some(value) = unsafe { Retained::retain(value.cast::<NSString>()) } {
101 content_types.insert(key, value);
102 }
103 });
104 }
105}
106
107#[cfg(target_os = "macos")]
108pub use macos::set_text_content_type;
109
110use gpui::SharedString;
111
112#[derive(Default)]
114pub struct NativeMenu {
115 pub items: Vec<NativeMenuItem>,
116}
117
118pub enum NativeMenuItem {
119 Separator,
120 Action {
121 label: SharedString,
122 disabled: bool,
123 action: Box<dyn gpui::Action>,
124 },
125}
126
127impl NativeMenu {
128 pub fn new() -> Self {
129 Self::default()
130 }
131
132 pub fn menu(self, label: impl Into<SharedString>, action: Box<dyn gpui::Action>) -> Self {
133 self.menu_with_disabled(label, false, action)
134 }
135
136 pub fn menu_with_disabled(
137 mut self,
138 label: impl Into<SharedString>,
139 disabled: bool,
140 action: Box<dyn gpui::Action>,
141 ) -> Self {
142 self.items.push(NativeMenuItem::Action {
143 label: label.into(),
144 disabled,
145 action,
146 });
147 self
148 }
149
150 pub fn separator(mut self) -> Self {
151 self.items.push(NativeMenuItem::Separator);
152 self
153 }
154}