chore(deeplink): integrate deep link handling into app lifecycle
Wire up deep link infrastructure with app initialization and event handling. Backend Integration: - Register deep link module and commands in mod.rs - Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url) - Handle deep links from single instance callback (Windows/Linux CLI) - Handle deep links from macOS system events - Add tauri-plugin-deep-link dependency (Cargo.toml) Frontend Integration: - Listen for deeplink-import/deeplink-error events in App.tsx - Update DeepLinkImportDialog component imports Configuration: - Enable deep link plugin in tauri.conf.json - Update Cargo.lock for new dependencies Localization: - Add Chinese translations for deep link UI (zh.json) - Add English translations for deep link UI (en.json) Files: 9 changed, 359 insertions(+), 18 deletions(-)
This commit is contained in:
111
src-tauri/Cargo.lock
generated
111
src-tauri/Cargo.lock
generated
@@ -579,6 +579,7 @@ dependencies = [
|
|||||||
"serial_test",
|
"serial_test",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
|
"tauri-plugin-deep-link",
|
||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
"tauri-plugin-log",
|
"tauri-plugin-log",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
@@ -591,6 +592,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"toml 0.8.2",
|
"toml 0.8.2",
|
||||||
"toml_edit 0.22.27",
|
"toml_edit 0.22.27",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -665,6 +667,26 @@ dependencies = [
|
|||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const-random"
|
||||||
|
version = "0.1.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||||
|
dependencies = [
|
||||||
|
"const-random-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const-random-macro"
|
||||||
|
version = "0.1.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.16",
|
||||||
|
"once_cell",
|
||||||
|
"tiny-keccak",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "convert_case"
|
name = "convert_case"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -754,6 +776,12 @@ version = "0.8.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crunchy"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
@@ -983,6 +1011,15 @@ dependencies = [
|
|||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dlv-list"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
|
||||||
|
dependencies = [
|
||||||
|
"const-random",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "downcast-rs"
|
name = "downcast-rs"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@@ -1671,6 +1708,12 @@ dependencies = [
|
|||||||
"ahash",
|
"ahash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.16.0"
|
version = "0.16.0"
|
||||||
@@ -1747,6 +1790,12 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-range"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.10.1"
|
version = "1.10.1"
|
||||||
@@ -2778,6 +2827,16 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ordered-multimap"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
|
||||||
|
dependencies = [
|
||||||
|
"dlv-list",
|
||||||
|
"hashbrown 0.14.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered-stream"
|
name = "ordered-stream"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -3623,6 +3682,16 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-ini"
|
||||||
|
version = "0.21.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"ordered-multimap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust_decimal"
|
name = "rust_decimal"
|
||||||
version = "1.38.0"
|
version = "1.38.0"
|
||||||
@@ -4362,6 +4431,7 @@ dependencies = [
|
|||||||
"gtk",
|
"gtk",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"http",
|
"http",
|
||||||
|
"http-range",
|
||||||
"jni",
|
"jni",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
@@ -4477,6 +4547,27 @@ dependencies = [
|
|||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-deep-link"
|
||||||
|
version = "2.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e82759f7c7d51de3cbde51c04b3f2332de52436ed84541182cd8944b04e9e73"
|
||||||
|
dependencies = [
|
||||||
|
"dunce",
|
||||||
|
"plist",
|
||||||
|
"rust-ini",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"tauri-utils",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
"tracing",
|
||||||
|
"url",
|
||||||
|
"windows-registry",
|
||||||
|
"windows-result 0.3.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-dialog"
|
name = "tauri-plugin-dialog"
|
||||||
version = "2.4.0"
|
version = "2.4.0"
|
||||||
@@ -4831,6 +4922,15 @@ dependencies = [
|
|||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-keccak"
|
||||||
|
version = "2.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||||
|
dependencies = [
|
||||||
|
"crunchy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
@@ -5754,6 +5854,17 @@ dependencies = [
|
|||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-registry"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link 0.1.3",
|
||||||
|
"windows-result 0.3.4",
|
||||||
|
"windows-strings 0.4.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-result"
|
name = "windows-result"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
|
|||||||
@@ -26,13 +26,14 @@ serde_json = "1.0"
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
tauri = { version = "2.8.2", features = ["tray-icon"] }
|
tauri = { version = "2.8.2", features = ["tray-icon", "protocol-asset"] }
|
||||||
tauri-plugin-log = "2"
|
tauri-plugin-log = "2"
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
tauri-plugin-process = "2"
|
tauri-plugin-process = "2"
|
||||||
tauri-plugin-updater = "2"
|
tauri-plugin-updater = "2"
|
||||||
tauri-plugin-dialog = "2"
|
tauri-plugin-dialog = "2"
|
||||||
tauri-plugin-store = "2"
|
tauri-plugin-store = "2"
|
||||||
|
tauri-plugin-deep-link = "2"
|
||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
toml_edit = "0.22"
|
toml_edit = "0.22"
|
||||||
@@ -42,6 +43,7 @@ futures = "0.3"
|
|||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
rquickjs = { version = "0.8", features = ["array-buffer", "classes"] }
|
rquickjs = { version = "0.8", features = ["array-buffer", "classes"] }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
url = "2.5"
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))'.dependencies]
|
[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))'.dependencies]
|
||||||
tauri-plugin-single-instance = "2"
|
tauri-plugin-single-instance = "2"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
mod deeplink;
|
||||||
mod import_export;
|
mod import_export;
|
||||||
mod mcp;
|
mod mcp;
|
||||||
mod misc;
|
mod misc;
|
||||||
@@ -10,6 +11,7 @@ mod provider;
|
|||||||
mod settings;
|
mod settings;
|
||||||
|
|
||||||
pub use config::*;
|
pub use config::*;
|
||||||
|
pub use deeplink::*;
|
||||||
pub use import_export::*;
|
pub use import_export::*;
|
||||||
pub use mcp::*;
|
pub use mcp::*;
|
||||||
pub use misc::*;
|
pub use misc::*;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ mod claude_plugin;
|
|||||||
mod codex_config;
|
mod codex_config;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod config;
|
mod config;
|
||||||
|
mod deeplink;
|
||||||
mod error;
|
mod error;
|
||||||
mod gemini_config; // 新增
|
mod gemini_config; // 新增
|
||||||
mod gemini_mcp;
|
mod gemini_mcp;
|
||||||
@@ -22,6 +23,7 @@ pub use app_config::{AppType, McpApps, McpServer, MultiAppConfig};
|
|||||||
pub use codex_config::{get_codex_auth_path, get_codex_config_path, write_codex_live_atomic};
|
pub use codex_config::{get_codex_auth_path, get_codex_config_path, write_codex_live_atomic};
|
||||||
pub use commands::*;
|
pub use commands::*;
|
||||||
pub use config::{get_claude_mcp_path, get_claude_settings_path, read_json_file};
|
pub use config::{get_claude_mcp_path, get_claude_settings_path, read_json_file};
|
||||||
|
pub use deeplink::{import_provider_from_deeplink, parse_deeplink_url, DeepLinkImportRequest};
|
||||||
pub use error::AppError;
|
pub use error::AppError;
|
||||||
pub use mcp::{
|
pub use mcp::{
|
||||||
import_from_claude, import_from_codex, import_from_gemini, remove_server_from_claude,
|
import_from_claude, import_from_codex, import_from_gemini, remove_server_from_claude,
|
||||||
@@ -35,6 +37,7 @@ pub use services::{
|
|||||||
};
|
};
|
||||||
pub use settings::{update_settings, AppSettings};
|
pub use settings::{update_settings, AppSettings};
|
||||||
pub use store::AppState;
|
pub use store::AppState;
|
||||||
|
use tauri_plugin_deep_link::DeepLinkExt;
|
||||||
|
|
||||||
use tauri::{
|
use tauri::{
|
||||||
menu::{CheckMenuItem, Menu, MenuBuilder, MenuItem},
|
menu::{CheckMenuItem, Menu, MenuBuilder, MenuItem},
|
||||||
@@ -281,6 +284,65 @@ fn handle_tray_menu_event(app: &tauri::AppHandle, event_id: &str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 统一处理 ccswitch:// 深链接 URL
|
||||||
|
///
|
||||||
|
/// - 解析 URL
|
||||||
|
/// - 向前端发射 `deeplink-import` / `deeplink-error` 事件
|
||||||
|
/// - 可选:在成功时聚焦主窗口
|
||||||
|
fn handle_deeplink_url(
|
||||||
|
app: &tauri::AppHandle,
|
||||||
|
url_str: &str,
|
||||||
|
focus_main_window: bool,
|
||||||
|
source: &str,
|
||||||
|
) -> bool {
|
||||||
|
if !url_str.starts_with("ccswitch://") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("✓ Deep link URL detected from {source}: {url_str}");
|
||||||
|
|
||||||
|
match crate::deeplink::parse_deeplink_url(url_str) {
|
||||||
|
Ok(request) => {
|
||||||
|
log::info!(
|
||||||
|
"✓ Successfully parsed deep link: resource={}, app={}, name={}",
|
||||||
|
request.resource,
|
||||||
|
request.app,
|
||||||
|
request.name
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = app.emit("deeplink-import", &request) {
|
||||||
|
log::error!("✗ Failed to emit deeplink-import event: {e}");
|
||||||
|
} else {
|
||||||
|
log::info!("✓ Emitted deeplink-import event to frontend");
|
||||||
|
}
|
||||||
|
|
||||||
|
if focus_main_window {
|
||||||
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
|
let _ = window.unminimize();
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.set_focus();
|
||||||
|
log::info!("✓ Window shown and focused");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("✗ Failed to parse deep link URL: {e}");
|
||||||
|
|
||||||
|
if let Err(emit_err) = app.emit(
|
||||||
|
"deeplink-error",
|
||||||
|
serde_json::json!({
|
||||||
|
"url": url_str,
|
||||||
|
"error": e.to_string()
|
||||||
|
}),
|
||||||
|
) {
|
||||||
|
log::error!("✗ Failed to emit deeplink-error event: {emit_err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
/// 内部切换供应商函数
|
/// 内部切换供应商函数
|
||||||
@@ -346,7 +408,27 @@ pub fn run() {
|
|||||||
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
|
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
|
||||||
{
|
{
|
||||||
builder = builder.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
|
builder = builder.plugin(tauri_plugin_single_instance::init(|app, args, _cwd| {
|
||||||
|
log::info!("=== Single Instance Callback Triggered ===");
|
||||||
|
log::info!("Args count: {}", args.len());
|
||||||
|
for (i, arg) in args.iter().enumerate() {
|
||||||
|
log::info!(" arg[{i}]: {arg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for deep link URL in args (mainly for Windows/Linux command line)
|
||||||
|
let mut found_deeplink = false;
|
||||||
|
for arg in &args {
|
||||||
|
if handle_deeplink_url(app, arg, false, "single_instance args") {
|
||||||
|
found_deeplink = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found_deeplink {
|
||||||
|
log::info!("ℹ No deep link URL found in args (this is expected on macOS when launched via system)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show and focus window regardless
|
||||||
if let Some(window) = app.get_webview_window("main") {
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
let _ = window.unminimize();
|
let _ = window.unminimize();
|
||||||
let _ = window.show();
|
let _ = window.show();
|
||||||
@@ -356,6 +438,8 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let builder = builder
|
let builder = builder
|
||||||
|
// 注册 deep-link 插件(处理 macOS AppleEvent 和其他平台的深链接)
|
||||||
|
.plugin(tauri_plugin_deep_link::init())
|
||||||
// 拦截窗口关闭:根据设置决定是否最小化到托盘
|
// 拦截窗口关闭:根据设置决定是否最小化到托盘
|
||||||
.on_window_event(|window, event| {
|
.on_window_event(|window, event| {
|
||||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||||
@@ -471,7 +555,40 @@ pub fn run() {
|
|||||||
config_guard.ensure_app(&app_config::AppType::Codex);
|
config_guard.ensure_app(&app_config::AppType::Codex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动阶段不再无条件保存,避免意外覆盖用户配置。
|
// 启动阶段不再无条件保存,避免意外覆盖用户配置。
|
||||||
|
|
||||||
|
// 注册 deep-link URL 处理器(使用正确的 DeepLinkExt API)
|
||||||
|
log::info!("=== Registering deep-link URL handler ===");
|
||||||
|
|
||||||
|
// Linux 和 Windows 调试模式需要显式注册
|
||||||
|
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
|
||||||
|
{
|
||||||
|
if let Err(e) = app.deep_link().register_all() {
|
||||||
|
log::error!("✗ Failed to register deep link schemes: {}", e);
|
||||||
|
} else {
|
||||||
|
log::info!("✓ Deep link schemes registered (Linux/Windows)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册 URL 处理回调(所有平台通用)
|
||||||
|
app.deep_link().on_open_url({
|
||||||
|
let app_handle = app.handle().clone();
|
||||||
|
move |event| {
|
||||||
|
log::info!("=== Deep Link Event Received (on_open_url) ===");
|
||||||
|
let urls = event.urls();
|
||||||
|
log::info!("Received {} URL(s)", urls.len());
|
||||||
|
|
||||||
|
for (i, url) in urls.iter().enumerate() {
|
||||||
|
let url_str = url.as_str();
|
||||||
|
log::info!(" URL[{i}]: {url_str}");
|
||||||
|
|
||||||
|
if handle_deeplink_url(&app_handle, url_str, true, "on_open_url") {
|
||||||
|
break; // Process only first ccswitch:// URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
log::info!("✓ Deep-link URL handler registered");
|
||||||
|
|
||||||
// 创建动态托盘菜单
|
// 创建动态托盘菜单
|
||||||
let menu = create_tray_menu(app.handle(), &app_state)?;
|
let menu = create_tray_menu(app.handle(), &app_state)?;
|
||||||
@@ -572,6 +689,9 @@ pub fn run() {
|
|||||||
commands::save_file_dialog,
|
commands::save_file_dialog,
|
||||||
commands::open_file_dialog,
|
commands::open_file_dialog,
|
||||||
commands::sync_current_providers_live,
|
commands::sync_current_providers_live,
|
||||||
|
// Deep link import
|
||||||
|
commands::parse_deeplink,
|
||||||
|
commands::import_from_deeplink,
|
||||||
update_tray_menu,
|
update_tray_menu,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -581,17 +701,74 @@ pub fn run() {
|
|||||||
|
|
||||||
app.run(|app_handle, event| {
|
app.run(|app_handle, event| {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
// macOS 在 Dock 图标被点击并重新激活应用时会触发 Reopen 事件,这里手动恢复主窗口
|
{
|
||||||
if let RunEvent::Reopen { .. } = event {
|
match event {
|
||||||
if let Some(window) = app_handle.get_webview_window("main") {
|
// macOS 在 Dock 图标被点击并重新激活应用时会触发 Reopen 事件,这里手动恢复主窗口
|
||||||
#[cfg(target_os = "windows")]
|
RunEvent::Reopen { .. } => {
|
||||||
{
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
let _ = window.set_skip_taskbar(false);
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
let _ = window.set_skip_taskbar(false);
|
||||||
|
}
|
||||||
|
let _ = window.unminimize();
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.set_focus();
|
||||||
|
apply_tray_policy(app_handle, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let _ = window.unminimize();
|
// 处理通过自定义 URL 协议触发的打开事件(例如 ccswitch://...)
|
||||||
let _ = window.show();
|
RunEvent::Opened { urls } => {
|
||||||
let _ = window.set_focus();
|
if let Some(url) = urls.first() {
|
||||||
apply_tray_policy(app_handle, true);
|
let url_str = url.to_string();
|
||||||
|
log::info!("RunEvent::Opened with URL: {url_str}");
|
||||||
|
|
||||||
|
if url_str.starts_with("ccswitch://") {
|
||||||
|
// 解析并广播深链接事件,复用与 single_instance 相同的逻辑
|
||||||
|
match crate::deeplink::parse_deeplink_url(&url_str) {
|
||||||
|
Ok(request) => {
|
||||||
|
log::info!(
|
||||||
|
"Successfully parsed deep link from RunEvent::Opened: resource={}, app={}",
|
||||||
|
request.resource,
|
||||||
|
request.app
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) =
|
||||||
|
app_handle.emit("deeplink-import", &request)
|
||||||
|
{
|
||||||
|
log::error!(
|
||||||
|
"Failed to emit deep link event from RunEvent::Opened: {e}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(
|
||||||
|
"Failed to parse deep link URL from RunEvent::Opened: {e}"
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(emit_err) = app_handle.emit(
|
||||||
|
"deeplink-error",
|
||||||
|
serde_json::json!({
|
||||||
|
"url": url_str,
|
||||||
|
"error": e.to_string()
|
||||||
|
}),
|
||||||
|
) {
|
||||||
|
log::error!(
|
||||||
|
"Failed to emit deep link error event from RunEvent::Opened: {emit_err}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保主窗口可见
|
||||||
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
|
let _ = window.unminimize();
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.set_focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
"csp": "default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' ipc: http://ipc.localhost https: http:"
|
"csp": "default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' ipc: http://ipc.localhost https: http:",
|
||||||
|
"assetProtocol": {
|
||||||
|
"enable": true,
|
||||||
|
"scope": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
@@ -41,9 +45,17 @@
|
|||||||
"wix": {
|
"wix": {
|
||||||
"template": "wix/per-user-main.wxs"
|
"template": "wix/per-user-main.wxs"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"macOS": {
|
||||||
|
"minimumSystemVersion": "10.15"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
|
"deep-link": {
|
||||||
|
"desktop": {
|
||||||
|
"schemes": ["ccswitch"]
|
||||||
|
}
|
||||||
|
},
|
||||||
"updater": {
|
"updater": {
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEM4MDI4QzlBNTczOTI4RTMKUldUaktEbFhtb3dDeUM5US9kT0FmdGR5Ti9vQzcwa2dTMlpibDVDUmQ2M0VGTzVOWnd0SGpFVlEK",
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEM4MDI4QzlBNTczOTI4RTMKUldUaktEbFhtb3dDeUM5US9kT0FmdGR5Ti9vQzcwa2dTMlpibDVDUmQ2M0VGTzVOWnd0SGpFVlEK",
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { UpdateBadge } from "@/components/UpdateBadge";
|
|||||||
import UsageScriptModal from "@/components/UsageScriptModal";
|
import UsageScriptModal from "@/components/UsageScriptModal";
|
||||||
import UnifiedMcpPanel from "@/components/mcp/UnifiedMcpPanel";
|
import UnifiedMcpPanel from "@/components/mcp/UnifiedMcpPanel";
|
||||||
import PromptPanel from "@/components/prompts/PromptPanel";
|
import PromptPanel from "@/components/prompts/PromptPanel";
|
||||||
|
import { DeepLinkImportDialog } from "@/components/DeepLinkImportDialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@@ -303,6 +304,8 @@ function App() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<UnifiedMcpPanel open={isMcpOpen} onOpenChange={setIsMcpOpen} />
|
<UnifiedMcpPanel open={isMcpOpen} onOpenChange={setIsMcpOpen} />
|
||||||
|
|
||||||
|
<DeepLinkImportDialog />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,10 +113,8 @@ export function DeepLinkImportDialog() {
|
|||||||
<div className="font-medium text-sm text-muted-foreground">
|
<div className="font-medium text-sm text-muted-foreground">
|
||||||
{t("deeplink.app")}
|
{t("deeplink.app")}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-2 text-sm">
|
<div className="col-span-2 text-sm font-medium capitalize">
|
||||||
<span className="inline-flex items-center rounded-full bg-primary/10 px-2.5 py-0.5 text-xs font-medium text-primary capitalize">
|
{request.app}
|
||||||
{request.app}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -608,5 +608,23 @@
|
|||||||
"deleteTitle": "Confirm Delete",
|
"deleteTitle": "Confirm Delete",
|
||||||
"deleteMessage": "Are you sure you want to delete prompt \"{{name}}\"?"
|
"deleteMessage": "Are you sure you want to delete prompt \"{{name}}\"?"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"deeplink": {
|
||||||
|
"confirmImport": "Confirm Import Provider",
|
||||||
|
"confirmImportDescription": "The following configuration will be imported from deep link into CC Switch",
|
||||||
|
"app": "App Type",
|
||||||
|
"providerName": "Provider Name",
|
||||||
|
"homepage": "Homepage",
|
||||||
|
"endpoint": "API Endpoint",
|
||||||
|
"apiKey": "API Key",
|
||||||
|
"model": "Model",
|
||||||
|
"notes": "Notes",
|
||||||
|
"import": "Import",
|
||||||
|
"importing": "Importing...",
|
||||||
|
"warning": "Please confirm the information above is correct before importing. You can edit or delete it later in the provider list.",
|
||||||
|
"parseError": "Failed to parse deep link",
|
||||||
|
"importSuccess": "Import successful",
|
||||||
|
"importSuccessDescription": "Provider \"{{name}}\" has been successfully imported",
|
||||||
|
"importError": "Failed to import"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -608,5 +608,23 @@
|
|||||||
"deleteTitle": "确认删除",
|
"deleteTitle": "确认删除",
|
||||||
"deleteMessage": "确定要删除提示词 \"{{name}}\" 吗?"
|
"deleteMessage": "确定要删除提示词 \"{{name}}\" 吗?"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"deeplink": {
|
||||||
|
"confirmImport": "确认导入供应商配置",
|
||||||
|
"confirmImportDescription": "以下配置将导入到 CC Switch",
|
||||||
|
"app": "应用类型",
|
||||||
|
"providerName": "供应商名称",
|
||||||
|
"homepage": "官网地址",
|
||||||
|
"endpoint": "API 端点",
|
||||||
|
"apiKey": "API 密钥",
|
||||||
|
"model": "模型",
|
||||||
|
"notes": "备注",
|
||||||
|
"import": "导入",
|
||||||
|
"importing": "导入中...",
|
||||||
|
"warning": "请确认以上信息准确无误后再导入。导入后可在供应商列表中编辑或删除。",
|
||||||
|
"parseError": "深链接解析失败",
|
||||||
|
"importSuccess": "导入成功",
|
||||||
|
"importSuccessDescription": "供应商 \"{{name}}\" 已成功导入",
|
||||||
|
"importError": "导入失败"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user