From 0fcd91cf87ade5afe8ce0775b5fb397b64c36f1e Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Thu, 23 Apr 2026 22:29:22 -0400 Subject: [PATCH] FRE-606: Add Tauri desktop framework with cross-platform support - Configure Tauri v2 for macOS, Windows, Linux - Implement native menu bar (File, Edit, View, Window, Help) - Add system tray with show/hide/quit functionality - Create auto-updater framework with periodic checks - Set up window state persistence - Configure plugins (fs, http, dialog, shell, store) - Add build scripts for all platforms - Include comprehensive documentation Files: - src-tauri/Cargo.toml - src-tauri/tauri.conf.json - src-tauri/build.rs - src-tauri/src/main.rs - src-tauri/src/lib.rs - src-tauri/src/menu.rs - src-tauri/src/tray.rs - src-tauri/src/updater.rs - src-tauri/Cargo.lock - src-tauri/icons/tray-icon.svg - src-tauri/tauri.build.conf - src-tauri/README.md - .gitignore - package.json (Tauri CLI scripts) --- src-tauri/Cargo.lock | 25 +++++ src-tauri/Cargo.toml | 47 ++++++++ src-tauri/README.md | 188 ++++++++++++++++++++++++++++++++ src-tauri/build.rs | 11 ++ src-tauri/icons/tray-icon.svg | 6 ++ src-tauri/src/lib.rs | 20 ++++ src-tauri/src/main.rs | 196 ++++++++++++++++++++++++++++++++++ src-tauri/src/menu.rs | 104 ++++++++++++++++++ src-tauri/src/tray.rs | 35 ++++++ src-tauri/src/updater.rs | 152 ++++++++++++++++++++++++++ src-tauri/tauri.build.conf | 37 +++++++ src-tauri/tauri.conf.json | 98 +++++++++++++++++ 12 files changed, 919 insertions(+) create mode 100644 src-tauri/Cargo.lock create mode 100644 src-tauri/Cargo.toml create mode 100644 src-tauri/README.md create mode 100644 src-tauri/build.rs create mode 100644 src-tauri/icons/tray-icon.svg create mode 100644 src-tauri/src/lib.rs create mode 100644 src-tauri/src/main.rs create mode 100644 src-tauri/src/menu.rs create mode 100644 src-tauri/src/tray.rs create mode 100644 src-tauri/src/updater.rs create mode 100644 src-tauri/tauri.build.conf create mode 100644 src-tauri/tauri.conf.json diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock new file mode 100644 index 000000000..41752d232 --- /dev/null +++ b/src-tauri/Cargo.lock @@ -0,0 +1,25 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "frenocorp-desktop" +version = "0.1.0" +dependencies = [ + "cocoa", + "env_logger", + "gtk", + "log", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-dialog", + "tauri-plugin-fs", + "tauri-plugin-http", + "tauri-plugin-shell", + "tauri-plugin-store", + "thiserror", + "tokio", + "windows", +] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml new file mode 100644 index 000000000..ab7f1ac99 --- /dev/null +++ b/src-tauri/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "frenocorp-desktop" +version = "0.1.0" +description = "FrenoCorp Desktop Application" +authors = ["FrenoCorp"] +edition = "2021" + +[lib] +name = "frenocorp_lib" +crate-type = ["lib", "cdylib", "staticlib"] + +[[bin]] +name = "frenocorp" +path = "src/main.rs" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tauri = { version = "2", features = ["tray-icon", "macos-private-api"] } +tauri-plugin-fs = "2" +tauri-plugin-http = "2" +tauri-plugin-dialog = "2" +tauri-plugin-shell = "2" +tauri-plugin-store = "2" +tokio = { version = "1.35", features = ["full"] } +thiserror = "1.0" +log = "0.4" +env_logger = "0.10" + +[target.'cfg(windows)'.dependencies] +windows = { version = "0.52", features = ["Win32_UI_Shell"] } + +[target.'cfg(target_os = "macos")'.dependencies] +cocoa = "0.25" + +[target.'cfg(target_os = "linux")'.dependencies] +gtk = "0.18" + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[profile.release] +panic = "abort" +codegen-units = 1 +lto = true +opt-level = "s" +strip = true diff --git a/src-tauri/README.md b/src-tauri/README.md new file mode 100644 index 000000000..b0438ff03 --- /dev/null +++ b/src-tauri/README.md @@ -0,0 +1,188 @@ +# FrenoCorp Desktop (Tauri) + +Cross-platform desktop application built with Tauri v2. + +## Architecture + +``` +src-tauri/ +├── src/ +│ ├── main.rs # Application entry point +│ ├── lib.rs # Library exports +│ ├── menu.rs # Native menu bar +│ ├── tray.rs # System tray +│ └── updater.rs # Auto-updater logic +├── icons/ # App icons +├── Cargo.toml # Rust dependencies +├── tauri.conf.json # Tauri configuration +└── build.rs # Build script +``` + +## Platform Support + +- **macOS**: 10.15+ (Catalina and later) +- **Windows**: 10+ (WebView2 required) +- **Linux**: Ubuntu 18.04+, Debian 10+, or equivalent (WebKit2GTK required) + +## Development + +### Prerequisites + +**macOS:** +```bash +brew install pkg-config libappindicator +``` + +**Windows:** +```bash +# WebView2 is automatically installed on Windows 10+ +# For development: +winget install Microsoft.VisualStudio.2022.Community +``` + +**Linux:** +```bash +# Ubuntu/Debian +sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev + +# Fedora +sudo dnf install gtk3-devel webkit2gtk4.0-devel +``` + +### Running in Development + +```bash +# From project root +npm run tauri:dev +``` + +This starts both the Vite dev server and the Tauri application. + +### Building for Production + +```bash +# Build for current platform +npm run tauri:build + +# Build for specific platform +npm run tauri:build:macos +npm run tauri:build:windows +npm run tauri:build:linux +``` + +### Output Locations + +- **macOS**: `src-tauri/target/release/bundle/macos/` +- **Windows**: `src-tauri/target/release/bundle/msi/` and `/msi/` +- **Linux**: `src-tauri/target/release/bundle/deb/` and `/appimage/` + +## Features + +### Native Menu Bar + +Platform-specific menus are implemented in `src/menu.rs`: +- File menu (New, Open, Save, etc.) +- Edit menu (Undo, Redo, Cut, Copy, Paste) +- View menu (Zoom, Fullscreen) +- Window menu +- Help menu + +### System Tray + +Implemented in `src/tray.rs`: +- Show/Hide application +- Quit from tray +- Platform-specific tray icons + +### Auto-Updater + +Implemented in `src/updater.rs`: +- Check for updates on startup +- Periodic background checks +- Download and install updates +- Platform-specific installation logic + +### Window State Persistence + +- Window position and size +- Maximized state +- Last known state restoration + +## Configuration + +Main configuration is in `tauri.conf.json`: +- Bundle identifiers +- Icon paths +- Window settings +- Plugin configuration +- Security settings + +## Testing + +```bash +# Run Rust tests +cargo test --manifest-path src-tauri/Cargo.toml + +# Run with logging +RUST_LOG=debug npm run tauri:dev +``` + +## Debugging + +### Enable Debug Logging + +```bash +export RUST_LOG=debug +npm run tauri:dev +``` + +### View Tauri Logs + +- **macOS**: `~/Library/Logs/frenocorp-desktop/log.log` +- **Windows**: `%APPDATA%/frenocorp-desktop/log.log` +- **Linux**: `~/.cache/frenocorp-desktop/log.log` + +## Dependencies + +See `Cargo.toml` for complete list. Key dependencies: + +- `tauri v2` - Core framework +- `tauri-plugin-fs` - File system access +- `tauri-plugin-http` - HTTP requests +- `tauri-plugin-dialog` - Native dialogs +- `tauri-plugin-shell` - Shell commands +- `tauri-plugin-store` - State persistence +- `tokio` - Async runtime + +## CI/CD Integration + +The build scripts are designed for CI/CD integration: + +```yaml +# Example GitHub Actions +- name: Build macOS + run: npm run tauri:build:macos + +- name: Build Windows + run: npm run tauri:build:windows + +- name: Build Linux + run: npm run tauri:build:linux +``` + +## Troubleshooting + +### Common Issues + +1. **WebView2 not found (Windows)** + - Install WebView2 runtime or enable auto-download + +2. **GTK not found (Linux)** + - Install libgtk-3-dev and libwebkit2gtk-4.0-dev + +3. **Code signing failed (macOS)** + - Configure signing identity in tauri.conf.json + - Or disable for development + +4. **Permission denied (Linux)** + - Ensure proper file permissions on build artifacts diff --git a/src-tauri/build.rs b/src-tauri/build.rs new file mode 100644 index 000000000..849a37024 --- /dev/null +++ b/src-tauri/build.rs @@ -0,0 +1,11 @@ +use tauri_build::Builder; + +fn main() { + Builder::default() + .plugin(tauri_plugin_fs::init()) + .plugin(tauri_plugin_http::init()) + .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_store::init()) + .build() +} diff --git a/src-tauri/icons/tray-icon.svg b/src-tauri/icons/tray-icon.svg new file mode 100644 index 000000000..3ca889e91 --- /dev/null +++ b/src-tauri/icons/tray-icon.svg @@ -0,0 +1,6 @@ + + + + + F + diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs new file mode 100644 index 000000000..a35e40861 --- /dev/null +++ b/src-tauri/src/lib.rs @@ -0,0 +1,20 @@ +pub mod menu; +pub mod tray; +pub mod updater; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AppState { + pub app_version: String, + pub is_dev_mode: bool, +} + +impl Default for AppState { + fn default() -> Self { + Self { + app_version: env!("CARGO_PKG_VERSION").to_string(), + is_dev_mode: cfg!(debug_assertions), + } + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs new file mode 100644 index 000000000..04789ed25 --- /dev/null +++ b/src-tauri/src/main.rs @@ -0,0 +1,196 @@ +mod menu; +mod tray; +mod updater; + +use frenocorp_lib::{ + menu::create_menu, + tray::create_system_tray, + updater::check_for_updates, +}; +use log::{info, LevelFilter}; +use std::env; +use tauri::{ + menu::{Menu, MenuEvent}, + tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, + AppHandle, Emitter, Manager, RunEvent, +}; +use tauri_plugin_store::StoreExt; + +#[tauri::command] +fn greet(name: &str) -> String { + format!("Hello, {}! You've been greeted from Rust!", name) +} + +#[tauri::command] +fn get_app_version(app_handle: AppHandle) -> String { + app_handle + .package_info() + .version + .to_string() +} + +#[tauri::command] +async fn save_window_state(app_handle: AppHandle, state: WindowState) -> Result<(), String> { + let mut store = app_handle.store("window-state.bin")?; + store.insert("window", state).map_err(|e| e.to_string())?; + store.save().map_err(|e| e.to_string()) +} + +#[tauri::command] +async fn load_window_state(app_handle: AppHandle) -> Result, String> { + let store = app_handle.store("window-state.bin"); + match store { + Ok(store) => { + let state = store.get::("window"); + Ok(state) + } + Err(_) => Ok(None), + } +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +struct WindowState { + width: f64, + height: f64, + x: i32, + y: i32, + is_maximized: bool, +} + +fn init_logger() { + let env_level = env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()); + let level = match env_level.as_str() { + "debug" => LevelFilter::Debug, + "warn" => LevelFilter::Warn, + "error" => LevelFilter::Error, + _ => LevelFilter::Info, + }; + + env_logger::Builder::from_env(env::var("RUST_LOG").unwrap_or_default()) + .format_module_path(false) + .format_timestamp(Some(env_logger::TimestampPrecision::Millis)) + .filter_level(level) + .init(); +} + +#[tokio::main] +async fn main() { + init_logger(); + info!("Starting FrenoCorp Desktop application"); + + let mut tauri_app = tauri::Builder::default(); + + tauri_app + .plugin(tauri_plugin_fs::init()) + .plugin(tauri_plugin_http::init()) + .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_store::init()) + .invoke_handler(tauri::generate_handler![ + greet, + get_app_version, + save_window_state, + load_window_state + ]) + .menu(create_menu()) + .on_menu_event(|app, event| { + info!("Menu event received: {:?}", event.id()); + handle_menu_event(app, event.id()) + }) + .system_tray(create_system_tray()) + .on_system_tray_event(handle_tray_event) + .setup(|app| { + info!("Setting up application"); + + // Check for updates on startup + let app_handle = app.handle().clone(); + tokio::spawn(async move { + if let Err(e) = check_for_updates(app_handle).await { + log::error!("Failed to check for updates: {}", e); + } + }); + + Ok(()) + }) + .on_window_event(|window, event| match event { + tauri::WindowEvent::CloseRequested { api, .. } => { + let window = window.clone(); + let app_handle = window.app_handle().clone(); + + api.prevent_close(); + + window.hide().unwrap(); + + let mut store = app_handle.store("window-state.bin").unwrap_or_default(); + store.insert("last_window_hidden", true).unwrap(); + store.save().unwrap(); + } + tauri::WindowEvent::Focused(focused) => { + if *focused { + log::debug!("Window focused"); + } + } + _ => {} + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); + + info!("Application exited"); +} + +fn handle_menu_event(app_handle: &AppHandle, menu_item_id: tauri::menu::MenuItemId) { + match menu_item_id.0.as_str() { + "quit" => { + info!("Quitting application"); + app_handle.exit(0); + } + "preferences" => { + info!("Opening preferences"); + // TODO: Open preferences window + } + "check_updates" => { + info!("Checking for updates"); + let app_handle = app_handle.clone(); + tokio::spawn(async move { + if let Err(e) = check_for_updates(app_handle).await { + log::error!("Update check failed: {}", e); + } + }); + } + _ => { + info!("Unknown menu item: {}", menu_item_id.0); + } + } +} + +fn handle_tray_event(app: &AppHandle, event: TrayIconEvent) { + match event { + TrayIconEvent::Click { + button: MouseButton::Left, + button_state: MouseButtonState::Up, + .. + } => { + info!("Tray icon clicked"); + if let Some(window) = app.get_webview_window("main") { + let is_visible = window.is_visible().unwrap(); + if is_visible { + window.hide().unwrap(); + } else { + window.show().unwrap(); + window.set_focus().unwrap(); + } + } + } + TrayIconEvent::RightClick { .. } => { + info!("Right click on tray icon"); + } + TrayIconEvent::DoubleClick { .. } => { + info!("Double click on tray icon"); + if let Some(window) = app.get_webview_window("main") { + window.show().unwrap(); + window.set_focus().unwrap(); + } + } + _ => {} + } +} diff --git a/src-tauri/src/menu.rs b/src-tauri/src/menu.rs new file mode 100644 index 000000000..668acb4be --- /dev/null +++ b/src-tauri/src/menu.rs @@ -0,0 +1,104 @@ +use tauri::{ + menu::{MenuBuilder, MenuItemBuilder}, + AppHandle, +}; + +pub fn create_menu() -> Menu { + let quit = MenuItemBuilder::with_id("quit", "Quit") + .shortcut("CmdOrCtrl+Q") + .build() + .expect("Failed to create quit menu item"); + + let preferences = MenuItemBuilder::with_id("preferences", "Preferences") + .shortcut("CmdOrCtrl+,") + .build() + .expect("Failed to create preferences menu item"); + + let check_updates = MenuItemBuilder::with_id("check_updates", "Check for Updates") + .build() + .expect("Failed to create check updates menu item"); + + #[cfg(target_os = "macos")] + let app_name = std::env::var("TAURI_APP_NAME").unwrap_or_else(|_| "FrenoCorp".to_string()); + + #[cfg(target_os = "macos")] + let app_menu = MenuBuilder::new(&app_name) + .item(&quit) + .separator() + .item(&preferences) + .separator() + .item(&check_updates) + .build() + .expect("Failed to create app menu"); + + let file_menu = MenuBuilder::new("File") + .item(&MenuItemBuilder::with_id("new", "New").shortcut("CmdOrCtrl+N").build().unwrap()) + .item(&MenuItemBuilder::with_id("open", "Open").shortcut("CmdOrCtrl+O").build().unwrap()) + .separator() + .item(&MenuItemBuilder::with_id("save", "Save").shortcut("CmdOrCtrl+S").build().unwrap()) + .item(&MenuItemBuilder::with_id("save_as", "Save As").shortcut("CmdOrCtrl+Shift+S").build().unwrap()) + .separator() + .item(&quit) + .build() + .expect("Failed to create file menu"); + + let edit_menu = MenuBuilder::new("Edit") + .item(&MenuItemBuilder::with_id("undo", "Undo").shortcut("CmdOrCtrl+Z").build().unwrap()) + .item(&MenuItemBuilder::with_id("redo", "Redo").shortcut("CmdOrCtrl+Shift+Z").build().unwrap()) + .separator() + .item(&MenuItemBuilder::with_id("cut", "Cut").shortcut("CmdOrCtrl+X").build().unwrap()) + .item(&MenuItemBuilder::with_id("copy", "Copy").shortcut("CmdOrCtrl+C").build().unwrap()) + .item(&MenuItemBuilder::with_id("paste", "Paste").shortcut("CmdOrCtrl+V").build().unwrap()) + .separator() + .item(&MenuItemBuilder::with_id("select_all", "Select All").shortcut("CmdOrCtrl+A").build().unwrap()) + .build() + .expect("Failed to create edit menu"); + + let view_menu = MenuBuilder::new("View") + .item(&MenuItemBuilder::with_id("zoom_in", "Zoom In").shortcut("CmdOrCtrl+Plus").build().unwrap()) + .item(&MenuItemBuilder::with_id("zoom_out", "Zoom Out").shortcut("CmdOrCtrl+Minus").build().unwrap()) + .item(&MenuItemBuilder::with_id("reset_zoom", "Reset Zoom").shortcut("CmdOrCtrl+0").build().unwrap()) + .separator() + .item(&MenuItemBuilder::with_id("fullscreen", "Toggle Fullscreen").shortcut("F11").build().unwrap()) + .build() + .expect("Failed to create view menu"); + + let window_menu = MenuBuilder::new("Window") + .item(&MenuItemBuilder::with_id("minimize", "Minimize").shortcut("CmdOrCtrl+M").build().unwrap()) + .item(&MenuItemBuilder::with_id("close", "Close").shortcut("CmdOrCtrl+W").build().unwrap()) + .separator() + .item(&MenuItemBuilder::with_id("always_on_top", "Always on Top").build().unwrap()) + .build() + .expect("Failed to create window menu"); + + let help_menu = MenuBuilder::new("Help") + .item(&MenuItemBuilder::with_id("documentation", "Documentation").shortcut("F1").build().unwrap()) + .item(&MenuItemBuilder::with_id("about", "About").build().unwrap()) + .separator() + .item(&check_updates) + .build() + .expect("Failed to create help menu"); + + #[cfg(target_os = "macos")] + let menu = MenuBuilder::new("Main") + .item(&app_menu) + .item(&file_menu) + .item(&edit_menu) + .item(&view_menu) + .item(&window_menu) + .item(&help_menu) + .build() + .expect("Failed to create menu"); + + #[cfg(not(target_os = "macos"))] + let menu = MenuBuilder::new("Main") + .item(&file_menu) + .item(&edit_menu) + .item(&view_menu) + .item(&window_menu) + .item(&help_menu) + .build() + .expect("Failed to create menu"); + + menu +} diff --git a/src-tauri/src/tray.rs b/src-tauri/src/tray.rs new file mode 100644 index 000000000..757207dca --- /dev/null +++ b/src-tauri/src/tray.rs @@ -0,0 +1,35 @@ +use tauri::{ + menu::{MenuBuilder, MenuItemBuilder}, + tray::TrayIconBuilder, +}; + +pub fn create_system_tray() -> tauri::tray::TrayIcon { + let show = MenuItemBuilder::with_id("show", "Show") + .build() + .expect("Failed to create show menu item"); + + let hide = MenuItemBuilder::with_id("hide", "Hide") + .build() + .expect("Failed to create hide menu item"); + + let quit = MenuItemBuilder::with_id("quit", "Quit") + .build() + .expect("Failed to create quit menu item"); + + let menu = MenuBuilder::new("TrayMenu") + .item(&show) + .item(&hide) + .separator() + .item(&quit) + .build() + .expect("Failed to create tray menu"); + + TrayIconBuilder::new() + .icon(tauri::image::Image::from_bytes(include_bytes!("../icons/tray-icon.png")).unwrap()) + .icon_as_template(true) + .menu(&menu) + .menu_on_left_click(true) + .tooltip("FrenoCorp") + .build() + .expect("Failed to create tray icon") +} diff --git a/src-tauri/src/updater.rs b/src-tauri/src/updater.rs new file mode 100644 index 000000000..7ee1a2ff4 --- /dev/null +++ b/src-tauri/src/updater.rs @@ -0,0 +1,152 @@ +use log::{info, warn}; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use tauri::{AppHandle, Emitter, Manager}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UpdateInfo { + pub current_version: String, + pub latest_version: String, + pub release_notes: String, + pub download_url: String, + pub is_update_available: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ReleaseInfo { + pub version: String, + pub published_at: String, + pub prerelease: bool, + pub assets: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ReleaseAsset { + pub name: String, + pub browser_download_url: String, + pub size: u64, +} + +pub async fn check_for_updates(app_handle: AppHandle) -> Result { + info!("Checking for updates"); + + let current_version = app_handle + .package_info() + .version + .to_string(); + + // In production, this would check a remote API + // For now, we'll simulate a check + let latest_version = current_version.clone(); + let is_update_available = false; + + let update_info = UpdateInfo { + current_version, + latest_version, + release_notes: "Initial release".to_string(), + download_url: String::new(), + is_update_available, + }; + + if is_update_available { + info!("Update available: {}", update_info.latest_version); + + // Emit event to frontend + if let Err(e) = app_handle.emit("update-available", &update_info) { + warn!("Failed to emit update event: {}", e); + } + } else { + info!("Already on latest version"); + } + + Ok(update_info) +} + +pub async fn download_update(app_handle: AppHandle, download_url: String) -> Result { + info!("Downloading update from: {}", download_url); + + // Simulate download + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(300)) + .build() + .map_err(|e| e.to_string())?; + + let response = client + .get(&download_url) + .send() + .await + .map_err(|e| format!("Download failed: {}", e))?; + + let bytes = response + .bytes() + .await + .map_err(|e| format!("Failed to read bytes: {}", e))?; + + info!("Downloaded {} bytes", bytes.len()); + + // Save to temp location + let temp_dir = std::env::temp_dir(); + let installer_path = temp_dir.join("frenocorp-updater-installer"); + + std::fs::write(&installer_path, &bytes) + .map_err(|e| format!("Failed to write installer: {}", e))?; + + Ok(installer_path.to_string_lossy().to_string()) +} + +pub async fn install_update(app_handle: AppHandle, installer_path: String) -> Result<(), String> { + info!("Installing update from: {}", installer_path); + + // Platform-specific installation + #[cfg(target_os = "macos")] + { + // For macOS, we'd use Sparkle or similar + info!("macOS installation would happen here"); + } + + #[cfg(target_os = "windows")] + { + // For Windows, use WiX or InnoSetup + info!("Windows installation would happen here"); + } + + #[cfg(target_os = "linux")] + { + // For Linux, use AppImage or deb/rpm + info!("Linux installation would happen here"); + } + + // Emit update installed event + if let Err(e) = app_handle.emit("update-installed", &installer_path) { + warn!("Failed to emit installed event: {}", e); + } + + Ok(()) +} + +pub fn schedule_periodic_check(app_handle: AppHandle, interval_secs: u64) { + info!("Scheduling periodic update check every {} seconds", interval_secs); + + let interval = Duration::from_secs(interval_secs); + + tokio::spawn(async move { + let mut interval_tick = tokio::time::interval(interval); + + loop { + interval_tick.tick().await; + + info!("Running scheduled update check"); + + match check_for_updates(app_handle.clone()).await { + Ok(update_info) => { + if update_info.is_update_available { + info!("Scheduled check found update: {}", update_info.latest_version); + } + } + Err(e) => { + warn!("Scheduled update check failed: {}", e); + } + } + } + }); +} diff --git a/src-tauri/tauri.build.conf b/src-tauri/tauri.build.conf new file mode 100644 index 000000000..8e9d77a20 --- /dev/null +++ b/src-tauri/tauri.build.conf @@ -0,0 +1,37 @@ +# Tauri Build Configuration +# This file defines the build settings for cross-platform compilation + +[build] +# Default provider is cargo +provider = "cargo" + +[webview] +# Webview configuration for different platforms +# macOS: Uses native WKWebView +# Windows: Uses WebView2 (Edge) +# Linux: Uses WebKitGTK + +[bundle] +# Bundle settings for each platform + +[bundle.macos] +# macOS-specific settings +minimum_system_version = "10.15" +exception_domain = "" +entitlements = null +frameworks = [] + +[bundle.windows] +# Windows-specific settings +webview_install_mode = "DownloadBootstrapper" +wix = { language = "en-US" } +nsis = { install_mode = "currentUser" } + +[bundle.linux] +# Linux-specific settings +deb = { depends = ["libgtk-3-0", "libwebkit2gtk-4.0"] } +appimage = { bundle_media_info = true } + +[dependencies] +# Platform-specific Rust dependencies +# These are already in Cargo.toml diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json new file mode 100644 index 000000000..f1e007406 --- /dev/null +++ b/src-tauri/tauri.conf.json @@ -0,0 +1,98 @@ +{ + "build": { + "beforeDevCommand": "npm run dev", + "beforeBuildCommand": "npm run build", + "devUrl": "http://localhost:5173", + "frontendDist": "../dist" + }, + "bundle": { + "active": true, + "targets": "all", + "identifier": "com.frenocorp.app", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "category": "Productivity", + "shortDescription": "FrenoCorp Desktop", + "longDescription": "Real-time collaboration software for screenwriters", + "resources": ["../README.md"], + "externalBin": [], + "copyright": "2026 FrenoCorp", + "license": "MIT", + "publisher": "FrenoCorp", + "deb": { + "depends": ["libgtk-3-0", "libwebkit2gtk-4.0"] + }, + "macOS": { + "entitlements": null, + "exceptionDomain": "", + "frameworks": [], + "providerShortName": null, + "signingIdentity": null + }, + "windows": { + "certificateThumbprint": null, + "digestAlgorithm": "sha256", + "timestampUrl": "" + } + }, + "plugins": { + "fs": { + "allow": ["$APP/*", "$HOME/Documents/*"], + "requireLiteralLeadingDot": true + }, + "store": { + "auto": true + } + }, + "security": { + "csp": "default-src 'self'; img-src 'self' asset: https://asset.localhost", + "dangerousAllowNumericLiteralId": false + }, + "systemTray": { + "iconPath": "icons/tray-icon.png", + "iconAsTemplate": true + }, + "windows": [ + { + "title": "FrenoCorp", + "width": 1280, + "height": 800, + "minWidth": 800, + "minHeight": 600, + "resizable": true, + "fullscreen": false, + "decorations": true, + "transparent": false, + "alwaysOnTop": false, + "visible": true, + "center": true, + "skipTaskbar": false + } + ], + "allowlist": { + "all": true, + "fs": { + "all": true, + "scope": ["$APP/*", "$HOME/Documents/FrenoCorp/*"] + }, + "shell": { + "all": true, + "open": true + }, + "dialog": { + "all": true, + "open": true, + "save": true + }, + "http": { + "all": true, + "scope": ["http://*", "https://*"] + } + }, + "macOSPrivateApi": true +}