feat: add hold-to-talk listener and restore Handy-managed preferences
This commit is contained in:
1 parent
0992f3b863
commit
e61f230978
7 files changed
+135
-47
No files matched your search
@@ -0,0 +1,48 @@
|
||||
"""Wait for release of the physical Super+D chord that launched this service."""
|
||||
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
from contextlib import closing
|
||||
|
||||
from evdev import InputDevice, ecodes as key, list_devices
|
||||
|
||||
|
||||
def held(keys):
|
||||
return key.KEY_D in keys and bool({key.KEY_LEFTMETA, key.KEY_RIGHTMETA} & set(keys))
|
||||
|
||||
|
||||
def interrupted(signum, frame):
|
||||
raise KeyboardInterrupt
|
||||
|
||||
|
||||
def main(handy):
|
||||
signal.signal(signal.SIGTERM, interrupted)
|
||||
for path in list_devices():
|
||||
try:
|
||||
device = InputDevice(path)
|
||||
except OSError:
|
||||
continue # Devices can disappear between enumeration and opening.
|
||||
with closing(device):
|
||||
if not held(device.active_keys()):
|
||||
continue
|
||||
action = "--cancel"
|
||||
try:
|
||||
subprocess.run([handy, "--toggle-transcription"], check=True)
|
||||
# A quick release may already be queued while the CLI starts.
|
||||
if held(device.active_keys()):
|
||||
for event in device.read_loop():
|
||||
if not held(device.active_keys()):
|
||||
break
|
||||
action = "--toggle-transcription"
|
||||
finally:
|
||||
# Disconnects, cancellation and service shutdown discard the recording.
|
||||
subprocess.run([handy, action], check=True)
|
||||
return
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main(sys.argv[1])
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Synthetic key-state checks; never reads a keyboard or records audio."""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import _push_to_talk as ptt
|
||||
|
||||
down = [ptt.key.KEY_LEFTMETA, ptt.key.KEY_D]
|
||||
|
||||
|
||||
def check(states, expected, error=None):
|
||||
device = Mock()
|
||||
device.active_keys.side_effect = states
|
||||
device.read_loop.return_value = iter([object(), object()])
|
||||
if error:
|
||||
device.read_loop.side_effect = error
|
||||
with (
|
||||
patch.object(ptt, "list_devices", return_value=["keyboard"]),
|
||||
patch.object(ptt, "InputDevice", return_value=device),
|
||||
patch.object(ptt.signal, "signal"),
|
||||
patch.object(ptt.subprocess, "run") as run,
|
||||
):
|
||||
try:
|
||||
ptt.main("handy")
|
||||
except (OSError, KeyboardInterrupt):
|
||||
assert error is not None
|
||||
assert [call.args[0][1] for call in run.call_args_list] == expected
|
||||
device.close.assert_called_once()
|
||||
|
||||
|
||||
check([[]], []) # Released before the listener starts: do not record.
|
||||
check([down, []], ["--toggle-transcription", "--toggle-transcription"])
|
||||
check([down, down, down, [ptt.key.KEY_D]], ["--toggle-transcription"] * 2)
|
||||
check([down, down], ["--toggle-transcription", "--cancel"], OSError("unplugged"))
|
||||
check([down, down], ["--toggle-transcription", "--cancel"], KeyboardInterrupt())
|
||||
assert ptt.held([ptt.key.KEY_RIGHTMETA, ptt.key.KEY_D])
|
||||
assert not ptt.held([ptt.key.KEY_LEFTMETA])
|
||||
print("Push-to-talk release, repeat, disconnect and cancellation checks passed")
|
||||
Reference in new issue
Block a user