|
| 1 | +use std::collections::HashMap; |
| 2 | +use std::error::Error; |
| 3 | +use std::fs; |
| 4 | +use std::path::{Path, PathBuf}; |
| 5 | + |
| 6 | +use crate::common::*; |
| 7 | + |
| 8 | +const APP_ID: &str = "rs.graphite.GraphiteEditor"; |
| 9 | + |
| 10 | +const PACKAGE: &str = "graphite-desktop-platform-mac"; |
| 11 | +const HELPER_BIN: &str = "graphite-desktop-platform-mac-helper"; |
| 12 | + |
| 13 | +const EXEC_PATH: &str = "Contents/MacOS"; |
| 14 | +const FRAMEWORKS_PATH: &str = "Contents/Frameworks"; |
| 15 | +const RESOURCES_PATH: &str = "Contents/Resources"; |
| 16 | +const FRAMEWORK: &str = "Chromium Embedded Framework.framework"; |
| 17 | + |
| 18 | +pub fn main() -> Result<(), Box<dyn Error>> { |
| 19 | + let app_bin = build_bin(PACKAGE, None)?; |
| 20 | + let helper_bin = build_bin(PACKAGE, Some(HELPER_BIN))?; |
| 21 | + |
| 22 | + let profile_path = profile_path(); |
| 23 | + let app_dir = bundle(&profile_path, &app_bin, &helper_bin); |
| 24 | + |
| 25 | + // TODO: Consider adding more useful cli |
| 26 | + if std::env::args().any(|a| a == "open") { |
| 27 | + let app_path = app_dir.to_string_lossy(); |
| 28 | + run_command("open", &[&app_path]).expect("failed to open app") |
| 29 | + } |
| 30 | + |
| 31 | + Ok(()) |
| 32 | +} |
| 33 | + |
| 34 | +fn bundle(out_dir: &Path, app_bin: &Path, helper_bin: &Path) -> PathBuf { |
| 35 | + let app_dir = out_dir.join(APP_NAME).with_extension("app"); |
| 36 | + |
| 37 | + clean_dir(&app_dir); |
| 38 | + |
| 39 | + create_app(&app_dir, APP_ID, APP_NAME, app_bin, false); |
| 40 | + |
| 41 | + for helper_type in [None, Some("GPU"), Some("Renderer"), Some("Plugin"), Some("Alerts")] { |
| 42 | + let helper_id_suffix = helper_type.map(|t| format!(".{t}")).unwrap_or_default(); |
| 43 | + let helper_id = format!("{APP_ID}.helper{helper_id_suffix}"); |
| 44 | + let helper_name_suffix = helper_type.map(|t| format!(" ({t})")).unwrap_or_default(); |
| 45 | + let helper_name = format!("{APP_NAME} Helper{helper_name_suffix}"); |
| 46 | + let helper_app_dir = app_dir.join(FRAMEWORKS_PATH).join(&helper_name).with_extension("app"); |
| 47 | + create_app(&helper_app_dir, &helper_id, &helper_name, helper_bin, true); |
| 48 | + } |
| 49 | + |
| 50 | + copy_dir(&cef_path().join(FRAMEWORK), &app_dir.join(FRAMEWORKS_PATH).join(FRAMEWORK)); |
| 51 | + |
| 52 | + app_dir |
| 53 | +} |
| 54 | + |
| 55 | +fn create_app(app_dir: &Path, id: &str, name: &str, bin: &Path, is_helper: bool) { |
| 56 | + fs::create_dir_all(app_dir.join(EXEC_PATH)).unwrap(); |
| 57 | + |
| 58 | + let app_contents_dir: &Path = &app_dir.join("Contents"); |
| 59 | + for p in &[EXEC_PATH, RESOURCES_PATH, FRAMEWORKS_PATH] { |
| 60 | + fs::create_dir_all(app_contents_dir.join(p)).unwrap(); |
| 61 | + } |
| 62 | + |
| 63 | + create_info_plist(app_contents_dir, id, name, is_helper).unwrap(); |
| 64 | + fs::copy(bin, app_dir.join(EXEC_PATH).join(name)).unwrap(); |
| 65 | +} |
| 66 | + |
| 67 | +fn create_info_plist(dir: &Path, id: &str, exec_name: &str, is_helper: bool) -> Result<(), Box<dyn std::error::Error>> { |
| 68 | + let info = InfoPlist { |
| 69 | + cf_bundle_development_region: "en".to_string(), |
| 70 | + cf_bundle_display_name: exec_name.to_string(), |
| 71 | + cf_bundle_executable: exec_name.to_string(), |
| 72 | + cf_bundle_identifier: id.to_string(), |
| 73 | + cf_bundle_info_dictionary_version: "6.0".to_string(), |
| 74 | + cf_bundle_name: exec_name.to_string(), |
| 75 | + cf_bundle_package_type: "APPL".to_string(), |
| 76 | + cf_bundle_signature: "????".to_string(), |
| 77 | + cf_bundle_version: "0.0.0".to_string(), |
| 78 | + cf_bundle_short_version_string: "0.0".to_string(), |
| 79 | + ls_environment: [("MallocNanoZone".to_string(), "0".to_string())].iter().cloned().collect(), |
| 80 | + ls_file_quarantine_enabled: true, |
| 81 | + ls_minimum_system_version: "11.0".to_string(), |
| 82 | + ls_ui_element: if is_helper { Some("1".to_string()) } else { None }, |
| 83 | + ns_bluetooth_always_usage_description: exec_name.to_string(), |
| 84 | + ns_supports_automatic_graphics_switching: true, |
| 85 | + ns_web_browser_publickey_credential_usage_description: exec_name.to_string(), |
| 86 | + ns_camera_usage_description: exec_name.to_string(), |
| 87 | + ns_microphone_usage_description: exec_name.to_string(), |
| 88 | + }; |
| 89 | + |
| 90 | + let plist_file = dir.join("Info.plist"); |
| 91 | + plist::to_file_xml(plist_file, &info)?; |
| 92 | + Ok(()) |
| 93 | +} |
| 94 | + |
| 95 | +#[derive(serde::Serialize)] |
| 96 | +struct InfoPlist { |
| 97 | + #[serde(rename = "CFBundleDevelopmentRegion")] |
| 98 | + cf_bundle_development_region: String, |
| 99 | + #[serde(rename = "CFBundleDisplayName")] |
| 100 | + cf_bundle_display_name: String, |
| 101 | + #[serde(rename = "CFBundleExecutable")] |
| 102 | + cf_bundle_executable: String, |
| 103 | + #[serde(rename = "CFBundleIdentifier")] |
| 104 | + cf_bundle_identifier: String, |
| 105 | + #[serde(rename = "CFBundleInfoDictionaryVersion")] |
| 106 | + cf_bundle_info_dictionary_version: String, |
| 107 | + #[serde(rename = "CFBundleName")] |
| 108 | + cf_bundle_name: String, |
| 109 | + #[serde(rename = "CFBundlePackageType")] |
| 110 | + cf_bundle_package_type: String, |
| 111 | + #[serde(rename = "CFBundleSignature")] |
| 112 | + cf_bundle_signature: String, |
| 113 | + #[serde(rename = "CFBundleVersion")] |
| 114 | + cf_bundle_version: String, |
| 115 | + #[serde(rename = "CFBundleShortVersionString")] |
| 116 | + cf_bundle_short_version_string: String, |
| 117 | + #[serde(rename = "LSEnvironment")] |
| 118 | + ls_environment: HashMap<String, String>, |
| 119 | + #[serde(rename = "LSFileQuarantineEnabled")] |
| 120 | + ls_file_quarantine_enabled: bool, |
| 121 | + #[serde(rename = "LSMinimumSystemVersion")] |
| 122 | + ls_minimum_system_version: String, |
| 123 | + #[serde(rename = "LSUIElement")] |
| 124 | + ls_ui_element: Option<String>, |
| 125 | + #[serde(rename = "NSBluetoothAlwaysUsageDescription")] |
| 126 | + ns_bluetooth_always_usage_description: String, |
| 127 | + #[serde(rename = "NSSupportsAutomaticGraphicsSwitching")] |
| 128 | + ns_supports_automatic_graphics_switching: bool, |
| 129 | + #[serde(rename = "NSWebBrowserPublicKeyCredentialUsageDescription")] |
| 130 | + ns_web_browser_publickey_credential_usage_description: String, |
| 131 | + #[serde(rename = "NSCameraUsageDescription")] |
| 132 | + ns_camera_usage_description: String, |
| 133 | + #[serde(rename = "NSMicrophoneUsageDescription")] |
| 134 | + ns_microphone_usage_description: String, |
| 135 | +} |
0 commit comments