remove keybinding manager

This commit is contained in:
CJ Pais 2025-05-06 11:46:47 -07:00
parent 2f38e407a5
commit 97700cda60
4 changed files with 46 additions and 203 deletions

View file

@ -2,7 +2,6 @@ mod managers;
use log::info;
use managers::audio::AudioRecordingManager;
use managers::keybinding::KeyBindingManager;
use managers::transcription::TranscriptionManager;
use rdev::{simulate, EventType, Key, SimulateError};
use std::sync::{Arc, Mutex};
@ -51,33 +50,6 @@ fn paste(text: String, app_handle: tauri::AppHandle) {
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)]
pub fn run() {
env_logger::init();
@ -94,8 +66,6 @@ pub fn run() {
.plugin(tauri_plugin_macos_permissions::init())
.plugin(tauri_plugin_opener::init())
.setup(move |app| {
let app_handle = app.handle().clone();
let vad_path = app.path().resolve(
"resources/silero_vad_v4.onnx",
tauri::path::BaseDirectory::Resource,
@ -119,54 +89,54 @@ pub fn run() {
.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)]
{
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(
tauri_plugin_global_shortcut::Builder::new()
.with_shortcuts(["ctrl+d", "alt+space"])?
.with_handler(|app, shortcut, event| {
if event.state == ShortcutState::Pressed {
if shortcut.matches(Modifiers::CONTROL, Code::KeyD) {
let _ = app.emit("shortcut-event", "Ctrl+D triggered");
println!("Ctrl+D triggered");
}
if shortcut.matches(Modifiers::ALT, Code::Space) {
let _ = app.emit("shortcut-event", "Alt+Space triggered");
println!("Alt+Space triggered");
}
}
if event.state == ShortcutState::Released {
if shortcut.matches(Modifiers::CONTROL, Code::KeyD) {
let _ = app.emit("shortcut-event", "Ctrl+D released");
println!("Ctrl+D released");
}
if shortcut.matches(Modifiers::ALT, Code::Space) {
let _ = app.emit("shortcut-event", "Alt+Space released");
println!("Alt+Space released");
.with_shortcuts(["alt+space"])? // Register "alt+space"
.with_handler(move |app_handle_from_plugin, shortcut, event| {
// Clone Arcs for potential async tasks to move ownership
let recording_manager = rm_for_shortcut_handler.clone();
let transcription_manager = tm_for_shortcut_handler.clone();
let app_handle_for_async_tasks = app_handle_from_plugin.clone();
if shortcut.matches(Modifiers::ALT, Code::Space) {
if event.state == ShortcutState::Pressed {
info!("Alt+Space pressed! (Global Shortcut)");
// Use the "alt-space" identifier for consistency
recording_manager.try_start_recording("alt-space");
} else if event.state == ShortcutState::Released {
info!("Alt+Space released! (Global Shortcut)");
tauri::async_runtime::spawn(async move {
// recording_manager, transcription_manager, and app_handle_for_async_tasks are moved into this async block
if let Some(samples) =
recording_manager.stop_recording("alt-space")
{
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
),
}
}
});
}
}
})

View file

@ -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);
}
}
_ => {}
}
}
}
}
}
}

View file

@ -1,3 +1,2 @@
pub mod audio;
pub mod keybinding;
pub mod transcription;

View file

@ -20,6 +20,10 @@ function App() {
}
setHasAccessibility(hasPermissions);
});
register("ctrl+d", () => {
console.log("Ctrl+D pressed");
});
}, []);
return (