Skip to main content
Trueears registers Ctrl+Shift+K permanently at startup for toggling recording. The Escape key shortcut is managed dynamically: it is registered only while a recording session is active and unregistered when the session ends, so it does not interfere with Escape usage in other applications.
The Escape shortcut is intentionally not registered at application startup. It is registered when the overlay becomes visible and unregistered when it is hidden. On Linux Wayland sessions, global Escape registration is skipped entirely.

register_escape_shortcut

Registers Escape as a global shortcut. When pressed while the overlay is visible, the backend emits a shortcut-cancelled event to the main window.
import { invoke } from '@tauri-apps/api/core';

await invoke('register_escape_shortcut');

Returns

Promise<void>

When to call

Call this command when the recording overlay becomes visible — that is, after the shortcut-pressed event is received and the frontend has started the MediaRecorder.

unregister_escape_shortcut

Unregisters the Escape global shortcut, returning Escape key behavior to the operating system and other applications.
import { invoke } from '@tauri-apps/api/core';

await invoke('unregister_escape_shortcut');

Returns

Promise<void>

When to call

Call this command whenever a recording session ends — whether the session completed normally, was cancelled via Escape, or was stopped by pressing the hotkey again.

Recording lifecycle with shortcuts

The following illustrates the complete recording flow including shortcut management. Note that the recording shortcut emits shortcut-pressed (with window info and selected text), and Escape emits shortcut-cancelled.
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';

interface ShortcutPressedPayload {
  window_info: ActiveWindowInfo | null;
  selected_text: string | null;
}

let mediaRecorder: MediaRecorder | null = null;

// 1. Listen for the global hotkey — carries window context
const unlistenPressed = await listen<ShortcutPressedPayload>('shortcut-pressed', async (event) => {
  if (mediaRecorder?.state === 'recording') {
    // Stop an in-progress recording
    mediaRecorder.stop();
  } else {
    // Start a new recording session with context
    await startRecording(event.payload.window_info, event.payload.selected_text);
  }
});

// 2. Listen for key release (Push-to-Talk mode)
const unlistenReleased = await listen('shortcut-released', async () => {
  if (recordingMode === 'push-to-talk' && mediaRecorder?.state === 'recording') {
    mediaRecorder.stop();
  }
});

// 3. Listen for the Escape cancel event
const unlistenCancelled = await listen('shortcut-cancelled', async () => {
  if (mediaRecorder?.state === 'recording') {
    mediaRecorder.stop();
    discardRecording();
  }
  // Always unregister Escape after handling the cancel
  await invoke('unregister_escape_shortcut');
});

async function startRecording(windowInfo: ActiveWindowInfo | null, selectedText: string | null) {
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
  mediaRecorder = new MediaRecorder(stream);

  mediaRecorder.onstart = async () => {
    // Register Escape now that recording is active
    await invoke('register_escape_shortcut');
  };

  mediaRecorder.onstop = async () => {
    // Always unregister Escape when the session ends
    await invoke('unregister_escape_shortcut');
  };

  mediaRecorder.start();
}