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/tauri-build.yml b/.github/workflows/tauri-build.yml
new file mode 100644
index 000000000..0ea6ce236
--- /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]
+ paths:
+ - 'frontend/src-tauri/**'
+ - 'frontend/src/**'
+ - 'frontend/package.json'
+ - 'frontend/package-lock.json'
+ - '.github/workflows/tauri-build.yml'
+ push:
+ branches: [main, V2, "infra/tauri-actions"]
+ 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/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/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/WebMvcConfig.java b/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java
index 31047d209..458441522 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
@@ -35,7 +35,7 @@ public class WebMvcConfig implements WebMvcConfigurer {
@ConditionalOnProperty(name = "STIRLING_PDF_TAURI_MODE", havingValue = "true")
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
- .allowedOrigins("http://localhost:5173", "http://tauri.localhost")
+ .allowedOrigins("http://localhost:5173", "http://tauri.localhost", "tauri://localhost")
.allowedMethods("*")
.allowedHeaders("*");
}
diff --git a/frontend/src-tauri/Cargo.lock b/frontend/src-tauri/Cargo.lock
index 32a032e70..7c63b0d41 100644
--- a/frontend/src-tauri/Cargo.lock
+++ b/frontend/src-tauri/Cargo.lock
@@ -94,7 +94,10 @@ checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
name = "app"
version = "0.1.0"
dependencies = [
+ "cocoa",
"log",
+ "objc",
+ "once_cell",
"reqwest 0.11.27",
"serde",
"serde_json",
@@ -195,6 +198,12 @@ dependencies = [
"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"
@@ -454,6 +463,36 @@ dependencies = [
"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"
@@ -506,6 +545,19 @@ 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"
@@ -514,11 +566,22 @@ checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
dependencies = [
"bitflags 2.9.1",
"core-foundation 0.10.1",
- "core-graphics-types",
+ "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"
@@ -1978,6 +2041,15 @@ 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"
@@ -2165,6 +2237,15 @@ 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"
@@ -3509,7 +3590,7 @@ checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08"
dependencies = [
"bytemuck",
"cfg_aliases",
- "core-graphics",
+ "core-graphics 0.24.0",
"foreign-types 0.5.0",
"js-sys",
"log",
@@ -3687,7 +3768,7 @@ checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82"
dependencies = [
"bitflags 2.9.1",
"core-foundation 0.10.1",
- "core-graphics",
+ "core-graphics 0.24.0",
"crossbeam-channel",
"dispatch",
"dlopen2",
diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml
index c09d1455b..17a2bd30e 100644
--- a/frontend/src-tauri/Cargo.toml
+++ b/frontend/src-tauri/Cargo.toml
@@ -1,8 +1,8 @@
[package]
-name = "app"
+name = "stirling-pdf"
version = "0.1.0"
-description = "A Tauri App"
-authors = ["you"]
+description = "Stirling-PDF Desktop Application"
+authors = ["Stirling-PDF Contributors"]
license = ""
repository = ""
edition = "2021"
@@ -27,3 +27,9 @@ 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/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/32x32.png b/frontend/src-tauri/icons/32x32.png
index f44f0c371..9e8dd8a5d 100644
Binary files a/frontend/src-tauri/icons/32x32.png 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/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
index 7b281937e..983df8577 100644
Binary files a/frontend/src-tauri/icons/icon.icns 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
index 8ad57cac7..b058a5591 100644
Binary files a/frontend/src-tauri/icons/icon.ico 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
index 5edc6eae2..5819d1b89 100644
Binary files a/frontend/src-tauri/icons/icon.png 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/src/commands/backend.rs b/frontend/src-tauri/src/commands/backend.rs
index 0149ccd78..c465f7cc8 100644
--- a/frontend/src-tauri/src/commands/backend.rs
+++ b/frontend/src-tauri/src/commands/backend.rs
@@ -112,13 +112,37 @@ fn normalize_path(path: &PathBuf) -> PathBuf {
// 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> {
- // Configure logging to write outside src-tauri to prevent dev server restarts
- let temp_dir = std::env::temp_dir();
- let log_dir = temp_dir.join("stirling-pdf-logs");
- std::fs::create_dir_all(&log_dir).ok(); // Create log directory if it doesn't exist
+ // 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")
+ };
- // Define all Java options in an array
+ // 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",
@@ -132,18 +156,49 @@ fn run_stirling_pdf_jar(app: &tauri::AppHandle, java_path: &PathBuf, jar_path: &
// Log the equivalent command for external testing
let java_command = format!(
- "TAURI_PARENT_PID={} && \"{}\" {}",
+ "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)
- .env("TAURI_PARENT_PID", std::process::id().to_string());
+ .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());
diff --git a/frontend/src-tauri/src/commands/files.rs b/frontend/src-tauri/src/commands/files.rs
index e77917e1f..7c397cfcf 100644
--- a/frontend/src-tauri/src/commands/files.rs
+++ b/frontend/src-tauri/src/commands/files.rs
@@ -1,15 +1,35 @@
use crate::utils::add_log;
+use std::sync::Mutex;
+
+// Store the opened file path globally
+static OPENED_FILE: Mutex