Files
Dave Friedel 0352c6b755 Add brightness/contrast sliders, input source switching, and 9-language localization
- 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>
2026-01-03 22:11:35 -05:00

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);
}
}
}