Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ rustflags = [
"link-arg=--max-memory=4294967296",
"--cfg=web_sys_unstable_apis",
]

[env]
CARGO_WORKSPACE_DIR = { value = "", relative = true }
56 changes: 54 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ members = [
"desktop",
"desktop/wrapper",
"desktop/embedded-resources",
"desktop/bundle",
"desktop/platform/linux",
"desktop/platform/mac",
"desktop/platform/win",
"editor",
"frontend/wasm",
"libraries/dyn-any",
Expand Down
3 changes: 0 additions & 3 deletions desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,3 @@ core-foundation = { version = "0.10", optional = true }
# Linux-specific dependencies
[target.'cfg(target_os = "linux")'.dependencies]
libc = { version = "0.2", optional = true }

[target.'cfg(target_os = "windows")'.build-dependencies]
winres = "0.1"
16 changes: 16 additions & 0 deletions desktop/bundle/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "graphite-desktop-bundle"
version = "0.0.0"
description = "Graphite Desktop Bundle"
authors = ["Graphite Authors <[email protected]>"]
license = "Apache-2.0"
repository = ""
edition = "2024"
rust-version = "1.87"

[dependencies]
cef-dll-sys = { workspace = true }

[target.'cfg(target_os = "macos")'.dependencies]
serde = { workspace = true }
plist = { version = "*" }
10 changes: 10 additions & 0 deletions desktop/bundle/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
fn main() {
println!("cargo:rerun-if-env-changed=CARGO_PROFILE");
println!("cargo:rerun-if-env-changed=PROFILE");
let profile = std::env::var("CARGO_PROFILE").or_else(|_| std::env::var("PROFILE")).unwrap();
println!("cargo:rustc-env=CARGO_PROFILE={profile}");

println!("cargo:rerun-if-env-changed=DEP_CEF_DLL_WRAPPER_CEF_DIR");
let cef_dir = std::env::var("DEP_CEF_DLL_WRAPPER_CEF_DIR").unwrap();
println!("cargo:rustc-env=CEF_PATH={cef_dir}");
}
66 changes: 66 additions & 0 deletions desktop/bundle/src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use std::error::Error;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

pub(crate) const APP_NAME: &str = "Graphite";

fn profile_name() -> &'static str {
let mut profile = env!("CARGO_PROFILE");
if profile == "debug" {
profile = "dev";
}
profile
}

pub(crate) fn profile_path() -> PathBuf {
PathBuf::from(env!("CARGO_WORKSPACE_DIR")).join(format!("target/{}", env!("CARGO_PROFILE")))
}

pub(crate) fn cef_path() -> PathBuf {
PathBuf::from(env!("CEF_PATH"))
}

pub(crate) fn build_bin(package: &str, bin: Option<&str>) -> Result<PathBuf, Box<dyn Error>> {
let profile = &profile_name();
let mut args = vec!["build", "--package", package, "--profile", profile];
if let Some(bin) = bin {
args.push("--bin");
args.push(bin);
}
run_command("cargo", &args)?;
let profile_path = profile_path();
let mut bin_path = if let Some(bin) = bin { profile_path.join(bin) } else { profile_path.join(package) };
if cfg!(target_os = "windows") {
bin_path.set_extension("exe");
}
Ok(bin_path)
}

pub(crate) fn run_command(program: &str, args: &[&str]) -> Result<(), Box<dyn std::error::Error>> {
let status = Command::new(program).args(args).stdout(Stdio::inherit()).stderr(Stdio::inherit()).status()?;
if !status.success() {
std::process::exit(1);
}
Ok(())
}

pub(crate) fn clean_dir(dir: &Path) {
if dir.exists() {
fs::remove_dir_all(dir).unwrap();
}
fs::create_dir_all(dir).unwrap();
}

