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(CMMTmpFolder, "ControlMyMonitor.exe"); static readonly string CMMsMonitors = Path.Combine(CMMTmpFolder, "smonitors.tmp"); public static async Task ScanMonitor() { await BytesToFileAsync(new(CMMexe)); await ConsoleHelper.CmdCommandAsync($"{CMMexe} /smonitors {CMMsMonitors}"); } public static Task PowerOn(string monitorSN) { return ConsoleHelper.CmdCommandAsync($"{CMMexe} /SetValue {monitorSN} D6 1"); } public static Task Sleep(string monitorSN) { return ConsoleHelper.CmdCommandAsync($"{CMMexe} /SetValue {monitorSN} D6 4"); } private static async Task GetMonitorValue(string monitorSN, string vcpCode = "D6", int? reTry = 0) { var value = string.Empty; while (reTry <= 5) { var cmdFileName = Path.Combine(CMMTmpFolder, $"{Guid.NewGuid()}.bat"); var cmd = $"{CMMexe} /GetValue {monitorSN} {vcpCode}\r\n" + $"echo %errorlevel%"; File.WriteAllText(cmdFileName, cmd); var values = await ConsoleHelper.ExecuteCommand(cmdFileName); File.Delete(cmdFileName); value = values.Split("\r\n", StringSplitOptions.RemoveEmptyEntries).LastOrDefault(); if (!string.IsNullOrEmpty(value) && value != "0") return value; await Task.Delay(500); await GetMonitorValue(monitorSN, vcpCode, reTry++); }; return value; } #region Brightness (VCP Code 10) public static Task SetBrightness(string monitorSN, int value) { return ConsoleHelper.CmdCommandAsync($"{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 Task SetContrast(string monitorSN, int value) { return ConsoleHelper.CmdCommandAsync($"{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 Task SetInputSource(string monitorSN, int value) { return ConsoleHelper.CmdCommandAsync($"{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.CmdCommandAsync($"{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; } private 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.CmdCommandAsync($"{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; } } private static readonly string DebugLog = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "MonitorParse.log"); /// /// 取得螢幕清單 /// public static async Task> ReadMonitorsData() { var monitors = new List(); if (!File.Exists(CMMsMonitors)) return monitors; // Try reading raw bytes to understand encoding var rawBytes = await File.ReadAllBytesAsync(CMMsMonitors); File.WriteAllText(DebugLog, $"File size: {rawBytes.Length} bytes\nFirst 20 bytes: {BitConverter.ToString(rawBytes.Take(20).ToArray())}\n\n"); // Try UTF-16 LE (common Windows Unicode) var content = System.Text.Encoding.Unicode.GetString(rawBytes); File.AppendAllText(DebugLog, $"Content preview:\n{content.Substring(0, Math.Min(500, content.Length))}\n\n"); XMonitor? mon = null; foreach (var line in content.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) { File.AppendAllText(DebugLog, $"LINE: [{line}]\n"); var colonIdx = line.IndexOf(':'); if (colonIdx < 0) continue; var key = line.Substring(0, colonIdx).Trim(); var val = line.Substring(colonIdx + 1).Trim().Trim('"'); File.AppendAllText(DebugLog, $" KEY=[{key}] VAL=[{val}]\n"); 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; File.AppendAllText(DebugLog, $" -> Adding monitor: Name=[{mon.MonitorName}] SN=[{mon.SerialNumber}]\n"); if (!string.IsNullOrEmpty(mon.SerialNumber)) monitors.Add(mon); mon = null; } } File.AppendAllText(DebugLog, $"\nTotal monitors with serial: {monitors.Count}\n"); return monitors; } static void BytesToFile(FileInfo fi) { fi.Refresh(); if (fi.Exists) return; if (!fi.Directory.Exists) fi.Directory.Create(); File.WriteAllBytes(fi.FullName, fi.Name.ResourceToByteArray()); } static async Task BytesToFileAsync(FileInfo fi) { fi.Refresh(); if (fi.Exists) return; if (!fi.Directory.Exists) fi.Directory.Create(); await File.WriteAllBytesAsync(fi.FullName, fi.Name.ResourceToByteArray()); } }