//! ThoughtSync desktop (Tauri v2). //! //! The window loads the shared Vue 3 frontend (`../../frontend`). The Rust core will //! own the on-device SQLite store (M10.4) and the opt-in sync engine (M10.7), which //! the frontend reaches through the `frontend/src/adapters/` seam (M10.3) over Tauri //! `invoke`. Today it exposes desktop integration (menu-entry install for the Linux //! AppImage) and boots the UI. mod integration; mod local; // `pub` (unlike the modules above) because parts of it have no in-crate caller yet — // the engine that will consume them is M10.7b/c, and a private module's unreachable // items read as dead code. pub mod sync; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { use tauri_plugin_log::{Target, TargetKind}; #[cfg(target_os = "linux")] harden_linux_webkit_rendering(); tauri::Builder::default() // Logging first, so startup diagnostics (and any setup error) are captured to // stdout AND a persistent file from the very beginning — the basis for // troubleshooting portability across environments. .plugin( tauri_plugin_log::Builder::new() .level(log::LevelFilter::Info) .targets([ Target::new(TargetKind::Stdout), Target::new(TargetKind::LogDir { file_name: None }), ]) .build(), ) .setup(|app| { use tauri::Manager; log_environment(app); // The on-device store lives in the platform app-data dir (e.g. Linux // ~/.local/share/com.fabledsword.thoughtsync/thoughtsync.db), created on // first launch. This is what makes the app work with no server or login. let dir = app.path().app_data_dir()?; std::fs::create_dir_all(&dir)?; let db_path = dir.join("thoughtsync.db"); log::info!("opening local store: {}", db_path.display()); let db = local::open(&db_path)?; log::info!("local store ready — {}", local::summary(&db)); app.manage(db); // Attachment bytes live beside the database, filed by content hash, so a // synced image is readable with no network (M10.7d). let blobs = sync::blobs::BlobStore::new(dir.join("blobs"))?; log::info!("attachment store ready: {}", blobs.root().display()); app.manage(blobs); Ok(()) }) .invoke_handler(tauri::generate_handler![ log_event, integration::integration_status, integration::integrate_desktop, integration::unintegrate_desktop, local::commands::config_get, local::commands::auth_me, local::commands::notes_list, local::commands::notes_get, local::commands::notes_create, local::commands::notes_create_titled, local::commands::notes_update, local::commands::notes_complete_reminder, local::commands::notes_snooze_reminder, local::commands::notes_set_labels, local::commands::notes_add_item, local::commands::notes_update_item, local::commands::notes_delete_item, local::commands::notes_delete_attachment, local::commands::notes_delete_preview, local::commands::notes_reorder, local::commands::notes_trash, local::commands::notes_restore, local::commands::notes_delete_forever, local::commands::notes_revisions, local::commands::notes_restore_revision, local::commands::notes_reminders, local::commands::notes_titles, local::commands::notes_search, local::commands::notes_backlinks, local::commands::notes_link_search, local::commands::labels_list, local::commands::labels_create, local::commands::labels_rename, local::commands::labels_set_color, local::commands::labels_remove, local::commands::labels_merge, local::commands::saved_filters_list, local::commands::saved_filters_create, local::commands::saved_filters_remove, local::commands::saved_filters_rename, sync::commands::sync_probe, sync::commands::sync_link, sync::commands::sync_unlink, sync::commands::sync_status, sync::commands::sync_now, sync::commands::sync_has_pending, ]) .run(tauri::generate_context!()) .expect("error while running the ThoughtSync desktop app"); } /// Frontend logging bridge: routes boot milestones and errors from the webview into /// the same stdout + file log as the Rust side (see frontend/src/desktop/bridge.ts). #[tauri::command] fn log_event(level: String, message: String) { match level.as_str() { "error" => log::error!(target: "frontend", "{message}"), "warn" => log::warn!(target: "frontend", "{message}"), "debug" => log::debug!(target: "frontend", "{message}"), _ => log::info!(target: "frontend", "{message}"), } } /// Log the app version and the environment that determines whether the window /// renders — the first thing to check when a build works on one machine but not /// another. fn log_environment(app: &tauri::App) { use tauri::Manager; log::info!( "ThoughtSync desktop v{} starting ({} {})", env!("CARGO_PKG_VERSION"), std::env::consts::OS, std::env::consts::ARCH, ); match app.path().app_log_dir() { Ok(d) => log::info!("log directory: {}", d.display()), Err(e) => log::warn!("could not resolve log dir: {e}"), } #[cfg(target_os = "linux")] log_linux_graphics_env(); } /// The Linux display + graphics stack, and the WebKit render-hardening vars actually /// in effect (set by harden_linux_webkit_rendering, which runs before the logger, so /// we report the resulting environment rather than logging from inside it). #[cfg(target_os = "linux")] fn log_linux_graphics_env() { let v = |k: &str| std::env::var(k).unwrap_or_else(|_| "(unset)".to_string()); log::info!( "display: session_type={} desktop={} wayland={} x11={} gdk_backend={}", v("XDG_SESSION_TYPE"), v("XDG_CURRENT_DESKTOP"), v("WAYLAND_DISPLAY"), v("DISPLAY"), v("GDK_BACKEND"), ); log::info!( "webkit hardening: dmabuf_disabled={} compositing_disabled={} nv_explicit_sync_disabled={}", v("WEBKIT_DISABLE_DMABUF_RENDERER"), v("WEBKIT_DISABLE_COMPOSITING_MODE"), v("__NV_DISABLE_EXPLICIT_SYNC"), ); } /// WebKitGTK's GPU-accelerated rendering (the DMA-BUF renderer + EGL compositing) fails /// to initialize on a wide range of Linux GPU/driver/Wayland setups — "Could not create /// default EGL display: EGL_BAD_PARAMETER" → a black/blank window. This is a well-known /// WebKitGTK issue that hits Tauri apps broadly, NOT app-specific. Tauri's guidance /// (https://v2.tauri.app/develop/debug/linux-graphics/) is to force the software /// fallbacks at startup, before the webview is created, so end users don't have to. /// /// Applied on all Linux launches (this UI doesn't need GPU compositing, and the failure /// spans AppImage, native, and dev builds), each var left overridable so a user can /// re-enable acceleration by exporting it themselves before launch. #[cfg(target_os = "linux")] fn harden_linux_webkit_rendering() { // Ordered per Tauri's escalation ladder; each set only if the user hasn't chosen. for (key, value) in [ ("__NV_DISABLE_EXPLICIT_SYNC", "1"), ("WEBKIT_DISABLE_DMABUF_RENDERER", "1"), ("WEBKIT_DISABLE_COMPOSITING_MODE", "1"), ] { if std::env::var_os(key).is_none() { std::env::set_var(key, value); } } }