#!/usr/bin/env python3 """ openwhispr-ptt.py — Push-to-talk wrapper for OpenWhispr Turns Ctrl+Super (hold) into Ctrl+` toggle pairs: - Press Ctrl+Super → sends Ctrl+` to START dictation - Release Ctrl+Super → sends Ctrl+` to STOP dictation Requires: pynput, xdotool Usage: python3 openwhispr-ptt.py """ import subprocess import threading import time import signal import sys try: from pynput import keyboard except ImportError: print("Error: pynput not installed.") print("Install with: pip3 install pynput") sys.exit(1) # ── Configuration ──────────────────────────────────────────────────────────── # The key combo YOU hold to talk PTT_MODIFIER = keyboard.Key.ctrl_l PTT_KEY = keyboard.Key.cmd # Super/Windows key # The key combo OpenWhispr uses (Ctrl+`) # We simulate this via xdotool for reliability with Electron apps WHISPR_HOTKEY = "ctrl+grave" # Minimum hold time (ms) to avoid accidental triggers MIN_HOLD_MS = 100 # ── State ──────────────────────────────────────────────────────────────────── pressed_keys = set() is_recording = False press_time = 0 lock = threading.Lock() def send_whispr_toggle(): """Send Ctrl+` to OpenWhispr via xdotool.""" try: subprocess.run( ["xdotool", "key", WHISPR_HOTKEY], timeout=2, capture_output=True, ) except FileNotFoundError: print("Error: xdotool not installed. Install with: sudo apt install xdotool") sys.exit(1) except subprocess.TimeoutExpired: pass def on_press(key): global is_recording, press_time with lock: pressed_keys.add(key) # Check if both PTT keys are held if PTT_MODIFIER in pressed_keys and PTT_KEY in pressed_keys: if not is_recording: press_time = time.time() is_recording = True send_whispr_toggle() # START print("\033[32m● Recording...\033[0m") def on_release(key): global is_recording, press_time with lock: # If either PTT key is released while recording if is_recording and key in (PTT_MODIFIER, PTT_KEY): elapsed_ms = (time.time() - press_time) * 1000 if elapsed_ms >= MIN_HOLD_MS: send_whispr_toggle() # STOP print(f"\033[0m○ Stopped ({int(elapsed_ms)}ms)") else: # Too short — send another toggle to cancel send_whispr_toggle() print(f"\033[33m○ Cancelled (too short: {int(elapsed_ms)}ms)\033[0m") is_recording = False pressed_keys.discard(key) def main(): print("═══════════════════════════════════════════") print(" OpenWhispr Push-to-Talk") print("═══════════════════════════════════════════") print(f" Hold: Ctrl + Super → start dictation") print(f" Release: Ctrl + Super → stop dictation") print(f" Quit: Ctrl + C") print("═══════════════════════════════════════════") print("Listening...\n") # Handle Ctrl+C gracefully signal.signal(signal.SIGINT, lambda *_: sys.exit(0)) with keyboard.Listener(on_press=on_press, on_release=on_release) as listener: listener.join() if __name__ == "__main__": main()