Make meeting lifecycle stateful

This commit is contained in:
2026-05-27 12:55:17 +02:00
parent d607b957bb
commit e85274829a
91 changed files with 4076 additions and 479 deletions
+39 -17
View File
@@ -1,27 +1,28 @@
using System.Runtime.InteropServices;
using System.Threading;
using MeetingAssistant.LaunchProfiles;
using MeetingAssistant.Recording;
using Microsoft.Extensions.Options;
namespace MeetingAssistant.Hotkeys;
public sealed class GlobalHotkeyService : BackgroundService
{
private const int HotkeyId = 0x4D41;
private const int HotkeyIdBase = 0x4D41;
private const int WmHotkey = 0x0312;
private const int WmQuit = 0x0012;
private readonly MeetingAssistantOptions options;
private readonly ILaunchProfileOptionsProvider launchProfiles;
private readonly MeetingRecordingCoordinator coordinator;
private readonly ILogger<GlobalHotkeyService> logger;
private readonly Dictionary<int, string> hotkeyProfiles = [];
private TaskCompletionSource? messageLoopCompletion;
private uint messageThreadId;
public GlobalHotkeyService(
IOptions<MeetingAssistantOptions> options,
ILaunchProfileOptionsProvider launchProfiles,
MeetingRecordingCoordinator coordinator,
ILogger<GlobalHotkeyService> logger)
{
this.options = options.Value;
this.launchProfiles = launchProfiles;
this.coordinator = coordinator;
this.logger = logger;
}
@@ -58,31 +59,49 @@ public sealed class GlobalHotkeyService : BackgroundService
private void RunMessageLoop(CancellationToken stoppingToken)
{
var hotkey = HotkeyDefinition.Parse(options.Hotkey.Toggle);
var hotkeys = launchProfiles.GetHotkeys();
messageThreadId = GetCurrentThreadId();
using var stoppingRegistration = stoppingToken.Register(() => PostQuitToMessageThread());
if (!RegisterHotKey(IntPtr.Zero, HotkeyId, (uint)hotkey.Modifiers, hotkey.VirtualKey))
for (var index = 0; index < hotkeys.Count; index++)
{
logger.LogWarning("Could not register global hotkey {Hotkey}", options.Hotkey.Toggle);
return;
}
var profileHotkey = hotkeys[index];
var hotkey = HotkeyDefinition.Parse(profileHotkey.Toggle);
var hotkeyId = HotkeyIdBase + index;
if (!RegisterHotKey(IntPtr.Zero, hotkeyId, (uint)hotkey.Modifiers, hotkey.VirtualKey))
{
logger.LogWarning(
"Could not register global hotkey {Hotkey} for launch profile {LaunchProfile}",
profileHotkey.Toggle,
profileHotkey.ProfileName);
continue;
}
logger.LogInformation("Registered global hotkey {Hotkey}", options.Hotkey.Toggle);
hotkeyProfiles[hotkeyId] = profileHotkey.ProfileName;
logger.LogInformation(
"Registered global hotkey {Hotkey} for launch profile {LaunchProfile}",
profileHotkey.Toggle,
profileHotkey.ProfileName);
}
try
{
while (!stoppingToken.IsCancellationRequested && GetMessage(out var message, IntPtr.Zero, 0, 0) > 0)
{
if (message.Message == WmHotkey)
if (message.Message == WmHotkey && hotkeyProfiles.TryGetValue((int)message.WParam, out var profileName))
{
_ = Task.Run(ToggleRecordingAsync, CancellationToken.None);
_ = Task.Run(() => ToggleRecordingAsync(profileName), CancellationToken.None);
}
}
}
finally
{
UnregisterHotKey(IntPtr.Zero, HotkeyId);
foreach (var hotkeyId in hotkeyProfiles.Keys)
{
UnregisterHotKey(IntPtr.Zero, hotkeyId);
}
hotkeyProfiles.Clear();
messageThreadId = 0;
}
}
@@ -93,12 +112,15 @@ public sealed class GlobalHotkeyService : BackgroundService
return base.StopAsync(cancellationToken);
}
private async Task ToggleRecordingAsync()
private async Task ToggleRecordingAsync(string profileName)
{
try
{
var status = await coordinator.ToggleAsync(CancellationToken.None);
logger.LogInformation("Hotkey toggled recording. IsRecording={IsRecording}", status.IsRecording);
var status = await coordinator.ToggleAsync(profileName, CancellationToken.None);
logger.LogInformation(
"Hotkey toggled recording for launch profile {LaunchProfile}. IsRecording={IsRecording}",
profileName,
status.IsRecording);
}
catch (Exception exception)
{