feat: custom hotkey with global shortcut

This commit is contained in:
PandaDEV 2024-11-15 17:39:20 +10:00
parent 02becca60d
commit ba743f7961
No known key found for this signature in database
GPG key ID: 13EFF9BAF70EE75C
7 changed files with 774 additions and 297 deletions

View file

@ -1,10 +1,10 @@
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use serde::{Deserialize, Serialize};
use serde_json;
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};
use std::fs;
use tauri::Manager;
use tauri::State;
use tauri::{Manager, Emitter};
use tokio::runtime::Runtime;
#[derive(Deserialize, Serialize)]
@ -118,18 +118,22 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
#[tauri::command]
pub async fn save_keybind(
app_handle: tauri::AppHandle,
keybind: Vec<String>,
pool: State<'_, SqlitePool>,
pool: tauri::State<'_, SqlitePool>,
) -> Result<(), String> {
let json = serde_json::to_string(&keybind).map_err(|e| e.to_string())?;
sqlx::query(
"INSERT OR REPLACE INTO settings (key, value) VALUES ('keybind', ?)"
)
.bind(json)
.execute(&*pool)
.await
.map_err(|e| e.to_string())?;
sqlx::query("INSERT OR REPLACE INTO settings (key, value) VALUES ('keybind', ?)")
.bind(json)
.execute(&*pool)
.await
.map_err(|e| e.to_string())?;
let keybind_str = keybind.join("+");
app_handle
.emit("update-shortcut", keybind_str)
.map_err(|e| e.to_string())?;
Ok(())
}
@ -138,18 +142,20 @@ pub async fn save_keybind(
pub async fn get_keybind(app_handle: tauri::AppHandle) -> Result<Vec<String>, String> {
let pool = app_handle.state::<SqlitePool>();
let result = sqlx::query_scalar::<_, String>(
"SELECT value FROM settings WHERE key = 'keybind'"
)
.fetch_optional(&*pool)
.await
.map_err(|e| e.to_string())?;
let result =
sqlx::query_scalar::<_, String>("SELECT value FROM settings WHERE key = 'keybind'")
.fetch_optional(&*pool)
.await
.map_err(|e| e.to_string())?;
match result {
Some(json) => {
let setting: KeybindSetting = serde_json::from_str(&json).map_err(|e| e.to_string())?;
Ok(setting.keybind)
},
None => Ok(vec!["Meta".to_string(), "V".to_string()]),
let keybind: Vec<String> = serde_json::from_str(&json).map_err(|e| e.to_string())?;
Ok(keybind)
}
None => {
let default_keybind = vec!["Meta".to_string(), "V".to_string()];
Ok(default_keybind)
}
}
}

View file

@ -1,44 +1,104 @@
use crate::api::database::get_keybind;
use crate::utils::commands::center_window_on_current_monitor;
use rdev::{listen, EventType, Key};
use tauri::Manager;
use global_hotkey::{
hotkey::{Code, HotKey, Modifiers},
GlobalHotKeyEvent, GlobalHotKeyManager,
};
use std::str::FromStr;
use tauri::{AppHandle, Listener, Manager};
fn key_to_string(key: &Key) -> String {
format!("{:?}", key)
pub fn setup(app_handle: tauri::AppHandle) {
let app_handle_clone = app_handle.clone();
tauri::async_runtime::spawn(async move {
match get_keybind(app_handle_clone.clone()).await {
Ok(keybind) => {
if !keybind.is_empty() {
let keybind_str = keybind.join("+");
println!("Keybind: {:?}", keybind_str);
if let Err(e) = register_shortcut(&app_handle_clone, &keybind_str) {
eprintln!("Error registering shortcut: {:?}", e);
}
}
}
Err(e) => {
eprintln!("Error getting keybind: {:?}", e);
}
}
});
let app_handle_for_listener = app_handle.clone();
app_handle.listen("update-shortcut", move |event| {
let payload_str = event.payload().to_string();
if let Err(e) = register_shortcut(&app_handle_for_listener, &payload_str) {
eprintln!("Error re-registering shortcut: {:?}", e);
}
});
let app_handle_for_hotkey = app_handle.clone();
tauri::async_runtime::spawn(async move {
loop {
if let Ok(_) = GlobalHotKeyEvent::receiver().recv() {
handle_hotkey_event(&app_handle_for_hotkey);
}
}
});
}
#[warn(dead_code)]
pub fn setup(app_handle: tauri::AppHandle) {
std::thread::spawn(move || {
let keybind = tauri::async_runtime::block_on(async { get_keybind(app_handle.clone()).await.unwrap_or_default() });
fn register_shortcut(
_app_handle: &tauri::AppHandle,
shortcut: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let manager = GlobalHotKeyManager::new()?;
let hotkey = parse_hotkey(shortcut)?;
manager.register(hotkey)?;
println!("Listening for keybind: {:?}", keybind);
println!("Listening for keybind: {}", shortcut);
Ok(())
}
let mut pressed_keys = vec![false; keybind.len()];
fn parse_hotkey(shortcut: &str) -> Result<HotKey, Box<dyn std::error::Error>> {
let mut modifiers = Modifiers::empty();
let mut code = None;
listen(move |event| {
match event.event_type {
EventType::KeyPress(key) => {
if let Some(index) = keybind.iter().position(|k| k == &key_to_string(&key)) {
pressed_keys[index] = true;
}
}
EventType::KeyRelease(key) => {
if let Some(index) = keybind.iter().position(|k| k == &key_to_string(&key)) {
pressed_keys[index] = false;
}
}
_ => {}
}
for part in shortcut.split('+') {
let part = part;
if part.to_lowercase().starts_with("ctrl") || part.to_lowercase().starts_with("control") {
modifiers |= Modifiers::CONTROL;
} else if part.to_lowercase().starts_with("alt") {
modifiers |= Modifiers::ALT;
} else if part.to_lowercase().starts_with("shift") {
modifiers |= Modifiers::SHIFT;
} else if part.to_lowercase().starts_with("super") || part.to_lowercase().starts_with("meta") || part.to_lowercase().starts_with("cmd") {
modifiers |= Modifiers::META;
} else {
let pascal_case_key = part
.split(|c: char| !c.is_alphanumeric())
.map(|word| {
let mut chars = word.chars();
let first_char = chars.next().unwrap().to_uppercase().collect::<String>();
let rest = chars.as_str();
first_char + rest
})
.collect::<String>();
code = Some(
Code::from_str(&pascal_case_key)
.map_err(|_| format!("Invalid key: {}", pascal_case_key))?,
);
}
}
if pressed_keys.iter().all(|&k| k) {
pressed_keys.iter_mut().for_each(|k| *k = false);
let window = app_handle.get_webview_window("main").unwrap();
window.show().unwrap();
window.set_focus().unwrap();
center_window_on_current_monitor(&window);
}
})
.unwrap();
});
}
Ok(HotKey::new(Some(modifiers), code.unwrap()))
}
fn handle_hotkey_event(app_handle: &AppHandle) {
let window = app_handle.get_webview_window("main").unwrap();
if window.is_visible().unwrap() {
window.hide().unwrap();
} else {
window.show().unwrap();
window.set_focus().unwrap();
center_window_on_current_monitor(&window);
}
}