@@ -108,10 +108,10 @@ mod volumes_linux;
108108mod stubs;
109109
110110use menu:: {
111- CLOSE_TAB_ID , CommandScope , MenuState , SHOW_HIDDEN_FILES_ID , SORT_ASCENDING_ID , SORT_BY_CREATED_ID ,
112- SORT_BY_EXTENSION_ID , SORT_BY_MODIFIED_ID , SORT_BY_NAME_ID , SORT_BY_SIZE_ID , SORT_DESCENDING_ID , TAB_CLOSE_ID ,
113- TAB_CLOSE_OTHERS_ID , TAB_PIN_ID , VIEW_MODE_BRIEF_ID , VIEW_MODE_FULL_ID , VIEWER_WORD_WRAP_ID , ViewMode ,
114- menu_id_to_command,
111+ CLOSE_TAB_ID , CommandScope , EDIT_COPY_ID , EDIT_CUT_ID , EDIT_PASTE_ID , MenuState , SHOW_HIDDEN_FILES_ID ,
112+ SORT_ASCENDING_ID , SORT_BY_CREATED_ID , SORT_BY_EXTENSION_ID , SORT_BY_MODIFIED_ID , SORT_BY_NAME_ID ,
113+ SORT_BY_SIZE_ID , SORT_DESCENDING_ID , TAB_CLOSE_ID , TAB_CLOSE_OTHERS_ID , TAB_PIN_ID , VIEW_MODE_BRIEF_ID ,
114+ VIEW_MODE_FULL_ID , VIEWER_WORD_WRAP_ID , ViewMode , menu_id_to_command,
115115} ;
116116use tauri:: { Emitter , Manager } ;
117117
@@ -121,6 +121,39 @@ fn greet(name: &str) -> String {
121121 format ! ( "Hello, {}! You've been greeted from Rust!" , name)
122122}
123123
124+ /// Sends a native clipboard action (copy:/cut:/paste:) through the responder chain.
125+ ///
126+ /// Used when a non-main window is focused: the custom Edit menu items can't use the native
127+ /// responder chain like PredefinedMenuItems do, so we replicate it manually via
128+ /// `NSApplication.sendAction:to:from:` with nil target (routes to the first responder).
129+ #[ cfg( target_os = "macos" ) ]
130+ fn send_native_clipboard_action ( menu_id : & str ) {
131+ use objc2:: sel;
132+ use objc2_app_kit:: NSApplication ;
133+
134+ let selector = match menu_id {
135+ EDIT_CUT_ID => sel ! ( cut: ) ,
136+ EDIT_COPY_ID => sel ! ( copy: ) ,
137+ EDIT_PASTE_ID => sel ! ( paste: ) ,
138+ _ => return ,
139+ } ;
140+
141+ // Safety: we're on the main thread (called from on_menu_event which runs on the main thread).
142+ let mtm = unsafe { objc2:: MainThreadMarker :: new_unchecked ( ) } ;
143+ let ns_app = NSApplication :: sharedApplication ( mtm) ;
144+
145+ // sendAction:to:from: with nil `to` sends to the first responder, exactly like
146+ // PredefinedMenuItems do internally. This lets WKWebView handle text clipboard natively.
147+ unsafe {
148+ let _: bool = objc2:: msg_send![
149+ & ns_app,
150+ sendAction: selector,
151+ to: std:: ptr:: null:: <objc2:: runtime:: AnyObject >( ) ,
152+ from: std:: ptr:: null:: <objc2:: runtime:: AnyObject >( ) ,
153+ ] ;
154+ }
155+ }
156+
124157#[ cfg_attr( mobile, tauri:: mobile_entry_point) ]
125158pub fn run ( ) {
126159 let builder = tauri:: Builder :: default ( ) ;
@@ -452,6 +485,34 @@ pub fn run() {
452485 return ;
453486 }
454487
488+ // === Clipboard exception: file clipboard in main window, native text clipboard elsewhere ===
489+ // Custom MenuItems for Cut/Copy/Paste route through execute-command in the main window
490+ // so the frontend can decide between file and text clipboard. In non-main windows
491+ // (viewer, settings), we send the native action through the responder chain so
492+ // WKWebView handles text clipboard natively — just like PredefinedMenuItems would.
493+ if id == EDIT_CUT_ID || id == EDIT_COPY_ID || id == EDIT_PASTE_ID {
494+ let main_focused = app
495+ . get_webview_window ( "main" )
496+ . is_some_and ( |w| w. is_focused ( ) . unwrap_or ( false ) ) ;
497+ if main_focused {
498+ let command_id = match id {
499+ EDIT_CUT_ID => "edit.cut" ,
500+ EDIT_COPY_ID => "edit.copy" ,
501+ _ => "edit.paste" ,
502+ } ;
503+ let _ = app. emit_to (
504+ "main" ,
505+ "execute-command" ,
506+ serde_json:: json!( { "commandId" : command_id } ) ,
507+ ) ;
508+ } else {
509+ // Send native clipboard action to the first responder chain
510+ #[ cfg( target_os = "macos" ) ]
511+ send_native_clipboard_action ( id) ;
512+ }
513+ return ;
514+ }
515+
455516 // === Unified dispatch: look up command ID from the mapping ===
456517 if let Some ( ( command_id, scope) ) = menu_id_to_command ( id) {
457518 if scope == CommandScope :: FileScoped {
@@ -782,6 +843,7 @@ pub fn run() {
782843 commands:: clipboard:: copy_files_to_clipboard,
783844 commands:: clipboard:: cut_files_to_clipboard,
784845 commands:: clipboard:: read_clipboard_files,
846+ commands:: clipboard:: read_clipboard_text,
785847 commands:: clipboard:: clear_clipboard_cut_state,
786848 ] )
787849 . on_window_event ( |window, event| {
0 commit comments