diff --git a/src/application.rs b/src/application.rs index 1d318d21678..57475659556 100644 --- a/src/application.rs +++ b/src/application.rs @@ -222,4 +222,13 @@ pub trait ApplicationHandler { fn memory_warning(&mut self, event_loop: &ActiveEventLoop) { let _ = event_loop; } + + /// Emitted when the application has received a URL, through a + /// custom URL handler. + /// + /// Only supported on macOS. + fn received_url(&mut self, event_loop: &ActiveEventLoop, url: String) { + let _ = event_loop; + let _ = url; + } } diff --git a/src/event.rs b/src/event.rs index 5cd3877a263..8b92684919d 100644 --- a/src/event.rs +++ b/src/event.rs @@ -103,6 +103,21 @@ pub enum Event { /// /// [`ApplicationHandler::memory_warning`]: crate::application::ApplicationHandler::memory_warning MemoryWarning, + + /// Emitted when the event loop receives an event that only occurs on some specific platform. + PlatformSpecific(PlatformSpecific), +} + +/// Describes an event from some specific platform. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PlatformSpecific { + MacOS(MacOS), +} + +/// Describes an event that only happens in `MacOS`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MacOS { + ReceivedUrl(String), } impl Event { @@ -119,6 +134,7 @@ impl Event { Suspended => Ok(Suspended), Resumed => Ok(Resumed), MemoryWarning => Ok(MemoryWarning), + PlatformSpecific(event) => Ok(PlatformSpecific(event)), } } } diff --git a/src/event_loop.rs b/src/event_loop.rs index c0f64d9e4b4..a2b5180c6ff 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -20,7 +20,7 @@ use web_time::{Duration, Instant}; use crate::application::ApplicationHandler; use crate::error::{EventLoopError, OsError}; -use crate::event::Event; +use crate::event::{self, Event}; use crate::monitor::MonitorHandle; use crate::platform_impl; use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes}; @@ -633,5 +633,8 @@ pub(crate) fn dispatch_event_for_app>( Event::AboutToWait => app.about_to_wait(event_loop), Event::LoopExiting => app.exiting(event_loop), Event::MemoryWarning => app.memory_warning(event_loop), + Event::PlatformSpecific(event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(url))) => { + app.received_url(event_loop, url) + }, } } diff --git a/src/platform_impl/macos/app_delegate.rs b/src/platform_impl/macos/app_delegate.rs index ffb25abcafb..cb03eaea2cd 100644 --- a/src/platform_impl/macos/app_delegate.rs +++ b/src/platform_impl/macos/app_delegate.rs @@ -7,7 +7,9 @@ use std::time::Instant; use objc2::rc::Id; use objc2::runtime::AnyObject; -use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; +use objc2::{ + class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass, +}; use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate}; use objc2_foundation::{MainThreadMarker, NSObject, NSObjectProtocol, NSSize}; @@ -17,7 +19,9 @@ use super::observer::{EventLoopWaker, RunLoop}; use super::window::WinitWindow; use super::{menu, WindowId, DEVICE_ID}; use crate::dpi::PhysicalSize; -use crate::event::{DeviceEvent, Event, InnerSizeWriter, StartCause, WindowEvent}; +use crate::event::{ + DeviceEvent, Event, InnerSizeWriter, MacOS, PlatformSpecific, StartCause, WindowEvent, +}; use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow}; use crate::window::WindowId as RootWindowId; @@ -54,6 +58,14 @@ pub(super) struct State { pending_redraw: RefCell>, } +/// Apple constants +#[allow(non_upper_case_globals)] +pub const kInternetEventClass: u32 = 0x4755524c; +#[allow(non_upper_case_globals)] +pub const kAEGetURL: u32 = 0x4755524c; +#[allow(non_upper_case_globals)] +pub const keyDirectObject: u32 = 0x2d2d2d2d; + declare_class!( #[derive(Debug)] pub(super) struct ApplicationDelegate; @@ -116,6 +128,33 @@ declare_class!( } } + #[method(applicationWillFinishLaunching:)] + fn will_finish_launching(&self, _sender: Option<&AnyObject>) { + trace_scope!("applicationWillFinishLaunching"); + + unsafe { + let event_manager = class!(NSAppleEventManager); + let shared_manager: *mut AnyObject = + msg_send![event_manager, sharedAppleEventManager]; + + let () = msg_send![shared_manager, + setEventHandler: self + andSelector: sel!(handleEvent:withReplyEvent:) + forEventClass: kInternetEventClass + andEventID: kAEGetURL + ]; + } + } + + #[method(handleEvent:withReplyEvent:)] + fn handle_url(&self, event: *mut AnyObject, _reply: u64) { + if let Some(string) = parse_url(event) { + self.handle_event(Event::PlatformSpecific(PlatformSpecific::MacOS( + MacOS::ReceivedUrl(string), + ))); + } + } + #[method(applicationWillTerminate:)] fn will_terminate(&self, _sender: Option<&AnyObject>) { trace_scope!("applicationWillTerminate:"); @@ -459,3 +498,21 @@ fn window_activation_hack(app: &NSApplication) { } }) } + +fn parse_url(event: *mut AnyObject) -> Option { + unsafe { + let class: u32 = msg_send![event, eventClass]; + let id: u32 = msg_send![event, eventID]; + if class != kInternetEventClass || id != kAEGetURL { + return None; + } + let subevent: *mut AnyObject = msg_send![event, paramDescriptorForKeyword: keyDirectObject]; + let nsstring: *mut AnyObject = msg_send![subevent, stringValue]; + let cstr: *const i8 = msg_send![nsstring, UTF8String]; + if !cstr.is_null() { + Some(std::ffi::CStr::from_ptr(cstr).to_string_lossy().into_owned()) + } else { + None + } + } +}