Public Access
- 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.
108 lines
3.3 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|