feat: option to enable global hotkey (#2665)

This commit is contained in:
Tunglies 2025-02-09 07:45:22 +08:00 committed by GitHub
parent 37e5c22a5a
commit 215dcee3f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 69 additions and 7 deletions

View File

@ -99,6 +99,9 @@ pub struct IVerge {
/// format: {func},{key} /// format: {func},{key}
pub hotkeys: Option<Vec<String>>, pub hotkeys: Option<Vec<String>>,
/// enable global hotkey
pub enable_global_hotkey: Option<bool>,
/// 切换代理时自动关闭连接 /// 切换代理时自动关闭连接
pub auto_close_connection: Option<bool>, pub auto_close_connection: Option<bool>,
@ -279,6 +282,7 @@ impl IVerge {
webdav_username: None, webdav_username: None,
webdav_password: None, webdav_password: None,
enable_tray_speed: Some(true), enable_tray_speed: Some(true),
enable_global_hotkey: Some(true),
..Self::default() ..Self::default()
} }
} }
@ -345,6 +349,7 @@ impl IVerge {
patch!(web_ui_list); patch!(web_ui_list);
patch!(clash_core); patch!(clash_core);
patch!(hotkeys); patch!(hotkeys);
patch!(enable_global_hotkey);
patch!(auto_close_connection); patch!(auto_close_connection);
patch!(auto_check_update); patch!(auto_check_update);
@ -411,6 +416,7 @@ pub struct IVergeResponse {
pub enable_silent_start: Option<bool>, pub enable_silent_start: Option<bool>,
pub enable_system_proxy: Option<bool>, pub enable_system_proxy: Option<bool>,
pub enable_proxy_guard: Option<bool>, pub enable_proxy_guard: Option<bool>,
pub enable_global_hotkey: Option<bool>,
pub use_default_bypass: Option<bool>, pub use_default_bypass: Option<bool>,
pub system_proxy_bypass: Option<String>, pub system_proxy_bypass: Option<String>,
pub proxy_guard_duration: Option<u64>, pub proxy_guard_duration: Option<u64>,
@ -472,6 +478,7 @@ impl From<IVerge> for IVergeResponse {
enable_silent_start: verge.enable_silent_start, enable_silent_start: verge.enable_silent_start,
enable_system_proxy: verge.enable_system_proxy, enable_system_proxy: verge.enable_system_proxy,
enable_proxy_guard: verge.enable_proxy_guard, enable_proxy_guard: verge.enable_proxy_guard,
enable_global_hotkey: verge.enable_global_hotkey,
use_default_bypass: verge.use_default_bypass, use_default_bypass: verge.use_default_bypass,
system_proxy_bypass: verge.system_proxy_bypass, system_proxy_bypass: verge.system_proxy_bypass,
proxy_guard_duration: verge.proxy_guard_duration, proxy_guard_duration: verge.proxy_guard_duration,

View File

@ -45,6 +45,13 @@ impl Hotkey {
Ok(()) Ok(())
} }
pub fn reset(&self) -> Result<()> {
let app_handle = handle::Handle::global().app_handle().unwrap();
let manager = app_handle.global_shortcut();
manager.unregister_all()?;
Ok(())
}
pub fn register(&self, hotkey: &str, func: &str) -> Result<()> { pub fn register(&self, hotkey: &str, func: &str) -> Result<()> {
let app_handle = handle::Handle::global().app_handle().unwrap(); let app_handle = handle::Handle::global().app_handle().unwrap();
let manager = app_handle.global_shortcut(); let manager = app_handle.global_shortcut();
@ -76,7 +83,17 @@ impl Hotkey {
} }
} }
} else { } else {
f(); if let Some(window) = app_handle.get_webview_window("main") {
let is_enable_global_hotkey = { Config::verge().latest().enable_global_hotkey} .unwrap();
let is_visible = window.is_visible().unwrap_or(false);
let is_focused = window.is_focused().unwrap_or(false);
if is_enable_global_hotkey {
f();
} else if is_focused && is_visible {
f();
}
}
} }
} }
}); });
@ -89,7 +106,6 @@ impl Hotkey {
let app_handle = handle::Handle::global().app_handle().unwrap(); let app_handle = handle::Handle::global().app_handle().unwrap();
let manager = app_handle.global_shortcut(); let manager = app_handle.global_shortcut();
manager.unregister(hotkey)?; manager.unregister(hotkey)?;
log::debug!(target: "app", "unregister hotkey {hotkey}"); log::debug!(target: "app", "unregister hotkey {hotkey}");
Ok(()) Ok(())
} }

