- Add VCP commands for brightness (10), contrast (12), input source (60) - Fix UTF-16 encoding for monitor data parsing - Add system tray app with monitor controls - Add localization for en, es, fr, de, zh, ja, pt, it, hi - Update to .NET 9.0 - Add LICENSE and README 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
493 lines
19 KiB
C#
493 lines
19 KiB
C#
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 DellMonitorControl;
|
|
|
|
/// <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);
|
|
}
|
|
}
|