mirror of
https://github.com/gopher64/gopher64.git
synced 2026-11-12 09:09:01 +01:00
Set Gopher64 as option for N64 ROM files on MacOS (#1364)
* add CFBundleDocumentTypes for MacOS * macOS: launch ROMs opened via Finder or open -a (#1367) Handle the 'open documents' Apple Event so ROMs can be opened by double-click, 'Open With' or 'open -a Gopher64 <rom>'. Files opened this way get a sandbox extension, so this works with the sandbox enabled. * tie obj2 deps to gui build --------- Co-authored-by: Michael Fuerst <29622305+lomdar67@users.noreply.github.com>
This commit is contained in:
1 parent
45f5c30011
commit
913cd4540d
6 files changed
+155
-1
No files matched your search
Generated
+26
@@ -2401,6 +2401,8 @@ dependencies = [
|
||||
"matchbox_socket",
|
||||
"mimalloc",
|
||||
"obfstr",
|
||||
"objc2 0.6.5",
|
||||
"objc2-foundation 0.3.2",
|
||||
"open",
|
||||
"postcard",
|
||||
"quick-xml 0.42.0",
|
||||
@@ -4211,6 +4213,18 @@ dependencies = [
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-services"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "583300ad934cba24ff5292aee751ecc070f7ca6b39a574cc21b7b5e588e06a0b"
|
||||
dependencies = [
|
||||
"dispatch2",
|
||||
"objc2 0.6.5",
|
||||
"objc2-core-foundation",
|
||||
"objc2-security",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-text"
|
||||
version = "0.3.2"
|
||||
@@ -4250,6 +4264,7 @@ dependencies = [
|
||||
"block2 0.6.2",
|
||||
"objc2 0.6.5",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-services",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4326,6 +4341,17 @@ dependencies = [
|
||||
"objc2-metal 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-security"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"objc2 0.6.5",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-symbols"
|
||||
version = "0.2.2"
|
||||
|
||||
+6
-1
@@ -6,7 +6,7 @@ rust-version = "1.99.1"
|
||||
|
||||
[features]
|
||||
default = ["gui"]
|
||||
gui = ["dep:rfd", "dep:slint", "dep:open", "dep:slint-build"]
|
||||
gui = ["dep:rfd", "dep:slint", "dep:open", "dep:slint-build", "dep:objc2", "dep:objc2-foundation"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
@@ -55,6 +55,10 @@ sdl3-sys = { version = "0.7", features = ["build-from-source"] }
|
||||
sdl3-ttf-sys = { version = "0.7", features = ["build-from-source", "no-sdlttf-harfbuzz", "no-sdlttf-plutosvg"] }
|
||||
webpki-roots = "1.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc2 = { version = "0.6", optional = true }
|
||||
objc2-foundation = { version = "0.3", optional = true, default-features = false, features = ["std", "NSAppleEventManager", "NSAppleEventDescriptor", "NSURL", "NSNotification", "NSString", "objc2-core-services"] }
|
||||
|
||||
[build-dependencies]
|
||||
winresource = "0.1"
|
||||
bindgen = "0.73"
|
||||
@@ -79,3 +83,4 @@ icon = ["data/icon/gopher64_128x128.png","data/icon/gopher64_256x256.png","data/
|
||||
[package.metadata.bundle.osx]
|
||||
frameworks = ["/opt/homebrew/opt/molten-vk/lib/libMoltenVK.dylib"]
|
||||
minimum_system_version = "15.0"
|
||||
info_plist_exts = ["data/macos/document_types.plist"]
|
||||
@@ -0,0 +1,17 @@
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Nintendo 64 ROM</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>n64</string>
|
||||
<string>v64</string>
|
||||
<string>z64</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
@@ -8,6 +8,8 @@ pub mod config;
|
||||
pub mod gui;
|
||||
pub mod input;
|
||||
pub mod input_profile;
|
||||
#[cfg(all(feature = "gui", target_os = "macos"))]
|
||||
pub mod macos;
|
||||
#[cfg(feature = "gui")]
|
||||
pub mod netplay;
|
||||
#[cfg(feature = "gui")]
|
||||
|
||||
@@ -568,6 +568,13 @@ pub fn app_window(
|
||||
run_with_path(app.as_weak(), path);
|
||||
}
|
||||
|
||||
// Launch ROMs opened via Finder or `open -a` (macOS "open documents" Apple Event)
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let weak = app.as_weak();
|
||||
ui::macos::register_open_documents_handler(move |path| run_with_path(weak.clone(), path));
|
||||
}
|
||||
|
||||
app.run().unwrap();
|
||||
retroachievements::shutdown_client();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
//! macOS-specific integration.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::NSObject;
|
||||
use objc2::{DefinedClass, MainThreadMarker, MainThreadOnly, define_class, msg_send, sel};
|
||||
use objc2_foundation::{
|
||||
NSAppleEventDescriptor, NSAppleEventManager, NSNotification, NSNotificationCenter, NSString,
|
||||
};
|
||||
|
||||
// Apple Event codes (FourCharCode), see AppleEvents.h / AEDataModel.h.
|
||||
const K_CORE_EVENT_CLASS: u32 = u32::from_be_bytes(*b"aevt"); // kCoreEventClass
|
||||
const K_AE_OPEN_DOCUMENTS: u32 = u32::from_be_bytes(*b"odoc"); // kAEOpenDocuments
|
||||
const KEY_DIRECT_OBJECT: u32 = u32::from_be_bytes(*b"----"); // keyDirectObject
|
||||
|
||||
struct Ivars {
|
||||
on_open: Box<dyn Fn(PathBuf)>,
|
||||
}
|
||||
|
||||
define_class!(
|
||||
#[unsafe(super(NSObject))]
|
||||
#[thread_kind = MainThreadOnly]
|
||||
#[name = "Gopher64OpenDocumentsHandler"]
|
||||
#[ivars = Ivars]
|
||||
struct OpenDocumentsHandler;
|
||||
|
||||
impl OpenDocumentsHandler {
|
||||
// AppKit installs its own Apple Event handlers during `finishLaunching`
|
||||
// (inside the event loop), which would replace ours if we registered it
|
||||
// earlier. "Will finish launching" is the point Apple recommends for
|
||||
// installing custom handlers. winit owns the app delegate, so we listen
|
||||
// for the notification instead.
|
||||
#[unsafe(method(applicationWillFinishLaunching:))]
|
||||
fn application_will_finish_launching(&self, _notification: &NSNotification) {
|
||||
let manager = NSAppleEventManager::sharedAppleEventManager();
|
||||
unsafe {
|
||||
manager.setEventHandler_andSelector_forEventClass_andEventID(
|
||||
self,
|
||||
sel!(handleOpenDocuments:withReplyEvent:),
|
||||
K_CORE_EVENT_CLASS,
|
||||
K_AE_OPEN_DOCUMENTS,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(method(handleOpenDocuments:withReplyEvent:))]
|
||||
fn handle_open_documents(
|
||||
&self,
|
||||
event: &NSAppleEventDescriptor,
|
||||
_reply: &NSAppleEventDescriptor,
|
||||
) {
|
||||
let Some(list) = event.paramDescriptorForKeyword(KEY_DIRECT_OBJECT) else {
|
||||
return;
|
||||
};
|
||||
// Only the first file is used, Gopher64 runs one ROM at a time.
|
||||
let Some(url) = list
|
||||
.descriptorAtIndex(1)
|
||||
.and_then(|item| item.fileURLValue())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(path) = url.to_file_path() else {
|
||||
return;
|
||||
};
|
||||
(self.ivars().on_open)(path);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/// Handles ROMs opened via Finder (double-click, "Open With") or `open -a Gopher64 <rom>`.
|
||||
///
|
||||
/// macOS delivers these as an "open documents" Apple Event. Files opened this way
|
||||
/// get a sandbox extension, so the ROM can be read without extra permissions.
|
||||
/// Must be called on the main thread before the event loop starts.
|
||||
pub fn register_open_documents_handler(on_open: impl Fn(PathBuf) + 'static) {
|
||||
let mtm = MainThreadMarker::new().expect("must be called on the main thread");
|
||||
let handler = OpenDocumentsHandler::alloc(mtm).set_ivars(Ivars {
|
||||
on_open: Box::new(on_open),
|
||||
});
|
||||
let handler: Retained<OpenDocumentsHandler> = unsafe { msg_send![super(handler), init] };
|
||||
|
||||
let center = NSNotificationCenter::defaultCenter();
|
||||
let name = NSString::from_str("NSApplicationWillFinishLaunchingNotification");
|
||||
unsafe {
|
||||
center.addObserver_selector_name_object(
|
||||
&handler,
|
||||
sel!(applicationWillFinishLaunching:),
|
||||
Some(&name),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
// Neither NSNotificationCenter nor NSAppleEventManager retain the handler,
|
||||
// so keep it alive for the lifetime of the app.
|
||||
std::mem::forget(handler);
|
||||
}
|
||||
Reference in new issue
Block a user