summary history branches tags files
commit:bff5c521250af4064ba972d3f3f539d8ebae8e96
author:Trevor Bentley
committer:GitHub
date:Sun Jul 30 15:00:22 2017 +0200
parents:6233cb4575014f374c2f2549b2438f9e32f9d809, bec1296491b90ffd0d2d67574c72d37837988e76
Merge pull request #11 from mrmekon/fruitbasket

Use fruitbasket to trampoline example into an app bundle
diff --git a/Cargo.toml b/Cargo.toml
line changes: +4/-0
index bac353c..55903d3
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -30,6 +30,10 @@ cocoa = "0.9.2"
 objc-foundation = "0.1.1"
 objc_id = "0.1"
 
+[dependencies.fruitbasket]
+version = "0.4"
+features = ["logging"]
+
 [target."cfg(target_os = \"macos\")".dependencies.objc]
 version = "0.2.2"
 features = ["exception"]

diff --git a/README.md b/README.md
line changes: +2/-2
index bd45fa5..a5cbb0c
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ Source for following screencast in `examples/example.rs`
 
 ## Run the example
 
-`$ cargo test && cargo run --example example_launcher`
+`$ cargo test && cargo run --example example`
 
 ## Cargo Crate
 
@@ -32,7 +32,7 @@ Note that access to the Control Strip is forbidden by Apple's guidelines.  Rubra
 
 To communicate with the Touch Bar service, apps using Rubrail *must* be executed from an app bundle (.app).  Running the executable directly will not crash, but the icon will not be registered with the Touch Bar service, so your Touch Bar UI will be unavailable.
 
-The included example comes with a bundling script (examples/example.sh) and a launcher (examples/example_launcher.rs) to move itself into an app bundle and execute.
+The included example uses the [fruitbasket](https://github.com/mrmekon/fruitbasket) crate to automatically re-launch itself as an OS X app bundle.
 
 
 ### Limitations

diff --git a/examples/example.rs b/examples/example.rs
line changes: +25/-15
index 24c4a2b..b6b18a1
--- a/examples/example.rs
+++ b/examples/example.rs
@@ -6,16 +6,16 @@
 // Usage notes:
 //
 // To access the touchbar, Rubrail *must* execute inside an OS X app bundle.
-// Since Cargo doesn't do that itself, this example is packaged with a second
-// wrapper example (example_launcher), and a bundling script (example.sh).
-// They can all be used together to setup and run the example from a bundle.
+// Since Cargo doesn't do that itself, this example uses the Trampoline feature
+// of the fruitbasket crate to relaunch itself in an app bundle.
 //
 // Simply run:
 //
-// $ cargo test && cargo run --example example_launcher
+// $ cargo test && cargo run --example example
 //
 //
 extern crate rubrail;
+extern crate fruitbasket;
 
 use rubrail::Touchbar;
 use rubrail::TTouchbar;
@@ -52,7 +52,7 @@ impl TScrubberData for TouchbarHandler {
     }
 }
 
