Add push-to-talk wrapper for OpenWhispr
Python script that converts Ctrl+Super hold-to-talk into Ctrl+` toggle pairs for OpenWhispr, working around the app's broken Hold activation mode on Linux. Uses pynput for key listening and xdotool to send hotkeys. https://claude.ai/code/session_01XKYC1basxdwtHy71tky7xm
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user