v0.3: PTT hotkey capture, mouse support, model params

- Replace PTT key dropdown with live key-capture textbox
- Use low-level keyboard hook for capture (distinguishes Left/Right Ctrl)
- Support mouse buttons as PTT (Left/Right/Middle/X1/X2 via WH_MOUSE_LL)
- Filter mouse clicks from capture, allow Left/Right Ctrl
- Add Noise/Speed/NoiseW sliders with live labels, reinit on MouseUp
- Stream audio via BufferedWaveProvider (no temp WAV files)
- Add text input field with Speak button
- Fix sentence terminator, UTF-8 marshaling, voice catalogue JSON
This commit is contained in:
2026-08-10 12:07:34 +00:00
parent f7ea07b78e
commit 9b0dc11ee6
3 changed files with 201 additions and 38 deletions
+84 -14
View File
@@ -31,11 +31,11 @@ internal sealed partial class MainForm : Form
private async void OnLoad(object? sender, EventArgs e)
{
PopulatePttKeys();
PopulateVoices();
PopulateOutputDevices();
cmbPttKey.SelectedItem = Keys.F8;
_pttKey = Keys.F8;
txtPttKey.Text = KeyToDisplayString(_pttKey);
if (cmbVoice.Items.Count > 0)
cmbVoice.SelectedIndex = 0;
@@ -45,7 +45,8 @@ internal sealed partial class MainForm : Form
btnTestVoice.Click += OnTestVoice;
btnManageVoices.Click += OnManageVoices;
btnClearLog.Click += (_, _) => txtLog.Clear();
cmbPttKey.SelectedIndexChanged += OnPttKeyChanged;
txtPttKey.Enter += OnPttKeyFocus;
txtPttKey.KeyDown += OnPttKeyDown;
cmbOutput.SelectedIndexChanged += OnOutputChanged;
cmbVoice.SelectedIndexChanged += OnVoiceChanged;
@@ -61,14 +62,88 @@ internal sealed partial class MainForm : Form
await InitializeEngineAsync();
}
private void PopulatePttKeys()
private Keys _pttKey = Keys.F8;
private bool _capturingPttKey;
private PttHotkey? _captureHook;
private void OnPttKeyFocus(object? sender, EventArgs e)
{
var keys = new[]
_capturingPttKey = true;
txtPttKey.Text = "Press a key...";
txtPttKey.BackColor = Color.LightYellow;
_captureHook?.Dispose();
_captureHook = new PttHotkey();
_captureHook.CaptureKeyPressed += OnCaptureKey;
_captureHook.InstallCaptureHook();
}
private void OnCaptureKey(Keys key)
{
if (!_capturingPttKey) return;
if (key == Keys.Escape)
{
Keys.F1, Keys.F2, Keys.F3, Keys.F4, Keys.F5, Keys.F6,
Keys.F7, Keys.F8, Keys.F9, Keys.F10, Keys.F11, Keys.F12,
CancelCapture();
return;
}
if (key is Keys.ShiftKey or Keys.Menu or Keys.LWin or Keys.RWin)
return;
_pttKey = key;
_capturingPttKey = false;
txtPttKey.Text = KeyToDisplayString(_pttKey);
txtPttKey.BackColor = SystemColors.Window;
_captureHook?.Dispose();
_captureHook = null;
SetupHotkey();
}
private void CancelCapture()
{
_capturingPttKey = false;
txtPttKey.Text = KeyToDisplayString(_pttKey);
txtPttKey.BackColor = SystemColors.Window;
_captureHook?.Dispose();
_captureHook = null;
}
private void OnPttKeyDown(object? sender, KeyEventArgs e)
{
if (_capturingPttKey && e.KeyCode == Keys.Escape)
{
CancelCapture();
e.SuppressKeyPress = true;
}
}
private static string KeyToDisplayString(Keys key)
{
return key switch
{
>= Keys.F1 and <= Keys.F12 => key.ToString(),
Keys.RControlKey => "Right Ctrl",
Keys.LControlKey => "Left Ctrl",
Keys.Space => "Space",
Keys.LButton => "Mouse Left",
Keys.RButton => "Mouse Right",
Keys.MButton => "Mouse Middle",
Keys.XButton1 => "Mouse X1",
Keys.XButton2 => "Mouse X2",
Keys.Oemtilde => "`",
Keys.CapsLock => "CapsLock",
Keys.NumLock => "NumLock",
Keys.Scroll => "ScrollLock",
Keys.Pause => "Pause",
Keys.Insert => "Insert",
Keys.Delete => "Delete",
Keys.Home => "Home",
Keys.End => "End",
Keys.PageUp => "PageUp",
Keys.PageDown => "PageDown",
_ => key.ToString(),
};
cmbPttKey.Items.AddRange(keys.Cast<object>().ToArray());
}
private void PopulateVoices()
@@ -160,7 +235,7 @@ internal sealed partial class MainForm : Form
private void SetupHotkey()
{
_pttHotkey?.Dispose();
_pttHotkey = new PttHotkey { Key = (Keys)(cmbPttKey.SelectedItem ?? Keys.F8) };
_pttHotkey = new PttHotkey { Key = _pttKey };
_pttHotkey.Pressed += OnPttPressed;
_pttHotkey.Released += OnPttReleased;
_pttHotkey.Install();
@@ -299,11 +374,6 @@ internal sealed partial class MainForm : Form
}
}
private void OnPttKeyChanged(object? sender, EventArgs e)
{
SetupHotkey();
}
private void OnOutputChanged(object? sender, EventArgs e)
{
if (_orchestrator is not null)