- 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>
290 lines
9.0 KiB
C#
290 lines
9.0 KiB
C#
using System.Windows.Controls;
|
|
using System.Windows;
|
|
using CMM.Library.ViewModel;
|
|
using CMM.Library.Method;
|
|
using CMM.Language;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace CMM.Management.Control
|
|
{
|
|
/// <summary>
|
|
/// Single monitor control
|
|
/// </summary>
|
|
internal class MonCtrl : System.Windows.Controls.Control
|
|
{
|
|
public static readonly DependencyProperty MonProperty;
|
|
private StackPanel _sp;
|
|
|
|
static MonCtrl()
|
|
{
|
|
DefaultStyleKeyProperty.OverrideMetadata(typeof(MonCtrl), new FrameworkPropertyMetadata(typeof(MonCtrl)));
|
|
|
|
MonProperty = DependencyProperty.Register(
|
|
"Monitor",
|
|
typeof(XMonitor),
|
|
typeof(MonCtrl),
|
|
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnMonChangedCallback));
|
|
}
|
|
|
|
public override void OnApplyTemplate()
|
|
{
|
|
_sp = Template.FindName("sp", this) as StackPanel;
|
|
if (_sp != null && Mon != null)
|
|
{
|
|
_ = LoadControlsAsync();
|
|
}
|
|
}
|
|
|
|
public XMonitor Mon
|
|
{
|
|
get => (XMonitor)GetValue(MonProperty);
|
|
set => SetValue(MonProperty, value);
|
|
}
|
|
|
|
static void OnMonChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs args)
|
|
{
|
|
var me = sender as MonCtrl;
|
|
if (me != null)
|
|
{
|
|
me.OnMonChanged((XMonitor)args.NewValue);
|
|
}
|
|
}
|
|
|
|
public virtual void OnMonChanged(XMonitor value)
|
|
{
|
|
if (_sp != null && value != null)
|
|
{
|
|
_ = LoadControlsAsync();
|
|
}
|
|
}
|
|
|
|
private async Task LoadControlsAsync()
|
|
{
|
|
if (_sp == null || Mon == null) return;
|
|
|
|
_sp.Children.Clear();
|
|
|
|
var brightness = await CMMCommand.GetBrightness(Mon.SerialNumber);
|
|
var contrast = await CMMCommand.GetContrast(Mon.SerialNumber);
|
|
var inputSource = await CMMCommand.GetInputSource(Mon.SerialNumber);
|
|
var inputOptions = await CMMCommand.GetInputSourceOptions(Mon.SerialNumber);
|
|
var powerStatus = await CMMCommand.GetMonPowerStatus(Mon.SerialNumber);
|
|
|
|
// Brightness slider
|
|
var brightnessPanel = CreateSliderControl(
|
|
Lang.Find("Brightness"),
|
|
brightness ?? 50,
|
|
Mon.SerialNumber,
|
|
async (sn, value) => await CMMCommand.SetBrightness(sn, value));
|
|
_sp.Children.Add(brightnessPanel);
|
|
|
|
// Contrast slider
|
|
var contrastPanel = CreateSliderControl(
|
|
Lang.Find("Contrast"),
|
|
contrast ?? 50,
|
|
Mon.SerialNumber,
|
|
async (sn, value) => await CMMCommand.SetContrast(sn, value));
|
|
_sp.Children.Add(contrastPanel);
|
|
|
|
// Input source dropdown
|
|
if (inputOptions.Count > 0)
|
|
{
|
|
var inputPanel = CreateInputSourceControl(
|
|
Mon.SerialNumber,
|
|
inputSource,
|
|
inputOptions);
|
|
_sp.Children.Add(inputPanel);
|
|
}
|
|
|
|
// Power button
|
|
var powerPanel = CreatePowerControl(Mon.SerialNumber, powerStatus);
|
|
_sp.Children.Add(powerPanel);
|
|
}
|
|
|
|
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, 5, 0, 5)
|
|
};
|
|
|
|
var labelBlock = new TextBlock
|
|
{
|
|
Text = label,
|
|
Width = 100,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
Foreground = System.Windows.Media.Brushes.White
|
|
};
|
|
|
|
var slider = new Slider
|
|
{
|
|
Style = (Style)FindResource("Horizontal_Slider"),
|
|
Minimum = 0,
|
|
Maximum = 100,
|
|
Value = currentValue,
|
|
Tag = monitorSN,
|
|
Width = 200
|
|
};
|
|
|
|
var valueBlock = new TextBlock
|
|
{
|
|
Text = currentValue.ToString(),
|
|
Width = 40,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
Foreground = System.Windows.Media.Brushes.White,
|
|
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))
|
|
{
|
|
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, 5, 0, 5)
|
|
};
|
|
|
|
var labelBlock = new TextBlock
|
|
{
|
|
Text = Lang.Find("InputSource"),
|
|
Width = 100,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
Foreground = System.Windows.Media.Brushes.White
|
|
};
|
|
|
|
var comboBox = new ComboBox
|
|
{
|
|
Width = 200,
|
|
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, 10, 0, 0)
|
|
};
|
|
|
|
var labelBlock = new TextBlock
|
|
{
|
|
Text = Lang.Find("Power"),
|
|
Width = 100,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
Foreground = System.Windows.Media.Brushes.White
|
|
};
|
|
|
|
var btn = new Button
|
|
{
|
|
Tag = monitorSN,
|
|
Content = GetLocalizedPowerStatus(powerStatus),
|
|
Width = 200,
|
|
Height = 30
|
|
};
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|