8585972a1e
WinForms tray app that captures text (from file or direct input) and synthesizes speech via libpiper (Piper TTS), outputting to any audio device (VB-CABLE for virtual mic routing). Features: - Global PTT hotkey (configurable F1-F12) via WH_KEYBOARD_LL - Text file sequential reader (line-by-line on each PTT cycle) - Direct text input with Speak button - Voice manager: browse 147-voice Piper catalogue, download, remove - libpiper P/Invoke wrapper with UTF-8 marshaling, streaming chunks - BufferedWaveProvider streaming playback with trailing silence flush - Sentence terminator auto-append (fixes espeak-ng final-word drop) - Tray icon with minimize-to-tray Architecture: - Robovoice.Core: ITtsEngine, ISttSource interfaces, voice catalogue - Robovoice.Tts.LibPiper: P/Invoke wrapper for libpiper.dll - Robovoice.Stt.File: text file STT source (testing without mic server) - Robovoice.Stt.Udp: UDP client stub (for future Linux mic server) - Robovoice.App: WinForms UI, orchestrator, PTT hotkey, audio output
95 lines
2.7 KiB
C#
95 lines
2.7 KiB
C#
using System.Runtime.InteropServices;
|
|
using System.Windows.Forms;
|
|
|
|
namespace Robovoice.App;
|
|
|
|
internal sealed class PttHotkey : IDisposable
|
|
{
|
|
private const int WH_KEYBOARD_LL = 13;
|
|
private const int WM_KEYDOWN = 0x0100;
|
|
private const int WM_KEYUP = 0x0101;
|
|
private const int WM_SYSKEYDOWN = 0x0104;
|
|
private const int WM_SYSKEYUP = 0x0105;
|
|
|
|
private readonly LowLevelKeyboardProc _proc;
|
|
private IntPtr _hook = IntPtr.Zero;
|
|
private bool _isDown;
|
|
private bool _disposed;
|
|
|
|
public Keys Key { get; set; } = Keys.F8;
|
|
|
|
public event EventHandler? Pressed;
|
|
public event EventHandler? Released;
|
|
|
|
public PttHotkey()
|
|
{
|
|
_proc = HookCallback;
|
|
}
|
|
|
|
public void Install()
|
|
{
|
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
|
if (_hook != IntPtr.Zero) return;
|
|
|
|
IntPtr hModule = GetModuleHandle(null);
|
|
_hook = SetWindowsHookEx(WH_KEYBOARD_LL, _proc, hModule, 0);
|
|
}
|
|
|
|
public void Uninstall()
|
|
{
|
|
if (_hook != IntPtr.Zero)
|
|
{
|
|
UnhookWindowsHookEx(_hook);
|
|
_hook = IntPtr.Zero;
|
|
_isDown = false;
|
|
}
|
|
}
|
|
|
|
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
|
|
{
|
|
if (nCode >= 0)
|
|
{
|
|
int vkCode = Marshal.ReadInt32(lParam);
|
|
Keys key = (Keys)vkCode;
|
|
|
|
bool isDown = wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN;
|
|
bool isUp = wParam == WM_KEYUP || wParam == WM_SYSKEYUP;
|
|
|
|
if (key == Key && isDown && !_isDown)
|
|
{
|
|
_isDown = true;
|
|
Pressed?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
else if (key == Key && isUp && _isDown)
|
|
{
|
|
_isDown = false;
|
|
Released?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
|
|
return CallNextHookEx(_hook, nCode, wParam, lParam);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed) return;
|
|
Uninstall();
|
|
_disposed = true;
|
|
}
|
|
|
|
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
|
|
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
|
|
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
|
|
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
private static extern IntPtr GetModuleHandle(string? lpModuleName);
|
|
}
|