Files
meeting-assistant/MeetingAssistant/MacOs/MacOsDesktopControlService.cs
T
dh 9542c05ea4 feat(macos): add native desktop controls and hotkey integration
- Introduced MacOsDesktopControlManifest to manage desktop control configurations.
- Implemented MacOsDesktopControlService to handle the lifecycle of the macOS helper process.
- Created MacOsWorkflowRulesEditorWindowService for managing workflow rules through a native window.
- Developed a Swift helper for macOS that integrates with AppKit and Carbon for menu-bar controls and global hotkeys.
- Added support for recording lifecycle actions via HTTP endpoints, maintaining consistency with existing Windows functionality.
- Enhanced user experience by allowing macOS users to start/stop recordings and access settings through a menu-bar interface.
- Documented design decisions, implementation evidence, and verification tasks for the new macOS features.
2026-08-06 08:18:52 +02:00

108 lines
3.3 KiB
C#

using System.Diagnostics;
using System.Text.Json;
using MeetingAssistant.LaunchProfiles;
using Microsoft.Extensions.Options;
namespace MeetingAssistant.MacOs;
public sealed class MacOsDesktopControlService : IHostedService, IDisposable
{
private readonly ILaunchProfileOptionsProvider launchProfiles;
private readonly IOptions<MeetingAssistantOptions> options;
private readonly IHostApplicationLifetime applicationLifetime;
private readonly ILogger<MacOsDesktopControlService> logger;
private Process? helper;
private string? manifestPath;
public MacOsDesktopControlService(
ILaunchProfileOptionsProvider launchProfiles,
IOptions<MeetingAssistantOptions> options,
IHostApplicationLifetime applicationLifetime,
ILogger<MacOsDesktopControlService> logger)
{
this.launchProfiles = launchProfiles;
this.options = options;
this.applicationLifetime = applicationLifetime;
this.logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
if (!OperatingSystem.IsMacOS())
{
return Task.CompletedTask;
}
var helperPath = Path.Combine(AppContext.BaseDirectory, "Native", "macos-desktop-controls");
if (!File.Exists(helperPath))
{
logger.LogWarning("macOS desktop controls helper was not found at {HelperPath}", helperPath);
return Task.CompletedTask;
}
var manifest = MacOsDesktopControlManifest.Create(
options.Value.Api.PublicBaseUrl,
launchProfiles.GetProfiles(),
launchProfiles.GetHotkeys());
manifestPath = Path.Combine(
Path.GetTempPath(),
$"meeting-assistant-desktop-{Environment.ProcessId}.json");
File.WriteAllText(
manifestPath,
JsonSerializer.Serialize(
manifest,
new JsonSerializerOptions(JsonSerializerDefaults.Web)));
helper = Process.Start(new ProcessStartInfo
{
FileName = helperPath,
ArgumentList = { manifestPath },
UseShellExecute = false,
CreateNoWindow = true
});
if (helper is null)
{
throw new InvalidOperationException("Could not start the macOS desktop controls helper.");
}
helper.EnableRaisingEvents = true;
helper.Exited += (_, _) =>
{
if (!applicationLifetime.ApplicationStopping.IsCancellationRequested)
{
logger.LogWarning("macOS desktop controls helper exited unexpectedly");
}
};
logger.LogInformation("Started macOS menu-bar controls with {HotkeyCount} global hotkeys", manifest.Hotkeys.Count);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
if (helper is { HasExited: false })
{
helper.Kill();
helper.WaitForExit(TimeSpan.FromSeconds(2));
}
DeleteManifest();
return Task.CompletedTask;
}
public void Dispose()
{
helper?.Dispose();
DeleteManifest();
}
private void DeleteManifest()
{
if (manifestPath is not null && File.Exists(manifestPath))
{
File.Delete(manifestPath);
manifestPath = null;
}
}
}