Ely Office: six offline-first desktop apps in Rust and GPUI
ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git), ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
This commit is contained in:
817 files changed
+143101
No files matched your search
@@ -0,0 +1,105 @@
|
||||
//! Chromium's app protocol on GPUI's own `NSApplication` class.
|
||||
|
||||
use std::{cell::Cell, ffi::CStr};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use objc2::{
|
||||
ffi,
|
||||
runtime::{AnyClass, AnyObject, Bool, Imp, Sel},
|
||||
sel,
|
||||
};
|
||||
|
||||
thread_local! {
|
||||
/// NSApp lives on the main thread; one flag serves it.
|
||||
static SENDING: Cell<bool> = const { Cell::new(false) };
|
||||
}
|
||||
|
||||
extern "C-unwind" fn is_handling(_this: *mut AnyObject, _cmd: Sel) -> Bool {
|
||||
Bool::new(SENDING.get())
|
||||
}
|
||||
|
||||
extern "C-unwind" fn set_handling(_this: *mut AnyObject, _cmd: Sel, on: Bool) {
|
||||
SENDING.set(on.as_bool());
|
||||
}
|
||||
|
||||
extern "C-unwind" fn send_event(this: *mut AnyObject, _cmd: Sel, event: *mut AnyObject) {
|
||||
let was = SENDING.replace(true);
|
||||
let base = AnyClass::get(c"NSApplication").expect("AppKit has NSApplication");
|
||||
unsafe {
|
||||
let _: () = objc2::msg_send![super(&*this, base), sendEvent: event];
|
||||
}
|
||||
SENDING.set(was);
|
||||
}
|
||||
|
||||
fn add(class: &AnyClass, name: Sel, imp: Imp, types: &CStr) -> Result<()> {
|
||||
let added = unsafe {
|
||||
ffi::class_addMethod(
|
||||
class as *const AnyClass as *mut AnyClass,
|
||||
name,
|
||||
imp,
|
||||
types.as_ptr(),
|
||||
)
|
||||
};
|
||||
if !added.as_bool() {
|
||||
bail!("{} already has -{name}", class.name().to_string_lossy());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gives NSApp's class what CEF checks before it runs.
|
||||
pub fn adopt_app_protocol() -> Result<()> {
|
||||
let Some(app) = (unsafe { objc2_app_kit_nsapp() }) else {
|
||||
bail!("no NSApp yet");
|
||||
};
|
||||
let class = unsafe { &*ffi::object_getClass(app.cast()) };
|
||||
if class.responds_to(sel!(isHandlingSendEvent)) {
|
||||
return Ok(());
|
||||
}
|
||||
unsafe {
|
||||
add(
|
||||
class,
|
||||
sel!(isHandlingSendEvent),
|
||||
std::mem::transmute(is_handling as *const ()),
|
||||
c"c@:",
|
||||
)?;
|
||||
add(
|
||||
class,
|
||||
sel!(setHandlingSendEvent:),
|
||||
std::mem::transmute(set_handling as *const ()),
|
||||
c"v@:c",
|
||||
)?;
|
||||
add(
|
||||
class,
|
||||
sel!(sendEvent:),
|
||||
std::mem::transmute(send_event as *const ()),
|
||||
c"v@:@",
|
||||
)?;
|
||||
}
|
||||
// Chromium checks the two `Cr` protocols; CEF's own is header-only.
|
||||
for (name, needed) in [
|
||||
(c"CrAppProtocol", true),
|
||||
(c"CrAppControlProtocol", true),
|
||||
(c"CefAppProtocol", false),
|
||||
] {
|
||||
let protocol = unsafe { ffi::objc_getProtocol(name.as_ptr()) };
|
||||
match (protocol.is_null(), needed) {
|
||||
(true, true) => bail!("CEF did not register {}", name.to_string_lossy()),
|
||||
(true, false) => log::info!("web: {} is not registered", name.to_string_lossy()),
|
||||
(false, _) => unsafe {
|
||||
ffi::class_addProtocol(class as *const AnyClass as *mut AnyClass, protocol);
|
||||
},
|
||||
}
|
||||
}
|
||||
log::info!(
|
||||
"web: {} takes CefAppProtocol",
|
||||
class.name().to_string_lossy()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The shared application, if made.
|
||||
unsafe fn objc2_app_kit_nsapp() -> Option<*mut AnyObject> {
|
||||
let class = AnyClass::get(c"NSApplication")?;
|
||||
let app: *mut AnyObject = unsafe { objc2::msg_send![class, sharedApplication] };
|
||||
(!app.is_null()).then_some(app)
|
||||
}
|
||||
Reference in new issue
Block a user