summary history branches tags files
examples/register_url.rs
/// Example that launches as Mac App with custom URL scheme handler
///
/// In one terminal, build and run:
///
/// $ cargo build --features=logging --examples
/// $ ./target/debug/examples/register_url && tail -f ~/.fruitbasket_register_url.log
///
/// In a second terminal, open custom URL:
///
/// $ open fruitbasket://test
///
/// Log output will show that the example has received and printed the custom URL.
///
extern crate fruitbasket;
use fruitbasket::ActivationPolicy;
use fruitbasket::Trampoline;
use fruitbasket::FruitApp;
use fruitbasket::InstallDir;
use fruitbasket::RunPeriod;
use fruitbasket::FruitError;
use fruitbasket::FruitCallbackKey;
use std::path::PathBuf;

#[macro_use]
extern crate log;

fn main() {
    let _ = fruitbasket::create_logger(".fruitbasket_register_url.log", fruitbasket::LogDir::Home, 5, 2).unwrap();

    // Find the icon file from the Cargo project dir
    let icon = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("examples").join("icon.png");

    // Re-launch self in an app bundle if not already running from one.
    info!("Executable must run from App bundle.  Let's try:");
    let mut app = match Trampoline::new("fruitbasket_register_url", "fruitbasket", "com.trevorbentley.fruitbasket_register_url")
        .version("2.1.3")
        .icon("fruitbasket.icns")
        .plist_key("CFBundleSpokenName","\"fruit basket\"")
        .plist_keys(&vec![
            ("LSMinimumSystemVersion", "10.12.0"),
            ("LSBackgroundOnly", "1"),
        ])
        // Register "fruitbasket://" and "fbasket://" URL schemes in Info.plist
        .plist_raw_string("
CFBundleURLTypes = ( {
  CFBundleTypeRole = \"Viewer\";
  CFBundleURLName = \"Fruitbasket Example URL\";
  CFBundleURLSchemes = (\"fruitbasket\", \"fbasket\");
} );\n".into())
        .resource(icon.to_str().unwrap())
        .build(InstallDir::Temp) {
            Err(FruitError::UnsupportedPlatform(_)) => {
                info!("This is not a Mac.  App bundling is not supported.");
                info!("It is still safe to use FruitApp::new(), though the dummy app will do nothing.");
                FruitApp::new()
            },
            Err(FruitError::IOError(e)) => {
                info!("IO error! {}", e);
                std::process::exit(1);
            },
            Err(FruitError::GeneralError(e)) => {
                info!("General error! {}", e);
                std::process::exit(1);
            },
            Ok(app) => app,
        };

    // App is guaranteed to be running in a bundle now!

    // Make it a regular app in the dock.
    // Note: Because 'LSBackgroundOnly' is set to true in the Info.plist, the
    // app will launch backgrounded and will not take focus.  If we only did
    // that, the app would stay in 'Prohibited' mode and would not create a dock
    // icon.  By overriding the activation policy now, it will stay background
    // but create the Dock and menu bar entries.  This basically implements a
    // "pop-under" behavior.
    app.set_activation_policy(ActivationPolicy::Regular);

    // Register a callback for when the ObjC application finishes launching
    let stopper = app.stopper();
    app.register_callback(FruitCallbackKey::Method("applicationWillFinishLaunching:"),
                          Box::new(move |_event| {
                              info!("applicationDidFinishLaunching.");
                              stopper.stop();
                          }));

    // Run until callback is called
    info!("Spawned process started.  Run until applicationDidFinishLaunching.");
    let _ = app.run(RunPeriod::Forever);

    info!("Application launched.  Registering URL callbacks.");
    // Register a callback to get receive custom URL schemes from any Mac program
    app.register_apple_event(fruitbasket::kInternetEventClass, fruitbasket::kAEGetURL);
    let stopper = app.stopper();
    app.register_callback(FruitCallbackKey::Method("handleEvent:withReplyEvent:"),
                          Box::new(move |event| {
                              // Event is a raw NSAppleEventDescriptor.
                              // Fruitbasket has a parser for URLs.  Call that to get the URL:
                              let url: String = fruitbasket::parse_url_event(event);
                              info!("Received URL: {}", url);
                              stopper.stop();
                          }));

    let stopper = app.stopper();
    app.register_callback(
        FruitCallbackKey::Method("application:openFile:"),
        Box::new(move |file| {
            // File is a raw NSString.
            // Fruitbasket has a converter to Rust String:
            let file: String = fruitbasket::nsstring_to_string(file);
            info!("Received file: {}", file);
            stopper.stop();
        }),
    );

    // Run 'forever', until one of the URL or file callbacks fire
    info!("Spawned process running!");
    let _ = app.run(RunPeriod::Forever);
    info!("Run loop stopped after URL callback.");

    // Cleanly terminate
    fruitbasket::FruitApp::terminate(0);
    info!("This will not print.");
}