Handle macOS kAEOpenDocuments for Finder Open With support
This commit is contained in:
parent
97653580e5
commit
2237fdbab3
|
|
@ -29,3 +29,7 @@ dirs = "6"
|
|||
arboard = "3"
|
||||
zip = "2"
|
||||
muda = "0.17"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc2 = "0.6"
|
||||
objc2-foundation = { version = "0.3", features = ["NSAppleEventDescriptor", "NSAppleEventManager", "NSString"] }
|
||||
|
|
|
|||
|
|
@ -6,9 +6,15 @@ use iced::widget::{
|
|||
use iced::{Background, Border, Color, Element, Fill, Length, Padding, Shadow, Subscription};
|
||||
use iced::{mouse, keyboard, window, Point, Vector};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
use std::time::Duration;
|
||||
|
||||
static OPEN_QUEUE: OnceLock<Mutex<Vec<PathBuf>>> = OnceLock::new();
|
||||
|
||||
pub fn open_queue() -> &'static Mutex<Vec<PathBuf>> {
|
||||
OPEN_QUEUE.get_or_init(|| Mutex::new(Vec::new()))
|
||||
}
|
||||
|
||||
use cord_expr::{classify, classify_from, expr_to_sdf, parse_expr, parse_expr_scene, ExprInfo};
|
||||
use crate::highlight::{CordHighlighter, CordHighlighterSettings, format_token};
|
||||
use crate::viewport::SdfViewport;
|
||||
|
|
@ -334,6 +340,11 @@ impl App {
|
|||
self.menu_ready = true;
|
||||
}
|
||||
self.poll_menu_events();
|
||||
if let Ok(mut q) = open_queue().lock() {
|
||||
for path in q.drain(..) {
|
||||
self.open_path(&path);
|
||||
}
|
||||
}
|
||||
let new_line = self.source.cursor().position.line;
|
||||
if new_line != self.cursor_line {
|
||||
self.cursor_line = new_line;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,133 @@ fn title(app: &App) -> String {
|
|||
app.title()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod apple_events {
|
||||
use std::path::PathBuf;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::{AnyObject, NSObject};
|
||||
use objc2::{define_class, msg_send, sel, AnyThread};
|
||||
|
||||
// kCoreEventClass = 'aevt', kAEOpenDocuments = 'odoc', keyDirectObject = '----'
|
||||
const KAEVT: u32 = u32::from_be_bytes(*b"aevt");
|
||||
const KODOC: u32 = u32::from_be_bytes(*b"odoc");
|
||||
const KEY_DIRECT_OBJECT: u32 = u32::from_be_bytes(*b"----");
|
||||
|
||||
define_class!(
|
||||
#[unsafe(super(NSObject))]
|
||||
#[name = "CordOpenHandler"]
|
||||
struct OpenHandler;
|
||||
|
||||
impl OpenHandler {
|
||||
#[unsafe(method(handleOpenEvent:withReplyEvent:))]
|
||||
fn handle_open_event(&self, event: &AnyObject, _reply: &AnyObject) {
|
||||
handle_odoc(event);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl OpenHandler {
|
||||
fn new() -> Retained<Self> {
|
||||
let this = Self::alloc().set_ivars(());
|
||||
unsafe { msg_send![super(this), init] }
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_odoc(event: &AnyObject) {
|
||||
let file_list: Option<Retained<AnyObject>> = unsafe {
|
||||
msg_send![event, paramDescriptorForKeyword: KEY_DIRECT_OBJECT]
|
||||
};
|
||||
let file_list = match file_list {
|
||||
Some(fl) => fl,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let count: isize = unsafe { msg_send![&*file_list, numberOfItems] };
|
||||
let queue = crate::app::open_queue();
|
||||
|
||||
for i in 1..=count {
|
||||
let desc: Option<Retained<AnyObject>> = unsafe {
|
||||
msg_send![&*file_list, descriptorAtIndex: i]
|
||||
};
|
||||
let desc = match desc {
|
||||
Some(d) => d,
|
||||
None => continue,
|
||||
};
|
||||
let url_str: Option<Retained<AnyObject>> = unsafe {
|
||||
msg_send![&*desc, stringValue]
|
||||
};
|
||||
let url_str = match url_str {
|
||||
Some(s) => s,
|
||||
None => continue,
|
||||
};
|
||||
let raw: *const std::ffi::c_char = unsafe { msg_send![&*url_str, UTF8String] };
|
||||
if raw.is_null() {
|
||||
continue;
|
||||
}
|
||||
let cstr = unsafe { std::ffi::CStr::from_ptr(raw) };
|
||||
let s = match cstr.to_str() {
|
||||
Ok(s) => s,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let path_str = if let Some(stripped) = s.strip_prefix("file://") {
|
||||
percent_decode(stripped)
|
||||
} else {
|
||||
s.to_string()
|
||||
};
|
||||
let path = PathBuf::from(&path_str);
|
||||
if path.exists() {
|
||||
if let Ok(mut q) = queue.lock() {
|
||||
q.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn percent_decode(input: &str) -> String {
|
||||
let mut out = Vec::with_capacity(input.len());
|
||||
let bytes = input.as_bytes();
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
if bytes[i] == b'%' && i + 2 < bytes.len() {
|
||||
if let Ok(byte) = u8::from_str_radix(
|
||||
&input[i + 1..i + 3],
|
||||
16,
|
||||
) {
|
||||
out.push(byte);
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
out.push(bytes[i]);
|
||||
i += 1;
|
||||
}
|
||||
String::from_utf8(out).unwrap_or_else(|_| input.to_string())
|
||||
}
|
||||
|
||||
pub fn register_open_handler() {
|
||||
use objc2_foundation::NSAppleEventManager;
|
||||
|
||||
let handler = OpenHandler::new();
|
||||
let mgr = NSAppleEventManager::sharedAppleEventManager();
|
||||
let sel = sel!(handleOpenEvent:withReplyEvent:);
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
&*mgr,
|
||||
setEventHandler: &*handler,
|
||||
andSelector: sel,
|
||||
forEventClass: KAEVT,
|
||||
andEventID: KODOC
|
||||
];
|
||||
}
|
||||
// Leak the handler so it lives for the process lifetime
|
||||
std::mem::forget(handler);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> iced::Result {
|
||||
#[cfg(target_os = "macos")]
|
||||
apple_events::register_open_handler();
|
||||
|
||||
iced::application(App::new, App::update, App::view)
|
||||
.title(title)
|
||||
.theme(theme)
|
||||
|
|
|
|||
Loading…
Reference in New Issue