View File

@ -209,10 +209,12 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
let http_enabled = patch.verge_http_enabled; let http_enabled = patch.verge_http_enabled;
let http_port = patch.verge_port; let http_port = patch.verge_port;
let enable_tray_speed = patch.enable_tray_speed; let enable_tray_speed = patch.enable_tray_speed;
let enable_global_hotkey = patch.enable_global_hotkey;
let res: std::result::Result<(), anyhow::Error> = { let res: std::result::Result<(), anyhow::Error> = {
let mut should_restart_core = false; let mut should_restart_core = false;
let mut should_update_clash_config = false; let mut should_update_clash_config = false;
let mut should_update_verge_config = false;
let mut should_update_launch = false; let mut should_update_launch = false;
let mut should_update_sysproxy = false; let mut should_update_sysproxy = false;
let mut should_update_systray_icon = false; let mut should_update_systray_icon = false;
@ -226,12 +228,13 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
should_update_systray_tooltip = true; should_update_systray_tooltip = true;
should_update_systray_icon = true; should_update_systray_icon = true;
} }
if enable_global_hotkey.is_some() {
should_update_verge_config = true;
}
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
if redir_enabled.is_some() || redir_port.is_some() { if redir_enabled.is_some() || redir_port.is_some() {
should_restart_core = true; should_restart_core = true;
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
if tproxy_enabled.is_some() || tproxy_port.is_some() { if tproxy_enabled.is_some() || tproxy_port.is_some() {
should_restart_core = true; should_restart_core = true;
@ -286,6 +289,10 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
CoreManager::global().update_config().await?; CoreManager::global().update_config().await?;
handle::Handle::refresh_clash(); handle::Handle::refresh_clash();
} }
if should_update_verge_config {
Config::verge().draft().enable_global_hotkey = enable_global_hotkey;
handle::Handle::refresh_verge();
}
if should_update_launch { if should_update_launch {
sysopt::Sysopt::global().update_launch()?; sysopt::Sysopt::global().update_launch()?;
} }

View File

@ -6,6 +6,7 @@ mod feat;
mod utils; mod utils;
use crate::core::hotkey; use crate::core::hotkey;
use crate::utils::{resolve, resolve::resolve_scheme, server}; use crate::utils::{resolve, resolve::resolve_scheme, server};
use config::Config;
use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_deep_link::DeepLinkExt; use tauri_plugin_deep_link::DeepLinkExt;
@ -159,6 +160,12 @@ pub fn run() {
{ {
log_err!(hotkey::Hotkey::global().register("Control+Q", "quit")); log_err!(hotkey::Hotkey::global().register("Control+Q", "quit"));
}; };
{
let is_enable_global_hotkey = { Config::verge().draft().enable_global_hotkey }.unwrap();
if !is_enable_global_hotkey {
log_err!(hotkey::Hotkey::global().init())
}
}
} }
tauri::WindowEvent::Focused(false) => { tauri::WindowEvent::Focused(false) => {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@ -169,6 +176,14 @@ pub fn run() {
{ {
log_err!(hotkey::Hotkey::global().unregister("Control+Q")); log_err!(hotkey::Hotkey::global().unregister("Control+Q"));
}; };
{
let is_enable_global_hotkey = { Config::verge().draft().enable_global_hotkey }.unwrap();
if !is_enable_global_hotkey {
log_err!(hotkey::Hotkey::global().reset())
} else {
log_err!(hotkey::Hotkey::global().init())
}
}
} }
tauri::WindowEvent::Destroyed => { tauri::WindowEvent::Destroyed => {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]

View File

@ -1,7 +1,7 @@
import { forwardRef, useImperativeHandle, useState } from "react"; import { forwardRef, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { styled, Typography } from "@mui/material"; import { styled, Typography, Switch } from "@mui/material";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import { BaseDialog, DialogRef, Notice } from "@/components/base"; import { BaseDialog, DialogRef, Notice } from "@/components/base";
import { HotkeyInput } from "./hotkey-input"; import { HotkeyInput } from "./hotkey-input";
@ -29,6 +29,9 @@ export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
const { verge, patchVerge } = useVerge(); const { verge, patchVerge } = useVerge();
const [hotkeyMap, setHotkeyMap] = useState<Record<string, string[]>>({}); const [hotkeyMap, setHotkeyMap] = useState<Record<string, string[]>>({});
const [enableGlobalHotkey, setEnableHotkey] = useState(
verge?.enable_global_hotkey ?? true,
);
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
open: () => { open: () => {
@ -69,7 +72,10 @@ export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
.filter(Boolean); .filter(Boolean);
try { try {
await patchVerge({ hotkeys }); await patchVerge({
hotkeys,
enable_global_hotkey: enableGlobalHotkey,
});
setOpen(false); setOpen(false);
} catch (err: any) { } catch (err: any) {
Notice.error(err.message || err.toString()); Notice.error(err.message || err.toString());
@ -80,13 +86,22 @@ export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
<BaseDialog <BaseDialog
open={open} open={open}
title={t("Hotkey Setting")} title={t("Hotkey Setting")}
contentSx={{ width: 450, maxHeight: 330 }} contentSx={{ width: 450, maxHeight: 380 }}
okBtn={t("Save")} okBtn={t("Save")}
cancelBtn={t("Cancel")} cancelBtn={t("Cancel")}
onClose={() => setOpen(false)} onClose={() => setOpen(false)}
onCancel={() => setOpen(false)} onCancel={() => setOpen(false)}
onOk={onSave} onOk={onSave}
> >
<ItemWrapper style={{ marginBottom: 16 }}>
<Typography>{t("Enable Global Hotkey")}</Typography>
<Switch
edge="end"
checked={enableGlobalHotkey}
onChange={(e) => setEnableHotkey(e.target.checked)}
/>
</ItemWrapper>
{HOTKEY_FUNC.map((func) => ( {HOTKEY_FUNC.map((func) => (
<ItemWrapper key={func}> <ItemWrapper key={func}>
<Typography>{t(func)}</Typography> <Typography>{t(func)}</Typography>

View File

@ -328,6 +328,7 @@
"Default Latency Test Info": "仅用于 HTTP 客户端请求测试,不会对配置文件产生影响", "Default Latency Test Info": "仅用于 HTTP 客户端请求测试,不会对配置文件产生影响",
"Default Latency Timeout": "测试超时时间", "Default Latency Timeout": "测试超时时间",
"Hotkey Setting": "热键设置", "Hotkey Setting": "热键设置",
"Enable Global Hotkey": "启用全局热键",
"open_or_close_dashboard": "打开/关闭面板", "open_or_close_dashboard": "打开/关闭面板",
"clash_mode_rule": "规则模式", "clash_mode_rule": "规则模式",
"clash_mode_global": "全局模式", "clash_mode_global": "全局模式",

View File

@ -711,6 +711,7 @@ interface IVergeConfig {
enable_auto_launch?: boolean; enable_auto_launch?: boolean;
enable_silent_start?: boolean; enable_silent_start?: boolean;
enable_system_proxy?: boolean; enable_system_proxy?: boolean;
enable_global_hotkey?: boolean;
proxy_auto_config?: boolean; proxy_auto_config?: boolean;
pac_file_content?: string; pac_file_content?: string;
enable_random_port?: boolean; enable_random_port?: boolean;