-fn populate(bar_rc: Rc<RefCell<Touchbar>>, count: u32) {
+fn populate(bar_rc: Rc<RefCell<Touchbar>>, count: u32, stopper: fruitbasket::FruitStopper) {
     // Get touchbar from the refcell.  It's wrapped in a cell so
     // it can be passed around in the button callbacks.
     let mut tb = (bar_rc).borrow_mut();
@@ -61,14 +61,15 @@ fn populate(bar_rc: Rc<RefCell<Touchbar>>, count: u32) {
     let mut barid = tb.create_bar();
 
     // Create a quit button for root bar
-    let quit_id = tb.create_button(None, Some("Quit"), Box::new(move |_| {rubrail::app::quit()}));
+    let quit_stopper = stopper.clone();
+    let quit_id = tb.create_button(None, Some("Quit"), Box::new(move |_| {quit_stopper.stop();}));
 
     // Create an action button for the root bar.  When clicked, it will
     // close the bar and re-create itself.
     let bar_copy = bar_rc.clone();
     let text = format!("button{}", count);
     let button1_id = tb.create_button(None, Some(&text), Box::new(move |_| {
-        populate(bar_copy.clone(), count+1)
+        populate(bar_copy.clone(), count+1, stopper.clone())
     }));
 
     // Create a text label for the root bar
@@ -153,18 +154,27 @@ fn populate(bar_rc: Rc<RefCell<Touchbar>>, count: u32) {
 
 fn main() {
     // Write log to home directory
-    rubrail::app::create_logger(".rubrail.log");
-
-    // Initialize OS X application.  A real app should probably not use this.
-    rubrail::app::init_app();
+    fruitbasket::create_logger(".rubrail.log", fruitbasket::LogDir::Home, 5, 2).unwrap();
+
+    // Initialize OS X application.
+    let icon = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+        .join("examples").join("icon.png");
+    let mut nsapp = fruitbasket::Trampoline::new(
+        "rubrail_example", "rubrail_example", "com.trevorbentley.rubrail_example")
+        .icon("icon.png")
+        .version(env!("CARGO_PKG_VERSION"))
+        .plist_key("LSBackgroundOnly", "1")
+        .resource(icon.to_str().unwrap())
+        .build(fruitbasket::InstallDir::Custom("target/".to_string())).unwrap();
+    nsapp.set_activation_policy(fruitbasket::ActivationPolicy::Prohibited);
 
     // Initialize the touchbar
     let bar_rc = Rc::new(RefCell::new(Touchbar::alloc("bar")));
 
+    let stopper = nsapp.stopper();
     // Populate the touchbar with UI elements
-    populate(bar_rc.clone(), 1);
+    populate(bar_rc.clone(), 1, stopper);
 
-    // Enter OS X application loop.  A real application should probably implement
-    // this itself.
-    rubrail::app::run_forever();
+    // Enter OS X application loop.
+    nsapp.run(fruitbasket::RunPeriod::Forever);
 }

diff --git a/examples/example.sh b/examples/example.sh
line changes: +0/-34
index 9188b47..0000000
--- a/examples/example.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/bash
-
-DST="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
-APPDIR="RubrailExample.app"
-
-
-echo "Building OS X app..."
-
-rm -rf "$DST/$APPDIR"
-mkdir "$DST/$APPDIR/"
-mkdir "$DST/$APPDIR/Contents/"
-mkdir "$DST/$APPDIR/Contents/Resources/"
-mkdir "$DST/$APPDIR/Contents/MacOS/"
-
-cp -a "$DST/example" "$DST/$APPDIR/Contents/MacOS/"
-cp -a "$DST/icon.png" "$DST/$APPDIR/Contents/Resources/"
-
-cat > "$DST/$APPDIR/Contents/Info.plist" << EOF
-{
-   CFBundleName = rubrail;
-   CFBundleDisplayName = RubrailExample;
-   CFBundleIdentifier = "com.trevorbentley.rubrail";
-   CFBundleExecutable = example;
-   CFBundleIconFile = "rubrail.icns";
-
-   CFBundleVersion = "0.0.2";
-   CFBundleInfoDictionaryVersion = "6.0";
-   CFBundlePackageType = APPL;
-   CFBundleSignature = xxxx;
-
-   LSMinimumSystemVersion = "10.10.0";
-}
-EOF
-echo "Done!"

diff --git a/examples/example_launcher.rs b/examples/example_launcher.rs
line changes: +0/-31
index d9d160e..0000000
--- a/examples/example_launcher.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-
-fn main() {
-    let exe = std::env::current_exe().unwrap();
-    let mut exe_dir = exe.clone();
-    exe_dir.pop();
-    let mut resource_dir = exe_dir.clone();
-    resource_dir.pop();
-    resource_dir.pop();
-    resource_dir.pop();
-    resource_dir.push("examples");
-    let mut example_app = exe_dir.clone();
-    example_app.push("RubrailExample.app");
-    let mut icon_src = resource_dir.clone();
-    icon_src.push("icon.png");
-    let mut script_src = resource_dir.clone();
-    script_src.push("example.sh");
-    let mut icon_dst = exe_dir.clone();
-    icon_dst.push("icon.png");
-    let mut script_dst = exe_dir.clone();
-    script_dst.push("example.sh");
-    let script_exe = script_dst.clone();
-    std::fs::copy(icon_src, icon_dst).unwrap();
-    std::fs::copy(script_src, script_dst).unwrap();
-    let _ = std::process::Command::new(script_exe)
-        .output()
-        .expect("Failed to run bundling script");
-    let _ = std::process::Command::new("open")
-        .arg(example_app)
-        .output()
-        .expect("Failed to launch app bundle");
-}

diff --git a/src/lib.rs b/src/lib.rs
line changes: +4/-133
index 953f8d1..4a7d8d1
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -28,11 +28,11 @@
 //! be registered with the system and will not display on the Control Strip,
 //! making it inaccessible to the user.
 //!
-//! The examples are bundled with a script to generate a minimal valid app
-//! bundle, and a wrapper example to move the real example into a bundle and
-//! execute it.  You can execute the examples correctly with this comand:
+//! The included example uses [fruitbasket](https://github.com/mrmekon/fruitbasket)
+//! to automatically bundle itself into an OS X app at runtime.  You can run the
+//! example with:
 //!
-//! `$ cargo test && cargo run --example example_launcher`
+//! `$ cargo test && cargo run --example example`
 //!
 //! # Memory Management
 //!
@@ -101,135 +101,6 @@ pub use dummy::DummyTouchbar as Touchbar;
 #[cfg(not(all(target_os = "macos", feature = "private_api")))]
 pub use dummy::util;
 
-/// Module for creating and running a simple Mac application
-///
-/// The `app` module contains helper functions for creating and running a very
-/// simple Mac application environment (NSApplication) with no window or dock
-/// icon.  It is provided here for the Rubrail examples to use, and may be
-/// useful for simple Touch Bar-only applications, but 'real' applications
-/// should probably implement the application logic themselves.
-pub mod app {
-    #[cfg(target_os = "macos")]
-    #[cfg(feature = "private_api")]
-    extern crate objc;
-    extern crate log4rs;
-    use std::env;
-    #[cfg(not(all(target_os = "macos", feature = "private_api")))]
-    use std::process;
-
-    #[cfg(target_os = "macos")]
-    #[cfg(feature = "private_api")]
-    /// Initialize a Mac application environment (NSApplication)
-    ///
-    /// This is a debug/convenience function for creating a window-less Mac
-    /// application.  It can be used in simple Touch Bar-only applications,
-    /// and is used by the examples, but more complex applications will want
-    /// to handle the application creation themselves.
-    ///
-    /// It initializes an NSApplication with the
-    /// _NSApplicationActivationPolicyAccessory_ policy, which means it will
-    /// have no window and no dock icon.
-    pub fn init_app() {
-        unsafe {
-            let cls = objc::runtime::Class::get("NSApplication").unwrap();
-            let app: *mut objc::runtime::Object = msg_send![cls, sharedApplication];
-            let _ = msg_send![app, setActivationPolicy: 1]; // NSApplicationActivationPolicyAccessory
-        }
-    }
-    #[cfg(not(all(target_os = "macos", feature = "private_api")))]
-    ///
-    pub fn init_app() {}
-
-    #[cfg(target_os = "macos")]
-    #[cfg(feature = "private_api")]
-    /// Run the application's event loop forever.
-    ///
-    /// This is a debug/convenience function to run the Mac application's event loop
-    /// forever, without returning.  It can be used with `init_app()` to run a
-    /// simple application or example, but more complicated applications should
-    /// implement the run loop themselves.
-    pub fn run_forever() {
-        unsafe {
-            let cls = objc::runtime::Class::get("NSApplication").unwrap();
-            let app: *mut objc::runtime::Object = msg_send![cls, sharedApplication];
-            let _ = msg_send![app, run];
-        }
-    }
-    #[cfg(not(all(target_os = "macos", feature = "private_api")))]
-    ///
-    pub fn run_forever() { loop {} }
-
-    #[cfg(target_os = "macos")]
-    #[cfg(feature = "private_api")]
-    /// Terminate the application run loop and quit.
-    ///
-    /// This is a debug/convenience function to terminate the Mac application and
-    /// end the process.  This can be used with `init_app()` and `run_forever()` for
-    /// simple applications and examples, but more complex applications will want
-    /// to implement custom handling for terminating.
-    pub fn quit() {
-        unsafe {
-            let cls = objc::runtime::Class::get("NSApplication").unwrap();
-            let app: *mut objc::runtime::Object = msg_send![cls, sharedApplication];
-            let _ = msg_send![app, terminate: 0];
-        }
-    }
-    #[cfg(not(all(target_os = "macos", feature = "private_api")))]
-    ///
-    pub fn quit() {
-        process::exit(0);
-    }
-
-    /// Enable logging to a file in the user's home directory
-    ///
-    /// This is a debug function which redirects the log output of Rubrail and its
-    /// examples to a text file of the given name in the user's home directory.
-    ///
-    /// # Arguments
-    ///
-    /// * `filename` - the filename **without** a path.  It is always saved in the
-    /// home directory.
-    ///
-    /// # Example
-    ///
-    /// ```rust,no_run
-    /// extern crate rubrail;
-    /// #[macro_use]
-    /// extern crate log;
-    /// fn main() {
-    ///   rubrail::app::create_logger(".rubrail.log");
-    ///   info!("This message is in ~/.rubrail.log");
-    /// }
-    /// ```
-    pub fn create_logger(filename: &str) {
-        use log::LogLevelFilter;
-        use self::log4rs::append::console::ConsoleAppender;
-        use self::log4rs::append::file::FileAppender;
-        use self::log4rs::encode::pattern::PatternEncoder;
-        use self::log4rs::config::{Appender, Config, Logger, Root};
-
-        let log_path = format!("{}/{}", env::home_dir().unwrap().display(), filename);
-        let stdout = ConsoleAppender::builder()
-            .encoder(Box::new(PatternEncoder::new("{m}{n}")))
-            .build();
-        let requests = FileAppender::builder()
-            .build(&log_path)
-            .unwrap();
-
-        let config = Config::builder()
-            .appender(Appender::builder().build("stdout", Box::new(stdout)))
-            .appender(Appender::builder().build("requests", Box::new(requests)))
-            .logger(Logger::builder().build("app::backend::db", LogLevelFilter::Info))
-            .logger(Logger::builder()
-                    .appender("requests")
-                    .additive(false)
-                    .build("app::requests", LogLevelFilter::Info))
-            .build(Root::builder().appender("stdout").appender("requests").build(LogLevelFilter::Info))
-            .unwrap();
-        let _ = log4rs::init_config(config).unwrap();
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use Touchbar;