From 7243212aee5ae58d968dd7d8e56a59fff7eb03d2 Mon Sep 17 00:00:00 2001 From: nighthawk24 Date: Fri, 19 Dec 2025 17:11:16 -0500 Subject: [PATCH] Add iOS build and run support for the DarkFi app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This pull request adds full iOS build and run support for the DarkFi app, including device, simulator and Xcode integration. It introduces ios-release  and ios-sim Makefile targets that build signed app bundles for devices and simulators. Add SDK detection and Apple Silicon arch via xcrun / xcodebuild and improved error messages. A new ios-xcode target generates a minimal DarkFi.xcodeproj using the external build system so the app can be archived and uploaded directly from Xcode. The changes include an ios-Info.plist , updated entitlements and embedded.mobileprovision handling to fix crash-on-launch issues and allow fullscreen UI and arbitrary network loads. iOS-specific path handling and ui_consts are added (with  #cfg(target_os = "ios")  and  HOME/Documents  logic) to avoid sandbox panics and unwritable home directory crashes while keeping Tor/arti working. Asset copying is standardized so the assets content (including forest_720x1280 / 1920x1080 and locales) is laid out in the bundle in a way that matches the code’s VID_PATH / LOCALE_PATH expectations and macroquad ’s resource lookup on iOS. Cargo dependencies are tuned for iOS by keeping required pieces like tor-dirmgr, excluding incompatible ones like tracing-android and ensuring no problematic iOS-only libraries cause link or runtime errors. --- bin/app/Cargo.lock | 1 + bin/app/Cargo.toml | 2 + bin/app/DarkFi.entitlements | 6 ++ bin/app/Makefile | 96 ++++++++++++++++++++++- bin/app/ios-Info.plist | 41 ++++++++++ bin/app/project.pbxproj.template | 118 +++++++++++++++++++++++++++++ bin/app/src/app/locale.rs | 5 ++ bin/app/src/app/schema/chat.rs | 4 +- bin/app/src/app/schema/menu.rs | 4 +- bin/app/src/app/schema/mod.rs | 50 +++++++++++- bin/app/src/app/schema/settings.rs | 4 +- bin/app/src/app/schema/test.rs | 4 +- bin/app/src/main.rs | 9 +++ 13 files changed, 332 insertions(+), 12 deletions(-) create mode 100644 bin/app/DarkFi.entitlements create mode 100644 bin/app/ios-Info.plist create mode 100644 bin/app/project.pbxproj.template diff --git a/bin/app/Cargo.lock b/bin/app/Cargo.lock index 0355bb1f7..e23d6c5f1 100644 --- a/bin/app/Cargo.lock +++ b/bin/app/Cargo.lock @@ -847,6 +847,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ + "sha2", "tinyvec", ] diff --git a/bin/app/Cargo.toml b/bin/app/Cargo.toml index 60f90227c..fbda76248 100644 --- a/bin/app/Cargo.toml +++ b/bin/app/Cargo.toml @@ -91,6 +91,8 @@ tracing-android = "0.2.0" # Required by Arti: tor-dirmgr tor-dirmgr = { version="0.36.0", features=["static"] } +[target.'cfg(target_os = "ios")'.dependencies] + [target.'cfg(target_os = "windows")'.dependencies] # Used by tor-dirmgr #rusqlite = {version = "0.37.0", features = ["bundled"]} diff --git a/bin/app/DarkFi.entitlements b/bin/app/DarkFi.entitlements new file mode 100644 index 000000000..6631ffa6f --- /dev/null +++ b/bin/app/DarkFi.entitlements @@ -0,0 +1,6 @@ + + + + + + diff --git a/bin/app/Makefile b/bin/app/Makefile index 11f59f88b..e7c8b83ef 100644 --- a/bin/app/Makefile +++ b/bin/app/Makefile @@ -31,6 +31,95 @@ android: android-release # Platform release builds +ios-release: $(SRC) fonts forest_720x1280 forest_1920x1080 + # Create the app bundle structure + rm -rf DarkFi.app + mkdir -p DarkFi.app + # Build for iOS device (aarch64) + SDKROOT=$$(xcrun --sdk iphoneos --show-sdk-path 2>/dev/null); \ + if [ -z "$$SDKROOT" ]; then \ + SDKROOT=$$(xcodebuild -version -sdk iphoneos Path 2>/dev/null); \ + fi; \ + if [ -z "$$SDKROOT" ]; then \ + echo "ERROR: Could not find iphoneos SDK."; \ + echo "Current xcode-select path: $$(xcode-select -p)"; \ + echo "Ensure Xcode is installed and selected: sudo xcode-select -s /Applications/Xcode.app"; \ + exit 1; \ + fi; \ + export SDKROOT=$$SDKROOT; \ + export IPHONEOS_DEPLOYMENT_TARGET=14.0; \ + cargo build --target aarch64-apple-ios --release $(RELEASE_FEATURES) + # Copy the binary + cp target/aarch64-apple-ios/release/darkfi-app DarkFi.app/ + # Copy resources + cp ios-Info.plist DarkFi.app/Info.plist + cp -r assets/lang DarkFi.app/ + cp -r assets/forest_720x1280 DarkFi.app/ + cp -r assets/forest_1920x1080 DarkFi.app/ + cp assets/*.png DarkFi.app/ || true + cp embedded.mobileprovision DarkFi.app/ || echo "Warning: No provisioning profile found. Device builds will fail to launch." + cp ibm-plex-mono-regular.otf DarkFi.app/ + cp NotoColorEmoji.ttf DarkFi.app/ + # Sign the app bundle with entitlements + codesign --force --sign - --entitlements DarkFi.entitlements --timestamp=none DarkFi.app + # Create a dummy PkgInfo (optional but good practice) + echo "APPL????" > DarkFi.app/PkgInfo + @echo "DarkFi.app built for device (aarch64)." + +ios-sim: $(SRC) fonts forest_720x1280 forest_1920x1080 + # Create the app bundle structure + rm -rf DarkFi.app + mkdir -p DarkFi.app + # Build for iOS simulator (Detect Arch) + SDKROOT=$$(xcrun --sdk iphonesimulator --show-sdk-path 2>/dev/null); \ + if [ -z "$$SDKROOT" ]; then \ + SDKROOT=$$(xcodebuild -version -sdk iphonesimulator Path 2>/dev/null); \ + fi; \ + if [ -z "$$SDKROOT" ]; then \ + echo "ERROR: Could not find iphonesimulator SDK."; \ + echo "Current xcode-select path: $$(xcode-select -p)"; \ + echo "Ensure Xcode is installed and selected: sudo xcode-select -s /Applications/Xcode.app"; \ + exit 1; \ + fi; \ + export SDKROOT=$$SDKROOT; \ + export IPHONEOS_DEPLOYMENT_TARGET=14.0; \ + ARCH=$$(uname -m); \ + if [ "$$ARCH" = "arm64" ]; then \ + echo "Detected Apple Silicon (arm64). Building for aarch64-apple-ios-sim..."; \ + cargo build --target aarch64-apple-ios-sim --release $(RELEASE_FEATURES); \ + cp target/aarch64-apple-ios-sim/release/darkfi-app DarkFi.app/; \ + else \ + echo "Detected Intel (x86_64). Building for x86_64-apple-ios..."; \ + cargo build --target x86_64-apple-ios --release $(RELEASE_FEATURES); \ + cp target/x86_64-apple-ios/release/darkfi-app DarkFi.app/; \ + fi + # Copy resources + cp ios-Info.plist DarkFi.app/Info.plist + cp -r assets/lang DarkFi.app/ + cp -r assets/forest_720x1280 DarkFi.app/ + cp -r assets/forest_1920x1080 DarkFi.app/ + cp assets/*.png DarkFi.app/ || true + cp ibm-plex-mono-regular.otf DarkFi.app/ + cp NotoColorEmoji.ttf DarkFi.app/ + # Sign the app bundle with entitlements + codesign --force --sign - --entitlements DarkFi.entitlements --timestamp=none DarkFi.app + echo "APPL????" > DarkFi.app/PkgInfo + @echo "DarkFi.app built for simulator." + +ios-xcode: + rm -rf DarkFi.xcodeproj + mkdir -p DarkFi.xcodeproj + # Generate unique IDs for the project file (24 chars) + sed -e "s/CONFIG_LIST_ID/$$(uuidgen | tr -d - | tr '[:lower:]' '[:upper:]' | cut -c 1-24)/g" \ + -e "s/MAIN_GROUP_ID/$$(uuidgen | tr -d - | tr '[:lower:]' '[:upper:]' | cut -c 1-24)/g" \ + -e "s/LEGACY_TARGET_ID/$$(uuidgen | tr -d - | tr '[:lower:]' '[:upper:]' | cut -c 1-24)/g" \ + -e "s/PROJECT_ID/$$(uuidgen | tr -d - | tr '[:lower:]' '[:upper:]' | cut -c 1-24)/g" \ + -e "s/PROJECT_CONFIG_LIST_ID/$$(uuidgen | tr -d - | tr '[:lower:]' '[:upper:]' | cut -c 1-24)/g" \ + -e "s/DEBUG_CONFIG_ID/$$(uuidgen | tr -d - | tr '[:lower:]' '[:upper:]' | cut -c 1-24)/g" \ + -e "s/RELEASE_CONFIG_ID/$$(uuidgen | tr -d - | tr '[:lower:]' '[:upper:]' | cut -c 1-24)/g" \ + project.pbxproj.template > DarkFi.xcodeproj/project.pbxproj + @echo "Generated DarkFi.xcodeproj. Open it in Xcode and ensure the 'External Build Tool' configuration points to 'make' and target 'ios-release' in this directory." + macos-release: build-release -mv darkfi-app darkfi-app.macos macos-debug: build-debug @@ -74,14 +163,14 @@ forest_1920x1080.zip: assets/forest_1920x1080/000.qoi: cd assets && unzip ../forest_1920x1080.zip forest_1920x1080: forest_1920x1080.zip assets/forest_1920x1080/000.qoi - rm -fr assets/forest_729x1280/ + # rm -fr assets/forest_729x1280/ forest_720x1280.zip: wget -c https://codeberg.org/darkrenaissance/darkfi/raw/branch/data/forest_720x1280.zip assets/forest_720x1280/000.qoi: cd assets && unzip ../forest_720x1280.zip forest_720x1280: forest_720x1280.zip assets/forest_720x1280/000.qoi - rm -fr assets/forest_1920x1080/ + # rm -fr assets/forest_1920x1080/ # Developer targets @@ -112,6 +201,9 @@ fmt: clean: podman run -v $(shell pwd):/root/dw -w /root/dw -t apk rm -fr target/ + rm -fr target/ rm -f darkfi-app.apk + rm -rf DarkFi.app + rm -rf DarkFi.xcodeproj .PHONY: all android cli clean diff --git a/bin/app/ios-Info.plist b/bin/app/ios-Info.plist new file mode 100644 index 000000000..bbd07e5dc --- /dev/null +++ b/bin/app/ios-Info.plist @@ -0,0 +1,41 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + darkfi-app + CFBundleIdentifier + fi.dark.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + DarkFi + CFBundleDisplayName + DarkFi + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIRequiresFullScreen + + UIViewControllerBasedStatusBarAppearance + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + diff --git a/bin/app/project.pbxproj.template b/bin/app/project.pbxproj.template new file mode 100644 index 000000000..6f8906ca0 --- /dev/null +++ b/bin/app/project.pbxproj.template @@ -0,0 +1,118 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ +/* End PBXFileReference section */ + +/* Begin PBXGroup section */ + MAIN_GROUP_ID = { + isa = PBXGroup; + children = ( + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXLegacyTarget section */ + LEGACY_TARGET_ID /* DarkFi */ = { + isa = PBXLegacyTarget; + buildArgumentsString = "ios-release"; + buildConfigurationList = CONFIG_LIST_ID /* Build configuration list for PBXLegacyTarget "DarkFi" */; + buildPhases = ( + ); + buildToolPath = /usr/bin/make; + buildWorkingDirectory = "$(PROJECT_DIR)"; + dependencies = ( + ); + name = "DarkFi (Make)"; + passBuildSettingsInEnvironment = 1; + productName = DarkFi; + }; +/* End PBXLegacyTarget section */ + +/* Begin PBXProject section */ + PROJECT_ID /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0720; + ORGANIZATIONNAME = "DarkFi"; + }; + buildConfigurationList = PROJECT_CONFIG_LIST_ID /* Build configuration list for PBXProject "DarkFi" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = MAIN_GROUP_ID; + projectDirPath = ""; + projectRoot = ""; + targets = ( + LEGACY_TARGET_ID /* DarkFi (Make) */, + ); + }; +/* End PBXProject section */ + +/* Begin XCBuildConfiguration section */ + DEBUG_CONFIG_ID /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + OTHER_CFLAGS = ""; + }; + name = Debug; + }; + RELEASE_CONFIG_ID /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + OTHER_CFLAGS = ""; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + PROJECT_CONFIG_LIST_ID /* Build configuration list for PBXProject "DarkFi" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DEBUG_CONFIG_ID /* Debug */, + RELEASE_CONFIG_ID /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CONFIG_LIST_ID /* Build configuration list for PBXLegacyTarget "DarkFi" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DEBUG_CONFIG_ID /* Debug */, + RELEASE_CONFIG_ID /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + + }; + rootObject = PROJECT_ID /* Project object */; +} diff --git a/bin/app/src/app/locale.rs b/bin/app/src/app/locale.rs index 73c40dfb0..f991ec72e 100644 --- a/bin/app/src/app/locale.rs +++ b/bin/app/src/app/locale.rs @@ -30,6 +30,11 @@ mod ui_consts { pub const LOCALE_PATH: &str = "assets/lang/{locale}/{entry}"; } +#[cfg(target_os = "ios")] +mod ui_consts { + pub const LOCALE_PATH: &str = "lang/{locale}/{entry}"; +} + pub use ui_consts::*; static ENTRIES: &[&'static str] = &["app.ftl"]; diff --git a/bin/app/src/app/schema/chat.rs b/bin/app/src/app/schema/chat.rs index 248156dea..7ae5bf14a 100644 --- a/bin/app/src/app/schema/chat.rs +++ b/bin/app/src/app/schema/chat.rs @@ -46,7 +46,7 @@ use crate::{ use super::{ColorScheme, COLOR_SCHEME}; -#[cfg(any(target_os = "android", feature = "emulate-android"))] +#[cfg(any(target_os = "android", target_os = "ios", feature = "emulate-android"))] mod android_ui_consts { use crate::gfx::{Point, Rectangle}; @@ -99,7 +99,7 @@ mod android_ui_consts { pub const ACTION_LABEL_POS: Point = Point::new(55., 50.); } -#[cfg(target_os = "android")] +#[cfg(any(target_os = "android", target_os = "ios"))] mod ui_consts { pub use super::android_ui_consts::*; } diff --git a/bin/app/src/app/schema/menu.rs b/bin/app/src/app/schema/menu.rs index fa1e48416..f0c9e64bc 100644 --- a/bin/app/src/app/schema/menu.rs +++ b/bin/app/src/app/schema/menu.rs @@ -31,7 +31,7 @@ use crate::{ use super::{ColorScheme, CHANNELS, COLOR_SCHEME}; -#[cfg(any(target_os = "android", feature = "emulate-android"))] +#[cfg(any(target_os = "android", target_os = "ios", feature = "emulate-android"))] mod android_ui_consts { pub const CHANNEL_LABEL_X: f32 = 40.; pub const CHANNEL_LABEL_Y: f32 = 35.; @@ -39,7 +39,7 @@ mod android_ui_consts { pub const CHANNEL_LABEL_FONTSIZE: f32 = 44.; } -#[cfg(target_os = "android")] +#[cfg(any(target_os = "android", target_os = "ios"))] mod ui_consts { pub use super::android_ui_consts::*; } diff --git a/bin/app/src/app/schema/mod.rs b/bin/app/src/app/schema/mod.rs index a4cd66079..0c6667f34 100644 --- a/bin/app/src/app/schema/mod.rs +++ b/bin/app/src/app/schema/mod.rs @@ -43,7 +43,7 @@ pub mod test; const COLOR_SCHEME: ColorScheme = ColorScheme::DarkMode; //const COLOR_SCHEME: ColorScheme = ColorScheme::PaperLight; -#[cfg(any(target_os = "android", feature = "emulate-android"))] +#[cfg(any(target_os = "android", target_os = "ios", feature = "emulate-android"))] mod android_ui_consts { pub const NETSTATUS_ICON_SIZE: f32 = 140.; pub const SETTINGS_ICON_SIZE: f32 = 140.; @@ -77,7 +77,53 @@ mod ui_consts { } } -#[cfg(not(target_os = "android"))] +#[cfg(target_os = "ios")] +mod ui_consts { + use std::path::PathBuf; + + pub const VID_PATH: &str = "forest_720x1280/{frame}.qoi"; + pub const VID_ASPECT_RATIO: f32 = 9. / 16.; + pub use super::android_ui_consts::*; + + fn get_ios_docs_dir() -> PathBuf { + let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string()); + PathBuf::from(home).join("Documents") + } + + pub fn get_chatdb_path() -> PathBuf { + let path = get_ios_docs_dir().join("darkfi/app/chatdb"); + if let Some(parent) = path.parent() { + let _ = std::fs::create_dir_all(parent); + } + path + } + + pub fn get_first_time_filename() -> PathBuf { + let path = get_ios_docs_dir().join("darkfi/app/first_time"); + if let Some(parent) = path.parent() { + let _ = std::fs::create_dir_all(parent); + } + path + } + + pub fn get_window_scale_filename() -> PathBuf { + let path = get_ios_docs_dir().join("darkfi/app/window_scale"); + if let Some(parent) = path.parent() { + let _ = std::fs::create_dir_all(parent); + } + path + } + + pub fn get_settingsdb_path() -> PathBuf { + let path = get_ios_docs_dir().join("darkfi/app/settings"); + if let Some(parent) = path.parent() { + let _ = std::fs::create_dir_all(parent); + } + path + } +} + +#[cfg(not(any(target_os = "android", target_os = "ios")))] mod desktop_paths { use std::path::PathBuf; diff --git a/bin/app/src/app/schema/settings.rs b/bin/app/src/app/schema/settings.rs index 8dceb0806..01bcec02d 100644 --- a/bin/app/src/app/schema/settings.rs +++ b/bin/app/src/app/schema/settings.rs @@ -36,7 +36,7 @@ use std::{ sync::{Arc, Mutex}, }; -#[cfg(any(target_os = "android", feature = "emulate-android"))] +#[cfg(any(target_os = "android", target_os = "ios", feature = "emulate-android"))] mod android_ui_consts { pub const SETTING_LABEL_X: f32 = 40.; pub const SETTING_LABEL_LINESPACE: f32 = 140.; @@ -59,7 +59,7 @@ mod android_ui_consts { pub const BACKARROW_BG_W: f32 = 120.; } -#[cfg(target_os = "android")] +#[cfg(any(target_os = "android", target_os = "ios"))] mod ui_consts { pub use super::android_ui_consts::*; } diff --git a/bin/app/src/app/schema/test.rs b/bin/app/src/app/schema/test.rs index f549cd04a..38759220b 100644 --- a/bin/app/src/app/schema/test.rs +++ b/bin/app/src/app/schema/test.rs @@ -33,14 +33,14 @@ use crate::{ const LIGHTMODE: bool = false; -#[cfg(target_os = "android")] +#[cfg(any(target_os = "android", target_os = "ios"))] mod ui_consts { //pub const CHATDB_PATH: &str = "/data/data/darkfi.app/chatdb/"; //pub const KING_PATH: &str = "king.png"; pub const VID_PATH: &str = "forest/forest_{frame}.png"; } -#[cfg(not(target_os = "android"))] +#[cfg(not(any(target_os = "android", target_os = "ios")))] mod ui_consts { //pub const CHATDB_PATH: &str = "chatdb"; //pub const KING_PATH: &str = "assets/king.png"; diff --git a/bin/app/src/main.rs b/bin/app/src/main.rs index 43e786d27..c0630e8c3 100644 --- a/bin/app/src/main.rs +++ b/bin/app/src/main.rs @@ -143,6 +143,15 @@ impl God { } } + #[cfg(target_os = "ios")] + { + let home = std::env::var("HOME").unwrap_or(".".to_string()); + let doc_path = std::path::PathBuf::from(home).join("Documents"); + unsafe { + std::env::set_var("HOME", doc_path); + } + } + let exe_path = std::env::current_exe().unwrap(); let basename = exe_path.parent().unwrap(); std::env::set_current_dir(basename).unwrap();