remove keybinding manager
This commit is contained in:
parent
2f38e407a5
commit
97700cda60
4 changed files with 46 additions and 203 deletions
|
|
@ -2,7 +2,6 @@ mod managers;
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use managers::audio::AudioRecordingManager;
|
use managers::audio::AudioRecordingManager;
|
||||||
use managers::keybinding::KeyBindingManager;
|
|
||||||
use managers::transcription::TranscriptionManager;
|
use managers::transcription::TranscriptionManager;
|
||||||
use rdev::{simulate, EventType, Key, SimulateError};
|
use rdev::{simulate, EventType, Key, SimulateError};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
@ -51,33 +50,6 @@ fn paste(text: String, app_handle: tauri::AppHandle) {
|
||||||
clipboard.write_text(&clipboard_content).unwrap();
|
clipboard.write_text(&clipboard_content).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_bindings(manager: &mut KeyBindingManager) {
|
|
||||||
manager.register(
|
|
||||||
"ctrl-meta".to_string(),
|
|
||||||
vec![Key::ControlRight, Key::MetaRight],
|
|
||||||
|ctx| {
|
|
||||||
info!("Ctrl+Meta pressed!");
|
|
||||||
ctx.recording_manager.try_start_recording("ctrl-meta");
|
|
||||||
None
|
|
||||||
},
|
|
||||||
|ctx| {
|
|
||||||
info!("release being called from ctrl-meta");
|
|
||||||
let ctx = ctx.clone();
|
|
||||||
Some(tauri::async_runtime::spawn(async move {
|
|
||||||
if let Some(samples) = ctx.recording_manager.stop_recording("ctrl-meta") {
|
|
||||||
match ctx.transcription_manager.transcribe(samples) {
|
|
||||||
Ok(transcription) => {
|
|
||||||
println!("Transcription: {}", transcription);
|
|
||||||
paste(transcription, ctx.app_handle.clone());
|
|
||||||
}
|
|
||||||
Err(err) => println!("Transcription error: {}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
@ -94,8 +66,6 @@ pub fn run() {
|
||||||
.plugin(tauri_plugin_macos_permissions::init())
|
.plugin(tauri_plugin_macos_permissions::init())
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
let app_handle = app.handle().clone();
|
|
||||||
|
|
||||||
let vad_path = app.path().resolve(
|
let vad_path = app.path().resolve(
|
||||||
"resources/silero_vad_v4.onnx",
|
"resources/silero_vad_v4.onnx",
|
||||||
tauri::path::BaseDirectory::Resource,
|
tauri::path::BaseDirectory::Resource,
|
||||||
|
|
@ -119,54 +89,54 @@ pub fn run() {
|
||||||
.expect("Failed to initialize transcription manager"),
|
.expect("Failed to initialize transcription manager"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let manager = Arc::new(Mutex::new(KeyBindingManager::new(
|
|
||||||
recording_manager.clone(),
|
|
||||||
transcription_manager.clone(),
|
|
||||||
// claude_client.clone(),
|
|
||||||
app_handle.clone(),
|
|
||||||
)));
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut manager = manager.lock().unwrap();
|
|
||||||
register_bindings(&mut manager);
|
|
||||||
}
|
|
||||||
|
|
||||||
let manager_clone = manager.clone();
|
|
||||||
tauri::async_runtime::spawn(async move {
|
|
||||||
rdev::listen(move |event| {
|
|
||||||
if let Ok(manager) = manager_clone.lock() {
|
|
||||||
manager.handle_event(&event);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
});
|
|
||||||
|
|
||||||
#[cfg(desktop)]
|
#[cfg(desktop)]
|
||||||
{
|
{
|
||||||
use tauri_plugin_global_shortcut::{Code, Modifiers, ShortcutState};
|
use tauri_plugin_global_shortcut::{Code, Modifiers, ShortcutState};
|
||||||
|
|
||||||
|
// Clone resources needed for the global shortcut handler
|
||||||
|
let rm_for_shortcut_handler = recording_manager.clone();
|
||||||
|
let tm_for_shortcut_handler = transcription_manager.clone();
|
||||||
|
|
||||||
app.handle().plugin(
|
app.handle().plugin(
|
||||||
tauri_plugin_global_shortcut::Builder::new()
|
tauri_plugin_global_shortcut::Builder::new()
|
||||||
.with_shortcuts(["ctrl+d", "alt+space"])?
|
.with_shortcuts(["alt+space"])? // Register "alt+space"
|
||||||
.with_handler(|app, shortcut, event| {
|
.with_handler(move |app_handle_from_plugin, shortcut, event| {
|
||||||
if event.state == ShortcutState::Pressed {
|
// Clone Arcs for potential async tasks to move ownership
|
||||||
if shortcut.matches(Modifiers::CONTROL, Code::KeyD) {
|
let recording_manager = rm_for_shortcut_handler.clone();
|
||||||
let _ = app.emit("shortcut-event", "Ctrl+D triggered");
|
let transcription_manager = tm_for_shortcut_handler.clone();
|
||||||
println!("Ctrl+D triggered");
|
let app_handle_for_async_tasks = app_handle_from_plugin.clone();
|
||||||
}
|
|
||||||
if shortcut.matches(Modifiers::ALT, Code::Space) {
|
if shortcut.matches(Modifiers::ALT, Code::Space) {
|
||||||
let _ = app.emit("shortcut-event", "Alt+Space triggered");
|
if event.state == ShortcutState::Pressed {
|
||||||
println!("Alt+Space triggered");
|
info!("Alt+Space pressed! (Global Shortcut)");
|
||||||
}
|
// Use the "alt-space" identifier for consistency
|
||||||
}
|
recording_manager.try_start_recording("alt-space");
|
||||||
if event.state == ShortcutState::Released {
|
} else if event.state == ShortcutState::Released {
|
||||||
if shortcut.matches(Modifiers::CONTROL, Code::KeyD) {
|
info!("Alt+Space released! (Global Shortcut)");
|
||||||
let _ = app.emit("shortcut-event", "Ctrl+D released");
|
tauri::async_runtime::spawn(async move {
|
||||||
println!("Ctrl+D released");
|
// recording_manager, transcription_manager, and app_handle_for_async_tasks are moved into this async block
|
||||||
}
|
if let Some(samples) =
|
||||||
if shortcut.matches(Modifiers::ALT, Code::Space) {
|
recording_manager.stop_recording("alt-space")
|
||||||
let _ = app.emit("shortcut-event", "Alt+Space released");
|
{
|
||||||
println!("Alt+Space released");
|
match transcription_manager.transcribe(samples) {
|
||||||
|
// Not .await, as transcribe is synchronous
|
||||||
|
Ok(transcription) => {
|
||||||
|
println!(
|
||||||
|
"Global Shortcut Transcription: {}",
|
||||||
|
transcription
|
||||||
|
);
|
||||||
|
paste(
|
||||||
|
transcription,
|
||||||
|
app_handle_for_async_tasks,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(err) => println!(
|
||||||
|
"Global Shortcut Transcription error: {}",
|
||||||
|
err
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
use super::audio::AudioRecordingManager;
|
|
||||||
use super::transcription::TranscriptionManager;
|
|
||||||
use rdev::{EventType, Key};
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
type KeySet = HashSet<Key>;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct BindingContext {
|
|
||||||
pub recording_manager: Arc<AudioRecordingManager>,
|
|
||||||
pub transcription_manager: Arc<TranscriptionManager>,
|
|
||||||
// pub anthropic: Arc<anthropic::Client>,
|
|
||||||
pub app_handle: tauri::AppHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct KeyBinding {
|
|
||||||
id: String,
|
|
||||||
keys: KeySet,
|
|
||||||
on_press: Box<
|
|
||||||
dyn Fn(&BindingContext) -> Option<tauri::async_runtime::JoinHandle<()>> + Send + 'static,
|
|
||||||
>,
|
|
||||||
on_release: Box<
|
|
||||||
dyn Fn(&BindingContext) -> Option<tauri::async_runtime::JoinHandle<()>> + Send + 'static,
|
|
||||||
>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyBinding {
|
|
||||||
fn new<F, G>(id: String, keys: Vec<Key>, on_press: F, on_release: G) -> Self
|
|
||||||
where
|
|
||||||
F: Fn(&BindingContext) -> Option<tauri::async_runtime::JoinHandle<()>> + Send + 'static,
|
|
||||||
G: Fn(&BindingContext) -> Option<tauri::async_runtime::JoinHandle<()>> + Send + 'static,
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
id,
|
|
||||||
keys: keys.into_iter().collect(),
|
|
||||||
on_press: Box::new(on_press),
|
|
||||||
on_release: Box::new(on_release),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct KeyBindingManager {
|
|
||||||
bindings: Vec<KeyBinding>,
|
|
||||||
global_pressed: Arc<Mutex<HashSet<Key>>>,
|
|
||||||
active_bindings: Arc<Mutex<HashSet<String>>>,
|
|
||||||
context: BindingContext,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyBindingManager {
|
|
||||||
pub fn new(
|
|
||||||
recording_manager: Arc<AudioRecordingManager>,
|
|
||||||
transcription_manager: Arc<TranscriptionManager>,
|
|
||||||
// anthropic: Arc<anthropic::Client>,
|
|
||||||
app_handle: tauri::AppHandle,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
bindings: Vec::new(),
|
|
||||||
global_pressed: Arc::new(Mutex::new(HashSet::new())),
|
|
||||||
active_bindings: Arc::new(Mutex::new(HashSet::new())),
|
|
||||||
context: BindingContext {
|
|
||||||
recording_manager,
|
|
||||||
transcription_manager,
|
|
||||||
// anthropic,
|
|
||||||
app_handle,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register<F, G>(&mut self, id: String, keys: Vec<Key>, on_press: F, on_release: G)
|
|
||||||
where
|
|
||||||
F: Fn(&BindingContext) -> Option<tauri::async_runtime::JoinHandle<()>> + Send + 'static,
|
|
||||||
G: Fn(&BindingContext) -> Option<tauri::async_runtime::JoinHandle<()>> + Send + 'static,
|
|
||||||
{
|
|
||||||
self.bindings
|
|
||||||
.push(KeyBinding::new(id, keys, on_press, on_release));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_event(&self, event: &rdev::Event) {
|
|
||||||
if let EventType::KeyPress(key) | EventType::KeyRelease(key) = event.event_type {
|
|
||||||
let mut is_press = matches!(event.event_type, EventType::KeyPress(_));
|
|
||||||
|
|
||||||
// Update global pressed keys
|
|
||||||
let mut pressed = self.global_pressed.lock().unwrap();
|
|
||||||
|
|
||||||
// If we get a release event for a key that wasn't pressed,
|
|
||||||
// treat it as a press event instead
|
|
||||||
if !is_press && !pressed.contains(&key) {
|
|
||||||
is_press = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_press {
|
|
||||||
pressed.insert(key);
|
|
||||||
} else {
|
|
||||||
pressed.remove(&key);
|
|
||||||
}
|
|
||||||
let pressed_snapshot = pressed.clone();
|
|
||||||
drop(pressed);
|
|
||||||
|
|
||||||
// Check binding states
|
|
||||||
let mut active = self.active_bindings.lock().unwrap();
|
|
||||||
for binding in &self.bindings {
|
|
||||||
let is_active = active.contains(&binding.id);
|
|
||||||
let all_required_pressed = binding.keys.is_subset(&pressed_snapshot);
|
|
||||||
let any_required_key = binding.keys.contains(&key);
|
|
||||||
|
|
||||||
// Only process if this event is for a key we care about
|
|
||||||
if any_required_key {
|
|
||||||
match (all_required_pressed, is_active) {
|
|
||||||
(true, false) => {
|
|
||||||
(binding.on_press)(&self.context);
|
|
||||||
active.insert(binding.id.clone());
|
|
||||||
}
|
|
||||||
(false, true) => {
|
|
||||||
// Only trigger release if none of the required keys are pressed
|
|
||||||
let any_binding_key_pressed =
|
|
||||||
binding.keys.iter().any(|k| pressed_snapshot.contains(k));
|
|
||||||
|
|
||||||
if !any_binding_key_pressed {
|
|
||||||
(binding.on_release)(&self.context);
|
|
||||||
active.remove(&binding.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +1,2 @@
|
||||||
pub mod audio;
|
pub mod audio;
|
||||||
pub mod keybinding;
|
|
||||||
pub mod transcription;
|
pub mod transcription;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,10 @@ function App() {
|
||||||
}
|
}
|
||||||
setHasAccessibility(hasPermissions);
|
setHasAccessibility(hasPermissions);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
register("ctrl+d", () => {
|
||||||
|
console.log("Ctrl+D pressed");
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue