diff --git a/.github/config/dependency-review-config.yml b/.github/config/dependency-review-config.yml new file mode 100644 index 000000000..5df58cdb9 --- /dev/null +++ b/.github/config/dependency-review-config.yml @@ -0,0 +1 @@ +allow-ghsas: GHSA-wrw7-89jp-8q8g \ No newline at end of file diff --git a/.github/workflows/README-tauri.md b/.github/workflows/README-tauri.md new file mode 100644 index 000000000..be6346045 --- /dev/null +++ b/.github/workflows/README-tauri.md @@ -0,0 +1,182 @@ +# Tauri Build Workflows + +This directory contains GitHub Actions workflows for building Tauri desktop applications for Stirling-PDF. + +## Workflow + +### `tauri-build.yml` - Unified Build Workflow + +**Purpose**: Build Tauri applications for all platforms (Windows, macOS, Linux) with comprehensive testing and validation. + +**Triggers**: +- Manual dispatch with platform selection (windows, macos, linux, or all) +- Pull requests affecting Tauri-related files +- Pushes to main branch affecting Tauri-related files + +**Platforms**: +- **Windows**: x86_64 (exe and msi) +- **macOS**: Apple Silicon (aarch64) and Intel (x86_64) (dmg) +- **Linux**: x86_64 (deb and AppImage) + +**Features**: +- **Dynamic Platform Selection**: Choose specific platforms or build all +- **Smart JRE Bundling**: Uses JLink to create optimized custom JRE +- **Apple Code Signing**: Full macOS notarization and signing support +- **Comprehensive Validation**: Artifact verification and size checks +- **Self-Contained**: No Java installation required for end users +- **Cross-Platform**: Builds on actual target platforms for compatibility +- **Detailed Logging**: Complete build process visibility + +## Usage + +### Manual Testing + +1. **Test All Platforms**: + ```bash + # Go to Actions tab in GitHub + # Run "Build Tauri Applications" workflow + # Select "all" for platform + ``` + +2. **Test Specific Platform**: + ```bash + # Go to Actions tab in GitHub + # Run "Build Tauri Applications" workflow + # Select specific platform (windows/macos/linux) + ``` + +3. **Automatic Testing**: + - Builds are automatically triggered on PRs and pushes + - All platforms are tested by default + - Artifacts are uploaded for download and testing + +## Configuration + +### Required Secrets + +#### For macOS Code Signing (Required for distribution) + +Configure these secrets in your repository for macOS app signing: + +- `APPLE_CERTIFICATE`: Base64-encoded .p12 certificate file +- `APPLE_CERTIFICATE_PASSWORD`: Password for the .p12 certificate +- `APPLE_SIGNING_IDENTITY`: Certificate name (e.g., "Developer ID Application: Your Name") +- `APPLE_ID`: Your Apple ID email +- `APPLE_PASSWORD`: App-specific password for your Apple ID +- `APPLE_TEAM_ID`: Your Apple Developer Team ID + +#### Setting Up Apple Code Signing + +1. **Get a Developer ID Certificate**: + - Join the Apple Developer Program ($99/year) + - Create a "Developer ID Application" certificate in Apple Developer portal + - Download the certificate as a .p12 file + +2. **Convert Certificate to Base64**: + ```bash + base64 -i certificate.p12 | pbcopy + ``` + +3. **Create App-Specific Password**: + - Go to appleid.apple.com → Sign-In and Security → App-Specific Passwords + - Generate a new password for "Tauri CI" + +4. **Find Your Team ID**: + - Apple Developer portal → Membership → Team ID + +5. **Add to GitHub Secrets**: + - Repository → Settings → Secrets and variables → Actions + - Add each secret with the exact names listed above + +#### For General Tauri Signing (Optional) + +- `TAURI_SIGNING_PRIVATE_KEY`: Private key for signing Tauri applications +- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`: Password for the signing private key + +### File Structure + +The workflows expect this structure: +``` +├── frontend/ +│ ├── src-tauri/ +│ │ ├── Cargo.toml +│ │ ├── tauri.conf.json +│ │ └── src/ +│ ├── package.json +│ └── src/ +├── gradlew +└── stirling-pdf/ + └── build/libs/ +``` + +## Validation + +Both workflows include comprehensive validation: + +1. **Build Validation**: Ensures all expected artifacts are created +2. **Size Validation**: Checks artifacts aren't suspiciously small +3. **Platform Validation**: Verifies platform-specific requirements +4. **Integration Testing**: Tests that Java backend builds correctly + +## Troubleshooting + +### Common Issues + +1. **Missing Dependencies**: + - Ubuntu: Ensure system dependencies are installed + - macOS: Check Rust toolchain targets + - Windows: Verify MSVC tools are available + +2. **Java Backend Build Fails**: + - Check Gradle permissions (`chmod +x ./gradlew`) + - Verify JDK 21 is properly configured + +3. **Artifact Size Issues**: + - Small artifacts usually indicate build failures + - Check that backend JAR is properly copied to Tauri resources + +4. **Signing Issues**: + - Ensure signing secrets are configured if needed + - Check that signing keys are valid + +### Debugging + +1. **Check Logs**: Each step provides detailed logging +2. **Artifact Inspection**: Download artifacts to verify contents +3. **Local Testing**: Test builds locally before running workflows + +## JLink Integration Benefits + +The workflows now use JLink to create custom Java runtimes: + +### **Self-Contained Applications** +- **No Java Required**: Users don't need Java installed +- **Consistent Runtime**: Same Java version across all deployments +- **Smaller Size**: Only includes needed Java modules (~30-50MB vs full JRE) + +### **Security & Performance** +- **Minimal Attack Surface**: Only required modules included +- **Faster Startup**: Optimized runtime with stripped debug info +- **Better Compression**: JLink level 2 compression reduces size + +### **Module Analysis** +- **Automatic Detection**: Uses `jdeps` to analyze JAR dependencies +- **Fallback Safety**: Predefined module list if analysis fails +- **Platform Optimized**: Different modules per platform if needed + +## Integration with Existing Workflows + +These workflows are designed to complement the existing build system: + +- Uses same JDK and Gradle setup as `build.yml` +- Follows same security practices as `multiOSReleases.yml` +- Compatible with existing release processes +- Integrates JLink logic from `build-tauri-jlink.sh/bat` scripts + +## Next Steps + +1. Test the workflows on your branch +2. Verify all platforms build successfully +3. Check artifact quality and sizes +4. Configure signing if needed +5. Merge when all tests pass \ No newline at end of file diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 154b6bdae..9d697e98f 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -25,3 +25,5 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: "Dependency Review" uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1 + with: + config-file: './.github/config/dependency-review-config.yml' diff --git a/.github/workflows/tauri-build.yml b/.github/workflows/tauri-build.yml new file mode 100644 index 000000000..631de2f70 --- /dev/null +++ b/.github/workflows/tauri-build.yml @@ -0,0 +1,329 @@ +name: Build Tauri Applications + +on: + workflow_dispatch: + inputs: + platform: + description: "Platform to build (windows, macos, linux, or all)" + required: true + default: "all" + type: choice + options: + - all + - windows + - macos + - linux + pull_request: + branches: [main, V2] + paths: + - 'frontend/src-tauri/**' + - 'frontend/src/**' + - 'frontend/package.json' + - 'frontend/package-lock.json' + - '.github/workflows/tauri-build.yml' + push: + branches: [main, V2] + paths: + - 'frontend/src-tauri/**' + - 'frontend/src/**' + - 'frontend/package.json' + - 'frontend/package-lock.json' + - '.github/workflows/tauri-build.yml' + +permissions: + contents: read + +jobs: + determine-matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Determine build matrix + id: set-matrix + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + case "${{ github.event.inputs.platform }}" in + "windows") + echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"}]}' >> $GITHUB_OUTPUT + ;; + "macos") + echo 'matrix={"include":[{"platform":"macos-latest","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-13","args":"--target x86_64-apple-darwin","name":"macos-x86_64"}]}' >> $GITHUB_OUTPUT + ;; + "linux") + echo 'matrix={"include":[{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT + ;; + *) + echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"},{"platform":"macos-latest","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-13","args":"--target x86_64-apple-darwin","name":"macos-x86_64"},{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT + ;; + esac + else + # For PR/push events, build all platforms + echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"},{"platform":"macos-latest","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-13","args":"--target x86_64-apple-darwin","name":"macos-x86_64"},{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT + fi + + build: + needs: determine-matrix + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.determine-matrix.outputs.matrix) }} + runs-on: ${{ matrix.platform }} + steps: + - name: Harden Runner + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install dependencies (ubuntu only) + if: matrix.platform == 'ubuntu-22.04' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libjavascriptcoregtk-4.0-dev libsoup2.4-dev libjavascriptcoregtk-4.1-dev libsoup-3.0-dev + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + targets: ${{ (matrix.platform == 'macos-latest' || matrix.platform == 'macos-13') && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} + + + + - name: Set up JDK 21 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + java-version: "21" + distribution: "temurin" + + - name: Build Java backend with JLink + working-directory: ./ + shell: bash + run: | + chmod +x ./gradlew + echo "🔧 Building Stirling-PDF JAR..." + # STIRLING_PDF_DESKTOP_UI=false ./gradlew clean bootJar --no-daemon + ./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube + + # Find the built JAR + STIRLING_JAR=$(ls app/core/build/libs/stirling-pdf-*.jar | head -n 1) + echo "✅ Built JAR: $STIRLING_JAR" + + # Create Tauri directories + mkdir -p ./frontend/src-tauri/libs + mkdir -p ./frontend/src-tauri/runtime + + # Copy JAR to Tauri libs + cp "$STIRLING_JAR" ./frontend/src-tauri/libs/ + echo "✅ JAR copied to Tauri libs" + + # Analyze JAR dependencies for jlink modules + echo "🔍 Analyzing JAR dependencies..." + if command -v jdeps &> /dev/null; then + DETECTED_MODULES=$(jdeps --print-module-deps --ignore-missing-deps "$STIRLING_JAR" 2>/dev/null || echo "") + if [ -n "$DETECTED_MODULES" ]; then + echo "📋 jdeps detected modules: $DETECTED_MODULES" + MODULES="$DETECTED_MODULES,java.compiler,java.instrument,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" + else + echo "⚠️ jdeps analysis failed, using predefined modules" + MODULES="java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" + fi + else + echo "⚠️ jdeps not available, using predefined modules" + MODULES="java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" + fi + + # Create custom JRE with jlink (always rebuild) + echo "🔧 Creating custom JRE with jlink..." + echo "📋 Using modules: $MODULES" + + # Remove any existing JRE + rm -rf ./frontend/src-tauri/runtime/jre + + # Create the custom JRE + jlink \ + --add-modules "$MODULES" \ + --strip-debug \ + --compress=2 \ + --no-header-files \ + --no-man-pages \ + --output ./frontend/src-tauri/runtime/jre + + if [ ! -d "./frontend/src-tauri/runtime/jre" ]; then + echo "❌ Failed to create JLink runtime" + exit 1 + fi + + # Test the bundled runtime + if [ -f "./frontend/src-tauri/runtime/jre/bin/java" ]; then + RUNTIME_VERSION=$(./frontend/src-tauri/runtime/jre/bin/java --version 2>&1 | head -n 1) + echo "✅ Custom JRE created successfully: $RUNTIME_VERSION" + else + echo "❌ Custom JRE executable not found" + exit 1 + fi + + # Calculate runtime size + RUNTIME_SIZE=$(du -sh ./frontend/src-tauri/runtime/jre | cut -f1) + echo "📊 Custom JRE size: $RUNTIME_SIZE" + env: + DISABLE_ADDITIONAL_FEATURES: true + + - name: Install frontend dependencies + working-directory: ./frontend + run: npm install + + - name: Import Apple Developer Certificate + if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-13' + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + echo "Importing Apple Developer Certificate..." + echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12 + security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain + security set-keychain-settings -t 3600 -u build.keychain + security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain + security find-identity -v -p codesigning build.keychain + - name: Verify Certificate + if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-13' + run: | + echo "Verifying Apple Developer Certificate..." + CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application") + echo "Certificate Info: $CERT_INFO" + CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') + echo "Certificate ID: $CERT_ID" + echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV + echo "Certificate imported." + + - name: Check DMG creation dependencies (macOS only) + if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-13' + run: | + echo "🔍 Checking DMG creation dependencies on ${{ matrix.platform }}..." + echo "hdiutil version: $(hdiutil --version || echo 'NOT FOUND')" + echo "create-dmg availability: $(which create-dmg || echo 'NOT FOUND')" + echo "Available disk space: $(df -h /tmp | tail -1)" + echo "macOS version: $(sw_vers -productVersion)" + echo "Available tools:" + ls -la /usr/bin/hd* || echo "No hd* tools found" + + - name: Build Tauri app + uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} + APPLE_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.APPIMAGETOOL_SIGN_PASSPHRASE }} + SIGN: 1 + CI: true + with: + projectPath: ./frontend + tauriScript: npx tauri + args: ${{ matrix.args }} + + - name: Rename artifacts + shell: bash + run: | + mkdir -p ./dist + cd ./frontend/src-tauri/target + + # Find and rename artifacts based on platform + if [ "${{ matrix.platform }}" = "windows-latest" ]; then + find . -name "*.exe" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.exe" \; + find . -name "*.msi" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.msi" \; + elif [ "${{ matrix.platform }}" = "macos-latest" ] || [ "${{ matrix.platform }}" = "macos-13" ]; then + find . -name "*.dmg" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.dmg" \; + find . -name "*.app" -exec cp -r {} "../../../dist/Stirling-PDF-${{ matrix.name }}.app" \; + else + find . -name "*.deb" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.deb" \; + find . -name "*.AppImage" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.AppImage" \; + fi + + - name: Upload artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: Stirling-PDF-${{ matrix.name }} + path: ./dist/* + retention-days: 7 + + - name: Verify build artifacts + shell: bash + run: | + cd ./frontend/src-tauri/target + + # Check for expected artifacts based on platform + if [ "${{ matrix.platform }}" = "windows-latest" ]; then + echo "Checking for Windows artifacts..." + find . -name "*.exe" -o -name "*.msi" | head -5 + if [ $(find . -name "*.exe" | wc -l) -eq 0 ]; then + echo "❌ No Windows executable found" + exit 1 + fi + elif [ "${{ matrix.platform }}" = "macos-latest" ] || [ "${{ matrix.platform }}" = "macos-13" ]; then + echo "Checking for macOS artifacts..." + find . -name "*.dmg" -o -name "*.app" | head -5 + if [ $(find . -name "*.dmg" -o -name "*.app" | wc -l) -eq 0 ]; then + echo "❌ No macOS artifacts found" + exit 1 + fi + else + echo "Checking for Linux artifacts..." + find . -name "*.deb" -o -name "*.AppImage" | head -5 + if [ $(find . -name "*.deb" -o -name "*.AppImage" | wc -l) -eq 0 ]; then + echo "❌ No Linux artifacts found" + exit 1 + fi + fi + + echo "✅ Build artifacts found for ${{ matrix.name }}" + + - name: Test artifact sizes + shell: bash + run: | + cd ./frontend/src-tauri/target + echo "Artifact sizes for ${{ matrix.name }}:" + find . -name "*.exe" -o -name "*.dmg" -o -name "*.deb" -o -name "*.AppImage" -o -name "*.msi" | while read file; do + if [ -f "$file" ]; then + size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo "unknown") + echo "$file: $size bytes" + # Check if file is suspiciously small (less than 1MB) + if [ "$size" != "unknown" ] && [ "$size" -lt 1048576 ]; then + echo "⚠️ Warning: $file is smaller than 1MB" + fi + fi + done + + report: + needs: build + runs-on: ubuntu-latest + if: always() + steps: + - name: Report build results + run: | + if [ "${{ needs.build.result }}" = "success" ]; then + echo "✅ All Tauri builds completed successfully!" + echo "Artifacts are ready for distribution." + else + echo "❌ Some Tauri builds failed." + echo "Please check the logs and fix any issues." + exit 1 + fi \ No newline at end of file diff --git a/DesktopApplicationDevelopmentGuide.md b/DesktopApplicationDevelopmentGuide.md new file mode 100644 index 000000000..aaefd5b1b --- /dev/null +++ b/DesktopApplicationDevelopmentGuide.md @@ -0,0 +1,228 @@ +# JLink Runtime Bundling for Stirling-PDF + +This guide explains how to use JLink to bundle a custom Java runtime with your Tauri application, eliminating the need for users to have Java installed. + +## Overview + +Instead of requiring users to install a JRE separately, JLink creates a minimal, custom Java runtime that includes only the modules your application needs. This approach: + +- **Eliminates JRE dependency**: Users don't need Java installed +- **Reduces size**: Only includes necessary Java modules +- **Improves security**: Minimal attack surface with fewer modules +- **Ensures consistency**: Same Java version across all deployments + +## Prerequisites + +- **JDK 17 or higher** (not just JRE - you need `jlink` command) +- **Node.js and npm** for the frontend +- **Rust and Tauri CLI** for building the desktop app + +## Quick Start + +### 1. Build with JLink + +Run the appropriate build script for your platform: + +**Linux/macOS:** +```bash +./scripts/build-tauri-jlink.sh +``` + +**Windows:** +```cmd +scripts\build-tauri-jlink.bat +``` + +### 2. Build Tauri Application + +```bash +cd frontend +npm run tauri-build +``` + +The resulting application will include the bundled JRE and won't require Java to be installed on the target system. + +## What the Build Script Does + +1. **Builds the Stirling-PDF JAR** using Gradle +2. **Analyzes dependencies** using `jdeps` to determine required Java modules +3. **Creates custom JRE** using `jlink` with only necessary modules +4. **Copies files** to the correct Tauri directories: + - JAR file → `frontend/src-tauri/libs/` + - Custom JRE → `frontend/src-tauri/runtime/jre/` +5. **Creates test launchers** for standalone testing + +## Directory Structure + +After running the build script: + +``` +frontend/src-tauri/ +├── libs/ +│ └── Stirling-PDF-X.X.X.jar +├── runtime/ +│ ├── jre/ # Custom JLink runtime +│ │ ├── bin/ +│ │ │ ├── java(.exe) +│ │ │ └── ... +│ │ ├── lib/ +│ │ └── ... +│ ├── launch-stirling.sh # Test launcher (Linux/macOS) +│ └── launch-stirling.bat # Test launcher (Windows) +└── tauri.conf.json # Already configured to bundle runtime +``` + +## Testing the Bundled Runtime + +Before building the full Tauri app, you can test the bundled runtime: + +**Linux/macOS:** +```bash +./frontend/src-tauri/runtime/launch-stirling.sh +``` + +**Windows:** +```cmd +frontend\src-tauri\runtime\launch-stirling.bat +``` + +This will start Stirling-PDF using the bundled JRE, accessible at http://localhost:8080 + +## Configuration Details + +### Tauri Configuration (`tauri.conf.json`) + +The bundle resources are configured to include both the JAR and runtime: + +```json +{ + "bundle": { + "resources": [ + "libs/*.jar", + "runtime/jre/**/*" + ] + } +} +``` + +### Gradle Configuration (`build.gradle`) + +JLink options are configured in the jpackage section: + +```gradle +jLinkOptions = [ + "--strip-debug", + "--compress=2", + "--no-header-files", + "--no-man-pages" +] + +addModules = [ + "java.base", + "java.desktop", + "java.logging", + "java.management", + // ... other required modules +] +``` + +### Rust Code (`lib.rs`) + +The application automatically detects and uses the bundled JRE instead of system Java. + +## Modules Included + +The custom runtime includes these Java modules: + +- `java.base` - Core Java functionality +- `java.desktop` - AWT/Swing (for UI components) +- `java.instrument` - Java instrumentation (required by Jetty) +- `java.logging` - Logging framework +- `java.management` - JMX and monitoring +- `java.naming` - JNDI services +- `java.net.http` - HTTP client +- `java.security.jgss` - Security services +- `java.sql` - Database connectivity +- `java.xml` - XML processing +- `java.xml.crypto` - XML security +- `jdk.crypto.ec` - Elliptic curve cryptography +- `jdk.crypto.cryptoki` - PKCS#11 support +- `jdk.unsupported` - Internal APIs (used by some libraries) + +## Troubleshooting + +### JLink Not Found +``` +❌ jlink is not available +``` +**Solution**: Install a full JDK (not just JRE). JLink is included with JDK 9+. + +### Module Not Found During Runtime +If the application fails with module-related errors, you may need to add additional modules to the `addModules` list in `build.gradle`. + +### Large Runtime Size +The bundled runtime should be 50-80MB. If it's much larger: +- Ensure `--strip-debug` and `--compress=2` options are used +- Review the module list - remove unnecessary modules +- Consider using `--no-header-files` and `--no-man-pages` + +## Benefits Over Traditional JAR Approach + +| Aspect | Traditional JAR | JLink Bundle | +|--------|----------------|--------------| +| User Setup | Requires JRE installation | No Java installation needed | +| Distribution Size | Smaller JAR, but requires ~200MB JRE | Larger bundle (~80MB), but self-contained | +| Java Version | Depends on user's installed version | Consistent, controlled version | +| Security Updates | User manages JRE updates | Developer controls runtime version | +| Startup Time | May be faster (shared JRE) | Slightly slower (isolated runtime) | + +## Advanced Usage + +### Custom Module Analysis + +To analyze your specific JAR's module requirements: + +```bash +jdeps --print-module-deps --ignore-missing-deps build/libs/Stirling-PDF-*.jar +``` + +### Manual JLink Command + +If you want to create the runtime manually: + +```bash +jlink \ + --add-modules java.base,java.desktop,java.logging,java.management,java.naming,java.net.http,java.security.jgss,java.sql,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported \ + --strip-debug \ + --compress=2 \ + --no-header-files \ + --no-man-pages \ + --output frontend/src-tauri/runtime/jre +``` + +## Migration Guide + +### From JAR-based Tauri App + +1. Update your Tauri configuration to include the runtime resources +2. Update your Rust code to use the bundled JRE path +3. Run the JLink build script +4. Test the bundled runtime +5. Build and distribute the new self-contained app + +### Deployment + +The final Tauri application will be completely self-contained. Users can: +- Install the app normally (no Java installation required) +- Run the app immediately after installation +- Not worry about Java version compatibility issues + +## Support + +If you encounter issues with the JLink bundling: + +1. Ensure you have a JDK (not JRE) installed +2. Check that the Java version is 17 or higher +3. Verify that the build script completed successfully +4. Test the bundled runtime using the provided launcher scripts +5. Check the Tauri build logs for any missing resources \ No newline at end of file diff --git a/DeveloperGuide.md b/DeveloperGuide.md index 0728a1cdc..0e9ce6c95 100644 --- a/DeveloperGuide.md +++ b/DeveloperGuide.md @@ -32,6 +32,12 @@ This guide focuses on developing for Stirling 2.0, including both the React fron - Docker for containerization - Gradle for build management +**Desktop Application (Tauri):** +- Tauri for cross-platform desktop app packaging +- Rust backend for system integration +- PDF file association support +- Self-contained JRE bundling with JLink + **Legacy (reference only during development):** - Thymeleaf templates (being completely replaced in 2.0) @@ -44,6 +50,8 @@ This guide focuses on developing for Stirling 2.0, including both the React fron - Java JDK 17 or later (JDK 21 recommended) - Node.js 18+ and npm (required for frontend development) - Gradle 7.0 or later (Included within the repo) +- Rust and Cargo (required for Tauri desktop app development) +- Tauri CLI (install with `cargo install tauri-cli`) ### Setup Steps @@ -95,6 +103,14 @@ Stirling 2.0 uses client-side file storage: ### Legacy Code Reference The existing Thymeleaf templates remain in the codebase during development as reference material but will be completely removed for the 2.0 release. +### Tauri Desktop App Development +Stirling-PDF can be packaged as a cross-platform desktop application using Tauri with PDF file association support and bundled JRE: + +**Quick Start:** +1. **Development/Testing**: `npm run tauri-dev "path/to/test.pdf"` +2. **Building**: See [DesktopApplicationDevelopmentGuide.md](DesktopApplicationDevelopmentGuide.md) for complete build instructions +3. **Features**: File associations, self-contained JRE, cross-platform support + ## 5. Project Structure ```bash @@ -109,6 +125,12 @@ Stirling-PDF/ │ │ ├── services/ # API and utility services │ │ ├── types/ # TypeScript type definitions │ │ └── utils/ # Utility functions +│ ├── src-tauri/ # Tauri desktop app configuration +│ │ ├── src/ # Rust backend code +│ │ ├── libs/ # JAR files (generated by build scripts) +│ │ ├── runtime/ # Bundled JRE (generated by build scripts) +│ │ ├── Cargo.toml # Rust dependencies +│ │ └── tauri.conf.json # Tauri configuration │ ├── public/ │ │ └── locales/ # Internationalization files (JSON) │ ├── package.json # Frontend dependencies diff --git a/app/core/build.gradle b/app/core/build.gradle index 745dbb87a..014f934de 100644 --- a/app/core/build.gradle +++ b/app/core/build.gradle @@ -29,7 +29,6 @@ dependencies { if (System.getenv('STIRLING_PDF_DESKTOP_UI') != 'false' || (project.hasProperty('STIRLING_PDF_DESKTOP_UI') && project.getProperty('STIRLING_PDF_DESKTOP_UI') != 'false')) { - implementation 'me.friwi:jcefmaven:132.3.1' implementation 'org.openjfx:javafx-controls:21' implementation 'org.openjfx:javafx-swing:21' } diff --git a/app/core/src/main/java/stirling/software/SPDF/SPDFApplication.java b/app/core/src/main/java/stirling/software/SPDF/SPDFApplication.java index 2131b4239..4211f5dcd 100644 --- a/app/core/src/main/java/stirling/software/SPDF/SPDFApplication.java +++ b/app/core/src/main/java/stirling/software/SPDF/SPDFApplication.java @@ -151,6 +151,13 @@ public class SPDFApplication { serverPortStatic = serverPort; String url = baseUrl + ":" + getStaticPort() + contextPath; + // Log Tauri mode information + if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_TAURI_MODE", "false"))) { + String parentPid = System.getenv("TAURI_PARENT_PID"); + log.info( + "Running in Tauri mode. Parent process PID: {}", + parentPid != null ? parentPid : "not set"); + } if (webBrowser != null && Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) { webBrowser.initWebUI(url); diff --git a/app/core/src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java b/app/core/src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java deleted file mode 100644 index 959e7f354..000000000 --- a/app/core/src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java +++ /dev/null @@ -1,497 +0,0 @@ -package stirling.software.SPDF.UI.impl; - -import java.awt.AWTException; -import java.awt.BorderLayout; -import java.awt.Frame; -import java.awt.Image; -import java.awt.MenuItem; -import java.awt.PopupMenu; -import java.awt.SystemTray; -import java.awt.TrayIcon; -import java.awt.event.WindowEvent; -import java.awt.event.WindowStateListener; -import java.io.File; -import java.io.InputStream; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; - -import javax.imageio.ImageIO; -import javax.swing.JFrame; -import javax.swing.JPanel; -import javax.swing.SwingUtilities; -import javax.swing.Timer; - -import org.cef.CefApp; -import org.cef.CefClient; -import org.cef.CefSettings; -import org.cef.browser.CefBrowser; -import org.cef.callback.CefBeforeDownloadCallback; -import org.cef.callback.CefDownloadItem; -import org.cef.callback.CefDownloadItemCallback; -import org.cef.handler.CefDownloadHandlerAdapter; -import org.cef.handler.CefLoadHandlerAdapter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Component; - -import jakarta.annotation.PreDestroy; - -import lombok.extern.slf4j.Slf4j; - -import me.friwi.jcefmaven.CefAppBuilder; -import me.friwi.jcefmaven.EnumProgress; -import me.friwi.jcefmaven.MavenCefAppHandlerAdapter; -import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler; - -import stirling.software.SPDF.UI.WebBrowser; -import stirling.software.common.configuration.InstallationPathConfig; -import stirling.software.common.util.UIScaling; - -@Component -@Slf4j -@ConditionalOnProperty( - name = "STIRLING_PDF_DESKTOP_UI", - havingValue = "true", - matchIfMissing = false) -public class DesktopBrowser implements WebBrowser { - private static CefApp cefApp; - private static CefClient client; - private static CefBrowser browser; - private static JFrame frame; - private static LoadingWindow loadingWindow; - private static volatile boolean browserInitialized = false; - private static TrayIcon trayIcon; - private static SystemTray systemTray; - - public DesktopBrowser() { - SwingUtilities.invokeLater( - () -> { - loadingWindow = new LoadingWindow(null, "Initializing..."); - loadingWindow.setVisible(true); - }); - } - - public void initWebUI(String url) { - CompletableFuture.runAsync( - () -> { - try { - CefAppBuilder builder = new CefAppBuilder(); - configureCefSettings(builder); - builder.setProgressHandler(createProgressHandler()); - builder.setInstallDir( - new File(InstallationPathConfig.getClientWebUIPath())); - // Build and initialize CEF - cefApp = builder.build(); - client = cefApp.createClient(); - - // Set up download handler - setupDownloadHandler(); - - // Create browser and frame on EDT - SwingUtilities.invokeAndWait( - () -> { - browser = client.createBrowser(url, false, false); - setupMainFrame(); - setupLoadHandler(); - - // Force initialize UI after 7 seconds if not already done - Timer timeoutTimer = - new Timer( - 2500, - e -> { - log.warn( - "Loading timeout reached. Forcing" - + " UI transition."); - if (!browserInitialized) { - // Force UI initialization - forceInitializeUI(); - } - }); - timeoutTimer.setRepeats(false); - timeoutTimer.start(); - }); - } catch (Exception e) { - log.error("Error initializing JCEF browser: ", e); - cleanup(); - } - }); - } - - private void configureCefSettings(CefAppBuilder builder) { - CefSettings settings = builder.getCefSettings(); - String basePath = InstallationPathConfig.getClientWebUIPath(); - log.info("basePath " + basePath); - settings.cache_path = new File(basePath + "cache").getAbsolutePath(); - settings.root_cache_path = new File(basePath + "root_cache").getAbsolutePath(); - // settings.browser_subprocess_path = new File(basePath + - // "subprocess").getAbsolutePath(); - // settings.resources_dir_path = new File(basePath + "resources").getAbsolutePath(); - // settings.locales_dir_path = new File(basePath + "locales").getAbsolutePath(); - settings.log_file = new File(basePath, "debug.log").getAbsolutePath(); - - settings.persist_session_cookies = true; - settings.windowless_rendering_enabled = false; - settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_INFO; - - builder.setAppHandler( - new MavenCefAppHandlerAdapter() { - @Override - public void stateHasChanged(org.cef.CefApp.CefAppState state) { - log.info("CEF state changed: " + state); - if (state == CefApp.CefAppState.TERMINATED) { - System.exit(0); - } - } - }); - } - - private void setupDownloadHandler() { - client.addDownloadHandler( - new CefDownloadHandlerAdapter() { - @Override - public boolean onBeforeDownload( - CefBrowser browser, - CefDownloadItem downloadItem, - String suggestedName, - CefBeforeDownloadCallback callback) { - callback.Continue("", true); - return true; - } - - @Override - public void onDownloadUpdated( - CefBrowser browser, - CefDownloadItem downloadItem, - CefDownloadItemCallback callback) { - if (downloadItem.isComplete()) { - log.info("Download completed: " + downloadItem.getFullPath()); - } else if (downloadItem.isCanceled()) { - log.info("Download canceled: " + downloadItem.getFullPath()); - } - } - }); - } - - private ConsoleProgressHandler createProgressHandler() { - return new ConsoleProgressHandler() { - @Override - public void handleProgress(EnumProgress state, float percent) { - Objects.requireNonNull(state, "state cannot be null"); - SwingUtilities.invokeLater( - () -> { - if (loadingWindow != null) { - switch (state) { - case LOCATING: - loadingWindow.setStatus("Locating Files..."); - loadingWindow.setProgress(0); - break; - case DOWNLOADING: - if (percent >= 0) { - loadingWindow.setStatus( - String.format( - "Downloading additional files: %.0f%%", - percent)); - loadingWindow.setProgress((int) percent); - } - break; - case EXTRACTING: - loadingWindow.setStatus("Extracting files..."); - loadingWindow.setProgress(60); - break; - case INITIALIZING: - loadingWindow.setStatus("Initializing UI..."); - loadingWindow.setProgress(80); - break; - case INITIALIZED: - loadingWindow.setStatus("Finalising startup..."); - loadingWindow.setProgress(90); - break; - } - } - }); - } - }; - } - - private void setupMainFrame() { - frame = new JFrame("Stirling-PDF"); - frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); - frame.setUndecorated(true); - frame.setOpacity(0.0f); - - JPanel contentPane = new JPanel(new BorderLayout()); - contentPane.setDoubleBuffered(true); - contentPane.add(browser.getUIComponent(), BorderLayout.CENTER); - frame.setContentPane(contentPane); - - frame.addWindowListener( - new java.awt.event.WindowAdapter() { - @Override - public void windowClosing(java.awt.event.WindowEvent windowEvent) { - cleanup(); - System.exit(0); - } - }); - - frame.setSize(UIScaling.scaleWidth(1280), UIScaling.scaleHeight(800)); - frame.setLocationRelativeTo(null); - - loadIcon(); - } - - private void setupLoadHandler() { - final long initStartTime = System.currentTimeMillis(); - log.info("Setting up load handler at: {}", initStartTime); - - client.addLoadHandler( - new CefLoadHandlerAdapter() { - @Override - public void onLoadingStateChange( - CefBrowser browser, - boolean isLoading, - boolean canGoBack, - boolean canGoForward) { - log.debug( - "Loading state change - isLoading: {}, canGoBack: {}, canGoForward:" - + " {}, browserInitialized: {}, Time elapsed: {}ms", - isLoading, - canGoBack, - canGoForward, - browserInitialized, - System.currentTimeMillis() - initStartTime); - - if (!isLoading && !browserInitialized) { - log.info( - "Browser finished loading, preparing to initialize UI" - + " components"); - browserInitialized = true; - SwingUtilities.invokeLater( - () -> { - try { - if (loadingWindow != null) { - log.info("Starting UI initialization sequence"); - - // Close loading window first - loadingWindow.setVisible(false); - loadingWindow.dispose(); - loadingWindow = null; - log.info("Loading window disposed"); - - // Then setup the main frame - frame.setVisible(false); - frame.dispose(); - frame.setOpacity(1.0f); - frame.setUndecorated(false); - frame.pack(); - frame.setSize( - UIScaling.scaleWidth(1280), - UIScaling.scaleHeight(800)); - frame.setLocationRelativeTo(null); - log.debug("Frame reconfigured"); - - // Show the main frame - frame.setVisible(true); - frame.requestFocus(); - frame.toFront(); - log.info("Main frame displayed and focused"); - - // Focus the browser component - Timer focusTimer = - new Timer( - 100, - e -> { - try { - browser.getUIComponent() - .requestFocus(); - log.info( - "Browser component" - + " focused"); - } catch (Exception ex) { - log.error( - "Error focusing" - + " browser", - ex); - } - }); - focusTimer.setRepeats(false); - focusTimer.start(); - } - } catch (Exception e) { - log.error("Error during UI initialization", e); - // Attempt cleanup on error - if (loadingWindow != null) { - loadingWindow.dispose(); - loadingWindow = null; - } - if (frame != null) { - frame.setVisible(true); - frame.requestFocus(); - } - } - }); - } - } - }); - } - - private void setupTrayIcon(Image icon) { - if (!SystemTray.isSupported()) { - log.warn("System tray is not supported"); - return; - } - - try { - systemTray = SystemTray.getSystemTray(); - - // Create popup menu - PopupMenu popup = new PopupMenu(); - - // Create menu items - MenuItem showItem = new MenuItem("Show"); - showItem.addActionListener( - e -> { - frame.setVisible(true); - frame.setState(Frame.NORMAL); - }); - - MenuItem exitItem = new MenuItem("Exit"); - exitItem.addActionListener( - e -> { - cleanup(); - System.exit(0); - }); - - // Add menu items to popup menu - popup.add(showItem); - popup.addSeparator(); - popup.add(exitItem); - - // Create tray icon - trayIcon = new TrayIcon(icon, "Stirling-PDF", popup); - trayIcon.setImageAutoSize(true); - - // Add double-click behavior - trayIcon.addActionListener( - e -> { - frame.setVisible(true); - frame.setState(Frame.NORMAL); - }); - - // Add tray icon to system tray - systemTray.add(trayIcon); - - // Modify frame behavior to minimize to tray - frame.addWindowStateListener( - new WindowStateListener() { - public void windowStateChanged(WindowEvent e) { - if (e.getNewState() == Frame.ICONIFIED) { - frame.setVisible(false); - } - } - }); - - } catch (AWTException e) { - log.error("Error setting up system tray icon", e); - } - } - - private void loadIcon() { - try { - Image icon = null; - String[] iconPaths = {"/static/favicon.ico"}; - - for (String path : iconPaths) { - if (icon != null) break; - try { - try (InputStream is = getClass().getResourceAsStream(path)) { - if (is != null) { - icon = ImageIO.read(is); - break; - } - } - } catch (Exception e) { - log.debug("Could not load icon from " + path, e); - } - } - - if (icon != null) { - frame.setIconImage(icon); - setupTrayIcon(icon); - } else { - log.warn("Could not load icon from any source"); - } - } catch (Exception e) { - log.error("Error loading icon", e); - } - } - - @PreDestroy - public void cleanup() { - if (browser != null) browser.close(true); - if (client != null) client.dispose(); - if (cefApp != null) cefApp.dispose(); - if (loadingWindow != null) loadingWindow.dispose(); - } - - public static void forceInitializeUI() { - try { - if (loadingWindow != null) { - log.info("Forcing start of UI initialization sequence"); - - // Close loading window first - loadingWindow.setVisible(false); - loadingWindow.dispose(); - loadingWindow = null; - log.info("Loading window disposed"); - - // Then setup the main frame - frame.setVisible(false); - frame.dispose(); - frame.setOpacity(1.0f); - frame.setUndecorated(false); - frame.pack(); - frame.setSize(UIScaling.scaleWidth(1280), UIScaling.scaleHeight(800)); - frame.setLocationRelativeTo(null); - log.debug("Frame reconfigured"); - - // Show the main frame - frame.setVisible(true); - frame.requestFocus(); - frame.toFront(); - log.info("Main frame displayed and focused"); - - // Focus the browser component if available - if (browser != null) { - Timer focusTimer = - new Timer( - 100, - e -> { - try { - browser.getUIComponent().requestFocus(); - log.info("Browser component focused"); - } catch (Exception ex) { - log.error( - "Error focusing browser during force ui" - + " initialization.", - ex); - } - }); - focusTimer.setRepeats(false); - focusTimer.start(); - } - } - } catch (Exception e) { - log.error("Error during Forced UI initialization.", e); - // Attempt cleanup on error - if (loadingWindow != null) { - loadingWindow.dispose(); - loadingWindow = null; - } - if (frame != null) { - frame.setVisible(true); - frame.setOpacity(1.0f); - frame.setUndecorated(false); - frame.requestFocus(); - } - } - } -} diff --git a/app/core/src/main/java/stirling/software/SPDF/config/TauriProcessMonitor.java b/app/core/src/main/java/stirling/software/SPDF/config/TauriProcessMonitor.java new file mode 100644 index 000000000..a3feded74 --- /dev/null +++ b/app/core/src/main/java/stirling/software/SPDF/config/TauriProcessMonitor.java @@ -0,0 +1,157 @@ +package stirling.software.SPDF.config; + +import java.lang.management.ManagementFactory; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; + +/** + * Monitor for Tauri parent process to detect orphaned Java backend processes. When running in Tauri + * mode, this component periodically checks if the parent Tauri process is still alive. If the + * parent process terminates unexpectedly, this will trigger a graceful shutdown of the Java backend + * to prevent orphaned processes. + */ +@Component +@ConditionalOnProperty(name = "STIRLING_PDF_TAURI_MODE", havingValue = "true") +public class TauriProcessMonitor { + + private static final Logger logger = LoggerFactory.getLogger(TauriProcessMonitor.class); + + private final ApplicationContext applicationContext; + private String parentProcessId; + private ScheduledExecutorService scheduler; + private volatile boolean monitoring = false; + + public TauriProcessMonitor(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @PostConstruct + public void init() { + parentProcessId = System.getenv("TAURI_PARENT_PID"); + + if (parentProcessId != null && !parentProcessId.trim().isEmpty()) { + logger.info("Tauri mode detected. Parent process ID: {}", parentProcessId); + startMonitoring(); + } else { + logger.warn( + "TAURI_PARENT_PID environment variable not found. Tauri process monitoring disabled."); + } + } + + private void startMonitoring() { + scheduler = + Executors.newSingleThreadScheduledExecutor( + r -> { + Thread t = new Thread(r, "tauri-process-monitor"); + t.setDaemon(true); + return t; + }); + + monitoring = true; + + // Check every 5 seconds + scheduler.scheduleAtFixedRate(this::checkParentProcess, 5, 5, TimeUnit.SECONDS); + + logger.info("Started monitoring parent Tauri process (PID: {})", parentProcessId); + } + + private void checkParentProcess() { + if (!monitoring) { + return; + } + + try { + if (!isProcessAlive(parentProcessId)) { + logger.warn( + "Parent Tauri process (PID: {}) is no longer alive. Initiating graceful shutdown...", + parentProcessId); + initiateGracefulShutdown(); + } + } catch (Exception e) { + logger.error("Error checking parent process status", e); + } + } + + private boolean isProcessAlive(String pid) { + try { + long processId = Long.parseLong(pid); + + // Check if process exists using ProcessHandle (Java 9+) + return ProcessHandle.of(processId).isPresent(); + + } catch (NumberFormatException e) { + logger.error("Invalid parent process ID format: {}", pid); + return false; + } catch (Exception e) { + logger.error("Error checking if process {} is alive", pid, e); + return false; + } + } + + private void initiateGracefulShutdown() { + monitoring = false; + + logger.info("Orphaned Java backend detected. Shutting down gracefully..."); + + // Shutdown asynchronously to avoid blocking the monitor thread + CompletableFuture.runAsync( + () -> { + try { + // Give a small delay to ensure logging completes + Thread.sleep(1000); + + if (applicationContext instanceof ConfigurableApplicationContext) { + ((ConfigurableApplicationContext) applicationContext).close(); + } else { + // Fallback to system exit + logger.warn( + "Unable to shutdown Spring context gracefully, using System.exit"); + System.exit(0); + } + } catch (Exception e) { + logger.error("Error during graceful shutdown", e); + System.exit(1); + } + }); + } + + @PreDestroy + public void cleanup() { + monitoring = false; + + if (scheduler != null && !scheduler.isShutdown()) { + logger.info("Shutting down Tauri process monitor"); + scheduler.shutdown(); + + try { + if (!scheduler.awaitTermination(2, TimeUnit.SECONDS)) { + scheduler.shutdownNow(); + } + } catch (InterruptedException e) { + scheduler.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + + /** Get the current Java process ID for logging/debugging purposes */ + public static String getCurrentProcessId() { + try { + return ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; + } catch (Exception e) { + return "unknown"; + } + } +} diff --git a/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java b/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java index c3e204b3c..96212c6bf 100644 --- a/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java +++ b/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java @@ -1,6 +1,7 @@ package stirling.software.SPDF.config; import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -28,4 +29,17 @@ public class WebMvcConfig implements WebMvcConfigurer { "file:" + InstallationPathConfig.getStaticPath(), "classpath:/static/"); // .setCachePeriod(0); // Optional: disable caching } + + @Override + public void addCorsMappings(CorsRegistry registry) { + if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_TAURI_MODE", "false"))) { + // Tauri mode CORS configuration + registry.addMapping("/**") + .allowedOrigins( + "http://localhost:5173", "http://tauri.localhost", "tauri://localhost") + .allowedMethods("*") + .allowedHeaders("*"); + return; + } + } } diff --git a/docker/frontend/entrypoint.sh b/docker/frontend/entrypoint.sh index a81272969..283e20a82 100644 --- a/docker/frontend/entrypoint.sh +++ b/docker/frontend/entrypoint.sh @@ -6,5 +6,13 @@ VITE_API_BASE_URL=${VITE_API_BASE_URL:-"http://backend:8080"} # Replace the placeholder in nginx.conf with the actual backend URL sed -i "s|\${VITE_API_BASE_URL}|${VITE_API_BASE_URL}|g" /etc/nginx/nginx.conf +# Inject runtime configuration into config.js +cat > /usr/share/nginx/html/config.js << EOF +// Runtime configuration - injected at container startup +window.runtimeConfig = { + apiBaseUrl: '${VITE_API_BASE_URL}' +}; +EOF + # Start nginx exec nginx -g "daemon off;" \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md index 115fcca84..1640a506e 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -72,3 +72,15 @@ This section has moved here: [https://facebook.github.io/create-react-app/docs/d ### `npm run build` fails to minify This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) + + +## Tauri +### Dev +To run Tauri in development. Use the command: +````npm run tauri-dev``` +This will run the gradle runboot command and the tauri dev command concurrently, starting the app once both are stable. + +### Build +To build a deployment of the Tauri app. Use the command: +```npm run tauri-build``` +This will bundle the backend and frontend into one executable for each target. Targets can be set within the `tauri.conf.json` file. diff --git a/frontend/index.html b/frontend/index.html index 0fc165c66..964c2d4b8 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -12,11 +12,12 @@ - Vite App + Stirling-PDF
+ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 60c2af7e4..629409568 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -17,6 +17,8 @@ "@mui/icons-material": "^7.1.0", "@mui/material": "^7.1.0", "@tailwindcss/postcss": "^4.1.8", + "@tauri-apps/api": "^2.5.0", + "@tauri-apps/plugin-fs": "^2.4.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", @@ -37,6 +39,7 @@ "web-vitals": "^2.1.4" }, "devDependencies": { + "@tauri-apps/cli": "^2.5.0", "@types/react": "^19.1.4", "@types/react-dom": "^19.1.5", "@vitejs/plugin-react": "^4.5.0", @@ -2019,6 +2022,242 @@ "tailwindcss": "4.1.8" } }, + "node_modules/@tauri-apps/api": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.6.0.tgz", + "integrity": "sha512-hRNcdercfgpzgFrMXWwNDBN0B7vNzOzRepy6ZAmhxi5mDLVPNrTpo9MGg2tN/F7JRugj4d2aF7E1rtPXAHaetg==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.5.0.tgz", + "integrity": "sha512-rAtHqG0Gh/IWLjN2zTf3nZqYqbo81oMbqop56rGTjrlWk9pTTAjkqOjSL9XQLIMZ3RbeVjveCqqCA0s8RnLdMg==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.5.0", + "@tauri-apps/cli-darwin-x64": "2.5.0", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.5.0", + "@tauri-apps/cli-linux-arm64-gnu": "2.5.0", + "@tauri-apps/cli-linux-arm64-musl": "2.5.0", + "@tauri-apps/cli-linux-riscv64-gnu": "2.5.0", + "@tauri-apps/cli-linux-x64-gnu": "2.5.0", + "@tauri-apps/cli-linux-x64-musl": "2.5.0", + "@tauri-apps/cli-win32-arm64-msvc": "2.5.0", + "@tauri-apps/cli-win32-ia32-msvc": "2.5.0", + "@tauri-apps/cli-win32-x64-msvc": "2.5.0" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.5.0.tgz", + "integrity": "sha512-VuVAeTFq86dfpoBDNYAdtQVLbP0+2EKCHIIhkaxjeoPARR0sLpFHz2zs0PcFU76e+KAaxtEtAJAXGNUc8E1PzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.5.0.tgz", + "integrity": "sha512-hUF01sC06cZVa8+I0/VtsHOk9BbO75rd+YdtHJ48xTdcYaQ5QIwL4yZz9OR1AKBTaUYhBam8UX9Pvd5V2/4Dpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.5.0.tgz", + "integrity": "sha512-LQKqttsK252LlqYyX8R02MinUsfFcy3+NZiJwHFgi5Y3+ZUIAED9cSxJkyNtuY5KMnR4RlpgWyLv4P6akN1xhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.5.0.tgz", + "integrity": "sha512-mTQufsPcpdHg5RW0zypazMo4L55EfeE5snTzrPqbLX4yCK2qalN7+rnP8O8GT06xhp6ElSP/Ku1M2MR297SByQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.0.tgz", + "integrity": "sha512-rQO1HhRUQqyEaal5dUVOQruTRda/TD36s9kv1hTxZiFuSq3558lsTjAcUEnMAtBcBkps20sbyTJNMT0AwYIk8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.5.0.tgz", + "integrity": "sha512-7oS18FN46yDxyw1zX/AxhLAd7T3GrLj3Ai6s8hZKd9qFVzrAn36ESL7d3G05s8wEtsJf26qjXnVF4qleS3dYsA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.5.0.tgz", + "integrity": "sha512-SG5sFNL7VMmDBdIg3nO3EzNRT306HsiEQ0N90ILe3ZABYAVoPDO/ttpCO37ApLInTzrq/DLN+gOlC/mgZvLw1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.0.tgz", + "integrity": "sha512-QXDM8zp/6v05PNWju5ELsVwF0VH1n6b5pk2E6W/jFbbiwz80Vs1lACl9pv5kEHkrxBj+aWU/03JzGuIj2g3SkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.5.0.tgz", + "integrity": "sha512-pFSHFK6b+o9y4Un8w0gGLwVyFTZaC3P0kQ7umRt/BLDkzD5RnQ4vBM7CF8BCU5nkwmEBUCZd7Wt3TWZxe41o6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.5.0.tgz", + "integrity": "sha512-EArv1IaRlogdLAQyGlKmEqZqm5RfHCUMhJoedWu7GtdbOMUfSAz6FMX2boE1PtEmNO4An+g188flLeVErrxEKg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.5.0.tgz", + "integrity": "sha512-lj43EFYbnAta8pd9JnUq87o+xRUR0odz+4rixBtTUwUgdRdwQ2V9CzFtsMu6FQKpFQ6mujRK6P1IEwhL6ADRsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/plugin-fs": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.4.0.tgz", + "integrity": "sha512-Sp8AdDcbyXyk6LD6Pmdx44SH3LPeNAvxR2TFfq/8CwqzfO1yOyV+RzT8fov0NNN7d9nvW7O7MtMAptJ42YXA5g==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.6.0" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", @@ -2629,6 +2868,21 @@ "node": ">=10" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -4719,21 +4973,6 @@ "postcss": "^8.0.0" } }, - "node_modules/postcss-cli/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/postcss-cli/node_modules/fs-extra": { "version": "11.3.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", @@ -4815,35 +5054,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/postcss-cli/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/postcss-cli/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/postcss-js": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", @@ -6341,6 +6551,35 @@ "engines": { "node": ">= 14.6" } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } } } } diff --git a/frontend/package.json b/frontend/package.json index aa5251545..b30f38553 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,8 @@ "@mui/icons-material": "^7.1.0", "@mui/material": "^7.1.0", "@tailwindcss/postcss": "^4.1.8", + "@tauri-apps/api": "^2.5.0", + "@tauri-apps/plugin-fs": "^2.4.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", @@ -36,6 +38,8 @@ "dev": "vite", "build": "vite build", "preview": "vite preview", + "tauri-dev": "tauri dev --no-watch", + "tauri-build": "tauri build", "generate-licenses": "node scripts/generate-licenses.js" }, "eslintConfig": { @@ -57,6 +61,7 @@ ] }, "devDependencies": { + "@tauri-apps/cli": "^2.5.0", "@types/react": "^19.1.4", "@types/react-dom": "^19.1.5", "@vitejs/plugin-react": "^4.5.0", diff --git a/frontend/public/config.js b/frontend/public/config.js new file mode 100644 index 000000000..b3c3d1615 --- /dev/null +++ b/frontend/public/config.js @@ -0,0 +1,4 @@ +// Runtime configuration - injected at container startup +window.runtimeConfig = { + apiBaseUrl: 'http://localhost:8080' +}; \ No newline at end of file diff --git a/frontend/src-tauri/.gitignore b/frontend/src-tauri/.gitignore new file mode 100644 index 000000000..1db95a7c8 --- /dev/null +++ b/frontend/src-tauri/.gitignore @@ -0,0 +1,5 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ +/gen/schemas +/runtime/ \ No newline at end of file diff --git a/frontend/src-tauri/Cargo.lock b/frontend/src-tauri/Cargo.lock new file mode 100644 index 000000000..7c63b0d41 --- /dev/null +++ b/frontend/src-tauri/Cargo.lock @@ -0,0 +1,5420 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_log-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" + +[[package]] +name = "android_logger" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f39be698127218cca460cb624878c9aa4e2b47dba3b277963d2bf00bad263b" +dependencies = [ + "android_log-sys", + "env_filter", + "log", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "app" +version = "0.1.0" +dependencies = [ + "cocoa", + "log", + "objc", + "once_cell", + "reqwest 0.11.27", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-fs", + "tauri-plugin-log", + "tauri-plugin-shell", + "tokio", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "serde", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" +dependencies = [ + "objc2 0.6.1", +] + +[[package]] +name = "borsh" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +dependencies = [ + "once_cell", + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "byte-unit" +version = "5.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cd29c3c585209b0cbc7309bfe3ed7efd8c84c21b7af29c8bfae908f8777174" +dependencies = [ + "rust_decimal", + "serde", + "utf8-width", +] + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytemuck" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.9.1", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.12", +] + +[[package]] +name = "cargo_toml" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257" +dependencies = [ + "serde", + "toml", +] + +[[package]] +name = "cc" +version = "1.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" +dependencies = [ + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "cocoa" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation 0.9.4", + "core-graphics 0.22.3", + "foreign-types 0.3.2", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "libc", + "objc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa 0.4.8", + "matches", + "phf 0.8.0", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.101", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.101", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.101", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.101", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.59.0", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "dlopen2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + +[[package]] +name = "embed-resource" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8fe7d068ca6b3a5782ca5ec9afc244acd99dd441e4686a83b1c3973aba1d489" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml", + "vswhom", + "winreg 0.55.0", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "fern" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29" +dependencies = [ + "log", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "flate2" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.9.1", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.9.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html5ever" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.15", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.15", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.3.1", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa 1.0.15", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "httparse", + "itoa 1.0.15", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.32", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "hyper-util" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.6.0", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown 0.15.3", + "serde", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.9.1", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kuchikiki" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 1.9.3", + "matches", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.9.1", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +dependencies = [ + "value-bag", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf 0.10.1", + "phf_codegen 0.10.0", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "muda" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de14a9b5d569ca68d7c891d613b390cf5ab4f851c77aaa2f9e435555d3d9492" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "once_cell", + "png", + "serde", + "thiserror 2.0.12", + "windows-sys 0.59.0", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.9.1", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.1", + "block2 0.6.1", + "libc", + "objc2 0.6.1", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-foundation 0.3.1", + "objc2-quartz-core 0.3.1", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" +dependencies = [ + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.9.1", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags 2.9.1", + "block2 0.6.1", + "libc", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.9.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.9.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" +dependencies = [ + "bitflags 2.9.1", + "block2 0.6.1", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "open" +version = "5.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "os_pipe" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared 0.10.0", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.1", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plist" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d" +dependencies = [ + "base64 0.22.1", + "indexmap 2.9.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit 0.20.7", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit 0.22.26", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quick-xml" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.12", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rust_decimal" +version = "1.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faa7de2ba56ac291bd90c6b9bece784a52ae1411f9506544b3eae36dd2356d50" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.101", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more", + "fxhash", + "log", + "matches", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", + "thin-slice", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299d9c19d7d466db4ab10addd5703e4c615dec2a5a16dbbafe191045e87ee66e" +dependencies = [ + "erased-serde", + "serde", + "typeid", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa 1.0.15", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.15", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.9.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "servo_arc" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shared_child" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e297bd52991bbe0686c086957bee142f13df85d1e79b0b21630a99d374ae9dc" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "softbuffer" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +dependencies = [ + "bytemuck", + "cfg_aliases", + "core-graphics 0.24.0", + "foreign-types 0.5.0", + "js-sys", + "log", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", + "raw-window-handle", + "redox_syscall", + "wasm-bindgen", + "web-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-foundation 0.3.1", + "once_cell", + "parking_lot", + "raw-window-handle", + "scopeguard", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7b0bc1aec81bda6bc455ea98fcaed26b3c98c1648c627ad6ff1c704e8bf8cbc" +dependencies = [ + "anyhow", + "bytes", + "dirs", + "dunce", + "embed_plist", + "futures-util", + "getrandom 0.2.16", + "glob", + "gtk", + "heck 0.5.0", + "http 1.3.1", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-foundation 0.3.1", + "objc2-ui-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest 0.12.19", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.12", + "tokio", + "tray-icon", + "url", + "urlpattern", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a0350f0df1db385ca5c02888a83e0e66655c245b7443db8b78a70da7d7f8fc" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93f035551bf7b11b3f51ad9bc231ebbe5e085565527991c16cf326aa38cdf47" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.101", + "tauri-utils", + "thiserror 2.0.12", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8db4df25e2d9d45de0c4c910da61cd5500190da14ae4830749fee3466dddd112" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.101", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a5ebe6a610d1b78a94650896e6f7c9796323f408800cef436e0fa0539de601" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars", + "serde", + "serde_json", + "tauri-utils", + "toml", + "walkdir", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ead0daec5d305adcefe05af9d970fc437bcc7996052d564e7393eb291252da" +dependencies = [ + "anyhow", + "dunce", + "glob", + "percent-encoding", + "schemars", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.12", + "toml", + "url", +] + +[[package]] +name = "tauri-plugin-log" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2b582d860eb214f28323f4ce4f2797ae3b78f197e27b11677f976f9f52aedb" +dependencies = [ + "android_logger", + "byte-unit", + "fern", + "log", + "objc2 0.6.1", + "objc2-foundation 0.3.1", + "serde", + "serde_json", + "serde_repr", + "swift-rs", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "time", +] + +[[package]] +name = "tauri-plugin-shell" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d5eb3368b959937ad2aeaf6ef9a8f5d11e01ffe03629d3530707bbcb27ff5d" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "tokio", +] + +[[package]] +name = "tauri-runtime" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f004905d549854069e6774533d742b03cacfd6f03deb08940a8677586cbe39" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http 1.3.1", + "jni", + "objc2 0.6.1", + "objc2-ui-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.12", + "url", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f85d056f4d4b014fe874814034f3416d57114b617a493a4fe552580851a3f3a2" +dependencies = [ + "gtk", + "http 1.3.1", + "jni", + "log", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-foundation 0.3.1", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2900399c239a471bcff7f15c4399eb1a8c4fe511ba2853e07c996d771a5e0a4" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dunce", + "glob", + "html5ever", + "http 1.3.1", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.3", + "proc-macro2", + "quote", + "regex", + "schemars", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.12", + "toml", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d321dbc6f998d825ab3f0d62673e810c861aac2d0de2cc2c395328f1d113b4" +dependencies = [ + "embed-resource", + "indexmap 2.9.0", + "toml", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thin-slice" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa 1.0.15", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.26", +] + +[[package]] +name = "toml_datetime" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.9.0", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap 2.9.0", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +dependencies = [ + "indexmap 2.9.0", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow 0.7.10", +] + +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 1.0.2", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7eee98ec5c90daf179d55c20a49d8c0d043054ce7c26336c09a24d31f14fa0" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", + "once_cell", + "png", + "serde", + "thiserror 2.0.12", + "windows-sys 0.59.0", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "value-bag" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.101", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webview2-com" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b542b5cfbd9618c46c2784e4d41ba218c336ac70d44c55e47b251033e7d85601" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "webview2-com-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae2d11c4a686e4409659d7891791254cf9286d3cfe0eef54df1523533d22295" +dependencies = [ + "thiserror 2.0.12", + "windows", + "windows-core", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-version" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "wry" +version = "0.51.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c886a0a9d2a94fd90cfa1d929629b79cfefb1546e2c7430c63a47f0664c0e4e2" +dependencies = [ + "base64 0.22.1", + "block2 0.6.1", + "cookie", + "crossbeam-channel", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http 1.3.1", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.12", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core", + "windows-version", + "x11-dl", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml new file mode 100644 index 000000000..17a2bd30e --- /dev/null +++ b/frontend/src-tauri/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "stirling-pdf" +version = "0.1.0" +description = "Stirling-PDF Desktop Application" +authors = ["Stirling-PDF Contributors"] +license = "" +repository = "" +edition = "2021" +rust-version = "1.77.2" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "app_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2.2.0", features = [] } + +[dependencies] +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } +log = "0.4" +tauri = { version = "2.5.0", features = [] } +tauri-plugin-log = "2.0.0-rc" +tauri-plugin-shell = "2.1.0" +tauri-plugin-fs = "2.0.0" +tokio = { version = "1.0", features = ["time"] } +reqwest = { version = "0.11", features = ["json"] } + +# macOS-specific dependencies for native file opening +[target.'cfg(target_os = "macos")'.dependencies] +objc = "0.2" +cocoa = "0.24" +once_cell = "1.19" diff --git a/frontend/src-tauri/build.rs b/frontend/src-tauri/build.rs new file mode 100644 index 000000000..d860e1e6a --- /dev/null +++ b/frontend/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/frontend/src-tauri/capabilities/default.json b/frontend/src-tauri/capabilities/default.json new file mode 100644 index 000000000..385973667 --- /dev/null +++ b/frontend/src-tauri/capabilities/default.json @@ -0,0 +1,15 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "enables the default permissions", + "windows": [ + "main" + ], + "permissions": [ + "core:default", + { + "identifier": "fs:allow-read-file", + "allow": [{ "path": "**" }] + } + ] +} diff --git a/frontend/src-tauri/icons/128x128.png b/frontend/src-tauri/icons/128x128.png new file mode 100644 index 000000000..d233685dd Binary files /dev/null and b/frontend/src-tauri/icons/128x128.png differ diff --git a/frontend/src-tauri/icons/128x128@2x.png b/frontend/src-tauri/icons/128x128@2x.png new file mode 100644 index 000000000..0ac88b8db Binary files /dev/null and b/frontend/src-tauri/icons/128x128@2x.png differ diff --git a/frontend/src-tauri/icons/16x16.png b/frontend/src-tauri/icons/16x16.png new file mode 100644 index 000000000..d88e6615d Binary files /dev/null and b/frontend/src-tauri/icons/16x16.png differ diff --git a/frontend/src-tauri/icons/192x192.png b/frontend/src-tauri/icons/192x192.png new file mode 100644 index 000000000..4219bb840 Binary files /dev/null and b/frontend/src-tauri/icons/192x192.png differ diff --git a/frontend/src-tauri/icons/32x32.png b/frontend/src-tauri/icons/32x32.png new file mode 100644 index 000000000..9e8dd8a5d Binary files /dev/null and b/frontend/src-tauri/icons/32x32.png differ diff --git a/frontend/src-tauri/icons/64x64.png b/frontend/src-tauri/icons/64x64.png new file mode 100644 index 000000000..280a9c5ac Binary files /dev/null and b/frontend/src-tauri/icons/64x64.png differ diff --git a/frontend/src-tauri/icons/Square107x107Logo.png b/frontend/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 000000000..840e43164 Binary files /dev/null and b/frontend/src-tauri/icons/Square107x107Logo.png differ diff --git a/frontend/src-tauri/icons/Square142x142Logo.png b/frontend/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 000000000..aa8b40576 Binary files /dev/null and b/frontend/src-tauri/icons/Square142x142Logo.png differ diff --git a/frontend/src-tauri/icons/Square150x150Logo.png b/frontend/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 000000000..3c33180f9 Binary files /dev/null and b/frontend/src-tauri/icons/Square150x150Logo.png differ diff --git a/frontend/src-tauri/icons/Square284x284Logo.png b/frontend/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 000000000..72c5d7188 Binary files /dev/null and b/frontend/src-tauri/icons/Square284x284Logo.png differ diff --git a/frontend/src-tauri/icons/Square30x30Logo.png b/frontend/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 000000000..dc2fe4cfc Binary files /dev/null and b/frontend/src-tauri/icons/Square30x30Logo.png differ diff --git a/frontend/src-tauri/icons/Square310x310Logo.png b/frontend/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 000000000..b5fa0be56 Binary files /dev/null and b/frontend/src-tauri/icons/Square310x310Logo.png differ diff --git a/frontend/src-tauri/icons/Square44x44Logo.png b/frontend/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 000000000..097839b00 Binary files /dev/null and b/frontend/src-tauri/icons/Square44x44Logo.png differ diff --git a/frontend/src-tauri/icons/Square71x71Logo.png b/frontend/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 000000000..fb066fb32 Binary files /dev/null and b/frontend/src-tauri/icons/Square71x71Logo.png differ diff --git a/frontend/src-tauri/icons/Square89x89Logo.png b/frontend/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 000000000..00d893d7c Binary files /dev/null and b/frontend/src-tauri/icons/Square89x89Logo.png differ diff --git a/frontend/src-tauri/icons/StoreLogo.png b/frontend/src-tauri/icons/StoreLogo.png new file mode 100644 index 000000000..c56df3f8a Binary files /dev/null and b/frontend/src-tauri/icons/StoreLogo.png differ diff --git a/frontend/src-tauri/icons/android-chrome-192x192.png b/frontend/src-tauri/icons/android-chrome-192x192.png new file mode 100644 index 000000000..4219bb840 Binary files /dev/null and b/frontend/src-tauri/icons/android-chrome-192x192.png differ diff --git a/frontend/src-tauri/icons/android-chrome-512x512.png b/frontend/src-tauri/icons/android-chrome-512x512.png new file mode 100644 index 000000000..19bc603ec Binary files /dev/null and b/frontend/src-tauri/icons/android-chrome-512x512.png differ diff --git a/frontend/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/frontend/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..6a361221e Binary files /dev/null and b/frontend/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ diff --git a/frontend/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/frontend/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..475ea7dbc Binary files /dev/null and b/frontend/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/frontend/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/frontend/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000..6a361221e Binary files /dev/null and b/frontend/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/frontend/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/frontend/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..a82e68b38 Binary files /dev/null and b/frontend/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ diff --git a/frontend/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/frontend/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..d563b2d25 Binary files /dev/null and b/frontend/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/frontend/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/frontend/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000..a82e68b38 Binary files /dev/null and b/frontend/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/frontend/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/frontend/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..6c28ce599 Binary files /dev/null and b/frontend/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/frontend/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/frontend/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..c0cf472de Binary files /dev/null and b/frontend/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/frontend/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/frontend/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000..6c28ce599 Binary files /dev/null and b/frontend/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/frontend/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/frontend/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..b5df806c1 Binary files /dev/null and b/frontend/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/frontend/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/frontend/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..21a8c5bfb Binary files /dev/null and b/frontend/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/frontend/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/frontend/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..b5df806c1 Binary files /dev/null and b/frontend/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/frontend/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/frontend/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..224b0169c Binary files /dev/null and b/frontend/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/frontend/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/frontend/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..516f2024e Binary files /dev/null and b/frontend/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/frontend/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/frontend/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..224b0169c Binary files /dev/null and b/frontend/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/frontend/src-tauri/icons/icon.icns b/frontend/src-tauri/icons/icon.icns new file mode 100644 index 000000000..983df8577 Binary files /dev/null and b/frontend/src-tauri/icons/icon.icns differ diff --git a/frontend/src-tauri/icons/icon.ico b/frontend/src-tauri/icons/icon.ico new file mode 100644 index 000000000..b058a5591 Binary files /dev/null and b/frontend/src-tauri/icons/icon.ico differ diff --git a/frontend/src-tauri/icons/icon.png b/frontend/src-tauri/icons/icon.png new file mode 100644 index 000000000..5819d1b89 Binary files /dev/null and b/frontend/src-tauri/icons/icon.png differ diff --git a/frontend/src-tauri/icons/icon_orig.png b/frontend/src-tauri/icons/icon_orig.png new file mode 100644 index 000000000..5edc6eae2 Binary files /dev/null and b/frontend/src-tauri/icons/icon_orig.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-20x20@1x.png b/frontend/src-tauri/icons/ios/AppIcon-20x20@1x.png new file mode 100644 index 000000000..b440dda9d Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/frontend/src-tauri/icons/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 000000000..44ec1c6bc Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-20x20@2x.png b/frontend/src-tauri/icons/ios/AppIcon-20x20@2x.png new file mode 100644 index 000000000..44ec1c6bc Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-20x20@3x.png b/frontend/src-tauri/icons/ios/AppIcon-20x20@3x.png new file mode 100644 index 000000000..e388901c8 Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-29x29@1x.png b/frontend/src-tauri/icons/ios/AppIcon-29x29@1x.png new file mode 100644 index 000000000..df4c10e2f Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/frontend/src-tauri/icons/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 000000000..8a78c7b87 Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-29x29@2x.png b/frontend/src-tauri/icons/ios/AppIcon-29x29@2x.png new file mode 100644 index 000000000..8a78c7b87 Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-29x29@3x.png b/frontend/src-tauri/icons/ios/AppIcon-29x29@3x.png new file mode 100644 index 000000000..da7b0097b Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-40x40@1x.png b/frontend/src-tauri/icons/ios/AppIcon-40x40@1x.png new file mode 100644 index 000000000..44ec1c6bc Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/frontend/src-tauri/icons/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 000000000..70f8711ff Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-40x40@2x.png b/frontend/src-tauri/icons/ios/AppIcon-40x40@2x.png new file mode 100644 index 000000000..70f8711ff Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-40x40@3x.png b/frontend/src-tauri/icons/ios/AppIcon-40x40@3x.png new file mode 100644 index 000000000..d1d0ee368 Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-512@2x.png b/frontend/src-tauri/icons/ios/AppIcon-512@2x.png new file mode 100644 index 000000000..346e4a702 Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-60x60@2x.png b/frontend/src-tauri/icons/ios/AppIcon-60x60@2x.png new file mode 100644 index 000000000..d1d0ee368 Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-60x60@3x.png b/frontend/src-tauri/icons/ios/AppIcon-60x60@3x.png new file mode 100644 index 000000000..5190cb21b Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-76x76@1x.png b/frontend/src-tauri/icons/ios/AppIcon-76x76@1x.png new file mode 100644 index 000000000..9d97d05bb Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-76x76@2x.png b/frontend/src-tauri/icons/ios/AppIcon-76x76@2x.png new file mode 100644 index 000000000..4085fefa3 Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/frontend/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/frontend/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 000000000..2ad07a090 Binary files /dev/null and b/frontend/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/frontend/src-tauri/icons/mstile-144x144.png b/frontend/src-tauri/icons/mstile-144x144.png new file mode 100644 index 000000000..ff28cf1ac Binary files /dev/null and b/frontend/src-tauri/icons/mstile-144x144.png differ diff --git a/frontend/src-tauri/icons/mstile-150x150.png b/frontend/src-tauri/icons/mstile-150x150.png new file mode 100644 index 000000000..c900c83ae Binary files /dev/null and b/frontend/src-tauri/icons/mstile-150x150.png differ diff --git a/frontend/src-tauri/icons/mstile-310x150.png b/frontend/src-tauri/icons/mstile-310x150.png new file mode 100644 index 000000000..43a095f36 Binary files /dev/null and b/frontend/src-tauri/icons/mstile-310x150.png differ diff --git a/frontend/src-tauri/icons/mstile-310x310.png b/frontend/src-tauri/icons/mstile-310x310.png new file mode 100644 index 000000000..fd52bd61d Binary files /dev/null and b/frontend/src-tauri/icons/mstile-310x310.png differ diff --git a/frontend/src-tauri/icons/mstile-70x70.png b/frontend/src-tauri/icons/mstile-70x70.png new file mode 100644 index 000000000..7692923f7 Binary files /dev/null and b/frontend/src-tauri/icons/mstile-70x70.png differ diff --git a/frontend/src-tauri/icons/rainbow.svg b/frontend/src-tauri/icons/rainbow.svg new file mode 100644 index 000000000..0ee5e48b8 --- /dev/null +++ b/frontend/src-tauri/icons/rainbow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src-tauri/src/commands/backend.rs b/frontend/src-tauri/src/commands/backend.rs new file mode 100644 index 000000000..c465f7cc8 --- /dev/null +++ b/frontend/src-tauri/src/commands/backend.rs @@ -0,0 +1,379 @@ +use tauri_plugin_shell::ShellExt; +use tauri::Manager; +use std::sync::Mutex; +use std::path::PathBuf; +use crate::utils::add_log; + +// Store backend process handle globally +static BACKEND_PROCESS: Mutex> = Mutex::new(None); +static BACKEND_STARTING: Mutex = Mutex::new(false); + +// Helper function to reset starting flag +fn reset_starting_flag() { + let mut starting_guard = BACKEND_STARTING.lock().unwrap(); + *starting_guard = false; +} + +// Check if backend is already running or starting +fn check_backend_status() -> Result<(), String> { + // Check if backend is already running + { + let process_guard = BACKEND_PROCESS.lock().unwrap(); + if process_guard.is_some() { + add_log("⚠️ Backend process already running, skipping start".to_string()); + return Err("Backend already running".to_string()); + } + } + + // Check and set starting flag to prevent multiple simultaneous starts + { + let mut starting_guard = BACKEND_STARTING.lock().unwrap(); + if *starting_guard { + add_log("⚠️ Backend already starting, skipping duplicate start".to_string()); + return Err("Backend startup already in progress".to_string()); + } + *starting_guard = true; + } + + Ok(()) +} + +// Find the bundled JRE and return the java executable path +fn find_bundled_jre(resource_dir: &PathBuf) -> Result { + let jre_dir = resource_dir.join("runtime").join("jre"); + let java_executable = if cfg!(windows) { + jre_dir.join("bin").join("java.exe") + } else { + jre_dir.join("bin").join("java") + }; + + if !java_executable.exists() { + let error_msg = format!("❌ Bundled JRE not found at: {:?}", java_executable); + add_log(error_msg.clone()); + return Err(error_msg); + } + + add_log(format!("✅ Found bundled JRE: {:?}", java_executable)); + Ok(java_executable) +} + +// Find the Stirling-PDF JAR file +fn find_stirling_jar(resource_dir: &PathBuf) -> Result { + let libs_dir = resource_dir.join("libs"); + let mut jar_files: Vec<_> = std::fs::read_dir(&libs_dir) + .map_err(|e| { + let error_msg = format!("Failed to read libs directory: {}. Make sure the JAR is copied to libs/", e); + add_log(error_msg.clone()); + error_msg + })? + .filter_map(|entry| entry.ok()) + .filter(|entry| { + let path = entry.path(); + // Match any .jar file containing "stirling-pdf" (case-insensitive) + path.extension().and_then(|s| s.to_str()).map(|ext| ext.eq_ignore_ascii_case("jar")).unwrap_or(false) + && path.file_name() + .and_then(|f| f.to_str()) + .map(|name| name.to_ascii_lowercase().contains("stirling-pdf")) + .unwrap_or(false) + }) + .collect(); + + if jar_files.is_empty() { + let error_msg = "No Stirling-PDF JAR found in libs directory.".to_string(); + add_log(error_msg.clone()); + return Err(error_msg); + } + + // Sort by filename to get the latest version (case-insensitive) + jar_files.sort_by(|a, b| { + let name_a = a.file_name().to_string_lossy().to_ascii_lowercase(); + let name_b = b.file_name().to_string_lossy().to_ascii_lowercase(); + name_b.cmp(&name_a) // Reverse order to get latest first + }); + + let jar_path = jar_files[0].path(); + add_log(format!("📋 Selected JAR: {:?}", jar_path.file_name().unwrap())); + Ok(jar_path) +} + +// Normalize path to remove Windows UNC prefix +fn normalize_path(path: &PathBuf) -> PathBuf { + if cfg!(windows) { + let path_str = path.to_string_lossy(); + if path_str.starts_with(r"\\?\") { + PathBuf::from(&path_str[4..]) // Remove \\?\ prefix + } else { + path.clone() + } + } else { + path.clone() + } +} + +// Create, configure and run the Java command to run Stirling-PDF JAR +fn run_stirling_pdf_jar(app: &tauri::AppHandle, java_path: &PathBuf, jar_path: &PathBuf) -> Result<(), String> { + // Get platform-specific application data directory for Tauri mode + let app_data_dir = if cfg!(target_os = "macos") { + let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); + PathBuf::from(home).join("Library").join("Application Support").join("Stirling-PDF") + } else if cfg!(target_os = "windows") { + let appdata = std::env::var("APPDATA").unwrap_or_else(|_| std::env::temp_dir().to_string_lossy().to_string()); + PathBuf::from(appdata).join("Stirling-PDF") + } else { + let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); + PathBuf::from(home).join(".config").join("Stirling-PDF") + }; + + // Create subdirectories for different purposes + let config_dir = app_data_dir.join("configs"); + let log_dir = app_data_dir.join("logs"); + let work_dir = app_data_dir.join("workspace"); + + // Create all necessary directories + std::fs::create_dir_all(&app_data_dir).ok(); + std::fs::create_dir_all(&log_dir).ok(); + std::fs::create_dir_all(&work_dir).ok(); + std::fs::create_dir_all(&config_dir).ok(); + + add_log(format!("📁 App data directory: {}", app_data_dir.display())); + add_log(format!("📁 Log directory: {}", log_dir.display())); + add_log(format!("📁 Working directory: {}", work_dir.display())); + add_log(format!("📁 Config directory: {}", config_dir.display())); + + // Define all Java options with Tauri-specific paths + let log_path_option = format!("-Dlogging.file.path={}", log_dir.display()); + + let java_options = vec![ + "-Xmx2g", + "-DBROWSER_OPEN=false", + "-DSTIRLING_PDF_DESKTOP_UI=false", + "-DSTIRLING_PDF_TAURI_MODE=true", + &log_path_option, + "-Dlogging.file.name=stirling-pdf.log", + "-jar", + jar_path.to_str().unwrap() + ]; + + // Log the equivalent command for external testing + let java_command = format!( + "TAURI_PARENT_PID={} \"{}\" {}", + std::process::id(), + java_path.display(), + java_options.join(" ") + ); + add_log(format!("🔧 Equivalent command: {}", java_command)); + add_log(format!("📁 Backend logs will be in: {}", log_dir.display())); + + // Additional macOS-specific checks + if cfg!(target_os = "macos") { + // Check if java executable has execute permissions + if let Ok(metadata) = std::fs::metadata(java_path) { + let permissions = metadata.permissions(); + add_log(format!("🔍 Java executable permissions: {:?}", permissions)); + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mode = permissions.mode(); + add_log(format!("🔍 Java executable mode: 0o{:o}", mode)); + if mode & 0o111 == 0 { + add_log("⚠️ Java executable may not have execute permissions".to_string()); + } + } + } + + // Check if we can read the JAR file + if let Ok(metadata) = std::fs::metadata(jar_path) { + add_log(format!("📦 JAR file size: {} bytes", metadata.len())); + } else { + add_log("⚠️ Cannot read JAR file metadata".to_string()); + } + } + + let sidecar_command = app + .shell() + .command(java_path.to_str().unwrap()) + .args(java_options) + .current_dir(&work_dir) // Set working directory to writable location + .env("TAURI_PARENT_PID", std::process::id().to_string()) + .env("STIRLING_PDF_CONFIG_DIR", config_dir.to_str().unwrap()) + .env("STIRLING_PDF_LOG_DIR", log_dir.to_str().unwrap()) + .env("STIRLING_PDF_WORK_DIR", work_dir.to_str().unwrap()); + + add_log("⚙️ Starting backend with bundled JRE...".to_string()); + + let (rx, child) = sidecar_command + .spawn() + .map_err(|e| { + let error_msg = format!("❌ Failed to spawn sidecar: {}", e); + add_log(error_msg.clone()); + error_msg + })?; + + // Store the process handle + { + let mut process_guard = BACKEND_PROCESS.lock().unwrap(); + *process_guard = Some(child); + } + + add_log("✅ Backend started with bundled JRE, monitoring output...".to_string()); + + // Start monitoring output + monitor_backend_output(rx); + + Ok(()) +} + +// Monitor backend output in a separate task +fn monitor_backend_output(mut rx: tauri::async_runtime::Receiver) { + tokio::spawn(async move { + let mut _startup_detected = false; + let mut error_count = 0; + + while let Some(event) = rx.recv().await { + match event { + tauri_plugin_shell::process::CommandEvent::Stdout(output) => { + let output_str = String::from_utf8_lossy(&output); + add_log(format!("📤 Backend: {}", output_str)); + + // Look for startup indicators + if output_str.contains("Started SPDFApplication") || + output_str.contains("Navigate to "){ + _startup_detected = true; + add_log(format!("🎉 Backend startup detected: {}", output_str)); + } + + // Look for port binding + if output_str.contains("8080") { + add_log(format!("🔌 Port 8080 related output: {}", output_str)); + } + } + tauri_plugin_shell::process::CommandEvent::Stderr(output) => { + let output_str = String::from_utf8_lossy(&output); + add_log(format!("📥 Backend Error: {}", output_str)); + + // Look for error indicators + if output_str.contains("ERROR") || output_str.contains("Exception") || output_str.contains("FATAL") { + error_count += 1; + add_log(format!("⚠️ Backend error #{}: {}", error_count, output_str)); + } + + // Look for specific common issues + if output_str.contains("Address already in use") { + add_log("🚨 CRITICAL: Port 8080 is already in use by another process!".to_string()); + } + if output_str.contains("java.lang.ClassNotFoundException") { + add_log("🚨 CRITICAL: Missing Java dependencies!".to_string()); + } + if output_str.contains("java.io.FileNotFoundException") { + add_log("🚨 CRITICAL: Required file not found!".to_string()); + } + } + tauri_plugin_shell::process::CommandEvent::Error(error) => { + add_log(format!("❌ Backend process error: {}", error)); + } + tauri_plugin_shell::process::CommandEvent::Terminated(payload) => { + add_log(format!("💀 Backend terminated with code: {:?}", payload.code)); + if let Some(code) = payload.code { + match code { + 0 => println!("✅ Process terminated normally"), + 1 => println!("❌ Process terminated with generic error"), + 2 => println!("❌ Process terminated due to misuse"), + 126 => println!("❌ Command invoked cannot execute"), + 127 => println!("❌ Command not found"), + 128 => println!("❌ Invalid exit argument"), + 130 => println!("❌ Process terminated by Ctrl+C"), + _ => println!("❌ Process terminated with code: {}", code), + } + } + // Clear the stored process handle + let mut process_guard = BACKEND_PROCESS.lock().unwrap(); + *process_guard = None; + } + _ => { + println!("🔍 Unknown command event: {:?}", event); + } + } + } + + if error_count > 0 { + println!("⚠️ Backend process ended with {} errors detected", error_count); + } + }); +} + +// Command to start the backend with bundled JRE +#[tauri::command] +pub async fn start_backend(app: tauri::AppHandle) -> Result { + add_log("🚀 start_backend() called - Attempting to start backend with bundled JRE...".to_string()); + + // Check if backend is already running or starting + if let Err(msg) = check_backend_status() { + return Ok(msg); + } + + // Use Tauri's resource API to find the bundled JRE and JAR + let resource_dir = app.path().resource_dir().map_err(|e| { + let error_msg = format!("❌ Failed to get resource directory: {}", e); + add_log(error_msg.clone()); + reset_starting_flag(); + error_msg + })?; + + add_log(format!("🔍 Resource directory: {:?}", resource_dir)); + + // Find the bundled JRE + let java_executable = find_bundled_jre(&resource_dir).map_err(|e| { + reset_starting_flag(); + e + })?; + + // Find the Stirling-PDF JAR + let jar_path = find_stirling_jar(&resource_dir).map_err(|e| { + reset_starting_flag(); + e + })?; + + // Normalize the paths to remove Windows UNC prefix + let normalized_java_path = normalize_path(&java_executable); + let normalized_jar_path = normalize_path(&jar_path); + + add_log(format!("📦 Found JAR file: {:?}", jar_path)); + add_log(format!("📦 Normalized JAR path: {:?}", normalized_jar_path)); + add_log(format!("📦 Normalized Java path: {:?}", normalized_java_path)); + + // Create and start the Java command + run_stirling_pdf_jar(&app, &normalized_java_path, &normalized_jar_path).map_err(|e| { + reset_starting_flag(); + e + })?; + + // Wait for the backend to start + println!("⏳ Waiting for backend startup..."); + tokio::time::sleep(std::time::Duration::from_millis(10000)).await; + + // Reset the starting flag since startup is complete + reset_starting_flag(); + add_log("✅ Backend startup sequence completed, starting flag cleared".to_string()); + + Ok("Backend startup initiated successfully with bundled JRE".to_string()) +} + +// Cleanup function to stop backend on app exit +pub fn cleanup_backend() { + let mut process_guard = BACKEND_PROCESS.lock().unwrap(); + if let Some(child) = process_guard.take() { + let pid = child.pid(); + add_log(format!("🧹 App shutting down, cleaning up backend process (PID: {})", pid)); + + match child.kill() { + Ok(_) => { + add_log(format!("✅ Backend process (PID: {}) terminated during cleanup", pid)); + } + Err(e) => { + add_log(format!("❌ Failed to terminate backend process during cleanup: {}", e)); + println!("❌ Failed to terminate backend process during cleanup: {}", e); + } + } + } +} \ No newline at end of file diff --git a/frontend/src-tauri/src/commands/files.rs b/frontend/src-tauri/src/commands/files.rs new file mode 100644 index 000000000..7c397cfcf --- /dev/null +++ b/frontend/src-tauri/src/commands/files.rs @@ -0,0 +1,48 @@ +use crate::utils::add_log; +use std::sync::Mutex; + +// Store the opened file path globally +static OPENED_FILE: Mutex> = Mutex::new(None); + +// Set the opened file path (called by macOS file open events) +pub fn set_opened_file(file_path: String) { + let mut opened_file = OPENED_FILE.lock().unwrap(); + *opened_file = Some(file_path.clone()); + add_log(format!("📂 File opened via file open event: {}", file_path)); +} + +// Command to get opened file path (if app was launched with a file) +#[tauri::command] +pub async fn get_opened_file() -> Result, String> { + // First check if we have a file from macOS file open events + { + let opened_file = OPENED_FILE.lock().unwrap(); + if let Some(ref file_path) = *opened_file { + add_log(format!("📂 Returning stored opened file: {}", file_path)); + return Ok(Some(file_path.clone())); + } + } + + // Fallback to command line arguments (Windows/Linux) + let args: Vec = std::env::args().collect(); + + // Look for a PDF file argument (skip the first arg which is the executable) + for arg in args.iter().skip(1) { + if arg.ends_with(".pdf") && std::path::Path::new(arg).exists() { + add_log(format!("📂 PDF file opened via command line: {}", arg)); + return Ok(Some(arg.clone())); + } + } + + Ok(None) +} + +// Command to clear the opened file (after processing) +#[tauri::command] +pub async fn clear_opened_file() -> Result<(), String> { + let mut opened_file = OPENED_FILE.lock().unwrap(); + *opened_file = None; + add_log("📂 Cleared opened file".to_string()); + Ok(()) +} + diff --git a/frontend/src-tauri/src/commands/health.rs b/frontend/src-tauri/src/commands/health.rs new file mode 100644 index 000000000..394c8462c --- /dev/null +++ b/frontend/src-tauri/src/commands/health.rs @@ -0,0 +1,36 @@ +// Command to check if backend is healthy +#[tauri::command] +pub async fn check_backend_health() -> Result { + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(5)) + .build() + .map_err(|e| format!("Failed to create HTTP client: {}", e))?; + + match client.get("http://localhost:8080/api/v1/info/status").send().await { + Ok(response) => { + let status = response.status(); + if status.is_success() { + match response.text().await { + Ok(_body) => { + println!("✅ Backend health check successful"); + Ok(true) + } + Err(e) => { + println!("⚠️ Failed to read health response: {}", e); + Ok(false) + } + } + } else { + println!("⚠️ Health check failed with status: {}", status); + Ok(false) + } + } + Err(e) => { + // Only log connection errors if they're not the common "connection refused" during startup + if !e.to_string().contains("connection refused") && !e.to_string().contains("No connection could be made") { + println!("❌ Health check error: {}", e); + } + Ok(false) + } + } +} \ No newline at end of file diff --git a/frontend/src-tauri/src/commands/mod.rs b/frontend/src-tauri/src/commands/mod.rs new file mode 100644 index 000000000..773f5d2dd --- /dev/null +++ b/frontend/src-tauri/src/commands/mod.rs @@ -0,0 +1,7 @@ +pub mod backend; +pub mod health; +pub mod files; + +pub use backend::{start_backend, cleanup_backend}; +pub use health::check_backend_health; +pub use files::{get_opened_file, clear_opened_file, set_opened_file}; \ No newline at end of file diff --git a/frontend/src-tauri/src/file_handler.rs b/frontend/src-tauri/src/file_handler.rs new file mode 100644 index 000000000..d432a8131 --- /dev/null +++ b/frontend/src-tauri/src/file_handler.rs @@ -0,0 +1,189 @@ +/// Multi-platform file opening handler +/// +/// This module provides unified file opening support across platforms: +/// - macOS: Uses native NSApplication delegate (proper Apple Events) +/// - Windows/Linux: Uses command line arguments (fallback approach) +/// - All platforms: Runtime event handling via Tauri events + +use crate::utils::add_log; +use crate::commands::set_opened_file; +use tauri::AppHandle; + + +/// Initialize file handling for the current platform +pub fn initialize_file_handler(app: &AppHandle) { + add_log("🔧 Initializing file handler...".to_string()); + + // Platform-specific initialization + #[cfg(target_os = "macos")] + { + add_log("🍎 Using macOS native file handler".to_string()); + macos_native::register_open_file_handler(app); + } + + #[cfg(not(target_os = "macos"))] + { + add_log("🖥️ Using command line argument file handler".to_string()); + let _ = app; // Suppress unused variable warning + } + + // Universal: Check command line arguments (works on all platforms) + check_command_line_args(); +} + +/// Early initialization for macOS delegate registration +pub fn early_init() { + #[cfg(target_os = "macos")] + { + add_log("🔄 Early macOS initialization...".to_string()); + macos_native::register_delegate_early(); + } +} + +/// Check command line arguments for file paths (universal fallback) +fn check_command_line_args() { + let args: Vec = std::env::args().collect(); + add_log(format!("🔍 DEBUG: All command line args: {:?}", args)); + + // Check command line arguments for file opening + for (i, arg) in args.iter().enumerate() { + add_log(format!("🔍 DEBUG: Arg {}: {}", i, arg)); + if i > 0 && arg.ends_with(".pdf") && std::path::Path::new(arg).exists() { + add_log(format!("📂 File argument detected: {}", arg)); + set_opened_file(arg.clone()); + break; // Only handle the first PDF file + } + } +} + +/// Handle runtime file open events (for future single-instance support) +#[allow(dead_code)] +pub fn handle_runtime_file_open(file_path: String) { + if file_path.ends_with(".pdf") && std::path::Path::new(&file_path).exists() { + add_log(format!("📂 Runtime file open: {}", file_path)); + set_opened_file(file_path); + } +} + +#[cfg(target_os = "macos")] +mod macos_native { + use objc::{class, msg_send, sel, sel_impl}; + use objc::runtime::{Class, Object, Sel}; + use cocoa::appkit::NSApplication; + use cocoa::base::{id, nil}; + use once_cell::sync::Lazy; + use std::sync::Mutex; + use tauri::{AppHandle, Emitter}; + + use crate::utils::add_log; + use crate::commands::set_opened_file; + + // Static app handle storage + static APP_HANDLE: Lazy>>> = Lazy::new(|| Mutex::new(None)); + + // Store files opened during launch + static LAUNCH_FILES: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); + + + extern "C" fn open_files(_self: &Object, _cmd: Sel, _sender: id, filenames: id) { + unsafe { + add_log(format!("📂 macOS native openFiles event called")); + + // filenames is an NSArray of NSString objects + let count: usize = msg_send![filenames, count]; + add_log(format!("📂 Number of files to open: {}", count)); + + for i in 0..count { + let filename: id = msg_send![filenames, objectAtIndex: i]; + let cstr = { + let bytes: *const std::os::raw::c_char = msg_send![filename, UTF8String]; + std::ffi::CStr::from_ptr(bytes) + }; + + if let Ok(path) = cstr.to_str() { + add_log(format!("📂 macOS file open: {}", path)); + if path.ends_with(".pdf") { + // Always set the opened file for command-line interface + set_opened_file(path.to_string()); + + if let Some(app) = APP_HANDLE.lock().unwrap().as_ref() { + // App is running, emit event immediately + add_log(format!("✅ App running, emitting file event: {}", path)); + let _ = app.emit("macos://open-file", path.to_string()); + } else { + // App not ready yet, store for later processing + add_log(format!("🚀 App not ready, storing file for later: {}", path)); + LAUNCH_FILES.lock().unwrap().push(path.to_string()); + } + } + } + } + } + } + + // Register the delegate immediately when the module loads + pub fn register_delegate_early() { + add_log("🔧 Registering macOS delegate early...".to_string()); + + unsafe { + let ns_app = NSApplication::sharedApplication(nil); + + // Check if there's already a delegate + let existing_delegate: id = msg_send![ns_app, delegate]; + if existing_delegate != nil { + add_log("⚠️ Tauri already has an NSApplication delegate, trying to extend it...".to_string()); + + // Try to add our method to the existing delegate's class + let delegate_class: id = msg_send![existing_delegate, class]; + let class_name: *const std::os::raw::c_char = msg_send![delegate_class, name]; + let class_name_str = std::ffi::CStr::from_ptr(class_name).to_string_lossy(); + add_log(format!("🔍 Existing delegate class: {}", class_name_str)); + + // This approach won't work with existing classes, so let's try a different method + // We'll use method swizzling or create a new delegate that forwards to the old one + add_log("🔄 Will try alternative approach...".to_string()); + } + + let delegate_class = Class::get("StirlingAppDelegate").unwrap_or_else(|| { + let superclass = class!(NSObject); + let mut decl = objc::declare::ClassDecl::new("StirlingAppDelegate", superclass).unwrap(); + + // Add file opening delegate method (modern plural version) + decl.add_method( + sel!(application:openFiles:), + open_files as extern "C" fn(&Object, Sel, id, id) + ); + + decl.register() + }); + + let delegate: id = msg_send![delegate_class, new]; + let _: () = msg_send![ns_app, setDelegate:delegate]; + } + + add_log("✅ macOS delegate registered early".to_string()); + } + + pub fn register_open_file_handler(app: &AppHandle) { + add_log("🔧 Connecting app handle to file handler...".to_string()); + + // Store the app handle + *APP_HANDLE.lock().unwrap() = Some(app.clone()); + + // Process any files that were opened during launch + let launch_files = { + let mut files = LAUNCH_FILES.lock().unwrap(); + let result = files.clone(); + files.clear(); + result + }; + + for file_path in launch_files { + add_log(format!("📂 Processing stored launch file: {}", file_path)); + set_opened_file(file_path.clone()); + let _ = app.emit("macos://open-file", file_path); + } + + add_log("✅ macOS file handler connected successfully".to_string()); + } +} \ No newline at end of file diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs new file mode 100644 index 000000000..f64b8bd0b --- /dev/null +++ b/frontend/src-tauri/src/lib.rs @@ -0,0 +1,65 @@ +use tauri::{RunEvent, WindowEvent, Emitter}; + +mod utils; +mod commands; +mod file_handler; + +use commands::{start_backend, check_backend_health, get_opened_file, clear_opened_file, cleanup_backend, set_opened_file}; +use utils::{add_log, get_tauri_logs}; + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + // Initialize file handler early for macOS + file_handler::early_init(); + + tauri::Builder::default() + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_fs::init()) + .setup(|app| { + add_log("🚀 Tauri app setup started".to_string()); + + // Initialize platform-specific file handler + file_handler::initialize_file_handler(&app.handle()); + + add_log("🔍 DEBUG: Setup completed".to_string()); + Ok(()) + }) + .invoke_handler(tauri::generate_handler![start_backend, check_backend_health, get_opened_file, clear_opened_file, get_tauri_logs]) + .build(tauri::generate_context!()) + .expect("error while building tauri application") + .run(|app_handle, event| { + match event { + RunEvent::ExitRequested { .. } => { + add_log("🔄 App exit requested, cleaning up...".to_string()); + cleanup_backend(); + // Use Tauri's built-in cleanup + app_handle.cleanup_before_exit(); + } + RunEvent::WindowEvent { event: WindowEvent::CloseRequested {.. }, .. } => { + add_log("🔄 Window close requested, cleaning up...".to_string()); + cleanup_backend(); + // Allow the window to close + } + #[cfg(target_os = "macos")] + RunEvent::Opened { urls } => { + add_log(format!("📂 Tauri file opened event: {:?}", urls)); + for url in urls { + let url_str = url.as_str(); + if url_str.starts_with("file://") { + let file_path = url_str.strip_prefix("file://").unwrap_or(url_str); + if file_path.ends_with(".pdf") { + add_log(format!("📂 Processing opened PDF: {}", file_path)); + set_opened_file(file_path.to_string()); + let _ = app_handle.emit("macos://open-file", file_path.to_string()); + } + } + } + } + _ => { + // Only log unhandled events in debug mode to reduce noise + // #[cfg(debug_assertions)] + // add_log(format!("🔍 DEBUG: Unhandled event: {:?}", event)); + } + } + }); +} \ No newline at end of file diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs new file mode 100644 index 000000000..ad5fe8399 --- /dev/null +++ b/frontend/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + app_lib::run(); +} diff --git a/frontend/src-tauri/src/utils/logging.rs b/frontend/src-tauri/src/utils/logging.rs new file mode 100644 index 000000000..bda1913ad --- /dev/null +++ b/frontend/src-tauri/src/utils/logging.rs @@ -0,0 +1,90 @@ +use std::sync::Mutex; +use std::collections::VecDeque; +use std::fs::OpenOptions; +use std::io::Write; +use std::path::PathBuf; + +// Store backend logs globally +static BACKEND_LOGS: Mutex> = Mutex::new(VecDeque::new()); + +// Get platform-specific log directory +fn get_log_directory() -> PathBuf { + if cfg!(target_os = "macos") { + // macOS: ~/Library/Logs/Stirling-PDF + let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); + PathBuf::from(home).join("Library").join("Logs").join("Stirling-PDF") + } else if cfg!(target_os = "windows") { + // Windows: %APPDATA%\Stirling-PDF\logs + let appdata = std::env::var("APPDATA").unwrap_or_else(|_| std::env::temp_dir().to_string_lossy().to_string()); + PathBuf::from(appdata).join("Stirling-PDF").join("logs") + } else { + // Linux: ~/.config/Stirling-PDF/logs + let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); + PathBuf::from(home).join(".config").join("Stirling-PDF").join("logs") + } +} + +// Helper function to add log entry +pub fn add_log(message: String) { + let timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + + let log_entry = format!("{}: {}", timestamp, message); + + // Add to memory logs + { + let mut logs = BACKEND_LOGS.lock().unwrap(); + logs.push_back(log_entry.clone()); + // Keep only last 100 log entries + if logs.len() > 100 { + logs.pop_front(); + } + } + + // Write to file + write_to_log_file(&log_entry); + + // Remove trailing newline if present + let clean_message = message.trim_end_matches('\n').to_string(); + println!("{}", clean_message); // Also print to console +} + +// Write log entry to file +fn write_to_log_file(log_entry: &str) { + let log_dir = get_log_directory(); + if let Err(e) = std::fs::create_dir_all(&log_dir) { + eprintln!("Failed to create log directory: {}", e); + return; + } + + let log_file = log_dir.join("tauri-backend.log"); + + match OpenOptions::new() + .create(true) + .append(true) + .open(&log_file) + { + Ok(mut file) => { + if let Err(e) = writeln!(file, "{}", log_entry) { + eprintln!("Failed to write to log file: {}", e); + } + } + Err(e) => { + eprintln!("Failed to open log file {:?}: {}", log_file, e); + } + } +} + +// Get current logs for debugging +pub fn get_logs() -> Vec { + let logs = BACKEND_LOGS.lock().unwrap(); + logs.iter().cloned().collect() +} + +// Command to get logs from frontend +#[tauri::command] +pub async fn get_tauri_logs() -> Result, String> { + Ok(get_logs()) +} \ No newline at end of file diff --git a/frontend/src-tauri/src/utils/mod.rs b/frontend/src-tauri/src/utils/mod.rs new file mode 100644 index 000000000..258efca6c --- /dev/null +++ b/frontend/src-tauri/src/utils/mod.rs @@ -0,0 +1,3 @@ +pub mod logging; + +pub use logging::{add_log, get_tauri_logs}; \ No newline at end of file diff --git a/frontend/src-tauri/stirling-pdf.desktop b/frontend/src-tauri/stirling-pdf.desktop new file mode 100644 index 000000000..9d6029377 --- /dev/null +++ b/frontend/src-tauri/stirling-pdf.desktop @@ -0,0 +1,14 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Name=Stirling-PDF +Comment=Locally hosted web application that allows you to perform various operations on PDF files +Icon={{icon}} +Terminal=false +MimeType=application/pdf; +Categories=Office;Graphics;Utility; +Actions=open-file; + +[Desktop Action open-file] +Name=Open PDF File +Exec=/usr/bin/stirling-pdf %F \ No newline at end of file diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json new file mode 100644 index 000000000..89b24641a --- /dev/null +++ b/frontend/src-tauri/tauri.conf.json @@ -0,0 +1,63 @@ +{ + "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", + "productName": "Stirling-PDF", + "version": "2.0.0", + "identifier": "stirling.pdf.dev", + "build": { + "frontendDist": "../dist", + "devUrl": "http://localhost:5173", + "beforeDevCommand": "npm run dev", + "beforeBuildCommand": "npm run build" + }, + "app": { + "windows": [ + { + "title": "Stirling-PDF", + "width": 1024, + "height": 768, + "resizable": true, + "fullscreen": false + } + ] + }, + "bundle": { + "active": true, + "targets": ["deb", "rpm", "dmg", "msi"], + "icon": [ + "icons/icon.png", + "icons/icon.icns", + "icons/icon.ico", + "icons/16x16.png", + "icons/32x32.png", + "icons/64x64.png", + "icons/128x128.png", + "icons/192x192.png" + ], + "resources": [ + "libs/*.jar", + "runtime/jre/**/*" + ], + "fileAssociations": [ + { + "ext": ["pdf"], + "name": "PDF Document", + "description": "Open PDF files with Stirling-PDF", + "role": "Editor", + "mimeType": "application/pdf" + } + ], + "linux": { + "deb": { + "desktopTemplate": "stirling-pdf.desktop" + } + } + }, + "plugins": { + "shell": { + "open": true + }, + "fs": { + "requireLiteralLeadingDot": false + } + } +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index de5001850..16d6c6973 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,18 +1,56 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { RainbowThemeProvider } from './components/shared/RainbowThemeProvider'; import { FileContextProvider } from './contexts/FileContext'; import HomePage from './pages/HomePage'; +import { useOpenedFile } from './hooks/useOpenedFile'; +import { useBackendInitializer } from './hooks/useBackendInitializer'; +import { fileOpenService } from './services/fileOpenService'; // Import global styles import './styles/tailwind.css'; import './index.css'; +import { BackendHealthIndicator } from './components/BackendHealthIndicator'; + export default function App() { + + // Initialize backend on app startup + useBackendInitializer(); + + // Handle file opened with app (Tauri mode) + const { openedFilePath, loading: openedFileLoading } = useOpenedFile(); + const [openedFile, setOpenedFile] = useState(null); + + // Load opened file once when path is available + useEffect(() => { + if (openedFilePath && !openedFileLoading) { + const loadOpenedFile = async () => { + try { + const fileData = await fileOpenService.readFileAsArrayBuffer(openedFilePath); + if (fileData) { + // Create a File object from the ArrayBuffer + const file = new File([fileData.arrayBuffer], fileData.fileName, { + type: 'application/pdf' + }); + setOpenedFile(file); + } + } catch (error) { + console.error('Failed to load opened file:', error); + } + }; + + loadOpenedFile(); + } + }, [openedFilePath, openedFileLoading]); + return ( + <> + - + + ); } diff --git a/frontend/src/components/BackendHealthIndicator.tsx b/frontend/src/components/BackendHealthIndicator.tsx new file mode 100644 index 000000000..4cf3a3d3a --- /dev/null +++ b/frontend/src/components/BackendHealthIndicator.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import '../index.css'; +import { useBackendHealth } from '../hooks/useBackendHealth'; + +interface BackendHealthIndicatorProps { + className?: string; +} + +export const BackendHealthIndicator: React.FC = ({ + className = '' +}) => { + const { isHealthy, isChecking, error, checkHealth } = useBackendHealth(); + + let statusColor = 'bg-red-500'; // offline + if (isChecking || (!isHealthy && error === 'Backend starting up...')) { + statusColor = 'bg-yellow-500'; // starting/checking + } else if (isHealthy) { + statusColor = 'bg-green-500'; // online + } + + return ( +
+ ); +}; \ No newline at end of file diff --git a/frontend/src/hooks/tools/compress/useCompressOperation.ts b/frontend/src/hooks/tools/compress/useCompressOperation.ts index 4582068a6..111006c36 100644 --- a/frontend/src/hooks/tools/compress/useCompressOperation.ts +++ b/frontend/src/hooks/tools/compress/useCompressOperation.ts @@ -5,6 +5,7 @@ import { useFileContext } from '../../../contexts/FileContext'; import { FileOperation } from '../../../types/fileContext'; import { zipFileService } from '../../../services/zipFileService'; import { generateThumbnailForFile } from '../../../utils/thumbnailUtils'; +import {makeApiUrl} from '../../../utils/api'; export interface CompressParameters { compressionLevel: number; @@ -184,7 +185,7 @@ export const useCompressOperation = (): CompressOperationHook => { setErrorMessage(null); try { - const response = await axios.post(endpoint, formData, { responseType: "blob" }); + const response = await axios.post(makeApiUrl(endpoint), formData, { responseType: "blob" }); // Determine the correct content type from the response const contentType = response.headers['content-type'] || 'application/zip'; diff --git a/frontend/src/hooks/tools/split/useSplitOperation.ts b/frontend/src/hooks/tools/split/useSplitOperation.ts index 3abf2981e..6f6d7b349 100644 --- a/frontend/src/hooks/tools/split/useSplitOperation.ts +++ b/frontend/src/hooks/tools/split/useSplitOperation.ts @@ -7,6 +7,7 @@ import { zipFileService } from '../../../services/zipFileService'; import { generateThumbnailForFile } from '../../../utils/thumbnailUtils'; import { SplitParameters } from '../../../components/tools/split/SplitSettings'; import { SPLIT_MODES, ENDPOINTS, type SplitMode } from '../../../constants/splitConstants'; +import { makeApiUrl } from '../../../utils/api'; export interface SplitOperationHook { executeOperation: ( @@ -184,7 +185,7 @@ export const useSplitOperation = (): SplitOperationHook => { setErrorMessage(null); try { - const response = await axios.post(endpoint, formData, { responseType: "blob" }); + const response = await axios.post(makeApiUrl(endpoint), formData, { responseType: "blob" }); const blob = new Blob([response.data], { type: "application/zip" }); const url = window.URL.createObjectURL(blob); diff --git a/frontend/src/hooks/useAppConfig.ts b/frontend/src/hooks/useAppConfig.ts index 899038f51..4e71152dd 100644 --- a/frontend/src/hooks/useAppConfig.ts +++ b/frontend/src/hooks/useAppConfig.ts @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { makeApiUrl } from '../utils/api'; export interface AppConfig { baseUrl?: string; @@ -46,7 +47,7 @@ export function useAppConfig(): UseAppConfigReturn { setLoading(true); setError(null); - const response = await fetch('/api/v1/config/app-config'); + const response = await fetch(makeApiUrl('/api/v1/config/app-config')); if (!response.ok) { throw new Error(`Failed to fetch config: ${response.status} ${response.statusText}`); diff --git a/frontend/src/hooks/useBackendHealth.ts b/frontend/src/hooks/useBackendHealth.ts new file mode 100644 index 000000000..506f523fd --- /dev/null +++ b/frontend/src/hooks/useBackendHealth.ts @@ -0,0 +1,124 @@ +import { useState, useEffect, useCallback } from 'react'; +import { makeApiUrl } from '../utils/api'; + +export interface BackendHealthState { + isHealthy: boolean; + isChecking: boolean; + lastChecked: Date | null; + error: string | null; +} + +export const useBackendHealth = (checkInterval: number = 2000) => { + const [healthState, setHealthState] = useState({ + isHealthy: false, + isChecking: false, + lastChecked: null, + error: null, + }); + + const [startupTime] = useState(new Date()); + const [attemptCount, setAttemptCount] = useState(0); + + const checkHealth = useCallback(async () => { + setHealthState(prev => ({ ...prev, isChecking: true, error: null })); + setAttemptCount(prev => prev + 1); + + try { + // Direct HTTP call to backend health endpoint using api.ts + const healthUrl = makeApiUrl('/api/v1/info/status'); + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout + + const response = await fetch(healthUrl, { + method: 'GET', + signal: controller.signal, + headers: { + 'Accept': 'application/json', + }, + }); + + clearTimeout(timeoutId); + + const isHealthy = response.ok; + + setHealthState({ + isHealthy, + isChecking: false, + lastChecked: new Date(), + error: null, + }); + + if (isHealthy) { + // Log success message if this is the first successful check after failures + if (attemptCount > 0) { + const now = new Date(); + const timeSinceStartup = now.getTime() - startupTime.getTime(); + console.log('✅ Backend health check successful:', { + timeSinceStartup: Math.round(timeSinceStartup / 1000) + 's', + attemptsBeforeSuccess: attemptCount, + }); + } + setAttemptCount(0); // Reset attempt count on success + } + } catch (error) { + const now = new Date(); + const timeSinceStartup = now.getTime() - startupTime.getTime(); + const isWithinStartupPeriod = timeSinceStartup < 60000; // 60 seconds + + let errorMessage: string; + + if (error instanceof Error) { + if (error.name === 'AbortError') { + errorMessage = isWithinStartupPeriod ? 'Backend starting up...' : 'Health check timeout'; + } else if (error.message.includes('fetch')) { + errorMessage = isWithinStartupPeriod ? 'Backend starting up...' : 'Cannot connect to backend'; + } else { + errorMessage = isWithinStartupPeriod ? 'Backend starting up...' : error.message; + } + } else { + errorMessage = isWithinStartupPeriod ? 'Backend starting up...' : 'Health check failed'; + } + + // Only log errors to console after startup period + if (!isWithinStartupPeriod) { + console.error('Backend health check failed:', { + error: error instanceof Error ? error.message : error, + timeSinceStartup: Math.round(timeSinceStartup / 1000) + 's', + attemptCount, + }); + } else { + // During startup, only log on first few attempts to reduce noise + if (attemptCount <= 3) { + console.log('Backend health check (startup period):', { + message: errorMessage, + timeSinceStartup: Math.round(timeSinceStartup / 1000) + 's', + attempt: attemptCount, + }); + } + } + + setHealthState({ + isHealthy: false, + isChecking: false, + lastChecked: new Date(), + error: errorMessage, + }); + } + }, [startupTime]); + + useEffect(() => { + // Initial health check + checkHealth(); + + // Set up periodic health checks + const interval = setInterval(checkHealth, checkInterval); + + return () => clearInterval(interval); + }, [checkHealth, checkInterval]); + + return { + ...healthState, + checkHealth, + }; +}; \ No newline at end of file diff --git a/frontend/src/hooks/useBackendInitializer.ts b/frontend/src/hooks/useBackendInitializer.ts new file mode 100644 index 000000000..52dd58738 --- /dev/null +++ b/frontend/src/hooks/useBackendInitializer.ts @@ -0,0 +1,29 @@ +import { useEffect } from 'react'; + +/** + * Custom hook to handle backend initialization in Tauri environment + * Automatically starts the backend when the app loads if running in Tauri + */ +export function useBackendInitializer() { + useEffect(() => { + // Only start backend if running in Tauri + const initializeBackend = async () => { + try { + // Check if we're running in Tauri environment + if (typeof window !== 'undefined' && (window.__TAURI__ || window.__TAURI_INTERNALS__)) { + const { tauriBackendService } = await import('../services/tauriBackendService'); + console.log('Running in Tauri - Starting backend on React app startup...'); + await tauriBackendService.startBackend(); + console.log('Backend started successfully'); + } + else { + console.warn('Not running in Tauri - Backend will not be started'); + } + } catch (error) { + console.error('Failed to start backend on app startup:', error); + } + }; + + initializeBackend(); + }, []); +} \ No newline at end of file diff --git a/frontend/src/hooks/useEndpointConfig.ts b/frontend/src/hooks/useEndpointConfig.ts index 13f13764b..51f283976 100644 --- a/frontend/src/hooks/useEndpointConfig.ts +++ b/frontend/src/hooks/useEndpointConfig.ts @@ -1,9 +1,11 @@ import { useState, useEffect } from 'react'; +import { makeApiUrl } from '../utils/api'; +import { useBackendHealth } from './useBackendHealth'; /** * Hook to check if a specific endpoint is enabled */ -export function useEndpointEnabled(endpoint: string): { +export function useEndpointEnabled(endpoint: string, backendHealthy?: boolean): { enabled: boolean | null; loading: boolean; error: string | null; @@ -24,7 +26,7 @@ export function useEndpointEnabled(endpoint: string): { setLoading(true); setError(null); - const response = await fetch(`/api/v1/config/endpoint-enabled?endpoint=${encodeURIComponent(endpoint)}`); + const response = await fetch(makeApiUrl(`/api/v1/config/endpoint-enabled?endpoint=${encodeURIComponent(endpoint)}`)); if (!response.ok) { throw new Error(`Failed to check endpoint: ${response.status} ${response.statusText}`); @@ -42,8 +44,16 @@ export function useEndpointEnabled(endpoint: string): { }; useEffect(() => { - fetchEndpointStatus(); - }, [endpoint]); + // Only fetch endpoint status if backend is healthy (or if backendHealthy is not provided) + if (backendHealthy === undefined || backendHealthy === true) { + fetchEndpointStatus(); + } else { + // Backend is not healthy, reset state + setEnabled(null); + setLoading(false); + setError('Backend not available'); + } + }, [endpoint, backendHealthy]); return { enabled, @@ -57,7 +67,7 @@ export function useEndpointEnabled(endpoint: string): { * Hook to check multiple endpoints at once using batch API * Returns a map of endpoint -> enabled status */ -export function useMultipleEndpointsEnabled(endpoints: string[]): { +export function useMultipleEndpointsEnabled(endpoints: string[], backendHealthy?: boolean): { endpointStatus: Record; loading: boolean; error: string | null; @@ -80,7 +90,7 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): { // Use batch API for efficiency const endpointsParam = endpoints.join(','); - const response = await fetch(`/api/v1/config/endpoints-enabled?endpoints=${encodeURIComponent(endpointsParam)}`); + const response = await fetch(makeApiUrl(`/api/v1/config/endpoints-enabled?endpoints=${encodeURIComponent(endpointsParam)}`)); if (!response.ok) { throw new Error(`Failed to check endpoints: ${response.status} ${response.statusText}`); @@ -105,8 +115,16 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): { }; useEffect(() => { - fetchAllEndpointStatuses(); - }, [endpoints.join(',')]); // Re-run when endpoints array changes + // Only fetch endpoint statuses if backend is healthy (or if backendHealthy is not provided) + if (backendHealthy === undefined || backendHealthy === true) { + fetchAllEndpointStatuses(); + } else { + // Backend is not healthy, reset state + setEndpointStatus({}); + setLoading(false); + setError('Backend not available'); + } + }, [endpoints.join(','), backendHealthy]); // Re-run when endpoints array changes or backend health changes return { endpointStatus, @@ -114,4 +132,50 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): { error, refetch: fetchAllEndpointStatuses, }; +} + +/** + * Convenience hook that combines backend health checking with endpoint status checking + * Only checks endpoint status once backend is healthy + */ +export function useEndpointEnabledWithHealthCheck(endpoint: string): { + enabled: boolean | null; + loading: boolean; + error: string | null; + backendHealthy: boolean; + refetch: () => Promise; +} { + const { isHealthy: backendHealthy } = useBackendHealth(); + const { enabled, loading, error, refetch } = useEndpointEnabled(endpoint, backendHealthy); + + return { + enabled, + loading, + error, + backendHealthy, + refetch, + }; +} + +/** + * Convenience hook that combines backend health checking with multiple endpoint status checking + * Only checks endpoint statuses once backend is healthy + */ +export function useMultipleEndpointsEnabledWithHealthCheck(endpoints: string[]): { + endpointStatus: Record; + loading: boolean; + error: string | null; + backendHealthy: boolean; + refetch: () => Promise; +} { + const { isHealthy: backendHealthy } = useBackendHealth(); + const { endpointStatus, loading, error, refetch } = useMultipleEndpointsEnabled(endpoints, backendHealthy); + + return { + endpointStatus, + loading, + error, + backendHealthy, + refetch, + }; } \ No newline at end of file diff --git a/frontend/src/hooks/useOpenedFile.ts b/frontend/src/hooks/useOpenedFile.ts new file mode 100644 index 000000000..a1ac024e9 --- /dev/null +++ b/frontend/src/hooks/useOpenedFile.ts @@ -0,0 +1,47 @@ +import { useState, useEffect } from 'react'; +import { fileOpenService } from '../services/fileOpenService'; + +export function useOpenedFile() { + const [openedFilePath, setOpenedFilePath] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const checkForOpenedFile = async () => { + console.log('🔍 Checking for opened file...'); + try { + const filePath = await fileOpenService.getOpenedFile(); + console.log('🔍 fileOpenService.getOpenedFile() returned:', filePath); + + if (filePath) { + console.log('✅ App opened with file:', filePath); + setOpenedFilePath(filePath); + + // Clear the file from service state after consuming it + await fileOpenService.clearOpenedFile(); + } else { + console.log('ℹ️ No file was opened with the app'); + } + + } catch (error) { + console.error('❌ Failed to check for opened file:', error); + } finally { + setLoading(false); + } + }; + + checkForOpenedFile(); + + // Listen for runtime file open events (abstracted through service) + const unlistenRuntimeEvents = fileOpenService.onFileOpened((filePath) => { + console.log('📂 Runtime file open event:', filePath); + setOpenedFilePath(filePath); + }); + + // Cleanup function + return () => { + unlistenRuntimeEvents(); + }; + }, []); + + return { openedFilePath, loading }; +} \ No newline at end of file diff --git a/frontend/src/hooks/useToolManagement.tsx b/frontend/src/hooks/useToolManagement.tsx index 317d6abbc..70b93f538 100644 --- a/frontend/src/hooks/useToolManagement.tsx +++ b/frontend/src/hooks/useToolManagement.tsx @@ -6,7 +6,7 @@ import ZoomInMapIcon from "@mui/icons-material/ZoomInMap"; import SplitPdfPanel from "../tools/Split"; import CompressPdfPanel from "../tools/Compress"; import MergePdfPanel from "../tools/Merge"; -import { useMultipleEndpointsEnabled } from "./useEndpointConfig"; +import { useMultipleEndpointsEnabledWithHealthCheck } from './useEndpointConfig'; type ToolRegistryEntry = { icon: React.ReactNode; @@ -40,7 +40,8 @@ export const useToolManagement = () => { const [toolSelectedFileIds, setToolSelectedFileIds] = useState([]); const allEndpoints = Array.from(new Set(Object.values(toolEndpoints).flat())); - const { endpointStatus, loading: endpointsLoading } = useMultipleEndpointsEnabled(allEndpoints); + const { endpointStatus, loading: endpointsLoading } = useMultipleEndpointsEnabledWithHealthCheck(allEndpoints); + const isToolAvailable = useCallback((toolKey: string): boolean => { if (endpointsLoading) return true; @@ -91,6 +92,5 @@ export const useToolManagement = () => { selectTool, clearToolSelection, setToolSelectedFileIds, - }; }; diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index eb4908856..276bed35b 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback} from "react"; +import React, { useState, useCallback, useEffect} from "react"; import { useTranslation } from 'react-i18next'; import { useFileContext } from "../contexts/FileContext"; import { useToolManagement } from "../hooks/useToolManagement"; @@ -15,9 +15,12 @@ import Viewer from "../components/viewer/Viewer"; import FileUploadSelector from "../components/shared/FileUploadSelector"; import ToolRenderer from "../components/tools/ToolRenderer"; import QuickAccessBar from "../components/shared/QuickAccessBar"; -import { useMultipleEndpointsEnabled } from "../hooks/useEndpointConfig"; -export default function HomePage() { +interface HomePageProps { + openedFile?: File | null; +} + +export default function HomePage({ openedFile }: HomePageProps) { const { t } = useTranslation(); const { isRainbowMode } = useRainbowThemeContext(); @@ -25,6 +28,7 @@ export default function HomePage() { const fileContext = useFileContext(); const { activeFiles, currentView, currentMode, setCurrentView, addFiles } = fileContext; + const { selectedToolKey, selectedTool, @@ -77,6 +81,26 @@ export default function HomePage() { } }, [activeFiles, addFiles]); + // Handle file opened with app (Tauri mode) + useEffect(() => { + if (openedFile) { + const loadOpenedFile = async () => { + try { + // Add to active files if not already present + await addToActiveFiles(openedFile); + + // Switch to viewer mode to show the opened file + setCurrentView('viewer'); + setReaderMode(true); + } catch (error) { + console.error('Failed to load opened file:', error); + } + }; + + loadOpenedFile(); + } + }, [openedFile]); + return ( diff --git a/frontend/src/services/fileOpenService.ts b/frontend/src/services/fileOpenService.ts new file mode 100644 index 000000000..3280ea028 --- /dev/null +++ b/frontend/src/services/fileOpenService.ts @@ -0,0 +1,148 @@ +import { invoke } from '@tauri-apps/api/core'; + +export interface FileOpenService { + getOpenedFile(): Promise; + readFileAsArrayBuffer(filePath: string): Promise<{ fileName: string; arrayBuffer: ArrayBuffer } | null>; + clearOpenedFile(): Promise; + onFileOpened(callback: (filePath: string) => void): () => void; // Returns unlisten function +} + +class TauriFileOpenService implements FileOpenService { + async getOpenedFile(): Promise { + try { + console.log('🔍 Calling invoke(get_opened_file)...'); + const result = await invoke('get_opened_file'); + console.log('🔍 invoke(get_opened_file) returned:', result); + return result; + } catch (error) { + console.error('❌ Failed to get opened file:', error); + return null; + } + } + + async readFileAsArrayBuffer(filePath: string): Promise<{ fileName: string; arrayBuffer: ArrayBuffer } | null> { + try { + const { readFile } = await import('@tauri-apps/plugin-fs'); + + const fileData = await readFile(filePath); + const fileName = filePath.split(/[\\/]/).pop() || 'opened-file.pdf'; + + return { + fileName, + arrayBuffer: fileData.buffer.slice(fileData.byteOffset, fileData.byteOffset + fileData.byteLength) + }; + } catch (error) { + console.error('Failed to read file:', error); + return null; + } + } + + async clearOpenedFile(): Promise { + try { + console.log('🔍 Calling invoke(clear_opened_file)...'); + await invoke('clear_opened_file'); + console.log('✅ Successfully cleared opened file'); + } catch (error) { + console.error('❌ Failed to clear opened file:', error); + } + } + + onFileOpened(callback: (filePath: string) => void): () => void { + let cleanup: (() => void) | null = null; + let isCleanedUp = false; + + const setupEventListeners = async () => { + try { + // Check if already cleaned up before async setup completes + if (isCleanedUp) { + return; + } + + // Only import if in Tauri environment + if (typeof window !== 'undefined' && ('__TAURI__' in window || '__TAURI_INTERNALS__' in window)) { + const { listen } = await import('@tauri-apps/api/event'); + + // Check again after async import + if (isCleanedUp) { + return; + } + + // Listen for macOS native file open events + const unlistenMacOS = await listen('macos://open-file', (event) => { + console.log('📂 macOS native file open event:', event.payload); + callback(event.payload as string); + }); + + // Listen for fallback file open events + const unlistenFallback = await listen('file-opened', (event) => { + console.log('📂 Fallback file open event:', event.payload); + callback(event.payload as string); + }); + + // Set up cleanup function only if not already cleaned up + if (!isCleanedUp) { + cleanup = () => { + try { + unlistenMacOS(); + unlistenFallback(); + console.log('✅ File event listeners cleaned up'); + } catch (error) { + console.error('❌ Error during file event cleanup:', error); + } + }; + } else { + // Clean up immediately if cleanup was called during setup + try { + unlistenMacOS(); + unlistenFallback(); + } catch (error) { + console.error('❌ Error during immediate cleanup:', error); + } + } + } + } catch (error) { + console.error('❌ Failed to setup file event listeners:', error); + } + }; + + setupEventListeners(); + + // Return cleanup function + return () => { + isCleanedUp = true; + if (cleanup) { + cleanup(); + } + }; + } +} + +class WebFileOpenService implements FileOpenService { + async getOpenedFile(): Promise { + // In web mode, there's no file association support + return null; + } + + async readFileAsArrayBuffer(filePath: string): Promise<{ fileName: string; arrayBuffer: ArrayBuffer } | null> { + // In web mode, cannot read arbitrary file paths + return null; + } + + async clearOpenedFile(): Promise { + // In web mode, no file clearing needed + } + + onFileOpened(callback: (filePath: string) => void): () => void { + // In web mode, no file events - return no-op cleanup function + console.log('ℹ️ Web mode: File event listeners not supported'); + return () => { + // No-op cleanup for web mode + }; + } +} + +// Export the appropriate service based on environment +export const fileOpenService: FileOpenService = + typeof window !== 'undefined' && ('__TAURI__' in window || '__TAURI_INTERNALS__' in window) + ? new TauriFileOpenService() + : new WebFileOpenService(); \ No newline at end of file diff --git a/frontend/src/services/tauriBackendService.ts b/frontend/src/services/tauriBackendService.ts new file mode 100644 index 000000000..79383c300 --- /dev/null +++ b/frontend/src/services/tauriBackendService.ts @@ -0,0 +1,57 @@ +import { invoke } from '@tauri-apps/api/core'; + +export class TauriBackendService { + private static instance: TauriBackendService; + private backendStarted = false; + + static getInstance(): TauriBackendService { + if (!TauriBackendService.instance) { + TauriBackendService.instance = new TauriBackendService(); + } + return TauriBackendService.instance; + } + + async startBackend(): Promise { + if (this.backendStarted) { + return; + } + + try { + const result = await invoke('start_backend'); + console.log('Backend started:', result); + this.backendStarted = true; + + // Wait for backend to be healthy + await this.waitForHealthy(); + } catch (error) { + console.error('Failed to start backend:', error); + throw error; + } + } + + async checkHealth(): Promise { + if (!this.backendStarted) { + return false; + } + try { + return await invoke('check_backend_health'); + } catch (error) { + console.error('Health check failed:', error); + return false; + } + } + + private async waitForHealthy(maxAttempts = 60): Promise { + for (let i = 0; i < maxAttempts; i++) { + const isHealthy = await this.checkHealth(); + if (isHealthy) { + console.log('Backend is healthy'); + return; + } + await new Promise(resolve => setTimeout(resolve, 1000)); + } + throw new Error('Backend failed to become healthy after 60 seconds'); + } +} + +export const tauriBackendService = TauriBackendService.getInstance(); \ No newline at end of file diff --git a/frontend/src/tools/Compress.tsx b/frontend/src/tools/Compress.tsx index b06945610..90a568457 100644 --- a/frontend/src/tools/Compress.tsx +++ b/frontend/src/tools/Compress.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useMemo } from "react"; import { Button, Stack, Text } from "@mantine/core"; import { useTranslation } from "react-i18next"; import DownloadIcon from "@mui/icons-material/Download"; -import { useEndpointEnabled } from "../hooks/useEndpointConfig"; +import { useEndpointEnabledWithHealthCheck } from "../hooks/useEndpointConfig"; import { useFileContext } from "../contexts/FileContext"; import ToolStep, { ToolStepContainer } from "../components/tools/shared/ToolStep"; @@ -16,6 +16,7 @@ import CompressSettings from "../components/tools/compress/CompressSettings"; import { useCompressParameters } from "../hooks/tools/compress/useCompressParameters"; import { useCompressOperation } from "../hooks/tools/compress/useCompressOperation"; + interface CompressProps { selectedFiles?: File[]; onPreviewFile?: (file: File | null) => void; @@ -29,7 +30,7 @@ const Compress = ({ selectedFiles = [], onPreviewFile }: CompressProps) => { const compressOperation = useCompressOperation(); // Endpoint validation - const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("compress-pdf"); + const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabledWithHealthCheck("compress-pdf"); useEffect(() => { compressOperation.resetResults(); diff --git a/frontend/src/tools/Merge.tsx b/frontend/src/tools/Merge.tsx index 582a3d6ec..3f94b6c26 100644 --- a/frontend/src/tools/Merge.tsx +++ b/frontend/src/tools/Merge.tsx @@ -3,7 +3,8 @@ import { Paper, Button, Checkbox, Stack, Text, Group, Loader, Alert } from "@man import { useTranslation } from "react-i18next"; import { FileWithUrl } from "../types/file"; import { fileStorage } from "../services/fileStorage"; -import { useEndpointEnabled } from "../hooks/useEndpointConfig"; +import { makeApiUrl } from "../utils/api"; +import { useEndpointEnabledWithHealthCheck } from "../hooks/useEndpointConfig"; export interface MergePdfPanelProps { files: FileWithUrl[]; @@ -26,7 +27,7 @@ const MergePdfPanel: React.FC = ({ const [downloadUrl, setLocalDownloadUrl] = useState(null); const [isLoading, setIsLoading] = useState(false); const [errorMessage, setErrorMessage] = useState(null); - const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("merge-pdfs"); + const { enabled: endpointEnabled, loading: endpointLoading, backendHealthy } = useEndpointEnabledWithHealthCheck("merge-pdfs"); useEffect(() => { setSelectedFiles(files.map(() => true)); @@ -61,7 +62,7 @@ const MergePdfPanel: React.FC = ({ setErrorMessage(null); try { - const response = await fetch("/api/v1/general/merge-pdfs", { + const response = await fetch(makeApiUrl("/api/v1/general/merge-pdfs"), { method: "POST", body: formData, }); diff --git a/frontend/src/utils/api.ts b/frontend/src/utils/api.ts new file mode 100644 index 000000000..c901e495b --- /dev/null +++ b/frontend/src/utils/api.ts @@ -0,0 +1,22 @@ +// Runtime configuration access +declare global { + interface Window { + runtimeConfig?: { + apiBaseUrl?: string; + }; + } +} + +export const makeApiUrl = (endpoint: string): string => { + + //const baseUrl = window.runtimeConfig?.apiBaseUrl || 'http://localhost:8080'; + + if (typeof window !== 'undefined' && (window.__TAURI__ || window.__TAURI_INTERNALS__)) { + // If running in Tauri, use the Tauri API base URL + const tauriApiBaseUrl = 'http://localhost:8080'; + return `${tauriApiBaseUrl}${endpoint}`; + } + + + return endpoint; +}; \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index d957fa3f0..fc93dbc18 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -4,6 +4,14 @@ import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], server: { + // make sure this port matches the devUrl port in tauri.conf.json file + port: 5173, + // Tauri expects a fixed port, fail if that port is not available + strictPort: true, + watch: { + // tell vite to ignore watching `src-tauri` + ignored: ['**/src-tauri/**'], + }, proxy: { '/api': { target: 'http://localhost:8080', @@ -12,4 +20,4 @@ export default defineConfig({ }, }, }, -}); +}); \ No newline at end of file diff --git a/scripts/build-tauri-jlink.bat b/scripts/build-tauri-jlink.bat new file mode 100644 index 000000000..d3a73f058 --- /dev/null +++ b/scripts/build-tauri-jlink.bat @@ -0,0 +1,134 @@ +@echo off +REM Build script for Tauri with JLink runtime bundling +REM This script creates a self-contained Java runtime for Stirling-PDF + +echo 🔧 Building Stirling-PDF with JLink runtime for Tauri... + +echo ▶ Checking Java environment... +java -version >nul 2>&1 +if errorlevel 1 ( + echo ❌ Java is not installed or not in PATH + exit /b 1 +) + +jlink --version >nul 2>&1 +if errorlevel 1 ( + echo ❌ jlink is not available. Please ensure you have a JDK ^(not just JRE^) installed. + exit /b 1 +) + +echo ✅ Java and jlink detected + +echo ▶ Building Stirling-PDF JAR... +call gradlew.bat clean bootJar --no-daemon +if errorlevel 1 ( + echo ❌ Failed to build Stirling-PDF JAR + exit /b 1 +) + +REM Find the built JAR(s) +echo ▶ Listing all built JAR files in app\core\build\libs: +dir /b app\core\build\libs\stirling-pdf-*.jar +for %%f in (app\core\build\libs\stirling-pdf-*.jar) do set STIRLING_JAR=%%f +if not exist "%STIRLING_JAR%" ( + echo ❌ No Stirling-PDF JAR found in build/libs/ + exit /b 1 +) + +echo ✅ Built JAR: %STIRLING_JAR% + +echo ▶ Creating Tauri directories... +if not exist "frontend\src-tauri\libs" mkdir "frontend\src-tauri\libs" +if not exist "frontend\src-tauri\runtime" mkdir "frontend\src-tauri\runtime" + +echo ▶ Copying JAR to Tauri libs directory... +copy "%STIRLING_JAR%" "frontend\src-tauri\libs\" +echo ✅ JAR copied to frontend\src-tauri\libs\ + +REM Log out all JAR files now in the Tauri libs directory +echo ▶ Listing all JAR files in frontend\src-tauri\libs after copy: +dir /b frontend\src-tauri\libs\stirling-pdf-*.jar + +echo ▶ Creating custom JRE with jlink... +if exist "frontend\src-tauri\runtime\jre" rmdir /s /q "frontend\src-tauri\runtime\jre" + +REM Use predefined module list for Windows (jdeps may not be available) +set MODULES=java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported + +echo ▶ Creating JLink runtime with modules: %MODULES% + +jlink ^ + --add-modules %MODULES% ^ + --strip-debug ^ + --compress=2 ^ + --no-header-files ^ + --no-man-pages ^ + --output "frontend\src-tauri\runtime\jre" + +if not exist "frontend\src-tauri\runtime\jre" ( + echo ❌ Failed to create JLink runtime + exit /b 1 +) + +echo ✅ JLink runtime created at frontend\src-tauri\runtime\jre + +echo ▶ Creating launcher scripts for testing... + +REM Create Windows launcher script +echo @echo off > "frontend\src-tauri\runtime\launch-stirling.bat" +echo REM Launcher script for Stirling-PDF with bundled JRE >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo. >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo set SCRIPT_DIR=%%~dp0 >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo set JRE_DIR=%%SCRIPT_DIR%%jre >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo set LIBS_DIR=%%SCRIPT_DIR%%..\libs >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo. >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo REM Find the Stirling-PDF JAR >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo for %%%%f in ("%%LIBS_DIR%%\Stirling-PDF-*.jar") do set STIRLING_JAR=%%%%f >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo. >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo if not exist "%%STIRLING_JAR%%" ^( >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo echo ❌ Stirling-PDF JAR not found in %%LIBS_DIR%% >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo exit /b 1 >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo ^) >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo. >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo REM Launch with bundled JRE >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo "%%JRE_DIR%%\bin\java.exe" ^^ >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo -Xmx2g ^^ >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo -DBROWSER_OPEN=true ^^ >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo -DSTIRLING_PDF_DESKTOP_UI=false ^^ >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo -jar "%%STIRLING_JAR%%" ^^ >> "frontend\src-tauri\runtime\launch-stirling.bat" +echo %%* >> "frontend\src-tauri\runtime\launch-stirling.bat" + +echo ✅ Created launcher scripts for testing + +echo ▶ Testing bundled JRE... +"frontend\src-tauri\runtime\jre\bin\java.exe" --version >nul 2>&1 +if errorlevel 1 ( + echo ❌ Bundled JRE test failed + exit /b 1 +) else ( + echo ✅ Bundled JRE works correctly +) + +echo. +echo ✅ 🎉 JLink build setup completed successfully! +echo. +echo 📊 Summary: +echo • JAR: %STIRLING_JAR% +echo • Runtime: frontend\src-tauri\runtime\jre +echo • Modules: %MODULES% +echo. +echo 📋 Next steps: +echo 1. cd frontend +echo 2. npm run tauri-build +echo. +echo 💡 Testing: +echo • Test bundled runtime: frontend\src-tauri\runtime\launch-stirling.bat +echo • Tauri configuration already updated to include bundled JRE +echo. +echo 💡 Benefits: +echo • No external JRE dependency +echo • Smaller distribution size with custom runtime +echo • Better security with minimal required modules +echo • Consistent Java version across all deployments +echo. +echo ✅ The application will now run without requiring users to install Java! \ No newline at end of file diff --git a/scripts/build-tauri-jlink.sh b/scripts/build-tauri-jlink.sh new file mode 100644 index 000000000..5b576d091 --- /dev/null +++ b/scripts/build-tauri-jlink.sh @@ -0,0 +1,230 @@ +#!/bin/bash + +# Build script for Tauri with JLink runtime bundling +# This script creates a self-contained Java runtime for Stirling-PDF + +set -e + +echo "🔧 Building Stirling-PDF with JLink runtime for Tauri..." + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_step() { + echo -e "${BLUE}▶ $1${NC}" +} + +print_success() { + echo -e "${GREEN}✅ $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +print_error() { + echo -e "${RED}❌ $1${NC}" +} + +# Check if Java is installed and version +print_step "Checking Java environment..." +if ! command -v java &> /dev/null; then + print_error "Java is not installed or not in PATH" + exit 1 +fi + +if ! command -v jlink &> /dev/null; then + print_error "jlink is not available. Please ensure you have a JDK (not just JRE) installed." + exit 1 +fi + +JAVA_VERSION=$(java -version 2>&1 | head -n 1 | cut -d'"' -f2 | cut -d'.' -f1) +if [ "$JAVA_VERSION" -lt 17 ]; then + print_error "Java 17 or higher is required. Found Java $JAVA_VERSION" + exit 1 +fi + +print_success "Java $JAVA_VERSION detected with jlink support" + +# Check if jpackage is available (Java 14+) +if command -v jpackage &> /dev/null; then + print_success "jpackage is available for native packaging" +else + print_warning "jpackage is not available - using jlink only" +fi + +# Clean and build the Stirling-PDF JAR +print_step "Building Stirling-PDF JAR..." +./gradlew clean bootJar --no-daemon + +if [ ! -f "app\core\build\libs\stirling-pdf-*.jar"*.jar ]; then + print_error "Failed to build stirling-pdf JAR" + exit 1 +fi + +# Find the built JAR +STIRLING_JAR=$(ls app\core\build\libs\stirling-pdf-*.jar | head -n 1) +print_success "Built JAR: $STIRLING_JAR" + +# Create directories for Tauri +TAURI_SRC_DIR="frontend/src-tauri" +TAURI_LIBS_DIR="$TAURI_SRC_DIR/libs" +TAURI_RUNTIME_DIR="$TAURI_SRC_DIR/runtime" + +print_step "Creating Tauri directories..." +mkdir -p "$TAURI_LIBS_DIR" +mkdir -p "$TAURI_RUNTIME_DIR" + +# Copy the JAR to Tauri libs directory +print_step "Copying JAR to Tauri libs directory..." +cp "$STIRLING_JAR" "$TAURI_LIBS_DIR/" +print_success "JAR copied to $TAURI_LIBS_DIR/" + +# Create a custom JRE using jlink +print_step "Creating custom JRE with jlink..." + +# Determine modules needed by analyzing the JAR +print_step "Analyzing JAR dependencies..." + +# Use jdeps to analyze module dependencies if available +if command -v jdeps &> /dev/null; then + print_step "Running jdeps analysis..." + REQUIRED_MODULES=$(jdeps --print-module-deps --ignore-missing-deps "$STIRLING_JAR" 2>/dev/null || echo "") + if [ -n "$REQUIRED_MODULES" ]; then + print_success "jdeps detected modules: $REQUIRED_MODULES" + # Add additional modules we know Stirling-PDF needs + MODULES="$REQUIRED_MODULES,java.compiler,java.instrument,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" + else + print_warning "jdeps analysis failed, using predefined module list" + MODULES="java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" + fi +else + print_warning "jdeps not available, using predefined module list" + MODULES="java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" +fi + +print_step "Creating JLink runtime with modules: $MODULES" + +# Remove existing runtime if present +rm -rf "$TAURI_RUNTIME_DIR/jre" + +# Create the custom JRE +jlink \ + --add-modules "$MODULES" \ + --strip-debug \ + --compress=2 \ + --no-header-files \ + --no-man-pages \ + --output "$TAURI_RUNTIME_DIR/jre" + +if [ ! -d "$TAURI_RUNTIME_DIR/jre" ]; then + print_error "Failed to create JLink runtime" + exit 1 +fi + +print_success "JLink runtime created at $TAURI_RUNTIME_DIR/jre" + +# Calculate runtime size +RUNTIME_SIZE=$(du -sh "$TAURI_RUNTIME_DIR/jre" | cut -f1) +print_success "Runtime size: $RUNTIME_SIZE" + +# Create launcher scripts for testing +print_step "Creating launcher scripts for testing..." + +LAUNCHER_SCRIPT="$TAURI_RUNTIME_DIR/launch-stirling.sh" +cat > "$LAUNCHER_SCRIPT" << 'EOF' +#!/bin/bash +# Launcher script for Stirling-PDF with bundled JRE + +# Get the directory of this script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +JRE_DIR="$SCRIPT_DIR/jre" +LIBS_DIR="$(dirname "$SCRIPT_DIR")/libs" + +# Find the Stirling-PDF JAR +STIRLING_JAR=$(ls "$LIBS_DIR"/Stirling-PDF-*.jar | head -n 1) + +if [ ! -f "$STIRLING_JAR" ]; then + echo "❌ Stirling-PDF JAR not found in $LIBS_DIR" + exit 1 +fi + +# Launch with bundled JRE +"$JRE_DIR/bin/java" \ + -Xmx2g \ + -DBROWSER_OPEN=true \ + -DSTIRLING_PDF_DESKTOP_UI=false \ + -jar "$STIRLING_JAR" \ + "$@" +EOF + +chmod +x "$LAUNCHER_SCRIPT" + +# Create Windows launcher +LAUNCHER_BAT="$TAURI_RUNTIME_DIR/launch-stirling.bat" +cat > "$LAUNCHER_BAT" << 'EOF' +@echo off +REM Launcher script for Stirling-PDF with bundled JRE + +set SCRIPT_DIR=%~dp0 +set JRE_DIR=%SCRIPT_DIR%jre +set LIBS_DIR=%SCRIPT_DIR%..\libs + +REM Find the Stirling-PDF JAR +for %%f in ("%LIBS_DIR%\Stirling-PDF-*.jar") do set STIRLING_JAR=%%f + +if not exist "%STIRLING_JAR%" ( + echo ❌ Stirling-PDF JAR not found in %LIBS_DIR% + exit /b 1 +) + +REM Launch with bundled JRE +"%JRE_DIR%\bin\java.exe" ^ + -Xmx2g ^ + -DBROWSER_OPEN=true ^ + -DSTIRLING_PDF_DESKTOP_UI=false ^ + -jar "%STIRLING_JAR%" ^ + %* +EOF + +print_success "Created launcher scripts for testing" + +# Test the bundled runtime +print_step "Testing bundled JRE..." +if [ -f "$TAURI_RUNTIME_DIR/jre/bin/java" ]; then + JAVA_VERSION_OUTPUT=$("$TAURI_RUNTIME_DIR/jre/bin/java" --version 2>&1 | head -n 1) + print_success "Bundled JRE works: $JAVA_VERSION_OUTPUT" +else + print_error "Bundled JRE executable not found" + exit 1 +fi + +# Display summary +echo "" +print_success "🎉 JLink build setup completed successfully!" +echo "" +echo -e "${BLUE}📊 Summary:${NC}" +echo " • JAR: $STIRLING_JAR" +echo " • Runtime: $TAURI_RUNTIME_DIR/jre ($RUNTIME_SIZE)" +echo " • Modules: $MODULES" +echo "" +echo -e "${BLUE}📋 Next steps:${NC}" +echo " 1. cd frontend" +echo " 2. npm run tauri-build" +echo "" +echo -e "${BLUE}💡 Testing:${NC}" +echo " • Test bundled runtime: $LAUNCHER_SCRIPT" +echo " • Tauri configuration already updated to include bundled JRE" +echo "" +echo -e "${BLUE}💡 Benefits:${NC}" +echo " • No external JRE dependency" +echo " • Smaller distribution size with custom runtime" +echo " • Better security with minimal required modules" +echo " • Consistent Java version across all deployments" +echo "" +print_success "The application will now run without requiring users to install Java!" \ No newline at end of file