mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-02 02:05:57 +08:00
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:
@@ -1,6 +1,7 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
mod config;
|
||||
mod deeplink;
|
||||
mod import_export;
|
||||
mod mcp;
|
||||
mod misc;
|
||||
@@ -10,6 +11,7 @@ mod provider;
|
||||
mod settings;
|
||||
|
||||
pub use config::*;
|
||||
pub use deeplink::*;
|
||||
pub use import_export::*;
|
||||
pub use mcp::*;
|
||||
pub use misc::*;
|
||||
|
||||
+189
-12
@@ -5,6 +5,7 @@ mod claude_plugin;
|
||||
mod codex_config;
|
||||
mod commands;
|
||||
mod config;
|
||||
mod deeplink;
|
||||
mod error;
|
||||
mod gemini_config; // 新增
|
||||
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 commands::*;
|
||||
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 mcp::{
|
||||
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 store::AppState;
|
||||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
|
||||
use tauri::{
|
||||
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"))]
|
||||
{
|
||||
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") {
|
||||
let _ = window.unminimize();
|
||||
let _ = window.show();
|
||||
@@ -356,6 +438,8 @@ pub fn run() {
|
||||
}
|
||||
|
||||
let builder = builder
|
||||
// 注册 deep-link 插件(处理 macOS AppleEvent 和其他平台的深链接)
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
// 拦截窗口关闭:根据设置决定是否最小化到托盘
|
||||
.on_window_event(|window, event| {
|
||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||
@@ -471,7 +555,40 @@ pub fn run() {
|
||||
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)?;
|
||||
@@ -572,6 +689,9 @@ pub fn run() {
|
||||
commands::save_file_dialog,
|
||||
commands::open_file_dialog,
|
||||
commands::sync_current_providers_live,
|
||||
// Deep link import
|
||||
commands::parse_deeplink,
|
||||
commands::import_from_deeplink,
|
||||
update_tray_menu,
|
||||
]);
|
||||
|
||||
@@ -581,17 +701,74 @@ pub fn run() {
|
||||
|
||||
app.run(|app_handle, event| {
|
||||
#[cfg(target_os = "macos")]
|
||||
// macOS 在 Dock 图标被点击并重新激活应用时会触发 Reopen 事件,这里手动恢复主窗口
|
||||
if let RunEvent::Reopen { .. } = event {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let _ = window.set_skip_taskbar(false);
|
||||
{
|
||||
match event {
|
||||
// macOS 在 Dock 图标被点击并重新激活应用时会触发 Reopen 事件,这里手动恢复主窗口
|
||||
RunEvent::Reopen { .. } => {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
#[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();
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
apply_tray_policy(app_handle, true);
|
||||
// 处理通过自定义 URL 协议触发的打开事件(例如 ccswitch://...)
|
||||
RunEvent::Opened { urls } => {
|
||||
if let Some(url) = urls.first() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user