use crate::{Application, InstallDir, Trampoline};
use std::{
io::{Error as IOError, Write},
path::{Path, PathBuf},
};
pub use objc2::rc::Retained;
pub use objc2_app_kit::NSApplication;
pub use objc2_foundation::{MainThreadMarker, NSBundle};
#[link(name = "AppKit", kind = "framework")] extern "C" {}
#[link(name = "Foundation", kind = "framework")] extern "C" {}
pub fn bundle(trampoline: &Trampoline, location: InstallDir) -> Result<Application, IOError> {
if let Some(bundle) = Trampoline::get_bundle() {
return Ok(Application::new(
trampoline.name.clone(),
trampoline.ident.clone(),
bundle,
));
}
let install_path = match location {
InstallDir::Temp => std::env::temp_dir(),
InstallDir::SystemApplications => PathBuf::from("/Applications"),
InstallDir::UserApplications => dirs::home_dir().unwrap().join("Applications"),
InstallDir::Custom(path) => std::fs::canonicalize(path)?,
};
let bundle_path = install_path.join(format!("{}.app", trampoline.name));
let contents_path = Path::new(&bundle_path).join("Contents");
let macos_path = contents_path.clone().join("MacOS");
let resources_path = contents_path.clone().join("Resources");
let plist = contents_path.clone().join("Info.plist");
let src_exe = std::env::current_exe()?;
let exe_name = src_exe
.file_name()
.expect("Could not determine executable name for current process.")
.to_str()
.expect("Could not convert executable name to string.");
let dst_exe = macos_path.clone().join(exe_name);
if bundle_path.try_exists()? {
std::fs::remove_dir_all(&bundle_path)?;
}
std::fs::create_dir_all(&macos_path)?;
std::fs::create_dir_all(&resources_path)?;
std::fs::copy(&src_exe, &dst_exe)?;
let mut f = std::fs::File::create(&plist)?;
writeln!(&mut f, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")?;
writeln!(&mut f, "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">")?;
writeln!(&mut f, "<plist version=\"1.0\">")?;
writeln!(&mut f, "<dict>")?;
writeln!(&mut f, "\t<key>CFBundleName</key>")?;
writeln!(&mut f, "\t<string>{}</string>", trampoline.name)?;
writeln!(&mut f, "\t<key>CFBundleDisplayName</key>")?;
writeln!(&mut f, "\t<string>{}</string>", trampoline.name)?;
writeln!(&mut f, "\t<key>CFBundleIdentifier</key>")?;
writeln!(&mut f, "\t<string>{}</string>", trampoline.ident)?;
writeln!(&mut f, "\t<key>CFBundleExecutable</key>")?;
writeln!(&mut f, "\t<string>{}</string>", exe_name)?;
writeln!(&mut f, "\t<key>CFBundleShortVersionString</key>")?;
writeln!(&mut f, "\t<string>{}</string>", trampoline.version)?;
writeln!(&mut f, "\t<key>CFBundleSupportedPlatforms</key>")?;
writeln!(&mut f, "\t<array>")?;
writeln!(&mut f, "\t\t<string>MacOSX</string>")?;
writeln!(&mut f, "\t</array>")?;
writeln!(&mut f, "\t<key>CFBundleVersion</key>")?;
writeln!(&mut f, "\t<string>{}</string>", trampoline.version)?;
writeln!(&mut f, "\t<key>NSPrincipalClass</key>")?;
writeln!(&mut f, "\t<string>NSApplication</string>")?;
writeln!(&mut f, "\t<key>NSHighResolutionCapable</key>")?;
writeln!(&mut f, "\t<true/>")?;
writeln!(&mut f, "\t<key>CFBundleInfoDictionaryVersion</key>")?;
writeln!(&mut f, "\t<string>6.0</string>")?;
writeln!(&mut f, "\t<key>CFBundlePackageType</key>")?;
writeln!(&mut f, "\t<string>APPL</string>")?;
writeln!(&mut f, "\t<key>CFBundleSignature</key>")?;
writeln!(&mut f, "\t<string>????</string>")?;
writeln!(&mut f, "\t<key>LSMinimumSystemVersion</key>")?;
writeln!(&mut f, "\t<string>10.10.0</string>")?;
writeln!(&mut f, "</dict>")?;
writeln!(&mut f, "</plist>")?;
let status = std::process::Command::new(dst_exe).spawn()?.wait()?;
match status.code() {
Some(code) => std::process::exit(code),
None => std::process::exit(125),
}
}