From 5d96aef14b7117ea916efa4f48e14f0b4385d926 Mon Sep 17 00:00:00 2001 From: BiggerRain <15911122312@163.COM> Date: Wed, 11 Dec 2024 09:07:27 +0800 Subject: [PATCH] add startup & hotkey (#45) * feat: add startup * feat: add hotkey switch --- .vscode/settings.json | 1 + src-tauri/capabilities/default.json | 8 +- src-tauri/src/lib.rs | 63 ++-- src/components/Settings/GeneralSettings.tsx | 166 +++++++++- src/components/Settings/SettingsSelect.tsx | 2 +- src/components/Settings/index2.tsx | 2 +- src/utils/tauri.ts | 349 ++++++++++++++++++++ 7 files changed, 553 insertions(+), 38 deletions(-) create mode 100644 src/utils/tauri.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 1bd474e2..648f7f4f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "cSpell.words": [ + "autolaunch", "Avenir", "callout", "clsx", diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index afd63d5d..c02ed01b 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -2,7 +2,7 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "Capability for the main window", - "windows": ["main", "chat"], + "windows": ["main", "chat", "settings"], "permissions": [ "core:default", "core:event:allow-emit", @@ -36,6 +36,12 @@ "websocket:default", "websocket:allow-connect", "websocket:allow-send", + "autostart:allow-enable", + "autostart:allow-disable", + "autostart:allow-is-enabled", + "global-shortcut:allow-is-registered", + "global-shortcut:allow-register", + "global-shortcut:allow-unregister", { "identifier": "http:default", "allow": [ diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e095ae94..1265bb59 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -3,6 +3,7 @@ use std::{fs::create_dir, io::Read}; use tauri::{AppHandle, Manager, Runtime, WebviewWindow}; use tauri_nspanel::{panel_delegate, ManagerExt, WebviewWindowExt}; use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut}; +use tauri_plugin_autostart::MacosLauncher; const DEFAULT_SHORTCUT: &str = "command+shift+space"; @@ -44,24 +45,53 @@ fn close_panel(handle: AppHandle) { panel.close(); } +// fn enable_autostart(app: &mut tauri::App) { +// use tauri_plugin_autostart::MacosLauncher; +// use tauri_plugin_autostart::ManagerExt; + +// app.handle() +// .plugin(tauri_plugin_autostart::init( +// MacosLauncher::AppleScript, +// None, +// )) +// .unwrap(); + +// let autostart_manager = app.autolaunch(); + +// match autostart_manager.is_enabled() { +// Ok(true) => { +// println!("Autostart is already enabled."); +// } +// Ok(false) => { +// match autostart_manager.enable() { +// Ok(_) => println!("Autostart enabled successfully."), +// Err(err) => eprintln!("Failed to enable autostart: {}", err), +// } +// } +// Err(err) => { +// eprintln!("Failed to check autostart status: {}", err); +// } +// } +// } + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_shell::init()) .plugin(tauri_nspanel::init()) + .plugin(tauri_plugin_autostart::init(MacosLauncher::AppleScript, None)) .invoke_handler(tauri::generate_handler![ greet, change_window_height, change_shortcut, show_panel, hide_panel, - close_panel, + close_panel, ]) .setup(|app| { init(app.app_handle()); - enable_autostart(app); enable_shortcut(app); enable_tray(app); @@ -100,23 +130,6 @@ fn init(app_handle: &AppHandle) { panel.set_delegate(delegate); } -fn enable_autostart(app: &mut tauri::App) { - use tauri_plugin_autostart::MacosLauncher; - use tauri_plugin_autostart::ManagerExt; - - app.handle() - .plugin(tauri_plugin_autostart::init( - MacosLauncher::AppleScript, - None, - )) - .unwrap(); - - // Get the autostart manager - let autostart_manager = app.autolaunch(); - // Enable autostart - let _ = autostart_manager.enable(); -} - fn enable_shortcut(app: &mut tauri::App) { use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut, ShortcutState}; @@ -246,14 +259,18 @@ fn enable_tray(app: &mut tauri::App) { window.show().unwrap(); window.set_focus().unwrap(); } else { - let window = tauri::window::WindowBuilder::new(app, "Settings") + let window = tauri::window::WindowBuilder::new(app, "settings") + .title("Settings Window") + .inner_size(800.0, 600.0) + .resizable(true) + .fullscreen(false) .build() .unwrap(); let webview_builder = WebviewBuilder::new( - "Settings", - tauri::WebviewUrl::App("index.html".into()), + "settings", + tauri::WebviewUrl::App("/ui/settings".into()), ); - let webview = window + let _webview = window .add_child( webview_builder, tauri::LogicalPosition::new(0, 0), diff --git a/src/components/Settings/GeneralSettings.tsx b/src/components/Settings/GeneralSettings.tsx index 9510ad91..dbc88b47 100644 --- a/src/components/Settings/GeneralSettings.tsx +++ b/src/components/Settings/GeneralSettings.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Command, Monitor, @@ -6,13 +6,16 @@ import { Layout, Star, Moon, - Sun + Sun, } from "lucide-react"; +import { isTauri, invoke } from "@tauri-apps/api/core"; +import { enable, isEnabled, disable } from "@tauri-apps/plugin-autostart"; import SettingsItem from "./SettingsItem"; import SettingsSelect from "./SettingsSelect"; import SettingsToggle from "./SettingsToggle"; import { ThemeOption } from "./index2"; +import { type Hotkey } from "../../utils/tauri"; interface GeneralSettingsProps { theme: "light" | "dark" | "system"; @@ -25,6 +28,140 @@ export default function GeneralSettings({ }: GeneralSettingsProps) { const [launchAtLogin, setLaunchAtLogin] = useState(true); + useEffect(() => { + const fetchAutoStartStatus = async () => { + if (isTauri()) { + try { + const status = await isEnabled(); + setLaunchAtLogin(status); + } catch (error) { + console.error("Failed to fetch autostart status:", error); + } + } + }; + + fetchAutoStartStatus(); + }, []); + + const enableAutoStart = async () => { + if (isTauri()) { + try { + await enable(); + } catch (error) { + console.error("Failed to enable autostart:", error); + } + } + setLaunchAtLogin(true); + }; + + const disableAutoStart = async () => { + if (isTauri()) { + try { + await disable(); + } catch (error) { + console.error("Failed to disable autostart:", error); + } + } + setLaunchAtLogin(false); + }; + + const [listening, setListening] = useState(false); + const [pressedKeys, setPressedKeys] = useState>(new Set()); + const [hotkey, setHotkey] = useState({ + alt: false, + code: "", + ctrl: false, + meta: true, + shift: true, + }); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + e.preventDefault(); + setPressedKeys((prev) => new Set(prev).add(e.code)); + }; + + const handleKeyUp = (e: KeyboardEvent) => { + setPressedKeys((prev) => { + const next = new Set(prev); + next.delete(e.code); + return next; + }); + }; + + if (listening) { + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); + } + + return () => { + if (listening) { + window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("keyup", handleKeyUp); + } + }; + }, [listening]); + + useEffect(() => { + if (pressedKeys.size === 0) return; + + const currentHotkey: Hotkey = { + meta: pressedKeys.has("MetaLeft") || pressedKeys.has("MetaRight"), + ctrl: pressedKeys.has("ControlLeft") || pressedKeys.has("ControlRight"), + shift: pressedKeys.has("ShiftLeft") || pressedKeys.has("ShiftRight"), + alt: pressedKeys.has("AltLeft") || pressedKeys.has("AltRight"), + code: + Array.from(pressedKeys).find( + (key) => + key.startsWith("Key") || key.startsWith("Digit") || key === "Space" + ) ?? "", + }; + + setHotkey(currentHotkey); + + if (currentHotkey.code) { + setListening(false); + } + }, [pressedKeys]); + + const convertShortcut = (shortcut: string): string => { + return shortcut + .replace(/⌘/g, "command") + .replace(/⇧/g, "shift") + .replace(/⎇/g, "alt") + .replace(/control/i, "ctrl") + .toLowerCase() + .replace(/\s+/g, "") + .trim(); + }; + + const formatHotkey = (hotkey: Hotkey | null): string => { + if (!hotkey) return "None"; + const parts: string[] = []; + if (hotkey.meta) parts.push("⌘"); + if (hotkey.ctrl) parts.push("Ctrl"); + if (hotkey.alt) parts.push("Alt"); + if (hotkey.shift) parts.push("Shift"); + if (hotkey.code === "Space" || hotkey.code === "") parts.push("Space"); + else if (hotkey.code.startsWith("Key")) parts.push(hotkey.code.slice(3)); + else if (hotkey.code.startsWith("Digit")) parts.push(hotkey.code.slice(5)); + + const shortcut = parts.join("+"); + + // Save the hotkey immediately + invoke("change_shortcut", { key: convertShortcut(shortcut) }).catch((err) => + console.error("Failed to save hotkey:", err) + ); + + return parts.join(" + "); + }; + + const handleStartListening = () => { + setPressedKeys(new Set()); + setHotkey(null); + setListening(true); + }; + return (
@@ -34,12 +171,14 @@ export default function GeneralSettings({
+ value ? enableAutoStart() : disableAutoStart() + } label="Launch at login" /> @@ -49,12 +188,15 @@ export default function GeneralSettings({ title="Coco Hotkey" description="Global shortcut to open Coco" > - - - + */}
- - + */} - Manage Favorites - + */}
diff --git a/src/components/Settings/SettingsSelect.tsx b/src/components/Settings/SettingsSelect.tsx index 06903df7..d128c5e4 100644 --- a/src/components/Settings/SettingsSelect.tsx +++ b/src/components/Settings/SettingsSelect.tsx @@ -15,7 +15,7 @@ export default function SettingsSelect({