Skip to content

Commit

Permalink
Switch to self-bundling and Mac app handling with fruitbasket
Browse files Browse the repository at this point in the history
  • Loading branch information
mrmekon committed Jul 31, 2017
1 parent eae57f0 commit bbb3d0e
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 91 deletions.
13 changes: 6 additions & 7 deletions Cargo.toml
@@ -1,15 +1,14 @@
[package]
name = "connectr"
version = "0.1.1-rc"
authors = [ "Trevor Bentley <trevor@trevorbentley.com>" ]

authors = [ "Trevor Bentley <mrmekon@gmail.com>" ]
description = "Spotify Connect library and systray/menubar application for controlling Spotify devices."
keywords = ["spotify", "connect", "webapi", "systray", "menubar"]
categories = ["api-bindings"]
homepage = "https://github.com/mrmekon/connectr"
repository = "https://github.com/mrmekon/connectr"
documentation = "https://mrmekon.github.io/connectr/connectr/"
license = "Apache-2.0"

build = "build.rs"

[lib]
Expand Down Expand Up @@ -45,10 +44,10 @@ rust-ini = "0.9"
time = "0.1"
timer = "0.1.6"
chrono = "0.3.0"
libc = "0.2"
log = "0.3.7"
log4rs = "0.6.3"
ctrlc = "3.0.1"
fruitbasket = "0.4"

[target."cfg(windows)".dependencies]
#systray = "0.1.1"
Expand All @@ -57,23 +56,23 @@ systray = {path = "deps/systray-rs", version="0.1.1-connectr"}

[target."cfg(windows)".dependencies.rubrail]
default-features=false
version = "0.4"
version = "0.5"
#path = "deps/rubrail-rs"
#version="0.4.3-rc"

[target."cfg(all(unix, not(target_os = \"macos\")))".dependencies]

[target."cfg(all(unix, not(target_os = \"macos\")))".dependencies.rubrail]
default-features = false
version = "0.4"
version = "0.5"
#path = "deps/rubrail-rs"
#version = "0.4.3-rc"

[target."cfg(target_os = \"macos\")".dependencies]
cocoa = "0.9"
objc-foundation = "0.1.1"
objc_id = "0.1"
rubrail = "0.4"
rubrail = "0.5"
#rubrail = {path = "deps/rubrail-rs", version="0.4.3-rc"}

[target."cfg(target_os = \"macos\")".dependencies.objc]
Expand Down
Binary file modified connectr.xcf
Binary file not shown.
2 changes: 1 addition & 1 deletion deps/rubrail-rs
Binary file added icon/connectr.icns
Binary file not shown.
Binary file added icon/connectr1024.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions icon/gen_iconset.sh
@@ -0,0 +1,15 @@
#!/bin/bash
NAME="connectr"
mkdir "$NAME.iconset"
sips -z 16 16 "${NAME}1024.png" --out "$NAME.iconset"/icon_16x16.png
sips -z 32 32 "${NAME}1024.png" --out "$NAME.iconset"/icon_16x16@2x.png
sips -z 32 32 "${NAME}1024.png" --out "$NAME.iconset"/icon_32x32.png
sips -z 64 64 "${NAME}1024.png" --out "$NAME.iconset"/icon_32x32@2x.png
sips -z 128 128 "${NAME}1024.png" --out "$NAME.iconset"/icon_128x128.png
sips -z 256 256 "${NAME}1024.png" --out "$NAME.iconset"/icon_128x128@2x.png
sips -z 256 256 "${NAME}1024.png" --out "$NAME.iconset"/icon_256x256.png
sips -z 512 512 "${NAME}1024.png" --out "$NAME.iconset"/icon_256x256@2x.png
sips -z 512 512 "${NAME}1024.png" --out "$NAME.iconset"/icon_512x512.png
cp "${NAME}1024.png" "$NAME.iconset"/icon_512x512@2x.png
iconutil -c icns "$NAME.iconset"
rm -R "$NAME.iconset"
24 changes: 23 additions & 1 deletion src/main.rs
Expand Up @@ -12,6 +12,8 @@ use rubrail::ImageTemplate;
use rubrail::SpacerType;
use rubrail::SwipeState;

extern crate fruitbasket;

