using CMM.Library.Base; using CMM.Library.Helpers; using CMM.Library.ViewModel; using System.IO; namespace CMM.Library.Method; /// /// Control My Monitor Management Command /// public static class CMMCommand { static readonly string CMMTmpFolder = Path.Combine(Path.GetTempPath(), "CMM"); static readonly string CMMexe = Path.Combine(AppContext.BaseDirectory, "ControlMyMonitor.exe"); static readonly string CMMsMonitors = Path.Combine(CMMTmpFolder, "smonitors.tmp"); public static async Task ScanMonitor() { // Ensure temp folder exists for output files Directory.CreateDirectory(CMMTmpFolder); await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/smonitors {CMMsMonitors}"); } public static async Task PowerOn(string monitorSN) { await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/SetValue {monitorSN} D6 1"); } public static async Task Sleep(string monitorSN) { await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/SetValue {monitorSN} D6 4"); } private static async Task GetMonitorValue(string monitorSN, string vcpCode = "D6", int maxRetries = 2) { for (int attempt = 0; attempt <= maxRetries; attempt++) { // Execute directly without batch file wrapper var (output, exitCode) = await ConsoleHelper.ExecuteExeAsync( CMMexe, $"/GetValue {monitorSN} {vcpCode}"); // Timeout if (exitCode == -1) return string.Empty; // ControlMyMonitor returns the value as the exit code // Exit code > 0 means success with that value if (exitCode > 0) return exitCode.ToString(); // Also check stdout in case it outputs there var value = output?.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).LastOrDefault()?.Trim(); if (!string.IsNullOrEmpty(value) && value != "0") return value; // Only retry on failure if (attempt < maxRetries) await Task.Delay(300); } return string.Empty; } #region Brightness (VCP Code 10) public static async Task SetBrightness(string monitorSN, int value) { await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/SetValue {monitorSN} 10 {value}"); } public static async Task GetBrightness(string monitorSN) { var value = await GetMonitorValue(monitorSN, "10"); return int.TryParse(value, out var result) ? result : null; } #endregion #region Contrast (VCP Code 12) public static async Task SetContrast(string monitorSN, int value) { await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/SetValue {monitorSN} 12 {value}"); } public static async Task GetContrast(string monitorSN) { var value = await GetMonitorValue(monitorSN, "12"); return int.TryParse(value, out var result) ? result : null; } #endregion #region Input Source (VCP Code 60) public static async Task SetInputSource(string monitorSN, int value) { await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/SetValue {monitorSN} 60 {value}"); } public static async Task GetInputSource(string monitorSN) { var value = await GetMonitorValue(monitorSN, "60"); return int.TryParse(value, out var result) ? result : null; } public static async Task> GetInputSourceOptions(string monitorSN) { var options = new List(); var savePath = Path.Combine(CMMTmpFolder, $"{monitorSN}_vcp.tmp"); await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/sjson {savePath} {monitorSN}"); if (!File.Exists(savePath)) return options; var monitorModel = JsonHelper.JsonFormFile>(savePath); var inputSourceVcp = monitorModel?.FirstOrDefault(m => m.VCPCode == "60"); if (inputSourceVcp?.PossibleValues != null) { var possibleValues = inputSourceVcp.PossibleValues .Split(",", StringSplitOptions.RemoveEmptyEntries) .Select(v => int.TryParse(v.Trim(), out var val) ? val : (int?)null) .Where(v => v.HasValue) .Select(v => v.Value); foreach (var value in possibleValues) { options.Add(new InputSourceOption(value, GetInputSourceName(value))); } } return options; } public static string GetInputSourceName(int vcpValue) { return vcpValue switch { 1 => "VGA-1", 2 => "VGA-2", 3 => "DVI-1", 4 => "DVI-2", 5 => "Composite-1", 6 => "Composite-2", 7 => "S-Video-1", 8 => "S-Video-2", 9 => "Tuner-1", 10 => "Tuner-2", 11 => "Tuner-3", 12 => "Component-1", 13 => "Component-2", 14 => "Component-3", 15 => "DisplayPort-1", 16 => "DisplayPort-2", 17 => "HDMI-1", 18 => "HDMI-2", _ => $"Input-{vcpValue}" }; } #endregion public static async Task GetMonPowerStatus(string monitorSN) { var status = await GetMonitorValue(monitorSN); return status switch { "1" => "PowerOn", "4" => "Sleep", "5" => "PowerOff", _ => string.Empty }; } public static async Task ScanMonitorStatus(IEnumerable monitors) { var taskList = monitors.Select(x => { return ScanMonitorStatus($"{CMMTmpFolder}\\{x.SerialNumber}.tmp", x); }); await Task.WhenAll(taskList); } static async Task ScanMonitorStatus(string savePath, XMonitor mon) { await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/sjson {savePath} {mon.MonitorID}"); var monitorModel = JsonHelper.JsonFormFile>(savePath); var status = monitorModel.ReadMonitorStatus(); mon.Status = new ObservableRangeCollection(status); } /// /// 取得螢幕狀態 /// public static IEnumerable ReadMonitorStatus(this IEnumerable monitorModel) { foreach (var m in monitorModel) { yield return new XMonitorStatus { VCP_Code = m.VCPCode, VCPCodeName = m.VCPCodeName, Read_Write = m.ReadWrite, CurrentValue = TryGetInt(m.CurrentValue), MaximumValue = TryGetInt(m.MaximumValue), PossibleValues = TryGetArrStr(m.PossibleValues), }; } IEnumerable TryGetArrStr(string str) { return str.Split(",", StringSplitOptions.RemoveEmptyEntries) .Select(x => TryGetInt(x)) .Where(x => x != null) .Select(x => (int)x) .ToList(); } int? TryGetInt(string str) { return int.TryParse(str, out var value) ? value : null; } } /// /// 取得螢幕清單 /// public static async Task> ReadMonitorsData() { var monitors = new List(); if (!File.Exists(CMMsMonitors)) return monitors; // Read with UTF-16 LE encoding (ControlMyMonitor outputs UTF-16) var rawBytes = await File.ReadAllBytesAsync(CMMsMonitors); var content = System.Text.Encoding.Unicode.GetString(rawBytes); XMonitor? mon = null; foreach (var line in content.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) { var colonIdx = line.IndexOf(':'); if (colonIdx < 0) continue; var key = line.Substring(0, colonIdx).Trim(); var val = line.Substring(colonIdx + 1).Trim().Trim('"'); if (key.Contains("Monitor Device Name")) { mon = new XMonitor { MonitorDeviceName = val }; } else if (mon != null && key.Contains("Monitor Name")) { mon.MonitorName = val; } else if (mon != null && key.Contains("Serial Number")) { mon.SerialNumber = val; } else if (mon != null && key.Contains("Adapter Name")) { mon.AdapterName = val; } else if (mon != null && key.Contains("Monitor ID")) { mon.MonitorID = val; if (!string.IsNullOrEmpty(mon.SerialNumber)) monitors.Add(mon); mon = null; } } return monitors; } }