Extend managed macOS client toward feature parity
.NET port / test (macos-latest) (push) Canceled after 0s
.NET port / test (ubuntu-24.04) (push) Canceled after 0s
.NET port / test (windows-latest) (push) Canceled after 0s
.NET port / apple-client (push) Canceled after 0s
.NET port / cpp-conformance (push) Canceled after 0s
.NET port / test (macos-latest) (push) Canceled after 0s
.NET port / test (ubuntu-24.04) (push) Canceled after 0s
.NET port / test (windows-latest) (push) Canceled after 0s
.NET port / apple-client (push) Canceled after 0s
.NET port / cpp-conformance (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
using AppKit;
|
||||
using CoreGraphics;
|
||||
using VoiceCat.Audio;
|
||||
|
||||
namespace VoiceCat.Mac;
|
||||
|
||||
internal sealed class SettingsWindowController : NSWindowController
|
||||
{
|
||||
private readonly MainWindowController owner;
|
||||
private readonly MacSettings settings;
|
||||
private readonly MacAudioBackend audio;
|
||||
private readonly NSPopUpButton mode = new(new CGRect(160, 455, 260, 28), false);
|
||||
private readonly NSSlider vad = new(new CGRect(160, 415, 260, 24)) { MinValue = 0, MaxValue = 100 };
|
||||
private readonly NSSlider inputGain = new(new CGRect(160, 375, 260, 24)) { MinValue = 0, MaxValue = 400 };
|
||||
private readonly NSButton noiseReduction = NSButton.CreateCheckbox("Noise reduction (RNNoise)", () => { });
|
||||
private readonly NSButton stereo = NSButton.CreateCheckbox("Stereo microphone", () => { });
|
||||
private readonly NSPopUpButton input = new(new CGRect(160, 285, 260, 28), false);
|
||||
private readonly NSPopUpButton output = new(new CGRect(160, 245, 260, 28), false);
|
||||
private readonly NSSlider outputGain = new(new CGRect(160, 205, 260, 24)) { MinValue = 0, MaxValue = 100 };
|
||||
private readonly NSButton auxiliary = NSButton.CreateCheckbox("Aux input stream", () => { });
|
||||
private readonly NSPopUpButton auxiliaryDevice = new(new CGRect(160, 125, 260, 28), false);
|
||||
private readonly NSSlider auxiliaryGain = new(new CGRect(160, 85, 260, 24)) { MinValue = 0, MaxValue = 400 };
|
||||
private readonly NSButton sounds = NSButton.CreateCheckbox("Event sounds", () => { });
|
||||
private readonly NSButton speech = NSButton.CreateCheckbox("Speak events", () => { });
|
||||
private readonly NSButton selfTalk = NSButton.CreateCheckbox("Own voice-activity sounds", () => { });
|
||||
private readonly NSButton pttSound = NSButton.CreateCheckbox("Push-to-talk cue", () => { });
|
||||
private readonly NSButton pttKey = new(new CGRect(220, 12, 200, 32)) { Title = "Change PTT key…" };
|
||||
private readonly NSSlider eventVolume = new(new CGRect(160, 505, 260, 24)) { MinValue = 0, MaxValue = 100 };
|
||||
|
||||
internal SettingsWindowController(MainWindowController owner, MacSettings settings, MacAudioBackend audio) : base(
|
||||
new NSWindow(new CGRect(0, 0, 460, 620), NSWindowStyle.Titled | NSWindowStyle.Closable,
|
||||
NSBackingStore.Buffered, false))
|
||||
{
|
||||
this.owner = owner; this.settings = settings; this.audio = audio;
|
||||
Window!.Title = "VoiceCat Settings"; Window.Center();
|
||||
NSView view = Window.ContentView!;
|
||||
mode.AddItems(["Voice activation", "Push to talk", "Always on"]);
|
||||
Add(view, "Sound volume", eventVolume, 515);
|
||||
Add(view, "Input mode", mode, 465); Add(view, "VAD sensitivity", vad, 425); Add(view, "Microphone volume", inputGain, 385);
|
||||
noiseReduction.Frame = new CGRect(160, 340, 260, 24); stereo.Frame = new CGRect(160, 315, 260, 24);
|
||||
view.AddSubview(noiseReduction); view.AddSubview(stereo);
|
||||
Add(view, "Input device", input, 295); Add(view, "Output device", output, 255); Add(view, "Output volume", outputGain, 215);
|
||||
auxiliary.Frame = new CGRect(160, 165, 260, 24); view.AddSubview(auxiliary);
|
||||
Add(view, "Aux device", auxiliaryDevice, 135); Add(view, "Aux volume", auxiliaryGain, 95);
|
||||
sounds.Frame = new CGRect(25, 45, 110, 24); speech.Frame = new CGRect(145, 45, 110, 24);
|
||||
selfTalk.Frame = new CGRect(265, 45, 180, 24); pttSound.Frame = new CGRect(25, 18, 180, 24);
|
||||
view.AddSubview(sounds); view.AddSubview(speech); view.AddSubview(selfTalk); view.AddSubview(pttSound); view.AddSubview(pttKey);
|
||||
foreach (NSControl control in new NSControl[] { mode, vad, inputGain, noiseReduction, stereo, input, output,
|
||||
outputGain, auxiliary, auxiliaryDevice, auxiliaryGain, sounds, speech, selfTalk, pttSound, eventVolume }) control.Activated += Changed;
|
||||
pttKey.Activated += CapturePushToTalkKey;
|
||||
Populate(input, true, settings.InputDeviceId); Populate(output, false, settings.OutputDeviceId); Populate(auxiliaryDevice, true, settings.AuxiliaryDeviceId);
|
||||
LoadValues(); SetAccessibility();
|
||||
}
|
||||
|
||||
private static void Add(NSView view, string label, NSView control, double y)
|
||||
{
|
||||
NSTextField text = NSTextField.CreateLabel(label); text.Frame = new CGRect(25, y, 125, 22); view.AddSubview(text); view.AddSubview(control);
|
||||
}
|
||||
|
||||
private void Populate(NSPopUpButton picker, bool capture, string? selected)
|
||||
{
|
||||
picker.RemoveAllItems(); IReadOnlyList<AudioDeviceInfo> devices = audio.Enumerate(capture);
|
||||
foreach (AudioDeviceInfo device in devices) { picker.AddItem(device.Name + (device.IsDefault ? " (default)" : "")); picker.LastItem!.RepresentedObject = new Foundation.NSString(device.Id); }
|
||||
int index = devices.ToList().FindIndex(device => device.Id == selected);
|
||||
if (index < 0) index = devices.ToList().FindIndex(device => device.IsDefault);
|
||||
if (index >= 0) picker.SelectItem(index); picker.Enabled = devices.Count > 0;
|
||||
}
|
||||
|
||||
private void LoadValues()
|
||||
{
|
||||
mode.SelectItem((int)settings.InputMode); vad.DoubleValue = settings.VadThreshold * 100;
|
||||
inputGain.DoubleValue = settings.InputGain * 100; outputGain.DoubleValue = settings.OutputGain * 100;
|
||||
noiseReduction.State = State(settings.InputNoiseReduction); stereo.State = State(settings.StereoMicrophone);
|
||||
auxiliary.State = State(settings.AuxiliaryEnabled); auxiliaryGain.DoubleValue = settings.AuxiliaryGain * 100;
|
||||
sounds.State = State(settings.EventSounds); speech.State = State(settings.SpokenEvents);
|
||||
eventVolume.DoubleValue = settings.EventVolume * 100;
|
||||
selfTalk.State = State(settings.SelfTalkSounds); pttSound.State = State(settings.PushToTalkSound);
|
||||
pttKey.Title = $"PTT key code: {settings.PushToTalkKeyCode}";
|
||||
UpdateEnabled();
|
||||
}
|
||||
|
||||
private void CapturePushToTalkKey(object? sender, EventArgs args)
|
||||
{
|
||||
var alert = new NSAlert { MessageText = "Set push-to-talk key", InformativeText = "Press the key to use while VoiceCat is focused." };
|
||||
alert.AddButton("Cancel"); NSObject? monitor = null;
|
||||
monitor = NSEvent.AddLocalMonitorForEventsMatchingMask(NSEventMask.KeyDown, value =>
|
||||
{
|
||||
settings.PushToTalkKeyCode = value.KeyCode; settings.Save(); pttKey.Title = $"PTT key code: {value.KeyCode}";
|
||||
NSApplication.SharedApplication.AbortModal(); return null!;
|
||||
});
|
||||
try { alert.RunModal(); }
|
||||
finally { if (monitor is not null) NSEvent.RemoveMonitor(monitor); }
|
||||
}
|
||||
|
||||
private async void Changed(object? sender, EventArgs args)
|
||||
{
|
||||
settings.InputMode = (AudioInputMode)mode.IndexOfSelectedItem; settings.VadThreshold = (float)vad.DoubleValue / 100;
|
||||
settings.InputGain = (float)inputGain.DoubleValue / 100; settings.OutputGain = (float)outputGain.DoubleValue / 100;
|
||||
settings.InputNoiseReduction = On(noiseReduction); settings.StereoMicrophone = On(stereo);
|
||||
settings.InputDeviceId = Selected(input); settings.OutputDeviceId = Selected(output);
|
||||
settings.AuxiliaryEnabled = On(auxiliary); settings.AuxiliaryDeviceId = Selected(auxiliaryDevice);
|
||||
settings.AuxiliaryGain = (float)auxiliaryGain.DoubleValue / 100; settings.EventSounds = On(sounds);
|
||||
settings.EventVolume = (float)eventVolume.DoubleValue / 100; settings.SpokenEvents = On(speech);
|
||||
settings.SelfTalkSounds = On(selfTalk); settings.PushToTalkSound = On(pttSound);
|
||||
settings.Save(); UpdateEnabled(); await owner.ApplySettingsAsync();
|
||||
}
|
||||
|
||||
private void UpdateEnabled() { vad.Enabled = settings.InputMode == AudioInputMode.VoiceActivation; auxiliaryDevice.Enabled = auxiliaryGain.Enabled = settings.AuxiliaryEnabled; }
|
||||
private void SetAccessibility()
|
||||
{
|
||||
((INSAccessibility)mode).AccessibilityLabel = "Microphone input mode"; ((INSAccessibility)vad).AccessibilityLabel = "Voice activation sensitivity";
|
||||
((INSAccessibility)inputGain).AccessibilityLabel = "Microphone volume"; ((INSAccessibility)input).AccessibilityLabel = "Microphone device";
|
||||
((INSAccessibility)output).AccessibilityLabel = "Output device"; ((INSAccessibility)outputGain).AccessibilityLabel = "Output volume";
|
||||
((INSAccessibility)auxiliaryDevice).AccessibilityLabel = "Auxiliary input device"; ((INSAccessibility)auxiliaryGain).AccessibilityLabel = "Auxiliary input volume";
|
||||
((INSAccessibility)eventVolume).AccessibilityLabel = "Event sound volume";
|
||||
}
|
||||
private static bool On(NSButton button) => button.State == NSCellStateValue.On;
|
||||
private static NSCellStateValue State(bool value) => value ? NSCellStateValue.On : NSCellStateValue.Off;
|
||||
private static string? Selected(NSPopUpButton picker) => picker.SelectedItem?.RepresentedObject?.ToString();
|
||||
}
|
||||
Reference in New Issue
Block a user