mirror of
https://github.com/0PandaDEV/Qopy.git
synced 2025-04-22 05:34:04 +02:00
feat: custom hotkey with global shortcut
This commit is contained in:
parent
02becca60d
commit
ba743f7961
7 changed files with 774 additions and 297 deletions
2
app.vue
2
app.vue
|
@ -11,12 +11,14 @@ import { onMounted } from 'vue'
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await listen('change_keybind', async () => {
|
await listen('change_keybind', async () => {
|
||||||
|
console.log("change_keybind");
|
||||||
await navigateTo('/keybind')
|
await navigateTo('/keybind')
|
||||||
await app.show();
|
await app.show();
|
||||||
await window.getCurrentWindow().show();
|
await window.getCurrentWindow().show();
|
||||||
})
|
})
|
||||||
|
|
||||||
await listen('main_route', async () => {
|
await listen('main_route', async () => {
|
||||||
|
console.log("main_route");
|
||||||
await navigateTo('/')
|
await navigateTo('/')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,32 +1,45 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="bg">
|
<div class="bg">
|
||||||
<div class="back">
|
<div class="back">
|
||||||
<img @click="router.push('/')" src="../public/back_arrow.svg">
|
<img @click="router.push('/')" src="../public/back_arrow.svg" />
|
||||||
<p>Back</p>
|
<p>Back</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom-bar">
|
<div class="bottom-bar">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<img alt="" class="logo" src="../public/logo.png" width="18px">
|
<img alt="" class="logo" src="../public/logo.png" width="18px" />
|
||||||
<p>Qopy</p>
|
<p>Qopy</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div @click="saveKeybind" class="actions">
|
<div @click="saveKeybind" class="actions">
|
||||||
<p>Save</p>
|
<p>Save</p>
|
||||||
<div>
|
<div>
|
||||||
<img alt="" src="../public/ctrl.svg" v-if="os === 'windows' || os === 'linux'">
|
<img alt="" src="../public/cmd.svg" v-if="os === 'macos'" />
|
||||||
<img alt="" src="../public/cmd.svg" v-if="os === 'macos'">
|
<img alt="" src="../public/ctrl.svg" v-if="os === 'linux' || os === 'windows'" />
|
||||||
<img alt="" src="../public/enter.svg">
|
<img alt="" src="../public/enter.svg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="keybind-container">
|
<div class="keybind-container">
|
||||||
<h2 class="title">Record a new Hotkey</h2>
|
<h2 class="title">Record a new Hotkey</h2>
|
||||||
<div @blur="onBlur" @focus="onFocus" @keydown="onKeyDown" @keyup="onKeyUp" class="keybind-input"
|
<div
|
||||||
ref="keybindInput" tabindex="0">
|
@blur="onBlur"
|
||||||
<span class="key" v-if="currentKeybind.length === 0">Click here</span>
|
@focus="onFocus"
|
||||||
|
@keydown="onKeyDown"
|
||||||
|
class="keybind-input"
|
||||||
|
ref="keybindInput"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span class="key" v-if="keybind.length === 0">Click here</span>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span :key="index" class="key" v-for="(key, index) in currentKeybind">{{ keyToDisplay(key) }}</span>
|
<span
|
||||||
|
:key="index"
|
||||||
|
class="key"
|
||||||
|
:class="{ modifier: isModifier(key) }"
|
||||||
|
v-for="(key, index) in keybind"
|
||||||
|
>
|
||||||
|
{{ keyToDisplay(key) }}
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -35,42 +48,59 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||||
import { platform } from '@tauri-apps/plugin-os';
|
import { platform } from '@tauri-apps/plugin-os';
|
||||||
import { onMounted, onUnmounted, ref } from 'vue';
|
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const activeModifiers = ref<Set<string>>(new Set());
|
const activeModifiers = reactive<Set<string>>(new Set());
|
||||||
const currentKeybind = ref<string[]>([]);
|
|
||||||
const isKeybindInputFocused = ref(false);
|
const isKeybindInputFocused = ref(false);
|
||||||
|
const keybind = ref<string[]>([]);
|
||||||
const keybindInput = ref<HTMLElement | null>(null);
|
const keybindInput = ref<HTMLElement | null>(null);
|
||||||
const lastNonModifier = ref('');
|
const lastBlurTime = ref(0);
|
||||||
const os = ref('');
|
const os = ref('');
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const lastBlurTime = ref(0);
|
|
||||||
|
|
||||||
const keyToDisplayMap: Record<string, string> = {
|
const keyToDisplayMap: Record<string, string> = {
|
||||||
" ": "Space",
|
' ': 'Space',
|
||||||
Alt: "Alt",
|
Alt: 'Alt',
|
||||||
ArrowDown: "↓",
|
AltLeft: 'Alt L',
|
||||||
ArrowLeft: "←",
|
AltRight: 'Alt R',
|
||||||
ArrowRight: "→",
|
ArrowDown: '↓',
|
||||||
ArrowUp: "↑",
|
ArrowLeft: '←',
|
||||||
Control: "Ctrl",
|
ArrowRight: '→',
|
||||||
Enter: "↵",
|
ArrowUp: '↑',
|
||||||
Meta: "Meta",
|
Control: 'Ctrl',
|
||||||
Shift: "⇧",
|
ControlLeft: 'Ctrl L',
|
||||||
|
ControlRight: 'Ctrl R',
|
||||||
|
Enter: '↵',
|
||||||
|
Meta: 'Meta',
|
||||||
|
MetaLeft: 'Meta L',
|
||||||
|
MetaRight: 'Meta R',
|
||||||
|
Shift: '⇧',
|
||||||
|
ShiftLeft: '⇧ L',
|
||||||
|
ShiftRight: '⇧ R',
|
||||||
};
|
};
|
||||||
|
|
||||||
const modifierKeySet = new Set(["Alt", "Control", "Meta", "Shift"]);
|
const modifierKeySet = new Set([
|
||||||
|
'Alt', 'AltLeft', 'AltRight',
|
||||||
|
'Control', 'ControlLeft', 'ControlRight',
|
||||||
|
'Meta', 'MetaLeft', 'MetaRight',
|
||||||
|
'Shift', 'ShiftLeft', 'ShiftRight'
|
||||||
|
]);
|
||||||
|
|
||||||
function keyToDisplay(key: string): string {
|
const isModifier = (key: string): boolean => {
|
||||||
return keyToDisplayMap[key] || key.toUpperCase();
|
return modifierKeySet.has(key);
|
||||||
}
|
};
|
||||||
|
|
||||||
function updateCurrentKeybind() {
|
const keyToDisplay = (key: string): string => {
|
||||||
const modifiers = Array.from(activeModifiers.value);
|
return keyToDisplayMap[key] || key;
|
||||||
currentKeybind.value = lastNonModifier.value ? [...modifiers, lastNonModifier.value] : modifiers;
|
};
|
||||||
}
|
|
||||||
|
const updateKeybind = () => {
|
||||||
|
const modifiers = Array.from(activeModifiers).sort();
|
||||||
|
const nonModifiers = keybind.value.filter(key => !isModifier(key));
|
||||||
|
keybind.value = [...modifiers, ...nonModifiers];
|
||||||
|
};
|
||||||
|
|
||||||
const onBlur = () => {
|
const onBlur = () => {
|
||||||
isKeybindInputFocused.value = false;
|
isKeybindInputFocused.value = false;
|
||||||
|
@ -79,46 +109,53 @@ const onBlur = () => {
|
||||||
|
|
||||||
const onFocus = () => {
|
const onFocus = () => {
|
||||||
isKeybindInputFocused.value = true;
|
isKeybindInputFocused.value = true;
|
||||||
activeModifiers.value.clear();
|
activeModifiers.clear();
|
||||||
lastNonModifier.value = '';
|
keybind.value = [];
|
||||||
updateCurrentKeybind();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const key = event.key;
|
const key = event.code;
|
||||||
|
|
||||||
if (key === "Escape") {
|
if (key === 'Escape') {
|
||||||
if (keybindInput.value) {
|
if (keybindInput.value) {
|
||||||
keybindInput.value.blur();
|
keybindInput.value.blur();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modifierKeySet.has(key)) {
|
if (isModifier(key)) {
|
||||||
activeModifiers.value.add(key);
|
activeModifiers.add(key);
|
||||||
} else {
|
} else if (!keybind.value.includes(key)) {
|
||||||
lastNonModifier.value = key;
|
keybind.value = keybind.value.filter(k => isModifier(k));
|
||||||
|
keybind.value.push(key);
|
||||||
}
|
}
|
||||||
updateCurrentKeybind();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onKeyUp = (event: KeyboardEvent) => {
|
updateKeybind();
|
||||||
event.preventDefault();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveKeybind = async () => {
|
const saveKeybind = async () => {
|
||||||
console.log("New:", currentKeybind.value);
|
console.log('New:', keybind.value);
|
||||||
console.log("Old: " + new Array(await invoke("get_keybind")));
|
const oldKeybind = await invoke<string[]>('get_keybind');
|
||||||
await invoke("save_keybind", { keybind: currentKeybind.value})
|
console.log('Old:', oldKeybind);
|
||||||
|
await invoke('save_keybind', { keybind: keybind.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGlobalKeyDown = (event: KeyboardEvent) => {
|
const handleGlobalKeyDown = (event: KeyboardEvent) => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if ((os.value === 'macos' ? event.metaKey : event.ctrlKey) && event.key === 'Enter' && !isKeybindInputFocused.value) {
|
if (
|
||||||
|
(os.value === 'macos'
|
||||||
|
? (event.code === 'MetaLeft' || event.code === 'MetaRight') && event.key === 'Enter'
|
||||||
|
: (event.code === 'ControlLeft' || event.code === 'ControlRight') && event.key === 'Enter') &&
|
||||||
|
!isKeybindInputFocused.value
|
||||||
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
saveKeybind();
|
saveKeybind();
|
||||||
} else if (event.key === 'Escape' && !isKeybindInputFocused.value && now - lastBlurTime.value > 100) {
|
} else if (
|
||||||
|
event.key === 'Escape' &&
|
||||||
|
!isKeybindInputFocused.value &&
|
||||||
|
now - lastBlurTime.value > 100
|
||||||
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
router.push('/');
|
router.push('/');
|
||||||
}
|
}
|
||||||
|
|
723
src-tauri/Cargo.lock
generated
723
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,13 +1,13 @@
|
||||||
[package]
|
[package]
|
||||||
name = "qopy"
|
name = "qopy"
|
||||||
version = "0.1.1"
|
version = "0.2.0"
|
||||||
description = "Qopy"
|
description = "Qopy"
|
||||||
authors = ["pandadev"]
|
authors = ["pandadev"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.70"
|
rust-version = "1.70"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.0.1", features = [] }
|
tauri-build = { version = "2.0.3", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2.0.1", features = [
|
tauri = { version = "2.0.1", features = [
|
||||||
|
@ -15,29 +15,30 @@ tauri = { version = "2.0.1", features = [
|
||||||
"tray-icon",
|
"tray-icon",
|
||||||
"image-png",
|
"image-png",
|
||||||
] }
|
] }
|
||||||
tauri-plugin-sql = { version = "2.0.1", features = ["sqlite"] }
|
tauri-plugin-sql = { version = "2.0.2", features = ["sqlite"] }
|
||||||
tauri-plugin-autostart = "2.0.1"
|
tauri-plugin-autostart = "2.0.1"
|
||||||
tauri-plugin-os = "2.0.1"
|
tauri-plugin-os = "2.0.1"
|
||||||
tauri-plugin-updater = "2.0.2"
|
tauri-plugin-updater = "2.0.2"
|
||||||
tauri-plugin-dialog = "2.0.1"
|
tauri-plugin-dialog = "2.0.3"
|
||||||
tauri-plugin-fs = "2.0.1"
|
tauri-plugin-fs = "2.0.3"
|
||||||
tauri-plugin-clipboard = "2.1.9"
|
tauri-plugin-clipboard = "2.1.11"
|
||||||
tauri-plugin-prevent-default = "0.6.1"
|
tauri-plugin-prevent-default = "0.7.5"
|
||||||
tauri-plugin-global-shortcut = "2.0.1"
|
tauri-plugin-global-shortcut = "2.0.1"
|
||||||
sqlx = { version = "0.8.2", features = ["runtime-tokio-native-tls", "sqlite"] }
|
sqlx = { version = "0.8.2", features = ["runtime-tokio-native-tls", "sqlite"] }
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
serde = { version = "1.0.215", features = ["derive"] }
|
||||||
tokio = { version = "1.40.0", features = ["full"] }
|
tokio = { version = "1.41.1", features = ["full"] }
|
||||||
serde_json = "1.0.128"
|
serde_json = "1.0.132"
|
||||||
rdev = "0.5.3"
|
rdev = "0.5.3"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
image = "0.25.2"
|
image = "0.25.5"
|
||||||
reqwest = { version = "0.12.8", features = ["blocking"] }
|
reqwest = { version = "0.12.9", features = ["blocking"] }
|
||||||
url = "2.5.2"
|
url = "2.5.3"
|
||||||
regex = "1.11.0"
|
regex = "1.11.1"
|
||||||
sha2 = "0.10.6"
|
sha2 = "0.10.6"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
time = "0.3"
|
time = "0.3"
|
||||||
|
global-hotkey = "0.6.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
custom-protocol = ["tauri/custom-protocol"]
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use rand::distributions::Alphanumeric;
|
use rand::distributions::Alphanumeric;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json;
|
||||||
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};
|
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use tauri::Manager;
|
use tauri::{Manager, Emitter};
|
||||||
use tauri::State;
|
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
|
@ -118,19 +118,23 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn save_keybind(
|
pub async fn save_keybind(
|
||||||
|
app_handle: tauri::AppHandle,
|
||||||
keybind: Vec<String>,
|
keybind: Vec<String>,
|
||||||
pool: State<'_, SqlitePool>,
|
pool: tauri::State<'_, SqlitePool>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let json = serde_json::to_string(&keybind).map_err(|e| e.to_string())?;
|
let json = serde_json::to_string(&keybind).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
sqlx::query(
|
sqlx::query("INSERT OR REPLACE INTO settings (key, value) VALUES ('keybind', ?)")
|
||||||
"INSERT OR REPLACE INTO settings (key, value) VALUES ('keybind', ?)"
|
|
||||||
)
|
|
||||||
.bind(json)
|
.bind(json)
|
||||||
.execute(&*pool)
|
.execute(&*pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,18 +142,20 @@ pub async fn save_keybind(
|
||||||
pub async fn get_keybind(app_handle: tauri::AppHandle) -> Result<Vec<String>, String> {
|
pub async fn get_keybind(app_handle: tauri::AppHandle) -> Result<Vec<String>, String> {
|
||||||
let pool = app_handle.state::<SqlitePool>();
|
let pool = app_handle.state::<SqlitePool>();
|
||||||
|
|
||||||
let result = sqlx::query_scalar::<_, String>(
|
let result =
|
||||||
"SELECT value FROM settings WHERE key = 'keybind'"
|
sqlx::query_scalar::<_, String>("SELECT value FROM settings WHERE key = 'keybind'")
|
||||||
)
|
|
||||||
.fetch_optional(&*pool)
|
.fetch_optional(&*pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Some(json) => {
|
Some(json) => {
|
||||||
let setting: KeybindSetting = serde_json::from_str(&json).map_err(|e| e.to_string())?;
|
let keybind: Vec<String> = serde_json::from_str(&json).map_err(|e| e.to_string())?;
|
||||||
Ok(setting.keybind)
|
Ok(keybind)
|
||||||
},
|
}
|
||||||
None => Ok(vec!["Meta".to_string(), "V".to_string()]),
|
None => {
|
||||||
|
let default_keybind = vec!["Meta".to_string(), "V".to_string()];
|
||||||
|
Ok(default_keybind)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +1,104 @@
|
||||||
use crate::api::database::get_keybind;
|
use crate::api::database::get_keybind;
|
||||||
use crate::utils::commands::center_window_on_current_monitor;
|
use crate::utils::commands::center_window_on_current_monitor;
|
||||||
use rdev::{listen, EventType, Key};
|
use global_hotkey::{
|
||||||
use tauri::Manager;
|
hotkey::{Code, HotKey, Modifiers},
|
||||||
|
GlobalHotKeyEvent, GlobalHotKeyManager,
|
||||||
|
};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use tauri::{AppHandle, Listener, Manager};
|
||||||
|
|
||||||
fn key_to_string(key: &Key) -> String {
|
pub fn setup(app_handle: tauri::AppHandle) {
|
||||||
format!("{:?}", key)
|
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)]
|
fn register_shortcut(
|
||||||
pub fn setup(app_handle: tauri::AppHandle) {
|
_app_handle: &tauri::AppHandle,
|
||||||
std::thread::spawn(move || {
|
shortcut: &str,
|
||||||
let keybind = tauri::async_runtime::block_on(async { get_keybind(app_handle.clone()).await.unwrap_or_default() });
|
) -> 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| {
|
for part in shortcut.split('+') {
|
||||||
match event.event_type {
|
let part = part;
|
||||||
EventType::KeyPress(key) => {
|
if part.to_lowercase().starts_with("ctrl") || part.to_lowercase().starts_with("control") {
|
||||||
if let Some(index) = keybind.iter().position(|k| k == &key_to_string(&key)) {
|
modifiers |= Modifiers::CONTROL;
|
||||||
pressed_keys[index] = true;
|
} 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))?,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventType::KeyRelease(key) => {
|
|
||||||
if let Some(index) = keybind.iter().position(|k| k == &key_to_string(&key)) {
|
|
||||||
pressed_keys[index] = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pressed_keys.iter().all(|&k| k) {
|
Ok(HotKey::new(Some(modifiers), code.unwrap()))
|
||||||
pressed_keys.iter_mut().for_each(|k| *k = false);
|
}
|
||||||
|
|
||||||
|
fn handle_hotkey_event(app_handle: &AppHandle) {
|
||||||
let window = app_handle.get_webview_window("main").unwrap();
|
let window = app_handle.get_webview_window("main").unwrap();
|
||||||
|
if window.is_visible().unwrap() {
|
||||||
|
window.hide().unwrap();
|
||||||
|
} else {
|
||||||
window.show().unwrap();
|
window.show().unwrap();
|
||||||
window.set_focus().unwrap();
|
window.set_focus().unwrap();
|
||||||
center_window_on_current_monitor(&window);
|
center_window_on_current_monitor(&window);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
});
|
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"productName": "Qopy",
|
"productName": "Qopy",
|
||||||
"version": "0.1.1",
|
"version": "0.2.0",
|
||||||
"identifier": "net.pandadev.qopy",
|
"identifier": "net.pandadev.qopy",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../dist",
|
"frontendDist": "../dist",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue