Initial implementation of global shortcut

This commit is contained in:
Thorsten Sommer 2026-01-20 10:54:07 +01:00
parent c9f037bb2c
commit 6ed9338d23
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
4 changed files with 65 additions and 4 deletions

View File

@ -1,5 +1,7 @@
using AIStudio.Provider;
using AIStudio.Tools;
using AIStudio.Tools.MIME;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
@ -24,6 +26,9 @@ public partial class VoiceRecorder : MSGComponentBase
protected override async Task OnInitializedAsync()
{
// Register for global shortcut events:
this.ApplyFilters([], [Event.TAURI_EVENT_RECEIVED]);
await base.OnInitializedAsync();
try
@ -37,6 +42,37 @@ public partial class VoiceRecorder : MSGComponentBase
}
}
protected override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
{
switch (triggeredEvent)
{
case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.GLOBAL_SHORTCUT_PRESSED } tauriEvent:
// Check if this is the voice recording toggle shortcut:
if (tauriEvent.Payload.Count > 0 && tauriEvent.Payload[0] == "voice_recording_toggle")
{
this.Logger.LogInformation("Global shortcut triggered for voice recording toggle.");
await this.ToggleRecordingFromShortcut();
}
break;
}
}
/// <summary>
/// Toggles the recording state when triggered by a global shortcut.
/// </summary>
private async Task ToggleRecordingFromShortcut()
{
// Don't allow toggle if transcription is in progress or preparing:
if (this.isTranscribing || this.isPreparing)
{
this.Logger.LogDebug("Ignoring shortcut: transcription or preparation is in progress.");
return;
}
// Toggle the recording state:
await this.OnRecordingToggled(!this.isRecording);
}
#endregion
private uint numReceivedChunks;

View File

@ -15,4 +15,6 @@ public enum TauriEventType
FILE_DROP_HOVERED,
FILE_DROP_DROPPED,
FILE_DROP_CANCELED,
GLOBAL_SHORTCUT_PRESSED,
}

View File

@ -9,7 +9,7 @@ authors = ["Thorsten Sommer"]
tauri-build = { version = "1.5", features = [] }
[dependencies]
tauri = { version = "1.8", features = [ "http-all", "updater", "shell-sidecar", "shell-open", "dialog"] }
tauri = { version = "1.8", features = [ "http-all", "updater", "shell-sidecar", "shell-open", "dialog", "global-shortcut"] }
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"

View File

@ -8,7 +8,7 @@ use rocket::serde::json::Json;
use rocket::serde::Serialize;
use serde::Deserialize;
use tauri::updater::UpdateResponse;
use tauri::{FileDropEvent, UpdaterEvent, RunEvent, Manager, PathResolver, Window, WindowEvent};
use tauri::{FileDropEvent, GlobalShortcutManager, UpdaterEvent, RunEvent, Manager, PathResolver, Window, WindowEvent};
use tauri::api::dialog::blocking::FileDialogBuilder;
use tokio::sync::broadcast;
use tokio::time;
@ -65,6 +65,9 @@ pub fn start_tauri() {
// Get the main window:
let window = app.get_window("main").expect("Failed to get main window.");
// Clone the event sender for the global shortcut handler before moving it into the window event closure:
let shortcut_event_sender = event_sender.clone();
// Register a callback for window events, such as file drops. We have to use
// this handler in addition to the app event handler, because file drop events
// are only available in the window event handler (is a bug, cf. https://github.com/tauri-apps/tauri/issues/14338):
@ -83,6 +86,24 @@ pub fn start_tauri() {
// Save the main window for later access:
*MAIN_WINDOW.lock().unwrap() = Some(window);
// Register global shortcuts for voice recording toggle.
// CmdOrControl+1 will be Cmd+1 on macOS and Ctrl+1 on Windows/Linux:
let mut shortcut_manager = app.global_shortcut_manager();
match shortcut_manager.register("CmdOrControl+1", move || {
info!(Source = "Tauri"; "Global shortcut CmdOrControl+1 triggered for voice recording toggle.");
let event = Event::new(TauriEventType::GlobalShortcutPressed, vec!["voice_recording_toggle".to_string()]);
let sender = shortcut_event_sender.clone();
tauri::async_runtime::spawn(async move {
match sender.send(event) {
Ok(_) => {},
Err(error) => error!(Source = "Tauri"; "Failed to send global shortcut event: {error}"),
}
});
}) {
Ok(_) => info!(Source = "Bootloader Tauri"; "Global shortcut CmdOrControl+1 registered successfully."),
Err(error) => error!(Source = "Bootloader Tauri"; "Failed to register global shortcut: {error}"),
}
info!(Source = "Bootloader Tauri"; "Setup is running.");
let data_path = app.path_resolver().app_local_data_dir().unwrap();
let data_path = data_path.join("data");
@ -323,6 +344,8 @@ pub enum TauriEventType {
FileDropHovered,
FileDropDropped,
FileDropCanceled,
GlobalShortcutPressed,
}
/// Changes the location of the main window to the given URL.