@@ -4,7 +4,9 @@ use std::env;
44use std:: path:: { Path , PathBuf } ;
55use std:: sync:: { Arc , Mutex } ;
66use notify:: { Watcher , RecommendedWatcher , RecursiveMode , Event , EventKind } ;
7- use tauri:: { AppHandle , Emitter } ;
7+ use tauri:: { AppHandle , Emitter , Manager } ;
8+ #[ cfg( any( target_os = "macos" , target_os = "ios" ) ) ]
9+ use tauri:: RunEvent ;
810use syntect:: parsing:: SyntaxSet ;
911use syntect:: highlighting:: ThemeSet ;
1012use syntect:: html:: highlighted_html_for_string;
@@ -20,6 +22,29 @@ const MAX_HTML_SIZE: usize = 100 * 1024 * 1024; // 100MB HTML limit for temp fil
2022// Global state for file watcher
2123type WatcherState = Arc < Mutex < Option < RecommendedWatcher > > > ;
2224
25+ // App state to store file opened via "Open With" on macOS
26+ #[ derive( Default ) ]
27+ struct OpenedFileState {
28+ file_path : Arc < Mutex < Option < String > > > ,
29+ }
30+
31+ impl OpenedFileState {
32+ fn set_file ( & self , path : String ) {
33+ let mut file_path = self . file_path . lock ( ) . unwrap ( ) ;
34+ * file_path = Some ( path) ;
35+ }
36+
37+ fn get_file ( & self ) -> Option < String > {
38+ let file_path = self . file_path . lock ( ) . unwrap ( ) ;
39+ file_path. clone ( )
40+ }
41+
42+ fn clear_file ( & self ) {
43+ let mut file_path = self . file_path . lock ( ) . unwrap ( ) ;
44+ * file_path = None ;
45+ }
46+ }
47+
2348// Security utilities for temp file handling
2449fn create_secure_temp_file ( content : & str ) -> Result < PathBuf , String > {
2550 // Basic size validation only
@@ -386,6 +411,16 @@ fn get_launch_args() -> Vec<String> {
386411 env:: args ( ) . collect ( )
387412}
388413
414+ #[ tauri:: command]
415+ fn get_opened_file ( state : tauri:: State < OpenedFileState > ) -> Option < String > {
416+ let file = state. get_file ( ) ;
417+ if file. is_some ( ) {
418+ // Clear the file after retrieving it so it's only opened once
419+ state. clear_file ( ) ;
420+ }
421+ file
422+ }
423+
389424#[ tauri:: command]
390425fn export_html ( content : String , title : String ) -> Result < String , String > {
391426 let html_template = format ! ( r#"<!DOCTYPE html>
@@ -664,23 +699,81 @@ async fn save_temp_html_and_open(html_content: String) -> Result<(), String> {
664699#[ cfg_attr( mobile, tauri:: mobile_entry_point) ]
665700pub fn run ( ) {
666701 let watcher_state: WatcherState = Arc :: new ( Mutex :: new ( None ) ) ;
702+ let opened_file_state = OpenedFileState :: default ( ) ;
667703
668704 tauri:: Builder :: default ( )
669705 . plugin ( tauri_plugin_opener:: init ( ) )
670706 . plugin ( tauri_plugin_dialog:: init ( ) )
671707 . plugin ( tauri_plugin_fs:: init ( ) )
672708 . manage ( watcher_state)
709+ . manage ( opened_file_state)
673710 . invoke_handler ( tauri:: generate_handler![
674711 greet,
675712 parse_markdown,
676713 read_markdown_file,
677714 get_launch_args,
715+ get_opened_file,
678716 start_watching_file,
679717 stop_watching_file,
680718 export_html,
681719 read_file_content,
682720 save_temp_html_and_open
683721 ] )
684- . run ( tauri:: generate_context!( ) )
685- . expect ( "error while running tauri application" ) ;
722+ . setup ( |app| {
723+ // Check command line args during setup (fallback for other platforms)
724+ let setup_args = env:: args ( ) . collect :: < Vec < String > > ( ) ;
725+
726+ // Check for markdown files in args
727+ for arg in setup_args. iter ( ) . skip ( 1 ) {
728+ if arg. ends_with ( ".md" ) || arg. ends_with ( ".markdown" ) ||
729+ arg. ends_with ( ".mdown" ) || arg. ends_with ( ".mkd" ) {
730+ // For command line arguments, store in the opened file state
731+ let opened_file_state = app. state :: < OpenedFileState > ( ) ;
732+ opened_file_state. set_file ( arg. clone ( ) ) ;
733+ break ;
734+ }
735+ }
736+
737+ Ok ( ( ) )
738+ } )
739+ . build ( tauri:: generate_context!( ) )
740+ . expect ( "error while building tauri application" )
741+ . run ( |_app_handle, event| {
742+ match event {
743+ // RunEvent::Opened is only available on macOS and iOS
744+ #[ cfg( any( target_os = "macos" , target_os = "ios" ) ) ]
745+ RunEvent :: Opened { urls } => {
746+ let app_handle = _app_handle;
747+ // Find the first markdown file in the opened URLs
748+ for url in urls {
749+ // Convert URL to string and handle file:// URLs
750+ let url_str = url. as_str ( ) ;
751+ let file_path = if url_str. starts_with ( "file://" ) {
752+ url_str. trim_start_matches ( "file://" ) . to_string ( )
753+ } else {
754+ url_str. to_string ( )
755+ } ;
756+
757+ if file_path. ends_with ( ".md" ) || file_path. ends_with ( ".markdown" ) ||
758+ file_path. ends_with ( ".mdown" ) || file_path. ends_with ( ".mkd" ) {
759+ // Validate the file path for security
760+ if let Ok ( validated_path) = validate_file_path ( & file_path) {
761+ let validated_str = validated_path. to_string_lossy ( ) . to_string ( ) ;
762+
763+ // Store the opened file in app state
764+ let opened_file_state = app_handle. state :: < OpenedFileState > ( ) ;
765+ opened_file_state. set_file ( validated_str. clone ( ) ) ;
766+
767+ // Also try to emit the event to the frontend if it's ready
768+ let _ = app_handle. emit ( "file-opened-via-os" , & validated_str) ;
769+ }
770+ break ;
771+ }
772+ }
773+ }
774+ _ => {
775+ // Handle other events as needed
776+ }
777+ }
778+ } ) ;
686779}
0 commit comments