using CMM.Library.Config; using CMM.Library.Method; using CMM.Library.ViewModel; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Threading; namespace DellMonitorControl; public partial class MainWindow : Window { private List<(XMonitor Monitor, List Options)> _loadedMonitors = new(); private Storyboard? _spinnerStoryboard; private DispatcherTimer? _showLogButtonTimer; public MainWindow() { InitializeComponent(); _spinnerStoryboard = (Storyboard)FindResource("SpinnerAnimation"); DebugLogger.Log("MainWindow initialized"); } public void ShowNearTray() { DebugLogger.Log("ShowNearTray called"); var workArea = SystemParameters.WorkArea; Left = workArea.Right - Width - 10; Top = workArea.Bottom - 350; Show(); Activate(); // Start spinner _spinnerStoryboard?.Begin(this, true); // Show log button after 3 seconds if still loading showLogButton.Visibility = Visibility.Collapsed; _showLogButtonTimer?.Stop(); _showLogButtonTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(3) }; _showLogButtonTimer.Tick += (s, e) => { _showLogButtonTimer.Stop(); if (loadingPanel.Visibility == Visibility.Visible || sp.Children.Contains(loadingPanel)) { showLogButton.Visibility = Visibility.Visible; DebugLogger.Log("Show Log button displayed (loading took > 3s)"); } }; _showLogButtonTimer.Start(); Dispatcher.BeginInvoke(new Action(() => { Top = workArea.Bottom - ActualHeight - 10; }), DispatcherPriority.Loaded); } private void Window_Deactivated(object sender, EventArgs e) { Hide(); _spinnerStoryboard?.Stop(this); } private void ExitButton_Click(object sender, RoutedEventArgs e) { Application.Current.Shutdown(); } private void ShowLogButton_Click(object sender, RoutedEventArgs e) { var logWindow = new Window { Title = "Debug Log", Width = 600, Height = 400, WindowStartupLocation = WindowStartupLocation.CenterScreen, Background = new SolidColorBrush(Color.FromRgb(45, 45, 45)) }; var grid = new Grid(); grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }); grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); var textBox = new TextBox { Text = DebugLogger.GetLogs(), IsReadOnly = true, Background = new SolidColorBrush(Color.FromRgb(30, 30, 30)), Foreground = Brushes.LightGray, FontFamily = new FontFamily("Consolas"), FontSize = 11, VerticalScrollBarVisibility = ScrollBarVisibility.Auto, HorizontalScrollBarVisibility = ScrollBarVisibility.Auto, TextWrapping = TextWrapping.NoWrap, Margin = new Thickness(10) }; Grid.SetRow(textBox, 0); grid.Children.Add(textBox); var buttonPanel = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, Margin = new Thickness(10) }; var copyBtn = new Button { Content = "Copy to Clipboard", Padding = new Thickness(10, 5, 10, 5), Margin = new Thickness(0, 0, 10, 0) }; copyBtn.Click += (s, args) => { Clipboard.SetText(DebugLogger.GetLogs()); MessageBox.Show("Log copied to clipboard!", "Debug Log", MessageBoxButton.OK, MessageBoxImage.Information); }; buttonPanel.Children.Add(copyBtn); var openFileBtn = new Button { Content = "Open Log File", Padding = new Thickness(10, 5, 10, 5), Margin = new Thickness(0, 0, 10, 0) }; openFileBtn.Click += (s, args) => { try { System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{DebugLogger.LogFilePath}\""); } catch { } }; buttonPanel.Children.Add(openFileBtn); var closeBtn = new Button { Content = "Close", Padding = new Thickness(10, 5, 10, 5) }; closeBtn.Click += (s, args) => logWindow.Close(); buttonPanel.Children.Add(closeBtn); Grid.SetRow(buttonPanel, 1); grid.Children.Add(buttonPanel); logWindow.Content = grid; logWindow.Show(); } public async Task LoadMonitors() { DebugLogger.Log("LoadMonitors started"); var newChildren = new List(); _loadedMonitors.Clear(); try { DebugLogger.Log("Scanning for monitors..."); await CMMCommand.ScanMonitor(); DebugLogger.Log("Scan complete, reading monitor data..."); var monitors = (await CMMCommand.ReadMonitorsData()).ToList(); DebugLogger.Log($"Found {monitors.Count} monitor(s)"); if (!monitors.Any()) { DebugLogger.Log("No DDC/CI monitors detected"); newChildren.Add(new TextBlock { Text = "No DDC/CI monitors detected", Foreground = Brushes.LightGray, FontSize = 12, HorizontalAlignment = HorizontalAlignment.Center, Margin = new Thickness(0, 30, 0, 30) }); } else { foreach (var m in monitors) { DebugLogger.Log($"Processing monitor: {m.MonitorName} (SN: {m.SerialNumber})"); DebugLogger.Log($" Getting brightness..."); var brightness = await CMMCommand.GetBrightness(m.SerialNumber) ?? 50; DebugLogger.Log($" Brightness: {brightness}"); DebugLogger.Log($" Getting contrast..."); var contrast = await CMMCommand.GetContrast(m.SerialNumber) ?? 50; DebugLogger.Log($" Contrast: {contrast}"); DebugLogger.Log($" Getting input source..."); var inputSource = await CMMCommand.GetInputSource(m.SerialNumber); DebugLogger.Log($" Input source: {inputSource}"); DebugLogger.Log($" Getting input options..."); var inputOptions = await CMMCommand.GetInputSourceOptions(m.SerialNumber); DebugLogger.Log($" Input options count: {inputOptions.Count}"); DebugLogger.Log($" Getting power status..."); var powerStatus = await CMMCommand.GetMonPowerStatus(m.SerialNumber) ?? "Unknown"; DebugLogger.Log($" Power status: {powerStatus}"); _loadedMonitors.Add((m, inputOptions)); // Apply config to filter hidden ports and use custom labels var filteredOptions = MonitorConfigManager.ApplyConfigToOptions(m.SerialNumber, inputOptions); DebugLogger.Log($" Filtered options count: {filteredOptions.Count}"); // Monitor name header with Config button newChildren.Add(CreateMonitorHeader(m, inputOptions)); newChildren.Add(CreateSliderRow("Brightness", brightness, m.SerialNumber, CMMCommand.SetBrightness)); newChildren.Add(CreateSliderRow("Contrast", contrast, m.SerialNumber, CMMCommand.SetContrast)); if (filteredOptions.Count > 0) newChildren.Add(CreateInputRow(inputSource, filteredOptions, m.SerialNumber)); newChildren.Add(CreatePowerRow(powerStatus, m.SerialNumber)); // Separator newChildren.Add(new Border { Height = 1, Background = new SolidColorBrush(Color.FromRgb(80, 80, 80)), Margin = new Thickness(0, 10, 0, 2) }); DebugLogger.Log($" Monitor {m.MonitorName} processing complete"); } // Remove last separator if (newChildren.Count > 0 && newChildren.Last() is Border) newChildren.RemoveAt(newChildren.Count - 1); } // Load quick-switch toolbar DebugLogger.Log("Loading quick-switch toolbar..."); LoadQuickSwitchToolbar(); DebugLogger.Log("Quick-switch toolbar loaded"); } catch (Exception ex) { DebugLogger.LogError("LoadMonitors failed", ex); newChildren.Clear(); newChildren.Add(new TextBlock { Text = $"Error: {ex.Message}", Foreground = Brushes.OrangeRed, FontSize = 11, TextWrapping = TextWrapping.Wrap }); } // Stop spinner and timer _spinnerStoryboard?.Stop(this); _showLogButtonTimer?.Stop(); sp.VerticalAlignment = VerticalAlignment.Top; sp.Children.Clear(); foreach (var child in newChildren) sp.Children.Add(child); DebugLogger.Log("LoadMonitors complete, UI updated"); Dispatcher.BeginInvoke(new Action(() => { var workArea = SystemParameters.WorkArea; Top = workArea.Bottom - ActualHeight - 10; }), DispatcherPriority.Loaded); } private void LoadQuickSwitchToolbar() { quickSwitchPanel.Children.Clear(); var quickItems = MonitorConfigManager.GetQuickSwitchItems(); if (quickItems.Count == 0) { quickSwitchPanel.Visibility = Visibility.Collapsed; return; } quickSwitchPanel.Visibility = Visibility.Visible; foreach (var item in quickItems) { var btn = new Button { Content = $"{item.PortLabel}", ToolTip = $"{item.MonitorName}: {item.PortLabel}", Margin = new Thickness(0, 0, 6, 6), Style = (Style)FindResource("QuickSwitchButton"), Tag = item }; btn.Click += async (s, e) => { if (s is Button b && b.Tag is QuickSwitchItem qsi) { await CMMCommand.SetInputSource(qsi.MonitorSerialNumber, qsi.PortVcpValue); UpdateInputDropdown(qsi.MonitorSerialNumber, qsi.PortVcpValue); } }; quickSwitchPanel.Children.Add(btn); } } private void UpdateInputDropdown(string serialNumber, int newValue) { foreach (var child in sp.Children) { if (child is StackPanel row) { foreach (var element in row.Children) { if (element is ComboBox cb && cb.Tag is string sn && sn == serialNumber) { var options = cb.ItemsSource as List; if (options != null) { var index = options.FindIndex(o => o.Value == newValue); if (index >= 0) cb.SelectedIndex = index; } return; } } } } } private StackPanel CreateMonitorHeader(XMonitor monitor, List allOptions) { var row = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 8, 0, 6) }; row.Children.Add(new TextBlock { Text = monitor.MonitorName, Foreground = Brushes.White, FontSize = 13, FontWeight = FontWeights.SemiBold, VerticalAlignment = VerticalAlignment.Center }); var configBtn = new Button { Content = "Config", Margin = new Thickness(10, 0, 0, 0), FontSize = 11, Style = (Style)FindResource("DarkButton"), Tag = (monitor, allOptions) }; configBtn.Click += ConfigButton_Click; row.Children.Add(configBtn); return row; } private async void ConfigButton_Click(object sender, RoutedEventArgs e) { if (sender is Button btn && btn.Tag is ValueTuple> data) { var (monitor, options) = data; var configWindow = new ConfigWindow(monitor.SerialNumber, monitor.MonitorName, options); configWindow.Owner = this; configWindow.ShowDialog(); if (configWindow.ConfigChanged) { MonitorConfigManager.ClearCache(); await LoadMonitors(); } } } private StackPanel CreateSliderRow(string label, int value, string serialNumber, Func setCommand) { var row = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 3) }; row.Children.Add(new TextBlock { Text = label, Foreground = Brushes.LightGray, Width = 70, FontSize = 12, VerticalAlignment = VerticalAlignment.Center }); var slider = new Slider { Minimum = 0, Maximum = 100, Value = value, Width = 140, Tag = serialNumber }; var valueText = new TextBlock { Text = value.ToString(), Foreground = Brushes.White, Width = 30, FontSize = 12, Margin = new Thickness(5, 0, 0, 0) }; slider.ValueChanged += (s, e) => valueText.Text = ((int)e.NewValue).ToString(); slider.PreviewMouseUp += async (s, e) => { if (s is Slider sl && sl.Tag is string sn) await setCommand(sn, (int)sl.Value); }; row.Children.Add(slider); row.Children.Add(valueText); return row; } private StackPanel CreateInputRow(int? currentInput, List options, string serialNumber) { var row = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 3) }; row.Children.Add(new TextBlock { Text = "Input", Foreground = Brushes.LightGray, Width = 70, FontSize = 12, VerticalAlignment = VerticalAlignment.Center }); var combo = new ComboBox { Width = 170, ItemsSource = options, DisplayMemberPath = "Name", Tag = serialNumber, Style = (Style)FindResource("DarkComboBox"), ItemContainerStyle = (Style)FindResource("DarkComboBoxItem") }; row.Children.Add(combo); // Set selection AFTER adding to visual tree, BEFORE adding event handler if (currentInput.HasValue) { var index = options.FindIndex(o => o.Value == currentInput.Value); if (index >= 0) combo.SelectedIndex = index; } // Add event handler AFTER setting the initial selection combo.SelectionChanged += async (s, e) => { // Only trigger if user actually changed the selection (not initial load) if (s is ComboBox cb && cb.Tag is string sn && cb.SelectedItem is InputSourceOption opt && e.AddedItems.Count > 0 && e.RemovedItems.Count > 0) await CMMCommand.SetInputSource(sn, opt.Value); }; return row; } private StackPanel CreatePowerRow(string status, string serialNumber) { var row = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 3) }; row.Children.Add(new TextBlock { Text = "Power", Foreground = Brushes.LightGray, Width = 70, FontSize = 12, VerticalAlignment = VerticalAlignment.Center }); var btn = new Button { Content = status, Width = 170, Tag = serialNumber, Style = (Style)FindResource("DarkButton") }; btn.Click += async (s, e) => { if (s is Button b && b.Tag is string sn) { var current = await CMMCommand.GetMonPowerStatus(sn); if (current == "Sleep" || current == "PowerOff") await CMMCommand.PowerOn(sn); else await CMMCommand.Sleep(sn); await Task.Delay(1000); b.Content = await CMMCommand.GetMonPowerStatus(sn); } }; row.Children.Add(btn); return row; } }