Skip to content

Commit 3e78cea

Browse files
authored
feat: finish Fn dictation with another key (#52)
1 parent c73c65b commit 3e78cea

5 files changed

Lines changed: 43 additions & 3 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ Modifier keys are logical choices: either the left or right physical key works.
170170
Your keyboard must report Fn as a Linux key; otherwise choose another key.
171171

172172
- Tap to start; tap again to finish and paste.
173+
- With Fn as the trigger, pressing another key while recording also finishes and pastes. Escape still cancels; the finishing key still reaches the foreground app.
173174
- Hold past the threshold to speak; release to finish and paste.
174175
- Select one preset, or choose **Record a shortcut…** to capture a custom combination such as Ctrl+Alt+Space. Only one trigger is active.
175176
- Press the active trigger twice to send Enter without dictation. This can submit messages or execute terminal commands.

‎README.zh-CN.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ Esc 可停止后续输入,但不能撤回已输入的文字;焦点变化时
132132
如果键盘没有向 Linux 上报 Fn 按键,请选择其他键。
133133

134134
- 短按开始,再按一次结束并粘贴。
135+
- 使用 Fn 作为触发键时,录音期间按其他键也会结束并粘贴。Esc 仍用于取消;结束录音的按键仍会传给前台应用。
135136
- 长按超过阈值开始说话,松开结束并粘贴。
136137
- 可直接选择一个预设,或选择**录制快捷键…**来录制 Ctrl+Alt+Space 这样的组合键;任何时候只会生效一个快捷键。
137138
- 连续按两次当前触发快捷键会在不听写的情况下发送回车,可能发送消息或执行终端命令。

‎src/doubao_input/app.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ def _build(self) -> None:
260260
enter=self._voice_enter, cancel_input=self._cancel_input,
261261
shortcut=self._injector.send_shortcut,
262262
prime=self._voice_prime, discard=self._tm.discard_primed_audio,
263+
is_recording=lambda: self.app_state.is_recording,
263264
debug_edge=self._debug_edge, error=lambda message: logger.warning("PTT error: %s", message))
264265

265266
# ---- Initial state: cached params? ----

‎src/doubao_input/trigger/controller.py‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ class TriggerController:
1212
def __init__(self, reader_factory, schedule, cancel, *, start, stop, toggle, enter,
1313
cancel_input, debug_edge, error, escape_edge=lambda pressed: None,
1414
prime=lambda: None, discard=lambda: None,
15-
shortcut=lambda key, modifiers=(): None):
15+
shortcut=lambda key, modifiers=(): None,
16+
is_recording=lambda: False):
1617
self._factory = reader_factory
1718
self._schedule, self._cancel = schedule, cancel
1819
self._actions = start, stop, toggle, enter
1920
self._prime, self._discard = prime, discard
2021
self._shortcut = shortcut
22+
self._is_recording = is_recording
2123
self._escape_edge = escape_edge
2224
self._cancel_input, self._debug_edge, self._error = cancel_input, debug_edge, error
2325
self._timers = TimerScope(schedule, cancel)
@@ -53,7 +55,7 @@ def configure(self, settings, *, strict=False):
5355
return self._available
5456
generation = self._generation + 1
5557
configured = (settings.doubao_key, *settings.doubao_modifiers)
56-
keys = ({1} | set(CAPTURABLE_KEY_CODES)) if self.capturing else (
58+
keys = ({1} | set(CAPTURABLE_KEY_CODES)) if (self.capturing or settings.doubao_key == 464) else (
5759
{1}.union(*(equivalent_key_codes(code) for code in configured))
5860
if settings.doubao_key else set())
5961
candidate = self._factory(on_press=lambda: None, on_release=lambda: None,
@@ -152,6 +154,10 @@ def _edge(self, code, pressed):
152154
return
153155
if not self._settings or not self._gesture:
154156
return
157+
if (pressed and self._settings.doubao_key == 464 and code != 464
158+
and self._is_recording()):
159+
self._actions[1]()
160+
return
155161
if pressed:
156162
self._down_keys.add(code)
157163
if (key_codes_match(code, self._settings.doubao_key)

‎tests/unit/test_trigger_controller.py‎

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ def reader(**kwargs):
2323
self.start, self.stop, self.toggle, self.enter, self.cancel = [Mock() for _ in range(5)]
2424
self.shortcut = Mock()
2525
self.error = Mock()
26+
self.recording_active = False
2627
self.control = TriggerController(reader, schedule, Mock(), start=self.start, stop=self.stop,
2728
toggle=self.toggle, enter=self.enter, cancel_input=self.cancel,
28-
debug_edge=Mock(return_value=False), error=self.error, shortcut=self.shortcut)
29+
debug_edge=Mock(return_value=False), error=self.error, shortcut=self.shortcut,
30+
is_recording=lambda: self.recording_active)
2931
self.settings = Settings(doubao_key=100)
3032
self.control.configure(self.settings)
3133

@@ -205,6 +207,35 @@ def test_hold_release_and_escape_preserve_gesture_behavior(self):
205207
self.edge(1, True)
206208
self.cancel.assert_called_once()
207209

210+
def test_fn_recording_finishes_on_any_other_key_press(self):
211+
self.control.configure(replace(self.settings, doubao_key=464))
212+
self.assertIn(30, self.readers[-1].callbacks["key_codes"])
213+
self.edge(464, True)
214+
self.edge(464, False)
215+
self.timers[-1][1]()
216+
self.toggle.assert_called_once()
217+
self.recording_active = True
218+
self.edge(30, True)
219+
self.stop.assert_called_once()
220+
self.edge(30, False)
221+
self.stop.assert_called_once()
222+
223+
def test_fn_other_key_before_recording_does_not_finish(self):
224+
self.control.configure(replace(self.settings, doubao_key=464))
225+
self.edge(30, True)
226+
self.stop.assert_not_called()
227+
self.toggle.assert_not_called()
228+
229+
def test_fn_recording_escape_still_cancels(self):
230+
self.control.configure(replace(self.settings, doubao_key=464))
231+
self.recording_active = True
232+
self.edge(1, True)
233+
self.cancel.assert_called_once()
234+
self.stop.assert_not_called()
235+
236+
def test_non_fn_trigger_keeps_listening_only_to_its_shortcut(self):
237+
self.assertNotIn(30, self.readers[-1].callbacks["key_codes"])
238+
208239
def test_dedicated_record_button_uses_same_gesture(self):
209240
self.aux("record", True)
210241
self.timers[-1][1]()

0 commit comments

Comments
 (0)