using CMM.Library.Config; using CMM.Library.Method; using CMM.Library.ViewModel; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace MonitorControl; public partial class ConfigWindow : Window { private readonly string _serialNumber; private readonly string _monitorName; private readonly List _availablePorts; private readonly List _portRows = new(); private CancellationTokenSource? _detectCts; public bool ConfigChanged { get; private set; } public ConfigWindow(string serialNumber, string monitorName, List availablePorts) { InitializeComponent(); _serialNumber = serialNumber; _monitorName = monitorName; _availablePorts = availablePorts; tbHeader.Text = $"Configure: {monitorName}"; LoadPortConfiguration(); } private void LoadPortConfiguration() { var config = MonitorConfigManager.GetMonitorConfig(_serialNumber); spPorts.Children.Clear(); _portRows.Clear(); // Show unsupported message if no ports available if (_availablePorts == null || _availablePorts.Count == 0) { spPorts.Children.Add(new TextBlock { Text = "Unsupported", Foreground = Brushes.Gray, FontSize = 16, FontStyle = FontStyles.Italic, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 40, 0, 40) }); return; } foreach (var port in _availablePorts) { var existingPortConfig = config.Ports.FirstOrDefault(p => p.VcpValue == port.Value); var row = new PortConfigRow { VcpValue = port.Value, DefaultName = port.Name, CustomLabel = existingPortConfig?.CustomLabel ?? "", IsHidden = existingPortConfig?.IsHidden ?? false, ShowInQuickSwitch = existingPortConfig?.ShowInQuickSwitch ?? false }; _portRows.Add(row); spPorts.Children.Add(CreatePortRow(row)); } } private UIElement CreatePortRow(PortConfigRow row) { var container = new Border { Background = new SolidColorBrush(Color.FromRgb(60, 60, 60)), CornerRadius = new CornerRadius(4), Padding = new Thickness(10), Margin = new Thickness(0, 0, 0, 8) }; var grid = new Grid(); grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); // Row 1: Port name and Hide checkbox var row1 = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 0, 0, 6) }; row1.Children.Add(new TextBlock { Text = row.DefaultName, Foreground = Brushes.White, FontWeight = FontWeights.SemiBold, FontSize = 13, Width = 120, VerticalAlignment = VerticalAlignment.Center }); var hideCheck = new CheckBox { Content = "Hide", IsChecked = row.IsHidden, Foreground = Brushes.LightGray, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(10, 0, 0, 0) }; hideCheck.Checked += (s, e) => row.IsHidden = true; hideCheck.Unchecked += (s, e) => row.IsHidden = false; row1.Children.Add(hideCheck); Grid.SetRow(row1, 0); grid.Children.Add(row1); // Row 2: Custom label var row2 = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 0, 0, 6) }; row2.Children.Add(new TextBlock { Text = "Label:", Foreground = Brushes.LightGray, FontSize = 12, Width = 50, VerticalAlignment = VerticalAlignment.Center }); var labelBox = new TextBox { Text = row.CustomLabel, Width = 200, Background = new SolidColorBrush(Color.FromRgb(50, 50, 50)), Foreground = Brushes.White, BorderBrush = new SolidColorBrush(Color.FromRgb(80, 80, 80)), Padding = new Thickness(6, 4, 6, 4) }; labelBox.TextChanged += (s, e) => row.CustomLabel = labelBox.Text; row2.Children.Add(labelBox); Grid.SetRow(row2, 1); grid.Children.Add(row2); // Row 3: Quick switch checkbox var quickSwitchCheck = new CheckBox { Content = "Show in quick-switch toolbar", IsChecked = row.ShowInQuickSwitch, Foreground = Brushes.LightGray, FontSize = 12 }; quickSwitchCheck.Checked += (s, e) => row.ShowInQuickSwitch = true; quickSwitchCheck.Unchecked += (s, e) => row.ShowInQuickSwitch = false; Grid.SetRow(quickSwitchCheck, 2); grid.Children.Add(quickSwitchCheck); container.Child = grid; return container; } private void SaveButton_Click(object sender, RoutedEventArgs e) { var config = new MonitorConfig { SerialNumber = _serialNumber, MonitorName = _monitorName, Ports = _portRows.Select(r => new PortConfig { VcpValue = r.VcpValue, DefaultName = r.DefaultName, CustomLabel = r.CustomLabel, IsHidden = r.IsHidden, ShowInQuickSwitch = r.ShowInQuickSwitch }).ToList() }; MonitorConfigManager.SaveMonitorConfig(config); ConfigChanged = true; Close(); } private void CancelButton_Click(object sender, RoutedEventArgs e) { Close(); } private void ResetButton_Click(object sender, RoutedEventArgs e) { // Reset all port rows to default values foreach (var row in _portRows) { row.CustomLabel = ""; row.IsHidden = false; row.ShowInQuickSwitch = false; } // Reload the UI to reflect changes LoadPortConfiguration(); // Clear any discovered ports from config var config = MonitorConfigManager.GetMonitorConfig(_serialNumber); config.Ports.Clear(); MonitorConfigManager.SaveMonitorConfig(config); MonitorConfigManager.ClearCache(); } private async void DetectButton_Click(object sender, RoutedEventArgs e) { // If detection is already running, cancel it if (_detectCts != null) { _detectCts.Cancel(); return; } // Warn the user about what this does var confirm = MessageBox.Show( "Port detection will temporarily switch your monitor's input source to probe for available ports. " + "Your screen may go dark briefly during each probe.\n\n" + "This is safe for most monitors, but avoid running it repeatedly in a short time.\n\n" + "You can click the Cancel button to stop detection at any time.\n\n" + "Continue?", "Port Detection", MessageBoxButton.YesNo, MessageBoxImage.Warning); if (confirm != MessageBoxResult.Yes) return; _detectCts = new CancellationTokenSource(); var ct = _detectCts.Token; btnDetect.Content = "Cancel"; try { // Only probe modern ports (DP/HDMI) by default — legacy ports (VGA/DVI) are rarely used // and probing them on monitors without those connectors wastes time and stresses the scaler var commonPorts = new[] { 15, 16, 17, 18 }; var detectedPorts = new List(); // Get current input so we can restore it var currentInput = await CMMCommand.GetInputSource(_serialNumber); foreach (var vcpValue in commonPorts) { ct.ThrowIfCancellationRequested(); // Skip ports we already know about if (_availablePorts.Any(p => p.Value == vcpValue)) continue; try { // Try to set the input - if it succeeds, the port exists await CMMCommand.SetInputSource(_serialNumber, vcpValue); // Give the monitor's scaler time to attempt signal lock on the new input. // 3 seconds is the minimum safe settle time for most scalers. await Task.Delay(3000, ct); // Check if the input actually changed var newInput = await CMMCommand.GetInputSource(_serialNumber); if (newInput == vcpValue) { detectedPorts.Add(vcpValue); } } catch (OperationCanceledException) { throw; } catch { // Port doesn't exist or isn't supported — continue to next } // Extra settle time between probes to let the scaler fully recover try { await Task.Delay(1000, ct); } catch (OperationCanceledException) { throw; } } // Restore original input — give it extra time to settle after all the switching if (currentInput.HasValue) { await CMMCommand.SetInputSource(_serialNumber, currentInput.Value); await Task.Delay(2000); } if (detectedPorts.Count > 0) { foreach (var vcpValue in detectedPorts) { var name = CMMCommand.GetInputSourceName(vcpValue); _availablePorts.Add(new InputSourceOption(vcpValue, name)); MonitorConfigManager.AddDiscoveredPort(_serialNumber, _monitorName, vcpValue, name); } MonitorConfigManager.ClearCache(); LoadPortConfiguration(); MessageBox.Show($"Detected {detectedPorts.Count} new port(s):\n" + string.Join("\n", detectedPorts.Select(v => $" • {CMMCommand.GetInputSourceName(v)}")), "Detection Complete", MessageBoxButton.OK, MessageBoxImage.Information); } else { MessageBox.Show("No additional ports were detected.", "Detection Complete", MessageBoxButton.OK, MessageBoxImage.Information); } } catch (OperationCanceledException) { // User cancelled — try to restore original input before exiting try { var currentInput = await CMMCommand.GetInputSource(_serialNumber); // Only restore if we can still talk to the monitor if (currentInput.HasValue) await CMMCommand.SetInputSource(_serialNumber, currentInput.Value); } catch { } MessageBox.Show("Port detection was cancelled.", "Detection Cancelled", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { MessageBox.Show($"Detection failed: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } finally { _detectCts?.Dispose(); _detectCts = null; btnDetect.Content = "Detect"; } } private class PortConfigRow { public int VcpValue { get; set; } public string DefaultName { get; set; } = ""; public string CustomLabel { get; set; } = ""; public bool IsHidden { get; set; } public bool ShowInQuickSwitch { get; set; } } }