pub(crate) fn copy_dir(src: &Path, dst: &Path) {
fs::create_dir_all(dst).unwrap();
for entry in fs::read_dir(src).unwrap() {
let entry = entry.unwrap();
let dst_path = dst.join(entry.file_name());
if entry.file_type().unwrap().is_dir() {
copy_dir(&entry.path(), &dst_path);
} else {
fs::copy(entry.path(), &dst_path).unwrap();
}
}
}
23 changes: 23 additions & 0 deletions desktop/bundle/src/linux.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use std::error::Error;

use crate::common::*;

const PACKAGE: &str = "graphite-desktop-platform-linux";

pub fn main() -> Result<(), Box<dyn Error>> {
let app_bin = build_bin(PACKAGE, None)?;

// TODO: Implement bundling for linux

// TODO: Consider adding more useful cli
if std::env::args().any(|a| a == "open") {
run_command(&app_bin.to_string_lossy(), &[]).expect("failed to open app");
} else {
println!("Binary built and placed at {}", app_bin.to_string_lossy());
eprintln!("Bundling for Linux is not yet implemented.");
eprintln!("You can still start the app with the `open` subcommand. `cargo run -p graphite-desktop-bundle -- open`");
std::process::exit(1);
}

Ok(())
}
135 changes: 135 additions & 0 deletions desktop/bundle/src/mac.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use std::collections::HashMap;
use std::error::Error;
use std::fs;
use std::path::{Path, PathBuf};

use crate::common::*;

const APP_ID: &str = "rs.graphite.GraphiteEditor";

const PACKAGE: &str = "graphite-desktop-platform-mac";
const HELPER_BIN: &str = "graphite-desktop-platform-mac-helper";

const EXEC_PATH: &str = "Contents/MacOS";
const FRAMEWORKS_PATH: &str = "Contents/Frameworks";
const RESOURCES_PATH: &str = "Contents/Resources";
const FRAMEWORK: &str = "Chromium Embedded Framework.framework";

pub fn main() -> Result<(), Box<dyn Error>> {
let app_bin = build_bin(PACKAGE, None)?;
let helper_bin = build_bin(PACKAGE, Some(HELPER_BIN))?;

let profile_path = profile_path();
let app_dir = bundle(&profile_path, &app_bin, &helper_bin);

// TODO: Consider adding more useful cli
if std::env::args().any(|a| a == "open") {
let executable_path = app_dir.join(EXEC_PATH).join(APP_NAME);
run_command(&executable_path.to_string_lossy(), &[]).expect("failed to open app");
}

Ok(())
}

fn bundle(out_dir: &Path, app_bin: &Path, helper_bin: &Path) -> PathBuf {
let app_dir = out_dir.join(APP_NAME).with_extension("app");

clean_dir(&app_dir);

create_app(&app_dir, APP_ID, APP_NAME, app_bin, false);

for helper_type in [None, Some("GPU"), Some("Renderer"), Some("Plugin"), Some("Alerts")] {
let helper_id_suffix = helper_type.map(|t| format!(".{t}")).unwrap_or_default();
let helper_id = format!("{APP_ID}.helper{helper_id_suffix}");
let helper_name_suffix = helper_type.map(|t| format!(" ({t})")).unwrap_or_default();
let helper_name = format!("{APP_NAME} Helper{helper_name_suffix}");
let helper_app_dir = app_dir.join(FRAMEWORKS_PATH).join(&helper_name).with_extension("app");
create_app(&helper_app_dir, &helper_id, &helper_name, helper_bin, true);
}

copy_dir(&cef_path().join(FRAMEWORK), &app_dir.join(FRAMEWORKS_PATH).join(FRAMEWORK));

app_dir
}

fn create_app(app_dir: &Path, id: &str, name: &str, bin: &Path, is_helper: bool) {
fs::create_dir_all(app_dir.join(EXEC_PATH)).unwrap();

let app_contents_dir: &Path = &app_dir.join("Contents");
for p in &[EXEC_PATH, RESOURCES_PATH, FRAMEWORKS_PATH] {
fs::create_dir_all(app_contents_dir.join(p)).unwrap();
}

create_info_plist(app_contents_dir, id, name, is_helper).unwrap();
fs::copy(bin, app_dir.join(EXEC_PATH).join(name)).unwrap();
}