extern crate ctrlc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
Expand Down Expand Up @@ -785,6 +787,22 @@ fn create_spotify_thread(rx_cmd: Receiver<String>) -> SpotifyThread {
}

fn main() {
// Relaunch in a Mac app bundle if running on OS X and not already bundled.
let icon = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("icon").join("connectr.icns");
let touchbar_icon = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("connectr_80px_300dpi.png");
if let Ok(nsapp) = fruitbasket::Trampoline::new(
"connectr", "connectr", "com.trevorbentley.connectr")
.icon("connectr.icns")
.version(env!("CARGO_PKG_VERSION"))
.plist_key("LSBackgroundOnly", "1")
.resource(icon.to_str().unwrap())
.resource(touchbar_icon.to_str().unwrap())
.build(fruitbasket::InstallDir::Custom("target/".to_string())) {
nsapp.set_activation_policy(fruitbasket::ActivationPolicy::Prohibited);
}

create_logger();
info!("Started Connectr");

Expand Down Expand Up @@ -832,11 +850,15 @@ fn main() {
warn!("Didn't find Wine in search path.");
}

let mut need_redraw: bool = false;
while running.load(Ordering::SeqCst) {
if spotify_thread.rx.recv_timeout(Duration::from_millis(100)).is_ok() {
// TODO: && status.can_redraw()
need_redraw = true;
}
if need_redraw && status.can_redraw() {
clear_menu(&mut app, &mut status);
fill_menu(&mut app, &spotify_thread, &mut status, &mut touchbar);
need_redraw = false;
}
status.run(false);
}
Expand Down
88 changes: 16 additions & 72 deletions src/osx/mod.rs
Expand Up @@ -3,7 +3,9 @@ mod rustnsobject;
extern crate objc;
extern crate objc_foundation;
extern crate cocoa;
extern crate libc;

extern crate fruitbasket;
use self::fruitbasket::FruitApp;

pub use ::TStatusBar;
pub use ::NSCallback;
Expand All @@ -12,11 +14,8 @@ use objc::runtime::Class;

use self::cocoa::base::{nil, YES};
use self::cocoa::appkit::NSStatusBar;
use self::cocoa::foundation::{NSAutoreleasePool,NSString};
use self::cocoa::appkit::{NSApp,
NSApplication,
NSApplicationActivationPolicyAccessory,
NSMenu,
use self::cocoa::foundation::NSString;
use self::cocoa::appkit::{NSMenu,
NSMenuItem,
NSImage,
NSVariableStatusItemLength,
Expand All @@ -27,51 +26,31 @@ use self::rustnsobject::{NSObj, NSObjTrait, NSObjCallbackTrait};

use std::sync::mpsc::Sender;
use std::ptr;
use std::cell::Cell;
use std::ffi::CStr;
use std::thread::sleep;
use std::time::Duration;

extern crate objc_id;
use self::objc_id::Id;

pub type Object = objc::runtime::Object;

pub struct OSXStatusBar {
object: NSObj,
app: *mut objc::runtime::Object,
app: FruitApp,
status_bar_item: *mut objc::runtime::Object,
menu_bar: *mut objc::runtime::Object,

// Run loop state
// Keeping these in persistent state instead of recalculating saves quite a
// bit of CPU during idle.
pool: Cell<*mut objc::runtime::Object>,
run_count: Cell<u64>,
run_mode: *mut objc::runtime::Object,
run_date: *mut objc::runtime::Object,
}

impl TStatusBar for OSXStatusBar {
type S = OSXStatusBar;
fn new(tx: Sender<String>) -> OSXStatusBar {
let mut bar;
unsafe {
let app = NSApp();
let nsapp = FruitApp::new();
nsapp.set_activation_policy(fruitbasket::ActivationPolicy::Prohibited);
let status_bar = NSStatusBar::systemStatusBar(nil);
let date_cls = Class::get("NSDate").unwrap();
bar = OSXStatusBar {
app: app,
app: nsapp,
status_bar_item: status_bar.statusItemWithLength_(NSVariableStatusItemLength),
menu_bar: NSMenu::new(nil),
object: NSObj::alloc(tx).setup(),
pool: Cell::new(nil),
run_count: Cell::new(0),
run_mode: NSString::alloc(nil).init_str("kCFRunLoopDefaultMode"),
run_date: msg_send![date_cls, distantPast],
};
// Don't become foreground app on launch
bar.app.setActivationPolicy_(NSApplicationActivationPolicyAccessory);

// Default mode for menu bar items: blue highlight when selected
msg_send![bar.status_bar_item, setHighlightMode:YES];
Expand All @@ -85,7 +64,7 @@ impl TStatusBar for OSXStatusBar {
// See docs/icons.md for explanation of icon files.
// TODO: Use the full list of search paths.
let icon_name = "connectr_80px_300dpi";
let img_path = match bundled_resource_path(icon_name, "png") {
let img_path = match fruitbasket::FruitApp::bundled_resource_path(icon_name, "png") {
Some(path) => path,
None => format!("{}.png", icon_name),
};
Expand Down Expand Up @@ -118,7 +97,6 @@ impl TStatusBar for OSXStatusBar {
cb(sender, &s.tx);
}
));
let _: () = msg_send![app, finishLaunching];
}
bar
}
Expand Down Expand Up @@ -212,27 +190,11 @@ impl TStatusBar for OSXStatusBar {
}
}
fn run(&mut self, block: bool) {
loop {
unsafe {
let run_count = self.run_count.get();
// Create a new release pool every once in a while, draining the old one
if run_count % 100 == 0 {
let old_pool = self.pool.get();
if run_count != 0 {
let _ = msg_send![old_pool, drain];
}
self.pool.set(NSAutoreleasePool::new(nil));
}
let mode = self.run_mode;
let event: Id<Object> = msg_send![self.app, nextEventMatchingMask: -1
untilDate: self.run_date inMode:mode dequeue: YES];
let _ = msg_send![self.app, sendEvent: event];
let _ = msg_send![self.app, updateWindows];
self.run_count.set(run_count + 1);
}
if !block { break; }
sleep(Duration::from_millis(50));
}
let period = match block {
true => fruitbasket::RunPeriod::Forever,
_ => fruitbasket::RunPeriod::Once,
};
self.app.run(period);
}
}

Expand All @@ -257,25 +219,7 @@ pub fn resource_dir() -> Option<String> {
let cls = Class::get("NSBundle").unwrap();
let bundle: *mut Object = msg_send![cls, mainBundle];
let path: *mut Object = msg_send![bundle, resourcePath];
let cstr: *const libc::c_char = msg_send![path, UTF8String];
if cstr != ptr::null() {
let rstr = CStr::from_ptr(cstr).to_string_lossy().into_owned();
return Some(rstr);
}
None
}
}

pub fn bundled_resource_path(name: &str, extension: &str) -> Option<String> {
unsafe {
let cls = Class::get("NSBundle").unwrap();
let bundle: *mut Object = msg_send![cls, mainBundle];
let res = NSString::alloc(nil).init_str(name);
let ext = NSString::alloc(nil).init_str(extension);
let ini: *mut Object = msg_send![bundle, pathForResource:res ofType:ext];
let _ = msg_send![res, release];
let _ = msg_send![ext, release];
let cstr: *const libc::c_char = msg_send![ini, UTF8String];
let cstr: *const i8 = msg_send![path, UTF8String];
if cstr != ptr::null() {
let rstr = CStr::from_ptr(cstr).to_string_lossy().into_owned();
return Some(rstr);
Expand Down
12 changes: 2 additions & 10 deletions src/settings/mod.rs
Expand Up @@ -2,9 +2,7 @@ extern crate ini;
use self::ini::Ini;

extern crate time;

#[cfg(target_os = "macos")]
use super::osx;
extern crate fruitbasket;

use std::env;
use std::fs;
Expand All @@ -22,19 +20,13 @@ pub struct Settings {
pub presets: Vec<(String,String)>,
}

#[cfg(target_os = "macos")]
fn bundled_ini() -> String {
match osx::bundled_resource_path("connectr", "ini") {
match fruitbasket::FruitApp::bundled_resource_path("connectr", "ini") {
Some(path) => path,
None => String::new(),
}
}

#[cfg(not(target_os = "macos"))]
fn bundled_ini() -> String {
String::new()
}

fn inifile() -> String {
// Try to load INI file from home directory
let path = format!("{}/.{}", env::home_dir().unwrap().display(), INIFILE);
Expand Down

0 comments on commit bbb3d0e

Please sign in to comment.