diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 8a87f3b..70ea6f7 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -25,7 +25,7 @@ jobs: node-version: "20" cache: "pnpm" - run: pnpm install - - run: pnpm run format-check + - run: pnpm run format:check # Static analyzer. clippy: @@ -79,14 +79,6 @@ jobs: - run: cargo doc --no-deps -p tauri-plugin-sharekit - run: cargo deadlinks --no-build - # Check links in markdown files. - mlc: - name: mlc - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: becheran/mlc@v1 - # Spellcheck. spellcheck: runs-on: ubuntu-latest diff --git a/.prettierignore b/.prettierignore index de4c568..b0234f9 100644 --- a/.prettierignore +++ b/.prettierignore @@ -8,3 +8,4 @@ node_modules schema.json target **/gen/* +**/.svelte-kit/ diff --git a/Cargo.toml b/Cargo.toml index c863029..9057f74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,15 @@ serde_repr = "0.1" [target.'cfg(target_os = "macos")'.dependencies] objc2 = "0.6" objc2-core-foundation = "0.3" -objc2-foundation = { version = "0.3", features = ["NSString"] } +objc2-foundation = { version = "0.3", features = [ + "NSString", + "NSBundle", + "NSUserDefaults", + "NSData", + "NSFileManager", + "NSURL", + "NSPathUtilities", +] } objc2-app-kit = { version = "0.3", features = ["NSSharingService", "NSView", "NSResponder"] } [target.'cfg(target_os = "windows")'.dependencies] @@ -29,10 +37,18 @@ windows = { version = "0.61", features = [ "Foundation", "Foundation_Collections", "Storage", + "Storage_FileProperties", + "Storage_Search", "Storage_Streams", "Win32_System_WinRT", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", + # Share Target support + "ApplicationModel", + "ApplicationModel_Activation", + "ApplicationModel_Core", + "ApplicationModel_DataTransfer_ShareTarget", + "Win32_Storage_Packaging_Appx", ] } windows-collections = "0.2" diff --git a/README.md b/README.md index 3385f5d..261ed5e 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,228 @@ await shareText('Hello!', { }); ``` +### Receiving Shared Content (Share Target) + +Your app can receive content shared from other apps: + +```javascript +import { + getPendingSharedContent, + clearPendingSharedContent, + onSharedContent +} from "@choochmeque/tauri-plugin-sharekit-api"; + +// Check for shared content on app startup (cold start) +const content = await getPendingSharedContent(); +if (content) { + if (content.type === 'text') { + console.log('Received text:', content.text); + } else if (content.type === 'files') { + for (const file of content.files) { + console.log('Received file:', file.name, file.path); + } + } + await clearPendingSharedContent(); +} + +// Listen for shares while app is running (warm start) +const unlisten = await onSharedContent((content) => { + console.log('Received:', content); +}); +``` + +## Platform Setup + +### Android + +To receive shared content on Android, add intent filters to your `AndroidManifest.xml`: + +`src-tauri/gen/android/app/src/main/AndroidManifest.xml` + +Add these intent filters inside your `` tag: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +You can customize the `mimeType` values to only accept specific file types. + +### iOS + +To receive shared content on iOS, you need to add a Share Extension. Use the `@choochmeque/tauri-apple-extensions` tool after initializing your iOS project: + +```bash +# First, initialize the iOS project if you haven't already +tauri ios init + +# Then add the Share Extension +npx @choochmeque/tauri-apple-extensions ios add share --plugin @choochmeque/tauri-plugin-sharekit-api +``` + +The setup tool will: +1. Create a Share Extension target in your Xcode project +2. Configure App Groups for communication between the extension and main app +3. Add a URL scheme for the extension to open your app + +**After running the script, you must:** + +1. Open the Xcode project (`src-tauri/gen/apple/*.xcodeproj`) +2. Select your Apple Developer Team for both targets: + - Main app target (e.g., `myapp_iOS`) + - Share Extension target (e.g., `myapp-ShareExtension`) +3. Enable the "App Groups" capability for **both** targets in Xcode +4. In Apple Developer Portal, create the App Group (e.g., `group.com.your.app`) and add it to both App IDs + +**App Group Configuration:** + +The extension and main app communicate via App Groups. The setup script uses `group.{your.bundle.id}` as the App Group identifier. Make sure this is configured in: +- Apple Developer Portal (create the App Group) +- Both App IDs (main app and extension) +- Xcode capabilities for both targets + +### macOS + +To receive shared content on macOS, you need to add a Share Extension. First, create the macOS Xcode project, then add the extension: + +```bash +# First, create the macOS Xcode project +npx @choochmeque/tauri-macos-xcode init + +# Then add the Share Extension +npx @choochmeque/tauri-apple-extensions macos add share --plugin @choochmeque/tauri-plugin-sharekit-api +``` + +The setup tool will: +1. Create a Share Extension target in your Xcode project +2. Configure App Groups for communication between the extension and main app +3. Add a URL scheme for the extension to open your app + +**After running the script, you must:** + +1. Open the Xcode project (`src-tauri/gen/apple-macos/*.xcodeproj`) +2. Select your Apple Developer Team for both targets: + - Main app target (e.g., `myapp_macOS`) + - Share Extension target (e.g., `myapp-ShareExtension`) +3. Enable the "App Groups" capability for **both** targets in Xcode +4. In Apple Developer Portal, create the App Group (e.g., `group.com.your.app`) and add it to both App IDs + +**Development workflow:** + +```bash +# Start the dev server and open Xcode +pnpm tauri:macos:dev + +# Then press Cmd+R in Xcode to build and run +``` + +### Windows + +To receive shared content on Windows, your app must be packaged as MSIX. + +**Requirements:** +- Windows 10 version 2004+ (build 19041) +- MSIX packaging (use [@choochmeque/tauri-windows-bundle](https://github.com/Choochmeque/tauri-windows-bundle)) + +**Setup:** + +1. Initialize Windows bundle (if not already done) and add share target extension: + +```bash +npx @choochmeque/tauri-windows-bundle init +npx @choochmeque/tauri-windows-bundle extension add share-target +``` + +2. Add share target handling to your Tauri setup: + +`src-tauri/src/main.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_sharekit::init()) + .setup(|app| { + #[cfg(target_os = "windows")] + { + use tauri_plugin_sharekit::ShareExt; + if app.share().handle_share_activation()? { + app.handle().exit(0); + } + } + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +### Displaying Received Images + +To display received images in your app, enable the asset protocol feature and configure the scope. + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri = { version = "2", features = ["protocol-asset"] } +``` + +`src-tauri/tauri.conf.json` + +```json +{ + "app": { + "security": { + "assetProtocol": { + "enable": true, + "scope": [ + "$CACHE/**", + "$APPCACHE/**", + "**/Containers/Shared/AppGroup/**" + ] + } + } + } +} +``` + +The `**/Containers/Shared/AppGroup/**` scope is required on iOS to access files shared via the Share Extension (works for both simulator and real devices). + +Then in your frontend: + +```javascript +import { convertFileSrc } from "@tauri-apps/api/core"; + +// file.path is from SharedContent.files +const imageUrl = convertFileSrc(file.path); +// Use imageUrl in an tag +``` + ## Contributing PRs accepted. Please make sure to read the Contributing Guide before making a pull request. diff --git a/android/src/main/java/SharePlugin.kt b/android/src/main/java/SharePlugin.kt index 5666604..ce7cef9 100644 --- a/android/src/main/java/SharePlugin.kt +++ b/android/src/main/java/SharePlugin.kt @@ -6,17 +6,22 @@ import android.content.pm.PackageManager import android.os.Build import android.webkit.WebView import android.net.Uri +import android.webkit.MimeTypeMap import androidx.activity.result.ActivityResult import app.tauri.annotation.ActivityCallback import app.tauri.annotation.Command import app.tauri.annotation.InvokeArg import app.tauri.annotation.TauriPlugin import app.tauri.plugin.Invoke +import app.tauri.plugin.JSObject import app.tauri.plugin.Plugin import androidx.core.content.FileProvider +import org.json.JSONArray +import org.json.JSONObject import java.io.File import java.io.FileInputStream import java.io.FileOutputStream +import java.util.UUID @InvokeArg class ShareTextOptions { @@ -34,6 +39,154 @@ class ShareFileOptions { @TauriPlugin class SharePlugin(private val activity: Activity): Plugin(activity) { + private var pendingSharedContent: JSObject? = null + + override fun load(webView: WebView) { + super.load(webView) + handleIntent(activity.intent) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + handleIntent(intent) + } + + private fun handleIntent(intent: Intent?) { + if (intent == null) return + + when (intent.action) { + Intent.ACTION_SEND -> handleSingleShare(intent) + Intent.ACTION_SEND_MULTIPLE -> handleMultipleShare(intent) + } + + if (pendingSharedContent != null) { + trigger("sharedContent", pendingSharedContent!!) + } + } + + private fun handleSingleShare(intent: Intent) { + val type = intent.type ?: return + + if (type.startsWith("text/")) { + val text = intent.getStringExtra(Intent.EXTRA_TEXT) + if (text != null) { + pendingSharedContent = JSObject().apply { + put("type", "text") + put("text", text) + } + } + } else { + val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableExtra(Intent.EXTRA_STREAM) + } + + if (uri != null) { + val file = copyUriToCache(uri) + if (file != null) { + val filesArray = JSONArray() + filesArray.put(file) + pendingSharedContent = JSObject().apply { + put("type", "files") + put("files", filesArray) + } + } + } + } + } + + private fun handleMultipleShare(intent: Intent) { + val uris = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM) + } + + if (uris != null && uris.isNotEmpty()) { + val filesArray = JSONArray() + for (uri in uris) { + val file = copyUriToCache(uri) + if (file != null) { + filesArray.put(file) + } + } + if (filesArray.length() > 0) { + pendingSharedContent = JSObject().apply { + put("type", "files") + put("files", filesArray) + } + } + } + } + + private fun copyUriToCache(uri: Uri): JSONObject? { + return try { + val contentResolver = activity.contentResolver + val mimeType = contentResolver.getType(uri) + + var fileName = getFileName(uri) + if (fileName == null) { + val ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) ?: "" + fileName = "${UUID.randomUUID()}.$ext" + } + + val cacheDir = File(activity.cacheDir, "shared_files") + cacheDir.mkdirs() + val destFile = File(cacheDir, fileName) + + contentResolver.openInputStream(uri)?.use { input -> + FileOutputStream(destFile).use { output -> + input.copyTo(output) + } + } + + JSONObject().apply { + put("path", destFile.absolutePath) + put("name", fileName) + if (mimeType != null) put("mimeType", mimeType) + put("size", destFile.length()) + } + } catch (e: Exception) { + null + } + } + + private fun getFileName(uri: Uri): String? { + var result: String? = null + if (uri.scheme == "content") { + activity.contentResolver.query(uri, null, null, null, null)?.use { cursor -> + if (cursor.moveToFirst()) { + val nameIndex = cursor.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME) + if (nameIndex >= 0) { + result = cursor.getString(nameIndex) + } + } + } + } + if (result == null) { + result = uri.path?.substringAfterLast('/') + } + return result + } + + @Command + fun getPendingSharedContent(invoke: Invoke) { + if (pendingSharedContent != null) { + invoke.resolve(pendingSharedContent!!) + } else { + invoke.resolve() + } + } + + @Command + fun clearPendingSharedContent(invoke: Invoke) { + pendingSharedContent = null + invoke.resolve() + } + /** * Open the native sharing interface to share some text */ diff --git a/build.rs b/build.rs index e9aa981..080d778 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,11 @@ -const COMMANDS: &[&str] = &["share_text", "share_file"]; +const COMMANDS: &[&str] = &[ + "register_listener", + "remove_listener", + "share_text", + "share_file", + "get_pending_shared_content", + "clear_pending_shared_content", +]; fn main() { tauri_plugin::Builder::new(COMMANDS) diff --git a/cspell.json b/cspell.json index 557af03..11abaff 100644 --- a/cspell.json +++ b/cspell.json @@ -26,7 +26,13 @@ "thiserror", "HSTRING", "SINGLETHREADED", - "NSURL" + "NSURL", + "xcodeproj", + "myapp", + "msix", + "APPCACHE", + "APPMODEL", + "unlisten" ], "useGitignore": true, "ignorePaths": [ diff --git a/examples/share-demo/package.json b/examples/share-demo/package.json index 9b25b02..3cc2c52 100644 --- a/examples/share-demo/package.json +++ b/examples/share-demo/package.json @@ -9,22 +9,27 @@ "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "tauri": "tauri" + "tauri": "tauri", + "tauri:macos:dev": "tauri-macos-xcode dev --open", + "tauri:windows:build": "tauri-windows-bundle build" }, "license": "MIT", "dependencies": { + "@choochmeque/tauri-plugin-sharekit-api": "file:../../", "@tauri-apps/api": "^2", - "@tauri-apps/plugin-opener": "^2", - "@choochmeque/tauri-plugin-sharekit-api": "file:../../" + "@tauri-apps/plugin-opener": "^2" }, "devDependencies": { + "@choochmeque/tauri-apple-extensions": "^0.2.2", + "@choochmeque/tauri-macos-xcode": "^0.1.0", "@sveltejs/adapter-static": "^3.0.6", "@sveltejs/kit": "^2.9.0", "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@tauri-apps/cli": "^2", "svelte": "^5.0.0", "svelte-check": "^4.0.0", "typescript": "~5.6.2", "vite": "^6.0.3", - "@tauri-apps/cli": "^2" + "@choochmeque/tauri-windows-bundle": "^0.1.11" } } diff --git a/examples/share-demo/pnpm-lock.yaml b/examples/share-demo/pnpm-lock.yaml index 5acf48e..d2dd7a9 100644 --- a/examples/share-demo/pnpm-lock.yaml +++ b/examples/share-demo/pnpm-lock.yaml @@ -18,6 +18,12 @@ importers: specifier: ^2 version: 2.5.2 devDependencies: + '@choochmeque/tauri-apple-extensions': + specifier: ^0.2.2 + version: 0.2.2 + '@choochmeque/tauri-macos-xcode': + specifier: ^0.1.0 + version: 0.1.2 '@sveltejs/adapter-static': specifier: ^3.0.6 version: 3.0.10(@sveltejs/kit@2.49.2(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.46.1)(vite@6.4.1))(svelte@5.46.1)(vite@6.4.1)) @@ -45,9 +51,21 @@ importers: packages: + '@choochmeque/tauri-apple-extensions@0.2.2': + resolution: {integrity: sha512-J4UtVipi4NvIZNfPTTzP2W+yrskanr3+Y61ATtEpB1XF6ScJQ267KFpaDpMvFGR6jIEoXRxyOaCJsMs6Edp8ng==} + engines: {node: '>=18.0.0'} + hasBin: true + + '@choochmeque/tauri-macos-xcode@0.1.2': + resolution: {integrity: sha512-TVPC1qsA73SS50y4LGuITBptsnQ94hK2inPt414Q+dLjeO8GN5nkweDgEoMIe8ZSvx4XlD8ZBYJ3A/C1X9ECPA==} + hasBin: true + '@choochmeque/tauri-plugin-sharekit-api@file:../..': resolution: {directory: ../.., type: directory} + '@emnapi/runtime@1.7.1': + resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} @@ -204,6 +222,143 @@ packages: cpu: [x64] os: [win32] + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -478,6 +633,10 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + commander@14.0.2: + resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} + engines: {node: '>=20'} + cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} @@ -495,6 +654,10 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + devalue@5.6.1: resolution: {integrity: sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==} @@ -576,9 +739,18 @@ packages: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + set-cookie-parser@2.7.2: resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + sirv@3.0.2: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} @@ -607,6 +779,9 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + typescript@5.6.3: resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} @@ -665,10 +840,24 @@ packages: snapshots: + '@choochmeque/tauri-apple-extensions@0.2.2': + dependencies: + commander: 14.0.2 + + '@choochmeque/tauri-macos-xcode@0.1.2': + dependencies: + commander: 14.0.2 + sharp: 0.34.5 + '@choochmeque/tauri-plugin-sharekit-api@file:../..': dependencies: '@tauri-apps/api': 2.9.1 + '@emnapi/runtime@1.7.1': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.25.12': optional: true @@ -747,6 +936,102 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true + '@img/colour@1.0.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.7.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -954,6 +1239,8 @@ snapshots: clsx@2.1.1: {} + commander@14.0.2: {} + cookie@0.6.0: {} debug@4.4.3: @@ -962,6 +1249,8 @@ snapshots: deepmerge@4.3.1: {} + detect-libc@2.1.2: {} + devalue@5.6.1: {} esbuild@0.25.12: @@ -1070,8 +1359,41 @@ snapshots: dependencies: mri: 1.2.0 + semver@7.7.3: {} + set-cookie-parser@2.7.2: {} + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + sirv@3.0.2: dependencies: '@polka/url': 1.0.0-next.29 @@ -1117,6 +1439,9 @@ snapshots: totalist@3.0.1: {} + tslib@2.8.1: + optional: true + typescript@5.6.3: {} vite@6.4.1: diff --git a/examples/share-demo/src-tauri/Cargo.toml b/examples/share-demo/src-tauri/Cargo.toml index 4a2f8b3..087b830 100644 --- a/examples/share-demo/src-tauri/Cargo.toml +++ b/examples/share-demo/src-tauri/Cargo.toml @@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -tauri = { version = "2", features = [] } +tauri = { version = "2", features = ["protocol-asset"] } tauri-plugin-opener = "2" tauri-plugin-sharekit = { path = "../../.." } serde = { version = "1", features = ["derive"] } diff --git a/examples/share-demo/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/examples/share-demo/src-tauri/gen/android/app/src/main/AndroidManifest.xml index 042abff..bd7a89e 100644 --- a/examples/share-demo/src-tauri/gen/android/app/src/main/AndroidManifest.xml +++ b/examples/share-demo/src-tauri/gen/android/app/src/main/AndroidManifest.xml @@ -22,6 +22,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.0' +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '11.0' + end + end +end diff --git a/examples/share-demo/src-tauri/gen/apple-macos/ShareExtension/Info.plist b/examples/share-demo/src-tauri/gen/apple-macos/ShareExtension/Info.plist new file mode 100644 index 0000000..d5ef0e9 --- /dev/null +++ b/examples/share-demo/src-tauri/gen/apple-macos/ShareExtension/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Share + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 0.1.0 + CFBundleVersion + 0.1.0 + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + + NSExtensionActivationSupportsFileWithMaxCount + 10 + NSExtensionActivationSupportsImageWithMaxCount + 10 + NSExtensionActivationSupportsMovieWithMaxCount + 10 + NSExtensionActivationSupportsText + + NSExtensionActivationSupportsWebURLWithMaxCount + 1 + + + NSExtensionPointIdentifier + com.apple.share-services + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).ShareViewController + + + diff --git a/examples/share-demo/src-tauri/gen/apple-macos/ShareExtension/ShareExtension.entitlements b/examples/share-demo/src-tauri/gen/apple-macos/ShareExtension/ShareExtension.entitlements new file mode 100644 index 0000000..3d66325 --- /dev/null +++ b/examples/share-demo/src-tauri/gen/apple-macos/ShareExtension/ShareExtension.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.application-groups + + group.com.tauri.dev + + + \ No newline at end of file diff --git a/examples/share-demo/src-tauri/gen/apple-macos/ShareExtension/ShareViewController.swift b/examples/share-demo/src-tauri/gen/apple-macos/ShareExtension/ShareViewController.swift new file mode 100644 index 0000000..f9dd25d --- /dev/null +++ b/examples/share-demo/src-tauri/gen/apple-macos/ShareExtension/ShareViewController.swift @@ -0,0 +1,259 @@ +import Cocoa +import UniformTypeIdentifiers + +class ShareViewController: NSViewController { + + // MARK: - Configuration (will be replaced by setup script) + private let appGroupIdentifier = "group.com.tauri.dev" + private let appURLScheme = "sharedemo" + + override var nibName: NSNib.Name? { + return nil + } + + override func loadView() { + self.view = NSView(frame: NSRect(x: 0, y: 0, width: 1, height: 1)) + } + + override func viewDidLoad() { + super.viewDidLoad() + } + + override func viewDidAppear() { + super.viewDidAppear() + handleSharedContent() + } + + private func handleSharedContent() { + // Check App Groups configuration early + if FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) == nil { + showError("App Groups not configured.\n\nPlease enable 'App Groups' capability in Xcode for both the main app and ShareExtension targets, and configure '\(appGroupIdentifier)' in Apple Developer Portal.") + return + } + + guard let extensionItems = extensionContext?.inputItems as? [NSExtensionItem] else { + completeRequest() + return + } + + // Use a serial queue to safely collect results + let resultQueue = DispatchQueue(label: "sharekit.results") + var sharedContent: [String: Any] = [:] + var files: [[String: Any]] = [] + var textContent: String? = nil + + let group = DispatchGroup() + + for extensionItem in extensionItems { + guard let attachments = extensionItem.attachments else { continue } + + for attachment in attachments { + group.enter() + + if attachment.hasItemConformingToTypeIdentifier(UTType.image.identifier) { + // Check image FIRST (before URL) because images can also be URLs + attachment.loadItem(forTypeIdentifier: UTType.image.identifier, options: nil) { [weak self] item, error in + defer { group.leave() } + guard error == nil else { return } + if let url = item as? URL { + if let fileInfo = self?.copyFileToAppGroup(url: url) { + resultQueue.sync { files.append(fileInfo) } + } + } else if let image = item as? NSImage { + if let fileInfo = self?.saveImageToAppGroup(image: image) { + resultQueue.sync { files.append(fileInfo) } + } + } else if let data = item as? Data { + if let image = NSImage(data: data), + let fileInfo = self?.saveImageToAppGroup(image: image) { + resultQueue.sync { files.append(fileInfo) } + } + } + } + } else if attachment.hasItemConformingToTypeIdentifier(UTType.fileURL.identifier) { + // Handle file URLs from Finder + attachment.loadItem(forTypeIdentifier: UTType.fileURL.identifier, options: nil) { [weak self] item, error in + defer { group.leave() } + if let url = item as? URL { + if let fileInfo = self?.copyFileToAppGroup(url: url) { + resultQueue.sync { files.append(fileInfo) } + } + } + } + } else if attachment.hasItemConformingToTypeIdentifier(UTType.url.identifier) { + attachment.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { [weak self] item, error in + defer { group.leave() } + guard let url = item as? URL else { return } + + if url.isFileURL { + if let fileInfo = self?.copyFileToAppGroup(url: url) { + resultQueue.sync { files.append(fileInfo) } + } + } else { + resultQueue.sync { textContent = url.absoluteString } + } + } + } else if attachment.hasItemConformingToTypeIdentifier(UTType.text.identifier) { + attachment.loadItem(forTypeIdentifier: UTType.text.identifier, options: nil) { item, error in + defer { group.leave() } + if let text = item as? String { + resultQueue.sync { textContent = text } + } + } + } else if attachment.hasItemConformingToTypeIdentifier(UTType.data.identifier) { + attachment.loadItem(forTypeIdentifier: UTType.data.identifier, options: nil) { [weak self] item, error in + defer { group.leave() } + if let url = item as? URL { + if let fileInfo = self?.copyFileToAppGroup(url: url) { + resultQueue.sync { files.append(fileInfo) } + } + } + } + } else { + group.leave() + } + } + } + + group.notify(queue: .main) { [weak self] in + guard let self = self else { return } + + if !files.isEmpty { + sharedContent["type"] = "files" + sharedContent["files"] = files + } else if let text = textContent { + sharedContent["type"] = "text" + sharedContent["text"] = text + } + + if !sharedContent.isEmpty { + _ = self.saveToAppGroup(content: sharedContent) + self.openMainAppAndComplete() + } else { + self.completeRequest() + } + } + } + + private func showError(_ message: String) { + DispatchQueue.main.async { [weak self] in + let alert = NSAlert() + alert.messageText = "ShareKit Error" + alert.informativeText = message + alert.alertStyle = .warning + alert.addButton(withTitle: "OK") + alert.runModal() + self?.completeRequest() + } + } + + private func copyFileToAppGroup(url: URL) -> [String: Any]? { + guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) else { + return nil + } + + let sharedFilesDir = containerURL.appendingPathComponent("shared_files", isDirectory: true) + try? FileManager.default.createDirectory(at: sharedFilesDir, withIntermediateDirectories: true) + + let fileName = url.lastPathComponent + let destinationURL = sharedFilesDir.appendingPathComponent(UUID().uuidString + "_" + fileName) + + do { + if url.startAccessingSecurityScopedResource() { + defer { url.stopAccessingSecurityScopedResource() } + try FileManager.default.copyItem(at: url, to: destinationURL) + } else { + try FileManager.default.copyItem(at: url, to: destinationURL) + } + + var fileInfo: [String: Any] = [ + "path": destinationURL.path, + "name": fileName + ] + + if let mimeType = getMimeType(for: url) { + fileInfo["mimeType"] = mimeType + } + + if let attributes = try? FileManager.default.attributesOfItem(atPath: destinationURL.path), + let size = attributes[.size] as? Int64 { + fileInfo["size"] = size + } + + return fileInfo + } catch { + print("ShareKit: Failed to copy file: \(error)") + return nil + } + } + + private func saveImageToAppGroup(image: NSImage) -> [String: Any]? { + guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) else { + return nil + } + + let sharedFilesDir = containerURL.appendingPathComponent("shared_files", isDirectory: true) + try? FileManager.default.createDirectory(at: sharedFilesDir, withIntermediateDirectories: true) + + let fileName = UUID().uuidString + ".png" + let destinationURL = sharedFilesDir.appendingPathComponent(fileName) + + guard let tiffData = image.tiffRepresentation, + let bitmapRep = NSBitmapImageRep(data: tiffData), + let pngData = bitmapRep.representation(using: .png, properties: [:]) else { + return nil + } + + do { + try pngData.write(to: destinationURL) + + return [ + "path": destinationURL.path, + "name": fileName, + "mimeType": "image/png", + "size": pngData.count + ] + } catch { + print("ShareKit: Failed to save image: \(error)") + return nil + } + } + + private func getMimeType(for url: URL) -> String? { + if let uti = UTType(filenameExtension: url.pathExtension) { + return uti.preferredMIMEType + } + return nil + } + + private func saveToAppGroup(content: [String: Any]) -> Bool { + guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else { + showError("App Groups not configured.\n\nPlease enable 'App Groups' capability in Xcode for both the main app and ShareExtension targets, and configure '\(appGroupIdentifier)' in Apple Developer Portal.") + return false + } + + do { + let data = try JSONSerialization.data(withJSONObject: content) + userDefaults.set(data, forKey: "pendingSharedContent") + userDefaults.synchronize() + return true + } catch { + showError("Failed to save shared content: \(error.localizedDescription)") + return false + } + } + + private func openMainAppAndComplete() { + guard let url = URL(string: "\(appURLScheme)://sharekit-content") else { + completeRequest() + return + } + + NSWorkspace.shared.open(url) + completeRequest() + } + + private func completeRequest() { + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) + } +} diff --git a/examples/share-demo/src-tauri/gen/apple-macos/project.yml b/examples/share-demo/src-tauri/gen/apple-macos/project.yml new file mode 100644 index 0000000..f2265d1 --- /dev/null +++ b/examples/share-demo/src-tauri/gen/apple-macos/project.yml @@ -0,0 +1,98 @@ +name: share-demo +options: + bundleIdPrefix: com.tauri + deploymentTarget: + macOS: "11.0" +fileGroups: [../../src] +configs: + debug: debug + release: release +settingGroups: + app: + base: + PRODUCT_NAME: share-demo + PRODUCT_BUNDLE_IDENTIFIER: com.tauri.dev +targets: + share-demo_macOS: + type: application + platform: macOS + sources: + - path: Assets.xcassets + - path: share-demo_macOS + info: + path: share-demo_macOS/Info.plist + properties: + CFBundleURLTypes: + - CFBundleURLName: com.tauri.dev + CFBundleURLSchemes: + - sharedemo + CFBundleShortVersionString: 0.1.0 + CFBundleVersion: 0.1.0 + LSMinimumSystemVersion: "11.0" + NSHighResolutionCapable: true + entitlements: + path: share-demo_macOS/share-demo_macOS.entitlements + settings: + base: + ARCHS: [arm64, x86_64] + VALID_ARCHS: [arm64, x86_64] + CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION: YES + groups: [app] + dependencies: + - target: share-demo-ShareExtension + embed: true + codeSign: true + - sdk: AppKit.framework + - sdk: CoreGraphics.framework + - sdk: Metal.framework + - sdk: MetalKit.framework + - sdk: QuartzCore.framework + - sdk: Security.framework + - sdk: WebKit.framework + postCompileScripts: + - script: ${PROJECT_DIR}/scripts/build-rust.sh ${CONFIGURATION} "${ARCHS}" + name: Build and Copy Rust Binary + basedOnDependencyAnalysis: false + + share-demo-ShareExtension: + type: app-extension + platform: macOS + deploymentTarget: "11.0" + sources: + - path: ShareExtension + info: + path: ShareExtension/Info.plist + properties: + CFBundleDisplayName: Share + CFBundleShortVersionString: "0.1.0" + CFBundleVersion: "0.1.0" + NSExtension: + NSExtensionAttributes: + NSExtensionActivationRule: + NSExtensionActivationSupportsFileWithMaxCount: 10 + NSExtensionActivationSupportsImageWithMaxCount: 10 + NSExtensionActivationSupportsMovieWithMaxCount: 10 + NSExtensionActivationSupportsText: true + NSExtensionActivationSupportsWebURLWithMaxCount: 1 + NSExtensionPointIdentifier: com.apple.share-services + NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).ShareViewController + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: com.tauri.dev.ShareExtension + SKIP_INSTALL: YES + CODE_SIGN_ENTITLEMENTS: ShareExtension/ShareExtension.entitlements + CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION: YES + +schemes: + share-demo_macOS: + build: + targets: + share-demo_macOS: all + run: + config: debug + profile: + config: release + analyze: + config: debug + archive: + config: release diff --git a/examples/share-demo/src-tauri/gen/apple-macos/scripts/build-rust.sh b/examples/share-demo/src-tauri/gen/apple-macos/scripts/build-rust.sh new file mode 100755 index 0000000..927785a --- /dev/null +++ b/examples/share-demo/src-tauri/gen/apple-macos/scripts/build-rust.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +# Source cargo environment (needed when running from Xcode) +source "$HOME/.cargo/env" 2>/dev/null || true + +# Environment setup for Swift script +export CARGO_TARGET_DIR="${BUILD_DIR}/cargo-target" +export PROJECT_ROOT="${PROJECT_DIR}/../.." + +# Run Swift build script +exec swift "$(dirname "$0")/build.swift" "$1" "$2" diff --git a/examples/share-demo/src-tauri/gen/apple-macos/scripts/build.swift b/examples/share-demo/src-tauri/gen/apple-macos/scripts/build.swift new file mode 100644 index 0000000..a632896 --- /dev/null +++ b/examples/share-demo/src-tauri/gen/apple-macos/scripts/build.swift @@ -0,0 +1,523 @@ +#!/usr/bin/env swift +import Foundation + +// MARK: - Cargo Diagnostic Types + +struct CargoMessage: Codable { + let reason: String? + let message: CompilerMessage? +} + +struct CompilerMessage: Codable { + let message: String + let level: String + let spans: [DiagnosticSpan] + let children: [CompilerMessage]? + let rendered: String? +} + +struct DiagnosticSpan: Codable { + let file_name: String + let line_start: Int + let line_end: Int + let column_start: Int + let column_end: Int + let is_primary: Bool +} + +// MARK: - Tauri Config Types + +struct TauriConfig: Codable { + let identifier: String? +} + +// MARK: - Environment + +struct BuildEnvironment { + let configuration: String + let archs: [String] + let cargoTargetDir: String + let projectRoot: String + let builtProductsDir: String + let executablePath: String + let executableFolderPath: String + let productBundleIdentifier: String + + var isRelease: Bool { + configuration.lowercased() == "release" + } + + var profile: String { + isRelease ? "release" : "debug" + } + + static func fromEnvironment(configuration: String, archs: String) -> BuildEnvironment { + BuildEnvironment( + configuration: configuration, + archs: archs.split(separator: " ").map(String.init), + cargoTargetDir: ProcessInfo.processInfo.environment["CARGO_TARGET_DIR"] ?? "", + projectRoot: ProcessInfo.processInfo.environment["PROJECT_ROOT"] ?? "", + builtProductsDir: ProcessInfo.processInfo.environment["BUILT_PRODUCTS_DIR"] ?? "", + executablePath: ProcessInfo.processInfo.environment["EXECUTABLE_PATH"] ?? "", + executableFolderPath: ProcessInfo.processInfo.environment["EXECUTABLE_FOLDER_PATH"] ?? "", + productBundleIdentifier: ProcessInfo.processInfo.environment["PRODUCT_BUNDLE_IDENTIFIER"] ?? "" + ) + } +} + +// MARK: - Utilities + +func xcodeError(_ message: String, file: String? = nil, line: Int? = nil, column: Int? = nil) { + if let file = file, let line = line { + if let column = column { + print("\(file):\(line):\(column): error: \(message)") + } else { + print("\(file):\(line): error: \(message)") + } + } else { + print("error: \(message)") + } +} + +func xcodeWarning(_ message: String, file: String? = nil, line: Int? = nil, column: Int? = nil) { + if let file = file, let line = line { + if let column = column { + print("\(file):\(line):\(column): warning: \(message)") + } else { + print("\(file):\(line): warning: \(message)") + } + } else { + print("warning: \(message)") + } +} + +func xcodeNote(_ message: String, file: String? = nil, line: Int? = nil, column: Int? = nil) { + if let file = file, let line = line { + if let column = column { + print("\(file):\(line):\(column): note: \(message)") + } else { + print("\(file):\(line): note: \(message)") + } + } else { + print("note: \(message)") + } +} + +func archToTarget(_ arch: String) -> String? { + switch arch { + case "arm64": return "aarch64-apple-darwin" + case "x86_64": return "x86_64-apple-darwin" + default: return nil + } +} + +func getBinaryName(projectRoot: String) -> String? { + let cargoTomlPath = "\(projectRoot)/Cargo.toml" + guard let content = try? String(contentsOfFile: cargoTomlPath, encoding: .utf8) else { + return nil + } + + // Simple regex to find name = "xxx" + let pattern = #"name\s*=\s*"([^"]+)""# + guard let regex = try? NSRegularExpression(pattern: pattern), + let match = regex.firstMatch(in: content, range: NSRange(content.startIndex..., in: content)), + let range = Range(match.range(at: 1), in: content) else { + return nil + } + + return String(content[range]) +} + +func validateBundleIdentifier(env: BuildEnvironment) -> Bool { + let tauriConfPath = "\(env.projectRoot)/tauri.conf.json" + + guard let data = FileManager.default.contents(atPath: tauriConfPath) else { + xcodeError("tauri.conf.json not found at: \(tauriConfPath)") + return false + } + + let config: TauriConfig + do { + config = try JSONDecoder().decode(TauriConfig.self, from: data) + } catch { + xcodeError("Failed to parse tauri.conf.json: \(error)", file: tauriConfPath) + return false + } + + guard let tauriIdentifier = config.identifier else { + xcodeError("identifier field not found in tauri.conf.json", file: tauriConfPath) + return false + } + + guard !env.productBundleIdentifier.isEmpty else { + xcodeError("PRODUCT_BUNDLE_IDENTIFIER not set in Xcode build settings") + return false + } + + if tauriIdentifier != env.productBundleIdentifier { + xcodeError("Bundle identifier mismatch: tauri.conf.json has '\(tauriIdentifier)' but Xcode has '\(env.productBundleIdentifier)'", file: tauriConfPath) + xcodeNote("Update identifier in tauri.conf.json or regenerate the Xcode project") + return false + } + + return true +} + +// MARK: - Diagnostic Formatting + +func formatDiagnostic(_ message: CompilerMessage, projectRoot: String) { + let primarySpan = message.spans.first { $0.is_primary } ?? message.spans.first + + let file: String? + let line: Int? + let column: Int? + + if let span = primarySpan { + // Make path absolute if relative + if span.file_name.hasPrefix("/") { + file = span.file_name + } else { + file = "\(projectRoot)/\(span.file_name)" + } + line = span.line_start + column = span.column_start + } else { + file = nil + line = nil + column = nil + } + + switch message.level { + case "error", "error: internal compiler error": + xcodeError(message.message, file: file, line: line, column: column) + case "warning": + xcodeWarning(message.message, file: file, line: line, column: column) + case "note", "help": + xcodeNote(message.message, file: file, line: line, column: column) + default: + print(message.message) + } + + // Format child messages (notes, help) + if let children = message.children { + for child in children { + formatDiagnostic(child, projectRoot: projectRoot) + } + } +} + +// MARK: - Build + +func runCargo(args: [String], env: BuildEnvironment) -> Bool { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/env") + process.arguments = ["cargo"] + args + ["--message-format=json"] + process.currentDirectoryURL = URL(fileURLWithPath: env.projectRoot) + + // Set CARGO_TARGET_DIR + var environment = ProcessInfo.processInfo.environment + environment["CARGO_TARGET_DIR"] = env.cargoTargetDir + process.environment = environment + + let stdoutPipe = Pipe() + process.standardOutput = stdoutPipe + process.standardError = FileHandle.standardError + + let decoder = JSONDecoder() + var stdoutBuffer = "" + + // Stream stdout in real-time + stdoutPipe.fileHandleForReading.readabilityHandler = { handle in + let data = handle.availableData + guard !data.isEmpty, let str = String(data: data, encoding: .utf8) else { return } + + stdoutBuffer += str + while let newlineIndex = stdoutBuffer.firstIndex(of: "\n") { + let line = String(stdoutBuffer[.. Bool { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/env") + process.arguments = ["cargo", "tauri", "--version"] + process.standardOutput = FileHandle.nullDevice + process.standardError = FileHandle.nullDevice + + do { + try process.run() + process.waitUntilExit() + return process.terminationStatus == 0 + } catch { + return false + } +} + +func processOutputLine(_ line: String, decoder: JSONDecoder, projectRoot: String) { + guard !line.isEmpty else { + print("") + return + } + + guard let lineData = line.data(using: .utf8) else { + print(line) + return + } + + // Try to parse as JSON cargo message + if let message = try? decoder.decode(CargoMessage.self, from: lineData) { + if message.reason == "compiler-message", let compilerMessage = message.message { + formatDiagnostic(compilerMessage, projectRoot: projectRoot) + } + // Skip other JSON messages (compiler-artifact, build-script-executed, etc.) + } else { + // Not JSON - print as regular output (frontend build, etc.) + print(line) + } + fflush(stdout) +} + +func runCargoTauri(args: [String], env: BuildEnvironment) -> Bool { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/env") + // Use -- to pass --message-format to cargo + process.arguments = ["cargo", "tauri"] + args + ["--", "--message-format=json"] + process.currentDirectoryURL = URL(fileURLWithPath: env.projectRoot) + + var environment = ProcessInfo.processInfo.environment + environment["CARGO_TARGET_DIR"] = env.cargoTargetDir + process.environment = environment + + let stdoutPipe = Pipe() + let stderrPipe = Pipe() + process.standardOutput = stdoutPipe + process.standardError = stderrPipe + + let decoder = JSONDecoder() + var stdoutBuffer = "" + var stderrBuffer = "" + + // Stream stdout in real-time + stdoutPipe.fileHandleForReading.readabilityHandler = { handle in + let data = handle.availableData + guard !data.isEmpty, let str = String(data: data, encoding: .utf8) else { return } + + stdoutBuffer += str + while let newlineIndex = stdoutBuffer.firstIndex(of: "\n") { + let line = String(stdoutBuffer[.. Bool { + print("Building \(env.profile) for \(target)...") + + if env.isRelease { + // Check for tauri-cli + if !checkTauriCli() { + xcodeError("tauri-cli is required for release builds. Install it with: cargo install tauri-cli") + return false + } + + // Use tauri build for release (embeds frontend) with JSON diagnostics + return runCargoTauri(args: ["build", "--no-bundle", "--target", target], env: env) + } else { + // Debug build with JSON diagnostics + return runCargo(args: ["build", "--target", target], env: env) + } +} + +func copyBinary(from source: String, to destination: String, folderPath: String) -> Bool { + let fileManager = FileManager.default + + // Create directory + let destFolder = (destination as NSString).deletingLastPathComponent + do { + try fileManager.createDirectory(atPath: destFolder, withIntermediateDirectories: true) + } catch { + xcodeError("Failed to create directory: \(error)") + return false + } + + // Remove old binary + try? fileManager.removeItem(atPath: destination) + + // Copy new binary + do { + try fileManager.copyItem(atPath: source, toPath: destination) + return true + } catch { + xcodeError("Failed to copy binary: \(error)") + return false + } +} + +func createUniversalBinary(binaries: [String], output: String) -> Bool { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/lipo") + process.arguments = ["-create"] + binaries + ["-output", output] + + do { + try process.run() + process.waitUntilExit() + return process.terminationStatus == 0 + } catch { + xcodeError("Failed to run lipo: \(error)") + return false + } +} + +// MARK: - Main + +func main() -> Int32 { + let args = CommandLine.arguments + + guard args.count >= 3 else { + xcodeError("Usage: build.swift ") + return 1 + } + + let env = BuildEnvironment.fromEnvironment(configuration: args[1], archs: args[2]) + + // Validate bundle identifier matches tauri.conf.json + if !validateBundleIdentifier(env: env) { + return 1 + } + + // Get binary name from Cargo.toml + guard let binaryName = getBinaryName(projectRoot: env.projectRoot) else { + xcodeError("Failed to read binary name from Cargo.toml") + return 1 + } + + let destinationPath = "\(env.builtProductsDir)/\(env.executablePath)" + + if env.archs.count > 1 { + // Universal binary + print("Building universal binary for: \(env.archs.joined(separator: " "))") + + var binaryPaths: [String] = [] + + for arch in env.archs { + guard let target = archToTarget(arch) else { + xcodeError("Unknown architecture: \(arch)") + return 1 + } + + if !buildTarget(target, env: env) { + return 1 + } + + let binaryPath = "\(env.cargoTargetDir)/\(target)/\(env.profile)/\(binaryName)" + + guard FileManager.default.fileExists(atPath: binaryPath) else { + xcodeError("Binary not found at \(binaryPath)") + return 1 + } + + binaryPaths.append(binaryPath) + } + + // Create destination directory + let destFolder = (destinationPath as NSString).deletingLastPathComponent + try? FileManager.default.createDirectory(atPath: destFolder, withIntermediateDirectories: true) + try? FileManager.default.removeItem(atPath: destinationPath) + + print("Creating universal binary with lipo...") + if !createUniversalBinary(binaries: binaryPaths, output: destinationPath) { + return 1 + } + + print("Universal build complete - binary copied to \(destinationPath)") + } else { + // Single architecture + guard let arch = env.archs.first, let target = archToTarget(arch) else { + xcodeError("No architecture specified or unknown architecture") + return 1 + } + + if !buildTarget(target, env: env) { + return 1 + } + + let binaryPath = "\(env.cargoTargetDir)/\(target)/\(env.profile)/\(binaryName)" + + guard FileManager.default.fileExists(atPath: binaryPath) else { + xcodeError("Binary not found at \(binaryPath)") + return 1 + } + + print("Found binary: \(binaryPath)") + + if !copyBinary(from: binaryPath, to: destinationPath, folderPath: env.executableFolderPath) { + return 1 + } + + print("Rust build complete - binary copied to \(destinationPath)") + } + + return 0 +} + +exit(main()) diff --git a/examples/share-demo/src-tauri/gen/apple-macos/share-demo.xcodeproj/project.pbxproj b/examples/share-demo/src-tauri/gen/apple-macos/share-demo.xcodeproj/project.pbxproj new file mode 100644 index 0000000..5631f32 --- /dev/null +++ b/examples/share-demo/src-tauri/gen/apple-macos/share-demo.xcodeproj/project.pbxproj @@ -0,0 +1,521 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 01EE6C783D39D087D985D47D /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA16044DC70F9D2B7FC46FEE /* MetalKit.framework */; }; + 11AE2AE5753997673A2A65BD /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCCE7C24ED2D73990175EA1 /* QuartzCore.framework */; }; + 32EA4C4B1A5E9314B6E64A67 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0864E1D675C23CE959828864 /* Assets.xcassets */; }; + 7722E43651AD6B0BB48A4AA4 /* share-demo-ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 2807C1E904B4BDB1E6B5D810 /* share-demo-ShareExtension.appex */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 796D69A2A02142ABBEDDF6CD /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B91EE2794260B4C425F3A13E /* ShareViewController.swift */; }; + 9260C886A1E7E4170CFC4956 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 55E2682F21AC29FB4514A69E /* AppKit.framework */; }; + A93FD688A1FF3E25A5237376 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82329E9CAAE5001915588760 /* WebKit.framework */; }; + BB3788685ACD5FA5BEF21B38 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 598DF56AC243E5FDDB6AFB64 /* Metal.framework */; }; + D5EDCAA89CF8E045EEEF4602 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E8DEA14E94C6C32C2B9B42F /* CoreGraphics.framework */; }; + E2FA4E1E0F7985C00C59B52F /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14B98FDF4C240973E9ED4568 /* Security.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 365E6FC3B0493C5181D241BB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D35B3B4EA26D54B805FBF21A /* Project object */; + proxyType = 1; + remoteGlobalIDString = 346FC50900A754FFA41A5481; + remoteInfo = "share-demo-ShareExtension"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + F4B79C7EE039C1D022DEF299 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 7722E43651AD6B0BB48A4AA4 /* share-demo-ShareExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0864E1D675C23CE959828864 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 0A00038C6B7CB75058E480C7 /* share-demo_macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "share-demo_macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 0E8DEA14E94C6C32C2B9B42F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 14B98FDF4C240973E9ED4568 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 2236F09A7167D83D5545E33A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 2807C1E904B4BDB1E6B5D810 /* share-demo-ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "share-demo-ShareExtension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + 51971532D1E6407413590AC8 /* lib.rs */ = {isa = PBXFileReference; path = lib.rs; sourceTree = ""; }; + 55E2682F21AC29FB4514A69E /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; + 598DF56AC243E5FDDB6AFB64 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; + 5BB59DA520024E76459AC050 /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = ""; }; + 5CCCE7C24ED2D73990175EA1 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + 6C2778D345994B7A2E68759C /* share-demo_macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "share-demo_macOS.entitlements"; sourceTree = ""; }; + 75D590016783904924889B84 /* main.rs */ = {isa = PBXFileReference; path = main.rs; sourceTree = ""; }; + 82329E9CAAE5001915588760 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + B91EE2794260B4C425F3A13E /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; + BD1F05C0FAB039E663966B72 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + FA16044DC70F9D2B7FC46FEE /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 359FF950D320427778840BE0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9260C886A1E7E4170CFC4956 /* AppKit.framework in Frameworks */, + D5EDCAA89CF8E045EEEF4602 /* CoreGraphics.framework in Frameworks */, + BB3788685ACD5FA5BEF21B38 /* Metal.framework in Frameworks */, + 01EE6C783D39D087D985D47D /* MetalKit.framework in Frameworks */, + 11AE2AE5753997673A2A65BD /* QuartzCore.framework in Frameworks */, + E2FA4E1E0F7985C00C59B52F /* Security.framework in Frameworks */, + A93FD688A1FF3E25A5237376 /* WebKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1623AA2D9C38DC7CFD2D4F31 /* src */ = { + isa = PBXGroup; + children = ( + 51971532D1E6407413590AC8 /* lib.rs */, + 75D590016783904924889B84 /* main.rs */, + ); + name = src; + path = ../../src; + sourceTree = ""; + }; + 388DB8912BD0BF9AAA9A8788 /* ShareExtension */ = { + isa = PBXGroup; + children = ( + 2236F09A7167D83D5545E33A /* Info.plist */, + 5BB59DA520024E76459AC050 /* ShareExtension.entitlements */, + B91EE2794260B4C425F3A13E /* ShareViewController.swift */, + ); + path = ShareExtension; + sourceTree = ""; + }; + 45D288B5A4AD8263E4BA34F1 = { + isa = PBXGroup; + children = ( + 0864E1D675C23CE959828864 /* Assets.xcassets */, + 59B61F33D92B7017D63E2E86 /* share-demo_macOS */, + 388DB8912BD0BF9AAA9A8788 /* ShareExtension */, + 1623AA2D9C38DC7CFD2D4F31 /* src */, + F79BF0B05981CF4AAA18753B /* Frameworks */, + 49004C1DCC4794256E3C86DA /* Products */, + ); + sourceTree = ""; + }; + 49004C1DCC4794256E3C86DA /* Products */ = { + isa = PBXGroup; + children = ( + 0A00038C6B7CB75058E480C7 /* share-demo_macOS.app */, + 2807C1E904B4BDB1E6B5D810 /* share-demo-ShareExtension.appex */, + ); + name = Products; + sourceTree = ""; + }; + 59B61F33D92B7017D63E2E86 /* share-demo_macOS */ = { + isa = PBXGroup; + children = ( + BD1F05C0FAB039E663966B72 /* Info.plist */, + 6C2778D345994B7A2E68759C /* share-demo_macOS.entitlements */, + ); + path = "share-demo_macOS"; + sourceTree = ""; + }; + F79BF0B05981CF4AAA18753B /* Frameworks */ = { + isa = PBXGroup; + children = ( + 55E2682F21AC29FB4514A69E /* AppKit.framework */, + 0E8DEA14E94C6C32C2B9B42F /* CoreGraphics.framework */, + 598DF56AC243E5FDDB6AFB64 /* Metal.framework */, + FA16044DC70F9D2B7FC46FEE /* MetalKit.framework */, + 5CCCE7C24ED2D73990175EA1 /* QuartzCore.framework */, + 14B98FDF4C240973E9ED4568 /* Security.framework */, + 82329E9CAAE5001915588760 /* WebKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 05BF27857A27B22CF975984A /* share-demo_macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3708933B4ED4D0B9CA288AAA /* Build configuration list for PBXNativeTarget "share-demo_macOS" */; + buildPhases = ( + 0803CD3B762C7836CD626C70 /* Sources */, + DA61AB729FC2D8DA15F60CEC /* Build and Copy Rust Binary */, + 9C0468521C59CD3D3F71E48C /* Resources */, + 359FF950D320427778840BE0 /* Frameworks */, + F4B79C7EE039C1D022DEF299 /* Embed Foundation Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 3DDC8A56D32CEDFE9B9C5A08 /* PBXTargetDependency */, + ); + name = "share-demo_macOS"; + packageProductDependencies = ( + ); + productName = "share-demo_macOS"; + productReference = 0A00038C6B7CB75058E480C7 /* share-demo_macOS.app */; + productType = "com.apple.product-type.application"; + }; + 346FC50900A754FFA41A5481 /* share-demo-ShareExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 473B2B96B38E42DF19FF5B9B /* Build configuration list for PBXNativeTarget "share-demo-ShareExtension" */; + buildPhases = ( + 21F0553D828CB8F2824F8D22 /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "share-demo-ShareExtension"; + packageProductDependencies = ( + ); + productName = "share-demo-ShareExtension"; + productReference = 2807C1E904B4BDB1E6B5D810 /* share-demo-ShareExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D35B3B4EA26D54B805FBF21A /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + }; + buildConfigurationList = 86125EF81C5B36403FF17BB9 /* Build configuration list for PBXProject "share-demo" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = 45D288B5A4AD8263E4BA34F1; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 346FC50900A754FFA41A5481 /* share-demo-ShareExtension */, + 05BF27857A27B22CF975984A /* share-demo_macOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 9C0468521C59CD3D3F71E48C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 32EA4C4B1A5E9314B6E64A67 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + DA61AB729FC2D8DA15F60CEC /* Build and Copy Rust Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Build and Copy Rust Binary"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "${PROJECT_DIR}/scripts/build-rust.sh ${CONFIGURATION} \"${ARCHS}\""; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 0803CD3B762C7836CD626C70 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 21F0553D828CB8F2824F8D22 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 796D69A2A02142ABBEDDF6CD /* ShareViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 3DDC8A56D32CEDFE9B9C5A08 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 346FC50900A754FFA41A5481 /* share-demo-ShareExtension */; + targetProxy = 365E6FC3B0493C5181D241BB /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 00AE2D1CC9242731B221AAE4 /* debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DEBUG=1", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = debug; + }; + 180632F4773CFD11C66B5087 /* release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES; + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = ShareExtension/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_BUNDLE_IDENTIFIER = com.tauri.dev.ShareExtension; + SDKROOT = macosx; + SKIP_INSTALL = YES; + }; + name = release; + }; + 4BFA4B69655D4497D1549A99 /* debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES; + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = ShareExtension/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_BUNDLE_IDENTIFIER = com.tauri.dev.ShareExtension; + SDKROOT = macosx; + SKIP_INSTALL = YES; + }; + name = debug; + }; + 5661C04ADF56F13CEB7199CF /* release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = release; + }; + AE64DCF564C0EE13C2591D77 /* debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + arm64, + x86_64, + ); + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES; + CODE_SIGN_ENTITLEMENTS = "share-demo_macOS/share-demo_macOS.entitlements"; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "share-demo_macOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.tauri.dev; + PRODUCT_NAME = "share-demo"; + SDKROOT = macosx; + VALID_ARCHS = ( + arm64, + x86_64, + ); + }; + name = debug; + }; + B1AC47758FA5F707C39A3D66 /* release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + arm64, + x86_64, + ); + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES; + CODE_SIGN_ENTITLEMENTS = "share-demo_macOS/share-demo_macOS.entitlements"; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "share-demo_macOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.tauri.dev; + PRODUCT_NAME = "share-demo"; + SDKROOT = macosx; + VALID_ARCHS = ( + arm64, + x86_64, + ); + }; + name = release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 3708933B4ED4D0B9CA288AAA /* Build configuration list for PBXNativeTarget "share-demo_macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AE64DCF564C0EE13C2591D77 /* debug */, + B1AC47758FA5F707C39A3D66 /* release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = debug; + }; + 473B2B96B38E42DF19FF5B9B /* Build configuration list for PBXNativeTarget "share-demo-ShareExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4BFA4B69655D4497D1549A99 /* debug */, + 180632F4773CFD11C66B5087 /* release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = debug; + }; + 86125EF81C5B36403FF17BB9 /* Build configuration list for PBXProject "share-demo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 00AE2D1CC9242731B221AAE4 /* debug */, + 5661C04ADF56F13CEB7199CF /* release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = D35B3B4EA26D54B805FBF21A /* Project object */; +} diff --git a/examples/share-demo/src-tauri/gen/apple-macos/share-demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/share-demo/src-tauri/gen/apple-macos/share-demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/examples/share-demo/src-tauri/gen/apple-macos/share-demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/share-demo/src-tauri/gen/apple-macos/share-demo.xcodeproj/xcshareddata/xcschemes/share-demo_macOS.xcscheme b/examples/share-demo/src-tauri/gen/apple-macos/share-demo.xcodeproj/xcshareddata/xcschemes/share-demo_macOS.xcscheme new file mode 100644 index 0000000..2f9a619 --- /dev/null +++ b/examples/share-demo/src-tauri/gen/apple-macos/share-demo.xcodeproj/xcshareddata/xcschemes/share-demo_macOS.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/share-demo/src-tauri/gen/apple-macos/share-demo_macOS/Info.plist b/examples/share-demo/src-tauri/gen/apple-macos/share-demo_macOS/Info.plist new file mode 100644 index 0000000..628e2de --- /dev/null +++ b/examples/share-demo/src-tauri/gen/apple-macos/share-demo_macOS/Info.plist @@ -0,0 +1,37 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.1.0 + CFBundleURLTypes + + + CFBundleURLName + com.tauri.dev + CFBundleURLSchemes + + sharedemo + + + + CFBundleVersion + 0.1.0 + LSMinimumSystemVersion + 11.0 + NSHighResolutionCapable + + + diff --git a/examples/share-demo/src-tauri/gen/apple-macos/share-demo_macOS/share-demo_macOS.entitlements b/examples/share-demo/src-tauri/gen/apple-macos/share-demo_macOS/share-demo_macOS.entitlements new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/examples/share-demo/src-tauri/gen/apple-macos/share-demo_macOS/share-demo_macOS.entitlements @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/share-demo/src-tauri/gen/apple/ShareExtension/Info.plist b/examples/share-demo/src-tauri/gen/apple/ShareExtension/Info.plist new file mode 100644 index 0000000..d5ef0e9 --- /dev/null +++ b/examples/share-demo/src-tauri/gen/apple/ShareExtension/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Share + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 0.1.0 + CFBundleVersion + 0.1.0 + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + + NSExtensionActivationSupportsFileWithMaxCount + 10 + NSExtensionActivationSupportsImageWithMaxCount + 10 + NSExtensionActivationSupportsMovieWithMaxCount + 10 + NSExtensionActivationSupportsText + + NSExtensionActivationSupportsWebURLWithMaxCount + 1 + + + NSExtensionPointIdentifier + com.apple.share-services + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).ShareViewController + + + diff --git a/examples/share-demo/src-tauri/gen/apple/ShareExtension/ShareExtension.entitlements b/examples/share-demo/src-tauri/gen/apple/ShareExtension/ShareExtension.entitlements new file mode 100644 index 0000000..0a83a3a --- /dev/null +++ b/examples/share-demo/src-tauri/gen/apple/ShareExtension/ShareExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.tauri.dev + + + \ No newline at end of file diff --git a/examples/share-demo/src-tauri/gen/apple/ShareExtension/ShareViewController.swift b/examples/share-demo/src-tauri/gen/apple/ShareExtension/ShareViewController.swift new file mode 100644 index 0000000..dc0d9d5 --- /dev/null +++ b/examples/share-demo/src-tauri/gen/apple/ShareExtension/ShareViewController.swift @@ -0,0 +1,246 @@ +import UIKit +import Social +import MobileCoreServices +import UniformTypeIdentifiers + +class ShareViewController: UIViewController { + + // MARK: - Configuration (will be replaced by setup script) + private let appGroupIdentifier = "group.com.tauri.dev" + private let appURLScheme = "sharedemo" + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .clear + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + handleSharedContent() + } + + private func handleSharedContent() { + // Check App Groups configuration early + if FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) == nil { + showError("App Groups not configured.\n\nPlease enable 'App Groups' capability in Xcode for both the main app and ShareExtension targets, and configure '\(appGroupIdentifier)' in Apple Developer Portal.") + return + } + + guard let extensionItems = extensionContext?.inputItems as? [NSExtensionItem] else { + completeRequest() + return + } + + // Use a serial queue to safely collect results + let resultQueue = DispatchQueue(label: "sharekit.results") + var sharedContent: [String: Any] = [:] + var files: [[String: Any]] = [] + var textContent: String? = nil + + let group = DispatchGroup() + + for extensionItem in extensionItems { + guard let attachments = extensionItem.attachments else { continue } + + for attachment in attachments { + group.enter() + + if attachment.hasItemConformingToTypeIdentifier(UTType.image.identifier) { + // Check image FIRST (before URL) because images can also be URLs + attachment.loadItem(forTypeIdentifier: UTType.image.identifier, options: nil) { [weak self] item, error in + defer { group.leave() } + if let url = item as? URL { + if let fileInfo = self?.copyFileToAppGroup(url: url) { + resultQueue.sync { files.append(fileInfo) } + } + } else if let image = item as? UIImage { + if let fileInfo = self?.saveImageToAppGroup(image: image) { + resultQueue.sync { files.append(fileInfo) } + } + } + } + } else if attachment.hasItemConformingToTypeIdentifier(UTType.url.identifier) { + attachment.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { [weak self] item, error in + defer { group.leave() } + guard let url = item as? URL else { return } + + if url.isFileURL { + if let fileInfo = self?.copyFileToAppGroup(url: url) { + resultQueue.sync { files.append(fileInfo) } + } + } else { + resultQueue.sync { textContent = url.absoluteString } + } + } + } else if attachment.hasItemConformingToTypeIdentifier(UTType.text.identifier) { + attachment.loadItem(forTypeIdentifier: UTType.text.identifier, options: nil) { item, error in + defer { group.leave() } + if let text = item as? String { + resultQueue.sync { textContent = text } + } + } + } else if attachment.hasItemConformingToTypeIdentifier(UTType.data.identifier) { + attachment.loadItem(forTypeIdentifier: UTType.data.identifier, options: nil) { [weak self] item, error in + defer { group.leave() } + if let url = item as? URL { + if let fileInfo = self?.copyFileToAppGroup(url: url) { + resultQueue.sync { files.append(fileInfo) } + } + } + } + } else { + group.leave() + } + } + } + + group.notify(queue: .main) { [weak self] in + guard let self = self else { return } + + if !files.isEmpty { + sharedContent["type"] = "files" + sharedContent["files"] = files + } else if let text = textContent { + sharedContent["type"] = "text" + sharedContent["text"] = text + } + + if !sharedContent.isEmpty { + _ = self.saveToAppGroup(content: sharedContent) + self.openMainAppAndComplete() + } else { + self.completeRequest() + } + } + } + + private func showError(_ message: String) { + let alert = UIAlertController( + title: "ShareKit Error", + message: message, + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: "OK", style: .default) { [weak self] _ in + self?.completeRequest() + }) + present(alert, animated: true) + } + + private func copyFileToAppGroup(url: URL) -> [String: Any]? { + guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) else { + return nil + } + + let sharedFilesDir = containerURL.appendingPathComponent("shared_files", isDirectory: true) + try? FileManager.default.createDirectory(at: sharedFilesDir, withIntermediateDirectories: true) + + let fileName = url.lastPathComponent + let destinationURL = sharedFilesDir.appendingPathComponent(UUID().uuidString + "_" + fileName) + + do { + if url.startAccessingSecurityScopedResource() { + defer { url.stopAccessingSecurityScopedResource() } + try FileManager.default.copyItem(at: url, to: destinationURL) + } else { + try FileManager.default.copyItem(at: url, to: destinationURL) + } + + var fileInfo: [String: Any] = [ + "path": destinationURL.path, + "name": fileName + ] + + if let mimeType = getMimeType(for: url) { + fileInfo["mimeType"] = mimeType + } + + if let attributes = try? FileManager.default.attributesOfItem(atPath: destinationURL.path), + let size = attributes[.size] as? Int64 { + fileInfo["size"] = size + } + + return fileInfo + } catch { + print("ShareKit: Failed to copy file: \(error)") + return nil + } + } + + private func saveImageToAppGroup(image: UIImage) -> [String: Any]? { + guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) else { + return nil + } + + let sharedFilesDir = containerURL.appendingPathComponent("shared_files", isDirectory: true) + try? FileManager.default.createDirectory(at: sharedFilesDir, withIntermediateDirectories: true) + + let fileName = UUID().uuidString + ".png" + let destinationURL = sharedFilesDir.appendingPathComponent(fileName) + + guard let data = image.pngData() else { return nil } + + do { + try data.write(to: destinationURL) + + return [ + "path": destinationURL.path, + "name": fileName, + "mimeType": "image/png", + "size": data.count + ] + } catch { + print("ShareKit: Failed to save image: \(error)") + return nil + } + } + + private func getMimeType(for url: URL) -> String? { + if let uti = UTType(filenameExtension: url.pathExtension) { + return uti.preferredMIMEType + } + return nil + } + + private func saveToAppGroup(content: [String: Any]) -> Bool { + guard let userDefaults = UserDefaults(suiteName: appGroupIdentifier) else { + showError("App Groups not configured.\n\nPlease enable 'App Groups' capability in Xcode for both the main app and ShareExtension targets, and configure '\(appGroupIdentifier)' in Apple Developer Portal.") + return false + } + + do { + let data = try JSONSerialization.data(withJSONObject: content) + userDefaults.set(data, forKey: "pendingSharedContent") + userDefaults.synchronize() + return true + } catch { + showError("Failed to save shared content: \(error.localizedDescription)") + return false + } + } + + private func openMainAppAndComplete() { + guard let url = URL(string: "\(appURLScheme)://sharekit-content") else { + completeRequest() + return + } + + var responder: UIResponder? = self + while responder != nil { + if let application = responder as? UIApplication { + if #available(iOS 18.0, *) { + application.open(url, options: [:], completionHandler: nil) + } else { + _ = application.perform(NSSelectorFromString("openURL:"), with: url) + } + break + } + responder = responder?.next + } + + completeRequest() + } + + private func completeRequest() { + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) + } +} diff --git a/examples/share-demo/src-tauri/gen/apple/project.yml b/examples/share-demo/src-tauri/gen/apple/project.yml index fc1eec3..9d8c695 100644 --- a/examples/share-demo/src-tauri/gen/apple/project.yml +++ b/examples/share-demo/src-tauri/gen/apple/project.yml @@ -39,6 +39,10 @@ targets: info: path: share-demo_iOS/Info.plist properties: + CFBundleURLTypes: + - CFBundleURLName: com.tauri.dev + CFBundleURLSchemes: + - sharedemo LSRequiresIPhoneOS: true UILaunchStoryboardName: LaunchScreen UIRequiredDeviceCapabilities: [arm64, metal] @@ -70,6 +74,9 @@ targets: EXCLUDED_ARCHS[sdk=iphoneos*]: x86_64 groups: [app] dependencies: + - target: share-demo-ShareExtension + embed: true + codeSign: true - framework: libapp.a embed: false - sdk: CoreGraphics.framework @@ -85,4 +92,31 @@ targets: basedOnDependencyAnalysis: false outputFiles: - $(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a - - $(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a \ No newline at end of file + - $(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a + share-demo-ShareExtension: + type: app-extension + platform: iOS + deploymentTarget: "14.0" + sources: + - path: ShareExtension + info: + path: ShareExtension/Info.plist + properties: + CFBundleDisplayName: Share + CFBundleShortVersionString: "0.1.0" + CFBundleVersion: "0.1.0" + NSExtension: + NSExtensionAttributes: + NSExtensionActivationRule: + NSExtensionActivationSupportsFileWithMaxCount: 10 + NSExtensionActivationSupportsImageWithMaxCount: 10 + NSExtensionActivationSupportsMovieWithMaxCount: 10 + NSExtensionActivationSupportsText: true + NSExtensionActivationSupportsWebURLWithMaxCount: 1 + NSExtensionPointIdentifier: com.apple.share-services + NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).ShareViewController + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: com.tauri.dev.ShareExtension + SKIP_INSTALL: YES + CODE_SIGN_ENTITLEMENTS: ShareExtension/ShareExtension.entitlements diff --git a/examples/share-demo/src-tauri/gen/apple/share-demo.xcodeproj/project.pbxproj b/examples/share-demo/src-tauri/gen/apple/share-demo.xcodeproj/project.pbxproj index a0560ff..44d5d54 100644 --- a/examples/share-demo/src-tauri/gen/apple/share-demo.xcodeproj/project.pbxproj +++ b/examples/share-demo/src-tauri/gen/apple/share-demo.xcodeproj/project.pbxproj @@ -3,40 +3,72 @@ archiveVersion = 1; classes = { }; - objectVersion = 77; + objectVersion = 63; objects = { /* Begin PBXBuildFile section */ 06DCEFF51CAB770C5D829309 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 7B778F2EECF81887D1A0BC95 /* assets */; }; 32C547235F3B94638AEF25F6 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = DCB952AB100B38C9D9520EF0 /* main.mm */; }; 76F33D09C65CD01A184DDB96 /* libapp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ECECAF43BD8026C337A33CF5 /* libapp.a */; }; + 796D69A2A02142ABBEDDF6CD /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B91EE2794260B4C425F3A13E /* ShareViewController.swift */; }; 79EB6780CF905A2FE96222A8 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82329E9CAAE5001915588760 /* WebKit.framework */; }; 7EB469D532759BC0057A239F /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 598DF56AC243E5FDDB6AFB64 /* Metal.framework */; }; 93305C477A44758969508E99 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 22E983BB538262B8C49B973B /* LaunchScreen.storyboard */; }; 991615C307B241B96D48D565 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0864E1D675C23CE959828864 /* Assets.xcassets */; }; A1485209031280B87D420915 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14B98FDF4C240973E9ED4568 /* Security.framework */; }; AD7339481B74FBC1CA9985F6 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA16044DC70F9D2B7FC46FEE /* MetalKit.framework */; }; + B8F352D3886A5EF860575CA8 /* share-demo-ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 2807C1E904B4BDB1E6B5D810 /* share-demo-ShareExtension.appex */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DB7DC651449146738810C0D8 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E8DEA14E94C6C32C2B9B42F /* CoreGraphics.framework */; }; + E5B7373174F06A27E9B63AB1 /* libapp.a in Resources */ = {isa = PBXBuildFile; fileRef = E1F0942C0878FE30C03DD5A7 /* libapp.a */; }; E6ED2AD0F9ADFD0DFB19AED2 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E5DFC6971A212EF857DFC766 /* UIKit.framework */; }; F9139FAF74159CDD003F84E6 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCCE7C24ED2D73990175EA1 /* QuartzCore.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + BF0608F085009237745F8B03 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D35B3B4EA26D54B805FBF21A /* Project object */; + proxyType = 1; + remoteGlobalIDString = 346FC50900A754FFA41A5481; + remoteInfo = "share-demo-ShareExtension"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 03AC8EC73F12342BD82BC50A /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + B8F352D3886A5EF860575CA8 /* share-demo-ShareExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 0864E1D675C23CE959828864 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0E8DEA14E94C6C32C2B9B42F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 14B98FDF4C240973E9ED4568 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 2236F09A7167D83D5545E33A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 22E983BB538262B8C49B973B /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + 2807C1E904B4BDB1E6B5D810 /* share-demo-ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "share-demo-ShareExtension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 49C8A2028B76F24C8A86F66C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; - 51971532D1E6407413590AC8 /* lib.rs */ = {isa = PBXFileReference; path = lib.rs; sourceTree = ""; }; + 51971532D1E6407413590AC8 /* lib.rs */ = {isa = PBXFileReference; lastKnownFileType = text; path = lib.rs; sourceTree = ""; }; 598DF56AC243E5FDDB6AFB64 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; + 5BB59DA520024E76459AC050 /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = ""; }; 5CCCE7C24ED2D73990175EA1 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; - 75C284D22AC76A9D522F0FE3 /* share-demo_iOS.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = "share-demo_iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 75D590016783904924889B84 /* main.rs */ = {isa = PBXFileReference; path = main.rs; sourceTree = ""; }; + 75C284D22AC76A9D522F0FE3 /* share-demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "share-demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 75D590016783904924889B84 /* main.rs */ = {isa = PBXFileReference; lastKnownFileType = text; path = main.rs; sourceTree = ""; }; 79BAB7738245C49FC7672AC8 /* share-demo_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "share-demo_iOS.entitlements"; sourceTree = ""; }; 7B778F2EECF81887D1A0BC95 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = SOURCE_ROOT; }; 7DDA650DA80CEFB62F9C4ABA /* bindings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bindings.h; sourceTree = ""; }; 82329E9CAAE5001915588760 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + B91EE2794260B4C425F3A13E /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; DCB952AB100B38C9D9520EF0 /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; + E1F0942C0878FE30C03DD5A7 /* libapp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libapp.a; sourceTree = ""; }; E5DFC6971A212EF857DFC766 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; ECECAF43BD8026C337A33CF5 /* libapp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libapp.a; sourceTree = ""; }; FA16044DC70F9D2B7FC46FEE /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; @@ -74,10 +106,21 @@ 26FE974358000C388335C53C /* Externals */ = { isa = PBXGroup; children = ( + 8BE18C4C2DAC928F623F9CEC /* arm64 */, ); path = Externals; sourceTree = ""; }; + 388DB8912BD0BF9AAA9A8788 /* ShareExtension */ = { + isa = PBXGroup; + children = ( + 2236F09A7167D83D5545E33A /* Info.plist */, + 5BB59DA520024E76459AC050 /* ShareExtension.entitlements */, + B91EE2794260B4C425F3A13E /* ShareViewController.swift */, + ); + path = ShareExtension; + sourceTree = ""; + }; 3991BEBBB00F80893E79FF29 /* share-demo */ = { isa = PBXGroup; children = ( @@ -95,6 +138,7 @@ 22E983BB538262B8C49B973B /* LaunchScreen.storyboard */, 26FE974358000C388335C53C /* Externals */, A45FB45B07A6EC7F401AB61F /* share-demo_iOS */, + 388DB8912BD0BF9AAA9A8788 /* ShareExtension */, FB35A88FE27DDCDF9226EEA2 /* Sources */, 1623AA2D9C38DC7CFD2D4F31 /* src */, F79BF0B05981CF4AAA18753B /* Frameworks */, @@ -105,7 +149,8 @@ 49004C1DCC4794256E3C86DA /* Products */ = { isa = PBXGroup; children = ( - 75C284D22AC76A9D522F0FE3 /* share-demo_iOS.app */, + 75C284D22AC76A9D522F0FE3 /* share-demo.app */, + 2807C1E904B4BDB1E6B5D810 /* share-demo-ShareExtension.appex */, ); name = Products; sourceTree = ""; @@ -118,6 +163,14 @@ path = bindings; sourceTree = ""; }; + 8BE18C4C2DAC928F623F9CEC /* arm64 */ = { + isa = PBXGroup; + children = ( + CD07150EA53EA673E709C6C7 /* debug */, + ); + path = arm64; + sourceTree = ""; + }; A45FB45B07A6EC7F401AB61F /* share-demo_iOS */ = { isa = PBXGroup; children = ( @@ -127,6 +180,14 @@ path = "share-demo_iOS"; sourceTree = ""; }; + CD07150EA53EA673E709C6C7 /* debug */ = { + isa = PBXGroup; + children = ( + E1F0942C0878FE30C03DD5A7 /* libapp.a */, + ); + path = debug; + sourceTree = ""; + }; F79BF0B05981CF4AAA18753B /* Frameworks */ = { isa = PBXGroup; children = ( @@ -153,6 +214,23 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 346FC50900A754FFA41A5481 /* share-demo-ShareExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 473B2B96B38E42DF19FF5B9B /* Build configuration list for PBXNativeTarget "share-demo-ShareExtension" */; + buildPhases = ( + 21F0553D828CB8F2824F8D22 /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "share-demo-ShareExtension"; + packageProductDependencies = ( + ); + productName = "share-demo-ShareExtension"; + productReference = 2807C1E904B4BDB1E6B5D810 /* share-demo-ShareExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; 6FDCEDE250E2BB0265D720AA /* share-demo_iOS */ = { isa = PBXNativeTarget; buildConfigurationList = 71B2A2182A0A59C8E30C088E /* Build configuration list for PBXNativeTarget "share-demo_iOS" */; @@ -161,16 +239,18 @@ CC3D428E7976C8BACBDC3D95 /* Sources */, AC009931973ADD8BF23106E1 /* Resources */, 0B9879E2EA5E6EEDA45A631D /* Frameworks */, + 03AC8EC73F12342BD82BC50A /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + 6FCE2BAA1BE5C7441E361C96 /* PBXTargetDependency */, ); name = "share-demo_iOS"; packageProductDependencies = ( ); productName = "share-demo_iOS"; - productReference = 75C284D22AC76A9D522F0FE3 /* share-demo_iOS.app */; + productReference = 75C284D22AC76A9D522F0FE3 /* share-demo.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -192,10 +272,10 @@ ); mainGroup = 45D288B5A4AD8263E4BA34F1; minimizedProjectReferenceProxies = 1; - preferredProjectObjectVersion = 77; projectDirPath = ""; projectRoot = ""; targets = ( + 346FC50900A754FFA41A5481 /* share-demo-ShareExtension */, 6FDCEDE250E2BB0265D720AA /* share-demo_iOS */, ); }; @@ -209,6 +289,7 @@ 991615C307B241B96D48D565 /* Assets.xcassets in Resources */, 93305C477A44758969508E99 /* LaunchScreen.storyboard in Resources */, 06DCEFF51CAB770C5D829309 /* assets in Resources */, + E5B7373174F06A27E9B63AB1 /* libapp.a in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -239,6 +320,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 21F0553D828CB8F2824F8D22 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 796D69A2A02142ABBEDDF6CD /* ShareViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; CC3D428E7976C8BACBDC3D95 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -249,6 +338,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 6FCE2BAA1BE5C7441E361C96 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 346FC50900A754FFA41A5481 /* share-demo-ShareExtension */; + targetProxy = BF0608F085009237745F8B03 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ 00AE2D1CC9242731B221AAE4 /* debug */ = { isa = XCBuildConfiguration; @@ -317,12 +414,11 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ARCHS = ( - arm64, - ); + ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "share-demo_iOS/share-demo_iOS.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = E48BXLAA3H; ENABLE_BITCODE = NO; "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64; FRAMEWORK_SEARCH_PATHS = ( @@ -334,8 +430,20 @@ "$(inherited)", "@executable_path/Frameworks", ); - "LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; - "LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; + "LIBRARY_SEARCH_PATHS[arch=arm64]" = ( + "$(inherited)", + "$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)", + "$(SDKROOT)/usr/lib/swift", + "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", + "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", + ); + "LIBRARY_SEARCH_PATHS[arch=x86_64]" = ( + "$(inherited)", + "$(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION)", + "$(SDKROOT)/usr/lib/swift", + "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", + "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", + ); PRODUCT_BUNDLE_IDENTIFIER = com.tauri.dev; PRODUCT_NAME = "share-demo"; SDKROOT = iphoneos; @@ -344,6 +452,44 @@ }; name = debug; }; + 180632F4773CFD11C66B5087 /* release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + DEVELOPMENT_TEAM = E48BXLAA3H; + INFOPLIST_FILE = ShareExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.tauri.dev.ShareExtension; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = release; + }; + 4BFA4B69655D4497D1549A99 /* debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + DEVELOPMENT_TEAM = E48BXLAA3H; + INFOPLIST_FILE = ShareExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.tauri.dev.ShareExtension; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = debug; + }; 5661C04ADF56F13CEB7199CF /* release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -404,12 +550,11 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ARCHS = ( - arm64, - ); + ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "share-demo_iOS/share-demo_iOS.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = E48BXLAA3H; ENABLE_BITCODE = NO; "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64; FRAMEWORK_SEARCH_PATHS = ( @@ -421,8 +566,20 @@ "$(inherited)", "@executable_path/Frameworks", ); - "LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; - "LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; + "LIBRARY_SEARCH_PATHS[arch=arm64]" = ( + "$(inherited)", + "$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)", + "$(SDKROOT)/usr/lib/swift", + "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", + "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", + ); + "LIBRARY_SEARCH_PATHS[arch=x86_64]" = ( + "$(inherited)", + "$(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION)", + "$(SDKROOT)/usr/lib/swift", + "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", + "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", + ); PRODUCT_BUNDLE_IDENTIFIER = com.tauri.dev; PRODUCT_NAME = "share-demo"; SDKROOT = iphoneos; @@ -434,6 +591,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 473B2B96B38E42DF19FF5B9B /* Build configuration list for PBXNativeTarget "share-demo-ShareExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4BFA4B69655D4497D1549A99 /* debug */, + 180632F4773CFD11C66B5087 /* release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = debug; + }; 71B2A2182A0A59C8E30C088E /* Build configuration list for PBXNativeTarget "share-demo_iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/examples/share-demo/src-tauri/gen/apple/share-demo_iOS/Info.plist b/examples/share-demo/src-tauri/gen/apple/share-demo_iOS/Info.plist index e0b1335..b78a8cf 100644 --- a/examples/share-demo/src-tauri/gen/apple/share-demo_iOS/Info.plist +++ b/examples/share-demo/src-tauri/gen/apple/share-demo_iOS/Info.plist @@ -16,6 +16,17 @@ APPL CFBundleShortVersionString 0.1.0 + CFBundleURLTypes + + + CFBundleURLName + com.tauri.dev + CFBundleURLSchemes + + sharedemo + + + CFBundleVersion 0.1.0 LSRequiresIPhoneOS diff --git a/examples/share-demo/src-tauri/gen/apple/share-demo_iOS/share-demo_iOS.entitlements b/examples/share-demo/src-tauri/gen/apple/share-demo_iOS/share-demo_iOS.entitlements index 0c67376..76b6692 100644 --- a/examples/share-demo/src-tauri/gen/apple/share-demo_iOS/share-demo_iOS.entitlements +++ b/examples/share-demo/src-tauri/gen/apple/share-demo_iOS/share-demo_iOS.entitlements @@ -1,5 +1,10 @@ - + + com.apple.security.application-groups + + group.com.tauri.dev + + diff --git a/examples/share-demo/src-tauri/gen/windows/.gitignore b/examples/share-demo/src-tauri/gen/windows/.gitignore new file mode 100644 index 0000000..64e107e --- /dev/null +++ b/examples/share-demo/src-tauri/gen/windows/.gitignore @@ -0,0 +1,2 @@ +# Generated files +# Keep bundle.config.json and templates in git diff --git a/examples/share-demo/src-tauri/gen/windows/AppxManifest.xml.template b/examples/share-demo/src-tauri/gen/windows/AppxManifest.xml.template new file mode 100644 index 0000000..eff062f --- /dev/null +++ b/examples/share-demo/src-tauri/gen/windows/AppxManifest.xml.template @@ -0,0 +1,51 @@ + + + + + + + {{DISPLAY_NAME}} + {{PUBLISHER_DISPLAY_NAME}} + Assets\StoreLogo.png + + + + + + + + + + + + + + + + +{{EXTENSIONS}} + + + + +{{CAPABILITIES}} + + diff --git a/examples/share-demo/src-tauri/gen/windows/Assets/LargeTile.png b/examples/share-demo/src-tauri/gen/windows/Assets/LargeTile.png new file mode 100644 index 0000000..f9bc048 Binary files /dev/null and b/examples/share-demo/src-tauri/gen/windows/Assets/LargeTile.png differ diff --git a/examples/share-demo/src-tauri/gen/windows/Assets/Square150x150Logo.png b/examples/share-demo/src-tauri/gen/windows/Assets/Square150x150Logo.png new file mode 100644 index 0000000..624c7bf Binary files /dev/null and b/examples/share-demo/src-tauri/gen/windows/Assets/Square150x150Logo.png differ diff --git a/examples/share-demo/src-tauri/gen/windows/Assets/Square44x44Logo.png b/examples/share-demo/src-tauri/gen/windows/Assets/Square44x44Logo.png new file mode 100644 index 0000000..d5fbfb2 Binary files /dev/null and b/examples/share-demo/src-tauri/gen/windows/Assets/Square44x44Logo.png differ diff --git a/examples/share-demo/src-tauri/gen/windows/Assets/StoreLogo.png b/examples/share-demo/src-tauri/gen/windows/Assets/StoreLogo.png new file mode 100644 index 0000000..4556388 Binary files /dev/null and b/examples/share-demo/src-tauri/gen/windows/Assets/StoreLogo.png differ diff --git a/examples/share-demo/src-tauri/gen/windows/Assets/Wide310x150Logo.png b/examples/share-demo/src-tauri/gen/windows/Assets/Wide310x150Logo.png new file mode 100644 index 0000000..1deec35 Binary files /dev/null and b/examples/share-demo/src-tauri/gen/windows/Assets/Wide310x150Logo.png differ diff --git a/examples/share-demo/src-tauri/gen/windows/bundle.config.json b/examples/share-demo/src-tauri/gen/windows/bundle.config.json new file mode 100644 index 0000000..d9fc22e --- /dev/null +++ b/examples/share-demo/src-tauri/gen/windows/bundle.config.json @@ -0,0 +1,18 @@ +{ + "publisher": "CN=YourCompany", + "publisherDisplayName": "Your Company Name", + "capabilities": { + "general": [ + "internetClient" + ] + }, + "extensions": { + "shareTarget": true, + "fileAssociations": [], + "protocolHandlers": [] + }, + "signing": { + "pfx": null, + "pfxPassword": null + } +} diff --git a/examples/share-demo/src-tauri/src/lib.rs b/examples/share-demo/src-tauri/src/lib.rs index d85e4ee..0d3994d 100644 --- a/examples/share-demo/src-tauri/src/lib.rs +++ b/examples/share-demo/src-tauri/src/lib.rs @@ -1,15 +1,19 @@ -// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ -#[tauri::command] -fn greet(name: &str) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) -} - #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_sharekit::init()) - .invoke_handler(tauri::generate_handler![greet]) + .setup(|app| { + // Windows Share Target handling + #[cfg(target_os = "windows")] + { + use tauri_plugin_sharekit::ShareExt; + if app.share().handle_share_activation()? { + app.handle().exit(0); + } + } + Ok(()) + }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/examples/share-demo/src-tauri/tauri.conf.json b/examples/share-demo/src-tauri/tauri.conf.json index 12c78d0..858b035 100644 --- a/examples/share-demo/src-tauri/tauri.conf.json +++ b/examples/share-demo/src-tauri/tauri.conf.json @@ -18,7 +18,15 @@ } ], "security": { - "csp": null + "csp": null, + "assetProtocol": { + "enable": true, + "scope": [ + "$CACHE/**", + "$APPCACHE/**", + "**/Containers/Shared/AppGroup/**" + ] + } } }, "bundle": { diff --git a/examples/share-demo/src/routes/+page.svelte b/examples/share-demo/src/routes/+page.svelte index a0ec325..423f00d 100644 --- a/examples/share-demo/src/routes/+page.svelte +++ b/examples/share-demo/src/routes/+page.svelte @@ -1,5 +1,19 @@ @@ -57,6 +125,32 @@

ShareKit Demo

+ {#if receivedContent} +
+

Received Content

+

Type: {receivedContent.type}

+ {#if receivedContent.type === "text" && receivedContent.text} +

Text: {receivedContent.text}

+ {:else if receivedContent.type === "files" && receivedContent.files} +

Files:

+
    + {#each receivedContent.files as file} +
  • + {#if isImage(file.mimeType)} + {file.name} + {/if} + {file.name} +
    Path: {file.path} + {#if file.mimeType}
    MIME: {file.mimeType}{/if} + {#if file.size}
    Size: {file.size} bytes{/if} +
  • + {/each} +
+ {/if} + +
+ {/if} +

Position Settings (iPad/macOS)

@@ -113,6 +207,12 @@

{fileStatus}

{/if}
+ +
+

Log Output

+
{logs || 'No activity yet...'}
+ +