fn create_info_plist(dir: &Path, id: &str, exec_name: &str, is_helper: bool) -> Result<(), Box<dyn std::error::Error>> {
let info = InfoPlist {
cf_bundle_development_region: "en".to_string(),
cf_bundle_display_name: exec_name.to_string(),
cf_bundle_executable: exec_name.to_string(),
cf_bundle_identifier: id.to_string(),
cf_bundle_info_dictionary_version: "6.0".to_string(),
cf_bundle_name: exec_name.to_string(),
cf_bundle_package_type: "APPL".to_string(),
cf_bundle_signature: "????".to_string(),
cf_bundle_version: "0.0.0".to_string(),
cf_bundle_short_version_string: "0.0".to_string(),
ls_environment: [("MallocNanoZone".to_string(), "0".to_string())].iter().cloned().collect(),
ls_file_quarantine_enabled: true,
ls_minimum_system_version: "11.0".to_string(),
ls_ui_element: if is_helper { Some("1".to_string()) } else { None },
ns_bluetooth_always_usage_description: exec_name.to_string(),
ns_supports_automatic_graphics_switching: true,
ns_web_browser_publickey_credential_usage_description: exec_name.to_string(),
ns_camera_usage_description: exec_name.to_string(),
ns_microphone_usage_description: exec_name.to_string(),
};

let plist_file = dir.join("Info.plist");
plist::to_file_xml(plist_file, &info)?;
Ok(())
}

#[derive(serde::Serialize)]
struct InfoPlist {
#[serde(rename = "CFBundleDevelopmentRegion")]
cf_bundle_development_region: String,
#[serde(rename = "CFBundleDisplayName")]
cf_bundle_display_name: String,
#[serde(rename = "CFBundleExecutable")]
cf_bundle_executable: String,
#[serde(rename = "CFBundleIdentifier")]
cf_bundle_identifier: String,
#[serde(rename = "CFBundleInfoDictionaryVersion")]
cf_bundle_info_dictionary_version: String,
#[serde(rename = "CFBundleName")]
cf_bundle_name: String,
#[serde(rename = "CFBundlePackageType")]
cf_bundle_package_type: String,
#[serde(rename = "CFBundleSignature")]
cf_bundle_signature: String,
#[serde(rename = "CFBundleVersion")]
cf_bundle_version: String,
#[serde(rename = "CFBundleShortVersionString")]
cf_bundle_short_version_string: String,
#[serde(rename = "LSEnvironment")]
ls_environment: HashMap<String, String>,
#[serde(rename = "LSFileQuarantineEnabled")]
ls_file_quarantine_enabled: bool,
#[serde(rename = "LSMinimumSystemVersion")]
ls_minimum_system_version: String,
#[serde(rename = "LSUIElement")]
ls_ui_element: Option<String>,
#[serde(rename = "NSBluetoothAlwaysUsageDescription")]
ns_bluetooth_always_usage_description: String,
#[serde(rename = "NSSupportsAutomaticGraphicsSwitching")]
ns_supports_automatic_graphics_switching: bool,
#[serde(rename = "NSWebBrowserPublicKeyCredentialUsageDescription")]
ns_web_browser_publickey_credential_usage_description: String,
#[serde(rename = "NSCameraUsageDescription")]
ns_camera_usage_description: String,
#[serde(rename = "NSMicrophoneUsageDescription")]
ns_microphone_usage_description: String,
}
17 changes: 17 additions & 0 deletions desktop/bundle/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
mod common;

#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod mac;
#[cfg(target_os = "windows")]
mod win;

fn main() {
#[cfg(target_os = "linux")]
linux::main().unwrap();
#[cfg(target_os = "macos")]
mac::main().unwrap();
#[cfg(target_os = "windows")]
win::main().unwrap();
}
Loading
Loading