From 57fe0ccc1fdbc988cd4c080e6c63fecbc57c6463 Mon Sep 17 00:00:00 2001 From: Silwings <1910506332@qq.com> Date: Mon, 25 Aug 2025 19:39:53 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E5=8F=B3=E9=94=AE=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/Cargo.lock | 1 + src-tauri/Cargo.toml | 1 + src-tauri/src/api/window_api.rs | 131 ++++++++++++++++++++++++++++++++ src-tauri/src/lib.rs | 1 + src/home.vue | 112 ++++++++++++++++++++++++++- src/launcher.vue | 10 ++- 6 files changed, 252 insertions(+), 4 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 4b82c29..e6f0ae9 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2735,6 +2735,7 @@ name = "one-click-launch" version = "1.0.3" dependencies = [ "anyhow", + "dirs 5.0.1", "itertools 0.14.0", "lazy_static", "rand 0.8.5", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 7293af5..dcef400 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -39,6 +39,7 @@ tokio = { version = "1.43.0", features = ["full"] } rand = "0.8.5" lazy_static = "1.5.0" itertools = "0.14.0" +dirs = "5.0.1" [features] portable = [] diff --git a/src-tauri/src/api/window_api.rs b/src-tauri/src/api/window_api.rs index 3b8e0a5..8e60267 100644 --- a/src-tauri/src/api/window_api.rs +++ b/src-tauri/src/api/window_api.rs @@ -5,6 +5,7 @@ use tauri::{ AppHandle, DragDropEvent, Manager, State, Theme, menu::{MenuBuilder, MenuItem}, tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, + utils::platform::current_exe, }; use tracing::{debug, info}; @@ -202,3 +203,133 @@ pub fn setup_tray(app: &AppHandle) -> Result<()> { pub struct ScaleFactorChangedState { pub last_reset: Mutex>, } + +use std::os::windows::ffi::OsStrExt; +use std::path::PathBuf; +use windows::{ + Win32::{ + System::Com::{ + CLSCTX_INPROC_SERVER, COINIT_APARTMENTTHREADED, CoCreateInstance, CoInitializeEx, + CoUninitialize, IPersistFile, + }, + UI::Shell::{IShellLinkW, ShellLink}, + }, + core::{HSTRING, Interface, PCWSTR}, +}; + +#[tauri::command] +pub async fn create_handler_shortcut(launcher_id: i64) -> Result { + println!("handler_id: {launcher_id}"); + // 获取当前应用程序的绝对路径 + // 使用launcher_id获取编组名称作为快捷方式名称 + // 使用 --launch 拼接launcher_id作为参数 + // 快捷方式存储到桌面 + // 1. 获取当前应用 exe 路径 + // 获取当前应用程序的路径 + let exe_path = std::env::current_exe() + .map_err(|e| e.to_string())?; + + // 转换为 Windows 可识别的普通路径 + let mut app_path = exe_path.to_string_lossy().to_string(); + if app_path.starts_with(r"\\?\") { + app_path = app_path.trim_start_matches(r"\\?\").to_string(); + } + + // 2. 根据 launcher_id 获取编组名称(TODO: 替换成你真实的获取逻辑) + // 这里我先用个占位符 + let shortcut_name = format!("Launcher{}", launcher_id); + + // 3. 构建参数 + let args = Some(vec![format!("--launch {}", launcher_id)]); + let args = None; + + // 4. 调用 create_shortcut 存储到桌面 + match create_shortcut( + &app_path, + &shortcut_name, + args, + None, // None 表示保存到桌面 + ) { + Ok(path) => Ok(path.to_string_lossy().to_string()), + Err(e) => { + tracing::error!("创建快捷方式失败: {}", e); + Err(format!("创建快捷方式失败: {e}")) + } + } +} + +/// 创建 Windows 快捷方式 (.lnk 文件) +pub fn create_shortcut( + app_path: &str, + shortcut_name: &str, + args: Option>, + target_dir: Option<&str>, +) -> anyhow::Result { + unsafe { + // 初始化 COM + CoInitializeEx(None, COINIT_APARTMENTTHREADED).ok()?; + +println!("1"); + + // 创建 IShellLink 实例 + let shell_link: IShellLinkW = CoCreateInstance(&ShellLink, None, CLSCTX_INPROC_SERVER)?; +println!("2"); + // 设置应用路径 + shell_link.SetPath(&HSTRING::from(dbg!(app_path)))?; +println!("3"); + // 设置参数 + if let Some(arguments) = args { + let arg_str = join_arguments(&arguments); + shell_link.SetArguments(&HSTRING::from(arg_str))?; + } +println!("4"); + // 设置工作目录(使用 app_path 的父目录) + if let Some(parent) = std::path::Path::new(app_path).parent() { + shell_link.SetWorkingDirectory(&HSTRING::from(parent.to_string_lossy().to_string()))?; + } +println!("5"); + // 获取 IPersistFile 接口 + let persist_file: IPersistFile = shell_link.cast()?; +println!("6"); + // 目标目录(默认桌面) + let save_dir = if let Some(dir) = target_dir { +println!("7"); + PathBuf::from(dir) + } else { +println!("8"); + dirs::desktop_dir().ok_or_else(|| anyhow::anyhow!("无法获取桌面路径"))? + }; +println!("9"); + // 拼接快捷方式路径 + let lnk_path = save_dir.join(format!("{}.lnk", shortcut_name)); +println!("10"); + // 转换为宽字符串 + let wide: Vec = lnk_path + .as_os_str() + .encode_wide() + .chain(std::iter::once(0)) + .collect(); +println!("11"); + // 保存 + persist_file.Save(PCWSTR::from_raw(wide.as_ptr()), true)?; +println!("12"); + // 释放 COM + CoUninitialize(); + + Ok(lnk_path) + } +} + +/// 将 Vec 拼接成命令行参数字符串,自动加引号 +fn join_arguments(args: &[String]) -> String { + args.iter() + .map(|a| { + if a.contains(' ') { + format!("\"{}\"", a) // 带空格加引号 + } else { + a.clone() + } + }) + .collect::>() + .join(" ") +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index b14d44b..0924178 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -165,6 +165,7 @@ pub async fn run() -> Result<()> { setting_api::save_setting, setting_api::read_setting, setting_api::read_all_setting, + window_api::create_handler_shortcut, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src/home.vue b/src/home.vue index 90909f7..ab50746 100644 --- a/src/home.vue +++ b/src/home.vue @@ -16,7 +16,8 @@
+ @launcher-updated="refreshLaunchers" @launcher-moved="moveLauncher" @settings-updated="refreshLaunchers" + @show-context-menu="toggleShowContextMenu" />
+ +
+ + +
@@ -67,12 +78,28 @@ export default { const showSetting = ref(false); const dragDropResourcePaths = ref([]); + // 右键菜单相关状态 + const showLauncherMenu = ref(false); + const launcherMenuPositionTop = ref(0); + const launcherMenuPositionLeft = ref(0); + const selectedLauncherMenuLauncher = ref(null); + + // 全局点击事件,关闭右键菜单 + const handleGlobalClick = (e) => { + // 如果点击的不是右键菜单本身,则关闭菜单 + if (!e.target.closest('.custom-contextmenu')) { + showLauncherMenu.value = false; + selectedLauncherMenuLauncher.value = null; + } + }; + const setupEventListener = async () => { listen('launcher:drag_drop_resource', async (event) => { if (dragDropResourcePaths.value.length == 0) { dragDropResourcePaths.value = Array.from(event.payload.paths); } }); + document.addEventListener('click', handleGlobalClick); }; const launch = async (launcherId) => { await invoke("launch", { launcherId: launcherId }); @@ -142,6 +169,35 @@ export default { await refreshLaunchers(); }; + // 显示右键菜单 + const toggleShowContextMenu = (launcherData, event) => { + event.preventDefault(); + event.stopPropagation(); + + // 设置菜单位置 + launcherMenuPositionTop.value = event.clientY; + launcherMenuPositionLeft.value = event.clientX; + + // 记录选中的launcher + selectedLauncherMenuLauncher.value = launcherData; + + // 显示菜单 + showLauncherMenu.value = true; + }; + + // 右键菜单操作 + const logLauncherName = () => { + console.log("当前Launcher名称: ", selectedLauncherMenuLauncher.value?.name); + showLauncherMenu.value = false; + }; + + // 创建launcher快捷键 + const createLauncherShortcut = async () => { + console.log("创建launcher快捷键: ", selectedLauncherMenuLauncher.value?.id); + await invoke("create_handler_shortcut",{launcherId: selectedLauncherMenuLauncher.value.id }); + showLauncherMenu.value = false; + } + // 在组件挂载时加载主题 onMounted(() => { setupEventListener(); @@ -150,6 +206,11 @@ export default { refreshLaunchers(); }); + // 清理全局事件监听 + const onUnmounted = () => { + document.removeEventListener('click', handleGlobalClick); + }; + return { theme, launchers, @@ -165,7 +226,14 @@ export default { closeSetting, dragDropResourcePaths, cleanDragDropResourcePaths, - confirmDragDrop + confirmDragDrop, + toggleShowContextMenu, + showLauncherMenu, + launcherMenuPositionTop, + launcherMenuPositionLeft, + selectedLauncherMenuLauncher, + logLauncherName, + createLauncherShortcut }; } }; @@ -427,4 +495,44 @@ button:hover { color: #f5ebeb; } +/* 自定义右键菜单样式 */ +.custom-contextmenu { + position: fixed; + background: #fff; + border: 1px solid #ddd; + border-radius: 4px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + padding: 5px 0; + min-width: 150px; + max-width: 300px; +} + +.dark .custom-contextmenu { + background-color: #2c2c2c; + border-color: #444; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); + color: #ccc; +} + +.menu-item { + padding: 8px 15px; + cursor: pointer; + transition: background-color 0.2s; + font-size: 14px; +} + +.menu-item:hover { + background-color: #f0f0f0; +} + +/* 深色模式 hover */ +.menu-item:hover.dark { + background-color: #444; + color: #fff; + /* hover 时文字更醒目 */ +} + +.dark .menu-item:hover { + background-color: #444; +} \ No newline at end of file diff --git a/src/launcher.vue b/src/launcher.vue index 9ba950d..5467506 100644 --- a/src/launcher.vue +++ b/src/launcher.vue @@ -1,5 +1,5 @@