refactor(ci): rename DellMonitorControl to MonitorControl
Rename project from DellMonitorControl to MonitorControl to reflect broader monitor compatibility beyond Dell hardware. Update all references in solution file, workflow, and project paths. Add DDC/CI rate limiting and throttling logic to prevent command failures: - Minimum 50ms interval between commands to same monitor - 8-second grace period after system resume before sending commands - 3-second cooldown after timeout to allow monitor recovery - Global semaphore to prevent command collisions Replace old icon with new generic monitor icon (ico and png formats).
This commit is contained in:
492
MonitorControl/ControlPanel.xaml.cs
Normal file
492
MonitorControl/ControlPanel.xaml.cs
Normal file
@@ -0,0 +1,492 @@
|
||||
using CMM.Language;
|
||||
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.Input;
|
||||
|
||||
namespace MonitorControl;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for ControlPanel.xaml
|
||||
/// </summary>
|
||||
public partial class ControlPanel : UserControl
|
||||
{
|
||||
public ControlPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public async Task Refresh()
|
||||
{
|
||||
try
|
||||
{
|
||||
sp.Children.Clear();
|
||||
|
||||
await CMMCommand.ScanMonitor();
|
||||
var monitors = (await CMMCommand.ReadMonitorsData()).ToList();
|
||||
|
||||
if (!monitors.Any())
|
||||
{
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "No DDC/CI monitors detected",
|
||||
Foreground = System.Windows.Media.Brushes.White,
|
||||
FontSize = 14
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var m in monitors)
|
||||
{
|
||||
var brightness = await CMMCommand.GetBrightness(m.SerialNumber) ?? 50;
|
||||
var contrast = await CMMCommand.GetContrast(m.SerialNumber) ?? 50;
|
||||
var inputSource = await CMMCommand.GetInputSource(m.SerialNumber);
|
||||
var inputOptions = await CMMCommand.GetInputSourceOptions(m.SerialNumber);
|
||||
var powerStatus = await CMMCommand.GetMonPowerStatus(m.SerialNumber) ?? "Unknown";
|
||||
|
||||
// Monitor name header
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = m.MonitorName,
|
||||
Foreground = System.Windows.Media.Brushes.White,
|
||||
FontSize = 14,
|
||||
FontWeight = FontWeights.Bold,
|
||||
Margin = new Thickness(0, 10, 0, 5)
|
||||
});
|
||||
|
||||
// Brightness row
|
||||
var brightnessRow = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 3) };
|
||||
brightnessRow.Children.Add(new TextBlock { Text = "Brightness", Foreground = System.Windows.Media.Brushes.LightGray, Width = 70, FontSize = 12, VerticalAlignment = VerticalAlignment.Center });
|
||||
var bSlider = new Slider { Minimum = 0, Maximum = 100, Value = brightness, Width = 140, VerticalAlignment = VerticalAlignment.Center, Tag = m.SerialNumber };
|
||||
var bValue = new TextBlock { Text = brightness.ToString(), Foreground = System.Windows.Media.Brushes.White, Width = 30, FontSize = 12, Margin = new Thickness(5, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center };
|
||||
bSlider.ValueChanged += (s, e) => bValue.Text = ((int)e.NewValue).ToString();
|
||||
bSlider.PreviewMouseUp += async (s, e) => { if (s is Slider sl && sl.Tag is string sn) await CMMCommand.SetBrightness(sn, (int)sl.Value); };
|
||||
brightnessRow.Children.Add(bSlider);
|
||||
brightnessRow.Children.Add(bValue);
|
||||
sp.Children.Add(brightnessRow);
|
||||
|
||||
// Contrast row
|
||||
var contrastRow = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 3) };
|
||||
contrastRow.Children.Add(new TextBlock { Text = "Contrast", Foreground = System.Windows.Media.Brushes.LightGray, Width = 70, FontSize = 12, VerticalAlignment = VerticalAlignment.Center });
|
||||
var cSlider = new Slider { Minimum = 0, Maximum = 100, Value = contrast, Width = 140, VerticalAlignment = VerticalAlignment.Center, Tag = m.SerialNumber };
|
||||
var cValue = new TextBlock { Text = contrast.ToString(), Foreground = System.Windows.Media.Brushes.White, Width = 30, FontSize = 12, Margin = new Thickness(5, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center };
|
||||
cSlider.ValueChanged += (s, e) => cValue.Text = ((int)e.NewValue).ToString();
|
||||
cSlider.PreviewMouseUp += async (s, e) => { if (s is Slider sl && sl.Tag is string sn) await CMMCommand.SetContrast(sn, (int)sl.Value); };
|
||||
contrastRow.Children.Add(cSlider);
|
||||
contrastRow.Children.Add(cValue);
|
||||
sp.Children.Add(contrastRow);
|
||||
|
||||
// Input source row (if options available)
|
||||
if (inputOptions.Count > 0)
|
||||
{
|
||||
var inputRow = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 3) };
|
||||
inputRow.Children.Add(new TextBlock { Text = "Input", Foreground = System.Windows.Media.Brushes.LightGray, Width = 70, FontSize = 12, VerticalAlignment = VerticalAlignment.Center });
|
||||
var combo = new ComboBox { Width = 170, ItemsSource = inputOptions, DisplayMemberPath = "Name", Tag = m.SerialNumber };
|
||||
if (inputSource.HasValue) combo.SelectedItem = inputOptions.Find(o => o.Value == inputSource.Value);
|
||||
combo.SelectionChanged += async (s, e) => { if (s is ComboBox cb && cb.Tag is string sn && cb.SelectedItem is InputSourceOption opt) await CMMCommand.SetInputSource(sn, opt.Value); };
|
||||
inputRow.Children.Add(combo);
|
||||
sp.Children.Add(inputRow);
|
||||
}
|
||||
|
||||
// Power row
|
||||
var powerRow = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 3) };
|
||||
powerRow.Children.Add(new TextBlock { Text = "Power", Foreground = System.Windows.Media.Brushes.LightGray, Width = 70, FontSize = 12, VerticalAlignment = VerticalAlignment.Center });
|
||||
var powerBtn = new Button { Content = powerStatus, Width = 170, Tag = m.SerialNumber };
|
||||
powerBtn.Click += async (s, e) =>
|
||||
{
|
||||
if (s is Button btn && btn.Tag is string sn)
|
||||
{
|
||||
var status = await CMMCommand.GetMonPowerStatus(sn);
|
||||
if (status == "Sleep" || status == "PowerOff") await CMMCommand.PowerOn(sn);
|
||||
else await CMMCommand.Sleep(sn);
|
||||
await Task.Delay(1000);
|
||||
btn.Content = await CMMCommand.GetMonPowerStatus(sn);
|
||||
}
|
||||
};
|
||||
powerRow.Children.Add(powerBtn);
|
||||
sp.Children.Add(powerRow);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
sp.Children.Clear();
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"Error: {ex.Message}",
|
||||
Foreground = System.Windows.Media.Brushes.Red,
|
||||
FontSize = 12,
|
||||
TextWrapping = TextWrapping.Wrap
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private StackPanel CreateControlSimple(
|
||||
XMonitor monitorModel,
|
||||
int? brightness,
|
||||
int? contrast,
|
||||
int? currentInputSource,
|
||||
List<InputSourceOption> inputOptions,
|
||||
string powerStatus)
|
||||
{
|
||||
var container = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Vertical,
|
||||
Margin = new Thickness(0, 5, 0, 10)
|
||||
};
|
||||
|
||||
// Monitor name
|
||||
container.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"{monitorModel.MonitorName} ({monitorModel.SerialNumber})",
|
||||
Foreground = System.Windows.Media.Brushes.White,
|
||||
FontSize = 14,
|
||||
FontWeight = FontWeights.Bold,
|
||||
Margin = new Thickness(0, 0, 0, 5)
|
||||
});
|
||||
|
||||
// Brightness
|
||||
var brightnessPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 2, 0, 2) };
|
||||
brightnessPanel.Children.Add(new TextBlock { Text = "Brightness:", Foreground = System.Windows.Media.Brushes.LightGray, Width = 80, FontSize = 12 });
|
||||
var brightnessSlider = new Slider { Minimum = 0, Maximum = 100, Value = brightness ?? 50, Width = 150, Tag = monitorModel.SerialNumber };
|
||||
var brightnessValue = new TextBlock { Text = (brightness ?? 50).ToString(), Foreground = System.Windows.Media.Brushes.White, Width = 30, FontSize = 12, Margin = new Thickness(5, 0, 0, 0) };
|
||||
brightnessSlider.ValueChanged += (s, e) => brightnessValue.Text = ((int)e.NewValue).ToString();
|
||||
brightnessSlider.PreviewMouseUp += async (s, e) =>
|
||||
{
|
||||
var sld = s as Slider;
|
||||
if (sld?.Tag is string sn)
|
||||
await CMMCommand.SetBrightness(sn, (int)sld.Value);
|
||||
};
|
||||
brightnessPanel.Children.Add(brightnessSlider);
|
||||
brightnessPanel.Children.Add(brightnessValue);
|
||||
container.Children.Add(brightnessPanel);
|
||||
|
||||
// Contrast
|
||||
var contrastPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 2, 0, 2) };
|
||||
contrastPanel.Children.Add(new TextBlock { Text = "Contrast:", Foreground = System.Windows.Media.Brushes.LightGray, Width = 80, FontSize = 12 });
|
||||
var contrastSlider = new Slider { Minimum = 0, Maximum = 100, Value = contrast ?? 50, Width = 150, Tag = monitorModel.SerialNumber };
|
||||
var contrastValue = new TextBlock { Text = (contrast ?? 50).ToString(), Foreground = System.Windows.Media.Brushes.White, Width = 30, FontSize = 12, Margin = new Thickness(5, 0, 0, 0) };
|
||||
contrastSlider.ValueChanged += (s, e) => contrastValue.Text = ((int)e.NewValue).ToString();
|
||||
contrastSlider.PreviewMouseUp += async (s, e) =>
|
||||
{
|
||||
var sld = s as Slider;
|
||||
if (sld?.Tag is string sn)
|
||||
await CMMCommand.SetContrast(sn, (int)sld.Value);
|
||||
};
|
||||
contrastPanel.Children.Add(contrastSlider);
|
||||
contrastPanel.Children.Add(contrastValue);
|
||||
container.Children.Add(contrastPanel);
|
||||
|
||||
// Input source (if available)
|
||||
if (inputOptions.Count > 0)
|
||||
{
|
||||
var inputPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 2, 0, 2) };
|
||||
inputPanel.Children.Add(new TextBlock { Text = "Input:", Foreground = System.Windows.Media.Brushes.LightGray, Width = 80, FontSize = 12 });
|
||||
var combo = new ComboBox { Width = 150, ItemsSource = inputOptions, DisplayMemberPath = "Name", Tag = monitorModel.SerialNumber };
|
||||
if (currentInputSource.HasValue)
|
||||
combo.SelectedItem = inputOptions.Find(o => o.Value == currentInputSource.Value);
|
||||
combo.SelectionChanged += async (s, e) =>
|
||||
{
|
||||
var cb = s as ComboBox;
|
||||
if (cb?.Tag is string sn && cb.SelectedItem is InputSourceOption opt)
|
||||
await CMMCommand.SetInputSource(sn, opt.Value);
|
||||
};
|
||||
inputPanel.Children.Add(combo);
|
||||
container.Children.Add(inputPanel);
|
||||
}
|
||||
|
||||
// Power
|
||||
var powerPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 2, 0, 2) };
|
||||
powerPanel.Children.Add(new TextBlock { Text = "Power:", Foreground = System.Windows.Media.Brushes.LightGray, Width = 80, FontSize = 12 });
|
||||
var powerBtn = new Button { Content = powerStatus, Width = 150, Tag = monitorModel.SerialNumber };
|
||||
powerBtn.Click += async (s, e) =>
|
||||
{
|
||||
var btn = s as Button;
|
||||
if (btn?.Tag is string sn)
|
||||
{
|
||||
var status = await CMMCommand.GetMonPowerStatus(sn);
|
||||
if (status == "Sleep" || status == "PowerOff")
|
||||
await CMMCommand.PowerOn(sn);
|
||||
else
|
||||
await CMMCommand.Sleep(sn);
|
||||
await Task.Delay(1000);
|
||||
btn.Content = await CMMCommand.GetMonPowerStatus(sn);
|
||||
}
|
||||
};
|
||||
powerPanel.Children.Add(powerBtn);
|
||||
container.Children.Add(powerPanel);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private StackPanel CreateControl(
|
||||
XMonitor monitorModel,
|
||||
int? brightness,
|
||||
int? contrast,
|
||||
int? currentInputSource,
|
||||
List<InputSourceOption> inputOptions,
|
||||
string powerStatus)
|
||||
{
|
||||
var container = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Vertical,
|
||||
Margin = new Thickness(10, 5, 10, 10)
|
||||
};
|
||||
|
||||
// Monitor name header
|
||||
var header = new TextBlock
|
||||
{
|
||||
Text = $"{monitorModel.MonitorName} ({monitorModel.SerialNumber})",
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
Style = (Style)FindResource("LableStyle"),
|
||||
Margin = new Thickness(0, 0, 0, 5)
|
||||
};
|
||||
container.Children.Add(header);
|
||||
|
||||
// Brightness slider
|
||||
var brightnessPanel = CreateSliderControl(
|
||||
Lang.Find("Brightness"),
|
||||
brightness ?? 50,
|
||||
monitorModel.SerialNumber,
|
||||
async (sn, value) => await CMMCommand.SetBrightness(sn, value));
|
||||
container.Children.Add(brightnessPanel);
|
||||
|
||||
// Contrast slider
|
||||
var contrastPanel = CreateSliderControl(
|
||||
Lang.Find("Contrast"),
|
||||
contrast ?? 50,
|
||||
monitorModel.SerialNumber,
|
||||
async (sn, value) => await CMMCommand.SetContrast(sn, value));
|
||||
container.Children.Add(contrastPanel);
|
||||
|
||||
// Input source dropdown
|
||||
if (inputOptions.Count > 0)
|
||||
{
|
||||
var inputPanel = CreateInputSourceControl(
|
||||
monitorModel.SerialNumber,
|
||||
currentInputSource,
|
||||
inputOptions);
|
||||
container.Children.Add(inputPanel);
|
||||
}
|
||||
|
||||
// Power button
|
||||
var powerPanel = CreatePowerControl(monitorModel.SerialNumber, powerStatus);
|
||||
container.Children.Add(powerPanel);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private StackPanel CreateSliderControl(
|
||||
string label,
|
||||
int currentValue,
|
||||
string monitorSN,
|
||||
Func<string, int, Task> onValueChanged)
|
||||
{
|
||||
var panel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
Margin = new Thickness(0, 3, 0, 3)
|
||||
};
|
||||
|
||||
var labelBlock = new TextBlock
|
||||
{
|
||||
Text = label,
|
||||
Width = 80,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Style = (Style)FindResource("LableStyle")
|
||||
};
|
||||
|
||||
var slider = new Slider
|
||||
{
|
||||
Style = (Style)FindResource("Horizontal_Slider"),
|
||||
Minimum = 0,
|
||||
Maximum = 100,
|
||||
Value = currentValue,
|
||||
Tag = monitorSN,
|
||||
Width = 180
|
||||
};
|
||||
|
||||
var valueBlock = new TextBlock
|
||||
{
|
||||
Text = currentValue.ToString(),
|
||||
Width = 35,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Style = (Style)FindResource("LableStyle"),
|
||||
TextAlignment = TextAlignment.Right
|
||||
};
|
||||
|
||||
// Update value display while dragging
|
||||
slider.ValueChanged += (s, e) =>
|
||||
{
|
||||
valueBlock.Text = ((int)e.NewValue).ToString();
|
||||
};
|
||||
|
||||
// Only send command on release (PreviewMouseUp)
|
||||
slider.PreviewMouseUp += async (s, e) =>
|
||||
{
|
||||
var sld = s as Slider;
|
||||
var sn = sld?.Tag?.ToString();
|
||||
if (!string.IsNullOrEmpty(sn) && sld != null)
|
||||
{
|
||||
await onValueChanged(sn, (int)sld.Value);
|
||||
}
|
||||
};
|
||||
|
||||
panel.Children.Add(labelBlock);
|
||||
panel.Children.Add(slider);
|
||||
panel.Children.Add(valueBlock);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private StackPanel CreateInputSourceControl(
|
||||
string monitorSN,
|
||||
int? currentInputSource,
|
||||
List<InputSourceOption> options)
|
||||
{
|
||||
var panel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
Margin = new Thickness(0, 3, 0, 3)
|
||||
};
|
||||
|
||||
var labelBlock = new TextBlock
|
||||
{
|
||||
Text = Lang.Find("InputSource"),
|
||||
Width = 80,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Style = (Style)FindResource("LableStyle")
|
||||
};
|
||||
|
||||
var comboBox = new ComboBox
|
||||
{
|
||||
Width = 180,
|
||||
Tag = monitorSN,
|
||||
ItemsSource = options,
|
||||
DisplayMemberPath = "Name"
|
||||
};
|
||||
|
||||
// Set current selection
|
||||
if (currentInputSource.HasValue)
|
||||
{
|
||||
var currentOption = options.Find(o => o.Value == currentInputSource.Value);
|
||||
if (currentOption != null)
|
||||
{
|
||||
comboBox.SelectedItem = currentOption;
|
||||
}
|
||||
}
|
||||
|
||||
comboBox.SelectionChanged += async (s, e) =>
|
||||
{
|
||||
var cb = s as ComboBox;
|
||||
var sn = cb?.Tag?.ToString();
|
||||
var selectedOption = cb?.SelectedItem as InputSourceOption;
|
||||
|
||||
if (!string.IsNullOrEmpty(sn) && selectedOption != null)
|
||||
{
|
||||
await CMMCommand.SetInputSource(sn, selectedOption.Value);
|
||||
}
|
||||
};
|
||||
|
||||
panel.Children.Add(labelBlock);
|
||||
panel.Children.Add(comboBox);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private StackPanel CreatePowerControl(string monitorSN, string powerStatus)
|
||||
{
|
||||
var panel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
Margin = new Thickness(0, 5, 0, 0)
|
||||
};
|
||||
|
||||
var labelBlock = new TextBlock
|
||||
{
|
||||
Text = Lang.Find("Power"),
|
||||
Width = 80,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Style = (Style)FindResource("LableStyle")
|
||||
};
|
||||
|
||||
var btn = new Button
|
||||
{
|
||||
Tag = monitorSN,
|
||||
Content = GetLocalizedPowerStatus(powerStatus),
|
||||
Style = (Style)FindResource("TextButtonStyle"),
|
||||
Width = 180
|
||||
};
|
||||
|
||||
btn.Click += async (s, e) => await TogglePower(s, e);
|
||||
|
||||
panel.Children.Add(labelBlock);
|
||||
panel.Children.Add(btn);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private string GetLocalizedPowerStatus(string status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
"PowerOn" => Lang.Find("PowerOn"),
|
||||
"Sleep" => Lang.Find("Sleep"),
|
||||
"PowerOff" => Lang.Find("PowerOff"),
|
||||
_ => status
|
||||
};
|
||||
}
|
||||
|
||||
private async Task TogglePower(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var btn = sender as Button;
|
||||
var tag = btn?.Tag?.ToString();
|
||||
if (string.IsNullOrEmpty(tag)) return;
|
||||
|
||||
var currentStatus = await CMMCommand.GetMonPowerStatus(tag);
|
||||
|
||||
if (currentStatus == "Sleep" || currentStatus == "PowerOff")
|
||||
{
|
||||
await CMMCommand.PowerOn(tag);
|
||||
}
|
||||
else
|
||||
{
|
||||
await CMMCommand.Sleep(tag);
|
||||
}
|
||||
|
||||
await Task.Delay(1000);
|
||||
var newStatus = await CMMCommand.GetMonPowerStatus(tag);
|
||||
btn!.Content = GetLocalizedPowerStatus(newStatus);
|
||||
}
|
||||
|
||||
private void Border_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
private void UserControl_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Use Dispatcher to let the popup fully render first
|
||||
Dispatcher.BeginInvoke(new Action(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await Refresh();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
sp.Children.Clear();
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"Error: {ex.Message}",
|
||||
Foreground = System.Windows.Media.Brushes.Red,
|
||||
FontSize = 12,
|
||||
TextWrapping = TextWrapping.Wrap
|
||||
});
|
||||
}
|
||||
}), System.Windows.Threading.DispatcherPriority.Background);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user