feat(macOS): add open file global menu for macOS, closes #2692 (#2708)

This commit is contained in:
Huang Xin
2025-12-14 00:36:25 +08:00
committed by GitHub
parent c1530cc5c4
commit 524de92f5e
4 changed files with 74 additions and 7 deletions
+2 -2
View File
@@ -136,7 +136,7 @@ fn get_executable_dir() -> String {
#[derive(Clone, serde::Serialize)]
#[allow(dead_code)]
struct Payload {
struct SingleInstancePayload {
args: Vec<String>,
cwd: String,
}
@@ -179,7 +179,7 @@ pub fn run() {
if !files.is_empty() {
allow_file_in_scopes(app, files.clone());
}
app.emit("single-instance", Payload { args: argv, cwd })
app.emit("single-instance", SingleInstancePayload { args: argv, cwd })
.unwrap();
}));
+52 -2
View File
@@ -1,8 +1,17 @@
use crate::allow_file_in_scopes;
use std::path::PathBuf;
use tauri::menu::MenuEvent;
use tauri::menu::{SubmenuBuilder, HELP_SUBMENU_ID};
use tauri::menu::{MenuItemBuilder, SubmenuBuilder, HELP_SUBMENU_ID};
use tauri::AppHandle;
use tauri::Emitter;
use tauri_plugin_opener::OpenerExt;
#[derive(Clone, serde::Serialize)]
#[allow(dead_code)]
struct OpenFilesPayload {
files: Vec<String>,
}
pub fn setup_macos_menu(app: &AppHandle) -> tauri::Result<()> {
let global_menu = app.menu().unwrap();
@@ -10,6 +19,23 @@ pub fn setup_macos_menu(app: &AppHandle) -> tauri::Result<()> {
global_menu.remove(&item)?;
}
let open_item = MenuItemBuilder::new("Open...")
.id("open_file")
.accelerator("Cmd+O")
.build(app)?;
if let Some(file_menu) = global_menu.items()?.iter().find(|item| {
if let Some(submenu) = item.as_submenu() {
submenu.text().ok().as_deref() == Some("File")
} else {
false
}
}) {
if let Some(file_submenu) = file_menu.as_submenu() {
file_submenu.insert(&open_item, 0)?;
}
}
global_menu.append(
&SubmenuBuilder::new(app, "Help")
.text("privacy_policy", "Privacy Policy")
@@ -28,7 +54,9 @@ pub fn setup_macos_menu(app: &AppHandle) -> tauri::Result<()> {
pub fn handle_menu_event(app: &AppHandle, event: &MenuEvent) {
let opener = app.opener();
if event.id() == "privacy_policy" {
if event.id() == "open_file" {
handle_open_file(app);
} else if event.id() == "privacy_policy" {
let _ = opener.open_url("https://readest.com/privacy-policy", None::<&str>);
} else if event.id() == "report_issue" {
let _ = opener.open_url("https://github.com/readest/readest/issues", None::<&str>);
@@ -36,3 +64,25 @@ pub fn handle_menu_event(app: &AppHandle, event: &MenuEvent) {
let _ = opener.open_url("https://readest.com/support", None::<&str>);
}
}
fn handle_open_file(app: &AppHandle) {
use tauri_plugin_dialog::DialogExt;
let app_handle = app.clone();
app.dialog()
.file()
.add_filter(
"Files",
&["epub", "pdf", "mobi", "azw", "azw3", "fb2", "cbz", "txt"],
)
.pick_file(move |file_path| {
if let Some(path) = file_path {
let payload = OpenFilesPayload {
files: vec![path.to_string()],
};
allow_file_in_scopes(&app_handle, vec![PathBuf::from(path.to_string())]);
let _ = app_handle.emit("open-files", payload);
}
});
}
+1 -1
View File
@@ -17,7 +17,7 @@ const DEFAULT_SHORTCUTS = {
onWikipediaSelection: ['ctrl+w', 'cmd+w'],
onReadAloudSelection: ['ctrl+r', 'cmd+r'],
onOpenFontLayoutSettings: ['shift+f', 'ctrl+,', 'cmd+,'],
onOpenBooks: ['ctrl+o', 'cmd+o'],
onOpenBooks: ['ctrl+o'],
onReloadPage: ['shift+r'],
onToggleFullscreen: ['F11'],
onCloseWindow: ['ctrl+w', 'cmd+w'],
+19 -2
View File
@@ -14,6 +14,10 @@ interface SingleInstancePayload {
cwd: string;
}
interface OpenFilesPayload {
files: string[];
}
interface SharedIntentPayload {
urls: string[];
}
@@ -80,8 +84,8 @@ export function useOpenWithBooks() {
// For Windows/Linux deep link and macOS open-file event
const unlistenDeeplink = getCurrentWindow().listen<SingleInstancePayload>(
'single-instance',
({ event, payload }) => {
console.log('Received deep link:', event, payload);
({ payload }) => {
console.log('Received deep link:', payload);
const { args } = payload;
if (args?.[1]) {
handleOpenWithFileUrl([args[1]]);
@@ -89,6 +93,18 @@ export function useOpenWithBooks() {
},
);
// macOS in-app open-files event
const unlistenOpenFiles = getCurrentWindow().listen<OpenFilesPayload>(
'open-files',
({ payload }) => {
console.log('Received open files:', payload);
const { files } = payload;
if (files && files.length > 0) {
handleOpenWithFileUrl(files);
}
},
);
// For Android "Share to Readest" intent
let unlistenSharedIntent: Promise<PluginListener> | null = null;
// FIXME: register/unregister plugin listeniner on iOS might cause app freeze for unknown reason
@@ -106,6 +122,7 @@ export function useOpenWithBooks() {
const unlistenOpenUrl = listenOpenWithFiles();
return () => {
unlistenDeeplink.then((f) => f());
unlistenOpenFiles.then((f) => f());
unlistenOpenUrl.then((f) => f());
unlistenSharedIntent?.then((f) => f.unregister());
};