using AppKit; using CoreGraphics; using Foundation; using ScreenCaptureKit; namespace VoiceCat.Mac; internal sealed record ScreenApplication(string Name, string BundleIdentifier); internal static class ScreenAudioPicker { internal static async Task ChooseAsync(ScreenAudioSelection initial, CancellationToken cancellationToken = default) { IReadOnlyList applications = await ScreenAudioCapture.GetApplicationsAsync(cancellationToken); var mode = new NSPopUpButton(new CGRect(0, 270, 390, 28), false); mode.AddItems(["Everything", "Only selected applications", "All except selected applications"]); mode.SelectItem((int)initial.Scope); ((INSAccessibility)mode).AccessibilityLabel = "Screen audio sharing mode"; var source = new ApplicationSource(applications); var table = new NSTableView(new CGRect(0, 0, 390, 225)) { DataSource = source, Delegate = source, AllowsMultipleSelection = true, HeaderView = null }; table.AddColumn(new NSTableColumn("application") { Title = "Application", Width = 370 }); ((INSAccessibility)table).AccessibilityLabel = "Applications to include or exclude"; uint[] selected = applications.Select((application, index) => initial.BundleIdentifiers.Contains(application.BundleIdentifier) ? (uint?)index : null) .Where(index => index.HasValue).Select(index => index!.Value).ToArray(); if (selected.Length > 0) table.SelectRows(NSIndexSet.FromArray(selected), false); var scroll = new NSScrollView(new CGRect(0, 38, 390, 225)) { DocumentView = table, HasVerticalScroller = true, BorderType = NSBorderType.BezelBorder }; var exclude = NSButton.CreateCheckbox("Exclude VoiceOver and speech-synthesis audio", () => { }); exclude.Frame = new CGRect(0, 5, 390, 24); exclude.State = initial.ExcludeScreenReader ? NSCellStateValue.On : NSCellStateValue.Off; ((INSAccessibility)exclude).AccessibilityLabel = "Exclude screen reader audio"; var accessory = new NSView(new CGRect(0, 0, 390, 305)); accessory.AddSubview(mode); accessory.AddSubview(scroll); accessory.AddSubview(exclude); var alert = new NSAlert { MessageText = "Choose screen audio", InformativeText = "Select what other people will hear.", AccessoryView = accessory }; alert.AddButton("Share"); alert.AddButton("Cancel"); if (alert.RunModal() != 1000) return null; var bundleIdentifiers = table.SelectedRows.ToArray().Select(index => applications[checked((int)index)].BundleIdentifier).ToHashSet(StringComparer.Ordinal); return new((ScreenAudioScope)(int)mode.IndexOfSelectedItem, bundleIdentifiers, exclude.State == NSCellStateValue.On); } private sealed class ApplicationSource(IReadOnlyList applications) : NSTableViewDataSource, INSTableViewDelegate { public override nint GetRowCount(NSTableView tableView) => applications.Count; [Export("tableView:viewForTableColumn:row:")] public NSView GetViewForItem(NSTableView tableView, NSTableColumn? tableColumn, nint row) { var value = NSTextField.CreateLabel(applications[checked((int)row)].Name); value.LineBreakMode = AppKit.NSLineBreakMode.TruncatingTail; return value; } } }