From 14945c3e69fac6b334befad85733a47f759f58ed Mon Sep 17 00:00:00 2001 From: Quick <577652+markdwags@users.noreply.github.com> Date: Wed, 7 Jan 2026 18:09:28 -0600 Subject: [PATCH 01/10] implement VCP feature support for brightness, contrast, and input; add feature resolver and update command handling --- DDCSwitch/DDCSwitch.csproj | 4 +- DDCSwitch/FeatureResolver.cs | 171 ++++++++++++++++++++ DDCSwitch/JsonContext.cs | 8 +- DDCSwitch/Monitor.cs | 114 ++++++++++++++ DDCSwitch/Program.cs | 295 +++++++++++++++++++++++++++-------- DDCSwitch/VcpFeature.cs | 83 ++++++++++ 6 files changed, 606 insertions(+), 69 deletions(-) create mode 100644 DDCSwitch/FeatureResolver.cs create mode 100644 DDCSwitch/VcpFeature.cs diff --git a/DDCSwitch/DDCSwitch.csproj b/DDCSwitch/DDCSwitch.csproj index 04e5d93..a2ee008 100644 --- a/DDCSwitch/DDCSwitch.csproj +++ b/DDCSwitch/DDCSwitch.csproj @@ -19,7 +19,7 @@ $(MSBuildProjectDirectory)\..\CHANGELOG.md - + @(ChangelogLines, '%0A') @@ -33,7 +33,7 @@ $(Version) $(Version) - + diff --git a/DDCSwitch/FeatureResolver.cs b/DDCSwitch/FeatureResolver.cs new file mode 100644 index 0000000..ffaa897 --- /dev/null +++ b/DDCSwitch/FeatureResolver.cs @@ -0,0 +1,171 @@ +using System.Globalization; + +namespace DDCSwitch; + +/// +/// Resolves feature names to VCP codes and handles value conversions +/// +public static class FeatureResolver +{ + private static readonly Dictionary FeatureMap = new(StringComparer.OrdinalIgnoreCase) + { + { "brightness", VcpFeature.Brightness }, + { "contrast", VcpFeature.Contrast }, + { "input", VcpFeature.InputSource } + }; + + /// + /// Attempts to resolve a feature name or VCP code to a VcpFeature + /// + /// Feature name (brightness, contrast, input) or VCP code (0x10, 0x12, etc.) + /// The resolved VcpFeature if successful + /// True if the feature was resolved successfully + public static bool TryResolveFeature(string input, out VcpFeature? feature) + { + feature = null; + + if (string.IsNullOrWhiteSpace(input)) + { + return false; + } + + // First try to resolve as a known feature name + if (FeatureMap.TryGetValue(input.Trim(), out feature)) + { + return true; + } + + // Try to parse as a VCP code + if (TryParseVcpCode(input, out byte vcpCode)) + { + // Create a generic VCP feature for unknown codes + feature = new VcpFeature(vcpCode, $"VCP_{vcpCode:X2}", VcpFeatureType.ReadWrite, false); + return true; + } + + return false; + } + + /// + /// Converts a percentage value (0-100) to raw VCP value based on the maximum value + /// + /// Percentage value (0-100) + /// Maximum raw value supported by the monitor + /// Raw VCP value + public static uint ConvertPercentageToRaw(uint percentage, uint maxValue) + { + if (percentage > 100) + { + throw new ArgumentOutOfRangeException(nameof(percentage), "Percentage must be between 0 and 100"); + } + + if (maxValue == 0) + { + return 0; + } + + // Convert percentage to raw value with proper rounding + return (uint)Math.Round((double)percentage * maxValue / 100.0); + } + + /// + /// Converts a raw VCP value to percentage based on the maximum value + /// + /// Raw VCP value + /// Maximum raw value supported by the monitor + /// Percentage value (0-100) + public static uint ConvertRawToPercentage(uint rawValue, uint maxValue) + { + if (maxValue == 0) + { + return 0; + } + + if (rawValue > maxValue) + { + rawValue = maxValue; + } + + // Convert raw value to percentage with proper rounding + return (uint)Math.Round((double)rawValue * 100.0 / maxValue); + } + + /// + /// Attempts to parse a VCP code from a string (supports hex format like 0x10 or decimal) + /// + /// Input string containing VCP code + /// Parsed VCP code if successful + /// True if parsing was successful + public static bool TryParseVcpCode(string input, out byte vcpCode) + { + vcpCode = 0; + + if (string.IsNullOrWhiteSpace(input)) + { + return false; + } + + input = input.Trim(); + + // Try hex format (0x10, 0X10) + if (input.StartsWith("0x", StringComparison.OrdinalIgnoreCase) || + input.StartsWith("0X", StringComparison.OrdinalIgnoreCase)) + { + var hexPart = input.Substring(2); + return byte.TryParse(hexPart, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out vcpCode); + } + + // Try decimal format + return byte.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out vcpCode); + } + + /// + /// Attempts to parse a percentage value from a string (supports % suffix) + /// + /// Input string containing percentage value + /// Parsed percentage value if successful + /// True if parsing was successful and value is in valid range (0-100) + public static bool TryParsePercentage(string input, out uint percentage) + { + percentage = 0; + + if (string.IsNullOrWhiteSpace(input)) + { + return false; + } + + input = input.Trim(); + + // Remove % suffix if present + if (input.EndsWith("%")) + { + input = input.Substring(0, input.Length - 1).Trim(); + } + + if (!uint.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out percentage)) + { + return false; + } + + // Validate range + return percentage <= 100; + } + + /// + /// Gets all known feature names + /// + /// Collection of known feature names + public static IEnumerable GetKnownFeatureNames() + { + return FeatureMap.Keys; + } + + /// + /// Gets all predefined VCP features + /// + /// Collection of predefined VCP features + public static IEnumerable GetPredefinedFeatures() + { + return FeatureMap.Values; + } +} \ No newline at end of file diff --git a/DDCSwitch/JsonContext.cs b/DDCSwitch/JsonContext.cs index f33dcde..18db944 100644 --- a/DDCSwitch/JsonContext.cs +++ b/DDCSwitch/JsonContext.cs @@ -5,8 +5,8 @@ namespace DDCSwitch; [JsonSerializable(typeof(ErrorResponse))] [JsonSerializable(typeof(ListMonitorsResponse))] [JsonSerializable(typeof(MonitorInfo))] -[JsonSerializable(typeof(GetInputResponse))] -[JsonSerializable(typeof(SetInputResponse))] +[JsonSerializable(typeof(GetVcpResponse))] +[JsonSerializable(typeof(SetVcpResponse))] [JsonSerializable(typeof(MonitorReference))] [JsonSourceGenerationOptions( WriteIndented = true, @@ -20,8 +20,8 @@ internal partial class JsonContext : JsonSerializerContext // Response models internal record ErrorResponse(bool Success, string Error, MonitorReference? Monitor = null); internal record ListMonitorsResponse(bool Success, List? Monitors = null, string? Error = null); -internal record GetInputResponse(bool Success, MonitorReference Monitor, string CurrentInput, string CurrentInputCode, uint MaxValue); -internal record SetInputResponse(bool Success, MonitorReference Monitor, string NewInput, string NewInputCode); +internal record GetVcpResponse(bool Success, MonitorReference Monitor, string FeatureName, uint RawValue, uint MaxValue, uint? PercentageValue = null, string? ErrorMessage = null); +internal record SetVcpResponse(bool Success, MonitorReference Monitor, string FeatureName, uint SetValue, uint? PercentageValue = null, string? ErrorMessage = null); // Data models internal record MonitorInfo( diff --git a/DDCSwitch/Monitor.cs b/DDCSwitch/Monitor.cs index ffcba81..1e9843d 100644 --- a/DDCSwitch/Monitor.cs +++ b/DDCSwitch/Monitor.cs @@ -1,3 +1,7 @@ +using System; +using System.Collections.Generic; +using System.Linq; + namespace DDCSwitch; /// @@ -42,6 +46,116 @@ public bool TrySetInputSource(uint value) return NativeMethods.SetVCPFeature(Handle, InputSource.VcpInputSource, value); } + /// + /// Attempts to read a VCP feature value from the monitor + /// + /// VCP code to read (0x00-0xFF) + /// Current value of the VCP feature + /// Maximum value supported by the VCP feature + /// True if the operation was successful + public bool TryGetVcpFeature(byte vcpCode, out uint currentValue, out uint maxValue) + { + currentValue = 0; + maxValue = 0; + + if (_disposed || Handle == IntPtr.Zero) + { + return false; + } + + return NativeMethods.GetVCPFeatureAndVCPFeatureReply( + Handle, + vcpCode, + out _, + out currentValue, + out maxValue); + } + + /// + /// Attempts to write a VCP feature value to the monitor + /// + /// VCP code to write (0x00-0xFF) + /// Value to set for the VCP feature + /// True if the operation was successful + public bool TrySetVcpFeature(byte vcpCode, uint value) + { + if (_disposed || Handle == IntPtr.Zero) + { + return false; + } + + return NativeMethods.SetVCPFeature(Handle, vcpCode, value); + } + + /// + /// Scans all VCP codes (0x00-0xFF) to discover supported features + /// + /// Dictionary mapping VCP codes to their feature information + public Dictionary ScanVcpFeatures() + { + var features = new Dictionary(); + + if (_disposed || Handle == IntPtr.Zero) + { + return features; + } + + // Get predefined features for name lookup + var predefinedFeatures = FeatureResolver.GetPredefinedFeatures() + .ToDictionary(f => f.Code, f => f); + + // Scan all possible VCP codes (0x00 to 0xFF) + for (int code = 0; code <= 255; code++) + { + byte vcpCode = (byte)code; + + if (TryGetVcpFeature(vcpCode, out uint currentValue, out uint maxValue)) + { + // Feature is supported - determine name and type + string name; + VcpFeatureType type; + + if (predefinedFeatures.TryGetValue(vcpCode, out VcpFeature? predefined)) + { + name = predefined.Name; + type = predefined.Type; + } + else + { + name = $"VCP_{vcpCode:X2}"; + type = VcpFeatureType.ReadWrite; // Assume read-write for unknown codes + } + + features[vcpCode] = new VcpFeatureInfo( + vcpCode, + name, + type, + currentValue, + maxValue, + true + ); + } + else + { + // Feature is not supported - still add entry for completeness + string name = predefinedFeatures.TryGetValue(vcpCode, out VcpFeature? predefined) + ? predefined.Name + : $"VCP_{vcpCode:X2}"; + + features[vcpCode] = new VcpFeatureInfo( + vcpCode, + name, + VcpFeatureType.ReadWrite, + 0, + 0, + false + ); + } + } + + return features; + } + public void Dispose() { if (!_disposed && Handle != IntPtr.Zero) diff --git a/DDCSwitch/Program.cs b/DDCSwitch/Program.cs index 925e737..d3283fd 100644 --- a/DDCSwitch/Program.cs +++ b/DDCSwitch/Program.cs @@ -95,38 +95,14 @@ private static int ShowUsage() AnsiConsole.Write(new FigletText("DDCSwitch").Color(Color.Blue)); AnsiConsole.MarkupLine($"[dim]Windows DDC/CI Monitor Input Switcher v{version}[/]\n"); - var table = new Table() - .Border(TableBorder.Rounded) - .AddColumn("Command") - .AddColumn("Description") - .AddColumn("Example"); - - table.AddRow( - "[yellow]list[/] or [yellow]ls[/]", - "List all DDC/CI capable monitors", - "[dim]DDCSwitch list[/]"); - - table.AddRow( - "[yellow]get[/] ", - "Get current input source for a monitor", - "[dim]DDCSwitch get 0[/]"); - - table.AddRow( - "[yellow]set[/] ", - "Set input source for a monitor", - "[dim]DDCSwitch set 0 HDMI1[/]"); - - table.AddRow( - "[yellow]version[/] or [yellow]-v[/]", - "Display version information", - "[dim]DDCSwitch version[/]"); - - AnsiConsole.Write(table); - - AnsiConsole.MarkupLine("\n[bold]Monitor:[/] Monitor index (0, 1, 2...) or name pattern"); - AnsiConsole.MarkupLine("[bold]Input:[/] Input name (HDMI1, HDMI2, DP1, DP2, DVI1, VGA1) or hex code (0x11)"); - AnsiConsole.MarkupLine("\n[bold]Options:[/]"); - AnsiConsole.MarkupLine(" [yellow]--json[/] Output in JSON format"); + AnsiConsole.MarkupLine("[yellow]Commands:[/]"); + AnsiConsole.MarkupLine(" list - List all DDC/CI capable monitors"); + AnsiConsole.MarkupLine(" get monitor feature - Get current value for a monitor feature"); + AnsiConsole.MarkupLine(" set monitor feature value - Set value for a monitor feature"); + AnsiConsole.MarkupLine(" version - Display version information"); + + AnsiConsole.MarkupLine("\nSupported features: brightness, contrast, input, or VCP codes like 0x10"); + AnsiConsole.MarkupLine("Use --json flag for JSON output"); return 0; } @@ -267,17 +243,35 @@ private static int ListMonitors(bool jsonOutput) private static int GetCurrentInput(string[] args, bool jsonOutput) { - if (args.Length < 2) + if (args.Length < 3) { if (jsonOutput) { - var error = new ErrorResponse(false, "Monitor identifier required"); + var error = new ErrorResponse(false, "Monitor identifier and feature required"); Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); } else { - AnsiConsole.MarkupLine("[red]Error:[/] Monitor identifier required."); - AnsiConsole.MarkupLine("Usage: [yellow]DDCSwitch get [/]"); + AnsiConsole.MarkupLine("[red]Error:[/] Monitor identifier and feature required."); + AnsiConsole.MarkupLine("Usage: [yellow]DDCSwitch get [/]"); + } + + return 1; + } + + string featureInput = args[2]; + + if (!FeatureResolver.TryResolveFeature(featureInput, out VcpFeature? feature)) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, $"Invalid feature '{featureInput}'"); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + AnsiConsole.MarkupLine($"[red]Error:[/] Invalid feature '{featureInput}'."); + AnsiConsole.MarkupLine("Valid features: brightness, contrast, input, or VCP code (0x10, 0x12, etc.)"); } return 1; @@ -324,20 +318,23 @@ private static int GetCurrentInput(string[] args, bool jsonOutput) return 1; } - if (!monitor.TryGetInputSource(out uint current, out uint max)) + // Use generic VCP method for all features + bool success = monitor.TryGetVcpFeature(feature!.Code, out uint current, out uint max); + + if (!success) { if (jsonOutput) { var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - var error = new ErrorResponse(false, $"Failed to get input source from monitor '{monitor.Name}'", + var error = new ErrorResponse(false, $"Failed to get {feature.Name} from monitor '{monitor.Name}'", monitorRef); Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); } else { - AnsiConsole.MarkupLine($"[red]Error:[/] Failed to get input source from monitor '{monitor.Name}'."); - AnsiConsole.MarkupLine("The monitor may not support DDC/CI or requires administrator privileges."); + AnsiConsole.MarkupLine($"[red]Error:[/] Failed to get {feature.Name} from monitor '{monitor.Name}'."); + AnsiConsole.MarkupLine("The monitor may not support this feature or requires administrator privileges."); } // Cleanup @@ -352,13 +349,42 @@ private static int GetCurrentInput(string[] args, bool jsonOutput) if (jsonOutput) { var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - var result = new GetInputResponse(true, monitorRef, InputSource.GetName(current), $"0x{current:X2}", max); - Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.GetInputResponse)); + + if (feature.Code == InputSource.VcpInputSource) + { + // Use generic VCP response for input as well + uint? percentageValue = feature.SupportsPercentage ? FeatureResolver.ConvertRawToPercentage(current, max) : null; + var result = new GetVcpResponse(true, monitorRef, feature.Name, current, max, percentageValue); + Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.GetVcpResponse)); + } + else + { + // Use generic VCP response for other features + uint? percentageValue = feature.SupportsPercentage ? FeatureResolver.ConvertRawToPercentage(current, max) : null; + var result = new GetVcpResponse(true, monitorRef, feature.Name, current, max, percentageValue); + Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.GetVcpResponse)); + } } else { AnsiConsole.MarkupLine($"[green]Monitor:[/] {monitor.Name} ({monitor.DeviceName})"); - AnsiConsole.MarkupLine($"[green]Current Input:[/] {InputSource.GetName(current)} (0x{current:X2})"); + + if (feature.Code == InputSource.VcpInputSource) + { + // Display input with name resolution + AnsiConsole.MarkupLine($"[green]Current {feature.Name}:[/] {InputSource.GetName(current)} (0x{current:X2})"); + } + else if (feature.SupportsPercentage) + { + // Display percentage for brightness/contrast + uint percentage = FeatureResolver.ConvertRawToPercentage(current, max); + AnsiConsole.MarkupLine($"[green]Current {feature.Name}:[/] {percentage}% (raw: {current}/{max})"); + } + else + { + // Display raw values for unknown VCP codes + AnsiConsole.MarkupLine($"[green]Current {feature.Name}:[/] {current} (max: {max})"); + } } // Cleanup @@ -372,34 +398,96 @@ private static int GetCurrentInput(string[] args, bool jsonOutput) private static int SetInput(string[] args, bool jsonOutput) { - if (args.Length < 3) + // Require 4 arguments: set + if (args.Length < 4) { if (jsonOutput) { - var error = new ErrorResponse(false, "Monitor and input source required"); + var error = new ErrorResponse(false, "Monitor, feature, and value required"); Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); } else { - AnsiConsole.MarkupLine("[red]Error:[/] Monitor and input source required."); - AnsiConsole.MarkupLine("Usage: [yellow]DDCSwitch set [/]"); + AnsiConsole.MarkupLine("[red]Error:[/] Monitor, feature, and value required."); + AnsiConsole.MarkupLine("Usage: [yellow]DDCSwitch set [/]"); } return 1; } - if (!InputSource.TryParse(args[2], out uint inputValue)) + string featureInput = args[2]; + string valueInput = args[3]; + + if (!FeatureResolver.TryResolveFeature(featureInput, out VcpFeature? feature)) { if (jsonOutput) { - var error = new ErrorResponse(false, $"Invalid input source '{args[2]}'"); + var error = new ErrorResponse(false, $"Invalid feature '{featureInput}'"); Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); } else { - AnsiConsole.MarkupLine($"[red]Error:[/] Invalid input source '{args[2]}'."); - AnsiConsole.MarkupLine( - "Valid inputs: HDMI1, HDMI2, DP1, DP2, DVI1, DVI2, VGA1, VGA2, or hex code (0x11)"); + AnsiConsole.MarkupLine($"[red]Error:[/] Invalid feature '{featureInput}'."); + AnsiConsole.MarkupLine("Valid features: brightness, contrast, input, or VCP code (0x10, 0x12, etc.)"); + } + + return 1; + } + + // Parse the value based on feature type + uint setValue; + uint? percentageValue = null; + + if (feature!.Code == InputSource.VcpInputSource) + { + // Use existing input source parsing for input feature + if (!InputSource.TryParse(valueInput, out setValue)) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, $"Invalid input source '{valueInput}'"); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + AnsiConsole.MarkupLine($"[red]Error:[/] Invalid input source '{valueInput}'."); + AnsiConsole.MarkupLine( + "Valid inputs: HDMI1, HDMI2, DP1, DP2, DVI1, DVI2, VGA1, VGA2, or hex code (0x11)"); + } + + return 1; + } + } + else if (feature.SupportsPercentage && FeatureResolver.TryParsePercentage(valueInput, out uint percentage)) + { + // Parse as percentage for brightness/contrast + percentageValue = percentage; + // We'll convert to raw value after getting monitor's max value + setValue = 0; // Placeholder + } + else if (uint.TryParse(valueInput, out uint rawValue)) + { + // Parse as raw value - we'll validate range after getting monitor's max value + setValue = rawValue; + } + else + { + if (jsonOutput) + { + var error = new ErrorResponse(false, $"Invalid value '{valueInput}' for feature '{feature.Name}'"); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + AnsiConsole.MarkupLine($"[red]Error:[/] Invalid value '{valueInput}' for feature '{feature.Name}'."); + if (feature.SupportsPercentage) + { + AnsiConsole.MarkupLine("Valid values: 0-100% or raw numeric value"); + } + else + { + AnsiConsole.MarkupLine("Valid values: numeric value within monitor's supported range"); + } } return 1; @@ -446,34 +534,100 @@ private static int SetInput(string[] args, bool jsonOutput) return 1; } + // If we have a percentage value, we need to get the max value first to convert it + // For raw values, we also need to validate they're within the monitor's supported range + if (percentageValue.HasValue || (!feature!.SupportsPercentage && feature.Code != InputSource.VcpInputSource)) + { + if (monitor.TryGetVcpFeature(feature.Code, out uint currentValue, out uint maxValue)) + { + if (percentageValue.HasValue) + { + // Convert percentage to raw value + setValue = FeatureResolver.ConvertPercentageToRaw(percentageValue.Value, maxValue); + } + else if (feature.Code != InputSource.VcpInputSource) + { + // Validate raw value is within supported range + if (setValue > maxValue) + { + if (jsonOutput) + { + var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); + var error = new ErrorResponse(false, $"Value {setValue} is out of range for {feature.Name}. Valid range: 0-{maxValue}", monitorRef); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + AnsiConsole.MarkupLine($"[red]Error:[/] Value {setValue} is out of range for {feature.Name}."); + AnsiConsole.MarkupLine($"Valid range: 0-{maxValue}"); + } + + // Cleanup + foreach (var m in monitors) + { + m.Dispose(); + } + + return 1; + } + } + } + else + { + if (jsonOutput) + { + var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); + var error = new ErrorResponse(false, $"Failed to read current {feature.Name} from monitor '{monitor.Name}' to validate range", monitorRef); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + AnsiConsole.MarkupLine($"[red]Error:[/] Failed to read current {feature.Name} from monitor '{monitor.Name}' to validate range."); + AnsiConsole.MarkupLine("The monitor may not support this feature or requires administrator privileges."); + } + + // Cleanup + foreach (var m in monitors) + { + m.Dispose(); + } + + return 1; + } + } + bool success = false; string? errorMsg = null; if (!jsonOutput) { + string displayValue = percentageValue.HasValue ? $"{percentageValue}%" : setValue.ToString(); AnsiConsole.Status() - .Start($"Switching {monitor.Name} to {InputSource.GetName(inputValue)}...", ctx => + .Start($"Setting {monitor.Name} {feature.Name} to {displayValue}...", ctx => { ctx.Spinner(Spinner.Known.Dots); - if (!monitor.TrySetInputSource(inputValue)) + if (!monitor.TrySetVcpFeature(feature!.Code, setValue)) { - errorMsg = - $"Failed to set input source on monitor '{monitor.Name}'. The monitor may not support this input or requires administrator privileges."; + errorMsg = $"Failed to set {feature.Name} on monitor '{monitor.Name}'. The monitor may not support this feature or requires administrator privileges."; } else { success = true; - // Give the monitor a moment to switch + } + + if (success) + { + // Give the monitor a moment to apply the change Thread.Sleep(500); } }); } else { - if (!monitor.TrySetInputSource(inputValue)) + if (!monitor.TrySetVcpFeature(feature!.Code, setValue)) { - errorMsg = $"Failed to set input source on monitor '{monitor.Name}'"; + errorMsg = $"Failed to set {feature.Name} on monitor '{monitor.Name}'"; } else { @@ -508,13 +662,28 @@ private static int SetInput(string[] args, bool jsonOutput) if (jsonOutput) { var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - var result = new SetInputResponse(true, monitorRef, InputSource.GetName(inputValue), $"0x{inputValue:X2}"); - Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.SetInputResponse)); + + // Use generic VCP response for all features + var result = new SetVcpResponse(true, monitorRef, feature!.Name, setValue, percentageValue); + Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.SetVcpResponse)); } else { - AnsiConsole.MarkupLine( - $"[green]✓[/] Successfully switched {monitor.Name} to {InputSource.GetName(inputValue)}"); + if (feature!.Code == InputSource.VcpInputSource) + { + // Display input with name resolution + AnsiConsole.MarkupLine($"[green]✓[/] Successfully set {monitor.Name} {feature.Name} to {InputSource.GetName(setValue)}"); + } + else if (percentageValue.HasValue) + { + // Display percentage for brightness/contrast + AnsiConsole.MarkupLine($"[green]✓[/] Successfully set {monitor.Name} {feature.Name} to {percentageValue}%"); + } + else + { + // Display raw value for unknown VCP codes + AnsiConsole.MarkupLine($"[green]✓[/] Successfully set {monitor.Name} {feature.Name} to {setValue}"); + } } // Cleanup diff --git a/DDCSwitch/VcpFeature.cs b/DDCSwitch/VcpFeature.cs new file mode 100644 index 0000000..3a4cd4a --- /dev/null +++ b/DDCSwitch/VcpFeature.cs @@ -0,0 +1,83 @@ +namespace DDCSwitch; + +/// +/// Defines the access type for a VCP feature +/// +public enum VcpFeatureType +{ + /// + /// Feature can only be read + /// + ReadOnly, + + /// + /// Feature can only be written + /// + WriteOnly, + + /// + /// Feature can be both read and written + /// + ReadWrite +} + +/// +/// Represents a VCP (Virtual Control Panel) feature with its properties +/// +public class VcpFeature +{ + public byte Code { get; } + public string Name { get; } + public VcpFeatureType Type { get; } + public bool SupportsPercentage { get; } + + public VcpFeature(byte code, string name, VcpFeatureType type, bool supportsPercentage) + { + Code = code; + Name = name; + Type = type; + SupportsPercentage = supportsPercentage; + } + + /// + /// Brightness control (VCP 0x10) + /// + public static VcpFeature Brightness => new(0x10, "brightness", VcpFeatureType.ReadWrite, true); + + /// + /// Contrast control (VCP 0x12) + /// + public static VcpFeature Contrast => new(0x12, "contrast", VcpFeatureType.ReadWrite, true); + + /// + /// Input source selection (VCP 0x60) + /// + public static VcpFeature InputSource => new(0x60, "input", VcpFeatureType.ReadWrite, false); + + public override string ToString() + { + return $"{Name} (0x{Code:X2})"; + } + + public override bool Equals(object? obj) + { + return obj is VcpFeature other && Code == other.Code; + } + + public override int GetHashCode() + { + return Code.GetHashCode(); + } +} + +/// +/// Information about a VCP feature discovered during scanning +/// +public record VcpFeatureInfo( + byte Code, + string Name, + VcpFeatureType Type, + uint CurrentValue, + uint MaxValue, + bool IsSupported +); \ No newline at end of file From f50e7babf424d625e55350ff23d794409b0ea430 Mon Sep 17 00:00:00 2001 From: Quick <577652+markdwags@users.noreply.github.com> Date: Wed, 7 Jan 2026 19:39:05 -0600 Subject: [PATCH 02/10] enhance VCP feature support with error handling; add brightness and contrast controls, VCP scanning, and update README --- DDCSwitch/FeatureResolver.cs | 127 +++- DDCSwitch/JsonContext.cs | 8 +- DDCSwitch/Monitor.cs | 57 +- DDCSwitch/Program.cs | 610 ++++++++++++++++--- DDCSwitch/VcpErrorHandler.cs | 259 ++++++++ EXAMPLES.md | 1095 ++++++++++++++++++++++++++++------ README.md | 123 +++- 7 files changed, 2012 insertions(+), 267 deletions(-) create mode 100644 DDCSwitch/VcpErrorHandler.cs diff --git a/DDCSwitch/FeatureResolver.cs b/DDCSwitch/FeatureResolver.cs index ffaa897..dd5aab2 100644 --- a/DDCSwitch/FeatureResolver.cs +++ b/DDCSwitch/FeatureResolver.cs @@ -95,7 +95,7 @@ public static uint ConvertRawToPercentage(uint rawValue, uint maxValue) /// /// Input string containing VCP code /// Parsed VCP code if successful - /// True if parsing was successful + /// True if parsing was successful and VCP code is in valid range (0x00-0xFF) public static bool TryParseVcpCode(string input, out byte vcpCode) { vcpCode = 0; @@ -112,11 +112,26 @@ public static bool TryParseVcpCode(string input, out byte vcpCode) input.StartsWith("0X", StringComparison.OrdinalIgnoreCase)) { var hexPart = input.Substring(2); - return byte.TryParse(hexPart, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out vcpCode); + if (byte.TryParse(hexPart, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out vcpCode)) + { + // VCP codes are inherently valid for byte range (0x00-0xFF) + return true; + } + return false; + } + + // Try decimal format - validate range for decimal input + if (uint.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out uint decimalValue)) + { + // Validate VCP code is in valid range (0x00-0xFF) + if (decimalValue <= 255) + { + vcpCode = (byte)decimalValue; + return true; + } } - // Try decimal format - return byte.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out vcpCode); + return false; } /// @@ -147,10 +162,112 @@ public static bool TryParsePercentage(string input, out uint percentage) return false; } - // Validate range + // Validate range (0-100%) + return percentage <= 100; + } + + /// + /// Validates that a raw VCP value is within the monitor's supported range + /// + /// Raw VCP value to validate + /// Maximum value supported by the monitor for this VCP code + /// True if the value is within the valid range (0 to maxValue) + public static bool IsValidRawVcpValue(uint value, uint maxValue) + { + return value <= maxValue; + } + + /// + /// Validates that a percentage value is in the valid range + /// + /// Percentage value to validate + /// True if the percentage is between 0 and 100 inclusive + public static bool IsValidPercentage(uint percentage) + { return percentage <= 100; } + /// + /// Validates that a VCP code is in the valid range + /// + /// VCP code to validate + /// True if the VCP code is in the valid range (0x00-0xFF) + public static bool IsValidVcpCode(byte vcpCode) + { + // All byte values are valid VCP codes (0x00-0xFF) + return true; + } + + /// + /// Gets a descriptive error message for invalid percentage values + /// + /// The invalid input that was provided + /// Error message describing the validation failure + public static string GetPercentageValidationError(string input) + { + if (string.IsNullOrWhiteSpace(input)) + { + return "Percentage value cannot be empty"; + } + + var cleanInput = input.Trim(); + if (cleanInput.EndsWith("%")) + { + cleanInput = cleanInput.Substring(0, cleanInput.Length - 1).Trim(); + } + + if (!uint.TryParse(cleanInput, out uint value)) + { + return $"'{input}' is not a valid percentage value. Expected format: 0-100 or 0%-100%"; + } + + if (value > 100) + { + return $"Percentage value {value}% is out of range. Valid range: 0-100%"; + } + + return $"'{input}' is not a valid percentage value"; + } + + /// + /// Gets a descriptive error message for invalid VCP codes + /// + /// The invalid input that was provided + /// Error message describing the validation failure + public static string GetVcpCodeValidationError(string input) + { + if (string.IsNullOrWhiteSpace(input)) + { + return "VCP code cannot be empty"; + } + + var cleanInput = input.Trim(); + + if (cleanInput.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + return $"'{input}' is not a valid VCP code. Expected hex format: 0x00-0xFF"; + } + + if (uint.TryParse(cleanInput, out uint value) && value > 255) + { + return $"VCP code {value} is out of range. Valid range: 0-255 (0x00-0xFF)"; + } + + return $"'{input}' is not a valid VCP code. Expected format: 0-255 or 0x00-0xFF"; + } + + /// + /// Gets a descriptive error message for invalid raw VCP values + /// + /// The invalid value that was provided + /// The maximum value supported by the monitor + /// The name of the VCP feature + /// Error message describing the validation failure + public static string GetRawValueValidationError(uint value, uint maxValue, string featureName) + { + return $"Value {value} is out of range for {featureName}. Valid range: 0-{maxValue}"; + } + /// /// Gets all known feature names /// diff --git a/DDCSwitch/JsonContext.cs b/DDCSwitch/JsonContext.cs index 18db944..059cdf0 100644 --- a/DDCSwitch/JsonContext.cs +++ b/DDCSwitch/JsonContext.cs @@ -7,6 +7,9 @@ namespace DDCSwitch; [JsonSerializable(typeof(MonitorInfo))] [JsonSerializable(typeof(GetVcpResponse))] [JsonSerializable(typeof(SetVcpResponse))] +[JsonSerializable(typeof(VcpScanResponse))] +[JsonSerializable(typeof(VcpFeatureInfo))] +[JsonSerializable(typeof(VcpFeatureType))] [JsonSerializable(typeof(MonitorReference))] [JsonSourceGenerationOptions( WriteIndented = true, @@ -22,6 +25,7 @@ internal record ErrorResponse(bool Success, string Error, MonitorReference? Moni internal record ListMonitorsResponse(bool Success, List? Monitors = null, string? Error = null); internal record GetVcpResponse(bool Success, MonitorReference Monitor, string FeatureName, uint RawValue, uint MaxValue, uint? PercentageValue = null, string? ErrorMessage = null); internal record SetVcpResponse(bool Success, MonitorReference Monitor, string FeatureName, uint SetValue, uint? PercentageValue = null, string? ErrorMessage = null); +internal record VcpScanResponse(bool Success, MonitorReference Monitor, List Features, string? ErrorMessage = null); // Data models internal record MonitorInfo( @@ -31,7 +35,9 @@ internal record MonitorInfo( bool IsPrimary, string? CurrentInput, string? CurrentInputCode, - string Status); + string Status, + string? Brightness = null, + string? Contrast = null); internal record MonitorReference(int Index, string Name, string DeviceName, bool IsPrimary = false); diff --git a/DDCSwitch/Monitor.cs b/DDCSwitch/Monitor.cs index 1e9843d..b09cd19 100644 --- a/DDCSwitch/Monitor.cs +++ b/DDCSwitch/Monitor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; namespace DDCSwitch; @@ -47,44 +48,88 @@ public bool TrySetInputSource(uint value) } /// - /// Attempts to read a VCP feature value from the monitor + /// Attempts to read a VCP feature value from the monitor with enhanced error detection /// /// VCP code to read (0x00-0xFF) /// Current value of the VCP feature /// Maximum value supported by the VCP feature + /// Win32 error code if operation fails /// True if the operation was successful - public bool TryGetVcpFeature(byte vcpCode, out uint currentValue, out uint maxValue) + public bool TryGetVcpFeature(byte vcpCode, out uint currentValue, out uint maxValue, out int errorCode) { currentValue = 0; maxValue = 0; + errorCode = 0; if (_disposed || Handle == IntPtr.Zero) { + errorCode = 0x00000006; // ERROR_INVALID_HANDLE return false; } - return NativeMethods.GetVCPFeatureAndVCPFeatureReply( + bool success = NativeMethods.GetVCPFeatureAndVCPFeatureReply( Handle, vcpCode, out _, out currentValue, out maxValue); + + if (!success) + { + errorCode = Marshal.GetLastWin32Error(); + } + + return success; + } + + /// + /// Attempts to read a VCP feature value from the monitor (legacy method for backward compatibility) + /// + /// VCP code to read (0x00-0xFF) + /// Current value of the VCP feature + /// Maximum value supported by the VCP feature + /// True if the operation was successful + public bool TryGetVcpFeature(byte vcpCode, out uint currentValue, out uint maxValue) + { + return TryGetVcpFeature(vcpCode, out currentValue, out maxValue, out _); } /// - /// Attempts to write a VCP feature value to the monitor + /// Attempts to write a VCP feature value to the monitor with enhanced error detection /// /// VCP code to write (0x00-0xFF) /// Value to set for the VCP feature + /// Win32 error code if operation fails /// True if the operation was successful - public bool TrySetVcpFeature(byte vcpCode, uint value) + public bool TrySetVcpFeature(byte vcpCode, uint value, out int errorCode) { + errorCode = 0; + if (_disposed || Handle == IntPtr.Zero) { + errorCode = 0x00000006; // ERROR_INVALID_HANDLE return false; } - return NativeMethods.SetVCPFeature(Handle, vcpCode, value); + bool success = NativeMethods.SetVCPFeature(Handle, vcpCode, value); + + if (!success) + { + errorCode = Marshal.GetLastWin32Error(); + } + + return success; + } + + /// + /// Attempts to write a VCP feature value to the monitor (legacy method for backward compatibility) + /// + /// VCP code to write (0x00-0xFF) + /// Value to set for the VCP feature + /// True if the operation was successful + public bool TrySetVcpFeature(byte vcpCode, uint value) + { + return TrySetVcpFeature(vcpCode, value, out _); } /// diff --git a/DDCSwitch/Program.cs b/DDCSwitch/Program.cs index d3283fd..5b2cf07 100644 --- a/DDCSwitch/Program.cs +++ b/DDCSwitch/Program.cs @@ -27,6 +27,14 @@ public static int Run(string[] args) bool jsonOutput = args.Contains("--json", StringComparer.OrdinalIgnoreCase); var filteredArgs = args.Where(a => !a.Equals("--json", StringComparison.OrdinalIgnoreCase)).ToArray(); + // Check for --verbose flag + bool verboseOutput = filteredArgs.Contains("--verbose", StringComparer.OrdinalIgnoreCase); + filteredArgs = filteredArgs.Where(a => !a.Equals("--verbose", StringComparison.OrdinalIgnoreCase)).ToArray(); + + // Check for --scan flag + bool scanOutput = filteredArgs.Contains("--scan", StringComparer.OrdinalIgnoreCase); + filteredArgs = filteredArgs.Where(a => !a.Equals("--scan", StringComparison.OrdinalIgnoreCase)).ToArray(); + if (filteredArgs.Length == 0) { ShowUsage(); @@ -39,7 +47,7 @@ public static int Run(string[] args) { return command switch { - "list" or "ls" => ListMonitors(jsonOutput), + "list" or "ls" => ListMonitors(jsonOutput, verboseOutput, scanOutput), "get" => GetCurrentInput(filteredArgs, jsonOutput), "set" => SetInput(filteredArgs, jsonOutput), "version" or "-v" or "--version" => ShowVersion(jsonOutput), @@ -96,13 +104,15 @@ private static int ShowUsage() AnsiConsole.MarkupLine($"[dim]Windows DDC/CI Monitor Input Switcher v{version}[/]\n"); AnsiConsole.MarkupLine("[yellow]Commands:[/]"); - AnsiConsole.MarkupLine(" list - List all DDC/CI capable monitors"); - AnsiConsole.MarkupLine(" get monitor feature - Get current value for a monitor feature"); + AnsiConsole.WriteLine(" list [--verbose] [--scan] - List all DDC/CI capable monitors"); + AnsiConsole.WriteLine(" get monitor [feature] - Get current value for a monitor feature or scan all features"); AnsiConsole.MarkupLine(" set monitor feature value - Set value for a monitor feature"); AnsiConsole.MarkupLine(" version - Display version information"); AnsiConsole.MarkupLine("\nSupported features: brightness, contrast, input, or VCP codes like 0x10"); - AnsiConsole.MarkupLine("Use --json flag for JSON output"); + AnsiConsole.MarkupLine("Use [yellow]--json[/] flag for JSON output"); + AnsiConsole.MarkupLine("Use [yellow]--verbose[/] flag with list to include brightness and contrast"); + AnsiConsole.MarkupLine("Use [yellow]--scan[/] flag with list to enumerate all VCP codes"); return 0; } @@ -123,15 +133,15 @@ private static int InvalidCommand(string command, bool jsonOutput) return 1; } - private static int ListMonitors(bool jsonOutput) + private static int ListMonitors(bool jsonOutput, bool verboseOutput = false, bool scanOutput = false) { if (!jsonOutput) { AnsiConsole.Status() - .Start("Enumerating monitors...", ctx => + .Start(scanOutput ? "Scanning VCP features..." : "Enumerating monitors...", ctx => { ctx.Spinner(Spinner.Known.Dots); - Thread.Sleep(100); // Brief pause for visual feedback + Thread.Sleep(scanOutput ? 500 : 100); // Longer pause for VCP scanning }); } @@ -152,6 +162,12 @@ private static int ListMonitors(bool jsonOutput) return 1; } + // If scan mode is enabled, perform VCP scanning for each monitor + if (scanOutput) + { + return HandleVcpScan(monitors, jsonOutput); + } + if (jsonOutput) { var monitorList = monitors.Select(monitor => @@ -159,6 +175,8 @@ private static int ListMonitors(bool jsonOutput) string? inputName = null; uint? inputCode = null; string status = "ok"; + string? brightness = null; + string? contrast = null; try { @@ -171,10 +189,41 @@ private static int ListMonitors(bool jsonOutput) { status = "no_ddc_ci"; } + + // Get brightness and contrast if verbose mode is enabled + if (verboseOutput && status == "ok") + { + // Try to get brightness (VCP 0x10) + if (monitor.TryGetVcpFeature(VcpFeature.Brightness.Code, out uint brightnessCurrent, out uint brightnessMax)) + { + uint brightnessPercentage = FeatureResolver.ConvertRawToPercentage(brightnessCurrent, brightnessMax); + brightness = $"{brightnessPercentage}%"; + } + else + { + brightness = "N/A"; + } + + // Try to get contrast (VCP 0x12) + if (monitor.TryGetVcpFeature(VcpFeature.Contrast.Code, out uint contrastCurrent, out uint contrastMax)) + { + uint contrastPercentage = FeatureResolver.ConvertRawToPercentage(contrastCurrent, contrastMax); + contrast = $"{contrastPercentage}%"; + } + else + { + contrast = "N/A"; + } + } } catch { status = "error"; + if (verboseOutput) + { + brightness = "N/A"; + contrast = "N/A"; + } } return new MonitorInfo( @@ -184,7 +233,9 @@ private static int ListMonitors(bool jsonOutput) monitor.IsPrimary, inputName, inputCode != null ? $"0x{inputCode:X2}" : null, - status); + status, + brightness, + contrast); }).ToList(); var result = new ListMonitorsResponse(true, monitorList); @@ -197,13 +248,23 @@ private static int ListMonitors(bool jsonOutput) .AddColumn("Index") .AddColumn("Monitor Name") .AddColumn("Device") - .AddColumn("Current Input") - .AddColumn("Status"); + .AddColumn("Current Input"); + + // Add brightness and contrast columns if verbose mode is enabled + if (verboseOutput) + { + table.AddColumn("Brightness"); + table.AddColumn("Contrast"); + } + + table.AddColumn("Status"); foreach (var monitor in monitors) { string inputInfo = "N/A"; string status = "[green]OK[/]"; + string brightnessInfo = "N/A"; + string contrastInfo = "N/A"; try { @@ -215,18 +276,66 @@ private static int ListMonitors(bool jsonOutput) { status = "[yellow]No DDC/CI[/]"; } + + // Get brightness and contrast if verbose mode is enabled and monitor supports DDC/CI + if (verboseOutput && status == "[green]OK[/]") + { + // Try to get brightness (VCP 0x10) + if (monitor.TryGetVcpFeature(VcpFeature.Brightness.Code, out uint brightnessCurrent, out uint brightnessMax)) + { + uint brightnessPercentage = FeatureResolver.ConvertRawToPercentage(brightnessCurrent, brightnessMax); + brightnessInfo = $"{brightnessPercentage}%"; + } + else + { + brightnessInfo = "[dim]N/A[/]"; + } + + // Try to get contrast (VCP 0x12) + if (monitor.TryGetVcpFeature(VcpFeature.Contrast.Code, out uint contrastCurrent, out uint contrastMax)) + { + uint contrastPercentage = FeatureResolver.ConvertRawToPercentage(contrastCurrent, contrastMax); + contrastInfo = $"{contrastPercentage}%"; + } + else + { + contrastInfo = "[dim]N/A[/]"; + } + } + else if (verboseOutput) + { + brightnessInfo = "[dim]N/A[/]"; + contrastInfo = "[dim]N/A[/]"; + } } catch { status = "[red]Error[/]"; + if (verboseOutput) + { + brightnessInfo = "[dim]N/A[/]"; + contrastInfo = "[dim]N/A[/]"; + } } - table.AddRow( + var row = new List + { monitor.IsPrimary ? $"{monitor.Index} [yellow]*[/]" : monitor.Index.ToString(), monitor.Name, monitor.DeviceName, - inputInfo, - status); + inputInfo + }; + + // Add brightness and contrast columns if verbose mode is enabled + if (verboseOutput) + { + row.Add(brightnessInfo); + row.Add(contrastInfo); + } + + row.Add(status); + + table.AddRow(row.ToArray()); } AnsiConsole.Write(table); @@ -243,34 +352,54 @@ private static int ListMonitors(bool jsonOutput) private static int GetCurrentInput(string[] args, bool jsonOutput) { - if (args.Length < 3) + if (args.Length < 2) { if (jsonOutput) { - var error = new ErrorResponse(false, "Monitor identifier and feature required"); + var error = new ErrorResponse(false, "Monitor identifier required"); Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); } else { - AnsiConsole.MarkupLine("[red]Error:[/] Monitor identifier and feature required."); - AnsiConsole.MarkupLine("Usage: [yellow]DDCSwitch get [/]"); + AnsiConsole.MarkupLine("[red]Error:[/] Monitor identifier required."); + AnsiConsole.WriteLine("Usage: DDCSwitch get [feature]"); } return 1; } + // If no feature is specified, perform VCP scan + if (args.Length == 2) + { + return HandleVcpScanForMonitor(args[1], jsonOutput); + } + string featureInput = args[2]; if (!FeatureResolver.TryResolveFeature(featureInput, out VcpFeature? feature)) { + string errorMessage; + + // Provide specific error message based on input type + if (FeatureResolver.TryParseVcpCode(featureInput, out _)) + { + // Valid VCP code but not in our predefined list + errorMessage = $"VCP code '{featureInput}' is valid but may not be supported by all monitors"; + } + else + { + // Invalid feature name or VCP code + errorMessage = $"Invalid feature '{featureInput}'. {FeatureResolver.GetVcpCodeValidationError(featureInput)}"; + } + if (jsonOutput) { - var error = new ErrorResponse(false, $"Invalid feature '{featureInput}'"); + var error = new ErrorResponse(false, errorMessage); Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); } else { - AnsiConsole.MarkupLine($"[red]Error:[/] Invalid feature '{featureInput}'."); + AnsiConsole.MarkupLine($"[red]Error:[/] {errorMessage}"); AnsiConsole.MarkupLine("Valid features: brightness, contrast, input, or VCP code (0x10, 0x12, etc.)"); } @@ -318,23 +447,40 @@ private static int GetCurrentInput(string[] args, bool jsonOutput) return 1; } - // Use generic VCP method for all features - bool success = monitor.TryGetVcpFeature(feature!.Code, out uint current, out uint max); + // Use generic VCP method for all features with enhanced error handling + bool success = monitor.TryGetVcpFeature(feature!.Code, out uint current, out uint max, out int errorCode); if (!success) { + string errorMessage; + + if (VcpErrorHandler.IsTimeoutError(errorCode)) + { + errorMessage = VcpErrorHandler.CreateTimeoutMessage(monitor, feature, "read"); + } + else if (VcpErrorHandler.IsUnsupportedFeatureError(errorCode)) + { + errorMessage = VcpErrorHandler.CreateUnsupportedFeatureMessage(monitor, feature); + } + else if (errorCode == 0x00000006) // ERROR_INVALID_HANDLE + { + errorMessage = VcpErrorHandler.CreateCommunicationFailureMessage(monitor); + } + else + { + errorMessage = VcpErrorHandler.CreateReadFailureMessage(monitor, feature); + } + if (jsonOutput) { var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - var error = new ErrorResponse(false, $"Failed to get {feature.Name} from monitor '{monitor.Name}'", - monitorRef); + var error = new ErrorResponse(false, errorMessage, monitorRef); Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); } else { - AnsiConsole.MarkupLine($"[red]Error:[/] Failed to get {feature.Name} from monitor '{monitor.Name}'."); - AnsiConsole.MarkupLine("The monitor may not support this feature or requires administrator privileges."); + AnsiConsole.MarkupLine($"[red]Error:[/] {errorMessage}"); } // Cleanup @@ -420,50 +566,60 @@ private static int SetInput(string[] args, bool jsonOutput) if (!FeatureResolver.TryResolveFeature(featureInput, out VcpFeature? feature)) { + string errorMessage; + + // Provide specific error message based on input type + if (FeatureResolver.TryParseVcpCode(featureInput, out _)) + { + // Valid VCP code but not in our predefined list + errorMessage = $"VCP code '{featureInput}' is valid but may not be supported by all monitors"; + } + else + { + // Invalid feature name or VCP code + errorMessage = $"Invalid feature '{featureInput}'. {FeatureResolver.GetVcpCodeValidationError(featureInput)}"; + } + if (jsonOutput) { - var error = new ErrorResponse(false, $"Invalid feature '{featureInput}'"); + var error = new ErrorResponse(false, errorMessage); Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); } else { - AnsiConsole.MarkupLine($"[red]Error:[/] Invalid feature '{featureInput}'."); + AnsiConsole.MarkupLine($"[red]Error:[/] {errorMessage}"); AnsiConsole.MarkupLine("Valid features: brightness, contrast, input, or VCP code (0x10, 0x12, etc.)"); } return 1; } - // Parse the value based on feature type - uint setValue; + // Parse and validate the value based on feature type + uint setValue = 0; // Initialize to avoid compiler error uint? percentageValue = null; + string? validationError = null; if (feature!.Code == InputSource.VcpInputSource) { // Use existing input source parsing for input feature if (!InputSource.TryParse(valueInput, out setValue)) { - if (jsonOutput) - { - var error = new ErrorResponse(false, $"Invalid input source '{valueInput}'"); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine($"[red]Error:[/] Invalid input source '{valueInput}'."); - AnsiConsole.MarkupLine( - "Valid inputs: HDMI1, HDMI2, DP1, DP2, DVI1, DVI2, VGA1, VGA2, or hex code (0x11)"); - } - - return 1; + validationError = $"Invalid input source '{valueInput}'. Valid inputs: HDMI1, HDMI2, DP1, DP2, DVI1, DVI2, VGA1, VGA2, or hex code (0x11)"; } } else if (feature.SupportsPercentage && FeatureResolver.TryParsePercentage(valueInput, out uint percentage)) { - // Parse as percentage for brightness/contrast - percentageValue = percentage; - // We'll convert to raw value after getting monitor's max value - setValue = 0; // Placeholder + // Parse as percentage for brightness/contrast - validate percentage range + if (!FeatureResolver.IsValidPercentage(percentage)) + { + validationError = VcpErrorHandler.CreateRangeValidationMessage(feature, percentage, 100, true); + } + else + { + percentageValue = percentage; + // We'll convert to raw value after getting monitor's max value + setValue = 0; // Placeholder + } } else if (uint.TryParse(valueInput, out uint rawValue)) { @@ -471,23 +627,29 @@ private static int SetInput(string[] args, bool jsonOutput) setValue = rawValue; } else + { + // Invalid value format + if (feature.SupportsPercentage) + { + validationError = FeatureResolver.GetPercentageValidationError(valueInput); + } + else + { + validationError = $"Invalid value '{valueInput}' for feature '{feature.Name}'. Expected: numeric value within monitor's supported range"; + } + } + + // If we have a validation error, return it now + if (validationError != null) { if (jsonOutput) { - var error = new ErrorResponse(false, $"Invalid value '{valueInput}' for feature '{feature.Name}'"); + var error = new ErrorResponse(false, validationError); Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); } else { - AnsiConsole.MarkupLine($"[red]Error:[/] Invalid value '{valueInput}' for feature '{feature.Name}'."); - if (feature.SupportsPercentage) - { - AnsiConsole.MarkupLine("Valid values: 0-100% or raw numeric value"); - } - else - { - AnsiConsole.MarkupLine("Valid values: numeric value within monitor's supported range"); - } + AnsiConsole.MarkupLine($"[red]Error:[/] {validationError}"); } return 1; @@ -534,11 +696,10 @@ private static int SetInput(string[] args, bool jsonOutput) return 1; } - // If we have a percentage value, we need to get the max value first to convert it - // For raw values, we also need to validate they're within the monitor's supported range - if (percentageValue.HasValue || (!feature!.SupportsPercentage && feature.Code != InputSource.VcpInputSource)) + // If we have a percentage value or need to validate raw value range, get the monitor's max value + if (percentageValue.HasValue || (feature!.Code != InputSource.VcpInputSource && !percentageValue.HasValue)) { - if (monitor.TryGetVcpFeature(feature.Code, out uint currentValue, out uint maxValue)) + if (monitor.TryGetVcpFeature(feature.Code, out uint currentValue, out uint maxValue, out int errorCode)) { if (percentageValue.HasValue) { @@ -548,18 +709,19 @@ private static int SetInput(string[] args, bool jsonOutput) else if (feature.Code != InputSource.VcpInputSource) { // Validate raw value is within supported range - if (setValue > maxValue) + if (!FeatureResolver.IsValidRawVcpValue(setValue, maxValue)) { + string rangeError = VcpErrorHandler.CreateRangeValidationMessage(feature, setValue, maxValue); + if (jsonOutput) { var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - var error = new ErrorResponse(false, $"Value {setValue} is out of range for {feature.Name}. Valid range: 0-{maxValue}", monitorRef); + var error = new ErrorResponse(false, rangeError, monitorRef); Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); } else { - AnsiConsole.MarkupLine($"[red]Error:[/] Value {setValue} is out of range for {feature.Name}."); - AnsiConsole.MarkupLine($"Valid range: 0-{maxValue}"); + AnsiConsole.MarkupLine($"[red]Error:[/] {rangeError}"); } // Cleanup @@ -574,16 +736,34 @@ private static int SetInput(string[] args, bool jsonOutput) } else { + string readError; + + if (VcpErrorHandler.IsTimeoutError(errorCode)) + { + readError = VcpErrorHandler.CreateTimeoutMessage(monitor, feature, "read"); + } + else if (VcpErrorHandler.IsUnsupportedFeatureError(errorCode)) + { + readError = VcpErrorHandler.CreateUnsupportedFeatureMessage(monitor, feature); + } + else if (errorCode == 0x00000006) // ERROR_INVALID_HANDLE + { + readError = VcpErrorHandler.CreateCommunicationFailureMessage(monitor); + } + else + { + readError = $"Failed to read current {feature.Name} from monitor '{monitor.Name}' to validate range. {VcpErrorHandler.CreateReadFailureMessage(monitor, feature)}"; + } + if (jsonOutput) { var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - var error = new ErrorResponse(false, $"Failed to read current {feature.Name} from monitor '{monitor.Name}' to validate range", monitorRef); + var error = new ErrorResponse(false, readError, monitorRef); Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); } else { - AnsiConsole.MarkupLine($"[red]Error:[/] Failed to read current {feature.Name} from monitor '{monitor.Name}' to validate range."); - AnsiConsole.MarkupLine("The monitor may not support this feature or requires administrator privileges."); + AnsiConsole.MarkupLine($"[red]Error:[/] {readError}"); } // Cleanup @@ -607,9 +787,24 @@ private static int SetInput(string[] args, bool jsonOutput) { ctx.Spinner(Spinner.Known.Dots); - if (!monitor.TrySetVcpFeature(feature!.Code, setValue)) + if (!monitor.TrySetVcpFeature(feature!.Code, setValue, out int errorCode)) { - errorMsg = $"Failed to set {feature.Name} on monitor '{monitor.Name}'. The monitor may not support this feature or requires administrator privileges."; + if (VcpErrorHandler.IsTimeoutError(errorCode)) + { + errorMsg = VcpErrorHandler.CreateTimeoutMessage(monitor, feature, "write"); + } + else if (VcpErrorHandler.IsUnsupportedFeatureError(errorCode)) + { + errorMsg = VcpErrorHandler.CreateUnsupportedFeatureMessage(monitor, feature); + } + else if (errorCode == 0x00000006) // ERROR_INVALID_HANDLE + { + errorMsg = VcpErrorHandler.CreateCommunicationFailureMessage(monitor); + } + else + { + errorMsg = VcpErrorHandler.CreateWriteFailureMessage(monitor, feature, setValue); + } } else { @@ -625,9 +820,24 @@ private static int SetInput(string[] args, bool jsonOutput) } else { - if (!monitor.TrySetVcpFeature(feature!.Code, setValue)) + if (!monitor.TrySetVcpFeature(feature!.Code, setValue, out int errorCode)) { - errorMsg = $"Failed to set {feature.Name} on monitor '{monitor.Name}'"; + if (VcpErrorHandler.IsTimeoutError(errorCode)) + { + errorMsg = VcpErrorHandler.CreateTimeoutMessage(monitor, feature, "write"); + } + else if (VcpErrorHandler.IsUnsupportedFeatureError(errorCode)) + { + errorMsg = VcpErrorHandler.CreateUnsupportedFeatureMessage(monitor, feature); + } + else if (errorCode == 0x00000006) // ERROR_INVALID_HANDLE + { + errorMsg = VcpErrorHandler.CreateCommunicationFailureMessage(monitor); + } + else + { + errorMsg = VcpErrorHandler.CreateWriteFailureMessage(monitor, feature, setValue); + } } else { @@ -694,4 +904,262 @@ private static int SetInput(string[] args, bool jsonOutput) return 0; } + + private static int HandleVcpScan(List monitors, bool jsonOutput) + { + if (jsonOutput) + { + // JSON output for VCP scan + var scanResults = new List(); + + foreach (var monitor in monitors) + { + try + { + var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); + var features = monitor.ScanVcpFeatures(); + + // Convert to list and filter only supported features for cleaner output + var supportedFeatures = features.Values + .Where(f => f.IsSupported) + .OrderBy(f => f.Code) + .ToList(); + + scanResults.Add(new VcpScanResponse(true, monitorRef, supportedFeatures)); + } + catch (Exception ex) + { + var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); + scanResults.Add(new VcpScanResponse(false, monitorRef, new List(), ex.Message)); + } + } + + // Output all scan results + foreach (var result in scanResults) + { + Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.VcpScanResponse)); + } + } + else + { + // Table output for VCP scan + foreach (var monitor in monitors) + { + try + { + AnsiConsole.MarkupLine($"\n[bold blue]Monitor {monitor.Index}: {monitor.Name}[/] ({monitor.DeviceName})"); + + var features = monitor.ScanVcpFeatures(); + var supportedFeatures = features.Values + .Where(f => f.IsSupported) + .OrderBy(f => f.Code) + .ToList(); + + if (supportedFeatures.Count == 0) + { + AnsiConsole.MarkupLine("[yellow] No supported VCP features found[/]"); + continue; + } + + var table = new Table() + .Border(TableBorder.Rounded) + .AddColumn("VCP Code") + .AddColumn("Feature Name") + .AddColumn("Access Type") + .AddColumn("Current Value") + .AddColumn("Max Value") + .AddColumn("Percentage"); + + foreach (var feature in supportedFeatures) + { + string vcpCode = $"0x{feature.Code:X2}"; + string accessType = feature.Type switch + { + VcpFeatureType.ReadOnly => "[yellow]Read-Only[/]", + VcpFeatureType.WriteOnly => "[red]Write-Only[/]", + VcpFeatureType.ReadWrite => "[green]Read-Write[/]", + _ => "[dim]Unknown[/]" + }; + + string currentValue = feature.CurrentValue.ToString(); + string maxValue = feature.MaxValue.ToString(); + + // Calculate percentage for known percentage-based features + string percentage = "N/A"; + if ((feature.Code == VcpFeature.Brightness.Code || feature.Code == VcpFeature.Contrast.Code) && feature.MaxValue > 0) + { + uint percentageValue = FeatureResolver.ConvertRawToPercentage(feature.CurrentValue, feature.MaxValue); + percentage = $"{percentageValue}%"; + } + + table.AddRow(vcpCode, feature.Name, accessType, currentValue, maxValue, percentage); + } + + AnsiConsole.Write(table); + } + catch (Exception ex) + { + AnsiConsole.MarkupLine($"[red]Error scanning monitor {monitor.Index} ({monitor.Name}): {ex.Message}[/]"); + } + } + } + + // Cleanup + foreach (var monitor in monitors) + { + monitor.Dispose(); + } + + return 0; + } + + private static int HandleVcpScanForMonitor(string monitorIdentifier, bool jsonOutput) + { + if (!jsonOutput) + { + AnsiConsole.Status() + .Start("Scanning VCP features...", ctx => + { + ctx.Spinner(Spinner.Known.Dots); + Thread.Sleep(500); // Pause for VCP scanning + }); + } + + var monitors = MonitorController.EnumerateMonitors(); + + if (monitors.Count == 0) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, "No DDC/CI capable monitors found"); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + AnsiConsole.MarkupLine("[red]Error:[/] No DDC/CI capable monitors found."); + } + + return 1; + } + + var monitor = MonitorController.FindMonitor(monitors, monitorIdentifier); + + if (monitor == null) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, $"Monitor '{monitorIdentifier}' not found"); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + AnsiConsole.MarkupLine($"[red]Error:[/] Monitor '{monitorIdentifier}' not found."); + AnsiConsole.MarkupLine("Use [yellow]DDCSwitch list[/] to see available monitors."); + } + + // Cleanup + foreach (var m in monitors) + { + m.Dispose(); + } + + return 1; + } + + try + { + var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); + var features = monitor.ScanVcpFeatures(); + + // Filter only supported features for cleaner output + var supportedFeatures = features.Values + .Where(f => f.IsSupported) + .OrderBy(f => f.Code) + .ToList(); + + if (jsonOutput) + { + // JSON output for VCP scan + var scanResult = new VcpScanResponse(true, monitorRef, supportedFeatures); + Console.WriteLine(JsonSerializer.Serialize(scanResult, JsonContext.Default.VcpScanResponse)); + } + else + { + // Table output for VCP scan - consistent with verbose listing format + AnsiConsole.MarkupLine($"[bold blue]Monitor {monitor.Index}: {monitor.Name}[/] ({monitor.DeviceName})"); + + if (supportedFeatures.Count == 0) + { + AnsiConsole.MarkupLine("[yellow] No supported VCP features found[/]"); + } + else + { + var table = new Table() + .Border(TableBorder.Rounded) + .AddColumn("VCP Code") + .AddColumn("Feature Name") + .AddColumn("Access Type") + .AddColumn("Current Value") + .AddColumn("Max Value") + .AddColumn("Percentage"); + + foreach (var feature in supportedFeatures) + { + string vcpCode = $"0x{feature.Code:X2}"; + string accessType = feature.Type switch + { + VcpFeatureType.ReadOnly => "[yellow]Read-Only[/]", + VcpFeatureType.WriteOnly => "[red]Write-Only[/]", + VcpFeatureType.ReadWrite => "[green]Read-Write[/]", + _ => "[dim]Unknown[/]" + }; + + string currentValue = feature.CurrentValue.ToString(); + string maxValue = feature.MaxValue.ToString(); + + // Calculate percentage for known percentage-based features + string percentage = "N/A"; + if ((feature.Code == VcpFeature.Brightness.Code || feature.Code == VcpFeature.Contrast.Code) && feature.MaxValue > 0) + { + uint percentageValue = FeatureResolver.ConvertRawToPercentage(feature.CurrentValue, feature.MaxValue); + percentage = $"{percentageValue}%"; + } + + table.AddRow(vcpCode, feature.Name, accessType, currentValue, maxValue, percentage); + } + + AnsiConsole.Write(table); + } + } + } + catch (Exception ex) + { + if (jsonOutput) + { + var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); + var scanResult = new VcpScanResponse(false, monitorRef, new List(), ex.Message); + Console.WriteLine(JsonSerializer.Serialize(scanResult, JsonContext.Default.VcpScanResponse)); + } + else + { + AnsiConsole.MarkupLine($"[red]Error scanning monitor {monitor.Index} ({monitor.Name}): {ex.Message}[/]"); + } + + // Cleanup + foreach (var m in monitors) + { + m.Dispose(); + } + + return 1; + } + + // Cleanup + foreach (var m in monitors) + { + m.Dispose(); + } + + return 0; + } } \ No newline at end of file diff --git a/DDCSwitch/VcpErrorHandler.cs b/DDCSwitch/VcpErrorHandler.cs new file mode 100644 index 0000000..aac96a6 --- /dev/null +++ b/DDCSwitch/VcpErrorHandler.cs @@ -0,0 +1,259 @@ +using System.Runtime.InteropServices; + +namespace DDCSwitch; + +/// +/// Provides enhanced error handling for VCP operations with specific error messages and suggestions +/// +public static class VcpErrorHandler +{ + /// + /// Creates a detailed error message for VCP read failures + /// + /// The monitor that failed + /// The VCP feature that failed + /// Detailed error message with suggestions + public static string CreateReadFailureMessage(Monitor monitor, VcpFeature feature) + { + var baseMessage = $"Failed to read {feature.Name} from monitor '{monitor.Name}'"; + var suggestions = GetReadFailureSuggestions(feature); + + return $"{baseMessage}. {suggestions}"; + } + + /// + /// Creates a detailed error message for VCP write failures + /// + /// The monitor that failed + /// The VCP feature that failed + /// The value that was attempted to be set + /// Detailed error message with suggestions + public static string CreateWriteFailureMessage(Monitor monitor, VcpFeature feature, uint attemptedValue) + { + var baseMessage = $"Failed to set {feature.Name} to {attemptedValue} on monitor '{monitor.Name}'"; + var suggestions = GetWriteFailureSuggestions(feature, attemptedValue); + + return $"{baseMessage}. {suggestions}"; + } + + /// + /// Creates a detailed error message for unsupported VCP features + /// + /// The monitor that doesn't support the feature + /// The unsupported VCP feature + /// Detailed error message with alternatives + public static string CreateUnsupportedFeatureMessage(Monitor monitor, VcpFeature feature) + { + var baseMessage = $"Monitor '{monitor.Name}' doesn't support {feature.Name} control (VCP 0x{feature.Code:X2})"; + var alternatives = GetFeatureAlternatives(feature); + + return $"{baseMessage}. {alternatives}"; + } + + /// + /// Creates a detailed error message for VCP communication timeouts + /// + /// The monitor that timed out + /// The VCP feature that timed out + /// The operation that timed out (read/write) + /// Detailed error message with troubleshooting steps + public static string CreateTimeoutMessage(Monitor monitor, VcpFeature feature, string operation) + { + var baseMessage = $"Timeout while trying to {operation} {feature.Name} on monitor '{monitor.Name}'"; + var troubleshooting = GetTimeoutTroubleshooting(); + + return $"{baseMessage}. {troubleshooting}"; + } + + /// + /// Creates a detailed error message for DDC/CI communication failures + /// + /// The monitor with communication issues + /// Detailed error message with troubleshooting steps + public static string CreateCommunicationFailureMessage(Monitor monitor) + { + var baseMessage = $"DDC/CI communication failed with monitor '{monitor.Name}'"; + var troubleshooting = GetCommunicationTroubleshooting(); + + return $"{baseMessage}. {troubleshooting}"; + } + + /// + /// Creates a detailed error message for value range validation failures + /// + /// The VCP feature + /// The invalid value + /// The maximum allowed value + /// Whether the value is a percentage + /// Detailed error message with valid range information + public static string CreateRangeValidationMessage(VcpFeature feature, uint attemptedValue, uint maxValue, bool isPercentage = false) + { + if (isPercentage) + { + return $"{feature.Name} value {attemptedValue}% is out of range. Valid range: 0-100%"; + } + + var baseMessage = $"{feature.Name} value {attemptedValue} is out of range for this monitor"; + var validRange = $"Valid range: 0-{maxValue}"; + var suggestion = GetRangeValidationSuggestion(feature, maxValue); + + return $"{baseMessage}. {validRange}. {suggestion}"; + } + + /// + /// Determines if a VCP operation failure is likely due to a timeout + /// + /// The last Win32 error code + /// True if the error indicates a timeout + public static bool IsTimeoutError(int lastError) + { + // Common timeout-related error codes + return lastError switch + { + 0x00000102 => true, // ERROR_TIMEOUT + 0x00000121 => true, // ERROR_SEM_TIMEOUT + 0x000005B4 => true, // ERROR_TIMEOUT (alternative) + 0x00000079 => true, // ERROR_SEM_TIMEOUT (alternative) + _ => false + }; + } + + /// + /// Determines if a VCP operation failure is likely due to unsupported feature + /// + /// The last Win32 error code + /// True if the error indicates unsupported feature + public static bool IsUnsupportedFeatureError(int lastError) + { + // Common unsupported feature error codes + return lastError switch + { + 0x00000001 => true, // ERROR_INVALID_FUNCTION + 0x00000057 => true, // ERROR_INVALID_PARAMETER + 0x0000007A => true, // ERROR_INSUFFICIENT_BUFFER + 0x00000032 => true, // ERROR_NOT_SUPPORTED + _ => false + }; + } + + /// + /// Gets suggestions for VCP read failures + /// + private static string GetReadFailureSuggestions(VcpFeature feature) + { + var suggestions = new List(); + + if (feature.Code == VcpFeature.Brightness.Code || feature.Code == VcpFeature.Contrast.Code) + { + suggestions.Add("Some monitors require administrator privileges for brightness/contrast control"); + suggestions.Add("Try running as administrator or check if the monitor supports DDC/CI for this feature"); + } + else if (feature.Code == VcpFeature.InputSource.Code) + { + suggestions.Add("Ensure the monitor supports DDC/CI input switching"); + suggestions.Add("Some monitors only support input switching when not in use"); + } + else + { + suggestions.Add($"VCP code 0x{feature.Code:X2} may not be supported by this monitor"); + suggestions.Add("Use 'DDCSwitch list --scan' to see all supported VCP codes"); + } + + suggestions.Add("Check that the monitor is properly connected and powered on"); + + return string.Join(". ", suggestions); + } + + /// + /// Gets suggestions for VCP write failures + /// + private static string GetWriteFailureSuggestions(VcpFeature feature, uint attemptedValue) + { + var suggestions = new List(); + + if (feature.Code == VcpFeature.Brightness.Code || feature.Code == VcpFeature.Contrast.Code) + { + suggestions.Add("Some monitors require administrator privileges for brightness/contrast control"); + suggestions.Add("Ensure the value is within the monitor's supported range (use 'get' command to check current range)"); + } + else if (feature.Code == VcpFeature.InputSource.Code) + { + suggestions.Add("Verify the input source is available on this monitor"); + suggestions.Add("Some monitors only allow input switching when the input is not active"); + } + else + { + suggestions.Add($"VCP code 0x{feature.Code:X2} may not support write operations on this monitor"); + suggestions.Add("Use 'DDCSwitch list --scan' to check if this VCP code supports write operations"); + } + + suggestions.Add("Try running as administrator if permission issues persist"); + + return string.Join(". ", suggestions); + } + + /// + /// Gets alternative features when a feature is unsupported + /// + private static string GetFeatureAlternatives(VcpFeature feature) + { + return feature.Code switch + { + 0x10 => "Try using your monitor's physical buttons or on-screen display (OSD) to adjust brightness", + 0x12 => "Try using your monitor's physical buttons or on-screen display (OSD) to adjust contrast", + 0x60 => "Try using your monitor's physical input selection button or check if the monitor supports other input switching methods", + _ => "Use 'DDCSwitch list --scan' to see all supported VCP codes for this monitor" + }; + } + + /// + /// Gets troubleshooting steps for timeout errors + /// + private static string GetTimeoutTroubleshooting() + { + var steps = new List + { + "The monitor may be busy or slow to respond", + "Try waiting a moment and running the command again", + "Check that no other DDC/CI applications are accessing the monitor", + "Ensure the monitor cable supports DDC/CI communication (some cheap cables don't)", + "Try power cycling the monitor if the issue persists" + }; + + return string.Join(". ", steps); + } + + /// + /// Gets troubleshooting steps for general communication failures + /// + private static string GetCommunicationTroubleshooting() + { + var steps = new List + { + "Ensure the monitor supports DDC/CI (check monitor documentation)", + "Verify the video cable supports DDC/CI (HDMI, DisplayPort, DVI-D, or VGA with DDC support)", + "Try running as administrator - some monitors require elevated privileges", + "Check if DDC/CI is enabled in the monitor's on-screen display (OSD) settings", + "Power cycle the monitor and try again" + }; + + return string.Join(". ", steps); + } + + /// + /// Gets suggestions for range validation failures + /// + private static string GetRangeValidationSuggestion(VcpFeature feature, uint maxValue) + { + if (feature.SupportsPercentage) + { + return "For percentage values, use 0-100% format (e.g., '75%')"; + } + + return feature.Code switch + { + 0x60 => "For input sources, use names like 'HDMI1', 'DP1', or hex codes like '0x11'", + _ => $"Use 'DDCSwitch get {feature.Name}' to see the current value and valid range" + }; + } +} \ No newline at end of file diff --git a/EXAMPLES.md b/EXAMPLES.md index 4dd0e27..f05054c 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -1,6 +1,6 @@ # DDCSwitch Examples -This document contains detailed examples and use cases for DDCSwitch. +This document contains detailed examples and use cases for DDCSwitch, including input switching, brightness/contrast control, and raw VCP access. ## Basic Usage Examples @@ -12,21 +12,192 @@ DDCSwitch list This will show all your monitors and indicate which ones support DDC/CI control. Monitors with "OK" status can be controlled. -### Get Current Input of Primary Monitor +### Verbose Monitor Information + +Get detailed information including brightness and contrast: ```powershell -# If primary monitor is index 0 +DDCSwitch list --verbose +``` + +This shows current brightness and contrast levels for each monitor (displays "N/A" for unsupported features). + +### Get Current Settings + +```powershell +# Get all VCP features for primary monitor (scans all supported features) DDCSwitch get 0 + +# Get specific features +DDCSwitch get 0 input # Current input source +DDCSwitch get 0 brightness # Current brightness +DDCSwitch get 0 contrast # Current contrast + +# Get raw VCP value +DDCSwitch get 0 0x10 # Brightness (raw) ``` -### Switch Between Two Inputs Quickly +### Set Monitor Settings ```powershell -# Switch to console (HDMI) +# Switch input DDCSwitch set 0 HDMI1 -# Switch to PC (DisplayPort) -DDCSwitch set 0 DP1 +# Set brightness to 75% +DDCSwitch set 0 brightness 75% + +# Set contrast to 80% +DDCSwitch set 0 contrast 80% + +# Set raw VCP value +DDCSwitch set 0 0x10 120 # Brightness (raw value) +``` + +## Brightness and Contrast Control + +### Basic Brightness Control + +```powershell +# Set brightness to specific percentage +DDCSwitch set 0 brightness 50% +DDCSwitch set 0 brightness 75% +DDCSwitch set 0 brightness 100% + +# Get current brightness +DDCSwitch get 0 brightness +# Output: Monitor: Generic PnP Monitor / Brightness: 75% (120/160) +``` + +### Basic Contrast Control + +```powershell +# Set contrast to specific percentage +DDCSwitch set 0 contrast 60% +DDCSwitch set 0 contrast 85% +DDCSwitch set 0 contrast 100% + +# Get current contrast +DDCSwitch get 0 contrast +# Output: Monitor: Generic PnP Monitor / Contrast: 85% (136/160) +``` + +### Brightness Presets + +Create quick brightness presets: + +```powershell +# brightness-low.ps1 +DDCSwitch set 0 brightness 25% +Write-Host "Brightness set to 25% (Low)" -ForegroundColor Green + +# brightness-medium.ps1 +DDCSwitch set 0 brightness 50% +Write-Host "Brightness set to 50% (Medium)" -ForegroundColor Green + +# brightness-high.ps1 +DDCSwitch set 0 brightness 75% +Write-Host "Brightness set to 75% (High)" -ForegroundColor Green + +# brightness-max.ps1 +DDCSwitch set 0 brightness 100% +Write-Host "Brightness set to 100% (Maximum)" -ForegroundColor Green +``` + +### Time-Based Brightness Control + +Automatically adjust brightness based on time of day: + +```powershell +# auto-brightness.ps1 +$hour = (Get-Date).Hour + +if ($hour -ge 6 -and $hour -lt 9) { + # Morning: Medium brightness + DDCSwitch set 0 brightness 60% + Write-Host "Morning brightness: 60%" -ForegroundColor Yellow +} elseif ($hour -ge 9 -and $hour -lt 18) { + # Daytime: High brightness + DDCSwitch set 0 brightness 85% + Write-Host "Daytime brightness: 85%" -ForegroundColor Green +} elseif ($hour -ge 18 -and $hour -lt 22) { + # Evening: Medium brightness + DDCSwitch set 0 brightness 50% + Write-Host "Evening brightness: 50%" -ForegroundColor Orange +} else { + # Night: Low brightness + DDCSwitch set 0 brightness 25% + Write-Host "Night brightness: 25%" -ForegroundColor Blue +} +``` + +### Gaming vs Work Profiles + +Create different brightness/contrast profiles: + +```powershell +# gaming-profile.ps1 +Write-Host "Activating Gaming Profile..." -ForegroundColor Cyan +DDCSwitch set 0 input HDMI1 # Switch to console +DDCSwitch set 0 brightness 90% # High brightness for gaming +DDCSwitch set 0 contrast 85% # High contrast for visibility +Write-Host "Gaming profile activated!" -ForegroundColor Green + +# work-profile.ps1 +Write-Host "Activating Work Profile..." -ForegroundColor Cyan +DDCSwitch set 0 input DP1 # Switch to PC +DDCSwitch set 0 brightness 60% # Comfortable brightness for long work +DDCSwitch set 0 contrast 75% # Balanced contrast for text +Write-Host "Work profile activated!" -ForegroundColor Green +``` + +## Raw VCP Access Examples + +### Discover VCP Features + +```powershell +# Use verbose listing to see all supported VCP features +DDCSwitch list --verbose +``` + +### Common VCP Codes + +```powershell +# Brightness (VCP 0x10) +DDCSwitch get 0 0x10 +DDCSwitch set 0 0x10 120 + +# Contrast (VCP 0x12) +DDCSwitch get 0 0x12 +DDCSwitch set 0 0x12 140 + +# Input Source (VCP 0x60) +DDCSwitch get 0 0x60 +DDCSwitch set 0 0x60 0x11 # HDMI1 + +# Color Temperature (VCP 0x14) - if supported +DDCSwitch get 0 0x14 +DDCSwitch set 0 0x14 6500 # 6500K +``` + +### Test Unknown VCP Codes + +```powershell +# test-vcp-codes.ps1 - Discover what VCP codes your monitor supports +Write-Host "Testing VCP codes for Monitor 0" -ForegroundColor Cyan + +$commonCodes = @(0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x20, 0x30, 0x60, 0x62, 0x6C, 0x6E, 0x70) + +foreach ($code in $commonCodes) { + $hexCode = "0x{0:X2}" -f $code + try { + $result = DDCSwitch get 0 $hexCode 2>$null + if ($result -notmatch "error|failed") { + Write-Host "✓ VCP $hexCode supported: $result" -ForegroundColor Green + } + } catch { + Write-Host "✗ VCP $hexCode not supported" -ForegroundColor Red + } +} ``` ## JSON Output and Automation @@ -35,38 +206,55 @@ All DDCSwitch commands support the `--json` flag for machine-readable output. Th ### PowerShell JSON Examples -#### Example 1: Conditional Input Switching +#### Example 1: Conditional Input Switching with Brightness Control -Check the current input and switch only if needed: +Check the current input and switch only if needed, then adjust brightness: ```powershell -# Check if monitor is on HDMI1, switch to DP1 if not +# Check if monitor is on HDMI1, switch to DP1 if not, then set work brightness $result = DDCSwitch get 0 --json | ConvertFrom-Json if ($result.success -and $result.currentInputCode -ne "0x11") { Write-Host "Monitor is on $($result.currentInput), switching to HDMI1..." DDCSwitch set 0 HDMI1 --json | Out-Null + DDCSwitch set 0 brightness 75% --json | Out-Null + Write-Host "Switched to HDMI1 and set brightness to 75%" -ForegroundColor Green } else { Write-Host "Monitor already on HDMI1" } ``` -#### Example 2: Switch All Monitors with Error Handling +#### Example 2: Complete Monitor Setup with All Features ```powershell -# Switch all available monitors to HDMI1 with error handling +# Switch all available monitors with full configuration $listResult = DDCSwitch list --json | ConvertFrom-Json if ($listResult.success) { foreach ($monitor in $listResult.monitors) { if ($monitor.status -eq "ok") { - Write-Host "Switching $($monitor.name) to HDMI1..." - $setResult = DDCSwitch set $monitor.index HDMI1 --json | ConvertFrom-Json + Write-Host "Configuring $($monitor.name)..." -ForegroundColor Cyan + # Set input + $setResult = DDCSwitch set $monitor.index HDMI1 --json | ConvertFrom-Json if ($setResult.success) { - Write-Host "✓ Successfully switched $($monitor.name)" -ForegroundColor Green + Write-Host " ✓ Input: HDMI1" -ForegroundColor Green + } + + # Set brightness + $brightnessResult = DDCSwitch set $monitor.index brightness 75% --json | ConvertFrom-Json + if ($brightnessResult.success) { + Write-Host " ✓ Brightness: 75%" -ForegroundColor Green + } else { + Write-Host " ✗ Brightness not supported" -ForegroundColor Yellow + } + + # Set contrast + $contrastResult = DDCSwitch set $monitor.index contrast 80% --json | ConvertFrom-Json + if ($contrastResult.success) { + Write-Host " ✓ Contrast: 80%" -ForegroundColor Green } else { - Write-Host "✗ Failed: $($setResult.error)" -ForegroundColor Red + Write-Host " ✗ Contrast not supported" -ForegroundColor Yellow } } } @@ -75,13 +263,13 @@ if ($listResult.success) { } ``` -#### Example 3: Monitor Status Dashboard +#### Example 3: Monitor Status Dashboard with VCP Features -Create a simple dashboard showing all monitor states: +Create a comprehensive dashboard showing all monitor states: ```powershell # monitor-dashboard.ps1 -$result = DDCSwitch list --json | ConvertFrom-Json +$result = DDCSwitch list --verbose --json | ConvertFrom-Json if ($result.success) { Write-Host "`n=== Monitor Status Dashboard ===" -ForegroundColor Cyan @@ -93,32 +281,55 @@ if ($result.success) { Write-Host " Device: $($monitor.deviceName)" Write-Host " Input: $($monitor.currentInput) ($($monitor.currentInputCode))" Write-Host " Status: $($monitor.status.ToUpper())" + + if ($monitor.brightness) { + Write-Host " Brightness: $($monitor.brightness)" -ForegroundColor Green + } else { + Write-Host " Brightness: N/A" -ForegroundColor Gray + } + + if ($monitor.contrast) { + Write-Host " Contrast: $($monitor.contrast)" -ForegroundColor Green + } else { + Write-Host " Contrast: N/A" -ForegroundColor Gray + } Write-Host "" } } ``` -#### Example 4: Toggle Between Two Inputs +#### Example 4: Smart Brightness Toggle ```powershell -# toggle-input.ps1 +# smart-brightness-toggle.ps1 param([int]$MonitorIndex = 0) -$result = DDCSwitch get $MonitorIndex --json | ConvertFrom-Json +$result = DDCSwitch get $MonitorIndex brightness --json | ConvertFrom-Json if ($result.success) { - $newInput = if ($result.currentInputCode -eq "0x11") { "DP1" } else { "HDMI1" } - $switchResult = DDCSwitch set $MonitorIndex $newInput --json | ConvertFrom-Json + $currentPercent = $result.percentageValue + + # Toggle between 25%, 50%, 75%, 100% + $newBrightness = switch ($currentPercent) { + {$_ -le 25} { 50 } + {$_ -le 50} { 75 } + {$_ -le 75} { 100 } + default { 25 } + } + + $switchResult = DDCSwitch set $MonitorIndex brightness "$newBrightness%" --json | ConvertFrom-Json if ($switchResult.success) { - Write-Host "Toggled from $($result.currentInput) to $($switchResult.newInput)" -ForegroundColor Green + Write-Host "Brightness: $currentPercent% → $newBrightness%" -ForegroundColor Green } +} else { + Write-Host "Brightness control not supported on this monitor" -ForegroundColor Yellow } ``` ### Python JSON Examples -#### Example 1: Simple Monitor Switcher +#### Example 1: Monitor Control with Brightness/Contrast ```python #!/usr/bin/env python3 @@ -136,16 +347,34 @@ def run_ddc(args): return json.loads(result.stdout) def list_monitors(): - """List all monitors""" - data = run_ddc(['list']) + """List all monitors with brightness/contrast info""" + data = run_ddc(['list', '--verbose']) if data['success']: for monitor in data['monitors']: primary = " [PRIMARY]" if monitor['isPrimary'] else "" print(f"{monitor['index']}: {monitor['name']}{primary}") - print(f" Current: {monitor['currentInput']} ({monitor['currentInputCode']})") + print(f" Input: {monitor['currentInput']} ({monitor['currentInputCode']})") + print(f" Brightness: {monitor.get('brightness', 'N/A')}") + print(f" Contrast: {monitor.get('contrast', 'N/A')}") else: print(f"Error: {data['error']}", file=sys.stderr) +def set_brightness(monitor_index, percentage): + """Set monitor brightness""" + data = run_ddc(['set', str(monitor_index), 'brightness', f'{percentage}%']) + if data['success']: + print(f"✓ Set brightness to {percentage}% on {data['monitor']['name']}") + else: + print(f"✗ Error: {data['error']}", file=sys.stderr) + +def set_contrast(monitor_index, percentage): + """Set monitor contrast""" + data = run_ddc(['set', str(monitor_index), 'contrast', f'{percentage}%']) + if data['success']: + print(f"✓ Set contrast to {percentage}% on {data['monitor']['name']}") + else: + print(f"✗ Error: {data['error']}", file=sys.stderr) + def switch_input(monitor_index, input_name): """Switch monitor input""" data = run_ddc(['set', str(monitor_index), input_name]) @@ -158,79 +387,101 @@ def switch_input(monitor_index, input_name): # Example usage if __name__ == '__main__': list_monitors() + # set_brightness(0, 75) + # set_contrast(0, 80) # switch_input(0, 'HDMI1') ``` -#### Example 2: Gaming Mode Automation +#### Example 2: Automated Brightness Control ```python #!/usr/bin/env python3 """ -Automatically switch monitors based on running applications -Usage: python gaming_mode.py +Automatically adjust monitor brightness based on time of day +Usage: python auto_brightness.py """ import subprocess import json import time -import psutil +from datetime import datetime def run_ddc(args): result = subprocess.run(['DDCSwitch'] + args + ['--json'], capture_output=True, text=True) return json.loads(result.stdout) -def switch_to_gaming(): - """Switch all monitors to HDMI (console inputs)""" - print("🎮 Activating gaming mode...") +def set_brightness_all(percentage): + """Set brightness on all supported monitors""" + print(f"🔆 Setting brightness to {percentage}%...") data = run_ddc(['list']) if data['success']: for monitor in data['monitors']: if monitor['status'] == 'ok': - result = run_ddc(['set', str(monitor['index']), 'HDMI1']) + result = run_ddc(['set', str(monitor['index']), 'brightness', f'{percentage}%']) if result['success']: - print(f" ✓ {monitor['name']} → HDMI1") + print(f" ✓ {monitor['name']} → {percentage}%") + else: + print(f" ✗ {monitor['name']} → Brightness not supported") -def switch_to_work(): - """Switch all monitors to DisplayPort (PC inputs)""" +def get_brightness_for_time(): + """Get appropriate brightness based on current time""" + hour = datetime.now().hour + + if 6 <= hour < 9: # Morning + return 60 + elif 9 <= hour < 18: # Daytime + return 85 + elif 18 <= hour < 22: # Evening + return 50 + else: # Night + return 25 + +def gaming_mode(): + """Switch to gaming setup with high brightness""" + print("🎮 Activating gaming mode...") + data = run_ddc(['list']) + + if data['success']: + for monitor in data['monitors']: + if monitor['status'] == 'ok': + # Switch to HDMI (console) + input_result = run_ddc(['set', str(monitor['index']), 'HDMI1']) + if input_result['success']: + print(f" ✓ {monitor['name']} → HDMI1") + + # Set high brightness for gaming + brightness_result = run_ddc(['set', str(monitor['index']), 'brightness', '90%']) + if brightness_result['success']: + print(f" ✓ {monitor['name']} → 90% brightness") + +def work_mode(): + """Switch to work setup with comfortable brightness""" print("💼 Activating work mode...") data = run_ddc(['list']) if data['success']: for monitor in data['monitors']: if monitor['status'] == 'ok': - result = run_ddc(['set', str(monitor['index']), 'DP1']) - if result['success']: + # Switch to DisplayPort (PC) + input_result = run_ddc(['set', str(monitor['index']), 'DP1']) + if input_result['success']: print(f" ✓ {monitor['name']} → DP1") + + # Set comfortable brightness for work + brightness_result = run_ddc(['set', str(monitor['index']), 'brightness', '60%']) + if brightness_result['success']: + print(f" ✓ {monitor['name']} → 60% brightness") -def is_game_running(): - """Check if specific games are running""" - game_processes = ['steam.exe', 'EpicGamesLauncher.exe'] - for proc in psutil.process_iter(['name']): - if proc.info['name'] in game_processes: - return True - return False - -# Monitor and switch automatically +# Auto-brightness based on time if __name__ == '__main__': - previous_state = None - - while True: - gaming = is_game_running() - - if gaming != previous_state: - if gaming: - switch_to_gaming() - else: - switch_to_work() - previous_state = gaming - - time.sleep(10) # Check every 10 seconds + brightness = get_brightness_for_time() + set_brightness_all(brightness) ``` ### Node.js JSON Examples -#### Example 1: Monitor Information API +#### Example 1: Monitor Control API with VCP Features ```javascript // monitor-api.js @@ -244,38 +495,67 @@ class DDCSwitch { return JSON.parse(output); } - static listMonitors() { - return this.exec(['list']); + static listMonitors(verbose = false) { + const args = verbose ? ['list', '--verbose'] : ['list']; + return this.exec(args); } static getCurrentInput(monitorIndex) { return this.exec(['get', monitorIndex]); } + static getBrightness(monitorIndex) { + return this.exec(['get', monitorIndex, 'brightness']); + } + + static getContrast(monitorIndex) { + return this.exec(['get', monitorIndex, 'contrast']); + } + static setInput(monitorIndex, input) { return this.exec(['set', monitorIndex, input]); } + + static setBrightness(monitorIndex, percentage) { + return this.exec(['set', monitorIndex, 'brightness', `${percentage}%`]); + } + + static setContrast(monitorIndex, percentage) { + return this.exec(['set', monitorIndex, 'contrast', `${percentage}%`]); + } + + static setRawVcp(monitorIndex, vcpCode, value) { + return this.exec(['set', monitorIndex, vcpCode, value]); + } } // Usage example -const monitors = DDCSwitch.listMonitors(); +const monitors = DDCSwitch.listMonitors(true); console.log(`Found ${monitors.monitors.length} monitors`); monitors.monitors.forEach(monitor => { - console.log(`${monitor.index}: ${monitor.name} - ${monitor.currentInput}`); + console.log(`${monitor.index}: ${monitor.name}`); + console.log(` Input: ${monitor.currentInput}`); + console.log(` Brightness: ${monitor.brightness || 'N/A'}`); + console.log(` Contrast: ${monitor.contrast || 'N/A'}`); }); -// Switch first monitor to HDMI1 -const result = DDCSwitch.setInput(0, 'HDMI1'); -if (result.success) { - console.log(`✓ Switched to ${result.newInput}`); +// Set brightness and contrast +const brightnessResult = DDCSwitch.setBrightness(0, 75); +if (brightnessResult.success) { + console.log(`✓ Set brightness to 75%`); +} + +const contrastResult = DDCSwitch.setContrast(0, 80); +if (contrastResult.success) { + console.log(`✓ Set contrast to 80%`); } ``` -#### Example 2: Express.js REST API +#### Example 2: Express.js REST API with VCP Support ```javascript -// server.js - Web API for monitor control +// server.js - Web API for complete monitor control const express = require('express'); const { execSync } = require('child_process'); @@ -293,18 +573,32 @@ function runDDC(args) { } } -// GET /monitors - List all monitors +// GET /monitors - List all monitors (with verbose option) app.get('/monitors', (req, res) => { - const result = runDDC(['list']); + const verbose = req.query.verbose === 'true'; + const args = verbose ? ['list', '--verbose'] : ['list']; + const result = runDDC(args); res.json(result); }); -// GET /monitors/:id - Get specific monitor +// GET /monitors/:id - Get specific monitor info app.get('/monitors/:id', (req, res) => { const result = runDDC(['get', req.params.id]); res.json(result); }); +// GET /monitors/:id/brightness - Get brightness +app.get('/monitors/:id/brightness', (req, res) => { + const result = runDDC(['get', req.params.id, 'brightness']); + res.json(result); +}); + +// GET /monitors/:id/contrast - Get contrast +app.get('/monitors/:id/contrast', (req, res) => { + const result = runDDC(['get', req.params.id, 'contrast']); + res.json(result); +}); + // POST /monitors/:id/input - Set monitor input app.post('/monitors/:id/input', (req, res) => { const { input } = req.body; @@ -312,8 +606,52 @@ app.post('/monitors/:id/input', (req, res) => { res.json(result); }); +// POST /monitors/:id/brightness - Set brightness +app.post('/monitors/:id/brightness', (req, res) => { + const { percentage } = req.body; + const result = runDDC(['set', req.params.id, 'brightness', `${percentage}%`]); + res.json(result); +}); + +// POST /monitors/:id/contrast - Set contrast +app.post('/monitors/:id/contrast', (req, res) => { + const { percentage } = req.body; + const result = runDDC(['set', req.params.id, 'contrast', `${percentage}%`]); + res.json(result); +}); + +// POST /monitors/:id/vcp - Set raw VCP value +app.post('/monitors/:id/vcp', (req, res) => { + const { code, value } = req.body; + const result = runDDC(['set', req.params.id, code, value]); + res.json(result); +}); + +// POST /monitors/:id/profile - Apply complete profile +app.post('/monitors/:id/profile', (req, res) => { + const { input, brightness, contrast } = req.body; + const results = {}; + + if (input) { + results.input = runDDC(['set', req.params.id, input]); + } + if (brightness) { + results.brightness = runDDC(['set', req.params.id, 'brightness', `${brightness}%`]); + } + if (contrast) { + results.contrast = runDDC(['set', req.params.id, 'contrast', `${contrast}%`]); + } + + res.json({ success: true, results }); +}); + app.listen(3000, () => { console.log('DDCSwitch API running on http://localhost:3000'); + console.log('Endpoints:'); + console.log(' GET /monitors?verbose=true'); + console.log(' GET /monitors/:id/brightness'); + console.log(' POST /monitors/:id/brightness {"percentage": 75}'); + console.log(' POST /monitors/:id/profile {"input": "HDMI1", "brightness": 75, "contrast": 80}'); }); ``` @@ -321,24 +659,45 @@ app.listen(3000, () => { ```batch @echo off -REM check-and-switch.bat - Switch only if not already on target input +REM complete-setup.bat - Set input, brightness, and contrast -for /f "delims=" %%i in ('DDCSwitch get 0 --json') do set JSON_OUTPUT=%%i +echo Setting up monitor configuration... -REM Simple check if contains HDMI1 -echo %JSON_OUTPUT% | find "0x11" >nul -if errorlevel 1 ( - echo Switching to HDMI1... - DDCSwitch set 0 HDMI1 +REM Switch to HDMI1 +for /f "delims=" %%i in ('DDCSwitch set 0 HDMI1 --json') do set INPUT_RESULT=%%i +echo %INPUT_RESULT% | find "\"success\":true" >nul +if not errorlevel 1 ( + echo ✓ Switched to HDMI1 +) else ( + echo ✗ Failed to switch input +) + +REM Set brightness to 75% +for /f "delims=" %%i in ('DDCSwitch set 0 brightness 75%% --json') do set BRIGHTNESS_RESULT=%%i +echo %BRIGHTNESS_RESULT% | find "\"success\":true" >nul +if not errorlevel 1 ( + echo ✓ Set brightness to 75%% +) else ( + echo ✗ Brightness not supported or failed +) + +REM Set contrast to 80% +for /f "delims=" %%i in ('DDCSwitch set 0 contrast 80%% --json') do set CONTRAST_RESULT=%%i +echo %CONTRAST_RESULT% | find "\"success\":true" >nul +if not errorlevel 1 ( + echo ✓ Set contrast to 80%% ) else ( - echo Already on HDMI1 + echo ✗ Contrast not supported or failed ) + +echo Monitor setup complete! +pause ``` ### Rust JSON Example ```rust -// monitor_switcher.rs +// monitor_controller.rs use serde::{Deserialize, Serialize}; use std::process::Command; @@ -354,6 +713,8 @@ struct MonitorInfo { current_input: Option, #[serde(rename = "currentInputCode")] current_input_code: Option, + brightness: Option, + contrast: Option, status: String, } @@ -364,26 +725,103 @@ struct ListResponse { error: Option, } -fn main() { +#[derive(Debug, Deserialize)] +struct SetResponse { + success: bool, + #[serde(rename = "percentageValue")] + percentage_value: Option, + #[serde(rename = "rawValue")] + raw_value: Option, + error: Option, +} + +fn run_ddc_command(args: &[&str]) -> Result> { + let mut cmd_args = args.to_vec(); + cmd_args.push("--json"); + let output = Command::new("DDCSwitch") - .args(&["list", "--json"]) - .output() - .expect("Failed to execute DDCSwitch"); + .args(&cmd_args) + .output()?; + + Ok(String::from_utf8_lossy(&output.stdout).to_string()) +} - let json_str = String::from_utf8_lossy(&output.stdout); - let result: ListResponse = serde_json::from_str(&json_str) - .expect("Failed to parse JSON"); +fn list_monitors(verbose: bool) -> Result> { + let args = if verbose { + vec!["list", "--verbose"] + } else { + vec!["list"] + }; + + let json_str = run_ddc_command(&args)?; + let result: ListResponse = serde_json::from_str(&json_str)?; + Ok(result) +} + +fn set_brightness(monitor_index: u32, percentage: u32) -> Result> { + let args = vec!["set", &monitor_index.to_string(), "brightness", &format!("{}%", percentage)]; + let json_str = run_ddc_command(&args)?; + let result: SetResponse = serde_json::from_str(&json_str)?; + Ok(result) +} + +fn set_contrast(monitor_index: u32, percentage: u32) -> Result> { + let args = vec!["set", &monitor_index.to_string(), "contrast", &format!("{}%", percentage)]; + let json_str = run_ddc_command(&args)?; + let result: SetResponse = serde_json::from_str(&json_str)?; + Ok(result) +} + +fn main() -> Result<(), Box> { + // List monitors with verbose info + let result = list_monitors(true)?; if result.success { if let Some(monitors) = result.monitors { - for monitor in monitors { - println!("{}: {} - {:?}", + println!("Found {} monitors:", monitors.len()); + for monitor in &monitors { + println!("{}: {} - Input: {:?}", monitor.index, monitor.name, monitor.current_input); + println!(" Brightness: {:?}", monitor.brightness); + println!(" Contrast: {:?}", monitor.contrast); + } + + // Set brightness and contrast on first monitor + if !monitors.is_empty() { + let monitor_index = monitors[0].index; + + match set_brightness(monitor_index, 75) { + Ok(response) if response.success => { + println!("✓ Set brightness to 75%"); + } + Ok(response) => { + println!("✗ Failed to set brightness: {:?}", response.error); + } + Err(e) => { + println!("✗ Error setting brightness: {}", e); + } + } + + match set_contrast(monitor_index, 80) { + Ok(response) if response.success => { + println!("✓ Set contrast to 80%"); + } + Ok(response) => { + println!("✗ Failed to set contrast: {:?}", response.error); + } + Err(e) => { + println!("✗ Error setting contrast: {}", e); + } + } } } + } else { + println!("Error: {:?}", result.error); } + + Ok(()) } ``` @@ -392,34 +830,53 @@ fn main() { ### Multi-Monitor Setup Scripts #### Scenario: Work Setup -Switch all monitors to PC inputs: +Switch all monitors to PC inputs with comfortable brightness: ```powershell # work-setup.ps1 Write-Host "Switching to work setup..." -ForegroundColor Cyan DDCSwitch set 0 DP1 +DDCSwitch set 0 brightness 60% +DDCSwitch set 0 contrast 75% DDCSwitch set 1 DP2 -DDCSwitch set 2 HDMI1 +DDCSwitch set 1 brightness 60% +DDCSwitch set 1 contrast 75% Write-Host "Work setup ready!" -ForegroundColor Green ``` #### Scenario: Gaming Setup -Switch monitors to console inputs: +Switch monitors to console inputs with high brightness: ```powershell # gaming-setup.ps1 Write-Host "Switching to gaming setup..." -ForegroundColor Cyan DDCSwitch set 0 HDMI1 # Main monitor to PS5 +DDCSwitch set 0 brightness 90% +DDCSwitch set 0 contrast 85% DDCSwitch set 1 HDMI2 # Secondary to Switch +DDCSwitch set 1 brightness 85% +DDCSwitch set 1 contrast 80% Write-Host "Gaming setup ready!" -ForegroundColor Green ``` +#### Scenario: Movie/Media Setup +Optimize for media consumption: + +```powershell +# media-setup.ps1 +Write-Host "Switching to media setup..." -ForegroundColor Cyan +DDCSwitch set 0 HDMI1 # Media device +DDCSwitch set 0 brightness 40% # Lower brightness for comfortable viewing +DDCSwitch set 0 contrast 90% # High contrast for better blacks +Write-Host "Media setup ready!" -ForegroundColor Green +``` + ### AutoHotkey Integration -Create a comprehensive input switching system: +Create a comprehensive input switching and brightness control system: ```autohotkey -; DDCSwitch AutoHotkey Script +; DDCSwitch AutoHotkey Script with VCP Support ; Place DDCSwitch.exe in C:\Tools\ or update path below ; Global variables @@ -431,6 +888,7 @@ RunDDCSwitch(args) { Run, %DDCSwitchPath% %args%, , Hide } +; Input switching hotkeys ; Ctrl+Alt+1: Switch monitor 0 to HDMI1 ^!1:: RunDDCSwitch("set 0 HDMI1") @@ -449,48 +907,133 @@ RunDDCSwitch(args) { TrayTip, DDCSwitch, Switched to DisplayPort, 1 return -; Ctrl+Alt+W: Work setup (all monitors to PC) +; Brightness control hotkeys +; Ctrl+Alt+Plus: Increase brightness by 10% +^!NumpadAdd:: +^!=:: + RunDDCSwitch("set 0 brightness +10%") + TrayTip, DDCSwitch, Brightness increased, 1 + return + +; Ctrl+Alt+Minus: Decrease brightness by 10% +^!NumpadSub:: +^!-:: + RunDDCSwitch("set 0 brightness -10%") + TrayTip, DDCSwitch, Brightness decreased, 1 + return + +; Brightness presets +; Ctrl+Alt+F1: 25% brightness (night mode) +^!F1:: + RunDDCSwitch("set 0 brightness 25%") + TrayTip, DDCSwitch, Night Mode (25%), 1 + return + +; Ctrl+Alt+F2: 50% brightness (comfortable) +^!F2:: + RunDDCSwitch("set 0 brightness 50%") + TrayTip, DDCSwitch, Comfortable (50%), 1 + return + +; Ctrl+Alt+F3: 75% brightness (bright) +^!F3:: + RunDDCSwitch("set 0 brightness 75%") + TrayTip, DDCSwitch, Bright (75%), 1 + return + +; Ctrl+Alt+F4: 100% brightness (maximum) +^!F4:: + RunDDCSwitch("set 0 brightness 100%") + TrayTip, DDCSwitch, Maximum (100%), 1 + return + +; Profile hotkeys +; Ctrl+Alt+W: Work setup (all monitors to PC with comfortable settings) ^!w:: RunDDCSwitch("set 0 DP1") Sleep 500 + RunDDCSwitch("set 0 brightness 60%") + Sleep 500 + RunDDCSwitch("set 0 contrast 75%") + Sleep 500 RunDDCSwitch("set 1 DP2") TrayTip, DDCSwitch, Work Setup Activated, 1 return -; Ctrl+Alt+G: Gaming setup (all monitors to console) +; Ctrl+Alt+G: Gaming setup (all monitors to console with high brightness) ^!g:: RunDDCSwitch("set 0 HDMI1") Sleep 500 + RunDDCSwitch("set 0 brightness 90%") + Sleep 500 + RunDDCSwitch("set 0 contrast 85%") + Sleep 500 RunDDCSwitch("set 1 HDMI2") TrayTip, DDCSwitch, Gaming Setup Activated, 1 return -; Ctrl+Alt+L: List all monitors +; Ctrl+Alt+M: Media setup (HDMI with low brightness, high contrast) +^!m:: + RunDDCSwitch("set 0 HDMI1") + Sleep 500 + RunDDCSwitch("set 0 brightness 40%") + Sleep 500 + RunDDCSwitch("set 0 contrast 90%") + TrayTip, DDCSwitch, Media Setup Activated, 1 + return + +; Ctrl+Alt+L: List all monitors with verbose info ^!l:: - Run, cmd /k DDCSwitch.exe list + Run, cmd /k DDCSwitch.exe list --verbose + return + +; Ctrl+Alt+I: Show current monitor info +^!i:: + Run, cmd /k "DDCSwitch.exe get 0 && DDCSwitch.exe get 0 brightness && DDCSwitch.exe get 0 contrast && pause" return ``` ### Stream Deck Integration -If you use Elgato Stream Deck, create a "System" action with these commands: +If you use Elgato Stream Deck, create actions for complete monitor control: -**Button 1: PC Input** +**Button 1: PC Mode** ``` Title: PC Mode Command: C:\Tools\DDCSwitch.exe set 0 DP1 +Arguments: && C:\Tools\DDCSwitch.exe set 0 brightness 60% ``` -**Button 2: Console Input** +**Button 2: Console Mode** ``` Title: Console Mode Command: C:\Tools\DDCSwitch.exe set 0 HDMI1 +Arguments: && C:\Tools\DDCSwitch.exe set 0 brightness 90% ``` -**Button 3: List Monitors** +**Button 3: Brightness Low** +``` +Title: 🔅 Low +Command: C:\Tools\DDCSwitch.exe set 0 brightness 25% +``` + +**Button 4: Brightness High** +``` +Title: 🔆 High +Command: C:\Tools\DDCSwitch.exe set 0 brightness 85% +``` + +**Button 5: Monitor Info** ``` Title: Monitor Info -Command: cmd /k C:\Tools\DDCSwitch.exe list +Command: cmd /k C:\Tools\DDCSwitch.exe list --verbose +``` + +**Button 6: Gaming Profile** +``` +Title: 🎮 Gaming +Command: C:\Tools\DDCSwitch.exe set 0 HDMI1 +Arguments: && timeout /t 1 && C:\Tools\DDCSwitch.exe set 0 brightness 90% && C:\Tools\DDCSwitch.exe set 0 contrast 85% ``` ### Task Scheduler Integration @@ -515,51 +1058,212 @@ Same steps, but with trigger at 6:00 PM and arguments: `set 0 HDMI1` Add to your PowerShell profile (`$PROFILE`): ```powershell -# DDCSwitch aliases -function ddc-list { DDCSwitch list } +# DDCSwitch aliases for complete monitor control +function ddc-list { DDCSwitch list --verbose } function ddc-work { DDCSwitch set 0 DP1 + DDCSwitch set 0 brightness 60% + DDCSwitch set 0 contrast 75% DDCSwitch set 1 DP2 Write-Host "✓ Work setup activated" -ForegroundColor Green } function ddc-game { DDCSwitch set 0 HDMI1 + DDCSwitch set 0 brightness 90% + DDCSwitch set 0 contrast 85% DDCSwitch set 1 HDMI2 Write-Host "✓ Gaming setup activated" -ForegroundColor Green } +function ddc-media { + DDCSwitch set 0 HDMI1 + DDCSwitch set 0 brightness 40% + DDCSwitch set 0 contrast 90% + Write-Host "✓ Media setup activated" -ForegroundColor Green +} function ddc-hdmi { DDCSwitch set 0 HDMI1 } function ddc-dp { DDCSwitch set 0 DP1 } +function ddc-bright([int]$level) { DDCSwitch set 0 brightness "$level%" } +function ddc-contrast([int]$level) { DDCSwitch set 0 contrast "$level%" } + +# Brightness shortcuts +function ddc-dim { DDCSwitch set 0 brightness 25% } +function ddc-normal { DDCSwitch set 0 brightness 60% } +function ddc-bright { DDCSwitch set 0 brightness 85% } +function ddc-max { DDCSwitch set 0 brightness 100% } -# Then use: ddc-work, ddc-game, ddc-hdmi, ddc-dp, ddc-list +# Then use: ddc-work, ddc-game, ddc-media, ddc-bright 75, ddc-list ``` -### KVM Switch Replacement +### Complete Monitor Control -Use DDCSwitch as a software KVM (without USB switching): +Use DDCSwitch as a comprehensive monitor management solution: ```powershell -# kvm-to-pc1.ps1 -DDCSwitch set 0 DP1 -DDCSwitch set 1 DP1 -Write-Host "Switched all monitors to PC1" -ForegroundColor Green +# complete-monitor-control.ps1 +param( + [string]$Profile = "work", # work, gaming, media, custom + [int]$Monitor = 0, + [string]$Input, + [int]$Brightness, + [int]$Contrast +) -# kvm-to-pc2.ps1 -DDCSwitch set 0 DP2 -DDCSwitch set 1 DP2 -Write-Host "Switched all monitors to PC2" -ForegroundColor Green +function Apply-Profile { + param($ProfileName, $MonitorIndex) + + switch ($ProfileName.ToLower()) { + "work" { + DDCSwitch set $MonitorIndex DP1 + DDCSwitch set $MonitorIndex brightness 60% + DDCSwitch set $MonitorIndex contrast 75% + Write-Host "✓ Applied work profile" -ForegroundColor Green + } + "gaming" { + DDCSwitch set $MonitorIndex HDMI1 + DDCSwitch set $MonitorIndex brightness 90% + DDCSwitch set $MonitorIndex contrast 85% + Write-Host "✓ Applied gaming profile" -ForegroundColor Green + } + "media" { + DDCSwitch set $MonitorIndex HDMI1 + DDCSwitch set $MonitorIndex brightness 40% + DDCSwitch set $MonitorIndex contrast 90% + Write-Host "✓ Applied media profile" -ForegroundColor Green + } + "custom" { + if ($Input) { DDCSwitch set $MonitorIndex $Input } + if ($Brightness) { DDCSwitch set $MonitorIndex brightness "$Brightness%" } + if ($Contrast) { DDCSwitch set $MonitorIndex contrast "$Contrast%" } + Write-Host "✓ Applied custom settings" -ForegroundColor Green + } + } +} + +Apply-Profile -ProfileName $Profile -MonitorIndex $Monitor + +# Usage examples: +# .\complete-monitor-control.ps1 -Profile work +# .\complete-monitor-control.ps1 -Profile gaming -Monitor 1 +# .\complete-monitor-control.ps1 -Profile custom -Input HDMI2 -Brightness 75 -Contrast 80 ``` -### Testing Monitor Compatibility +### Testing Monitor VCP Support -If your monitor doesn't respond to standard codes, try discovering the correct codes: +Test what VCP features your monitor supports: ```powershell -# Test different input codes +# test-vcp-support.ps1 - Test brightness, contrast, and other VCP features $monitor = 0 -foreach ($code in 0x01, 0x03, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15) { - Write-Host "Testing code 0x$($code.ToString('X2'))..." - DDCSwitch set $monitor "0x$($code.ToString('X2'))" - Start-Sleep -Seconds 3 + +Write-Host "Testing VCP feature support for Monitor $monitor" -ForegroundColor Cyan +Write-Host "=" * 50 + +# Test brightness support +Write-Host "`nTesting Brightness (VCP 0x10)..." -ForegroundColor Yellow +try { + $brightness = DDCSwitch get $monitor brightness 2>$null + if ($brightness -match "Brightness:") { + Write-Host "✓ Brightness supported: $brightness" -ForegroundColor Green + + # Test setting brightness + DDCSwitch set $monitor brightness 50% | Out-Null + Start-Sleep -Seconds 1 + $newBrightness = DDCSwitch get $monitor brightness + Write-Host "✓ Brightness control works: $newBrightness" -ForegroundColor Green + } else { + Write-Host "✗ Brightness not supported" -ForegroundColor Red + } +} catch { + Write-Host "✗ Brightness not supported" -ForegroundColor Red +} + +# Test contrast support +Write-Host "`nTesting Contrast (VCP 0x12)..." -ForegroundColor Yellow +try { + $contrast = DDCSwitch get $monitor contrast 2>$null + if ($contrast -match "Contrast:") { + Write-Host "✓ Contrast supported: $contrast" -ForegroundColor Green + + # Test setting contrast + DDCSwitch set $monitor contrast 75% | Out-Null + Start-Sleep -Seconds 1 + $newContrast = DDCSwitch get $monitor contrast + Write-Host "✓ Contrast control works: $newContrast" -ForegroundColor Green + } else { + Write-Host "✗ Contrast not supported" -ForegroundColor Red + } +} catch { + Write-Host "✗ Contrast not supported" -ForegroundColor Red +} + +# Test raw VCP codes +Write-Host "`nTesting Raw VCP Codes..." -ForegroundColor Yellow +$vcpCodes = @{ + "0x10" = "Brightness" + "0x12" = "Contrast" + "0x14" = "Color Temperature" + "0x16" = "Red Gain" + "0x18" = "Green Gain" + "0x1A" = "Blue Gain" + "0x60" = "Input Source" + "0x62" = "Audio Volume" + "0x6C" = "Red Black Level" + "0x6E" = "Green Black Level" + "0x70" = "Blue Black Level" +} + +foreach ($code in $vcpCodes.Keys) { + try { + $result = DDCSwitch get $monitor $code 2>$null + if ($result -and $result -notmatch "error|failed|not supported") { + Write-Host "✓ VCP $code ($($vcpCodes[$code])): $result" -ForegroundColor Green + } else { + Write-Host "✗ VCP $code ($($vcpCodes[$code])): Not supported" -ForegroundColor Gray + } + } catch { + Write-Host "✗ VCP $code ($($vcpCodes[$code])): Not supported" -ForegroundColor Gray + } +} + +Write-Host "`nVCP Support Test Complete!" -ForegroundColor Cyan +``` + +### Finding Optimal Settings + +Find the best brightness and contrast settings for different scenarios: + +```powershell +# find-optimal-settings.ps1 +Write-Host "Monitor Calibration Helper" -ForegroundColor Cyan +Write-Host "This script will cycle through different brightness/contrast combinations" +Write-Host "Press any key after each setting to continue, or Ctrl+C to stop" +Write-Host "" + +$monitor = 0 +$brightnessLevels = @(25, 40, 50, 60, 75, 85, 100) +$contrastLevels = @(60, 70, 75, 80, 85, 90, 95) + +foreach ($brightness in $brightnessLevels) { + foreach ($contrast in $contrastLevels) { + Write-Host "Setting: Brightness $brightness%, Contrast $contrast%" -ForegroundColor Yellow + + DDCSwitch set $monitor brightness "$brightness%" | Out-Null + Start-Sleep -Milliseconds 500 + DDCSwitch set $monitor contrast "$contrast%" | Out-Null + + Write-Host "How does this look? (Press Enter to continue, 'q' to quit, 's' to save this setting)" -ForegroundColor Green + $input = Read-Host + + if ($input -eq 'q') { + Write-Host "Calibration stopped." -ForegroundColor Red + break + } elseif ($input -eq 's') { + Write-Host "Saved setting: Brightness $brightness%, Contrast $contrast%" -ForegroundColor Cyan + Write-Host "Command to reproduce: DDCSwitch set $monitor brightness $brightness%; DDCSwitch set $monitor contrast $contrast%" -ForegroundColor White + Read-Host "Press Enter to continue or Ctrl+C to stop" + } + } + if ($input -eq 'q') { break } } ``` @@ -700,43 +1404,83 @@ Make them double-clickable for quick access! ## Common Patterns -### Pattern 1: Toggle Between Two Inputs +### Pattern 1: Toggle Between Settings ```powershell -# toggle-input.ps1 -$current = (DDCSwitch get 0 | Select-String -Pattern "0x([0-9A-F]{2})").Matches[0].Groups[1].Value -$currentDecimal = [Convert]::ToInt32($current, 16) +# toggle-brightness.ps1 - Toggle between low/medium/high brightness +$current = DDCSwitch get 0 brightness --json | ConvertFrom-Json -if ($currentDecimal -eq 0x11) { # Currently HDMI1 - DDCSwitch set 0 DP1 - Write-Host "Switched to DisplayPort" +if ($current.success) { + $currentPercent = $current.percentageValue + + # Cycle through 25% → 50% → 75% → 100% → 25% + $newBrightness = switch ($currentPercent) { + {$_ -le 25} { 50 } + {$_ -le 50} { 75 } + {$_ -le 75} { 100 } + default { 25 } + } + + DDCSwitch set 0 brightness "$newBrightness%" + Write-Host "Brightness: $currentPercent% → $newBrightness%" -ForegroundColor Green } else { - DDCSwitch set 0 HDMI1 - Write-Host "Switched to HDMI1" + Write-Host "Brightness control not supported" -ForegroundColor Red } ``` -### Pattern 2: Check Before Switch +### Pattern 2: Check Before Set ```powershell -# smart-switch.ps1 -param([string]$Input = "HDMI1") +# smart-profile-switch.ps1 +param([string]$Profile = "work") + +# Get current settings +$inputResult = DDCSwitch get 0 --json | ConvertFrom-Json +$brightnessResult = DDCSwitch get 0 brightness --json | ConvertFrom-Json -$monitors = DDCSwitch list -if ($monitors -match "No DDC/CI") { - Write-Error "No compatible monitors found" +if (-not $inputResult.success) { + Write-Error "Monitor not accessible" exit 1 } -DDCSwitch set 0 $Input -Write-Host "Successfully switched to $Input" -ForegroundColor Green +# Define profiles +$profiles = @{ + "work" = @{ input = "DP1"; brightness = 60; contrast = 75 } + "gaming" = @{ input = "HDMI1"; brightness = 90; contrast = 85 } + "media" = @{ input = "HDMI1"; brightness = 40; contrast = 90 } +} + +if (-not $profiles.ContainsKey($Profile)) { + Write-Error "Unknown profile: $Profile. Available: work, gaming, media" + exit 1 +} + +$targetProfile = $profiles[$Profile] + +# Apply settings only if different +if ($inputResult.currentInputCode -ne $targetProfile.input) { + DDCSwitch set 0 $targetProfile.input + Write-Host "✓ Input: $($targetProfile.input)" -ForegroundColor Green +} + +if ($brightnessResult.success -and $brightnessResult.percentageValue -ne $targetProfile.brightness) { + DDCSwitch set 0 brightness "$($targetProfile.brightness)%" + Write-Host "✓ Brightness: $($targetProfile.brightness)%" -ForegroundColor Green +} + +DDCSwitch set 0 contrast "$($targetProfile.contrast)%" +Write-Host "✓ Profile '$Profile' applied" -ForegroundColor Cyan ``` -### Pattern 3: Switch All Monitors to Same Input +### Pattern 3: Sync All Monitors ```powershell -# sync-all.ps1 - Using JSON for reliable monitor enumeration -param([string]$Input = "HDMI1") +# sync-all-monitors.ps1 - Apply same settings to all monitors +param( + [string]$Input = "HDMI1", + [int]$Brightness = 75, + [int]$Contrast = 80 +) $result = DDCSwitch list --json | ConvertFrom-Json @@ -748,30 +1492,37 @@ if (-not $result.success) { $okMonitors = $result.monitors | Where-Object { $_.status -eq "ok" } foreach ($monitor in $okMonitors) { - Write-Host "Switching monitor $($monitor.index) ($($monitor.name)) to $Input..." - DDCSwitch set $monitor.index $Input - Start-Sleep -Milliseconds 500 -} - -Write-Host "Switched $($okMonitors.Count) monitors to $Input" -ForegroundColor Green -``` - -**Alternative (without JSON):** -```powershell -# sync-all-legacy.ps1 -param([string]$Input = "HDMI1") - -$output = DDCSwitch list -$monitorCount = ($output | Select-String -Pattern "^\│ \d+" -AllMatches).Matches.Count - -for ($i = 0; $i -lt $monitorCount; $i++) { - Write-Host "Switching monitor $i to $Input..." - DDCSwitch set $i $Input - Start-Sleep -Milliseconds 500 + Write-Host "Configuring monitor $($monitor.index) ($($monitor.name))..." -ForegroundColor Cyan + + # Set input + $inputResult = DDCSwitch set $monitor.index $Input --json | ConvertFrom-Json + if ($inputResult.success) { + Write-Host " ✓ Input: $Input" -ForegroundColor Green + } else { + Write-Host " ✗ Input failed: $($inputResult.error)" -ForegroundColor Red + } + + # Set brightness + $brightnessResult = DDCSwitch set $monitor.index brightness "$Brightness%" --json | ConvertFrom-Json + if ($brightnessResult.success) { + Write-Host " ✓ Brightness: $Brightness%" -ForegroundColor Green + } else { + Write-Host " ✗ Brightness not supported" -ForegroundColor Yellow + } + + # Set contrast + $contrastResult = DDCSwitch set $monitor.index contrast "$Contrast%" --json | ConvertFrom-Json + if ($contrastResult.success) { + Write-Host " ✓ Contrast: $Contrast%" -ForegroundColor Green + } else { + Write-Host " ✗ Contrast not supported" -ForegroundColor Yellow + } + + Start-Sleep -Milliseconds 500 # Prevent DDC/CI overload } -Write-Host "All monitors switched to $Input" -ForegroundColor Green +Write-Host "Synchronized $($okMonitors.Count) monitors" -ForegroundColor Cyan ``` -Happy switching! +Happy switching and brightness controlling! diff --git a/README.md b/README.md index 5b00607..abd1128 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![.NET](https://img.shields.io/badge/.NET-10.0-512BD4)](https://dotnet.microsoft.com/) [![JSON Output](https://img.shields.io/badge/JSON-Output%20Support-green)](https://github.com/markdwags/DDCSwitch#json-output-for-automation) -A Windows command-line utility to control monitor input sources via DDC/CI (Display Data Channel Command Interface). Switch between HDMI, DisplayPort, DVI, and VGA inputs without touching physical buttons. +A Windows command-line utility to control monitor settings via DDC/CI (Display Data Channel Command Interface). Control input sources, brightness, contrast, and other VCP features without touching physical buttons. 📚 **[Examples](EXAMPLES.md)** | 📝 **[Changelog](CHANGELOG.md)** @@ -14,6 +14,9 @@ A Windows command-line utility to control monitor input sources via DDC/CI (Disp - 🖥️ **List all DDC/CI capable monitors** with their current input sources - 🔄 **Switch monitor inputs** programmatically (HDMI, DisplayPort, DVI, VGA, etc.) +- 🔆 **Control brightness and contrast** with percentage values (0-100%) +- 🎛️ **Raw VCP access** for advanced users to control any monitor feature +- 🔍 **VCP scanning** to discover all supported monitor features - 🎯 **Simple CLI interface** perfect for scripts, shortcuts, and hotkeys - 📊 **JSON output support** - Machine-readable output for automation and integration - ⚡ **Fast and lightweight** - NativeAOT compiled for instant startup @@ -65,19 +68,52 @@ Example output: ╰───────┴─────────────────────┴──────────────┴───────────────────────────┴────────╯ ``` +#### Verbose Listing + +Add `--verbose` to include brightness and contrast information: + +```powershell +DDCSwitch list --verbose +``` + +Example output: +``` +╭───────┬─────────────────────┬──────────────┬───────────────────────────┬────────┬────────────┬──────────╮ +│ Index │ Monitor Name │ Device │ Current Input │ Status │ Brightness │ Contrast │ +├───────┼─────────────────────┼──────────────┼───────────────────────────┼────────┼────────────┼──────────┤ +│ 0 │ Generic PnP Monitor │ \\.\DISPLAY2 │ HDMI1 (0x11) │ OK │ 75% │ 80% │ +│ 1* │ VG270U P │ \\.\DISPLAY1 │ DisplayPort1 (DP1) (0x0F) │ OK │ N/A │ N/A │ +╰───────┴─────────────────────┴──────────────┴───────────────────────────┴────────┴────────────┴──────────╯ +``` + Add `--json` for machine-readable output (see [EXAMPLES.md](EXAMPLES.md) for automation examples). -### Get Current Input +### Get Current Settings -Get the current input source for a specific monitor: +Get all VCP features for a specific monitor: ```powershell DDCSwitch get 0 ``` -Output: `Monitor: Generic PnP Monitor (\\.\DISPLAY2)` / `Current Input: HDMI1 (0x11)` +This will scan and display all supported VCP features for monitor 0, showing their names, access types, current values, and maximum values. -### Set Input Source +Get a specific feature: + +```powershell +# Get current input source +DDCSwitch get 0 input + +# Get brightness as percentage +DDCSwitch get 0 brightness + +# Get contrast as percentage +DDCSwitch get 0 contrast +``` + +Output: `Monitor: Generic PnP Monitor` / `Brightness: 75% (120/160)` + +### Set Monitor Settings Switch a monitor to a different input: @@ -89,10 +125,43 @@ DDCSwitch set 0 HDMI1 DDCSwitch set "LG ULTRAGEAR" HDMI2 ``` -Output: `✓ Successfully switched Generic PnP Monitor to HDMI1` +Set brightness or contrast with percentage values: + +```powershell +# Set brightness to 75% +DDCSwitch set 0 brightness 75% + +# Set contrast to 80% +DDCSwitch set 0 contrast 80% +``` + +Output: `✓ Successfully set brightness to 75% (120/160)` -### Supported Input Names +### Raw VCP Access +For advanced users, access any VCP feature by code: + +```powershell +# Get raw VCP value (e.g., VCP code 0x10 for brightness) +DDCSwitch get 0 0x10 + +# Set raw VCP value +DDCSwitch set 0 0x10 120 +``` + +### VCP Feature Scanning + +Discover all supported VCP features on a monitor: + +```powershell +DDCSwitch list --verbose +``` + +This scans all VCP codes (0x00-0xFF) and displays supported features with their current values, maximum values, and access types (read-only, write-only, read-write). + +### Supported Features + +#### Input Sources - **HDMI**: `HDMI1`, `HDMI2` - **DisplayPort**: `DP1`, `DP2`, `DisplayPort1`, `DisplayPort2` - **DVI**: `DVI1`, `DVI2` @@ -100,6 +169,16 @@ Output: `✓ Successfully switched Generic PnP Monitor to HDMI1` - **Other**: `SVideo1`, `SVideo2`, `Tuner1`, `ComponentVideo1`, etc. - **Custom codes**: Use hex values like `0x11` for manufacturer-specific inputs +#### Common VCP Features +- **Brightness**: `brightness` (VCP 0x10) - accepts percentage values (0-100%) +- **Contrast**: `contrast` (VCP 0x12) - accepts percentage values (0-100%) +- **Input Source**: `input` (VCP 0x60) - existing functionality maintained + +#### Raw VCP Codes +- Any VCP code from `0x00` to `0xFF` +- Values must be within the monitor's supported range +- Use hex format: `0x10`, `0x12`, etc. + ## Use Cases ### Quick Examples @@ -110,13 +189,27 @@ DDCSwitch set 0 HDMI1 DDCSwitch set 1 DP1 ``` +**Control brightness and contrast:** +```powershell +DDCSwitch set 0 brightness 75% +DDCSwitch set 0 contrast 80% +DDCSwitch get 0 brightness +``` + +**Raw VCP access:** +```powershell +DDCSwitch get 0 0x10 # Get brightness (raw) +DDCSwitch set 0 0x10 120 # Set brightness (raw value) +``` + **Desktop shortcut:** -Create a shortcut with target: `C:\Path\To\DDCSwitch.exe set 0 HDMI1` +Create a shortcut with target: `C:\Path\To\DDCSwitch.exe set 0 brightness 50%` **AutoHotkey:** ```autohotkey -^!h::Run, DDCSwitch.exe set 0 HDMI1 ; Ctrl+Alt+H for HDMI1 -^!d::Run, DDCSwitch.exe set 0 DP1 ; Ctrl+Alt+D for DisplayPort +^!h::Run, DDCSwitch.exe set 0 HDMI1 ; Ctrl+Alt+H for HDMI1 +^!d::Run, DDCSwitch.exe set 0 DP1 ; Ctrl+Alt+D for DisplayPort +^!b::Run, DDCSwitch.exe set 0 brightness 75% ; Ctrl+Alt+B for 75% brightness ``` ### JSON Output for Automation @@ -171,11 +264,17 @@ If you need to verify DDC/CI values or troubleshoot monitor-specific issues, try ## Technical Details -DDCSwitch uses the Windows DXVA2 API to communicate with monitors via DDC/CI protocol. It reads/writes VCP (Virtual Control Panel) feature 0x60 (Input Source) following the MCCS specification. +DDCSwitch uses the Windows DXVA2 API to communicate with monitors via DDC/CI protocol. It reads/writes VCP (Virtual Control Panel) features following the MCCS specification. -**Common VCP Input Codes:** +**Common VCP Codes:** +- `0x10` Brightness, `0x12` Contrast, `0x60` Input Source - `0x01` VGA, `0x03` DVI, `0x0F` DisplayPort 1, `0x10` DisplayPort 2, `0x11` HDMI 1, `0x12` HDMI 2 +**VCP Feature Types:** +- **Read-Write**: Can get and set values (brightness, contrast, input) +- **Read-Only**: Can only read current value (some monitor info) +- **Write-Only**: Can only set values (some calibration features) + **NativeAOT Compatible:** Uses source generators for JSON, `DllImport` for P/Invoke, and zero reflection for reliable AOT compilation. ## Why Windows Only? From 5bc61d42f8f9e57e23a4fcbccc88beb42a9d46a9 Mon Sep 17 00:00:00 2001 From: Quick <577652+markdwags@users.noreply.github.com> Date: Wed, 7 Jan 2026 20:17:28 -0600 Subject: [PATCH 03/10] add full DDC/CI support for all compliant monitors; enhance error handling, improve documentation, and optimize input switching performance --- CHANGELOG.md | 8 + DDCSwitch/FeatureResolver.cs | 112 ++- DDCSwitch/Monitor.cs | 4 + DDCSwitch/Program.cs | 86 +- DDCSwitch/Properties/launchSettings.json | 29 + DDCSwitch/VcpFeature.cs | 989 ++++++++++++++++++++++- EXAMPLES.md | 202 ++++- README.md | 68 +- 8 files changed, 1454 insertions(+), 44 deletions(-) create mode 100644 DDCSwitch/Properties/launchSettings.json diff --git a/CHANGELOG.md b/CHANGELOG.md index d0f9366..60827e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to DDCSwitch will be documented in this file. +## [1.0.2] - 2026-01-07 + +### Added +- Full support for all DDC/CI compliant monitors +- Improved error handling for unsupported monitors +- Enhanced documentation with additional examples +- Optimized performance for input switching operations + ## [1.0.1] - 2026-01-07 ### Added diff --git a/DDCSwitch/FeatureResolver.cs b/DDCSwitch/FeatureResolver.cs index dd5aab2..ffb3f51 100644 --- a/DDCSwitch/FeatureResolver.cs +++ b/DDCSwitch/FeatureResolver.cs @@ -7,12 +7,45 @@ namespace DDCSwitch; /// public static class FeatureResolver { - private static readonly Dictionary FeatureMap = new(StringComparer.OrdinalIgnoreCase) + private static readonly Dictionary FeatureMap = BuildFeatureMap(); + private static readonly Dictionary CodeMap = BuildCodeMap(); + + /// + /// Builds the feature name to VcpFeature mapping including aliases + /// + private static Dictionary BuildFeatureMap() + { + var map = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var feature in VcpFeature.AllFeatures) + { + // Add primary name + map[feature.Name] = feature; + + // Add aliases + foreach (var alias in feature.Aliases) + { + map[alias] = feature; + } + } + + return map; + } + + /// + /// Builds the VCP code to VcpFeature mapping + /// + private static Dictionary BuildCodeMap() { - { "brightness", VcpFeature.Brightness }, - { "contrast", VcpFeature.Contrast }, - { "input", VcpFeature.InputSource } - }; + var map = new Dictionary(); + + foreach (var feature in VcpFeature.AllFeatures) + { + map[feature.Code] = feature; + } + + return map; + } /// /// Attempts to resolve a feature name or VCP code to a VcpFeature @@ -29,7 +62,7 @@ public static bool TryResolveFeature(string input, out VcpFeature? feature) return false; } - // First try to resolve as a known feature name + // First try to resolve as a known feature name or alias if (FeatureMap.TryGetValue(input.Trim(), out feature)) { return true; @@ -38,14 +71,75 @@ public static bool TryResolveFeature(string input, out VcpFeature? feature) // Try to parse as a VCP code if (TryParseVcpCode(input, out byte vcpCode)) { + // Check if we have a predefined feature for this code + if (CodeMap.TryGetValue(vcpCode, out feature)) + { + return true; + } + // Create a generic VCP feature for unknown codes - feature = new VcpFeature(vcpCode, $"VCP_{vcpCode:X2}", VcpFeatureType.ReadWrite, false); + feature = new VcpFeature(vcpCode, $"VCP_{vcpCode:X2}", $"Unknown VCP feature 0x{vcpCode:X2}", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); return true; } return false; } + /// + /// Gets VCP features by category + /// + /// The category to filter by + /// Array of VCP features in the specified category + public static VcpFeature[] GetFeaturesByCategory(VcpFeatureCategory category) + { + return VcpFeature.AllFeatures.Where(f => f.Category == category).ToArray(); + } + + /// + /// Searches for VCP features by partial name matching + /// + /// Partial name to search for + /// Array of VCP features matching the partial name + public static VcpFeature[] SearchFeatures(string partialName) + { + if (string.IsNullOrWhiteSpace(partialName)) + { + return Array.Empty(); + } + + var searchTerm = partialName.Trim(); + return VcpFeature.AllFeatures + .Where(f => f.Name.Contains(searchTerm, StringComparison.OrdinalIgnoreCase) || + f.Description.Contains(searchTerm, StringComparison.OrdinalIgnoreCase) || + f.Aliases.Any(alias => alias.Contains(searchTerm, StringComparison.OrdinalIgnoreCase))) + .ToArray(); + } + + /// + /// Gets a VCP feature by its code + /// + /// VCP code + /// VCP feature if found, otherwise a generic feature for the code + public static VcpFeature GetFeatureByCode(byte code) + { + if (CodeMap.TryGetValue(code, out var feature)) + { + return feature; + } + + // Return a generic feature for unknown codes + return new VcpFeature(code, $"VCP_{code:X2}", $"Unknown VCP feature 0x{code:X2}", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + } + + /// + /// Gets all available VCP feature categories + /// + /// Array of category names + public static string[] GetAllCategories() + { + return Enum.GetNames(); + } + /// /// Converts a percentage value (0-100) to raw VCP value based on the maximum value /// @@ -269,7 +363,7 @@ public static string GetRawValueValidationError(uint value, uint maxValue, strin } /// - /// Gets all known feature names + /// Gets all known feature names including aliases /// /// Collection of known feature names public static IEnumerable GetKnownFeatureNames() @@ -283,6 +377,6 @@ public static IEnumerable GetKnownFeatureNames() /// Collection of predefined VCP features public static IEnumerable GetPredefinedFeatures() { - return FeatureMap.Values; + return VcpFeature.AllFeatures; } } \ No newline at end of file diff --git a/DDCSwitch/Monitor.cs b/DDCSwitch/Monitor.cs index b09cd19..00dab9a 100644 --- a/DDCSwitch/Monitor.cs +++ b/DDCSwitch/Monitor.cs @@ -174,7 +174,9 @@ public Dictionary ScanVcpFeatures() features[vcpCode] = new VcpFeatureInfo( vcpCode, name, + predefined?.Description ?? $"VCP feature {name}", type, + predefined?.Category ?? VcpFeatureCategory.Miscellaneous, currentValue, maxValue, true @@ -190,7 +192,9 @@ public Dictionary ScanVcpFeatures() features[vcpCode] = new VcpFeatureInfo( vcpCode, name, + predefined?.Description ?? $"VCP feature {name}", VcpFeatureType.ReadWrite, + predefined?.Category ?? VcpFeatureCategory.Miscellaneous, 0, 0, false diff --git a/DDCSwitch/Program.cs b/DDCSwitch/Program.cs index 5b2cf07..39c675e 100644 --- a/DDCSwitch/Program.cs +++ b/DDCSwitch/Program.cs @@ -135,17 +135,22 @@ private static int InvalidCommand(string command, bool jsonOutput) private static int ListMonitors(bool jsonOutput, bool verboseOutput = false, bool scanOutput = false) { + List monitors; + if (!jsonOutput) { + monitors = null!; AnsiConsole.Status() .Start(scanOutput ? "Scanning VCP features..." : "Enumerating monitors...", ctx => { ctx.Spinner(Spinner.Known.Dots); - Thread.Sleep(scanOutput ? 500 : 100); // Longer pause for VCP scanning + monitors = MonitorController.EnumerateMonitors(); }); } - - var monitors = MonitorController.EnumerateMonitors(); + else + { + monitors = MonitorController.EnumerateMonitors(); + } if (monitors.Count == 0) { @@ -448,7 +453,24 @@ private static int GetCurrentInput(string[] args, bool jsonOutput) } // Use generic VCP method for all features with enhanced error handling - bool success = monitor.TryGetVcpFeature(feature!.Code, out uint current, out uint max, out int errorCode); + bool success = false; + uint current = 0; + uint max = 0; + int errorCode = 0; + + if (!jsonOutput) + { + AnsiConsole.Status() + .Start($"Reading {feature!.Name} from {monitor.Name}...", ctx => + { + ctx.Spinner(Spinner.Known.Dots); + success = monitor.TryGetVcpFeature(feature.Code, out current, out max, out errorCode); + }); + } + else + { + success = monitor.TryGetVcpFeature(feature!.Code, out current, out max, out errorCode); + } if (!success) { @@ -496,20 +518,9 @@ private static int GetCurrentInput(string[] args, bool jsonOutput) { var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - if (feature.Code == InputSource.VcpInputSource) - { - // Use generic VCP response for input as well - uint? percentageValue = feature.SupportsPercentage ? FeatureResolver.ConvertRawToPercentage(current, max) : null; - var result = new GetVcpResponse(true, monitorRef, feature.Name, current, max, percentageValue); - Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.GetVcpResponse)); - } - else - { - // Use generic VCP response for other features - uint? percentageValue = feature.SupportsPercentage ? FeatureResolver.ConvertRawToPercentage(current, max) : null; - var result = new GetVcpResponse(true, monitorRef, feature.Name, current, max, percentageValue); - Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.GetVcpResponse)); - } + uint? percentageValue = feature.SupportsPercentage ? FeatureResolver.ConvertRawToPercentage(current, max) : null; + var result = new GetVcpResponse(true, monitorRef, feature.Name, current, max, percentageValue); + Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.GetVcpResponse)); } else { @@ -949,7 +960,14 @@ private static int HandleVcpScan(List monitors, bool jsonOutp { AnsiConsole.MarkupLine($"\n[bold blue]Monitor {monitor.Index}: {monitor.Name}[/] ({monitor.DeviceName})"); - var features = monitor.ScanVcpFeatures(); + Dictionary features = null!; + AnsiConsole.Status() + .Start($"Scanning VCP features for {monitor.Name}...", ctx => + { + ctx.Spinner(Spinner.Known.Dots); + features = monitor.ScanVcpFeatures(); + }); + var supportedFeatures = features.Values .Where(f => f.IsSupported) .OrderBy(f => f.Code) @@ -1015,17 +1033,22 @@ private static int HandleVcpScan(List monitors, bool jsonOutp private static int HandleVcpScanForMonitor(string monitorIdentifier, bool jsonOutput) { + List monitors; + if (!jsonOutput) { + monitors = null!; AnsiConsole.Status() - .Start("Scanning VCP features...", ctx => + .Start("Enumerating monitors...", ctx => { ctx.Spinner(Spinner.Known.Dots); - Thread.Sleep(500); // Pause for VCP scanning + monitors = MonitorController.EnumerateMonitors(); }); } - - var monitors = MonitorController.EnumerateMonitors(); + else + { + monitors = MonitorController.EnumerateMonitors(); + } if (monitors.Count == 0) { @@ -1069,7 +1092,22 @@ private static int HandleVcpScanForMonitor(string monitorIdentifier, bool jsonOu try { var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - var features = monitor.ScanVcpFeatures(); + Dictionary features; + + if (!jsonOutput) + { + features = null!; + AnsiConsole.Status() + .Start($"Scanning VCP features for {monitor.Name}...", ctx => + { + ctx.Spinner(Spinner.Known.Dots); + features = monitor.ScanVcpFeatures(); + }); + } + else + { + features = monitor.ScanVcpFeatures(); + } // Filter only supported features for cleaner output var supportedFeatures = features.Values diff --git a/DDCSwitch/Properties/launchSettings.json b/DDCSwitch/Properties/launchSettings.json new file mode 100644 index 0000000..7d604ca --- /dev/null +++ b/DDCSwitch/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "profiles": { + "DDCSwitch (get 0)": { + "commandName": "Project", + "commandLineArgs": "get 0" + }, + "DDCSwitch (list)": { + "commandName": "Project", + "commandLineArgs": "list" + }, + "DDCSwitch (list --verbose)": { + "commandName": "Project", + "commandLineArgs": "list --verbose" + }, + "DDCSwitch (set)": { + "commandName": "Project", + "commandLineArgs": "set 0 input HDMI1" + }, + "DDCSwitch (version)": { + "commandName": "Project", + "commandLineArgs": "version" + }, + "DDCSwitch (help)": { + "commandName": "Project", + "commandLineArgs": "help" + } + } +} + diff --git a/DDCSwitch/VcpFeature.cs b/DDCSwitch/VcpFeature.cs index 3a4cd4a..b82093b 100644 --- a/DDCSwitch/VcpFeature.cs +++ b/DDCSwitch/VcpFeature.cs @@ -21,6 +21,42 @@ public enum VcpFeatureType ReadWrite } +/// +/// Defines the functional category of a VCP feature +/// +public enum VcpFeatureCategory +{ + /// + /// Image adjustment features (brightness, contrast, sharpness, etc.) + /// + ImageAdjustment, + + /// + /// Color control features (RGB gains, color temperature, etc.) + /// + ColorControl, + + /// + /// Geometry features (position, size, pincushion - mainly CRT) + /// + Geometry, + + /// + /// Audio features (volume, mute, balance, etc.) + /// + Audio, + + /// + /// Preset and factory features (restore defaults, degauss, etc.) + /// + Preset, + + /// + /// Miscellaneous features that don't fit other categories + /// + Miscellaneous +} + /// /// Represents a VCP (Virtual Control Panel) feature with its properties /// @@ -28,31 +64,974 @@ public class VcpFeature { public byte Code { get; } public string Name { get; } + public string Description { get; } public VcpFeatureType Type { get; } + public VcpFeatureCategory Category { get; } public bool SupportsPercentage { get; } + public string[] Aliases { get; } - public VcpFeature(byte code, string name, VcpFeatureType type, bool supportsPercentage) + public VcpFeature(byte code, string name, string description, VcpFeatureType type, VcpFeatureCategory category, bool supportsPercentage, params string[] aliases) { Code = code; Name = name; + Description = description; Type = type; + Category = category; SupportsPercentage = supportsPercentage; + Aliases = aliases ?? Array.Empty(); } + // Legacy constructor for backward compatibility + public VcpFeature(byte code, string name, VcpFeatureType type, bool supportsPercentage) + : this(code, name, $"VCP feature {name} (0x{code:X2})", type, VcpFeatureCategory.Miscellaneous, supportsPercentage) + { + } + + // ===== IMAGE ADJUSTMENT FEATURES ===== + /// /// Brightness control (VCP 0x10) /// - public static VcpFeature Brightness => new(0x10, "brightness", VcpFeatureType.ReadWrite, true); + public static VcpFeature Brightness => new(0x10, "brightness", "Brightness control", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true, "bright"); + + /// + /// Flesh tone enhancement (VCP 0x11) + /// + public static VcpFeature FleshToneEnhancement => new(0x11, "flesh-tone-enhancement", "Flesh tone enhancement", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); /// /// Contrast control (VCP 0x12) /// - public static VcpFeature Contrast => new(0x12, "contrast", VcpFeatureType.ReadWrite, true); + public static VcpFeature Contrast => new(0x12, "contrast", "Contrast control", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Backlight control (VCP 0x13) + /// + public static VcpFeature Backlight => new(0x13, "backlight", "Backlight control", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Select color preset (VCP 0x14) + /// + public static VcpFeature SelectColorPreset => new(0x14, "select-color-preset", "Select color preset", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false, "color-preset"); + + /// + /// Focus (VCP 0x1C) + /// + public static VcpFeature Focus => new(0x1C, "focus", "Focus", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Auto setup (VCP 0x1E) + /// + public static VcpFeature AutoSetup => new(0x1E, "auto-setup", "Auto setup", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Auto color setup (VCP 0x1F) + /// + public static VcpFeature AutoColorSetup => new(0x1F, "auto-color-setup", "Auto color setup", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Gray scale expansion (VCP 0x2E) + /// + public static VcpFeature GrayScaleExpansion => new(0x2E, "gray-scale-expansion", "Gray scale expansion", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Horizontal Moire (VCP 0x56) + /// + public static VcpFeature HorizontalMoire => new(0x56, "horizontal-moire", "Horizontal Moire", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Vertical Moire (VCP 0x58) + /// + public static VcpFeature VerticalMoire => new(0x58, "vertical-moire", "Vertical Moire", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); /// /// Input source selection (VCP 0x60) /// - public static VcpFeature InputSource => new(0x60, "input", VcpFeatureType.ReadWrite, false); + public static VcpFeature InputSource => new(0x60, "input", "Input source selection", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false, "source"); + + /// + /// Adjust Focal Plane (VCP 0x7A) + /// + public static VcpFeature AdjustFocalPlane => new(0x7A, "adjust-focal-plane", "Adjust Focal Plane", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Adjust Zoom (VCP 0x7C) + /// + public static VcpFeature AdjustZoom => new(0x7C, "adjust-zoom", "Adjust Zoom", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Trapezoid (VCP 0x7E) + /// + public static VcpFeature Trapezoid => new(0x7E, "trapezoid", "Trapezoid", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Keystone (VCP 0x80) + /// + public static VcpFeature Keystone => new(0x80, "keystone", "Keystone", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Horizontal Mirror (Flip) (VCP 0x82) + /// + public static VcpFeature HorizontalMirror => new(0x82, "horizontal-mirror", "Horizontal Mirror (Flip)", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Vertical Mirror (Flip) (VCP 0x84) + /// + public static VcpFeature VerticalMirror => new(0x84, "vertical-mirror", "Vertical Mirror (Flip)", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Display Scaling (VCP 0x86) + /// + public static VcpFeature DisplayScaling => new(0x86, "display-scaling", "Display Scaling", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Sharpness control (VCP 0x87) + /// + public static VcpFeature Sharpness => new(0x87, "sharpness", "Sharpness control", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true, "sharp"); + + /// + /// Velocity Scan Modulation (VCP 0x88) + /// + public static VcpFeature VelocityScanModulation => new(0x88, "velocity-scan-modulation", "Velocity Scan Modulation", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// TV Sharpness (VCP 0x8C) + /// + public static VcpFeature TVSharpness => new(0x8C, "tv-sharpness", "TV Sharpness", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// TV Contrast (VCP 0x8E) + /// + public static VcpFeature TVContrast => new(0x8E, "tv-contrast", "TV Contrast", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// TV Black level/Luminesence (VCP 0x92) + /// + public static VcpFeature TVBlackLevel => new(0x92, "tv-black-level", "TV Black level/Luminesence", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Window background (VCP 0x9A) + /// + public static VcpFeature WindowBackground => new(0x9A, "window-background", "Window background", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Auto setup on/off (VCP 0xA2) + /// + public static VcpFeature AutoSetupOnOff => new(0xA2, "auto-setup-on-off", "Auto setup on/off", VcpFeatureType.WriteOnly, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Window mask control (VCP 0xA4) + /// + public static VcpFeature WindowMaskControl => new(0xA4, "window-mask-control", "Window mask control", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Change the selected window (VCP 0xA5) + /// + public static VcpFeature ChangeSelectedWindow => new(0xA5, "change-selected-window", "Change the selected window", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Screen Orientation (VCP 0xAA) + /// + public static VcpFeature ScreenOrientation => new(0xAA, "screen-orientation", "Screen Orientation", VcpFeatureType.ReadOnly, VcpFeatureCategory.ImageAdjustment, false, "orientation"); + + /// + /// Stereo video mode (VCP 0xD4) + /// + public static VcpFeature StereoVideoMode => new(0xD4, "stereo-video-mode", "Stereo video mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Scan mode (VCP 0xDA) + /// + public static VcpFeature ScanMode => new(0xDA, "scan-mode", "Scan mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Image Mode (VCP 0xDB) + /// + public static VcpFeature ImageMode => new(0xDB, "image-mode", "Image Mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false, "mode"); + + /// + /// Display Mode (VCP 0xDC) + /// + public static VcpFeature DisplayMode => new(0xDC, "display-mode", "Display Mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + // ===== COLOR CONTROL FEATURES ===== + + /// + /// Red video gain (VCP 0x16) + /// + public static VcpFeature RedGain => new(0x16, "red-gain", "Video gain: Red", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true, "red"); + + /// + /// User color vision compensation (VCP 0x17) + /// + public static VcpFeature UserColorVisionCompensation => new(0x17, "user-color-vision-compensation", "User color vision compensation", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Green video gain (VCP 0x18) + /// + public static VcpFeature GreenGain => new(0x18, "green-gain", "Video gain: Green", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true, "green"); + + /// + /// Blue video gain (VCP 0x1A) + /// + public static VcpFeature BlueGain => new(0x1A, "blue-gain", "Video gain: Blue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true, "blue"); + + /// + /// 6 axis saturation: Red (VCP 0x59) + /// + public static VcpFeature SixAxisSaturationRed => new(0x59, "6-axis-saturation-red", "6 axis saturation: Red", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis saturation: Yellow (VCP 0x5A) + /// + public static VcpFeature SixAxisSaturationYellow => new(0x5A, "6-axis-saturation-yellow", "6 axis saturation: Yellow", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis saturation: Green (VCP 0x5B) + /// + public static VcpFeature SixAxisSaturationGreen => new(0x5B, "6-axis-saturation-green", "6 axis saturation: Green", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis saturation: Cyan (VCP 0x5C) + /// + public static VcpFeature SixAxisSaturationCyan => new(0x5C, "6-axis-saturation-cyan", "6 axis saturation: Cyan", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis saturation: Blue (VCP 0x5D) + /// + public static VcpFeature SixAxisSaturationBlue => new(0x5D, "6-axis-saturation-blue", "6 axis saturation: Blue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis saturation: Magenta (VCP 0x5E) + /// + public static VcpFeature SixAxisSaturationMagenta => new(0x5E, "6-axis-saturation-magenta", "6 axis saturation: Magenta", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Backlight Level: White (VCP 0x6B) + /// + public static VcpFeature BacklightLevelWhite => new(0x6B, "backlight-level-white", "Backlight Level: White", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Red video black level (VCP 0x6C) + /// + public static VcpFeature RedBlackLevel => new(0x6C, "red-black-level", "Video black level: Red", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Backlight Level: Red (VCP 0x6D) + /// + public static VcpFeature BacklightLevelRed => new(0x6D, "backlight-level-red", "Backlight Level: Red", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Green video black level (VCP 0x6E) + /// + public static VcpFeature GreenBlackLevel => new(0x6E, "green-black-level", "Video black level: Green", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Backlight Level: Green (VCP 0x6F) + /// + public static VcpFeature BacklightLevelGreen => new(0x6F, "backlight-level-green", "Backlight Level: Green", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Blue video black level (VCP 0x70) + /// + public static VcpFeature BlueBlackLevel => new(0x70, "blue-black-level", "Video black level: Blue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Backlight Level: Blue (VCP 0x71) + /// + public static VcpFeature BacklightLevelBlue => new(0x71, "backlight-level-blue", "Backlight Level: Blue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Gamma (VCP 0x72) + /// + public static VcpFeature Gamma => new(0x72, "gamma", "Gamma", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, false); + + /// + /// Color Saturation (VCP 0x8A) + /// + public static VcpFeature ColorSaturation => new(0x8A, "color-saturation", "Color Saturation", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true, "saturation", "sat"); + + /// + /// Hue (VCP 0x90) + /// + public static VcpFeature Hue => new(0x90, "hue", "Hue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis hue control: Red (VCP 0x9B) + /// + public static VcpFeature SixAxisHueRed => new(0x9B, "6-axis-hue-red", "6 axis hue control: Red", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis hue control: Yellow (VCP 0x9C) + /// + public static VcpFeature SixAxisHueYellow => new(0x9C, "6-axis-hue-yellow", "6 axis hue control: Yellow", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis hue control: Green (VCP 0x9D) + /// + public static VcpFeature SixAxisHueGreen => new(0x9D, "6-axis-hue-green", "6 axis hue control: Green", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis hue control: Cyan (VCP 0x9E) + /// + public static VcpFeature SixAxisHueCyan => new(0x9E, "6-axis-hue-cyan", "6 axis hue control: Cyan", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis hue control: Blue (VCP 0x9F) + /// + public static VcpFeature SixAxisHueBlue => new(0x9F, "6-axis-hue-blue", "6 axis hue control: Blue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis hue control: Magenta (VCP 0xA0) + /// + public static VcpFeature SixAxisHueMagenta => new(0xA0, "6-axis-hue-magenta", "6 axis hue control: Magenta", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + // ===== GEOMETRY FEATURES (mainly CRT) ===== + + /// + /// Horizontal position (VCP 0x20) + /// + public static VcpFeature HorizontalPosition => new(0x20, "h-position", "Horizontal Position (Phase)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false, "h-pos"); + + /// + /// Horizontal size (VCP 0x22) + /// + public static VcpFeature HorizontalSize => new(0x22, "h-size", "Horizontal Size", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Horizontal pincushion (VCP 0x24) + /// + public static VcpFeature HorizontalPincushion => new(0x24, "h-pincushion", "Horizontal Pincushion", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Horizontal pincushion balance (VCP 0x26) + /// + public static VcpFeature HorizontalPincushionBalance => new(0x26, "h-pincushion-balance", "Horizontal Pincushion Balance", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Horizontal convergence R/B (VCP 0x28) + /// + public static VcpFeature HorizontalConvergenceRB => new(0x28, "h-convergence-rb", "Horizontal Convergence R/B", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Horizontal convergence M/G (VCP 0x29) + /// + public static VcpFeature HorizontalConvergenceMG => new(0x29, "h-convergence-mg", "Horizontal Convergence M/G", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Horizontal linearity (VCP 0x2A) + /// + public static VcpFeature HorizontalLinearity => new(0x2A, "h-linearity", "Horizontal Linearity", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Horizontal linearity balance (VCP 0x2C) + /// + public static VcpFeature HorizontalLinearityBalance => new(0x2C, "h-linearity-balance", "Horizontal Linearity Balance", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical position (VCP 0x30) + /// + public static VcpFeature VerticalPosition => new(0x30, "v-position", "Vertical Position (Phase)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false, "v-pos"); + + /// + /// Vertical size (VCP 0x32) + /// + public static VcpFeature VerticalSize => new(0x32, "v-size", "Vertical Size", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical pincushion (VCP 0x34) + /// + public static VcpFeature VerticalPincushion => new(0x34, "v-pincushion", "Vertical Pincushion", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical pincushion balance (VCP 0x36) + /// + public static VcpFeature VerticalPincushionBalance => new(0x36, "v-pincushion-balance", "Vertical Pincushion Balance", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical convergence R/B (VCP 0x38) + /// + public static VcpFeature VerticalConvergenceRB => new(0x38, "v-convergence-rb", "Vertical Convergence R/B", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical convergence M/G (VCP 0x39) + /// + public static VcpFeature VerticalConvergenceMG => new(0x39, "v-convergence-mg", "Vertical Convergence M/G", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical linearity (VCP 0x3A) + /// + public static VcpFeature VerticalLinearity => new(0x3A, "v-linearity", "Vertical Linearity", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical linearity balance (VCP 0x3C) + /// + public static VcpFeature VerticalLinearityBalance => new(0x3C, "v-linearity-balance", "Vertical Linearity Balance", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Clock phase (VCP 0x3E) + /// + public static VcpFeature ClockPhase => new(0x3E, "clock-phase", "Clock Phase", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false, "phase"); + + /// + /// Horizontal Parallelogram (VCP 0x40) + /// + public static VcpFeature HorizontalParallelogram => new(0x40, "horizontal-parallelogram", "Horizontal Parallelogram", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical Parallelogram (VCP 0x41) + /// + public static VcpFeature VerticalParallelogram => new(0x41, "vertical-parallelogram", "Vertical Parallelogram", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Horizontal Keystone (VCP 0x42) + /// + public static VcpFeature HorizontalKeystone => new(0x42, "horizontal-keystone", "Horizontal Keystone", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical Keystone (VCP 0x43) + /// + public static VcpFeature VerticalKeystone => new(0x43, "vertical-keystone", "Vertical Keystone", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Rotation (VCP 0x44) + /// + public static VcpFeature Rotation => new(0x44, "rotation", "Rotation", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Top Corner Flare (VCP 0x46) + /// + public static VcpFeature TopCornerFlare => new(0x46, "top-corner-flare", "Top Corner Flare", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Top Corner Hook (VCP 0x48) + /// + public static VcpFeature TopCornerHook => new(0x48, "top-corner-hook", "Top Corner Hook", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Bottom Corner Flare (VCP 0x4A) + /// + public static VcpFeature BottomCornerFlare => new(0x4A, "bottom-corner-flare", "Bottom Corner Flare", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Bottom Corner Hook (VCP 0x4C) + /// + public static VcpFeature BottomCornerHook => new(0x4C, "bottom-corner-hook", "Bottom Corner Hook", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Window Position(TL_X) (VCP 0x95) + /// + public static VcpFeature WindowPositionTLX => new(0x95, "window-position-tl-x", "Window Position(TL_X)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Window Position(TL_Y) (VCP 0x96) + /// + public static VcpFeature WindowPositionTLY => new(0x96, "window-position-tl-y", "Window Position(TL_Y)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Window Position(BR_X) (VCP 0x97) + /// + public static VcpFeature WindowPositionBRX => new(0x97, "window-position-br-x", "Window Position(BR_X)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Window Position(BR_Y) (VCP 0x98) + /// + public static VcpFeature WindowPositionBRY => new(0x98, "window-position-br-y", "Window Position(BR_Y)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Window control on/off (VCP 0x99) + /// + public static VcpFeature WindowControlOnOff => new(0x99, "window-control-on-off", "Window control on/off", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + // ===== AUDIO FEATURES ===== + + /// + /// Audio speaker volume (VCP 0x62) + /// + public static VcpFeature AudioVolume => new(0x62, "volume", "Audio speaker volume", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, true, "vol"); + + /// + /// Speaker Select (VCP 0x63) + /// + public static VcpFeature SpeakerSelect => new(0x63, "speaker-select", "Speaker Select", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, false); + + /// + /// Audio: Microphone Volume (VCP 0x64) + /// + public static VcpFeature MicrophoneVolume => new(0x64, "microphone-volume", "Audio: Microphone Volume", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, true, "mic-volume"); + + /// + /// Audio mute/Screen blank (VCP 0x8D) + /// + public static VcpFeature AudioMute => new(0x8D, "mute", "Audio mute/Screen blank", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, false); + + /// + /// Audio treble (VCP 0x8F) + /// + public static VcpFeature AudioTreble => new(0x8F, "audio-treble", "Audio treble", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, true, "treble"); + + /// + /// Audio bass (VCP 0x91) + /// + public static VcpFeature AudioBass => new(0x91, "audio-bass", "Audio bass", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, true, "bass"); + + /// + /// Audio balance L/R (VCP 0x93) + /// + public static VcpFeature AudioBalance => new(0x93, "audio-balance", "Audio balance L/R", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, true, "balance"); + + /// + /// Audio processor mode (VCP 0x94) + /// + public static VcpFeature AudioProcessorMode => new(0x94, "audio-processor-mode", "Audio processor mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, false); + + // ===== PRESET FEATURES ===== + + /// + /// Degauss (VCP 0x01) + /// + public static VcpFeature Degauss => new(0x01, "degauss", "Degauss", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false); + + /// + /// New control value (VCP 0x02) + /// + public static VcpFeature NewControlValue => new(0x02, "new-control-value", "New control value", VcpFeatureType.ReadWrite, VcpFeatureCategory.Preset, false); + + /// + /// Soft controls (VCP 0x03) + /// + public static VcpFeature SoftControls => new(0x03, "soft-controls", "Soft controls", VcpFeatureType.ReadWrite, VcpFeatureCategory.Preset, false); + + /// + /// Restore factory defaults (VCP 0x04) + /// + public static VcpFeature RestoreDefaults => new(0x04, "restore-defaults", "Restore factory defaults", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false, "factory-reset"); + + /// + /// Restore factory brightness/contrast defaults (VCP 0x05) + /// + public static VcpFeature RestoreBrightnessContrastDefaults => new(0x05, "restore-brightness-contrast", "Restore factory brightness/contrast defaults", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false); + + /// + /// Restore factory geometry defaults (VCP 0x06) + /// + public static VcpFeature RestoreGeometryDefaults => new(0x06, "restore-geometry", "Restore factory geometry defaults", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false); + + /// + /// Restore factory color defaults (VCP 0x08) + /// + public static VcpFeature RestoreColorDefaults => new(0x08, "restore-color", "Restore factory color defaults", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false); + + /// + /// Restore factory TV defaults (VCP 0x0A) + /// + public static VcpFeature RestoreTVDefaults => new(0x0A, "restore-tv-defaults", "Restore factory TV defaults", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false); + + /// + /// Color temperature increment (VCP 0x0B) + /// + public static VcpFeature ColorTemperatureIncrement => new(0x0B, "color-temp-increment", "Color temperature increment", VcpFeatureType.ReadOnly, VcpFeatureCategory.ColorControl, false); + + /// + /// Color temperature request (VCP 0x0C) + /// + public static VcpFeature ColorTemperatureRequest => new(0x0C, "color-temp-request", "Color temperature request", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, false, "color-temp"); + + /// + /// Clock (VCP 0x0E) + /// + public static VcpFeature Clock => new(0x0E, "clock", "Clock", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + // ===== MISCELLANEOUS FEATURES ===== + + /// + /// Active control (VCP 0x52) + /// + public static VcpFeature ActiveControl => new(0x52, "active-control", "Active control", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Performance Preservation (VCP 0x54) + /// + public static VcpFeature PerformancePreservation => new(0x54, "performance-preservation", "Performance Preservation", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Ambient light sensor (VCP 0x66) + /// + public static VcpFeature AmbientLightSensor => new(0x66, "ambient-light-sensor", "Ambient light sensor", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// LUT Size (VCP 0x73) + /// + public static VcpFeature LUTSize => new(0x73, "lut-size", "LUT Size", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Single point LUT operation (VCP 0x74) + /// + public static VcpFeature SinglePointLUTOperation => new(0x74, "single-point-lut-operation", "Single point LUT operation", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Block LUT operation (VCP 0x75) + /// + public static VcpFeature BlockLUTOperation => new(0x75, "block-lut-operation", "Block LUT operation", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Remote Procedure Call (VCP 0x76) + /// + public static VcpFeature RemoteProcedureCall => new(0x76, "remote-procedure-call", "Remote Procedure Call", VcpFeatureType.WriteOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Display Identification Operation (VCP 0x78) + /// + public static VcpFeature DisplayIdentificationOperation => new(0x78, "display-identification-operation", "Display Identification Operation", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// TV Channel Up/Down (VCP 0x8B) + /// + public static VcpFeature TVChannelUpDown => new(0x8B, "tv-channel-up-down", "TV Channel Up/Down", VcpFeatureType.WriteOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Horizontal frequency (VCP 0xAC) + /// + public static VcpFeature HorizontalFrequency => new(0xAC, "horizontal-frequency", "Horizontal frequency", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false, "h-freq"); + + /// + /// Vertical frequency (VCP 0xAE) + /// + public static VcpFeature VerticalFrequency => new(0xAE, "vertical-frequency", "Vertical frequency", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false, "v-freq"); + + /// + /// Settings (VCP 0xB0) + /// + public static VcpFeature Settings => new(0xB0, "settings", "Settings", VcpFeatureType.WriteOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Flat panel sub-pixel layout (VCP 0xB2) + /// + public static VcpFeature FlatPanelSubPixelLayout => new(0xB2, "flat-panel-sub-pixel-layout", "Flat panel sub-pixel layout", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Source Timing Mode (VCP 0xB4) + /// + public static VcpFeature SourceTimingMode => new(0xB4, "source-timing-mode", "Source Timing Mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Display technology type (VCP 0xB6) + /// + public static VcpFeature DisplayTechnologyType => new(0xB6, "display-technology-type", "Display technology type", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Monitor status (VCP 0xB7) + /// + public static VcpFeature MonitorStatus => new(0xB7, "monitor-status", "Monitor status", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Packet count (VCP 0xB8) + /// + public static VcpFeature PacketCount => new(0xB8, "packet-count", "Packet count", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Monitor X origin (VCP 0xB9) + /// + public static VcpFeature MonitorXOrigin => new(0xB9, "monitor-x-origin", "Monitor X origin", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Monitor Y origin (VCP 0xBA) + /// + public static VcpFeature MonitorYOrigin => new(0xBA, "monitor-y-origin", "Monitor Y origin", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Header error count (VCP 0xBB) + /// + public static VcpFeature HeaderErrorCount => new(0xBB, "header-error-count", "Header error count", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Body CRC error count (VCP 0xBC) + /// + public static VcpFeature BodyCRCErrorCount => new(0xBC, "body-crc-error-count", "Body CRC error count", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Client ID (VCP 0xBD) + /// + public static VcpFeature ClientID => new(0xBD, "client-id", "Client ID", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Link control (VCP 0xBE) + /// + public static VcpFeature LinkControl => new(0xBE, "link-control", "Link control", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Display usage time (VCP 0xC0) + /// + public static VcpFeature DisplayUsageTime => new(0xC0, "display-usage-time", "Display usage time", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Display descriptor length (VCP 0xC2) + /// + public static VcpFeature DisplayDescriptorLength => new(0xC2, "display-descriptor-length", "Display descriptor length", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Transmit display descriptor (VCP 0xC3) + /// + public static VcpFeature TransmitDisplayDescriptor => new(0xC3, "transmit-display-descriptor", "Transmit display descriptor", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Enable display of 'display descriptor' (VCP 0xC4) + /// + public static VcpFeature EnableDisplayOfDisplayDescriptor => new(0xC4, "enable-display-of-display-descriptor", "Enable display of 'display descriptor'", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Application enable key (VCP 0xC6) + /// + public static VcpFeature ApplicationEnableKey => new(0xC6, "application-enable-key", "Application enable key", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Display controller type (VCP 0xC8) + /// + public static VcpFeature DisplayControllerType => new(0xC8, "display-controller-type", "Display controller type", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Display firmware level (VCP 0xC9) + /// + public static VcpFeature DisplayFirmwareLevel => new(0xC9, "display-firmware-level", "Display firmware level", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false, "firmware"); + + /// + /// OSD/Button Control (VCP 0xCA) + /// + public static VcpFeature OSDButtonControl => new(0xCA, "osd-button-control", "OSD/Button Control", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false, "osd"); + + /// + /// OSD Language (VCP 0xCC) + /// + public static VcpFeature OSDLanguage => new(0xCC, "osd-language", "OSD Language", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false, "language"); + + /// + /// Status Indicators (VCP 0xCD) + /// + public static VcpFeature StatusIndicators => new(0xCD, "status-indicators", "Status Indicators", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Auxiliary display size (VCP 0xCE) + /// + public static VcpFeature AuxiliaryDisplaySize => new(0xCE, "auxiliary-display-size", "Auxiliary display size", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Auxiliary display data (VCP 0xCF) + /// + public static VcpFeature AuxiliaryDisplayData => new(0xCF, "auxiliary-display-data", "Auxiliary display data", VcpFeatureType.WriteOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Output select (VCP 0xD0) + /// + public static VcpFeature OutputSelect => new(0xD0, "output-select", "Output select", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Asset Tag (VCP 0xD2) + /// + public static VcpFeature AssetTag => new(0xD2, "asset-tag", "Asset Tag", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Power mode (VCP 0xD6) + /// + public static VcpFeature PowerMode => new(0xD6, "power-mode", "Power mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false, "power"); + + /// + /// Auxiliary power output (VCP 0xD7) + /// + public static VcpFeature AuxiliaryPowerOutput => new(0xD7, "auxiliary-power-output", "Auxiliary power output", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Scratch Pad (VCP 0xDE) + /// + public static VcpFeature ScratchPad => new(0xDE, "scratch-pad", "Scratch Pad", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// VCP Version (VCP 0xDF) + /// + public static VcpFeature VcpVersion => new(0xDF, "vcp-version", "VCP Version", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false, "version"); + + /// + /// All features registry - contains all predefined MCCS features + /// + public static IReadOnlyList AllFeatures { get; } = BuildAllFeatures(); + + /// + /// Builds the complete registry of all MCCS VCP features + /// + private static List BuildAllFeatures() + { + return new List + { + // Preset Features (0x01-0x0F range) + Degauss, + NewControlValue, + SoftControls, + RestoreDefaults, + RestoreBrightnessContrastDefaults, + RestoreGeometryDefaults, + RestoreColorDefaults, + RestoreTVDefaults, + ColorTemperatureIncrement, + ColorTemperatureRequest, + Clock, + + // Image Adjustment Features (0x10-0x1F range) + Brightness, + FleshToneEnhancement, + Contrast, + Backlight, + SelectColorPreset, + RedGain, + UserColorVisionCompensation, + GreenGain, + BlueGain, + Focus, + AutoSetup, + AutoColorSetup, + + // Geometry Features (0x20-0x4F range) + HorizontalPosition, + HorizontalSize, + HorizontalPincushion, + HorizontalPincushionBalance, + HorizontalConvergenceRB, + HorizontalConvergenceMG, + HorizontalLinearity, + HorizontalLinearityBalance, + GrayScaleExpansion, + VerticalPosition, + VerticalSize, + VerticalPincushion, + VerticalPincushionBalance, + VerticalConvergenceRB, + VerticalConvergenceMG, + VerticalLinearity, + VerticalLinearityBalance, + ClockPhase, + HorizontalParallelogram, + VerticalParallelogram, + HorizontalKeystone, + VerticalKeystone, + Rotation, + TopCornerFlare, + TopCornerHook, + BottomCornerFlare, + BottomCornerHook, + + // Control and Performance Features (0x50-0x5F range) + ActiveControl, + PerformancePreservation, + HorizontalMoire, + VerticalMoire, + SixAxisSaturationRed, + SixAxisSaturationYellow, + SixAxisSaturationGreen, + SixAxisSaturationCyan, + SixAxisSaturationBlue, + SixAxisSaturationMagenta, + + // Input and Audio Features (0x60-0x6F range) + InputSource, + AudioVolume, + SpeakerSelect, + MicrophoneVolume, + AmbientLightSensor, + BacklightLevelWhite, + RedBlackLevel, + BacklightLevelRed, + GreenBlackLevel, + BacklightLevelGreen, + BlueBlackLevel, + BacklightLevelBlue, + + // Advanced Color and LUT Features (0x70-0x7F range) + Gamma, + LUTSize, + SinglePointLUTOperation, + BlockLUTOperation, + RemoteProcedureCall, + DisplayIdentificationOperation, + AdjustFocalPlane, + AdjustZoom, + Trapezoid, + + // Image Processing Features (0x80-0x9F range) + Keystone, + HorizontalMirror, + VerticalMirror, + DisplayScaling, + Sharpness, + VelocityScanModulation, + ColorSaturation, + TVChannelUpDown, + TVSharpness, + AudioMute, + TVContrast, + AudioTreble, + Hue, + AudioBass, + TVBlackLevel, + AudioBalance, + AudioProcessorMode, + WindowPositionTLX, + WindowPositionTLY, + WindowPositionBRX, + WindowPositionBRY, + WindowControlOnOff, + WindowBackground, + SixAxisHueRed, + SixAxisHueYellow, + SixAxisHueGreen, + SixAxisHueCyan, + SixAxisHueBlue, + SixAxisHueMagenta, + + // Setup and Window Features (0xA0-0xAF range) + AutoSetupOnOff, + WindowMaskControl, + ChangeSelectedWindow, + ScreenOrientation, + HorizontalFrequency, + VerticalFrequency, + + // System Information Features (0xB0-0xBF range) + Settings, + FlatPanelSubPixelLayout, + SourceTimingMode, + DisplayTechnologyType, + MonitorStatus, + PacketCount, + MonitorXOrigin, + MonitorYOrigin, + HeaderErrorCount, + BodyCRCErrorCount, + ClientID, + LinkControl, + + // Display Management Features (0xC0-0xDF range) + DisplayUsageTime, + DisplayDescriptorLength, + TransmitDisplayDescriptor, + EnableDisplayOfDisplayDescriptor, + ApplicationEnableKey, + DisplayControllerType, + DisplayFirmwareLevel, + OSDButtonControl, + OSDLanguage, + StatusIndicators, + AuxiliaryDisplaySize, + AuxiliaryDisplayData, + OutputSelect, + AssetTag, + StereoVideoMode, + PowerMode, + AuxiliaryPowerOutput, + ScanMode, + ImageMode, + DisplayMode, + ScratchPad, + VcpVersion + }; + } public override string ToString() { @@ -76,7 +1055,9 @@ public override int GetHashCode() public record VcpFeatureInfo( byte Code, string Name, + string Description, VcpFeatureType Type, + VcpFeatureCategory Category, uint CurrentValue, uint MaxValue, bool IsSupported diff --git a/EXAMPLES.md b/EXAMPLES.md index f05054c..24ae3ec 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -1,6 +1,206 @@ # DDCSwitch Examples -This document contains detailed examples and use cases for DDCSwitch, including input switching, brightness/contrast control, and raw VCP access. +This document contains detailed examples and use cases for DDCSwitch, including input switching, brightness/contrast control, comprehensive VCP feature access, and automation. + +## Comprehensive VCP Feature Examples + +DDCSwitch now supports all MCCS (Monitor Control Command Set) standardized VCP features, organized by categories for easy discovery. + +### VCP Feature Categories + +List all available feature categories: + +```powershell +DDCSwitch list --categories +``` + +Output: +``` +Available VCP Feature Categories: +- ImageAdjustment: Brightness, contrast, sharpness, backlight controls +- ColorControl: RGB gains, color temperature, gamma, hue, saturation +- Geometry: Position, size, pincushion controls (mainly CRT monitors) +- Audio: Volume, mute, balance, treble, bass controls +- Preset: Factory defaults, degauss, calibration features +- Miscellaneous: Power mode, OSD settings, firmware information +``` + +### Browse Features by Category + +```powershell +# Image adjustment features +DDCSwitch list --category image + +# Color control features +DDCSwitch list --category color + +# Audio features +DDCSwitch list --category audio +``` + +### Color Control Examples + +Control RGB gains for color calibration: + +```powershell +# Set individual RGB gains (percentage values) +DDCSwitch set 0 red-gain 95% +DDCSwitch set 0 green-gain 90% +DDCSwitch set 0 blue-gain 85% + +# Get current RGB values +DDCSwitch get 0 red-gain +DDCSwitch get 0 green-gain +DDCSwitch get 0 blue-gain + +# Color temperature control (if supported) +DDCSwitch set 0 color-temp-request 6500 +DDCSwitch get 0 color-temp-request + +# Gamma control +DDCSwitch set 0 gamma 2.2 +DDCSwitch get 0 gamma + +# Hue and saturation +DDCSwitch set 0 hue 50% +DDCSwitch set 0 saturation 80% +``` + +### Audio Control Examples + +Control monitor speakers (if supported): + +```powershell +# Volume control (percentage) +DDCSwitch set 0 volume 75% +DDCSwitch get 0 volume + +# Mute/unmute +DDCSwitch set 0 mute 1 # Mute +DDCSwitch set 0 mute 0 # Unmute + +# Audio balance (if supported) +DDCSwitch set 0 audio-balance 50% # Centered + +# Treble and bass (if supported) +DDCSwitch set 0 audio-treble 60% +DDCSwitch set 0 audio-bass 70% +``` + +### Advanced Image Controls + +Beyond basic brightness and contrast: + +```powershell +# Sharpness control +DDCSwitch set 0 sharpness 75% +DDCSwitch get 0 sharpness + +# Backlight control (LED monitors) +DDCSwitch set 0 backlight 80% +DDCSwitch get 0 backlight + +# Image orientation (if supported) +DDCSwitch set 0 image-orientation 0 # Normal +DDCSwitch set 0 image-orientation 1 # 90° rotation + +# Image mode presets (if supported) +DDCSwitch set 0 image-mode 1 # Standard +DDCSwitch set 0 image-mode 2 # Movie +DDCSwitch set 0 image-mode 3 # Game +``` + +### Factory Reset and Calibration + +```powershell +# Restore all factory defaults +DDCSwitch set 0 restore-defaults 1 + +# Restore specific defaults +DDCSwitch set 0 restore-brightness-contrast 1 +DDCSwitch set 0 restore-color 1 +DDCSwitch set 0 restore-geometry 1 + +# Degauss (CRT monitors) +DDCSwitch set 0 degauss 1 + +# Auto calibration features (if supported) +DDCSwitch set 0 auto-color-setup 1 +DDCSwitch set 0 auto-size-center 1 +``` + +### Complete Monitor Profile Examples + +Create comprehensive monitor profiles using all available features: + +```powershell +# gaming-profile-advanced.ps1 +Write-Host "Activating Advanced Gaming Profile..." -ForegroundColor Cyan + +# Input and basic settings +DDCSwitch set 0 input HDMI1 +DDCSwitch set 0 brightness 90% +DDCSwitch set 0 contrast 85% + +# Color optimization for gaming +DDCSwitch set 0 red-gain 100% +DDCSwitch set 0 green-gain 95% +DDCSwitch set 0 blue-gain 90% +DDCSwitch set 0 saturation 110% # Enhanced colors +DDCSwitch set 0 sharpness 80% # Crisp details + +# Audio settings +DDCSwitch set 0 volume 60% +DDCSwitch set 0 mute 0 + +Write-Host "Advanced Gaming Profile Activated!" -ForegroundColor Green +``` + +```powershell +# work-profile-advanced.ps1 +Write-Host "Activating Advanced Work Profile..." -ForegroundColor Cyan + +# Input and basic settings +DDCSwitch set 0 input DP1 +DDCSwitch set 0 brightness 60% +DDCSwitch set 0 contrast 75% + +# Color optimization for text work +DDCSwitch set 0 red-gain 85% +DDCSwitch set 0 green-gain 90% +DDCSwitch set 0 blue-gain 95% +DDCSwitch set 0 saturation 70% # Reduced saturation for comfort +DDCSwitch set 0 sharpness 60% # Softer for long reading + +# Audio settings +DDCSwitch set 0 volume 40% # Lower for office environment +DDCSwitch set 0 mute 0 + +Write-Host "Advanced Work Profile Activated!" -ForegroundColor Green +``` + +```powershell +# photo-editing-profile.ps1 +Write-Host "Activating Photo Editing Profile..." -ForegroundColor Cyan + +# Input and basic settings +DDCSwitch set 0 input DP1 +DDCSwitch set 0 brightness 70% +DDCSwitch set 0 contrast 80% + +# Accurate color reproduction +DDCSwitch set 0 red-gain 90% +DDCSwitch set 0 green-gain 90% +DDCSwitch set 0 blue-gain 90% +DDCSwitch set 0 saturation 100% # Natural saturation +DDCSwitch set 0 gamma 2.2 # Standard gamma +DDCSwitch set 0 color-temp-request 6500 # D65 standard + +# Disable audio to avoid distractions +DDCSwitch set 0 mute 1 + +Write-Host "Photo Editing Profile Activated!" -ForegroundColor Green +``` ## Basic Usage Examples diff --git a/README.md b/README.md index abd1128..6cd32e7 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ A Windows command-line utility to control monitor settings via DDC/CI (Display D - 🖥️ **List all DDC/CI capable monitors** with their current input sources - 🔄 **Switch monitor inputs** programmatically (HDMI, DisplayPort, DVI, VGA, etc.) - 🔆 **Control brightness and contrast** with percentage values (0-100%) -- 🎛️ **Raw VCP access** for advanced users to control any monitor feature +- 🎛️ **Comprehensive VCP feature support** - Access all MCCS standardized monitor controls +- 🏷️ **Feature categories and discovery** - Browse VCP features by category (Image, Color, Geometry, Audio, etc.) - 🔍 **VCP scanning** to discover all supported monitor features - 🎯 **Simple CLI interface** perfect for scripts, shortcuts, and hotkeys - 📊 **JSON output support** - Machine-readable output for automation and integration @@ -159,6 +160,34 @@ DDCSwitch list --verbose This scans all VCP codes (0x00-0xFF) and displays supported features with their current values, maximum values, and access types (read-only, write-only, read-write). +### VCP Feature Categories and Discovery + +Discover and browse VCP features by category: + +```powershell +# List all available categories +DDCSwitch list --categories + +# List features in a specific category +DDCSwitch list --category image +DDCSwitch list --category color +DDCSwitch list --category audio +``` + +Example output: +``` +Image Adjustment Features: +- brightness (0x10): Brightness control +- contrast (0x12): Contrast control +- sharpness (0x87): Sharpness control +- backlight (0x13): Backlight control + +Color Control Features: +- red-gain (0x16): Video gain: Red +- green-gain (0x18): Video gain: Green +- blue-gain (0x1A): Video gain: Blue +``` + ### Supported Features #### Input Sources @@ -173,6 +202,18 @@ This scans all VCP codes (0x00-0xFF) and displays supported features with their - **Brightness**: `brightness` (VCP 0x10) - accepts percentage values (0-100%) - **Contrast**: `contrast` (VCP 0x12) - accepts percentage values (0-100%) - **Input Source**: `input` (VCP 0x60) - existing functionality maintained +- **Color Controls**: `red-gain`, `green-gain`, `blue-gain` (VCP 0x16, 0x18, 0x1A) +- **Audio**: `volume`, `mute` (VCP 0x62, 0x8D) - volume accepts percentage values +- **Geometry**: `h-position`, `v-position`, `clock`, `phase` (mainly for CRT monitors) +- **Presets**: `restore-defaults`, `degauss` (VCP 0x04, 0x01) + +#### VCP Feature Categories +- **Image Adjustment**: brightness, contrast, sharpness, backlight, etc. +- **Color Control**: RGB gains, color temperature, gamma, hue, saturation +- **Geometry**: position, size, pincushion controls (mainly CRT) +- **Audio**: volume, mute, balance, treble, bass +- **Preset**: factory defaults, degauss, calibration +- **Miscellaneous**: power mode, OSD settings, firmware info #### Raw VCP Codes - Any VCP code from `0x00` to `0xFF` @@ -189,17 +230,32 @@ DDCSwitch set 0 HDMI1 DDCSwitch set 1 DP1 ``` -**Control brightness and contrast:** +**Control comprehensive VCP features:** ```powershell DDCSwitch set 0 brightness 75% DDCSwitch set 0 contrast 80% DDCSwitch get 0 brightness + +# Color controls +DDCSwitch set 0 red-gain 90% +DDCSwitch set 0 green-gain 85% +DDCSwitch set 0 blue-gain 95% + +# Audio controls (if supported) +DDCSwitch set 0 volume 50% +DDCSwitch set 0 mute 1 ``` -**Raw VCP access:** +**VCP feature discovery:** ```powershell -DDCSwitch get 0 0x10 # Get brightness (raw) -DDCSwitch set 0 0x10 120 # Set brightness (raw value) +# List all available VCP feature categories +DDCSwitch list --categories + +# List features in a specific category +DDCSwitch list --category color + +# Search for features by name +DDCSwitch get 0 bright # Matches "brightness" ``` **Desktop shortcut:** @@ -291,6 +347,6 @@ MIT License - see LICENSE file for details ## Acknowledgments -- Inspired by `ddcutil` for Linux +- Inspired by [ddcutil](https://www.ddcutil.com) for Linux - Uses Spectre.Console for beautiful terminal output From c81489a869dbc5134afd10e652b34092b3d3b2fa Mon Sep 17 00:00:00 2001 From: Quick <577652+markdwags@users.noreply.github.com> Date: Wed, 7 Jan 2026 20:38:25 -0600 Subject: [PATCH 04/10] add VCP feature generation script and data; embed VCP feature data JSON, create build script for NativeAOT, and update project files --- .gitignore | 4 +- DDCSwitch/DDCSwitch.csproj | 12 + DDCSwitch/VcpFeature.Generated.cs | 935 +++++++++++++++++ DDCSwitch/VcpFeature.cs | 959 +----------------- DDCSwitch/VcpFeatureData.json | 1561 +++++++++++++++++++++++++++++ build.cmd | 53 + tools/GenerateVcpFeatures.py | 114 +++ tools/Regenerate.ps1 | 30 + 8 files changed, 2716 insertions(+), 952 deletions(-) create mode 100644 DDCSwitch/VcpFeature.Generated.cs create mode 100644 DDCSwitch/VcpFeatureData.json create mode 100644 build.cmd create mode 100644 tools/GenerateVcpFeatures.py create mode 100644 tools/Regenerate.ps1 diff --git a/.gitignore b/.gitignore index be2f63d..dd4c524 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,6 @@ artifacts/ # NuGet Packages *.nupkg *.snupkg -**/packages/* \ No newline at end of file +**/packages/* + +dist/ \ No newline at end of file diff --git a/DDCSwitch/DDCSwitch.csproj b/DDCSwitch/DDCSwitch.csproj index a2ee008..2b27bce 100644 --- a/DDCSwitch/DDCSwitch.csproj +++ b/DDCSwitch/DDCSwitch.csproj @@ -40,5 +40,17 @@ + + + + + + + True + True + VcpFeatureData.json + + + diff --git a/DDCSwitch/VcpFeature.Generated.cs b/DDCSwitch/VcpFeature.Generated.cs new file mode 100644 index 0000000..554118c --- /dev/null +++ b/DDCSwitch/VcpFeature.Generated.cs @@ -0,0 +1,935 @@ +// +// This file is automatically generated from VcpFeatureData.json +// Do not edit this file directly. Instead, edit VcpFeatureData.json and regenerate. + +namespace DDCSwitch; + +public partial class VcpFeature +{ + // ===== GENERATED VCP FEATURES ===== + + // ===== PRESET FEATURES ===== + + /// + /// Degauss (VCP 0x01) + /// + public static VcpFeature Degauss => new(0x01, "degauss", "Degauss", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false); + + /// + /// New control value (VCP 0x02) + /// + public static VcpFeature NewControlValue => new(0x02, "new-control-value", "New control value", VcpFeatureType.ReadWrite, VcpFeatureCategory.Preset, false); + + /// + /// Soft controls (VCP 0x03) + /// + public static VcpFeature SoftControls => new(0x03, "soft-controls", "Soft controls", VcpFeatureType.ReadWrite, VcpFeatureCategory.Preset, false); + + /// + /// Restore factory defaults (VCP 0x04) + /// + public static VcpFeature RestoreDefaults => new(0x04, "restore-defaults", "Restore factory defaults", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false, "factory-reset"); + + /// + /// Restore factory brightness/contrast defaults (VCP 0x05) + /// + public static VcpFeature RestoreBrightnessContrastDefaults => new(0x05, "restore-brightness-contrast", "Restore factory brightness/contrast defaults", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false); + + /// + /// Restore factory geometry defaults (VCP 0x06) + /// + public static VcpFeature RestoreGeometryDefaults => new(0x06, "restore-geometry", "Restore factory geometry defaults", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false); + + /// + /// Restore factory color defaults (VCP 0x08) + /// + public static VcpFeature RestoreColorDefaults => new(0x08, "restore-color", "Restore factory color defaults", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false); + + /// + /// Restore factory TV defaults (VCP 0x0A) + /// + public static VcpFeature RestoreTVDefaults => new(0x0A, "restore-tv-defaults", "Restore factory TV defaults", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false); + + + // ===== COLORCONTROL FEATURES ===== + + /// + /// Color temperature increment (VCP 0x0B) + /// + public static VcpFeature ColorTemperatureIncrement => new(0x0B, "color-temp-increment", "Color temperature increment", VcpFeatureType.ReadOnly, VcpFeatureCategory.ColorControl, false); + + /// + /// Color temperature request (VCP 0x0C) + /// + public static VcpFeature ColorTemperatureRequest => new(0x0C, "color-temp-request", "Color temperature request", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, false, "color-temp"); + + /// + /// Video gain: Red (VCP 0x16) + /// + public static VcpFeature RedGain => new(0x16, "red-gain", "Video gain: Red", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true, "red"); + + /// + /// User color vision compensation (VCP 0x17) + /// + public static VcpFeature UserColorVisionCompensation => new(0x17, "user-color-vision-compensation", "User color vision compensation", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Video gain: Green (VCP 0x18) + /// + public static VcpFeature GreenGain => new(0x18, "green-gain", "Video gain: Green", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true, "green"); + + /// + /// Video gain: Blue (VCP 0x1A) + /// + public static VcpFeature BlueGain => new(0x1A, "blue-gain", "Video gain: Blue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true, "blue"); + + /// + /// 6 axis saturation: Red (VCP 0x59) + /// + public static VcpFeature SixAxisSaturationRed => new(0x59, "6-axis-saturation-red", "6 axis saturation: Red", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis saturation: Yellow (VCP 0x5A) + /// + public static VcpFeature SixAxisSaturationYellow => new(0x5A, "6-axis-saturation-yellow", "6 axis saturation: Yellow", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis saturation: Green (VCP 0x5B) + /// + public static VcpFeature SixAxisSaturationGreen => new(0x5B, "6-axis-saturation-green", "6 axis saturation: Green", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis saturation: Cyan (VCP 0x5C) + /// + public static VcpFeature SixAxisSaturationCyan => new(0x5C, "6-axis-saturation-cyan", "6 axis saturation: Cyan", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis saturation: Blue (VCP 0x5D) + /// + public static VcpFeature SixAxisSaturationBlue => new(0x5D, "6-axis-saturation-blue", "6 axis saturation: Blue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis saturation: Magenta (VCP 0x5E) + /// + public static VcpFeature SixAxisSaturationMagenta => new(0x5E, "6-axis-saturation-magenta", "6 axis saturation: Magenta", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Backlight Level: White (VCP 0x6B) + /// + public static VcpFeature BacklightLevelWhite => new(0x6B, "backlight-level-white", "Backlight Level: White", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Video black level: Red (VCP 0x6C) + /// + public static VcpFeature RedBlackLevel => new(0x6C, "red-black-level", "Video black level: Red", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Backlight Level: Red (VCP 0x6D) + /// + public static VcpFeature BacklightLevelRed => new(0x6D, "backlight-level-red", "Backlight Level: Red", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Video black level: Green (VCP 0x6E) + /// + public static VcpFeature GreenBlackLevel => new(0x6E, "green-black-level", "Video black level: Green", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Backlight Level: Green (VCP 0x6F) + /// + public static VcpFeature BacklightLevelGreen => new(0x6F, "backlight-level-green", "Backlight Level: Green", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Video black level: Blue (VCP 0x70) + /// + public static VcpFeature BlueBlackLevel => new(0x70, "blue-black-level", "Video black level: Blue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Backlight Level: Blue (VCP 0x71) + /// + public static VcpFeature BacklightLevelBlue => new(0x71, "backlight-level-blue", "Backlight Level: Blue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// Gamma (VCP 0x72) + /// + public static VcpFeature Gamma => new(0x72, "gamma", "Gamma", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, false); + + /// + /// Color Saturation (VCP 0x8A) + /// + public static VcpFeature ColorSaturation => new(0x8A, "color-saturation", "Color Saturation", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true, "saturation", "sat"); + + /// + /// Hue (VCP 0x90) + /// + public static VcpFeature Hue => new(0x90, "hue", "Hue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis hue control: Red (VCP 0x9B) + /// + public static VcpFeature SixAxisHueRed => new(0x9B, "6-axis-hue-red", "6 axis hue control: Red", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis hue control: Yellow (VCP 0x9C) + /// + public static VcpFeature SixAxisHueYellow => new(0x9C, "6-axis-hue-yellow", "6 axis hue control: Yellow", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis hue control: Green (VCP 0x9D) + /// + public static VcpFeature SixAxisHueGreen => new(0x9D, "6-axis-hue-green", "6 axis hue control: Green", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis hue control: Cyan (VCP 0x9E) + /// + public static VcpFeature SixAxisHueCyan => new(0x9E, "6-axis-hue-cyan", "6 axis hue control: Cyan", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis hue control: Blue (VCP 0x9F) + /// + public static VcpFeature SixAxisHueBlue => new(0x9F, "6-axis-hue-blue", "6 axis hue control: Blue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + /// + /// 6 axis hue control: Magenta (VCP 0xA0) + /// + public static VcpFeature SixAxisHueMagenta => new(0xA0, "6-axis-hue-magenta", "6 axis hue control: Magenta", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); + + + // ===== GEOMETRY FEATURES ===== + + /// + /// Clock (VCP 0x0E) + /// + public static VcpFeature Clock => new(0x0E, "clock", "Clock", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Horizontal Position (Phase) (VCP 0x20) + /// + public static VcpFeature HorizontalPosition => new(0x20, "h-position", "Horizontal Position (Phase)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false, "h-pos"); + + /// + /// Horizontal Size (VCP 0x22) + /// + public static VcpFeature HorizontalSize => new(0x22, "h-size", "Horizontal Size", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Horizontal Pincushion (VCP 0x24) + /// + public static VcpFeature HorizontalPincushion => new(0x24, "h-pincushion", "Horizontal Pincushion", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Horizontal Pincushion Balance (VCP 0x26) + /// + public static VcpFeature HorizontalPincushionBalance => new(0x26, "h-pincushion-balance", "Horizontal Pincushion Balance", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Horizontal Convergence R/B (VCP 0x28) + /// + public static VcpFeature HorizontalConvergenceRB => new(0x28, "h-convergence-rb", "Horizontal Convergence R/B", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Horizontal Convergence M/G (VCP 0x29) + /// + public static VcpFeature HorizontalConvergenceMG => new(0x29, "h-convergence-mg", "Horizontal Convergence M/G", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Horizontal Linearity (VCP 0x2A) + /// + public static VcpFeature HorizontalLinearity => new(0x2A, "h-linearity", "Horizontal Linearity", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Horizontal Linearity Balance (VCP 0x2C) + /// + public static VcpFeature HorizontalLinearityBalance => new(0x2C, "h-linearity-balance", "Horizontal Linearity Balance", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical Position (Phase) (VCP 0x30) + /// + public static VcpFeature VerticalPosition => new(0x30, "v-position", "Vertical Position (Phase)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false, "v-pos"); + + /// + /// Vertical Size (VCP 0x32) + /// + public static VcpFeature VerticalSize => new(0x32, "v-size", "Vertical Size", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical Pincushion (VCP 0x34) + /// + public static VcpFeature VerticalPincushion => new(0x34, "v-pincushion", "Vertical Pincushion", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical Pincushion Balance (VCP 0x36) + /// + public static VcpFeature VerticalPincushionBalance => new(0x36, "v-pincushion-balance", "Vertical Pincushion Balance", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical Convergence R/B (VCP 0x38) + /// + public static VcpFeature VerticalConvergenceRB => new(0x38, "v-convergence-rb", "Vertical Convergence R/B", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical Convergence M/G (VCP 0x39) + /// + public static VcpFeature VerticalConvergenceMG => new(0x39, "v-convergence-mg", "Vertical Convergence M/G", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical Linearity (VCP 0x3A) + /// + public static VcpFeature VerticalLinearity => new(0x3A, "v-linearity", "Vertical Linearity", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical Linearity Balance (VCP 0x3C) + /// + public static VcpFeature VerticalLinearityBalance => new(0x3C, "v-linearity-balance", "Vertical Linearity Balance", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Clock Phase (VCP 0x3E) + /// + public static VcpFeature ClockPhase => new(0x3E, "clock-phase", "Clock Phase", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false, "phase"); + + /// + /// Horizontal Parallelogram (VCP 0x40) + /// + public static VcpFeature HorizontalParallelogram => new(0x40, "horizontal-parallelogram", "Horizontal Parallelogram", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical Parallelogram (VCP 0x41) + /// + public static VcpFeature VerticalParallelogram => new(0x41, "vertical-parallelogram", "Vertical Parallelogram", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Horizontal Keystone (VCP 0x42) + /// + public static VcpFeature HorizontalKeystone => new(0x42, "horizontal-keystone", "Horizontal Keystone", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Vertical Keystone (VCP 0x43) + /// + public static VcpFeature VerticalKeystone => new(0x43, "vertical-keystone", "Vertical Keystone", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Rotation (VCP 0x44) + /// + public static VcpFeature Rotation => new(0x44, "rotation", "Rotation", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Top Corner Flare (VCP 0x46) + /// + public static VcpFeature TopCornerFlare => new(0x46, "top-corner-flare", "Top Corner Flare", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Top Corner Hook (VCP 0x48) + /// + public static VcpFeature TopCornerHook => new(0x48, "top-corner-hook", "Top Corner Hook", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Bottom Corner Flare (VCP 0x4A) + /// + public static VcpFeature BottomCornerFlare => new(0x4A, "bottom-corner-flare", "Bottom Corner Flare", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Bottom Corner Hook (VCP 0x4C) + /// + public static VcpFeature BottomCornerHook => new(0x4C, "bottom-corner-hook", "Bottom Corner Hook", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Window Position(TL_X) (VCP 0x95) + /// + public static VcpFeature WindowPositionTLX => new(0x95, "window-position-tl-x", "Window Position(TL_X)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Window Position(TL_Y) (VCP 0x96) + /// + public static VcpFeature WindowPositionTLY => new(0x96, "window-position-tl-y", "Window Position(TL_Y)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Window Position(BR_X) (VCP 0x97) + /// + public static VcpFeature WindowPositionBRX => new(0x97, "window-position-br-x", "Window Position(BR_X)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Window Position(BR_Y) (VCP 0x98) + /// + public static VcpFeature WindowPositionBRY => new(0x98, "window-position-br-y", "Window Position(BR_Y)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + /// + /// Window control on/off (VCP 0x99) + /// + public static VcpFeature WindowControlOnOff => new(0x99, "window-control-on-off", "Window control on/off", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); + + + // ===== IMAGEADJUSTMENT FEATURES ===== + + /// + /// Brightness control (VCP 0x10) + /// + public static VcpFeature Brightness => new(0x10, "brightness", "Brightness control", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true, "bright"); + + /// + /// Flesh tone enhancement (VCP 0x11) + /// + public static VcpFeature FleshToneEnhancement => new(0x11, "flesh-tone-enhancement", "Flesh tone enhancement", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Contrast control (VCP 0x12) + /// + public static VcpFeature Contrast => new(0x12, "contrast", "Contrast control", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Backlight control (VCP 0x13) + /// + public static VcpFeature Backlight => new(0x13, "backlight", "Backlight control", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Select color preset (VCP 0x14) + /// + public static VcpFeature SelectColorPreset => new(0x14, "select-color-preset", "Select color preset", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false, "color-preset"); + + /// + /// Focus (VCP 0x1C) + /// + public static VcpFeature Focus => new(0x1C, "focus", "Focus", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Auto setup (VCP 0x1E) + /// + public static VcpFeature AutoSetup => new(0x1E, "auto-setup", "Auto setup", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Auto color setup (VCP 0x1F) + /// + public static VcpFeature AutoColorSetup => new(0x1F, "auto-color-setup", "Auto color setup", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Gray scale expansion (VCP 0x2E) + /// + public static VcpFeature GrayScaleExpansion => new(0x2E, "gray-scale-expansion", "Gray scale expansion", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Horizontal Moire (VCP 0x56) + /// + public static VcpFeature HorizontalMoire => new(0x56, "horizontal-moire", "Horizontal Moire", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Vertical Moire (VCP 0x58) + /// + public static VcpFeature VerticalMoire => new(0x58, "vertical-moire", "Vertical Moire", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Input source selection (VCP 0x60) + /// + public static VcpFeature InputSource => new(0x60, "input", "Input source selection", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false, "source"); + + /// + /// Adjust Focal Plane (VCP 0x7A) + /// + public static VcpFeature AdjustFocalPlane => new(0x7A, "adjust-focal-plane", "Adjust Focal Plane", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Adjust Zoom (VCP 0x7C) + /// + public static VcpFeature AdjustZoom => new(0x7C, "adjust-zoom", "Adjust Zoom", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Trapezoid (VCP 0x7E) + /// + public static VcpFeature Trapezoid => new(0x7E, "trapezoid", "Trapezoid", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Keystone (VCP 0x80) + /// + public static VcpFeature Keystone => new(0x80, "keystone", "Keystone", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Horizontal Mirror (Flip) (VCP 0x82) + /// + public static VcpFeature HorizontalMirror => new(0x82, "horizontal-mirror", "Horizontal Mirror (Flip)", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Vertical Mirror (Flip) (VCP 0x84) + /// + public static VcpFeature VerticalMirror => new(0x84, "vertical-mirror", "Vertical Mirror (Flip)", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Display Scaling (VCP 0x86) + /// + public static VcpFeature DisplayScaling => new(0x86, "display-scaling", "Display Scaling", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Sharpness control (VCP 0x87) + /// + public static VcpFeature Sharpness => new(0x87, "sharpness", "Sharpness control", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true, "sharp"); + + /// + /// Velocity Scan Modulation (VCP 0x88) + /// + public static VcpFeature VelocityScanModulation => new(0x88, "velocity-scan-modulation", "Velocity Scan Modulation", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// TV Sharpness (VCP 0x8C) + /// + public static VcpFeature TVSharpness => new(0x8C, "tv-sharpness", "TV Sharpness", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// TV Contrast (VCP 0x8E) + /// + public static VcpFeature TVContrast => new(0x8E, "tv-contrast", "TV Contrast", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// TV Black level/Luminesence (VCP 0x92) + /// + public static VcpFeature TVBlackLevel => new(0x92, "tv-black-level", "TV Black level/Luminesence", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Window background (VCP 0x9A) + /// + public static VcpFeature WindowBackground => new(0x9A, "window-background", "Window background", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); + + /// + /// Auto setup on/off (VCP 0xA2) + /// + public static VcpFeature AutoSetupOnOff => new(0xA2, "auto-setup-on-off", "Auto setup on/off", VcpFeatureType.WriteOnly, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Window mask control (VCP 0xA4) + /// + public static VcpFeature WindowMaskControl => new(0xA4, "window-mask-control", "Window mask control", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Change the selected window (VCP 0xA5) + /// + public static VcpFeature ChangeSelectedWindow => new(0xA5, "change-selected-window", "Change the selected window", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Screen Orientation (VCP 0xAA) + /// + public static VcpFeature ScreenOrientation => new(0xAA, "screen-orientation", "Screen Orientation", VcpFeatureType.ReadOnly, VcpFeatureCategory.ImageAdjustment, false, "orientation"); + + /// + /// Stereo video mode (VCP 0xD4) + /// + public static VcpFeature StereoVideoMode => new(0xD4, "stereo-video-mode", "Stereo video mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Scan mode (VCP 0xDA) + /// + public static VcpFeature ScanMode => new(0xDA, "scan-mode", "Scan mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + /// + /// Image Mode (VCP 0xDB) + /// + public static VcpFeature ImageMode => new(0xDB, "image-mode", "Image Mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false, "mode"); + + /// + /// Display Mode (VCP 0xDC) + /// + public static VcpFeature DisplayMode => new(0xDC, "display-mode", "Display Mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); + + + // ===== MISCELLANEOUS FEATURES ===== + + /// + /// Active control (VCP 0x52) + /// + public static VcpFeature ActiveControl => new(0x52, "active-control", "Active control", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Performance Preservation (VCP 0x54) + /// + public static VcpFeature PerformancePreservation => new(0x54, "performance-preservation", "Performance Preservation", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Ambient light sensor (VCP 0x66) + /// + public static VcpFeature AmbientLightSensor => new(0x66, "ambient-light-sensor", "Ambient light sensor", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// LUT Size (VCP 0x73) + /// + public static VcpFeature LUTSize => new(0x73, "lut-size", "LUT Size", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Single point LUT operation (VCP 0x74) + /// + public static VcpFeature SinglePointLUTOperation => new(0x74, "single-point-lut-operation", "Single point LUT operation", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Block LUT operation (VCP 0x75) + /// + public static VcpFeature BlockLUTOperation => new(0x75, "block-lut-operation", "Block LUT operation", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Remote Procedure Call (VCP 0x76) + /// + public static VcpFeature RemoteProcedureCall => new(0x76, "remote-procedure-call", "Remote Procedure Call", VcpFeatureType.WriteOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Display Identification Operation (VCP 0x78) + /// + public static VcpFeature DisplayIdentificationOperation => new(0x78, "display-identification-operation", "Display Identification Operation", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// TV Channel Up/Down (VCP 0x8B) + /// + public static VcpFeature TVChannelUpDown => new(0x8B, "tv-channel-up-down", "TV Channel Up/Down", VcpFeatureType.WriteOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Horizontal frequency (VCP 0xAC) + /// + public static VcpFeature HorizontalFrequency => new(0xAC, "horizontal-frequency", "Horizontal frequency", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false, "h-freq"); + + /// + /// Vertical frequency (VCP 0xAE) + /// + public static VcpFeature VerticalFrequency => new(0xAE, "vertical-frequency", "Vertical frequency", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false, "v-freq"); + + /// + /// Settings (VCP 0xB0) + /// + public static VcpFeature Settings => new(0xB0, "settings", "Settings", VcpFeatureType.WriteOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Flat panel sub-pixel layout (VCP 0xB2) + /// + public static VcpFeature FlatPanelSubPixelLayout => new(0xB2, "flat-panel-sub-pixel-layout", "Flat panel sub-pixel layout", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Source Timing Mode (VCP 0xB4) + /// + public static VcpFeature SourceTimingMode => new(0xB4, "source-timing-mode", "Source Timing Mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Display technology type (VCP 0xB6) + /// + public static VcpFeature DisplayTechnologyType => new(0xB6, "display-technology-type", "Display technology type", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Monitor status (VCP 0xB7) + /// + public static VcpFeature MonitorStatus => new(0xB7, "monitor-status", "Monitor status", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Packet count (VCP 0xB8) + /// + public static VcpFeature PacketCount => new(0xB8, "packet-count", "Packet count", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Monitor X origin (VCP 0xB9) + /// + public static VcpFeature MonitorXOrigin => new(0xB9, "monitor-x-origin", "Monitor X origin", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Monitor Y origin (VCP 0xBA) + /// + public static VcpFeature MonitorYOrigin => new(0xBA, "monitor-y-origin", "Monitor Y origin", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Header error count (VCP 0xBB) + /// + public static VcpFeature HeaderErrorCount => new(0xBB, "header-error-count", "Header error count", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Body CRC error count (VCP 0xBC) + /// + public static VcpFeature BodyCRCErrorCount => new(0xBC, "body-crc-error-count", "Body CRC error count", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Client ID (VCP 0xBD) + /// + public static VcpFeature ClientID => new(0xBD, "client-id", "Client ID", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Link control (VCP 0xBE) + /// + public static VcpFeature LinkControl => new(0xBE, "link-control", "Link control", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Display usage time (VCP 0xC0) + /// + public static VcpFeature DisplayUsageTime => new(0xC0, "display-usage-time", "Display usage time", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Display descriptor length (VCP 0xC2) + /// + public static VcpFeature DisplayDescriptorLength => new(0xC2, "display-descriptor-length", "Display descriptor length", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Transmit display descriptor (VCP 0xC3) + /// + public static VcpFeature TransmitDisplayDescriptor => new(0xC3, "transmit-display-descriptor", "Transmit display descriptor", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Enable display of 'display descriptor' (VCP 0xC4) + /// + public static VcpFeature EnableDisplayOfDisplayDescriptor => new(0xC4, "enable-display-of-display-descriptor", "Enable display of 'display descriptor'", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Application enable key (VCP 0xC6) + /// + public static VcpFeature ApplicationEnableKey => new(0xC6, "application-enable-key", "Application enable key", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Display controller type (VCP 0xC8) + /// + public static VcpFeature DisplayControllerType => new(0xC8, "display-controller-type", "Display controller type", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Display firmware level (VCP 0xC9) + /// + public static VcpFeature DisplayFirmwareLevel => new(0xC9, "display-firmware-level", "Display firmware level", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false, "firmware"); + + /// + /// OSD/Button Control (VCP 0xCA) + /// + public static VcpFeature OSDButtonControl => new(0xCA, "osd-button-control", "OSD/Button Control", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false, "osd"); + + /// + /// OSD Language (VCP 0xCC) + /// + public static VcpFeature OSDLanguage => new(0xCC, "osd-language", "OSD Language", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false, "language"); + + /// + /// Status Indicators (VCP 0xCD) + /// + public static VcpFeature StatusIndicators => new(0xCD, "status-indicators", "Status Indicators", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Auxiliary display size (VCP 0xCE) + /// + public static VcpFeature AuxiliaryDisplaySize => new(0xCE, "auxiliary-display-size", "Auxiliary display size", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Auxiliary display data (VCP 0xCF) + /// + public static VcpFeature AuxiliaryDisplayData => new(0xCF, "auxiliary-display-data", "Auxiliary display data", VcpFeatureType.WriteOnly, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Output select (VCP 0xD0) + /// + public static VcpFeature OutputSelect => new(0xD0, "output-select", "Output select", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Asset Tag (VCP 0xD2) + /// + public static VcpFeature AssetTag => new(0xD2, "asset-tag", "Asset Tag", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Power mode (VCP 0xD6) + /// + public static VcpFeature PowerMode => new(0xD6, "power-mode", "Power mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false, "power"); + + /// + /// Auxiliary power output (VCP 0xD7) + /// + public static VcpFeature AuxiliaryPowerOutput => new(0xD7, "auxiliary-power-output", "Auxiliary power output", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// Scratch Pad (VCP 0xDE) + /// + public static VcpFeature ScratchPad => new(0xDE, "scratch-pad", "Scratch Pad", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); + + /// + /// VCP Version (VCP 0xDF) + /// + public static VcpFeature VcpVersion => new(0xDF, "vcp-version", "VCP Version", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false, "version"); + + + // ===== AUDIO FEATURES ===== + + /// + /// Audio speaker volume (VCP 0x62) + /// + public static VcpFeature AudioVolume => new(0x62, "volume", "Audio speaker volume", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, true, "vol"); + + /// + /// Speaker Select (VCP 0x63) + /// + public static VcpFeature SpeakerSelect => new(0x63, "speaker-select", "Speaker Select", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, false); + + /// + /// Audio: Microphone Volume (VCP 0x64) + /// + public static VcpFeature MicrophoneVolume => new(0x64, "microphone-volume", "Audio: Microphone Volume", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, true, "mic-volume"); + + /// + /// Audio mute/Screen blank (VCP 0x8D) + /// + public static VcpFeature AudioMute => new(0x8D, "mute", "Audio mute/Screen blank", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, false); + + /// + /// Audio treble (VCP 0x8F) + /// + public static VcpFeature AudioTreble => new(0x8F, "audio-treble", "Audio treble", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, true, "treble"); + + /// + /// Audio bass (VCP 0x91) + /// + public static VcpFeature AudioBass => new(0x91, "audio-bass", "Audio bass", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, true, "bass"); + + /// + /// Audio balance L/R (VCP 0x93) + /// + public static VcpFeature AudioBalance => new(0x93, "audio-balance", "Audio balance L/R", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, true, "balance"); + + /// + /// Audio processor mode (VCP 0x94) + /// + public static VcpFeature AudioProcessorMode => new(0x94, "audio-processor-mode", "Audio processor mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, false); + + + /// + /// All features registry - contains all predefined MCCS features + /// + public static IReadOnlyList AllFeatures { get; } = new List + { + Degauss, + NewControlValue, + SoftControls, + RestoreDefaults, + RestoreBrightnessContrastDefaults, + RestoreGeometryDefaults, + RestoreColorDefaults, + RestoreTVDefaults, + ColorTemperatureIncrement, + ColorTemperatureRequest, + Clock, + Brightness, + FleshToneEnhancement, + Contrast, + Backlight, + SelectColorPreset, + RedGain, + UserColorVisionCompensation, + GreenGain, + BlueGain, + Focus, + AutoSetup, + AutoColorSetup, + HorizontalPosition, + HorizontalSize, + HorizontalPincushion, + HorizontalPincushionBalance, + HorizontalConvergenceRB, + HorizontalConvergenceMG, + HorizontalLinearity, + HorizontalLinearityBalance, + GrayScaleExpansion, + VerticalPosition, + VerticalSize, + VerticalPincushion, + VerticalPincushionBalance, + VerticalConvergenceRB, + VerticalConvergenceMG, + VerticalLinearity, + VerticalLinearityBalance, + ClockPhase, + HorizontalParallelogram, + VerticalParallelogram, + HorizontalKeystone, + VerticalKeystone, + Rotation, + TopCornerFlare, + TopCornerHook, + BottomCornerFlare, + BottomCornerHook, + ActiveControl, + PerformancePreservation, + HorizontalMoire, + VerticalMoire, + SixAxisSaturationRed, + SixAxisSaturationYellow, + SixAxisSaturationGreen, + SixAxisSaturationCyan, + SixAxisSaturationBlue, + SixAxisSaturationMagenta, + InputSource, + AudioVolume, + SpeakerSelect, + MicrophoneVolume, + AmbientLightSensor, + BacklightLevelWhite, + RedBlackLevel, + BacklightLevelRed, + GreenBlackLevel, + BacklightLevelGreen, + BlueBlackLevel, + BacklightLevelBlue, + Gamma, + LUTSize, + SinglePointLUTOperation, + BlockLUTOperation, + RemoteProcedureCall, + DisplayIdentificationOperation, + AdjustFocalPlane, + AdjustZoom, + Trapezoid, + Keystone, + HorizontalMirror, + VerticalMirror, + DisplayScaling, + Sharpness, + VelocityScanModulation, + ColorSaturation, + TVChannelUpDown, + TVSharpness, + AudioMute, + TVContrast, + AudioTreble, + Hue, + AudioBass, + TVBlackLevel, + AudioBalance, + AudioProcessorMode, + WindowPositionTLX, + WindowPositionTLY, + WindowPositionBRX, + WindowPositionBRY, + WindowControlOnOff, + WindowBackground, + SixAxisHueRed, + SixAxisHueYellow, + SixAxisHueGreen, + SixAxisHueCyan, + SixAxisHueBlue, + SixAxisHueMagenta, + AutoSetupOnOff, + WindowMaskControl, + ChangeSelectedWindow, + ScreenOrientation, + HorizontalFrequency, + VerticalFrequency, + Settings, + FlatPanelSubPixelLayout, + SourceTimingMode, + DisplayTechnologyType, + MonitorStatus, + PacketCount, + MonitorXOrigin, + MonitorYOrigin, + HeaderErrorCount, + BodyCRCErrorCount, + ClientID, + LinkControl, + DisplayUsageTime, + DisplayDescriptorLength, + TransmitDisplayDescriptor, + EnableDisplayOfDisplayDescriptor, + ApplicationEnableKey, + DisplayControllerType, + DisplayFirmwareLevel, + OSDButtonControl, + OSDLanguage, + StatusIndicators, + AuxiliaryDisplaySize, + AuxiliaryDisplayData, + OutputSelect, + AssetTag, + StereoVideoMode, + PowerMode, + AuxiliaryPowerOutput, + ScanMode, + ImageMode, + DisplayMode, + ScratchPad, + VcpVersion + }; +} diff --git a/DDCSwitch/VcpFeature.cs b/DDCSwitch/VcpFeature.cs index b82093b..74c19c1 100644 --- a/DDCSwitch/VcpFeature.cs +++ b/DDCSwitch/VcpFeature.cs @@ -1,4 +1,4 @@ -namespace DDCSwitch; +namespace DDCSwitch; /// /// Defines the access type for a VCP feature @@ -58,9 +58,11 @@ public enum VcpFeatureCategory } /// -/// Represents a VCP (Virtual Control Panel) feature with its properties +/// Represents a VCP (Virtual Control Panel) feature with its properties. +/// This is a partial class - feature definitions are in VcpFeature.Generated.cs +/// which is auto-generated from VcpFeatureData.json. /// -public class VcpFeature +public partial class VcpFeature { public byte Code { get; } public string Name { get; } @@ -78,7 +80,7 @@ public VcpFeature(byte code, string name, string description, VcpFeatureType typ Type = type; Category = category; SupportsPercentage = supportsPercentage; - Aliases = aliases ?? Array.Empty(); + Aliases = aliases; } // Legacy constructor for backward compatibility @@ -87,952 +89,6 @@ public VcpFeature(byte code, string name, VcpFeatureType type, bool supportsPerc { } - // ===== IMAGE ADJUSTMENT FEATURES ===== - - /// - /// Brightness control (VCP 0x10) - /// - public static VcpFeature Brightness => new(0x10, "brightness", "Brightness control", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true, "bright"); - - /// - /// Flesh tone enhancement (VCP 0x11) - /// - public static VcpFeature FleshToneEnhancement => new(0x11, "flesh-tone-enhancement", "Flesh tone enhancement", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); - - /// - /// Contrast control (VCP 0x12) - /// - public static VcpFeature Contrast => new(0x12, "contrast", "Contrast control", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); - - /// - /// Backlight control (VCP 0x13) - /// - public static VcpFeature Backlight => new(0x13, "backlight", "Backlight control", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); - - /// - /// Select color preset (VCP 0x14) - /// - public static VcpFeature SelectColorPreset => new(0x14, "select-color-preset", "Select color preset", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false, "color-preset"); - - /// - /// Focus (VCP 0x1C) - /// - public static VcpFeature Focus => new(0x1C, "focus", "Focus", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); - - /// - /// Auto setup (VCP 0x1E) - /// - public static VcpFeature AutoSetup => new(0x1E, "auto-setup", "Auto setup", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); - - /// - /// Auto color setup (VCP 0x1F) - /// - public static VcpFeature AutoColorSetup => new(0x1F, "auto-color-setup", "Auto color setup", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); - - /// - /// Gray scale expansion (VCP 0x2E) - /// - public static VcpFeature GrayScaleExpansion => new(0x2E, "gray-scale-expansion", "Gray scale expansion", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); - - /// - /// Horizontal Moire (VCP 0x56) - /// - public static VcpFeature HorizontalMoire => new(0x56, "horizontal-moire", "Horizontal Moire", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); - - /// - /// Vertical Moire (VCP 0x58) - /// - public static VcpFeature VerticalMoire => new(0x58, "vertical-moire", "Vertical Moire", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); - - /// - /// Input source selection (VCP 0x60) - /// - public static VcpFeature InputSource => new(0x60, "input", "Input source selection", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false, "source"); - - /// - /// Adjust Focal Plane (VCP 0x7A) - /// - public static VcpFeature AdjustFocalPlane => new(0x7A, "adjust-focal-plane", "Adjust Focal Plane", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); - - /// - /// Adjust Zoom (VCP 0x7C) - /// - public static VcpFeature AdjustZoom => new(0x7C, "adjust-zoom", "Adjust Zoom", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); - - /// - /// Trapezoid (VCP 0x7E) - /// - public static VcpFeature Trapezoid => new(0x7E, "trapezoid", "Trapezoid", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); - - /// - /// Keystone (VCP 0x80) - /// - public static VcpFeature Keystone => new(0x80, "keystone", "Keystone", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); - - /// - /// Horizontal Mirror (Flip) (VCP 0x82) - /// - public static VcpFeature HorizontalMirror => new(0x82, "horizontal-mirror", "Horizontal Mirror (Flip)", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); - - /// - /// Vertical Mirror (Flip) (VCP 0x84) - /// - public static VcpFeature VerticalMirror => new(0x84, "vertical-mirror", "Vertical Mirror (Flip)", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); - - /// - /// Display Scaling (VCP 0x86) - /// - public static VcpFeature DisplayScaling => new(0x86, "display-scaling", "Display Scaling", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); - - /// - /// Sharpness control (VCP 0x87) - /// - public static VcpFeature Sharpness => new(0x87, "sharpness", "Sharpness control", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true, "sharp"); - - /// - /// Velocity Scan Modulation (VCP 0x88) - /// - public static VcpFeature VelocityScanModulation => new(0x88, "velocity-scan-modulation", "Velocity Scan Modulation", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); - - /// - /// TV Sharpness (VCP 0x8C) - /// - public static VcpFeature TVSharpness => new(0x8C, "tv-sharpness", "TV Sharpness", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); - - /// - /// TV Contrast (VCP 0x8E) - /// - public static VcpFeature TVContrast => new(0x8E, "tv-contrast", "TV Contrast", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); - - /// - /// TV Black level/Luminesence (VCP 0x92) - /// - public static VcpFeature TVBlackLevel => new(0x92, "tv-black-level", "TV Black level/Luminesence", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); - - /// - /// Window background (VCP 0x9A) - /// - public static VcpFeature WindowBackground => new(0x9A, "window-background", "Window background", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, true); - - /// - /// Auto setup on/off (VCP 0xA2) - /// - public static VcpFeature AutoSetupOnOff => new(0xA2, "auto-setup-on-off", "Auto setup on/off", VcpFeatureType.WriteOnly, VcpFeatureCategory.ImageAdjustment, false); - - /// - /// Window mask control (VCP 0xA4) - /// - public static VcpFeature WindowMaskControl => new(0xA4, "window-mask-control", "Window mask control", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); - - /// - /// Change the selected window (VCP 0xA5) - /// - public static VcpFeature ChangeSelectedWindow => new(0xA5, "change-selected-window", "Change the selected window", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); - - /// - /// Screen Orientation (VCP 0xAA) - /// - public static VcpFeature ScreenOrientation => new(0xAA, "screen-orientation", "Screen Orientation", VcpFeatureType.ReadOnly, VcpFeatureCategory.ImageAdjustment, false, "orientation"); - - /// - /// Stereo video mode (VCP 0xD4) - /// - public static VcpFeature StereoVideoMode => new(0xD4, "stereo-video-mode", "Stereo video mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); - - /// - /// Scan mode (VCP 0xDA) - /// - public static VcpFeature ScanMode => new(0xDA, "scan-mode", "Scan mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); - - /// - /// Image Mode (VCP 0xDB) - /// - public static VcpFeature ImageMode => new(0xDB, "image-mode", "Image Mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false, "mode"); - - /// - /// Display Mode (VCP 0xDC) - /// - public static VcpFeature DisplayMode => new(0xDC, "display-mode", "Display Mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.ImageAdjustment, false); - - // ===== COLOR CONTROL FEATURES ===== - - /// - /// Red video gain (VCP 0x16) - /// - public static VcpFeature RedGain => new(0x16, "red-gain", "Video gain: Red", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true, "red"); - - /// - /// User color vision compensation (VCP 0x17) - /// - public static VcpFeature UserColorVisionCompensation => new(0x17, "user-color-vision-compensation", "User color vision compensation", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// Green video gain (VCP 0x18) - /// - public static VcpFeature GreenGain => new(0x18, "green-gain", "Video gain: Green", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true, "green"); - - /// - /// Blue video gain (VCP 0x1A) - /// - public static VcpFeature BlueGain => new(0x1A, "blue-gain", "Video gain: Blue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true, "blue"); - - /// - /// 6 axis saturation: Red (VCP 0x59) - /// - public static VcpFeature SixAxisSaturationRed => new(0x59, "6-axis-saturation-red", "6 axis saturation: Red", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// 6 axis saturation: Yellow (VCP 0x5A) - /// - public static VcpFeature SixAxisSaturationYellow => new(0x5A, "6-axis-saturation-yellow", "6 axis saturation: Yellow", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// 6 axis saturation: Green (VCP 0x5B) - /// - public static VcpFeature SixAxisSaturationGreen => new(0x5B, "6-axis-saturation-green", "6 axis saturation: Green", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// 6 axis saturation: Cyan (VCP 0x5C) - /// - public static VcpFeature SixAxisSaturationCyan => new(0x5C, "6-axis-saturation-cyan", "6 axis saturation: Cyan", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// 6 axis saturation: Blue (VCP 0x5D) - /// - public static VcpFeature SixAxisSaturationBlue => new(0x5D, "6-axis-saturation-blue", "6 axis saturation: Blue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// 6 axis saturation: Magenta (VCP 0x5E) - /// - public static VcpFeature SixAxisSaturationMagenta => new(0x5E, "6-axis-saturation-magenta", "6 axis saturation: Magenta", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// Backlight Level: White (VCP 0x6B) - /// - public static VcpFeature BacklightLevelWhite => new(0x6B, "backlight-level-white", "Backlight Level: White", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// Red video black level (VCP 0x6C) - /// - public static VcpFeature RedBlackLevel => new(0x6C, "red-black-level", "Video black level: Red", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// Backlight Level: Red (VCP 0x6D) - /// - public static VcpFeature BacklightLevelRed => new(0x6D, "backlight-level-red", "Backlight Level: Red", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// Green video black level (VCP 0x6E) - /// - public static VcpFeature GreenBlackLevel => new(0x6E, "green-black-level", "Video black level: Green", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// Backlight Level: Green (VCP 0x6F) - /// - public static VcpFeature BacklightLevelGreen => new(0x6F, "backlight-level-green", "Backlight Level: Green", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// Blue video black level (VCP 0x70) - /// - public static VcpFeature BlueBlackLevel => new(0x70, "blue-black-level", "Video black level: Blue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// Backlight Level: Blue (VCP 0x71) - /// - public static VcpFeature BacklightLevelBlue => new(0x71, "backlight-level-blue", "Backlight Level: Blue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// Gamma (VCP 0x72) - /// - public static VcpFeature Gamma => new(0x72, "gamma", "Gamma", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, false); - - /// - /// Color Saturation (VCP 0x8A) - /// - public static VcpFeature ColorSaturation => new(0x8A, "color-saturation", "Color Saturation", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true, "saturation", "sat"); - - /// - /// Hue (VCP 0x90) - /// - public static VcpFeature Hue => new(0x90, "hue", "Hue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// 6 axis hue control: Red (VCP 0x9B) - /// - public static VcpFeature SixAxisHueRed => new(0x9B, "6-axis-hue-red", "6 axis hue control: Red", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// 6 axis hue control: Yellow (VCP 0x9C) - /// - public static VcpFeature SixAxisHueYellow => new(0x9C, "6-axis-hue-yellow", "6 axis hue control: Yellow", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// 6 axis hue control: Green (VCP 0x9D) - /// - public static VcpFeature SixAxisHueGreen => new(0x9D, "6-axis-hue-green", "6 axis hue control: Green", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// 6 axis hue control: Cyan (VCP 0x9E) - /// - public static VcpFeature SixAxisHueCyan => new(0x9E, "6-axis-hue-cyan", "6 axis hue control: Cyan", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// 6 axis hue control: Blue (VCP 0x9F) - /// - public static VcpFeature SixAxisHueBlue => new(0x9F, "6-axis-hue-blue", "6 axis hue control: Blue", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - /// - /// 6 axis hue control: Magenta (VCP 0xA0) - /// - public static VcpFeature SixAxisHueMagenta => new(0xA0, "6-axis-hue-magenta", "6 axis hue control: Magenta", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, true); - - // ===== GEOMETRY FEATURES (mainly CRT) ===== - - /// - /// Horizontal position (VCP 0x20) - /// - public static VcpFeature HorizontalPosition => new(0x20, "h-position", "Horizontal Position (Phase)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false, "h-pos"); - - /// - /// Horizontal size (VCP 0x22) - /// - public static VcpFeature HorizontalSize => new(0x22, "h-size", "Horizontal Size", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Horizontal pincushion (VCP 0x24) - /// - public static VcpFeature HorizontalPincushion => new(0x24, "h-pincushion", "Horizontal Pincushion", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Horizontal pincushion balance (VCP 0x26) - /// - public static VcpFeature HorizontalPincushionBalance => new(0x26, "h-pincushion-balance", "Horizontal Pincushion Balance", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Horizontal convergence R/B (VCP 0x28) - /// - public static VcpFeature HorizontalConvergenceRB => new(0x28, "h-convergence-rb", "Horizontal Convergence R/B", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Horizontal convergence M/G (VCP 0x29) - /// - public static VcpFeature HorizontalConvergenceMG => new(0x29, "h-convergence-mg", "Horizontal Convergence M/G", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Horizontal linearity (VCP 0x2A) - /// - public static VcpFeature HorizontalLinearity => new(0x2A, "h-linearity", "Horizontal Linearity", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Horizontal linearity balance (VCP 0x2C) - /// - public static VcpFeature HorizontalLinearityBalance => new(0x2C, "h-linearity-balance", "Horizontal Linearity Balance", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Vertical position (VCP 0x30) - /// - public static VcpFeature VerticalPosition => new(0x30, "v-position", "Vertical Position (Phase)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false, "v-pos"); - - /// - /// Vertical size (VCP 0x32) - /// - public static VcpFeature VerticalSize => new(0x32, "v-size", "Vertical Size", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Vertical pincushion (VCP 0x34) - /// - public static VcpFeature VerticalPincushion => new(0x34, "v-pincushion", "Vertical Pincushion", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Vertical pincushion balance (VCP 0x36) - /// - public static VcpFeature VerticalPincushionBalance => new(0x36, "v-pincushion-balance", "Vertical Pincushion Balance", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Vertical convergence R/B (VCP 0x38) - /// - public static VcpFeature VerticalConvergenceRB => new(0x38, "v-convergence-rb", "Vertical Convergence R/B", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Vertical convergence M/G (VCP 0x39) - /// - public static VcpFeature VerticalConvergenceMG => new(0x39, "v-convergence-mg", "Vertical Convergence M/G", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Vertical linearity (VCP 0x3A) - /// - public static VcpFeature VerticalLinearity => new(0x3A, "v-linearity", "Vertical Linearity", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Vertical linearity balance (VCP 0x3C) - /// - public static VcpFeature VerticalLinearityBalance => new(0x3C, "v-linearity-balance", "Vertical Linearity Balance", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Clock phase (VCP 0x3E) - /// - public static VcpFeature ClockPhase => new(0x3E, "clock-phase", "Clock Phase", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false, "phase"); - - /// - /// Horizontal Parallelogram (VCP 0x40) - /// - public static VcpFeature HorizontalParallelogram => new(0x40, "horizontal-parallelogram", "Horizontal Parallelogram", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Vertical Parallelogram (VCP 0x41) - /// - public static VcpFeature VerticalParallelogram => new(0x41, "vertical-parallelogram", "Vertical Parallelogram", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Horizontal Keystone (VCP 0x42) - /// - public static VcpFeature HorizontalKeystone => new(0x42, "horizontal-keystone", "Horizontal Keystone", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Vertical Keystone (VCP 0x43) - /// - public static VcpFeature VerticalKeystone => new(0x43, "vertical-keystone", "Vertical Keystone", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Rotation (VCP 0x44) - /// - public static VcpFeature Rotation => new(0x44, "rotation", "Rotation", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Top Corner Flare (VCP 0x46) - /// - public static VcpFeature TopCornerFlare => new(0x46, "top-corner-flare", "Top Corner Flare", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Top Corner Hook (VCP 0x48) - /// - public static VcpFeature TopCornerHook => new(0x48, "top-corner-hook", "Top Corner Hook", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Bottom Corner Flare (VCP 0x4A) - /// - public static VcpFeature BottomCornerFlare => new(0x4A, "bottom-corner-flare", "Bottom Corner Flare", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Bottom Corner Hook (VCP 0x4C) - /// - public static VcpFeature BottomCornerHook => new(0x4C, "bottom-corner-hook", "Bottom Corner Hook", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Window Position(TL_X) (VCP 0x95) - /// - public static VcpFeature WindowPositionTLX => new(0x95, "window-position-tl-x", "Window Position(TL_X)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Window Position(TL_Y) (VCP 0x96) - /// - public static VcpFeature WindowPositionTLY => new(0x96, "window-position-tl-y", "Window Position(TL_Y)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Window Position(BR_X) (VCP 0x97) - /// - public static VcpFeature WindowPositionBRX => new(0x97, "window-position-br-x", "Window Position(BR_X)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Window Position(BR_Y) (VCP 0x98) - /// - public static VcpFeature WindowPositionBRY => new(0x98, "window-position-br-y", "Window Position(BR_Y)", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - /// - /// Window control on/off (VCP 0x99) - /// - public static VcpFeature WindowControlOnOff => new(0x99, "window-control-on-off", "Window control on/off", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - // ===== AUDIO FEATURES ===== - - /// - /// Audio speaker volume (VCP 0x62) - /// - public static VcpFeature AudioVolume => new(0x62, "volume", "Audio speaker volume", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, true, "vol"); - - /// - /// Speaker Select (VCP 0x63) - /// - public static VcpFeature SpeakerSelect => new(0x63, "speaker-select", "Speaker Select", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, false); - - /// - /// Audio: Microphone Volume (VCP 0x64) - /// - public static VcpFeature MicrophoneVolume => new(0x64, "microphone-volume", "Audio: Microphone Volume", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, true, "mic-volume"); - - /// - /// Audio mute/Screen blank (VCP 0x8D) - /// - public static VcpFeature AudioMute => new(0x8D, "mute", "Audio mute/Screen blank", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, false); - - /// - /// Audio treble (VCP 0x8F) - /// - public static VcpFeature AudioTreble => new(0x8F, "audio-treble", "Audio treble", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, true, "treble"); - - /// - /// Audio bass (VCP 0x91) - /// - public static VcpFeature AudioBass => new(0x91, "audio-bass", "Audio bass", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, true, "bass"); - - /// - /// Audio balance L/R (VCP 0x93) - /// - public static VcpFeature AudioBalance => new(0x93, "audio-balance", "Audio balance L/R", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, true, "balance"); - - /// - /// Audio processor mode (VCP 0x94) - /// - public static VcpFeature AudioProcessorMode => new(0x94, "audio-processor-mode", "Audio processor mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.Audio, false); - - // ===== PRESET FEATURES ===== - - /// - /// Degauss (VCP 0x01) - /// - public static VcpFeature Degauss => new(0x01, "degauss", "Degauss", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false); - - /// - /// New control value (VCP 0x02) - /// - public static VcpFeature NewControlValue => new(0x02, "new-control-value", "New control value", VcpFeatureType.ReadWrite, VcpFeatureCategory.Preset, false); - - /// - /// Soft controls (VCP 0x03) - /// - public static VcpFeature SoftControls => new(0x03, "soft-controls", "Soft controls", VcpFeatureType.ReadWrite, VcpFeatureCategory.Preset, false); - - /// - /// Restore factory defaults (VCP 0x04) - /// - public static VcpFeature RestoreDefaults => new(0x04, "restore-defaults", "Restore factory defaults", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false, "factory-reset"); - - /// - /// Restore factory brightness/contrast defaults (VCP 0x05) - /// - public static VcpFeature RestoreBrightnessContrastDefaults => new(0x05, "restore-brightness-contrast", "Restore factory brightness/contrast defaults", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false); - - /// - /// Restore factory geometry defaults (VCP 0x06) - /// - public static VcpFeature RestoreGeometryDefaults => new(0x06, "restore-geometry", "Restore factory geometry defaults", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false); - - /// - /// Restore factory color defaults (VCP 0x08) - /// - public static VcpFeature RestoreColorDefaults => new(0x08, "restore-color", "Restore factory color defaults", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false); - - /// - /// Restore factory TV defaults (VCP 0x0A) - /// - public static VcpFeature RestoreTVDefaults => new(0x0A, "restore-tv-defaults", "Restore factory TV defaults", VcpFeatureType.WriteOnly, VcpFeatureCategory.Preset, false); - - /// - /// Color temperature increment (VCP 0x0B) - /// - public static VcpFeature ColorTemperatureIncrement => new(0x0B, "color-temp-increment", "Color temperature increment", VcpFeatureType.ReadOnly, VcpFeatureCategory.ColorControl, false); - - /// - /// Color temperature request (VCP 0x0C) - /// - public static VcpFeature ColorTemperatureRequest => new(0x0C, "color-temp-request", "Color temperature request", VcpFeatureType.ReadWrite, VcpFeatureCategory.ColorControl, false, "color-temp"); - - /// - /// Clock (VCP 0x0E) - /// - public static VcpFeature Clock => new(0x0E, "clock", "Clock", VcpFeatureType.ReadWrite, VcpFeatureCategory.Geometry, false); - - // ===== MISCELLANEOUS FEATURES ===== - - /// - /// Active control (VCP 0x52) - /// - public static VcpFeature ActiveControl => new(0x52, "active-control", "Active control", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Performance Preservation (VCP 0x54) - /// - public static VcpFeature PerformancePreservation => new(0x54, "performance-preservation", "Performance Preservation", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Ambient light sensor (VCP 0x66) - /// - public static VcpFeature AmbientLightSensor => new(0x66, "ambient-light-sensor", "Ambient light sensor", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// LUT Size (VCP 0x73) - /// - public static VcpFeature LUTSize => new(0x73, "lut-size", "LUT Size", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Single point LUT operation (VCP 0x74) - /// - public static VcpFeature SinglePointLUTOperation => new(0x74, "single-point-lut-operation", "Single point LUT operation", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Block LUT operation (VCP 0x75) - /// - public static VcpFeature BlockLUTOperation => new(0x75, "block-lut-operation", "Block LUT operation", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Remote Procedure Call (VCP 0x76) - /// - public static VcpFeature RemoteProcedureCall => new(0x76, "remote-procedure-call", "Remote Procedure Call", VcpFeatureType.WriteOnly, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Display Identification Operation (VCP 0x78) - /// - public static VcpFeature DisplayIdentificationOperation => new(0x78, "display-identification-operation", "Display Identification Operation", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); - - /// - /// TV Channel Up/Down (VCP 0x8B) - /// - public static VcpFeature TVChannelUpDown => new(0x8B, "tv-channel-up-down", "TV Channel Up/Down", VcpFeatureType.WriteOnly, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Horizontal frequency (VCP 0xAC) - /// - public static VcpFeature HorizontalFrequency => new(0xAC, "horizontal-frequency", "Horizontal frequency", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false, "h-freq"); - - /// - /// Vertical frequency (VCP 0xAE) - /// - public static VcpFeature VerticalFrequency => new(0xAE, "vertical-frequency", "Vertical frequency", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false, "v-freq"); - - /// - /// Settings (VCP 0xB0) - /// - public static VcpFeature Settings => new(0xB0, "settings", "Settings", VcpFeatureType.WriteOnly, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Flat panel sub-pixel layout (VCP 0xB2) - /// - public static VcpFeature FlatPanelSubPixelLayout => new(0xB2, "flat-panel-sub-pixel-layout", "Flat panel sub-pixel layout", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Source Timing Mode (VCP 0xB4) - /// - public static VcpFeature SourceTimingMode => new(0xB4, "source-timing-mode", "Source Timing Mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Display technology type (VCP 0xB6) - /// - public static VcpFeature DisplayTechnologyType => new(0xB6, "display-technology-type", "Display technology type", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Monitor status (VCP 0xB7) - /// - public static VcpFeature MonitorStatus => new(0xB7, "monitor-status", "Monitor status", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Packet count (VCP 0xB8) - /// - public static VcpFeature PacketCount => new(0xB8, "packet-count", "Packet count", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Monitor X origin (VCP 0xB9) - /// - public static VcpFeature MonitorXOrigin => new(0xB9, "monitor-x-origin", "Monitor X origin", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Monitor Y origin (VCP 0xBA) - /// - public static VcpFeature MonitorYOrigin => new(0xBA, "monitor-y-origin", "Monitor Y origin", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Header error count (VCP 0xBB) - /// - public static VcpFeature HeaderErrorCount => new(0xBB, "header-error-count", "Header error count", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Body CRC error count (VCP 0xBC) - /// - public static VcpFeature BodyCRCErrorCount => new(0xBC, "body-crc-error-count", "Body CRC error count", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Client ID (VCP 0xBD) - /// - public static VcpFeature ClientID => new(0xBD, "client-id", "Client ID", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Link control (VCP 0xBE) - /// - public static VcpFeature LinkControl => new(0xBE, "link-control", "Link control", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Display usage time (VCP 0xC0) - /// - public static VcpFeature DisplayUsageTime => new(0xC0, "display-usage-time", "Display usage time", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Display descriptor length (VCP 0xC2) - /// - public static VcpFeature DisplayDescriptorLength => new(0xC2, "display-descriptor-length", "Display descriptor length", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Transmit display descriptor (VCP 0xC3) - /// - public static VcpFeature TransmitDisplayDescriptor => new(0xC3, "transmit-display-descriptor", "Transmit display descriptor", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Enable display of 'display descriptor' (VCP 0xC4) - /// - public static VcpFeature EnableDisplayOfDisplayDescriptor => new(0xC4, "enable-display-of-display-descriptor", "Enable display of 'display descriptor'", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Application enable key (VCP 0xC6) - /// - public static VcpFeature ApplicationEnableKey => new(0xC6, "application-enable-key", "Application enable key", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Display controller type (VCP 0xC8) - /// - public static VcpFeature DisplayControllerType => new(0xC8, "display-controller-type", "Display controller type", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Display firmware level (VCP 0xC9) - /// - public static VcpFeature DisplayFirmwareLevel => new(0xC9, "display-firmware-level", "Display firmware level", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false, "firmware"); - - /// - /// OSD/Button Control (VCP 0xCA) - /// - public static VcpFeature OSDButtonControl => new(0xCA, "osd-button-control", "OSD/Button Control", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false, "osd"); - - /// - /// OSD Language (VCP 0xCC) - /// - public static VcpFeature OSDLanguage => new(0xCC, "osd-language", "OSD Language", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false, "language"); - - /// - /// Status Indicators (VCP 0xCD) - /// - public static VcpFeature StatusIndicators => new(0xCD, "status-indicators", "Status Indicators", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Auxiliary display size (VCP 0xCE) - /// - public static VcpFeature AuxiliaryDisplaySize => new(0xCE, "auxiliary-display-size", "Auxiliary display size", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Auxiliary display data (VCP 0xCF) - /// - public static VcpFeature AuxiliaryDisplayData => new(0xCF, "auxiliary-display-data", "Auxiliary display data", VcpFeatureType.WriteOnly, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Output select (VCP 0xD0) - /// - public static VcpFeature OutputSelect => new(0xD0, "output-select", "Output select", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Asset Tag (VCP 0xD2) - /// - public static VcpFeature AssetTag => new(0xD2, "asset-tag", "Asset Tag", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Power mode (VCP 0xD6) - /// - public static VcpFeature PowerMode => new(0xD6, "power-mode", "Power mode", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false, "power"); - - /// - /// Auxiliary power output (VCP 0xD7) - /// - public static VcpFeature AuxiliaryPowerOutput => new(0xD7, "auxiliary-power-output", "Auxiliary power output", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// Scratch Pad (VCP 0xDE) - /// - public static VcpFeature ScratchPad => new(0xDE, "scratch-pad", "Scratch Pad", VcpFeatureType.ReadWrite, VcpFeatureCategory.Miscellaneous, false); - - /// - /// VCP Version (VCP 0xDF) - /// - public static VcpFeature VcpVersion => new(0xDF, "vcp-version", "VCP Version", VcpFeatureType.ReadOnly, VcpFeatureCategory.Miscellaneous, false, "version"); - - /// - /// All features registry - contains all predefined MCCS features - /// - public static IReadOnlyList AllFeatures { get; } = BuildAllFeatures(); - - /// - /// Builds the complete registry of all MCCS VCP features - /// - private static List BuildAllFeatures() - { - return new List - { - // Preset Features (0x01-0x0F range) - Degauss, - NewControlValue, - SoftControls, - RestoreDefaults, - RestoreBrightnessContrastDefaults, - RestoreGeometryDefaults, - RestoreColorDefaults, - RestoreTVDefaults, - ColorTemperatureIncrement, - ColorTemperatureRequest, - Clock, - - // Image Adjustment Features (0x10-0x1F range) - Brightness, - FleshToneEnhancement, - Contrast, - Backlight, - SelectColorPreset, - RedGain, - UserColorVisionCompensation, - GreenGain, - BlueGain, - Focus, - AutoSetup, - AutoColorSetup, - - // Geometry Features (0x20-0x4F range) - HorizontalPosition, - HorizontalSize, - HorizontalPincushion, - HorizontalPincushionBalance, - HorizontalConvergenceRB, - HorizontalConvergenceMG, - HorizontalLinearity, - HorizontalLinearityBalance, - GrayScaleExpansion, - VerticalPosition, - VerticalSize, - VerticalPincushion, - VerticalPincushionBalance, - VerticalConvergenceRB, - VerticalConvergenceMG, - VerticalLinearity, - VerticalLinearityBalance, - ClockPhase, - HorizontalParallelogram, - VerticalParallelogram, - HorizontalKeystone, - VerticalKeystone, - Rotation, - TopCornerFlare, - TopCornerHook, - BottomCornerFlare, - BottomCornerHook, - - // Control and Performance Features (0x50-0x5F range) - ActiveControl, - PerformancePreservation, - HorizontalMoire, - VerticalMoire, - SixAxisSaturationRed, - SixAxisSaturationYellow, - SixAxisSaturationGreen, - SixAxisSaturationCyan, - SixAxisSaturationBlue, - SixAxisSaturationMagenta, - - // Input and Audio Features (0x60-0x6F range) - InputSource, - AudioVolume, - SpeakerSelect, - MicrophoneVolume, - AmbientLightSensor, - BacklightLevelWhite, - RedBlackLevel, - BacklightLevelRed, - GreenBlackLevel, - BacklightLevelGreen, - BlueBlackLevel, - BacklightLevelBlue, - - // Advanced Color and LUT Features (0x70-0x7F range) - Gamma, - LUTSize, - SinglePointLUTOperation, - BlockLUTOperation, - RemoteProcedureCall, - DisplayIdentificationOperation, - AdjustFocalPlane, - AdjustZoom, - Trapezoid, - - // Image Processing Features (0x80-0x9F range) - Keystone, - HorizontalMirror, - VerticalMirror, - DisplayScaling, - Sharpness, - VelocityScanModulation, - ColorSaturation, - TVChannelUpDown, - TVSharpness, - AudioMute, - TVContrast, - AudioTreble, - Hue, - AudioBass, - TVBlackLevel, - AudioBalance, - AudioProcessorMode, - WindowPositionTLX, - WindowPositionTLY, - WindowPositionBRX, - WindowPositionBRY, - WindowControlOnOff, - WindowBackground, - SixAxisHueRed, - SixAxisHueYellow, - SixAxisHueGreen, - SixAxisHueCyan, - SixAxisHueBlue, - SixAxisHueMagenta, - - // Setup and Window Features (0xA0-0xAF range) - AutoSetupOnOff, - WindowMaskControl, - ChangeSelectedWindow, - ScreenOrientation, - HorizontalFrequency, - VerticalFrequency, - - // System Information Features (0xB0-0xBF range) - Settings, - FlatPanelSubPixelLayout, - SourceTimingMode, - DisplayTechnologyType, - MonitorStatus, - PacketCount, - MonitorXOrigin, - MonitorYOrigin, - HeaderErrorCount, - BodyCRCErrorCount, - ClientID, - LinkControl, - - // Display Management Features (0xC0-0xDF range) - DisplayUsageTime, - DisplayDescriptorLength, - TransmitDisplayDescriptor, - EnableDisplayOfDisplayDescriptor, - ApplicationEnableKey, - DisplayControllerType, - DisplayFirmwareLevel, - OSDButtonControl, - OSDLanguage, - StatusIndicators, - AuxiliaryDisplaySize, - AuxiliaryDisplayData, - OutputSelect, - AssetTag, - StereoVideoMode, - PowerMode, - AuxiliaryPowerOutput, - ScanMode, - ImageMode, - DisplayMode, - ScratchPad, - VcpVersion - }; - } - public override string ToString() { return $"{Name} (0x{Code:X2})"; @@ -1061,4 +117,5 @@ public record VcpFeatureInfo( uint CurrentValue, uint MaxValue, bool IsSupported -); \ No newline at end of file +); + diff --git a/DDCSwitch/VcpFeatureData.json b/DDCSwitch/VcpFeatureData.json new file mode 100644 index 0000000..e555c5e --- /dev/null +++ b/DDCSwitch/VcpFeatureData.json @@ -0,0 +1,1561 @@ +{ + "$schema": "vcp-feature-schema", + "description": "VCP (VESA Display Data Channel Command Interface) feature definitions from MCCS specification", + "features": [ + { + "code": "0x01", + "property": "Degauss", + "name": "degauss", + "description": "Degauss", + "type": "WriteOnly", + "category": "Preset", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x02", + "property": "NewControlValue", + "name": "new-control-value", + "description": "New control value", + "type": "ReadWrite", + "category": "Preset", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x03", + "property": "SoftControls", + "name": "soft-controls", + "description": "Soft controls", + "type": "ReadWrite", + "category": "Preset", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x04", + "property": "RestoreDefaults", + "name": "restore-defaults", + "description": "Restore factory defaults", + "type": "WriteOnly", + "category": "Preset", + "supportsPercentage": false, + "aliases": [ + "factory-reset" + ] + }, + { + "code": "0x05", + "property": "RestoreBrightnessContrastDefaults", + "name": "restore-brightness-contrast", + "description": "Restore factory brightness/contrast defaults", + "type": "WriteOnly", + "category": "Preset", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x06", + "property": "RestoreGeometryDefaults", + "name": "restore-geometry", + "description": "Restore factory geometry defaults", + "type": "WriteOnly", + "category": "Preset", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x08", + "property": "RestoreColorDefaults", + "name": "restore-color", + "description": "Restore factory color defaults", + "type": "WriteOnly", + "category": "Preset", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x0A", + "property": "RestoreTVDefaults", + "name": "restore-tv-defaults", + "description": "Restore factory TV defaults", + "type": "WriteOnly", + "category": "Preset", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x0B", + "property": "ColorTemperatureIncrement", + "name": "color-temp-increment", + "description": "Color temperature increment", + "type": "ReadOnly", + "category": "ColorControl", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x0C", + "property": "ColorTemperatureRequest", + "name": "color-temp-request", + "description": "Color temperature request", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": false, + "aliases": [ + "color-temp" + ] + }, + { + "code": "0x0E", + "property": "Clock", + "name": "clock", + "description": "Clock", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x10", + "property": "Brightness", + "name": "brightness", + "description": "Brightness control", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": true, + "aliases": [ + "bright" + ] + }, + { + "code": "0x11", + "property": "FleshToneEnhancement", + "name": "flesh-tone-enhancement", + "description": "Flesh tone enhancement", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x12", + "property": "Contrast", + "name": "contrast", + "description": "Contrast control", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x13", + "property": "Backlight", + "name": "backlight", + "description": "Backlight control", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x14", + "property": "SelectColorPreset", + "name": "select-color-preset", + "description": "Select color preset", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [ + "color-preset" + ] + }, + { + "code": "0x16", + "property": "RedGain", + "name": "red-gain", + "description": "Video gain: Red", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [ + "red" + ] + }, + { + "code": "0x17", + "property": "UserColorVisionCompensation", + "name": "user-color-vision-compensation", + "description": "User color vision compensation", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x18", + "property": "GreenGain", + "name": "green-gain", + "description": "Video gain: Green", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [ + "green" + ] + }, + { + "code": "0x1A", + "property": "BlueGain", + "name": "blue-gain", + "description": "Video gain: Blue", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [ + "blue" + ] + }, + { + "code": "0x1C", + "property": "Focus", + "name": "focus", + "description": "Focus", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x1E", + "property": "AutoSetup", + "name": "auto-setup", + "description": "Auto setup", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x1F", + "property": "AutoColorSetup", + "name": "auto-color-setup", + "description": "Auto color setup", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x20", + "property": "HorizontalPosition", + "name": "h-position", + "description": "Horizontal Position (Phase)", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [ + "h-pos" + ] + }, + { + "code": "0x22", + "property": "HorizontalSize", + "name": "h-size", + "description": "Horizontal Size", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x24", + "property": "HorizontalPincushion", + "name": "h-pincushion", + "description": "Horizontal Pincushion", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x26", + "property": "HorizontalPincushionBalance", + "name": "h-pincushion-balance", + "description": "Horizontal Pincushion Balance", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x28", + "property": "HorizontalConvergenceRB", + "name": "h-convergence-rb", + "description": "Horizontal Convergence R/B", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x29", + "property": "HorizontalConvergenceMG", + "name": "h-convergence-mg", + "description": "Horizontal Convergence M/G", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x2A", + "property": "HorizontalLinearity", + "name": "h-linearity", + "description": "Horizontal Linearity", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x2C", + "property": "HorizontalLinearityBalance", + "name": "h-linearity-balance", + "description": "Horizontal Linearity Balance", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x2E", + "property": "GrayScaleExpansion", + "name": "gray-scale-expansion", + "description": "Gray scale expansion", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x30", + "property": "VerticalPosition", + "name": "v-position", + "description": "Vertical Position (Phase)", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [ + "v-pos" + ] + }, + { + "code": "0x32", + "property": "VerticalSize", + "name": "v-size", + "description": "Vertical Size", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x34", + "property": "VerticalPincushion", + "name": "v-pincushion", + "description": "Vertical Pincushion", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x36", + "property": "VerticalPincushionBalance", + "name": "v-pincushion-balance", + "description": "Vertical Pincushion Balance", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x38", + "property": "VerticalConvergenceRB", + "name": "v-convergence-rb", + "description": "Vertical Convergence R/B", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x39", + "property": "VerticalConvergenceMG", + "name": "v-convergence-mg", + "description": "Vertical Convergence M/G", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x3A", + "property": "VerticalLinearity", + "name": "v-linearity", + "description": "Vertical Linearity", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x3C", + "property": "VerticalLinearityBalance", + "name": "v-linearity-balance", + "description": "Vertical Linearity Balance", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x3E", + "property": "ClockPhase", + "name": "clock-phase", + "description": "Clock Phase", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [ + "phase" + ] + }, + { + "code": "0x40", + "property": "HorizontalParallelogram", + "name": "horizontal-parallelogram", + "description": "Horizontal Parallelogram", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x41", + "property": "VerticalParallelogram", + "name": "vertical-parallelogram", + "description": "Vertical Parallelogram", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x42", + "property": "HorizontalKeystone", + "name": "horizontal-keystone", + "description": "Horizontal Keystone", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x43", + "property": "VerticalKeystone", + "name": "vertical-keystone", + "description": "Vertical Keystone", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x44", + "property": "Rotation", + "name": "rotation", + "description": "Rotation", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x46", + "property": "TopCornerFlare", + "name": "top-corner-flare", + "description": "Top Corner Flare", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x48", + "property": "TopCornerHook", + "name": "top-corner-hook", + "description": "Top Corner Hook", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x4A", + "property": "BottomCornerFlare", + "name": "bottom-corner-flare", + "description": "Bottom Corner Flare", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x4C", + "property": "BottomCornerHook", + "name": "bottom-corner-hook", + "description": "Bottom Corner Hook", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x52", + "property": "ActiveControl", + "name": "active-control", + "description": "Active control", + "type": "ReadOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x54", + "property": "PerformancePreservation", + "name": "performance-preservation", + "description": "Performance Preservation", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x56", + "property": "HorizontalMoire", + "name": "horizontal-moire", + "description": "Horizontal Moire", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x58", + "property": "VerticalMoire", + "name": "vertical-moire", + "description": "Vertical Moire", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x59", + "property": "SixAxisSaturationRed", + "name": "6-axis-saturation-red", + "description": "6 axis saturation: Red", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x5A", + "property": "SixAxisSaturationYellow", + "name": "6-axis-saturation-yellow", + "description": "6 axis saturation: Yellow", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x5B", + "property": "SixAxisSaturationGreen", + "name": "6-axis-saturation-green", + "description": "6 axis saturation: Green", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x5C", + "property": "SixAxisSaturationCyan", + "name": "6-axis-saturation-cyan", + "description": "6 axis saturation: Cyan", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x5D", + "property": "SixAxisSaturationBlue", + "name": "6-axis-saturation-blue", + "description": "6 axis saturation: Blue", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x5E", + "property": "SixAxisSaturationMagenta", + "name": "6-axis-saturation-magenta", + "description": "6 axis saturation: Magenta", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x60", + "property": "InputSource", + "name": "input", + "description": "Input source selection", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [ + "source" + ] + }, + { + "code": "0x62", + "property": "AudioVolume", + "name": "volume", + "description": "Audio speaker volume", + "type": "ReadWrite", + "category": "Audio", + "supportsPercentage": true, + "aliases": [ + "vol" + ] + }, + { + "code": "0x63", + "property": "SpeakerSelect", + "name": "speaker-select", + "description": "Speaker Select", + "type": "ReadWrite", + "category": "Audio", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x64", + "property": "MicrophoneVolume", + "name": "microphone-volume", + "description": "Audio: Microphone Volume", + "type": "ReadWrite", + "category": "Audio", + "supportsPercentage": true, + "aliases": [ + "mic-volume" + ] + }, + { + "code": "0x66", + "property": "AmbientLightSensor", + "name": "ambient-light-sensor", + "description": "Ambient light sensor", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x6B", + "property": "BacklightLevelWhite", + "name": "backlight-level-white", + "description": "Backlight Level: White", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x6C", + "property": "RedBlackLevel", + "name": "red-black-level", + "description": "Video black level: Red", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x6D", + "property": "BacklightLevelRed", + "name": "backlight-level-red", + "description": "Backlight Level: Red", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x6E", + "property": "GreenBlackLevel", + "name": "green-black-level", + "description": "Video black level: Green", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x6F", + "property": "BacklightLevelGreen", + "name": "backlight-level-green", + "description": "Backlight Level: Green", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x70", + "property": "BlueBlackLevel", + "name": "blue-black-level", + "description": "Video black level: Blue", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x71", + "property": "BacklightLevelBlue", + "name": "backlight-level-blue", + "description": "Backlight Level: Blue", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x72", + "property": "Gamma", + "name": "gamma", + "description": "Gamma", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x73", + "property": "LUTSize", + "name": "lut-size", + "description": "LUT Size", + "type": "ReadOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x74", + "property": "SinglePointLUTOperation", + "name": "single-point-lut-operation", + "description": "Single point LUT operation", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x75", + "property": "BlockLUTOperation", + "name": "block-lut-operation", + "description": "Block LUT operation", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x76", + "property": "RemoteProcedureCall", + "name": "remote-procedure-call", + "description": "Remote Procedure Call", + "type": "WriteOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x78", + "property": "DisplayIdentificationOperation", + "name": "display-identification-operation", + "description": "Display Identification Operation", + "type": "ReadOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x7A", + "property": "AdjustFocalPlane", + "name": "adjust-focal-plane", + "description": "Adjust Focal Plane", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x7C", + "property": "AdjustZoom", + "name": "adjust-zoom", + "description": "Adjust Zoom", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x7E", + "property": "Trapezoid", + "name": "trapezoid", + "description": "Trapezoid", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x80", + "property": "Keystone", + "name": "keystone", + "description": "Keystone", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x82", + "property": "HorizontalMirror", + "name": "horizontal-mirror", + "description": "Horizontal Mirror (Flip)", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x84", + "property": "VerticalMirror", + "name": "vertical-mirror", + "description": "Vertical Mirror (Flip)", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x86", + "property": "DisplayScaling", + "name": "display-scaling", + "description": "Display Scaling", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x87", + "property": "Sharpness", + "name": "sharpness", + "description": "Sharpness control", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": true, + "aliases": [ + "sharp" + ] + }, + { + "code": "0x88", + "property": "VelocityScanModulation", + "name": "velocity-scan-modulation", + "description": "Velocity Scan Modulation", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x8A", + "property": "ColorSaturation", + "name": "color-saturation", + "description": "Color Saturation", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [ + "saturation", + "sat" + ] + }, + { + "code": "0x8B", + "property": "TVChannelUpDown", + "name": "tv-channel-up-down", + "description": "TV Channel Up/Down", + "type": "WriteOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x8C", + "property": "TVSharpness", + "name": "tv-sharpness", + "description": "TV Sharpness", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x8D", + "property": "AudioMute", + "name": "mute", + "description": "Audio mute/Screen blank", + "type": "ReadWrite", + "category": "Audio", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x8E", + "property": "TVContrast", + "name": "tv-contrast", + "description": "TV Contrast", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x8F", + "property": "AudioTreble", + "name": "audio-treble", + "description": "Audio treble", + "type": "ReadWrite", + "category": "Audio", + "supportsPercentage": true, + "aliases": [ + "treble" + ] + }, + { + "code": "0x90", + "property": "Hue", + "name": "hue", + "description": "Hue", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x91", + "property": "AudioBass", + "name": "audio-bass", + "description": "Audio bass", + "type": "ReadWrite", + "category": "Audio", + "supportsPercentage": true, + "aliases": [ + "bass" + ] + }, + { + "code": "0x92", + "property": "TVBlackLevel", + "name": "tv-black-level", + "description": "TV Black level/Luminesence", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x93", + "property": "AudioBalance", + "name": "audio-balance", + "description": "Audio balance L/R", + "type": "ReadWrite", + "category": "Audio", + "supportsPercentage": true, + "aliases": [ + "balance" + ] + }, + { + "code": "0x94", + "property": "AudioProcessorMode", + "name": "audio-processor-mode", + "description": "Audio processor mode", + "type": "ReadWrite", + "category": "Audio", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x95", + "property": "WindowPositionTLX", + "name": "window-position-tl-x", + "description": "Window Position(TL_X)", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x96", + "property": "WindowPositionTLY", + "name": "window-position-tl-y", + "description": "Window Position(TL_Y)", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x97", + "property": "WindowPositionBRX", + "name": "window-position-br-x", + "description": "Window Position(BR_X)", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x98", + "property": "WindowPositionBRY", + "name": "window-position-br-y", + "description": "Window Position(BR_Y)", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x99", + "property": "WindowControlOnOff", + "name": "window-control-on-off", + "description": "Window control on/off", + "type": "ReadWrite", + "category": "Geometry", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0x9A", + "property": "WindowBackground", + "name": "window-background", + "description": "Window background", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x9B", + "property": "SixAxisHueRed", + "name": "6-axis-hue-red", + "description": "6 axis hue control: Red", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x9C", + "property": "SixAxisHueYellow", + "name": "6-axis-hue-yellow", + "description": "6 axis hue control: Yellow", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x9D", + "property": "SixAxisHueGreen", + "name": "6-axis-hue-green", + "description": "6 axis hue control: Green", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x9E", + "property": "SixAxisHueCyan", + "name": "6-axis-hue-cyan", + "description": "6 axis hue control: Cyan", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0x9F", + "property": "SixAxisHueBlue", + "name": "6-axis-hue-blue", + "description": "6 axis hue control: Blue", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0xA0", + "property": "SixAxisHueMagenta", + "name": "6-axis-hue-magenta", + "description": "6 axis hue control: Magenta", + "type": "ReadWrite", + "category": "ColorControl", + "supportsPercentage": true, + "aliases": [] + }, + { + "code": "0xA2", + "property": "AutoSetupOnOff", + "name": "auto-setup-on-off", + "description": "Auto setup on/off", + "type": "WriteOnly", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xA4", + "property": "WindowMaskControl", + "name": "window-mask-control", + "description": "Window mask control", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xA5", + "property": "ChangeSelectedWindow", + "name": "change-selected-window", + "description": "Change the selected window", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xAA", + "property": "ScreenOrientation", + "name": "screen-orientation", + "description": "Screen Orientation", + "type": "ReadOnly", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [ + "orientation" + ] + }, + { + "code": "0xAC", + "property": "HorizontalFrequency", + "name": "horizontal-frequency", + "description": "Horizontal frequency", + "type": "ReadOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [ + "h-freq" + ] + }, + { + "code": "0xAE", + "property": "VerticalFrequency", + "name": "vertical-frequency", + "description": "Vertical frequency", + "type": "ReadOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [ + "v-freq" + ] + }, + { + "code": "0xB0", + "property": "Settings", + "name": "settings", + "description": "Settings", + "type": "WriteOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xB2", + "property": "FlatPanelSubPixelLayout", + "name": "flat-panel-sub-pixel-layout", + "description": "Flat panel sub-pixel layout", + "type": "ReadOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xB4", + "property": "SourceTimingMode", + "name": "source-timing-mode", + "description": "Source Timing Mode", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xB6", + "property": "DisplayTechnologyType", + "name": "display-technology-type", + "description": "Display technology type", + "type": "ReadOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xB7", + "property": "MonitorStatus", + "name": "monitor-status", + "description": "Monitor status", + "type": "ReadOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xB8", + "property": "PacketCount", + "name": "packet-count", + "description": "Packet count", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xB9", + "property": "MonitorXOrigin", + "name": "monitor-x-origin", + "description": "Monitor X origin", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xBA", + "property": "MonitorYOrigin", + "name": "monitor-y-origin", + "description": "Monitor Y origin", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xBB", + "property": "HeaderErrorCount", + "name": "header-error-count", + "description": "Header error count", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xBC", + "property": "BodyCRCErrorCount", + "name": "body-crc-error-count", + "description": "Body CRC error count", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xBD", + "property": "ClientID", + "name": "client-id", + "description": "Client ID", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xBE", + "property": "LinkControl", + "name": "link-control", + "description": "Link control", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xC0", + "property": "DisplayUsageTime", + "name": "display-usage-time", + "description": "Display usage time", + "type": "ReadOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xC2", + "property": "DisplayDescriptorLength", + "name": "display-descriptor-length", + "description": "Display descriptor length", + "type": "ReadOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xC3", + "property": "TransmitDisplayDescriptor", + "name": "transmit-display-descriptor", + "description": "Transmit display descriptor", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xC4", + "property": "EnableDisplayOfDisplayDescriptor", + "name": "enable-display-of-display-descriptor", + "description": "Enable display of 'display descriptor'", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xC6", + "property": "ApplicationEnableKey", + "name": "application-enable-key", + "description": "Application enable key", + "type": "ReadOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xC8", + "property": "DisplayControllerType", + "name": "display-controller-type", + "description": "Display controller type", + "type": "ReadOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xC9", + "property": "DisplayFirmwareLevel", + "name": "display-firmware-level", + "description": "Display firmware level", + "type": "ReadOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [ + "firmware" + ] + }, + { + "code": "0xCA", + "property": "OSDButtonControl", + "name": "osd-button-control", + "description": "OSD/Button Control", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [ + "osd" + ] + }, + { + "code": "0xCC", + "property": "OSDLanguage", + "name": "osd-language", + "description": "OSD Language", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [ + "language" + ] + }, + { + "code": "0xCD", + "property": "StatusIndicators", + "name": "status-indicators", + "description": "Status Indicators", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xCE", + "property": "AuxiliaryDisplaySize", + "name": "auxiliary-display-size", + "description": "Auxiliary display size", + "type": "ReadOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xCF", + "property": "AuxiliaryDisplayData", + "name": "auxiliary-display-data", + "description": "Auxiliary display data", + "type": "WriteOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xD0", + "property": "OutputSelect", + "name": "output-select", + "description": "Output select", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xD2", + "property": "AssetTag", + "name": "asset-tag", + "description": "Asset Tag", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xD4", + "property": "StereoVideoMode", + "name": "stereo-video-mode", + "description": "Stereo video mode", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xD6", + "property": "PowerMode", + "name": "power-mode", + "description": "Power mode", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [ + "power" + ] + }, + { + "code": "0xD7", + "property": "AuxiliaryPowerOutput", + "name": "auxiliary-power-output", + "description": "Auxiliary power output", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xDA", + "property": "ScanMode", + "name": "scan-mode", + "description": "Scan mode", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xDB", + "property": "ImageMode", + "name": "image-mode", + "description": "Image Mode", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [ + "mode" + ] + }, + { + "code": "0xDC", + "property": "DisplayMode", + "name": "display-mode", + "description": "Display Mode", + "type": "ReadWrite", + "category": "ImageAdjustment", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xDE", + "property": "ScratchPad", + "name": "scratch-pad", + "description": "Scratch Pad", + "type": "ReadWrite", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [] + }, + { + "code": "0xDF", + "property": "VcpVersion", + "name": "vcp-version", + "description": "VCP Version", + "type": "ReadOnly", + "category": "Miscellaneous", + "supportsPercentage": false, + "aliases": [ + "version" + ] + } + ] +} \ No newline at end of file diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000..0122b57 --- /dev/null +++ b/build.cmd @@ -0,0 +1,53 @@ +@echo off +setlocal enabledelayedexpansion + +echo ======================================== +echo Building DDCSwitch with NativeAOT +echo ======================================== +echo. + +REM Clean previous build +echo Cleaning previous build... +dotnet clean DDCSwitch\DDCSwitch.csproj -c Release +if errorlevel 1 ( + echo ERROR: Clean failed + exit /b 1 +) +echo. + +REM Build with NativeAOT +echo Building with NativeAOT... +dotnet publish DDCSwitch\DDCSwitch.csproj -c Release -r win-x64 --self-contained +if errorlevel 1 ( + echo ERROR: Build failed + exit /b 1 +) +echo. + +REM Create dist folder +echo Creating dist folder... +if not exist "dist" mkdir "dist" + +REM Copy the NativeAOT executable +echo Copying executable to dist folder... +copy /Y "DDCSwitch\bin\Release\net10.0\win-x64\publish\DDCSwitch.exe" "dist\DDCSwitch.exe" +if errorlevel 1 ( + echo ERROR: Failed to copy executable + exit /b 1 +) + +echo. +echo ======================================== +echo Build completed successfully! +echo Output: dist\DDCSwitch.exe +echo ======================================== + +REM Display file size +for %%A in ("dist\DDCSwitch.exe") do ( + set size=%%~zA + set /a sizeMB=!size! / 1048576 + echo File size: !sizeMB! MB +) + +endlocal + diff --git a/tools/GenerateVcpFeatures.py b/tools/GenerateVcpFeatures.py new file mode 100644 index 0000000..f2c5355 --- /dev/null +++ b/tools/GenerateVcpFeatures.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +""" +Generates VcpFeature static properties from VcpFeatureData.json. +This creates a partial class that can be combined with the core VcpFeature class. +""" + +import json +from pathlib import Path + +def generate_feature_property(feature): + """Generate a C# static property for a VCP feature.""" + code = feature['code'] + prop_name = feature['property'] + name = feature['name'] + description = feature['description'] + feature_type = feature['type'] + category = feature['category'] + supports_percentage = 'true' if feature['supportsPercentage'] else 'false' + aliases = feature.get('aliases', []) + + # Generate XML doc comment + lines = [ + f" /// ", + f" /// {description} (VCP {code})", + f" /// " + ] + + # Generate property + if aliases: + aliases_str = ', '.join(f'"{alias}"' for alias in aliases) + lines.append(f' public static VcpFeature {prop_name} => new({code}, "{name}", "{description}", VcpFeatureType.{feature_type}, VcpFeatureCategory.{category}, {supports_percentage}, {aliases_str});') + else: + lines.append(f' public static VcpFeature {prop_name} => new({code}, "{name}", "{description}", VcpFeatureType.{feature_type}, VcpFeatureCategory.{category}, {supports_percentage});') + + return '\n'.join(lines) + +def generate_all_features_list(features): + """Generate the AllFeatures list.""" + lines = [ + " /// ", + " /// All features registry - contains all predefined MCCS features", + " /// ", + " public static IReadOnlyList AllFeatures { get; } = new List", + " {" + ] + + for i, feature in enumerate(features): + comma = ',' if i < len(features) - 1 else '' + lines.append(f" {feature['property']}{comma}") + + lines.append(" };") + return '\n'.join(lines) + +def main(): + script_dir = Path(__file__).parent + json_file = script_dir.parent / 'DDCSwitch' / 'VcpFeatureData.json' + output_file = script_dir.parent / 'DDCSwitch' / 'VcpFeature.Generated.cs' + + print(f"Loading {json_file}...") + with open(json_file, 'r', encoding='utf-8') as f: + data = json.load(f) + + features = data['features'] + print(f"Found {len(features)} features") + + # Generate the file + lines = [ + "// ", + "// This file is automatically generated from VcpFeatureData.json", + "// Do not edit this file directly. Instead, edit VcpFeatureData.json and regenerate.", + "", + "namespace DDCSwitch;", + "", + "public partial class VcpFeature", + "{", + " // ===== GENERATED VCP FEATURES =====" + ] + + # Group features by category + categories = {} + for feature in features: + category = feature['category'] + if category not in categories: + categories[category] = [] + categories[category].append(feature) + + # Generate properties grouped by category + for category, category_features in categories.items(): + lines.append("") + lines.append(f" // ===== {category.upper()} FEATURES =====") + lines.append("") + for feature in category_features: + lines.append(generate_feature_property(feature)) + lines.append("") + + # Generate AllFeatures list + lines.append("") + lines.append(generate_all_features_list(features)) + + lines.append("}") + lines.append("") + + content = '\n'.join(lines) + + print(f"Writing to {output_file}...") + with open(output_file, 'w', encoding='utf-8') as f: + f.write(content) + + print("Done!") + print(f"Generated {len(features)} feature properties") + +if __name__ == '__main__': + main() + diff --git a/tools/Regenerate.ps1 b/tools/Regenerate.ps1 new file mode 100644 index 0000000..763b982 --- /dev/null +++ b/tools/Regenerate.ps1 @@ -0,0 +1,30 @@ +# Regenerate VCP Feature Code +# Run this after editing VcpFeatureData.json + +Write-Host "=" * 60 +Write-Host "VCP Feature Code Generator" +Write-Host "=" * 60 + +$scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path +$projectRoot = Split-Path -Parent $scriptPath + +Push-Location $projectRoot + +try { + Write-Host "`nGenerating VcpFeature.Generated.cs from VcpFeatureData.json..." + python "$scriptPath\GenerateVcpFeatures.py" + + if ($LASTEXITCODE -eq 0) { + Write-Host "`n✓ Generation successful!" -ForegroundColor Green + Write-Host "`nNext steps:" + Write-Host " 1. Review the changes in VcpFeature.Generated.cs" + Write-Host " 2. Build the project: dotnet build" + Write-Host " 3. Test the changes" + } else { + Write-Host "`n✗ Generation failed!" -ForegroundColor Red + exit 1 + } +} finally { + Pop-Location +} + From a660ebc260a8ed1012a3b04ac8d670f8c5ec95bd Mon Sep 17 00:00:00 2001 From: Quick <577652+markdwags@users.noreply.github.com> Date: Wed, 7 Jan 2026 20:45:18 -0600 Subject: [PATCH 05/10] Refactor how commands are handled, --- DDCSwitch/Commands/CommandRouter.cs | 79 ++ DDCSwitch/Commands/ConsoleOutputFormatter.cs | 22 + DDCSwitch/Commands/GetCommand.cs | 229 ++++ DDCSwitch/Commands/HelpCommand.cs | 53 + DDCSwitch/Commands/ListCommand.cs | 240 ++++ DDCSwitch/Commands/SetCommand.cs | 388 ++++++ DDCSwitch/Commands/VcpScanCommand.cs | 278 ++++ DDCSwitch/Program.cs | 1203 +----------------- 8 files changed, 1291 insertions(+), 1201 deletions(-) create mode 100644 DDCSwitch/Commands/CommandRouter.cs create mode 100644 DDCSwitch/Commands/ConsoleOutputFormatter.cs create mode 100644 DDCSwitch/Commands/GetCommand.cs create mode 100644 DDCSwitch/Commands/HelpCommand.cs create mode 100644 DDCSwitch/Commands/ListCommand.cs create mode 100644 DDCSwitch/Commands/SetCommand.cs create mode 100644 DDCSwitch/Commands/VcpScanCommand.cs diff --git a/DDCSwitch/Commands/CommandRouter.cs b/DDCSwitch/Commands/CommandRouter.cs new file mode 100644 index 0000000..a376628 --- /dev/null +++ b/DDCSwitch/Commands/CommandRouter.cs @@ -0,0 +1,79 @@ +using System.Text.Json; + +namespace DDCSwitch.Commands; + +internal static class CommandRouter +{ + public static int Route(string[] args) + { + if (args.Length == 0) + { + HelpCommand.ShowUsage(); + return 1; + } + + // Check for --json flag + bool jsonOutput = args.Contains("--json", StringComparer.OrdinalIgnoreCase); + var filteredArgs = args.Where(a => !a.Equals("--json", StringComparison.OrdinalIgnoreCase)).ToArray(); + + // Check for --verbose flag + bool verboseOutput = filteredArgs.Contains("--verbose", StringComparer.OrdinalIgnoreCase); + filteredArgs = filteredArgs.Where(a => !a.Equals("--verbose", StringComparison.OrdinalIgnoreCase)).ToArray(); + + // Check for --scan flag + bool scanOutput = filteredArgs.Contains("--scan", StringComparer.OrdinalIgnoreCase); + filteredArgs = filteredArgs.Where(a => !a.Equals("--scan", StringComparison.OrdinalIgnoreCase)).ToArray(); + + if (filteredArgs.Length == 0) + { + HelpCommand.ShowUsage(); + return 1; + } + + var command = filteredArgs[0].ToLowerInvariant(); + + try + { + return command switch + { + "list" or "ls" => ListCommand.Execute(jsonOutput, verboseOutput, scanOutput), + "get" => GetCommand.Execute(filteredArgs, jsonOutput), + "set" => SetCommand.Execute(filteredArgs, jsonOutput), + "version" or "-v" or "--version" => HelpCommand.ShowVersion(jsonOutput), + "help" or "-h" or "--help" or "/?" => HelpCommand.ShowUsage(), + _ => InvalidCommand(filteredArgs[0], jsonOutput) + }; + } + catch (Exception ex) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, ex.Message); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError(ex.Message); + } + + return 1; + } + } + + private static int InvalidCommand(string command, bool jsonOutput) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, $"Unknown command: {command}"); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError($"Unknown command: {command}"); + ConsoleOutputFormatter.WriteInfo("Run DDCSwitch help for usage information."); + } + + return 1; + } +} + diff --git a/DDCSwitch/Commands/ConsoleOutputFormatter.cs b/DDCSwitch/Commands/ConsoleOutputFormatter.cs new file mode 100644 index 0000000..f58ed3c --- /dev/null +++ b/DDCSwitch/Commands/ConsoleOutputFormatter.cs @@ -0,0 +1,22 @@ +using Spectre.Console; + +namespace DDCSwitch.Commands; + +internal static class ConsoleOutputFormatter +{ + public static void WriteError(string message) + { + AnsiConsole.MarkupLine($"[red]Error:[/] {message}"); + } + + public static void WriteInfo(string message) + { + AnsiConsole.MarkupLine(message); + } + + public static void WriteSuccess(string message) + { + AnsiConsole.MarkupLine($"[green]✓[/] {message}"); + } +} + diff --git a/DDCSwitch/Commands/GetCommand.cs b/DDCSwitch/Commands/GetCommand.cs new file mode 100644 index 0000000..83718c9 --- /dev/null +++ b/DDCSwitch/Commands/GetCommand.cs @@ -0,0 +1,229 @@ +using Spectre.Console; +using System.Text.Json; + +namespace DDCSwitch.Commands; + +internal static class GetCommand +{ + public static int Execute(string[] args, bool jsonOutput) + { + if (args.Length < 2) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, "Monitor identifier required"); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError("Monitor identifier required."); + AnsiConsole.WriteLine("Usage: DDCSwitch get [feature]"); + } + + return 1; + } + + // If no feature is specified, perform VCP scan + if (args.Length == 2) + { + return VcpScanCommand.ScanSingleMonitor(args[1], jsonOutput); + } + + string featureInput = args[2]; + + if (!FeatureResolver.TryResolveFeature(featureInput, out VcpFeature? feature)) + { + return HandleInvalidFeature(featureInput, jsonOutput); + } + + var monitors = MonitorController.EnumerateMonitors(); + + if (monitors.Count == 0) + { + return HandleNoMonitors(jsonOutput); + } + + var monitor = MonitorController.FindMonitor(monitors, args[1]); + + if (monitor == null) + { + return HandleMonitorNotFound(monitors, args[1], jsonOutput); + } + + int result = ReadFeatureValue(monitor, feature!, jsonOutput); + + // Cleanup + foreach (var m in monitors) + { + m.Dispose(); + } + + return result; + } + + private static int HandleInvalidFeature(string featureInput, bool jsonOutput) + { + string errorMessage; + + // Provide specific error message based on input type + if (FeatureResolver.TryParseVcpCode(featureInput, out _)) + { + // Valid VCP code but not in our predefined list + errorMessage = $"VCP code '{featureInput}' is valid but may not be supported by all monitors"; + } + else + { + // Invalid feature name or VCP code + errorMessage = $"Invalid feature '{featureInput}'. {FeatureResolver.GetVcpCodeValidationError(featureInput)}"; + } + + if (jsonOutput) + { + var error = new ErrorResponse(false, errorMessage); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError(errorMessage); + AnsiConsole.MarkupLine("Valid features: brightness, contrast, input, or VCP code (0x10, 0x12, etc.)"); + } + + return 1; + } + + private static int HandleNoMonitors(bool jsonOutput) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, "No DDC/CI capable monitors found"); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError("No DDC/CI capable monitors found."); + } + + return 1; + } + + private static int HandleMonitorNotFound(List monitors, string monitorId, bool jsonOutput) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, $"Monitor '{monitorId}' not found"); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError($"Monitor '{monitorId}' not found."); + AnsiConsole.MarkupLine("Use [yellow]DDCSwitch list[/] to see available monitors."); + } + + // Cleanup + foreach (var m in monitors) + { + m.Dispose(); + } + + return 1; + } + + private static int ReadFeatureValue(Monitor monitor, VcpFeature feature, bool jsonOutput) + { + bool success = false; + uint current = 0; + uint max = 0; + int errorCode = 0; + + if (!jsonOutput) + { + AnsiConsole.Status() + .Start($"Reading {feature.Name} from {monitor.Name}...", ctx => + { + ctx.Spinner(Spinner.Known.Dots); + success = monitor.TryGetVcpFeature(feature.Code, out current, out max, out errorCode); + }); + } + else + { + success = monitor.TryGetVcpFeature(feature.Code, out current, out max, out errorCode); + } + + if (!success) + { + return HandleReadFailure(monitor, feature, errorCode, jsonOutput); + } + + OutputFeatureValue(monitor, feature, current, max, jsonOutput); + return 0; + } + + private static int HandleReadFailure(Monitor monitor, VcpFeature feature, int errorCode, bool jsonOutput) + { + string errorMessage; + + if (VcpErrorHandler.IsTimeoutError(errorCode)) + { + errorMessage = VcpErrorHandler.CreateTimeoutMessage(monitor, feature, "read"); + } + else if (VcpErrorHandler.IsUnsupportedFeatureError(errorCode)) + { + errorMessage = VcpErrorHandler.CreateUnsupportedFeatureMessage(monitor, feature); + } + else if (errorCode == 0x00000006) // ERROR_INVALID_HANDLE + { + errorMessage = VcpErrorHandler.CreateCommunicationFailureMessage(monitor); + } + else + { + errorMessage = VcpErrorHandler.CreateReadFailureMessage(monitor, feature); + } + + if (jsonOutput) + { + var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); + var error = new ErrorResponse(false, errorMessage, monitorRef); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError(errorMessage); + } + + return 1; + } + + private static void OutputFeatureValue(Monitor monitor, VcpFeature feature, uint current, uint max, bool jsonOutput) + { + if (jsonOutput) + { + var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); + + uint? percentageValue = feature.SupportsPercentage ? FeatureResolver.ConvertRawToPercentage(current, max) : null; + var result = new GetVcpResponse(true, monitorRef, feature.Name, current, max, percentageValue); + Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.GetVcpResponse)); + } + else + { + AnsiConsole.MarkupLine($"[green]Monitor:[/] {monitor.Name} ({monitor.DeviceName})"); + + if (feature.Code == InputSource.VcpInputSource) + { + // Display input with name resolution + AnsiConsole.MarkupLine($"[green]Current {feature.Name}:[/] {InputSource.GetName(current)} (0x{current:X2})"); + } + else if (feature.SupportsPercentage) + { + // Display percentage for brightness/contrast + uint percentage = FeatureResolver.ConvertRawToPercentage(current, max); + AnsiConsole.MarkupLine($"[green]Current {feature.Name}:[/] {percentage}% (raw: {current}/{max})"); + } + else + { + // Display raw values for unknown VCP codes + AnsiConsole.MarkupLine($"[green]Current {feature.Name}:[/] {current} (max: {max})"); + } + } + } +} + diff --git a/DDCSwitch/Commands/HelpCommand.cs b/DDCSwitch/Commands/HelpCommand.cs new file mode 100644 index 0000000..1c52dde --- /dev/null +++ b/DDCSwitch/Commands/HelpCommand.cs @@ -0,0 +1,53 @@ +using Spectre.Console; + +namespace DDCSwitch.Commands; + +internal static class HelpCommand +{ + public static string GetVersion() + { + var version = typeof(HelpCommand).Assembly + .GetName().Version?.ToString(3) ?? "0.0.0"; + return version; + } + + public static int ShowVersion(bool jsonOutput) + { + var version = GetVersion(); + + if (jsonOutput) + { + Console.WriteLine($"{{\"version\":\"{version}\"}}"); + } + else + { + AnsiConsole.Write(new FigletText("DDCSwitch").Color(Color.Blue)); + AnsiConsole.MarkupLine($"[bold]Version:[/] [green]{version}[/]"); + AnsiConsole.MarkupLine("[dim]Windows DDC/CI Monitor Input Switcher[/]"); + } + + return 0; + } + + public static int ShowUsage() + { + var version = GetVersion(); + + AnsiConsole.Write(new FigletText("DDCSwitch").Color(Color.Blue)); + AnsiConsole.MarkupLine($"[dim]Windows DDC/CI Monitor Input Switcher v{version}[/]\n"); + + AnsiConsole.MarkupLine("[yellow]Commands:[/]"); + AnsiConsole.WriteLine(" list [--verbose] [--scan] - List all DDC/CI capable monitors"); + AnsiConsole.WriteLine(" get monitor [feature] - Get current value for a monitor feature or scan all features"); + AnsiConsole.MarkupLine(" set monitor feature value - Set value for a monitor feature"); + AnsiConsole.MarkupLine(" version - Display version information"); + + AnsiConsole.MarkupLine("\nSupported features: brightness, contrast, input, or VCP codes like 0x10"); + AnsiConsole.MarkupLine("Use [yellow]--json[/] flag for JSON output"); + AnsiConsole.MarkupLine("Use [yellow]--verbose[/] flag with list to include brightness and contrast"); + AnsiConsole.MarkupLine("Use [yellow]--scan[/] flag with list to enumerate all VCP codes"); + + return 0; + } +} + diff --git a/DDCSwitch/Commands/ListCommand.cs b/DDCSwitch/Commands/ListCommand.cs new file mode 100644 index 0000000..9d02c22 --- /dev/null +++ b/DDCSwitch/Commands/ListCommand.cs @@ -0,0 +1,240 @@ +using Spectre.Console; +using System.Text.Json; + +namespace DDCSwitch.Commands; + +internal static class ListCommand +{ + public static int Execute(bool jsonOutput, bool verboseOutput, bool scanOutput) + { + List monitors; + + if (!jsonOutput) + { + monitors = null!; + AnsiConsole.Status() + .Start(scanOutput ? "Scanning VCP features..." : "Enumerating monitors...", ctx => + { + ctx.Spinner(Spinner.Known.Dots); + monitors = MonitorController.EnumerateMonitors(); + }); + } + else + { + monitors = MonitorController.EnumerateMonitors(); + } + + if (monitors.Count == 0) + { + if (jsonOutput) + { + var result = new ListMonitorsResponse(false, null, "No DDC/CI capable monitors found"); + Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.ListMonitorsResponse)); + } + else + { + AnsiConsole.MarkupLine("[yellow]No DDC/CI capable monitors found.[/]"); + } + + return 1; + } + + // If scan mode is enabled, perform VCP scanning for each monitor + if (scanOutput) + { + return VcpScanCommand.ScanAllMonitors(monitors, jsonOutput); + } + + if (jsonOutput) + { + OutputJsonList(monitors, verboseOutput); + } + else + { + OutputTableList(monitors, verboseOutput); + } + + // Cleanup + foreach (var monitor in monitors) + { + monitor.Dispose(); + } + + return 0; + } + + private static void OutputJsonList(List monitors, bool verboseOutput) + { + var monitorList = monitors.Select(monitor => + { + string? inputName = null; + uint? inputCode = null; + string status = "ok"; + string? brightness = null; + string? contrast = null; + + try + { + if (monitor.TryGetInputSource(out uint current, out _)) + { + inputName = InputSource.GetName(current); + inputCode = current; + } + else + { + status = "no_ddc_ci"; + } + + // Get brightness and contrast if verbose mode is enabled + if (verboseOutput && status == "ok") + { + // Try to get brightness (VCP 0x10) + if (monitor.TryGetVcpFeature(VcpFeature.Brightness.Code, out uint brightnessCurrent, out uint brightnessMax)) + { + uint brightnessPercentage = FeatureResolver.ConvertRawToPercentage(brightnessCurrent, brightnessMax); + brightness = $"{brightnessPercentage}%"; + } + else + { + brightness = "N/A"; + } + + // Try to get contrast (VCP 0x12) + if (monitor.TryGetVcpFeature(VcpFeature.Contrast.Code, out uint contrastCurrent, out uint contrastMax)) + { + uint contrastPercentage = FeatureResolver.ConvertRawToPercentage(contrastCurrent, contrastMax); + contrast = $"{contrastPercentage}%"; + } + else + { + contrast = "N/A"; + } + } + } + catch + { + status = "error"; + if (verboseOutput) + { + brightness = "N/A"; + contrast = "N/A"; + } + } + + return new MonitorInfo( + monitor.Index, + monitor.Name, + monitor.DeviceName, + monitor.IsPrimary, + inputName, + inputCode != null ? $"0x{inputCode:X2}" : null, + status, + brightness, + contrast); + }).ToList(); + + var result = new ListMonitorsResponse(true, monitorList); + Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.ListMonitorsResponse)); + } + + private static void OutputTableList(List monitors, bool verboseOutput) + { + var table = new Table() + .Border(TableBorder.Rounded) + .AddColumn("Index") + .AddColumn("Monitor Name") + .AddColumn("Device") + .AddColumn("Current Input"); + + // Add brightness and contrast columns if verbose mode is enabled + if (verboseOutput) + { + table.AddColumn("Brightness"); + table.AddColumn("Contrast"); + } + + table.AddColumn("Status"); + + foreach (var monitor in monitors) + { + string inputInfo = "N/A"; + string status = "[green]OK[/]"; + string brightnessInfo = "N/A"; + string contrastInfo = "N/A"; + + try + { + if (monitor.TryGetInputSource(out uint current, out _)) + { + inputInfo = $"{InputSource.GetName(current)} (0x{current:X2})"; + } + else + { + status = "[yellow]No DDC/CI[/]"; + } + + // Get brightness and contrast if verbose mode is enabled and monitor supports DDC/CI + if (verboseOutput && status == "[green]OK[/]") + { + // Try to get brightness (VCP 0x10) + if (monitor.TryGetVcpFeature(VcpFeature.Brightness.Code, out uint brightnessCurrent, out uint brightnessMax)) + { + uint brightnessPercentage = FeatureResolver.ConvertRawToPercentage(brightnessCurrent, brightnessMax); + brightnessInfo = $"{brightnessPercentage}%"; + } + else + { + brightnessInfo = "[dim]N/A[/]"; + } + + // Try to get contrast (VCP 0x12) + if (monitor.TryGetVcpFeature(VcpFeature.Contrast.Code, out uint contrastCurrent, out uint contrastMax)) + { + uint contrastPercentage = FeatureResolver.ConvertRawToPercentage(contrastCurrent, contrastMax); + contrastInfo = $"{contrastPercentage}%"; + } + else + { + contrastInfo = "[dim]N/A[/]"; + } + } + else if (verboseOutput) + { + brightnessInfo = "[dim]N/A[/]"; + contrastInfo = "[dim]N/A[/]"; + } + } + catch + { + status = "[red]Error[/]"; + if (verboseOutput) + { + brightnessInfo = "[dim]N/A[/]"; + contrastInfo = "[dim]N/A[/]"; + } + } + + var row = new List + { + monitor.IsPrimary ? $"{monitor.Index} [yellow]*[/]" : monitor.Index.ToString(), + monitor.Name, + monitor.DeviceName, + inputInfo + }; + + // Add brightness and contrast columns if verbose mode is enabled + if (verboseOutput) + { + row.Add(brightnessInfo); + row.Add(contrastInfo); + } + + row.Add(status); + + table.AddRow(row.ToArray()); + } + + AnsiConsole.Write(table); + } +} + diff --git a/DDCSwitch/Commands/SetCommand.cs b/DDCSwitch/Commands/SetCommand.cs new file mode 100644 index 0000000..bcbaafa --- /dev/null +++ b/DDCSwitch/Commands/SetCommand.cs @@ -0,0 +1,388 @@ +using Spectre.Console; +using System.Text.Json; + +namespace DDCSwitch.Commands; + +internal static class SetCommand +{ + public static int Execute(string[] args, bool jsonOutput) + { + // Require 4 arguments: set + if (args.Length < 4) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, "Monitor, feature, and value required"); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError("Monitor, feature, and value required."); + AnsiConsole.MarkupLine("Usage: [yellow]DDCSwitch set [/]"); + } + + return 1; + } + + string featureInput = args[2]; + string valueInput = args[3]; + + if (!FeatureResolver.TryResolveFeature(featureInput, out VcpFeature? feature)) + { + return HandleInvalidFeature(featureInput, jsonOutput); + } + + var (setValue, percentageValue, validationError) = ParseAndValidateValue(feature!, valueInput); + + if (validationError != null) + { + return HandleValidationError(validationError, jsonOutput); + } + + var monitors = MonitorController.EnumerateMonitors(); + + if (monitors.Count == 0) + { + return HandleNoMonitors(jsonOutput); + } + + var monitor = MonitorController.FindMonitor(monitors, args[1]); + + if (monitor == null) + { + return HandleMonitorNotFound(monitors, args[1], jsonOutput); + } + + int result = SetFeatureValue(monitor, feature!, setValue, percentageValue, jsonOutput); + + // Cleanup + foreach (var m in monitors) + { + m.Dispose(); + } + + return result; + } + + private static int HandleInvalidFeature(string featureInput, bool jsonOutput) + { + string errorMessage; + + // Provide specific error message based on input type + if (FeatureResolver.TryParseVcpCode(featureInput, out _)) + { + // Valid VCP code but not in our predefined list + errorMessage = $"VCP code '{featureInput}' is valid but may not be supported by all monitors"; + } + else + { + // Invalid feature name or VCP code + errorMessage = $"Invalid feature '{featureInput}'. {FeatureResolver.GetVcpCodeValidationError(featureInput)}"; + } + + if (jsonOutput) + { + var error = new ErrorResponse(false, errorMessage); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError(errorMessage); + AnsiConsole.MarkupLine("Valid features: brightness, contrast, input, or VCP code (0x10, 0x12, etc.)"); + } + + return 1; + } + + private static (uint setValue, uint? percentageValue, string? validationError) ParseAndValidateValue(VcpFeature feature, string valueInput) + { + uint setValue = 0; + uint? percentageValue = null; + string? validationError = null; + + if (feature.Code == InputSource.VcpInputSource) + { + // Use existing input source parsing for input feature + if (!InputSource.TryParse(valueInput, out setValue)) + { + validationError = $"Invalid input source '{valueInput}'. Valid inputs: HDMI1, HDMI2, DP1, DP2, DVI1, DVI2, VGA1, VGA2, or hex code (0x11)"; + } + } + else if (feature.SupportsPercentage && FeatureResolver.TryParsePercentage(valueInput, out uint percentage)) + { + // Parse as percentage for brightness/contrast - validate percentage range + if (!FeatureResolver.IsValidPercentage(percentage)) + { + validationError = VcpErrorHandler.CreateRangeValidationMessage(feature, percentage, 100, true); + } + else + { + percentageValue = percentage; + // We'll convert to raw value after getting monitor's max value + setValue = 0; // Placeholder + } + } + else if (uint.TryParse(valueInput, out uint rawValue)) + { + // Parse as raw value - we'll validate range after getting monitor's max value + setValue = rawValue; + } + else + { + // Invalid value format + if (feature.SupportsPercentage) + { + validationError = FeatureResolver.GetPercentageValidationError(valueInput); + } + else + { + validationError = $"Invalid value '{valueInput}' for feature '{feature.Name}'. Expected: numeric value within monitor's supported range"; + } + } + + return (setValue, percentageValue, validationError); + } + + private static int HandleValidationError(string validationError, bool jsonOutput) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, validationError); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError(validationError); + } + + return 1; + } + + private static int HandleNoMonitors(bool jsonOutput) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, "No DDC/CI capable monitors found"); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError("No DDC/CI capable monitors found."); + } + + return 1; + } + + private static int HandleMonitorNotFound(List monitors, string monitorId, bool jsonOutput) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, $"Monitor '{monitorId}' not found"); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError($"Monitor '{monitorId}' not found."); + AnsiConsole.MarkupLine("Use [yellow]DDCSwitch list[/] to see available monitors."); + } + + // Cleanup + foreach (var m in monitors) + { + m.Dispose(); + } + + return 1; + } + + private static int SetFeatureValue(Monitor monitor, VcpFeature feature, uint setValue, uint? percentageValue, bool jsonOutput) + { + // If we have a percentage value or need to validate raw value range, get the monitor's max value + if (percentageValue.HasValue || (feature.Code != InputSource.VcpInputSource && !percentageValue.HasValue)) + { + if (monitor.TryGetVcpFeature(feature.Code, out _, out uint maxValue, out int errorCode)) + { + if (percentageValue.HasValue) + { + // Convert percentage to raw value + setValue = FeatureResolver.ConvertPercentageToRaw(percentageValue.Value, maxValue); + } + else if (feature.Code != InputSource.VcpInputSource) + { + // Validate raw value is within supported range + if (!FeatureResolver.IsValidRawVcpValue(setValue, maxValue)) + { + string rangeError = VcpErrorHandler.CreateRangeValidationMessage(feature, setValue, maxValue); + + if (jsonOutput) + { + var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); + var error = new ErrorResponse(false, rangeError, monitorRef); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError(rangeError); + } + + return 1; + } + } + } + else + { + return HandleReadErrorDuringValidation(monitor, feature, errorCode, jsonOutput); + } + } + + return WriteFeatureValue(monitor, feature, setValue, percentageValue, jsonOutput); + } + + private static int HandleReadErrorDuringValidation(Monitor monitor, VcpFeature feature, int errorCode, bool jsonOutput) + { + string readError; + + if (VcpErrorHandler.IsTimeoutError(errorCode)) + { + readError = VcpErrorHandler.CreateTimeoutMessage(monitor, feature, "read"); + } + else if (VcpErrorHandler.IsUnsupportedFeatureError(errorCode)) + { + readError = VcpErrorHandler.CreateUnsupportedFeatureMessage(monitor, feature); + } + else if (errorCode == 0x00000006) // ERROR_INVALID_HANDLE + { + readError = VcpErrorHandler.CreateCommunicationFailureMessage(monitor); + } + else + { + readError = $"Failed to read current {feature.Name} from monitor '{monitor.Name}' to validate range. {VcpErrorHandler.CreateReadFailureMessage(monitor, feature)}"; + } + + if (jsonOutput) + { + var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); + var error = new ErrorResponse(false, readError, monitorRef); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError(readError); + } + + return 1; + } + + private static int WriteFeatureValue(Monitor monitor, VcpFeature feature, uint setValue, uint? percentageValue, bool jsonOutput) + { + bool success = false; + string? errorMsg = null; + + if (!jsonOutput) + { + string displayValue = percentageValue.HasValue ? $"{percentageValue}%" : setValue.ToString(); + AnsiConsole.Status() + .Start($"Setting {monitor.Name} {feature.Name} to {displayValue}...", ctx => + { + ctx.Spinner(Spinner.Known.Dots); + + if (!monitor.TrySetVcpFeature(feature.Code, setValue, out int errorCode)) + { + errorMsg = GetWriteErrorMessage(monitor, feature, setValue, errorCode); + } + else + { + success = true; + } + + if (success) + { + // Give the monitor a moment to apply the change + Thread.Sleep(500); + } + }); + } + else + { + if (!monitor.TrySetVcpFeature(feature.Code, setValue, out int errorCode)) + { + errorMsg = GetWriteErrorMessage(monitor, feature, setValue, errorCode); + } + else + { + success = true; + Thread.Sleep(500); + } + } + + if (!success) + { + if (jsonOutput) + { + var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); + var error = new ErrorResponse(false, errorMsg!, monitorRef); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError(errorMsg!); + } + + return 1; + } + + OutputSuccess(monitor, feature, setValue, percentageValue, jsonOutput); + return 0; + } + + private static string GetWriteErrorMessage(Monitor monitor, VcpFeature feature, uint setValue, int errorCode) + { + if (VcpErrorHandler.IsTimeoutError(errorCode)) + { + return VcpErrorHandler.CreateTimeoutMessage(monitor, feature, "write"); + } + else if (VcpErrorHandler.IsUnsupportedFeatureError(errorCode)) + { + return VcpErrorHandler.CreateUnsupportedFeatureMessage(monitor, feature); + } + else if (errorCode == 0x00000006) // ERROR_INVALID_HANDLE + { + return VcpErrorHandler.CreateCommunicationFailureMessage(monitor); + } + else + { + return VcpErrorHandler.CreateWriteFailureMessage(monitor, feature, setValue); + } + } + + private static void OutputSuccess(Monitor monitor, VcpFeature feature, uint setValue, uint? percentageValue, bool jsonOutput) + { + if (jsonOutput) + { + var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); + + // Use generic VCP response for all features + var result = new SetVcpResponse(true, monitorRef, feature.Name, setValue, percentageValue); + Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.SetVcpResponse)); + } + else + { + if (feature.Code == InputSource.VcpInputSource) + { + // Display input with name resolution + ConsoleOutputFormatter.WriteSuccess($"Successfully set {monitor.Name} {feature.Name} to {InputSource.GetName(setValue)}"); + } + else if (percentageValue.HasValue) + { + // Display percentage for brightness/contrast + ConsoleOutputFormatter.WriteSuccess($"Successfully set {monitor.Name} {feature.Name} to {percentageValue}%"); + } + else + { + // Display raw value for unknown VCP codes + ConsoleOutputFormatter.WriteSuccess($"Successfully set {monitor.Name} {feature.Name} to {setValue}"); + } + } + } +} + diff --git a/DDCSwitch/Commands/VcpScanCommand.cs b/DDCSwitch/Commands/VcpScanCommand.cs new file mode 100644 index 0000000..101115e --- /dev/null +++ b/DDCSwitch/Commands/VcpScanCommand.cs @@ -0,0 +1,278 @@ +using Spectre.Console; +using System.Text.Json; + +namespace DDCSwitch.Commands; + +internal static class VcpScanCommand +{ + public static int ScanAllMonitors(List monitors, bool jsonOutput) + { + if (jsonOutput) + { + OutputJsonScanAll(monitors); + } + else + { + OutputTableScanAll(monitors); + } + + // Cleanup + foreach (var monitor in monitors) + { + monitor.Dispose(); + } + + return 0; + } + + public static int ScanSingleMonitor(string monitorIdentifier, bool jsonOutput) + { + List monitors; + + if (!jsonOutput) + { + monitors = null!; + AnsiConsole.Status() + .Start("Enumerating monitors...", ctx => + { + ctx.Spinner(Spinner.Known.Dots); + monitors = MonitorController.EnumerateMonitors(); + }); + } + else + { + monitors = MonitorController.EnumerateMonitors(); + } + + if (monitors.Count == 0) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, "No DDC/CI capable monitors found"); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError("No DDC/CI capable monitors found."); + } + + return 1; + } + + var monitor = MonitorController.FindMonitor(monitors, monitorIdentifier); + + if (monitor == null) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, $"Monitor '{monitorIdentifier}' not found"); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError($"Monitor '{monitorIdentifier}' not found."); + AnsiConsole.MarkupLine("Use [yellow]DDCSwitch list[/] to see available monitors."); + } + + // Cleanup + foreach (var m in monitors) + { + m.Dispose(); + } + + return 1; + } + + int result; + + try + { + var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); + Dictionary features; + + if (!jsonOutput) + { + features = null!; + AnsiConsole.Status() + .Start($"Scanning VCP features for {monitor.Name}...", ctx => + { + ctx.Spinner(Spinner.Known.Dots); + features = monitor.ScanVcpFeatures(); + }); + } + else + { + features = monitor.ScanVcpFeatures(); + } + + // Filter only supported features for cleaner output + var supportedFeatures = features.Values + .Where(f => f.IsSupported) + .OrderBy(f => f.Code) + .ToList(); + + if (jsonOutput) + { + OutputJsonScanSingle(monitorRef, supportedFeatures); + } + else + { + OutputTableScanSingle(monitor, supportedFeatures); + } + + result = 0; + } + catch (Exception ex) + { + if (jsonOutput) + { + var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); + var scanResult = new VcpScanResponse(false, monitorRef, new List(), ex.Message); + Console.WriteLine(JsonSerializer.Serialize(scanResult, JsonContext.Default.VcpScanResponse)); + } + else + { + ConsoleOutputFormatter.WriteError($"Error scanning monitor {monitor.Index} ({monitor.Name}): {ex.Message}"); + } + + result = 1; + } + + // Cleanup + foreach (var m in monitors) + { + m.Dispose(); + } + + return result; + } + + private static void OutputJsonScanAll(List monitors) + { + var scanResults = new List(); + + foreach (var monitor in monitors) + { + try + { + var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); + var features = monitor.ScanVcpFeatures(); + + // Convert to list and filter only supported features for cleaner output + var supportedFeatures = features.Values + .Where(f => f.IsSupported) + .OrderBy(f => f.Code) + .ToList(); + + scanResults.Add(new VcpScanResponse(true, monitorRef, supportedFeatures)); + } + catch (Exception ex) + { + var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); + scanResults.Add(new VcpScanResponse(false, monitorRef, new List(), ex.Message)); + } + } + + // Output all scan results + foreach (var result in scanResults) + { + Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.VcpScanResponse)); + } + } + + private static void OutputTableScanAll(List monitors) + { + foreach (var monitor in monitors) + { + try + { + AnsiConsole.MarkupLine($"\n[bold blue]Monitor {monitor.Index}: {monitor.Name}[/] ({monitor.DeviceName})"); + + Dictionary features = null!; + AnsiConsole.Status() + .Start($"Scanning VCP features for {monitor.Name}...", ctx => + { + ctx.Spinner(Spinner.Known.Dots); + features = monitor.ScanVcpFeatures(); + }); + + var supportedFeatures = features.Values + .Where(f => f.IsSupported) + .OrderBy(f => f.Code) + .ToList(); + + if (supportedFeatures.Count == 0) + { + AnsiConsole.MarkupLine("[yellow] No supported VCP features found[/]"); + continue; + } + + OutputFeatureTable(supportedFeatures); + } + catch (Exception ex) + { + ConsoleOutputFormatter.WriteError($"Error scanning monitor {monitor.Index} ({monitor.Name}): {ex.Message}"); + } + } + } + + private static void OutputJsonScanSingle(MonitorReference monitorRef, List supportedFeatures) + { + var scanResult = new VcpScanResponse(true, monitorRef, supportedFeatures); + Console.WriteLine(JsonSerializer.Serialize(scanResult, JsonContext.Default.VcpScanResponse)); + } + + private static void OutputTableScanSingle(Monitor monitor, List supportedFeatures) + { + AnsiConsole.MarkupLine($"[bold blue]Monitor {monitor.Index}: {monitor.Name}[/] ({monitor.DeviceName})"); + + if (supportedFeatures.Count == 0) + { + AnsiConsole.MarkupLine("[yellow] No supported VCP features found[/]"); + } + else + { + OutputFeatureTable(supportedFeatures); + } + } + + private static void OutputFeatureTable(List supportedFeatures) + { + var table = new Table() + .Border(TableBorder.Rounded) + .AddColumn("VCP Code") + .AddColumn("Feature Name") + .AddColumn("Access Type") + .AddColumn("Current Value") + .AddColumn("Max Value") + .AddColumn("Percentage"); + + foreach (var feature in supportedFeatures) + { + string vcpCode = $"0x{feature.Code:X2}"; + string accessType = feature.Type switch + { + VcpFeatureType.ReadOnly => "[yellow]Read-Only[/]", + VcpFeatureType.WriteOnly => "[red]Write-Only[/]", + VcpFeatureType.ReadWrite => "[green]Read-Write[/]", + _ => "[dim]Unknown[/]" + }; + + string currentValue = feature.CurrentValue.ToString(); + string maxValue = feature.MaxValue.ToString(); + + // Calculate percentage for known percentage-based features + string percentage = "N/A"; + if ((feature.Code == VcpFeature.Brightness.Code || feature.Code == VcpFeature.Contrast.Code) && feature.MaxValue > 0) + { + uint percentageValue = FeatureResolver.ConvertRawToPercentage(feature.CurrentValue, feature.MaxValue); + percentage = $"{percentageValue}%"; + } + + table.AddRow(vcpCode, feature.Name, accessType, currentValue, maxValue, percentage); + } + + AnsiConsole.Write(table); + } +} + diff --git a/DDCSwitch/Program.cs b/DDCSwitch/Program.cs index 39c675e..0cac76e 100644 --- a/DDCSwitch/Program.cs +++ b/DDCSwitch/Program.cs @@ -1,1203 +1,4 @@ -using DDCSwitch; -using Spectre.Console; -using System.Text.Json; -using System.Text.Json.Serialization; +using DDCSwitch.Commands; -return DDCSwitchProgram.Run(args); +return CommandRouter.Route(args); -static class DDCSwitchProgram -{ - private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web) - { - WriteIndented = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - TypeInfoResolver = JsonContext.Default - }; - - public static int Run(string[] args) - { - if (args.Length == 0) - { - ShowUsage(); - return 1; - } - - // Check for --json flag - bool jsonOutput = args.Contains("--json", StringComparer.OrdinalIgnoreCase); - var filteredArgs = args.Where(a => !a.Equals("--json", StringComparison.OrdinalIgnoreCase)).ToArray(); - - // Check for --verbose flag - bool verboseOutput = filteredArgs.Contains("--verbose", StringComparer.OrdinalIgnoreCase); - filteredArgs = filteredArgs.Where(a => !a.Equals("--verbose", StringComparison.OrdinalIgnoreCase)).ToArray(); - - // Check for --scan flag - bool scanOutput = filteredArgs.Contains("--scan", StringComparer.OrdinalIgnoreCase); - filteredArgs = filteredArgs.Where(a => !a.Equals("--scan", StringComparison.OrdinalIgnoreCase)).ToArray(); - - if (filteredArgs.Length == 0) - { - ShowUsage(); - return 1; - } - - var command = filteredArgs[0].ToLowerInvariant(); - - try - { - return command switch - { - "list" or "ls" => ListMonitors(jsonOutput, verboseOutput, scanOutput), - "get" => GetCurrentInput(filteredArgs, jsonOutput), - "set" => SetInput(filteredArgs, jsonOutput), - "version" or "-v" or "--version" => ShowVersion(jsonOutput), - "help" or "-h" or "--help" or "/?" => ShowUsage(), - _ => InvalidCommand(filteredArgs[0], jsonOutput) - }; - } - catch (Exception ex) - { - if (jsonOutput) - { - var error = new ErrorResponse(false, ex.Message); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine($"[red]Error:[/] {ex.Message}"); - } - - return 1; - } - } - - private static string GetVersion() - { - var version = typeof(DDCSwitchProgram).Assembly - .GetName().Version?.ToString(3) ?? "0.0.0"; - return version; - } - - private static int ShowVersion(bool jsonOutput) - { - var version = GetVersion(); - - if (jsonOutput) - { - Console.WriteLine($"{{\"version\":\"{version}\"}}"); - } - else - { - AnsiConsole.Write(new FigletText("DDCSwitch").Color(Color.Blue)); - AnsiConsole.MarkupLine($"[bold]Version:[/] [green]{version}[/]"); - AnsiConsole.MarkupLine("[dim]Windows DDC/CI Monitor Input Switcher[/]"); - } - - return 0; - } - - private static int ShowUsage() - { - var version = GetVersion(); - - AnsiConsole.Write(new FigletText("DDCSwitch").Color(Color.Blue)); - AnsiConsole.MarkupLine($"[dim]Windows DDC/CI Monitor Input Switcher v{version}[/]\n"); - - AnsiConsole.MarkupLine("[yellow]Commands:[/]"); - AnsiConsole.WriteLine(" list [--verbose] [--scan] - List all DDC/CI capable monitors"); - AnsiConsole.WriteLine(" get monitor [feature] - Get current value for a monitor feature or scan all features"); - AnsiConsole.MarkupLine(" set monitor feature value - Set value for a monitor feature"); - AnsiConsole.MarkupLine(" version - Display version information"); - - AnsiConsole.MarkupLine("\nSupported features: brightness, contrast, input, or VCP codes like 0x10"); - AnsiConsole.MarkupLine("Use [yellow]--json[/] flag for JSON output"); - AnsiConsole.MarkupLine("Use [yellow]--verbose[/] flag with list to include brightness and contrast"); - AnsiConsole.MarkupLine("Use [yellow]--scan[/] flag with list to enumerate all VCP codes"); - - return 0; - } - - private static int InvalidCommand(string command, bool jsonOutput) - { - if (jsonOutput) - { - var error = new ErrorResponse(false, $"Unknown command: {command}"); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine($"[red]Unknown command:[/] {command}"); - AnsiConsole.MarkupLine("Run [yellow]DDCSwitch help[/] for usage information."); - } - - return 1; - } - - private static int ListMonitors(bool jsonOutput, bool verboseOutput = false, bool scanOutput = false) - { - List monitors; - - if (!jsonOutput) - { - monitors = null!; - AnsiConsole.Status() - .Start(scanOutput ? "Scanning VCP features..." : "Enumerating monitors...", ctx => - { - ctx.Spinner(Spinner.Known.Dots); - monitors = MonitorController.EnumerateMonitors(); - }); - } - else - { - monitors = MonitorController.EnumerateMonitors(); - } - - if (monitors.Count == 0) - { - if (jsonOutput) - { - var result = new ListMonitorsResponse(false, null, "No DDC/CI capable monitors found"); - Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.ListMonitorsResponse)); - } - else - { - AnsiConsole.MarkupLine("[yellow]No DDC/CI capable monitors found.[/]"); - } - - return 1; - } - - // If scan mode is enabled, perform VCP scanning for each monitor - if (scanOutput) - { - return HandleVcpScan(monitors, jsonOutput); - } - - if (jsonOutput) - { - var monitorList = monitors.Select(monitor => - { - string? inputName = null; - uint? inputCode = null; - string status = "ok"; - string? brightness = null; - string? contrast = null; - - try - { - if (monitor.TryGetInputSource(out uint current, out uint max)) - { - inputName = InputSource.GetName(current); - inputCode = current; - } - else - { - status = "no_ddc_ci"; - } - - // Get brightness and contrast if verbose mode is enabled - if (verboseOutput && status == "ok") - { - // Try to get brightness (VCP 0x10) - if (monitor.TryGetVcpFeature(VcpFeature.Brightness.Code, out uint brightnessCurrent, out uint brightnessMax)) - { - uint brightnessPercentage = FeatureResolver.ConvertRawToPercentage(brightnessCurrent, brightnessMax); - brightness = $"{brightnessPercentage}%"; - } - else - { - brightness = "N/A"; - } - - // Try to get contrast (VCP 0x12) - if (monitor.TryGetVcpFeature(VcpFeature.Contrast.Code, out uint contrastCurrent, out uint contrastMax)) - { - uint contrastPercentage = FeatureResolver.ConvertRawToPercentage(contrastCurrent, contrastMax); - contrast = $"{contrastPercentage}%"; - } - else - { - contrast = "N/A"; - } - } - } - catch - { - status = "error"; - if (verboseOutput) - { - brightness = "N/A"; - contrast = "N/A"; - } - } - - return new MonitorInfo( - monitor.Index, - monitor.Name, - monitor.DeviceName, - monitor.IsPrimary, - inputName, - inputCode != null ? $"0x{inputCode:X2}" : null, - status, - brightness, - contrast); - }).ToList(); - - var result = new ListMonitorsResponse(true, monitorList); - Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.ListMonitorsResponse)); - } - else - { - var table = new Table() - .Border(TableBorder.Rounded) - .AddColumn("Index") - .AddColumn("Monitor Name") - .AddColumn("Device") - .AddColumn("Current Input"); - - // Add brightness and contrast columns if verbose mode is enabled - if (verboseOutput) - { - table.AddColumn("Brightness"); - table.AddColumn("Contrast"); - } - - table.AddColumn("Status"); - - foreach (var monitor in monitors) - { - string inputInfo = "N/A"; - string status = "[green]OK[/]"; - string brightnessInfo = "N/A"; - string contrastInfo = "N/A"; - - try - { - if (monitor.TryGetInputSource(out uint current, out uint max)) - { - inputInfo = $"{InputSource.GetName(current)} (0x{current:X2})"; - } - else - { - status = "[yellow]No DDC/CI[/]"; - } - - // Get brightness and contrast if verbose mode is enabled and monitor supports DDC/CI - if (verboseOutput && status == "[green]OK[/]") - { - // Try to get brightness (VCP 0x10) - if (monitor.TryGetVcpFeature(VcpFeature.Brightness.Code, out uint brightnessCurrent, out uint brightnessMax)) - { - uint brightnessPercentage = FeatureResolver.ConvertRawToPercentage(brightnessCurrent, brightnessMax); - brightnessInfo = $"{brightnessPercentage}%"; - } - else - { - brightnessInfo = "[dim]N/A[/]"; - } - - // Try to get contrast (VCP 0x12) - if (monitor.TryGetVcpFeature(VcpFeature.Contrast.Code, out uint contrastCurrent, out uint contrastMax)) - { - uint contrastPercentage = FeatureResolver.ConvertRawToPercentage(contrastCurrent, contrastMax); - contrastInfo = $"{contrastPercentage}%"; - } - else - { - contrastInfo = "[dim]N/A[/]"; - } - } - else if (verboseOutput) - { - brightnessInfo = "[dim]N/A[/]"; - contrastInfo = "[dim]N/A[/]"; - } - } - catch - { - status = "[red]Error[/]"; - if (verboseOutput) - { - brightnessInfo = "[dim]N/A[/]"; - contrastInfo = "[dim]N/A[/]"; - } - } - - var row = new List - { - monitor.IsPrimary ? $"{monitor.Index} [yellow]*[/]" : monitor.Index.ToString(), - monitor.Name, - monitor.DeviceName, - inputInfo - }; - - // Add brightness and contrast columns if verbose mode is enabled - if (verboseOutput) - { - row.Add(brightnessInfo); - row.Add(contrastInfo); - } - - row.Add(status); - - table.AddRow(row.ToArray()); - } - - AnsiConsole.Write(table); - } - - // Cleanup - foreach (var monitor in monitors) - { - monitor.Dispose(); - } - - return 0; - } - - private static int GetCurrentInput(string[] args, bool jsonOutput) - { - if (args.Length < 2) - { - if (jsonOutput) - { - var error = new ErrorResponse(false, "Monitor identifier required"); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine("[red]Error:[/] Monitor identifier required."); - AnsiConsole.WriteLine("Usage: DDCSwitch get [feature]"); - } - - return 1; - } - - // If no feature is specified, perform VCP scan - if (args.Length == 2) - { - return HandleVcpScanForMonitor(args[1], jsonOutput); - } - - string featureInput = args[2]; - - if (!FeatureResolver.TryResolveFeature(featureInput, out VcpFeature? feature)) - { - string errorMessage; - - // Provide specific error message based on input type - if (FeatureResolver.TryParseVcpCode(featureInput, out _)) - { - // Valid VCP code but not in our predefined list - errorMessage = $"VCP code '{featureInput}' is valid but may not be supported by all monitors"; - } - else - { - // Invalid feature name or VCP code - errorMessage = $"Invalid feature '{featureInput}'. {FeatureResolver.GetVcpCodeValidationError(featureInput)}"; - } - - if (jsonOutput) - { - var error = new ErrorResponse(false, errorMessage); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine($"[red]Error:[/] {errorMessage}"); - AnsiConsole.MarkupLine("Valid features: brightness, contrast, input, or VCP code (0x10, 0x12, etc.)"); - } - - return 1; - } - - var monitors = MonitorController.EnumerateMonitors(); - - if (monitors.Count == 0) - { - if (jsonOutput) - { - var error = new ErrorResponse(false, "No DDC/CI capable monitors found"); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine("[red]Error:[/] No DDC/CI capable monitors found."); - } - - return 1; - } - - var monitor = MonitorController.FindMonitor(monitors, args[1]); - - if (monitor == null) - { - if (jsonOutput) - { - var error = new ErrorResponse(false, $"Monitor '{args[1]}' not found"); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine($"[red]Error:[/] Monitor '{args[1]}' not found."); - AnsiConsole.MarkupLine("Use [yellow]DDCSwitch list[/] to see available monitors."); - } - - // Cleanup - foreach (var m in monitors) - { - m.Dispose(); - } - - return 1; - } - - // Use generic VCP method for all features with enhanced error handling - bool success = false; - uint current = 0; - uint max = 0; - int errorCode = 0; - - if (!jsonOutput) - { - AnsiConsole.Status() - .Start($"Reading {feature!.Name} from {monitor.Name}...", ctx => - { - ctx.Spinner(Spinner.Known.Dots); - success = monitor.TryGetVcpFeature(feature.Code, out current, out max, out errorCode); - }); - } - else - { - success = monitor.TryGetVcpFeature(feature!.Code, out current, out max, out errorCode); - } - - if (!success) - { - string errorMessage; - - if (VcpErrorHandler.IsTimeoutError(errorCode)) - { - errorMessage = VcpErrorHandler.CreateTimeoutMessage(monitor, feature, "read"); - } - else if (VcpErrorHandler.IsUnsupportedFeatureError(errorCode)) - { - errorMessage = VcpErrorHandler.CreateUnsupportedFeatureMessage(monitor, feature); - } - else if (errorCode == 0x00000006) // ERROR_INVALID_HANDLE - { - errorMessage = VcpErrorHandler.CreateCommunicationFailureMessage(monitor); - } - else - { - errorMessage = VcpErrorHandler.CreateReadFailureMessage(monitor, feature); - } - - if (jsonOutput) - { - var monitorRef = - new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - var error = new ErrorResponse(false, errorMessage, monitorRef); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine($"[red]Error:[/] {errorMessage}"); - } - - // Cleanup - foreach (var m in monitors) - { - m.Dispose(); - } - - return 1; - } - - if (jsonOutput) - { - var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - - uint? percentageValue = feature.SupportsPercentage ? FeatureResolver.ConvertRawToPercentage(current, max) : null; - var result = new GetVcpResponse(true, monitorRef, feature.Name, current, max, percentageValue); - Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.GetVcpResponse)); - } - else - { - AnsiConsole.MarkupLine($"[green]Monitor:[/] {monitor.Name} ({monitor.DeviceName})"); - - if (feature.Code == InputSource.VcpInputSource) - { - // Display input with name resolution - AnsiConsole.MarkupLine($"[green]Current {feature.Name}:[/] {InputSource.GetName(current)} (0x{current:X2})"); - } - else if (feature.SupportsPercentage) - { - // Display percentage for brightness/contrast - uint percentage = FeatureResolver.ConvertRawToPercentage(current, max); - AnsiConsole.MarkupLine($"[green]Current {feature.Name}:[/] {percentage}% (raw: {current}/{max})"); - } - else - { - // Display raw values for unknown VCP codes - AnsiConsole.MarkupLine($"[green]Current {feature.Name}:[/] {current} (max: {max})"); - } - } - - // Cleanup - foreach (var m in monitors) - { - m.Dispose(); - } - - return 0; - } - - private static int SetInput(string[] args, bool jsonOutput) - { - // Require 4 arguments: set - if (args.Length < 4) - { - if (jsonOutput) - { - var error = new ErrorResponse(false, "Monitor, feature, and value required"); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine("[red]Error:[/] Monitor, feature, and value required."); - AnsiConsole.MarkupLine("Usage: [yellow]DDCSwitch set [/]"); - } - - return 1; - } - - string featureInput = args[2]; - string valueInput = args[3]; - - if (!FeatureResolver.TryResolveFeature(featureInput, out VcpFeature? feature)) - { - string errorMessage; - - // Provide specific error message based on input type - if (FeatureResolver.TryParseVcpCode(featureInput, out _)) - { - // Valid VCP code but not in our predefined list - errorMessage = $"VCP code '{featureInput}' is valid but may not be supported by all monitors"; - } - else - { - // Invalid feature name or VCP code - errorMessage = $"Invalid feature '{featureInput}'. {FeatureResolver.GetVcpCodeValidationError(featureInput)}"; - } - - if (jsonOutput) - { - var error = new ErrorResponse(false, errorMessage); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine($"[red]Error:[/] {errorMessage}"); - AnsiConsole.MarkupLine("Valid features: brightness, contrast, input, or VCP code (0x10, 0x12, etc.)"); - } - - return 1; - } - - // Parse and validate the value based on feature type - uint setValue = 0; // Initialize to avoid compiler error - uint? percentageValue = null; - string? validationError = null; - - if (feature!.Code == InputSource.VcpInputSource) - { - // Use existing input source parsing for input feature - if (!InputSource.TryParse(valueInput, out setValue)) - { - validationError = $"Invalid input source '{valueInput}'. Valid inputs: HDMI1, HDMI2, DP1, DP2, DVI1, DVI2, VGA1, VGA2, or hex code (0x11)"; - } - } - else if (feature.SupportsPercentage && FeatureResolver.TryParsePercentage(valueInput, out uint percentage)) - { - // Parse as percentage for brightness/contrast - validate percentage range - if (!FeatureResolver.IsValidPercentage(percentage)) - { - validationError = VcpErrorHandler.CreateRangeValidationMessage(feature, percentage, 100, true); - } - else - { - percentageValue = percentage; - // We'll convert to raw value after getting monitor's max value - setValue = 0; // Placeholder - } - } - else if (uint.TryParse(valueInput, out uint rawValue)) - { - // Parse as raw value - we'll validate range after getting monitor's max value - setValue = rawValue; - } - else - { - // Invalid value format - if (feature.SupportsPercentage) - { - validationError = FeatureResolver.GetPercentageValidationError(valueInput); - } - else - { - validationError = $"Invalid value '{valueInput}' for feature '{feature.Name}'. Expected: numeric value within monitor's supported range"; - } - } - - // If we have a validation error, return it now - if (validationError != null) - { - if (jsonOutput) - { - var error = new ErrorResponse(false, validationError); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine($"[red]Error:[/] {validationError}"); - } - - return 1; - } - - var monitors = MonitorController.EnumerateMonitors(); - - if (monitors.Count == 0) - { - if (jsonOutput) - { - var error = new ErrorResponse(false, "No DDC/CI capable monitors found"); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine("[red]Error:[/] No DDC/CI capable monitors found."); - } - - return 1; - } - - var monitor = MonitorController.FindMonitor(monitors, args[1]); - - if (monitor == null) - { - if (jsonOutput) - { - var error = new ErrorResponse(false, $"Monitor '{args[1]}' not found"); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine($"[red]Error:[/] Monitor '{args[1]}' not found."); - AnsiConsole.MarkupLine("Use [yellow]DDCSwitch list[/] to see available monitors."); - } - - // Cleanup - foreach (var m in monitors) - { - m.Dispose(); - } - - return 1; - } - - // If we have a percentage value or need to validate raw value range, get the monitor's max value - if (percentageValue.HasValue || (feature!.Code != InputSource.VcpInputSource && !percentageValue.HasValue)) - { - if (monitor.TryGetVcpFeature(feature.Code, out uint currentValue, out uint maxValue, out int errorCode)) - { - if (percentageValue.HasValue) - { - // Convert percentage to raw value - setValue = FeatureResolver.ConvertPercentageToRaw(percentageValue.Value, maxValue); - } - else if (feature.Code != InputSource.VcpInputSource) - { - // Validate raw value is within supported range - if (!FeatureResolver.IsValidRawVcpValue(setValue, maxValue)) - { - string rangeError = VcpErrorHandler.CreateRangeValidationMessage(feature, setValue, maxValue); - - if (jsonOutput) - { - var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - var error = new ErrorResponse(false, rangeError, monitorRef); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine($"[red]Error:[/] {rangeError}"); - } - - // Cleanup - foreach (var m in monitors) - { - m.Dispose(); - } - - return 1; - } - } - } - else - { - string readError; - - if (VcpErrorHandler.IsTimeoutError(errorCode)) - { - readError = VcpErrorHandler.CreateTimeoutMessage(monitor, feature, "read"); - } - else if (VcpErrorHandler.IsUnsupportedFeatureError(errorCode)) - { - readError = VcpErrorHandler.CreateUnsupportedFeatureMessage(monitor, feature); - } - else if (errorCode == 0x00000006) // ERROR_INVALID_HANDLE - { - readError = VcpErrorHandler.CreateCommunicationFailureMessage(monitor); - } - else - { - readError = $"Failed to read current {feature.Name} from monitor '{monitor.Name}' to validate range. {VcpErrorHandler.CreateReadFailureMessage(monitor, feature)}"; - } - - if (jsonOutput) - { - var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - var error = new ErrorResponse(false, readError, monitorRef); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine($"[red]Error:[/] {readError}"); - } - - // Cleanup - foreach (var m in monitors) - { - m.Dispose(); - } - - return 1; - } - } - - bool success = false; - string? errorMsg = null; - - if (!jsonOutput) - { - string displayValue = percentageValue.HasValue ? $"{percentageValue}%" : setValue.ToString(); - AnsiConsole.Status() - .Start($"Setting {monitor.Name} {feature.Name} to {displayValue}...", ctx => - { - ctx.Spinner(Spinner.Known.Dots); - - if (!monitor.TrySetVcpFeature(feature!.Code, setValue, out int errorCode)) - { - if (VcpErrorHandler.IsTimeoutError(errorCode)) - { - errorMsg = VcpErrorHandler.CreateTimeoutMessage(monitor, feature, "write"); - } - else if (VcpErrorHandler.IsUnsupportedFeatureError(errorCode)) - { - errorMsg = VcpErrorHandler.CreateUnsupportedFeatureMessage(monitor, feature); - } - else if (errorCode == 0x00000006) // ERROR_INVALID_HANDLE - { - errorMsg = VcpErrorHandler.CreateCommunicationFailureMessage(monitor); - } - else - { - errorMsg = VcpErrorHandler.CreateWriteFailureMessage(monitor, feature, setValue); - } - } - else - { - success = true; - } - - if (success) - { - // Give the monitor a moment to apply the change - Thread.Sleep(500); - } - }); - } - else - { - if (!monitor.TrySetVcpFeature(feature!.Code, setValue, out int errorCode)) - { - if (VcpErrorHandler.IsTimeoutError(errorCode)) - { - errorMsg = VcpErrorHandler.CreateTimeoutMessage(monitor, feature, "write"); - } - else if (VcpErrorHandler.IsUnsupportedFeatureError(errorCode)) - { - errorMsg = VcpErrorHandler.CreateUnsupportedFeatureMessage(monitor, feature); - } - else if (errorCode == 0x00000006) // ERROR_INVALID_HANDLE - { - errorMsg = VcpErrorHandler.CreateCommunicationFailureMessage(monitor); - } - else - { - errorMsg = VcpErrorHandler.CreateWriteFailureMessage(monitor, feature, setValue); - } - } - else - { - success = true; - Thread.Sleep(500); - } - } - - if (!success) - { - if (jsonOutput) - { - var monitorRef = - new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - var error = new ErrorResponse(false, errorMsg!, monitorRef); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine($"[red]Error:[/] {errorMsg}"); - } - - // Cleanup - foreach (var m in monitors) - { - m.Dispose(); - } - - return 1; - } - - if (jsonOutput) - { - var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - - // Use generic VCP response for all features - var result = new SetVcpResponse(true, monitorRef, feature!.Name, setValue, percentageValue); - Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.SetVcpResponse)); - } - else - { - if (feature!.Code == InputSource.VcpInputSource) - { - // Display input with name resolution - AnsiConsole.MarkupLine($"[green]✓[/] Successfully set {monitor.Name} {feature.Name} to {InputSource.GetName(setValue)}"); - } - else if (percentageValue.HasValue) - { - // Display percentage for brightness/contrast - AnsiConsole.MarkupLine($"[green]✓[/] Successfully set {monitor.Name} {feature.Name} to {percentageValue}%"); - } - else - { - // Display raw value for unknown VCP codes - AnsiConsole.MarkupLine($"[green]✓[/] Successfully set {monitor.Name} {feature.Name} to {setValue}"); - } - } - - // Cleanup - foreach (var m in monitors) - { - m.Dispose(); - } - - return 0; - } - - private static int HandleVcpScan(List monitors, bool jsonOutput) - { - if (jsonOutput) - { - // JSON output for VCP scan - var scanResults = new List(); - - foreach (var monitor in monitors) - { - try - { - var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - var features = monitor.ScanVcpFeatures(); - - // Convert to list and filter only supported features for cleaner output - var supportedFeatures = features.Values - .Where(f => f.IsSupported) - .OrderBy(f => f.Code) - .ToList(); - - scanResults.Add(new VcpScanResponse(true, monitorRef, supportedFeatures)); - } - catch (Exception ex) - { - var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - scanResults.Add(new VcpScanResponse(false, monitorRef, new List(), ex.Message)); - } - } - - // Output all scan results - foreach (var result in scanResults) - { - Console.WriteLine(JsonSerializer.Serialize(result, JsonContext.Default.VcpScanResponse)); - } - } - else - { - // Table output for VCP scan - foreach (var monitor in monitors) - { - try - { - AnsiConsole.MarkupLine($"\n[bold blue]Monitor {monitor.Index}: {monitor.Name}[/] ({monitor.DeviceName})"); - - Dictionary features = null!; - AnsiConsole.Status() - .Start($"Scanning VCP features for {monitor.Name}...", ctx => - { - ctx.Spinner(Spinner.Known.Dots); - features = monitor.ScanVcpFeatures(); - }); - - var supportedFeatures = features.Values - .Where(f => f.IsSupported) - .OrderBy(f => f.Code) - .ToList(); - - if (supportedFeatures.Count == 0) - { - AnsiConsole.MarkupLine("[yellow] No supported VCP features found[/]"); - continue; - } - - var table = new Table() - .Border(TableBorder.Rounded) - .AddColumn("VCP Code") - .AddColumn("Feature Name") - .AddColumn("Access Type") - .AddColumn("Current Value") - .AddColumn("Max Value") - .AddColumn("Percentage"); - - foreach (var feature in supportedFeatures) - { - string vcpCode = $"0x{feature.Code:X2}"; - string accessType = feature.Type switch - { - VcpFeatureType.ReadOnly => "[yellow]Read-Only[/]", - VcpFeatureType.WriteOnly => "[red]Write-Only[/]", - VcpFeatureType.ReadWrite => "[green]Read-Write[/]", - _ => "[dim]Unknown[/]" - }; - - string currentValue = feature.CurrentValue.ToString(); - string maxValue = feature.MaxValue.ToString(); - - // Calculate percentage for known percentage-based features - string percentage = "N/A"; - if ((feature.Code == VcpFeature.Brightness.Code || feature.Code == VcpFeature.Contrast.Code) && feature.MaxValue > 0) - { - uint percentageValue = FeatureResolver.ConvertRawToPercentage(feature.CurrentValue, feature.MaxValue); - percentage = $"{percentageValue}%"; - } - - table.AddRow(vcpCode, feature.Name, accessType, currentValue, maxValue, percentage); - } - - AnsiConsole.Write(table); - } - catch (Exception ex) - { - AnsiConsole.MarkupLine($"[red]Error scanning monitor {monitor.Index} ({monitor.Name}): {ex.Message}[/]"); - } - } - } - - // Cleanup - foreach (var monitor in monitors) - { - monitor.Dispose(); - } - - return 0; - } - - private static int HandleVcpScanForMonitor(string monitorIdentifier, bool jsonOutput) - { - List monitors; - - if (!jsonOutput) - { - monitors = null!; - AnsiConsole.Status() - .Start("Enumerating monitors...", ctx => - { - ctx.Spinner(Spinner.Known.Dots); - monitors = MonitorController.EnumerateMonitors(); - }); - } - else - { - monitors = MonitorController.EnumerateMonitors(); - } - - if (monitors.Count == 0) - { - if (jsonOutput) - { - var error = new ErrorResponse(false, "No DDC/CI capable monitors found"); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine("[red]Error:[/] No DDC/CI capable monitors found."); - } - - return 1; - } - - var monitor = MonitorController.FindMonitor(monitors, monitorIdentifier); - - if (monitor == null) - { - if (jsonOutput) - { - var error = new ErrorResponse(false, $"Monitor '{monitorIdentifier}' not found"); - Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); - } - else - { - AnsiConsole.MarkupLine($"[red]Error:[/] Monitor '{monitorIdentifier}' not found."); - AnsiConsole.MarkupLine("Use [yellow]DDCSwitch list[/] to see available monitors."); - } - - // Cleanup - foreach (var m in monitors) - { - m.Dispose(); - } - - return 1; - } - - try - { - var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - Dictionary features; - - if (!jsonOutput) - { - features = null!; - AnsiConsole.Status() - .Start($"Scanning VCP features for {monitor.Name}...", ctx => - { - ctx.Spinner(Spinner.Known.Dots); - features = monitor.ScanVcpFeatures(); - }); - } - else - { - features = monitor.ScanVcpFeatures(); - } - - // Filter only supported features for cleaner output - var supportedFeatures = features.Values - .Where(f => f.IsSupported) - .OrderBy(f => f.Code) - .ToList(); - - if (jsonOutput) - { - // JSON output for VCP scan - var scanResult = new VcpScanResponse(true, monitorRef, supportedFeatures); - Console.WriteLine(JsonSerializer.Serialize(scanResult, JsonContext.Default.VcpScanResponse)); - } - else - { - // Table output for VCP scan - consistent with verbose listing format - AnsiConsole.MarkupLine($"[bold blue]Monitor {monitor.Index}: {monitor.Name}[/] ({monitor.DeviceName})"); - - if (supportedFeatures.Count == 0) - { - AnsiConsole.MarkupLine("[yellow] No supported VCP features found[/]"); - } - else - { - var table = new Table() - .Border(TableBorder.Rounded) - .AddColumn("VCP Code") - .AddColumn("Feature Name") - .AddColumn("Access Type") - .AddColumn("Current Value") - .AddColumn("Max Value") - .AddColumn("Percentage"); - - foreach (var feature in supportedFeatures) - { - string vcpCode = $"0x{feature.Code:X2}"; - string accessType = feature.Type switch - { - VcpFeatureType.ReadOnly => "[yellow]Read-Only[/]", - VcpFeatureType.WriteOnly => "[red]Write-Only[/]", - VcpFeatureType.ReadWrite => "[green]Read-Write[/]", - _ => "[dim]Unknown[/]" - }; - - string currentValue = feature.CurrentValue.ToString(); - string maxValue = feature.MaxValue.ToString(); - - // Calculate percentage for known percentage-based features - string percentage = "N/A"; - if ((feature.Code == VcpFeature.Brightness.Code || feature.Code == VcpFeature.Contrast.Code) && feature.MaxValue > 0) - { - uint percentageValue = FeatureResolver.ConvertRawToPercentage(feature.CurrentValue, feature.MaxValue); - percentage = $"{percentageValue}%"; - } - - table.AddRow(vcpCode, feature.Name, accessType, currentValue, maxValue, percentage); - } - - AnsiConsole.Write(table); - } - } - } - catch (Exception ex) - { - if (jsonOutput) - { - var monitorRef = new MonitorReference(monitor.Index, monitor.Name, monitor.DeviceName, monitor.IsPrimary); - var scanResult = new VcpScanResponse(false, monitorRef, new List(), ex.Message); - Console.WriteLine(JsonSerializer.Serialize(scanResult, JsonContext.Default.VcpScanResponse)); - } - else - { - AnsiConsole.MarkupLine($"[red]Error scanning monitor {monitor.Index} ({monitor.Name}): {ex.Message}[/]"); - } - - // Cleanup - foreach (var m in monitors) - { - m.Dispose(); - } - - return 1; - } - - // Cleanup - foreach (var m in monitors) - { - m.Dispose(); - } - - return 0; - } -} \ No newline at end of file From ff0b80882651477c9fc56c77184d283130af4c4a Mon Sep 17 00:00:00 2001 From: Quick <577652+markdwags@users.noreply.github.com> Date: Thu, 8 Jan 2026 08:17:29 -0600 Subject: [PATCH 06/10] enhance command handling and output formatting; update scan and list commands for improved usability, add support for monitor name queries, and refine console output styles --- DDCSwitch/Commands/CommandRouter.cs | 4 +- DDCSwitch/Commands/ConsoleOutputFormatter.cs | 26 ++++++- DDCSwitch/Commands/GetCommand.cs | 25 +++++-- DDCSwitch/Commands/HelpCommand.cs | 72 ++++++++++++++++---- DDCSwitch/Commands/ListCommand.cs | 41 +++++------ DDCSwitch/Commands/SetCommand.cs | 20 +++++- DDCSwitch/Commands/VcpScanCommand.cs | 67 +++++++++++------- DDCSwitch/Properties/launchSettings.json | 4 ++ EXAMPLES.md | 30 +++++++- README.md | 31 ++++++++- 10 files changed, 246 insertions(+), 74 deletions(-) diff --git a/DDCSwitch/Commands/CommandRouter.cs b/DDCSwitch/Commands/CommandRouter.cs index a376628..a77f613 100644 --- a/DDCSwitch/Commands/CommandRouter.cs +++ b/DDCSwitch/Commands/CommandRouter.cs @@ -21,8 +21,8 @@ public static int Route(string[] args) filteredArgs = filteredArgs.Where(a => !a.Equals("--verbose", StringComparison.OrdinalIgnoreCase)).ToArray(); // Check for --scan flag - bool scanOutput = filteredArgs.Contains("--scan", StringComparer.OrdinalIgnoreCase); - filteredArgs = filteredArgs.Where(a => !a.Equals("--scan", StringComparison.OrdinalIgnoreCase)).ToArray(); + bool scanOutput = filteredArgs.Contains("--all", StringComparer.OrdinalIgnoreCase); + filteredArgs = filteredArgs.Where(a => !a.Equals("--alll", StringComparison.OrdinalIgnoreCase)).ToArray(); if (filteredArgs.Length == 0) { diff --git a/DDCSwitch/Commands/ConsoleOutputFormatter.cs b/DDCSwitch/Commands/ConsoleOutputFormatter.cs index f58ed3c..3a917f2 100644 --- a/DDCSwitch/Commands/ConsoleOutputFormatter.cs +++ b/DDCSwitch/Commands/ConsoleOutputFormatter.cs @@ -6,17 +6,37 @@ internal static class ConsoleOutputFormatter { public static void WriteError(string message) { - AnsiConsole.MarkupLine($"[red]Error:[/] {message}"); + AnsiConsole.MarkupLine($"[bold red]X Error:[/] [red]{message}[/]"); } public static void WriteInfo(string message) { - AnsiConsole.MarkupLine(message); + AnsiConsole.MarkupLine($"[cyan]i[/] {message}"); } public static void WriteSuccess(string message) { - AnsiConsole.MarkupLine($"[green]✓[/] {message}"); + AnsiConsole.MarkupLine($"[bold green]> Success:[/] [green]{message}[/]"); + } + + public static void WriteWarning(string message) + { + AnsiConsole.MarkupLine($"[bold yellow]! Warning:[/] [yellow]{message}[/]"); + } + + public static void WriteHeader(string text) + { + var rule = new Rule($"[bold cyan]{text}[/]") + { + Justification = Justify.Left + }; + AnsiConsole.Write(rule); + } + + public static void WriteMonitorInfo(string label, string value, bool highlight = false) + { + var color = highlight ? "yellow" : "cyan"; + AnsiConsole.MarkupLine($" [bold {color}]{label}:[/] {value}"); } } diff --git a/DDCSwitch/Commands/GetCommand.cs b/DDCSwitch/Commands/GetCommand.cs index 83718c9..4a65a45 100644 --- a/DDCSwitch/Commands/GetCommand.cs +++ b/DDCSwitch/Commands/GetCommand.cs @@ -141,6 +141,7 @@ private static int ReadFeatureValue(Monitor monitor, VcpFeature feature, bool js .Start($"Reading {feature.Name} from {monitor.Name}...", ctx => { ctx.Spinner(Spinner.Known.Dots); + ctx.SpinnerStyle(Style.Parse("cyan")); success = monitor.TryGetVcpFeature(feature.Code, out current, out max, out errorCode); }); } @@ -205,23 +206,39 @@ private static void OutputFeatureValue(Monitor monitor, VcpFeature feature, uint } else { - AnsiConsole.MarkupLine($"[green]Monitor:[/] {monitor.Name} ({monitor.DeviceName})"); + var panel = new Panel( + $"[bold cyan]Monitor:[/] {monitor.Name}\n" + + $"[dim]Device:[/] [dim]{monitor.DeviceName}[/]") + { + Header = new PanelHeader($"[bold green]>> Feature Value[/]", Justify.Left), + Border = BoxBorder.Rounded, + BorderStyle = new Style(Color.Cyan) + }; + AnsiConsole.Write(panel); if (feature.Code == InputSource.VcpInputSource) { // Display input with name resolution - AnsiConsole.MarkupLine($"[green]Current {feature.Name}:[/] {InputSource.GetName(current)} (0x{current:X2})"); + var inputName = InputSource.GetName(current); + AnsiConsole.MarkupLine($" [bold yellow]{feature.Name}:[/] [cyan]{inputName}[/] [dim](0x{current:X2})[/]"); } else if (feature.SupportsPercentage) { // Display percentage for brightness/contrast uint percentage = FeatureResolver.ConvertRawToPercentage(current, max); - AnsiConsole.MarkupLine($"[green]Current {feature.Name}:[/] {percentage}% (raw: {current}/{max})"); + var progressBar = new BarChart() + .Width(40) + .Label($"[bold yellow]{feature.Name}[/]") + .CenterLabel() + .AddItem("", percentage, Color.Green); + + AnsiConsole.Write(progressBar); + AnsiConsole.MarkupLine($" [bold green]{percentage}%[/] [dim](raw: {current}/{max})[/]"); } else { // Display raw values for unknown VCP codes - AnsiConsole.MarkupLine($"[green]Current {feature.Name}:[/] {current} (max: {max})"); + AnsiConsole.MarkupLine($" [bold yellow]{feature.Name}:[/] [green]{current}[/] [dim](max: {max})[/]"); } } } diff --git a/DDCSwitch/Commands/HelpCommand.cs b/DDCSwitch/Commands/HelpCommand.cs index 1c52dde..366deae 100644 --- a/DDCSwitch/Commands/HelpCommand.cs +++ b/DDCSwitch/Commands/HelpCommand.cs @@ -21,9 +21,9 @@ public static int ShowVersion(bool jsonOutput) } else { - AnsiConsole.Write(new FigletText("DDCSwitch").Color(Color.Blue)); - AnsiConsole.MarkupLine($"[bold]Version:[/] [green]{version}[/]"); - AnsiConsole.MarkupLine("[dim]Windows DDC/CI Monitor Input Switcher[/]"); + AnsiConsole.Write(new FigletText("DDCSwitch").Color(Color.Cyan1)); + AnsiConsole.MarkupLine($"[bold cyan]Version:[/] [green]{version}[/]"); + AnsiConsole.MarkupLine("[dim italic]>> Windows DDC/CI Monitor Input Switcher[/]"); } return 0; @@ -33,19 +33,63 @@ public static int ShowUsage() { var version = GetVersion(); - AnsiConsole.Write(new FigletText("DDCSwitch").Color(Color.Blue)); - AnsiConsole.MarkupLine($"[dim]Windows DDC/CI Monitor Input Switcher v{version}[/]\n"); + AnsiConsole.Write(new FigletText("DDCSwitch").Color(Color.Cyan1)); + AnsiConsole.MarkupLine($"[bold white]{version}[/]"); + AnsiConsole.MarkupLine($"[dim italic]>> A Windows command-line utility to control monitors using DDC/CI[/]\n"); - AnsiConsole.MarkupLine("[yellow]Commands:[/]"); - AnsiConsole.WriteLine(" list [--verbose] [--scan] - List all DDC/CI capable monitors"); - AnsiConsole.WriteLine(" get monitor [feature] - Get current value for a monitor feature or scan all features"); - AnsiConsole.MarkupLine(" set monitor feature value - Set value for a monitor feature"); - AnsiConsole.MarkupLine(" version - Display version information"); + var commandsTable = new Table() + .Border(TableBorder.Rounded) + .BorderColor(Color.White) + .AddColumn(new TableColumn("[bold yellow]Command[/]").LeftAligned()) + .AddColumn(new TableColumn("[bold yellow]Description[/]").LeftAligned()); + + commandsTable.AddRow( + "[cyan]list[/] [dim]or[/] [cyan]ls[/]", + "List all DDC/CI capable monitors with current input sources"); + commandsTable.AddRow( + "[cyan]list --verbose[/]", + "Include brightness and contrast information"); + commandsTable.AddRow( + "[cyan]list --scan[/]", + "Enumerate all supported VCP features for each monitor"); + commandsTable.AddRow( + "[cyan]get[/] [green][/] [blue][[feature]][/]", + "Get current value for a monitor feature or get all features"); + commandsTable.AddRow( + "[cyan]set[/] [green][/] [blue][/] [magenta][/]", + "Set value for a monitor feature"); + commandsTable.AddRow( + "[cyan]version[/] [dim]or[/] [cyan]-v[/]", + "Display version information"); + commandsTable.AddRow( + "[cyan]help[/] [dim]or[/] [cyan]-h[/]", + "Show this help message"); + + AnsiConsole.Write(commandsTable); + + AnsiConsole.WriteLine(); - AnsiConsole.MarkupLine("\nSupported features: brightness, contrast, input, or VCP codes like 0x10"); - AnsiConsole.MarkupLine("Use [yellow]--json[/] flag for JSON output"); - AnsiConsole.MarkupLine("Use [yellow]--verbose[/] flag with list to include brightness and contrast"); - AnsiConsole.MarkupLine("Use [yellow]--scan[/] flag with list to enumerate all VCP codes"); + var panel = new Panel( + "[bold yellow]Features:[/] brightness, contrast, input, or VCP codes like [cyan]0x10[/]\n" + + "[bold yellow]Flags:[/]\n" + + " [cyan]--json[/] Machine-readable JSON output for automation\n" + + " [cyan]--verbose[/] Include detailed information in list command\n" + + " [cyan]--all[/] Enumerate all VCP features in list command") + { + Header = new PanelHeader("[bold green]>> Quick Reference[/]", Justify.Left), + Border = BoxBorder.Rounded, + BorderStyle = new Style(Color.Green) + }; + + AnsiConsole.Write(panel); + + AnsiConsole.WriteLine(); + AnsiConsole.MarkupLine("[dim]Examples:[/]"); + AnsiConsole.MarkupLine(" [grey]$[/] DDCSwitch list"); + AnsiConsole.MarkupLine(" [grey]$[/] DDCSwitch get 0"); + AnsiConsole.MarkupLine(" [grey]$[/] DDCSwitch get 0 brightness"); + AnsiConsole.MarkupLine(" [grey]$[/] DDCSwitch set 0 input HDMI1"); + AnsiConsole.MarkupLine(" [grey]$[/] DDCSwitch set 1 brightness 75%"); return 0; } diff --git a/DDCSwitch/Commands/ListCommand.cs b/DDCSwitch/Commands/ListCommand.cs index 9d02c22..64f7114 100644 --- a/DDCSwitch/Commands/ListCommand.cs +++ b/DDCSwitch/Commands/ListCommand.cs @@ -16,6 +16,7 @@ public static int Execute(bool jsonOutput, bool verboseOutput, bool scanOutput) .Start(scanOutput ? "Scanning VCP features..." : "Enumerating monitors...", ctx => { ctx.Spinner(Spinner.Known.Dots); + ctx.SpinnerStyle(Style.Parse("cyan")); monitors = MonitorController.EnumerateMonitors(); }); } @@ -141,46 +142,48 @@ private static void OutputTableList(List monitors, bool verboseOutput) { var table = new Table() .Border(TableBorder.Rounded) - .AddColumn("Index") - .AddColumn("Monitor Name") - .AddColumn("Device") - .AddColumn("Current Input"); + .BorderColor(Color.White) + .AddColumn(new TableColumn("[bold yellow]Index[/]").Centered()) + .AddColumn(new TableColumn("[bold yellow]Monitor Name[/]").LeftAligned()) + .AddColumn(new TableColumn("[bold yellow]Device[/]").LeftAligned()) + .AddColumn(new TableColumn("[bold yellow]Current Input[/]").LeftAligned()); // Add brightness and contrast columns if verbose mode is enabled if (verboseOutput) { - table.AddColumn("Brightness"); - table.AddColumn("Contrast"); + table.AddColumn(new TableColumn("[bold yellow]Brightness[/]").Centered()); + table.AddColumn(new TableColumn("[bold yellow]Contrast[/]").Centered()); } - table.AddColumn("Status"); + table.AddColumn(new TableColumn("[bold yellow]Status[/]").Centered()); foreach (var monitor in monitors) { - string inputInfo = "N/A"; - string status = "[green]OK[/]"; - string brightnessInfo = "N/A"; - string contrastInfo = "N/A"; + string inputInfo = "[dim]N/A[/]"; + string status = "[green]+[/] [bold green]OK[/]"; + string brightnessInfo = "[dim]N/A[/]"; + string contrastInfo = "[dim]N/A[/]"; try { if (monitor.TryGetInputSource(out uint current, out _)) { - inputInfo = $"{InputSource.GetName(current)} (0x{current:X2})"; + var inputName = InputSource.GetName(current); + inputInfo = $"[cyan]{inputName}[/] [dim](0x{current:X2})[/]"; } else { - status = "[yellow]No DDC/CI[/]"; + status = "[yellow]~[/] [bold yellow]No DDC/CI[/]"; } // Get brightness and contrast if verbose mode is enabled and monitor supports DDC/CI - if (verboseOutput && status == "[green]OK[/]") + if (verboseOutput && status == "[green]+[/] [bold green]OK[/]") { // Try to get brightness (VCP 0x10) if (monitor.TryGetVcpFeature(VcpFeature.Brightness.Code, out uint brightnessCurrent, out uint brightnessMax)) { uint brightnessPercentage = FeatureResolver.ConvertRawToPercentage(brightnessCurrent, brightnessMax); - brightnessInfo = $"{brightnessPercentage}%"; + brightnessInfo = $"[green]{brightnessPercentage}%[/]"; } else { @@ -191,7 +194,7 @@ private static void OutputTableList(List monitors, bool verboseOutput) if (monitor.TryGetVcpFeature(VcpFeature.Contrast.Code, out uint contrastCurrent, out uint contrastMax)) { uint contrastPercentage = FeatureResolver.ConvertRawToPercentage(contrastCurrent, contrastMax); - contrastInfo = $"{contrastPercentage}%"; + contrastInfo = $"[green]{contrastPercentage}%[/]"; } else { @@ -206,7 +209,7 @@ private static void OutputTableList(List monitors, bool verboseOutput) } catch { - status = "[red]Error[/]"; + status = "[red]X[/] [bold red]Error[/]"; if (verboseOutput) { brightnessInfo = "[dim]N/A[/]"; @@ -216,9 +219,9 @@ private static void OutputTableList(List monitors, bool verboseOutput) var row = new List { - monitor.IsPrimary ? $"{monitor.Index} [yellow]*[/]" : monitor.Index.ToString(), + monitor.IsPrimary ? $"[bold cyan]{monitor.Index}[/] [yellow]*[/]" : $"[cyan]{monitor.Index}[/]", monitor.Name, - monitor.DeviceName, + $"[dim]{monitor.DeviceName}[/]", inputInfo }; diff --git a/DDCSwitch/Commands/SetCommand.cs b/DDCSwitch/Commands/SetCommand.cs index bcbaafa..b78515c 100644 --- a/DDCSwitch/Commands/SetCommand.cs +++ b/DDCSwitch/Commands/SetCommand.cs @@ -285,6 +285,7 @@ private static int WriteFeatureValue(Monitor monitor, VcpFeature feature, uint s .Start($"Setting {monitor.Name} {feature.Name} to {displayValue}...", ctx => { ctx.Spinner(Spinner.Known.Dots); + ctx.SpinnerStyle(Style.Parse("cyan")); if (!monitor.TrySetVcpFeature(feature.Code, setValue, out int errorCode)) { @@ -367,21 +368,34 @@ private static void OutputSuccess(Monitor monitor, VcpFeature feature, uint setV } else { + string displayValue; if (feature.Code == InputSource.VcpInputSource) { // Display input with name resolution - ConsoleOutputFormatter.WriteSuccess($"Successfully set {monitor.Name} {feature.Name} to {InputSource.GetName(setValue)}"); + displayValue = $"[cyan]{InputSource.GetName(setValue)}[/]"; } else if (percentageValue.HasValue) { // Display percentage for brightness/contrast - ConsoleOutputFormatter.WriteSuccess($"Successfully set {monitor.Name} {feature.Name} to {percentageValue}%"); + displayValue = $"[green]{percentageValue}%[/]"; } else { // Display raw value for unknown VCP codes - ConsoleOutputFormatter.WriteSuccess($"Successfully set {monitor.Name} {feature.Name} to {setValue}"); + displayValue = $"[green]{setValue}[/]"; } + + var successPanel = new Panel( + $"[bold cyan]Monitor:[/] {monitor.Name}\n" + + $"[bold yellow]Feature:[/] {feature.Name}\n" + + $"[bold green]New Value:[/] {displayValue}") + { + Header = new PanelHeader("[bold green]>> Successfully Applied[/]", Justify.Left), + Border = BoxBorder.Rounded, + BorderStyle = new Style(Color.Green) + }; + + AnsiConsole.Write(successPanel); } } } diff --git a/DDCSwitch/Commands/VcpScanCommand.cs b/DDCSwitch/Commands/VcpScanCommand.cs index 101115e..5560fec 100644 --- a/DDCSwitch/Commands/VcpScanCommand.cs +++ b/DDCSwitch/Commands/VcpScanCommand.cs @@ -36,6 +36,7 @@ public static int ScanSingleMonitor(string monitorIdentifier, bool jsonOutput) .Start("Enumerating monitors...", ctx => { ctx.Spinner(Spinner.Known.Dots); + ctx.SpinnerStyle(Style.Parse("cyan")); monitors = MonitorController.EnumerateMonitors(); }); } @@ -97,6 +98,7 @@ public static int ScanSingleMonitor(string monitorIdentifier, bool jsonOutput) .Start($"Scanning VCP features for {monitor.Name}...", ctx => { ctx.Spinner(Spinner.Known.Dots); + ctx.SpinnerStyle(Style.Parse("cyan")); features = monitor.ScanVcpFeatures(); }); } @@ -186,13 +188,19 @@ private static void OutputTableScanAll(List monitors) { try { - AnsiConsole.MarkupLine($"\n[bold blue]Monitor {monitor.Index}: {monitor.Name}[/] ({monitor.DeviceName})"); + var rule = new Rule($"[bold cyan]Monitor {monitor.Index}: {monitor.Name}[/] [dim]({monitor.DeviceName})[/]") + { + Justification = Justify.Left, + Style = new Style(Color.Cyan) + }; + AnsiConsole.Write(rule); Dictionary features = null!; AnsiConsole.Status() .Start($"Scanning VCP features for {monitor.Name}...", ctx => { ctx.Spinner(Spinner.Known.Dots); + ctx.SpinnerStyle(Style.Parse("cyan")); features = monitor.ScanVcpFeatures(); }); @@ -203,15 +211,19 @@ private static void OutputTableScanAll(List monitors) if (supportedFeatures.Count == 0) { - AnsiConsole.MarkupLine("[yellow] No supported VCP features found[/]"); + ConsoleOutputFormatter.WriteWarning("No supported VCP features found"); + AnsiConsole.WriteLine(); continue; } + AnsiConsole.MarkupLine($"[bold green]>> Found {supportedFeatures.Count} supported features[/]\n"); OutputFeatureTable(supportedFeatures); + AnsiConsole.WriteLine(); } catch (Exception ex) { ConsoleOutputFormatter.WriteError($"Error scanning monitor {monitor.Index} ({monitor.Name}): {ex.Message}"); + AnsiConsole.WriteLine(); } } } @@ -224,11 +236,20 @@ private static void OutputJsonScanSingle(MonitorReference monitorRef, List supportedFeatures) { - AnsiConsole.MarkupLine($"[bold blue]Monitor {monitor.Index}: {monitor.Name}[/] ({monitor.DeviceName})"); + var panel = new Panel( + $"[bold cyan]Monitor:[/] {monitor.Name}\n" + + $"[dim]Device:[/] [dim]{monitor.DeviceName}[/]\n" + + $"[bold yellow]Supported Features:[/] [green]{supportedFeatures.Count}[/]") + { + Header = new PanelHeader($"[bold cyan]>> VCP Feature Scan Results[/]", Justify.Left), + Border = BoxBorder.Rounded, + BorderStyle = new Style(Color.White) + }; + AnsiConsole.Write(panel); if (supportedFeatures.Count == 0) { - AnsiConsole.MarkupLine("[yellow] No supported VCP features found[/]"); + ConsoleOutputFormatter.WriteWarning("No supported VCP features found"); } else { @@ -240,36 +261,36 @@ private static void OutputFeatureTable(List supportedFeatures) { var table = new Table() .Border(TableBorder.Rounded) - .AddColumn("VCP Code") - .AddColumn("Feature Name") - .AddColumn("Access Type") - .AddColumn("Current Value") - .AddColumn("Max Value") - .AddColumn("Percentage"); + .BorderColor(Color.White) + .AddColumn(new TableColumn("[bold yellow]VCP Code[/]").Centered()) + .AddColumn(new TableColumn("[bold yellow]Feature Name[/]").LeftAligned()) + .AddColumn(new TableColumn("[bold yellow]Access[/]").Centered()) + .AddColumn(new TableColumn("[bold yellow]Current[/]").RightAligned()) + .AddColumn(new TableColumn("[bold yellow]Max[/]").RightAligned()); foreach (var feature in supportedFeatures) { - string vcpCode = $"0x{feature.Code:X2}"; + string vcpCode = $"[cyan]0x{feature.Code:X2}[/]"; string accessType = feature.Type switch { - VcpFeatureType.ReadOnly => "[yellow]Read-Only[/]", - VcpFeatureType.WriteOnly => "[red]Write-Only[/]", - VcpFeatureType.ReadWrite => "[green]Read-Write[/]", - _ => "[dim]Unknown[/]" + VcpFeatureType.ReadOnly => "[yellow]Read Only[/]", + VcpFeatureType.WriteOnly => "[red]Write Only[/]", + VcpFeatureType.ReadWrite => "[green]Read+Write[/]", + _ => "[dim]? ?[/]" }; - string currentValue = feature.CurrentValue.ToString(); - string maxValue = feature.MaxValue.ToString(); + string currentValue = $"[green]{feature.CurrentValue}[/]"; + string maxValue = $"[cyan]{feature.MaxValue}[/]"; + + var name = feature.Name; - // Calculate percentage for known percentage-based features - string percentage = "N/A"; - if ((feature.Code == VcpFeature.Brightness.Code || feature.Code == VcpFeature.Contrast.Code) && feature.MaxValue > 0) + // If the feature name isn't known, make it dim + if (feature.Name.StartsWith("VCP_")) { - uint percentageValue = FeatureResolver.ConvertRawToPercentage(feature.CurrentValue, feature.MaxValue); - percentage = $"{percentageValue}%"; + name = $"[dim]{feature.Name}[/]"; } - table.AddRow(vcpCode, feature.Name, accessType, currentValue, maxValue, percentage); + table.AddRow(vcpCode, name, accessType, currentValue, maxValue); } AnsiConsole.Write(table); diff --git a/DDCSwitch/Properties/launchSettings.json b/DDCSwitch/Properties/launchSettings.json index 7d604ca..a689a4b 100644 --- a/DDCSwitch/Properties/launchSettings.json +++ b/DDCSwitch/Properties/launchSettings.json @@ -23,6 +23,10 @@ "DDCSwitch (help)": { "commandName": "Project", "commandLineArgs": "help" + }, + "DDCSwitch (error command)": { + "commandName": "Project", + "commandLineArgs": "gettt" } } } diff --git a/EXAMPLES.md b/EXAMPLES.md index 24ae3ec..4314941 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -228,11 +228,19 @@ This shows current brightness and contrast levels for each monitor (displays "N/ # Get all VCP features for primary monitor (scans all supported features) DDCSwitch get 0 -# Get specific features +# Get all features by monitor name (partial matching supported) +DDCSwitch get "VG270U P" +DDCSwitch get "Generic PnP" + +# Get specific features by index DDCSwitch get 0 input # Current input source DDCSwitch get 0 brightness # Current brightness DDCSwitch get 0 contrast # Current contrast +# Get specific features by monitor name +DDCSwitch get "Generic PnP" input +DDCSwitch get "LG" brightness + # Get raw VCP value DDCSwitch get 0 0x10 # Brightness (raw) ``` @@ -355,8 +363,14 @@ Write-Host "Work profile activated!" -ForegroundColor Green ### Discover VCP Features ```powershell -# Use verbose listing to see all supported VCP features -DDCSwitch list --verbose +# Scan all VCP features for all monitors +DDCSwitch list --all + +# Scan all VCP features for a specific monitor +DDCSwitch get 0 + +# Scan by monitor name +DDCSwitch get "VG270U P" ``` ### Common VCP Codes @@ -1527,6 +1541,12 @@ Write-Host "Testing complete! Document which codes worked for your monitor." -Fo # Get current input (if this works, DDC/CI is functional) DDCSwitch get 0 +# Get by monitor name +DDCSwitch get "VG270U" + +# List all monitors with all VCP values +DDCSwitch list --all + # Try setting to current input (should succeed instantly) DDCSwitch list # Note the current input DDCSwitch set 0 @@ -1556,6 +1576,10 @@ DDCSwitch set "LG ULTRAGEAR" HDMI1 # Partial name matching works DDCSwitch set "ULTRAGEAR" HDMI1 + +# Get settings by monitor name +DDCSwitch get "VG270U" brightness +DDCSwitch get "Generic PnP" # Gets all VCP values for this monitor ``` ## Integration with Other Tools diff --git a/README.md b/README.md index 6cd32e7..8d4590b 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,14 @@ DDCSwitch get 0 This will scan and display all supported VCP features for monitor 0, showing their names, access types, current values, and maximum values. +You can also use the monitor name instead of the index (partial name matching supported): + +```powershell +# Get all settings by monitor name +DDCSwitch get "VG270U P" +DDCSwitch get "Generic PnP" +``` + Get a specific feature: ```powershell @@ -110,6 +118,10 @@ DDCSwitch get 0 brightness # Get contrast as percentage DDCSwitch get 0 contrast + +# Works with monitor names too +DDCSwitch get "VG270U P" brightness +DDCSwitch get "Generic PnP" input ``` Output: `Monitor: Generic PnP Monitor` / `Brightness: 75% (120/160)` @@ -152,13 +164,23 @@ DDCSwitch set 0 0x10 120 ### VCP Feature Scanning -Discover all supported VCP features on a monitor: +Discover all supported VCP features on all monitors: ```powershell -DDCSwitch list --verbose +DDCSwitch list --all ``` -This scans all VCP codes (0x00-0xFF) and displays supported features with their current values, maximum values, and access types (read-only, write-only, read-write). +This scans all VCP codes (0x00-0xFF) for every monitor and displays supported features with their current values, maximum values, and access types (read-only, write-only, read-write). + +To scan a specific monitor, use the `get` command: + +```powershell +# Scan specific monitor by index +DDCSwitch get 0 + +# Scan specific monitor by name +DDCSwitch get "VG270U" +``` ### VCP Feature Categories and Discovery @@ -256,6 +278,9 @@ DDCSwitch list --category color # Search for features by name DDCSwitch get 0 bright # Matches "brightness" + +# Or by monitor name +DDCSwitch get "VG270U" bright ``` **Desktop shortcut:** From b66f1b2185e25ef49c83dbbfaa65d30990cd7327 Mon Sep 17 00:00:00 2001 From: Quick <577652+markdwags@users.noreply.github.com> Date: Thu, 8 Jan 2026 12:30:14 -0600 Subject: [PATCH 07/10] refactor command handling; replace --all flag with 'get all' command for improved clarity and usability, update documentation and examples accordingly --- DDCSwitch/Commands/CommandRouter.cs | 6 +-- DDCSwitch/Commands/GetCommand.cs | 61 +++++++++++++++++++++++++++++ DDCSwitch/Commands/HelpCommand.cs | 10 ++--- DDCSwitch/Commands/ListCommand.cs | 10 +---- EXAMPLES.md | 4 +- README.md | 4 +- 6 files changed, 74 insertions(+), 21 deletions(-) diff --git a/DDCSwitch/Commands/CommandRouter.cs b/DDCSwitch/Commands/CommandRouter.cs index a77f613..ff61316 100644 --- a/DDCSwitch/Commands/CommandRouter.cs +++ b/DDCSwitch/Commands/CommandRouter.cs @@ -20,9 +20,7 @@ public static int Route(string[] args) bool verboseOutput = filteredArgs.Contains("--verbose", StringComparer.OrdinalIgnoreCase); filteredArgs = filteredArgs.Where(a => !a.Equals("--verbose", StringComparison.OrdinalIgnoreCase)).ToArray(); - // Check for --scan flag - bool scanOutput = filteredArgs.Contains("--all", StringComparer.OrdinalIgnoreCase); - filteredArgs = filteredArgs.Where(a => !a.Equals("--alll", StringComparison.OrdinalIgnoreCase)).ToArray(); + if (filteredArgs.Length == 0) { @@ -36,7 +34,7 @@ public static int Route(string[] args) { return command switch { - "list" or "ls" => ListCommand.Execute(jsonOutput, verboseOutput, scanOutput), + "list" or "ls" => ListCommand.Execute(jsonOutput, verboseOutput), "get" => GetCommand.Execute(filteredArgs, jsonOutput), "set" => SetCommand.Execute(filteredArgs, jsonOutput), "version" or "-v" or "--version" => HelpCommand.ShowVersion(jsonOutput), diff --git a/DDCSwitch/Commands/GetCommand.cs b/DDCSwitch/Commands/GetCommand.cs index 4a65a45..ff173eb 100644 --- a/DDCSwitch/Commands/GetCommand.cs +++ b/DDCSwitch/Commands/GetCommand.cs @@ -18,11 +18,34 @@ public static int Execute(string[] args, bool jsonOutput) { ConsoleOutputFormatter.WriteError("Monitor identifier required."); AnsiConsole.WriteLine("Usage: DDCSwitch get [feature]"); + AnsiConsole.WriteLine(" DDCSwitch get all"); } return 1; } + // Check if the monitor identifier is "all" + if (args[1].Equals("all", StringComparison.OrdinalIgnoreCase)) + { + // "get all" should only scan all monitors, no specific feature + if (args.Length > 2) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, "Feature specification not supported with 'get all'"); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError("Feature specification not supported with 'get all'."); + AnsiConsole.WriteLine("Usage: DDCSwitch get all"); + } + return 1; + } + + return GetAllMonitors(jsonOutput); + } + // If no feature is specified, perform VCP scan if (args.Length == 2) { @@ -61,6 +84,44 @@ public static int Execute(string[] args, bool jsonOutput) return result; } + private static int GetAllMonitors(bool jsonOutput) + { + List monitors; + + if (!jsonOutput) + { + monitors = null!; + AnsiConsole.Status() + .Start("Enumerating monitors...", ctx => + { + ctx.Spinner(Spinner.Known.Dots); + ctx.SpinnerStyle(Style.Parse("cyan")); + monitors = MonitorController.EnumerateMonitors(); + }); + } + else + { + monitors = MonitorController.EnumerateMonitors(); + } + + if (monitors.Count == 0) + { + if (jsonOutput) + { + var error = new ErrorResponse(false, "No DDC/CI capable monitors found"); + Console.WriteLine(JsonSerializer.Serialize(error, JsonContext.Default.ErrorResponse)); + } + else + { + ConsoleOutputFormatter.WriteError("No DDC/CI capable monitors found."); + } + + return 1; + } + + return VcpScanCommand.ScanAllMonitors(monitors, jsonOutput); + } + private static int HandleInvalidFeature(string featureInput, bool jsonOutput) { string errorMessage; diff --git a/DDCSwitch/Commands/HelpCommand.cs b/DDCSwitch/Commands/HelpCommand.cs index 366deae..9c35f55 100644 --- a/DDCSwitch/Commands/HelpCommand.cs +++ b/DDCSwitch/Commands/HelpCommand.cs @@ -34,7 +34,7 @@ public static int ShowUsage() var version = GetVersion(); AnsiConsole.Write(new FigletText("DDCSwitch").Color(Color.Cyan1)); - AnsiConsole.MarkupLine($"[bold white]{version}[/]"); + AnsiConsole.MarkupLine($"[bold white]v{version}[/]"); AnsiConsole.MarkupLine($"[dim italic]>> A Windows command-line utility to control monitors using DDC/CI[/]\n"); var commandsTable = new Table() @@ -50,8 +50,8 @@ public static int ShowUsage() "[cyan]list --verbose[/]", "Include brightness and contrast information"); commandsTable.AddRow( - "[cyan]list --scan[/]", - "Enumerate all supported VCP features for each monitor"); + "[cyan]get all[/]", + "Enumerate all supported VCP features for all monitors"); commandsTable.AddRow( "[cyan]get[/] [green][/] [blue][[feature]][/]", "Get current value for a monitor feature or get all features"); @@ -73,8 +73,7 @@ public static int ShowUsage() "[bold yellow]Features:[/] brightness, contrast, input, or VCP codes like [cyan]0x10[/]\n" + "[bold yellow]Flags:[/]\n" + " [cyan]--json[/] Machine-readable JSON output for automation\n" + - " [cyan]--verbose[/] Include detailed information in list command\n" + - " [cyan]--all[/] Enumerate all VCP features in list command") + " [cyan]--verbose[/] Include detailed information in list command\n\n") { Header = new PanelHeader("[bold green]>> Quick Reference[/]", Justify.Left), Border = BoxBorder.Rounded, @@ -86,6 +85,7 @@ public static int ShowUsage() AnsiConsole.WriteLine(); AnsiConsole.MarkupLine("[dim]Examples:[/]"); AnsiConsole.MarkupLine(" [grey]$[/] DDCSwitch list"); + AnsiConsole.MarkupLine(" [grey]$[/] DDCSwitch get all"); AnsiConsole.MarkupLine(" [grey]$[/] DDCSwitch get 0"); AnsiConsole.MarkupLine(" [grey]$[/] DDCSwitch get 0 brightness"); AnsiConsole.MarkupLine(" [grey]$[/] DDCSwitch set 0 input HDMI1"); diff --git a/DDCSwitch/Commands/ListCommand.cs b/DDCSwitch/Commands/ListCommand.cs index 64f7114..87cb204 100644 --- a/DDCSwitch/Commands/ListCommand.cs +++ b/DDCSwitch/Commands/ListCommand.cs @@ -5,7 +5,7 @@ namespace DDCSwitch.Commands; internal static class ListCommand { - public static int Execute(bool jsonOutput, bool verboseOutput, bool scanOutput) + public static int Execute(bool jsonOutput, bool verboseOutput) { List monitors; @@ -13,7 +13,7 @@ public static int Execute(bool jsonOutput, bool verboseOutput, bool scanOutput) { monitors = null!; AnsiConsole.Status() - .Start(scanOutput ? "Scanning VCP features..." : "Enumerating monitors...", ctx => + .Start("Enumerating monitors...", ctx => { ctx.Spinner(Spinner.Known.Dots); ctx.SpinnerStyle(Style.Parse("cyan")); @@ -40,12 +40,6 @@ public static int Execute(bool jsonOutput, bool verboseOutput, bool scanOutput) return 1; } - // If scan mode is enabled, perform VCP scanning for each monitor - if (scanOutput) - { - return VcpScanCommand.ScanAllMonitors(monitors, jsonOutput); - } - if (jsonOutput) { OutputJsonList(monitors, verboseOutput); diff --git a/EXAMPLES.md b/EXAMPLES.md index 4314941..11c6f03 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -364,7 +364,7 @@ Write-Host "Work profile activated!" -ForegroundColor Green ```powershell # Scan all VCP features for all monitors -DDCSwitch list --all +DDCSwitch get all # Scan all VCP features for a specific monitor DDCSwitch get 0 @@ -1545,7 +1545,7 @@ DDCSwitch get 0 DDCSwitch get "VG270U" # List all monitors with all VCP values -DDCSwitch list --all +DDCSwitch get all # Try setting to current input (should succeed instantly) DDCSwitch list # Note the current input diff --git a/README.md b/README.md index 8d4590b..5ccfedd 100644 --- a/README.md +++ b/README.md @@ -167,12 +167,12 @@ DDCSwitch set 0 0x10 120 Discover all supported VCP features on all monitors: ```powershell -DDCSwitch list --all +DDCSwitch get all ``` This scans all VCP codes (0x00-0xFF) for every monitor and displays supported features with their current values, maximum values, and access types (read-only, write-only, read-write). -To scan a specific monitor, use the `get` command: +To scan a specific monitor: ```powershell # Scan specific monitor by index From 68c06a8877b0d6da740174e83ead8a364336c453 Mon Sep 17 00:00:00 2001 From: Quick <577652+markdwags@users.noreply.github.com> Date: Thu, 8 Jan 2026 12:45:57 -0600 Subject: [PATCH 08/10] normalize casing for 'ddcswitch' throughout the codebase; update usage examples and error messages for consistency --- CHANGELOG.md | 3 +- DDCSwitch/Commands/GetCommand.cs | 8 +- DDCSwitch/Commands/HelpCommand.cs | 22 +- DDCSwitch/Commands/SetCommand.cs | 4 +- DDCSwitch/Commands/VcpScanCommand.cs | 2 +- DDCSwitch/DDCSwitch.csproj | 3 +- DDCSwitch/Properties/launchSettings.json | 14 +- DDCSwitch/VcpErrorHandler.cs | 8 +- EXAMPLES.md | 666 +++++++++++------------ README.md | 98 ++-- build.cmd | 8 +- 11 files changed, 419 insertions(+), 417 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60827e3..392330f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -All notable changes to DDCSwitch will be documented in this file. +All notable changes to ddcswitch will be documented in this file. ## [1.0.2] - 2026-01-07 @@ -9,6 +9,7 @@ All notable changes to DDCSwitch will be documented in this file. - Improved error handling for unsupported monitors - Enhanced documentation with additional examples - Optimized performance for input switching operations +- Better clarity in CLI output messages ## [1.0.1] - 2026-01-07 diff --git a/DDCSwitch/Commands/GetCommand.cs b/DDCSwitch/Commands/GetCommand.cs index ff173eb..7c6914d 100644 --- a/DDCSwitch/Commands/GetCommand.cs +++ b/DDCSwitch/Commands/GetCommand.cs @@ -17,8 +17,8 @@ public static int Execute(string[] args, bool jsonOutput) else { ConsoleOutputFormatter.WriteError("Monitor identifier required."); - AnsiConsole.WriteLine("Usage: DDCSwitch get [feature]"); - AnsiConsole.WriteLine(" DDCSwitch get all"); + AnsiConsole.WriteLine("Usage: ddcswitch get [feature]"); + AnsiConsole.WriteLine(" ddcswitch get all"); } return 1; @@ -38,7 +38,7 @@ public static int Execute(string[] args, bool jsonOutput) else { ConsoleOutputFormatter.WriteError("Feature specification not supported with 'get all'."); - AnsiConsole.WriteLine("Usage: DDCSwitch get all"); + AnsiConsole.WriteLine("Usage: ddcswitch get all"); } return 1; } @@ -177,7 +177,7 @@ private static int HandleMonitorNotFound(List monitors, string monitorI else { ConsoleOutputFormatter.WriteError($"Monitor '{monitorId}' not found."); - AnsiConsole.MarkupLine("Use [yellow]DDCSwitch list[/] to see available monitors."); + AnsiConsole.MarkupLine("Use [yellow]ddcswitch list[/] to see available monitors."); } // Cleanup diff --git a/DDCSwitch/Commands/HelpCommand.cs b/DDCSwitch/Commands/HelpCommand.cs index 9c35f55..54895b0 100644 --- a/DDCSwitch/Commands/HelpCommand.cs +++ b/DDCSwitch/Commands/HelpCommand.cs @@ -21,8 +21,8 @@ public static int ShowVersion(bool jsonOutput) } else { - AnsiConsole.Write(new FigletText("DDCSwitch").Color(Color.Cyan1)); - AnsiConsole.MarkupLine($"[bold cyan]Version:[/] [green]{version}[/]"); + AnsiConsole.Write(new FigletText("ddcswitch").Color(Color.Cyan1)); + AnsiConsole.MarkupLine($"[bold white] v{version}[/]"); AnsiConsole.MarkupLine("[dim italic]>> Windows DDC/CI Monitor Input Switcher[/]"); } @@ -33,7 +33,7 @@ public static int ShowUsage() { var version = GetVersion(); - AnsiConsole.Write(new FigletText("DDCSwitch").Color(Color.Cyan1)); + AnsiConsole.Write(new FigletText("ddcswitch").Color(Color.Cyan1)); AnsiConsole.MarkupLine($"[bold white]v{version}[/]"); AnsiConsole.MarkupLine($"[dim italic]>> A Windows command-line utility to control monitors using DDC/CI[/]\n"); @@ -73,23 +73,23 @@ public static int ShowUsage() "[bold yellow]Features:[/] brightness, contrast, input, or VCP codes like [cyan]0x10[/]\n" + "[bold yellow]Flags:[/]\n" + " [cyan]--json[/] Machine-readable JSON output for automation\n" + - " [cyan]--verbose[/] Include detailed information in list command\n\n") + " [cyan]--verbose[/] Include detailed information in list command") { Header = new PanelHeader("[bold green]>> Quick Reference[/]", Justify.Left), Border = BoxBorder.Rounded, - BorderStyle = new Style(Color.Green) + BorderStyle = new Style(Color.White) }; AnsiConsole.Write(panel); AnsiConsole.WriteLine(); AnsiConsole.MarkupLine("[dim]Examples:[/]"); - AnsiConsole.MarkupLine(" [grey]$[/] DDCSwitch list"); - AnsiConsole.MarkupLine(" [grey]$[/] DDCSwitch get all"); - AnsiConsole.MarkupLine(" [grey]$[/] DDCSwitch get 0"); - AnsiConsole.MarkupLine(" [grey]$[/] DDCSwitch get 0 brightness"); - AnsiConsole.MarkupLine(" [grey]$[/] DDCSwitch set 0 input HDMI1"); - AnsiConsole.MarkupLine(" [grey]$[/] DDCSwitch set 1 brightness 75%"); + AnsiConsole.MarkupLine(" [grey]$[/] ddcswitch list"); + AnsiConsole.MarkupLine(" [grey]$[/] ddcswitch get all"); + AnsiConsole.MarkupLine(" [grey]$[/] ddcswitch get 0"); + AnsiConsole.MarkupLine(" [grey]$[/] ddcswitch get 0 brightness"); + AnsiConsole.MarkupLine(" [grey]$[/] ddcswitch set 0 input HDMI1"); + AnsiConsole.MarkupLine(" [grey]$[/] ddcswitch set 1 brightness 75%"); return 0; } diff --git a/DDCSwitch/Commands/SetCommand.cs b/DDCSwitch/Commands/SetCommand.cs index b78515c..ce4b6c2 100644 --- a/DDCSwitch/Commands/SetCommand.cs +++ b/DDCSwitch/Commands/SetCommand.cs @@ -18,7 +18,7 @@ public static int Execute(string[] args, bool jsonOutput) else { ConsoleOutputFormatter.WriteError("Monitor, feature, and value required."); - AnsiConsole.MarkupLine("Usage: [yellow]DDCSwitch set [/]"); + AnsiConsole.MarkupLine("Usage: [yellow]ddcswitch set [/]"); } return 1; @@ -183,7 +183,7 @@ private static int HandleMonitorNotFound(List monitors, string monitorI else { ConsoleOutputFormatter.WriteError($"Monitor '{monitorId}' not found."); - AnsiConsole.MarkupLine("Use [yellow]DDCSwitch list[/] to see available monitors."); + AnsiConsole.MarkupLine("Use [yellow]ddcswitch list[/] to see available monitors."); } // Cleanup diff --git a/DDCSwitch/Commands/VcpScanCommand.cs b/DDCSwitch/Commands/VcpScanCommand.cs index 5560fec..971465e 100644 --- a/DDCSwitch/Commands/VcpScanCommand.cs +++ b/DDCSwitch/Commands/VcpScanCommand.cs @@ -72,7 +72,7 @@ public static int ScanSingleMonitor(string monitorIdentifier, bool jsonOutput) else { ConsoleOutputFormatter.WriteError($"Monitor '{monitorIdentifier}' not found."); - AnsiConsole.MarkupLine("Use [yellow]DDCSwitch list[/] to see available monitors."); + AnsiConsole.MarkupLine("Use [yellow]ddcswitch list[/] to see available monitors."); } // Cleanup diff --git a/DDCSwitch/DDCSwitch.csproj b/DDCSwitch/DDCSwitch.csproj index 2b27bce..c9d1921 100644 --- a/DDCSwitch/DDCSwitch.csproj +++ b/DDCSwitch/DDCSwitch.csproj @@ -11,6 +11,7 @@ Speed false true + ddcswitch @@ -33,7 +34,7 @@ $(Version) $(Version) - + diff --git a/DDCSwitch/Properties/launchSettings.json b/DDCSwitch/Properties/launchSettings.json index a689a4b..6e77e58 100644 --- a/DDCSwitch/Properties/launchSettings.json +++ b/DDCSwitch/Properties/launchSettings.json @@ -1,30 +1,30 @@ { "profiles": { - "DDCSwitch (get 0)": { + "ddcswitch (get 0)": { "commandName": "Project", "commandLineArgs": "get 0" }, - "DDCSwitch (list)": { + "ddcswitch (list)": { "commandName": "Project", "commandLineArgs": "list" }, - "DDCSwitch (list --verbose)": { + "ddcswitch (list --verbose)": { "commandName": "Project", "commandLineArgs": "list --verbose" }, - "DDCSwitch (set)": { + "ddcswitch (set)": { "commandName": "Project", "commandLineArgs": "set 0 input HDMI1" }, - "DDCSwitch (version)": { + "ddcswitch (version)": { "commandName": "Project", "commandLineArgs": "version" }, - "DDCSwitch (help)": { + "ddcswitch (help)": { "commandName": "Project", "commandLineArgs": "help" }, - "DDCSwitch (error command)": { + "ddcswitch (error command)": { "commandName": "Project", "commandLineArgs": "gettt" } diff --git a/DDCSwitch/VcpErrorHandler.cs b/DDCSwitch/VcpErrorHandler.cs index aac96a6..c210331 100644 --- a/DDCSwitch/VcpErrorHandler.cs +++ b/DDCSwitch/VcpErrorHandler.cs @@ -156,7 +156,7 @@ private static string GetReadFailureSuggestions(VcpFeature feature) else { suggestions.Add($"VCP code 0x{feature.Code:X2} may not be supported by this monitor"); - suggestions.Add("Use 'DDCSwitch list --scan' to see all supported VCP codes"); + suggestions.Add("Use 'ddcswitch get ' to see all supported VCP codes"); } suggestions.Add("Check that the monitor is properly connected and powered on"); @@ -184,7 +184,7 @@ private static string GetWriteFailureSuggestions(VcpFeature feature, uint attemp else { suggestions.Add($"VCP code 0x{feature.Code:X2} may not support write operations on this monitor"); - suggestions.Add("Use 'DDCSwitch list --scan' to check if this VCP code supports write operations"); + suggestions.Add("Use 'ddcswitch get ' to see all supported VCP codes"); } suggestions.Add("Try running as administrator if permission issues persist"); @@ -202,7 +202,7 @@ private static string GetFeatureAlternatives(VcpFeature feature) 0x10 => "Try using your monitor's physical buttons or on-screen display (OSD) to adjust brightness", 0x12 => "Try using your monitor's physical buttons or on-screen display (OSD) to adjust contrast", 0x60 => "Try using your monitor's physical input selection button or check if the monitor supports other input switching methods", - _ => "Use 'DDCSwitch list --scan' to see all supported VCP codes for this monitor" + _ => "Use 'ddcswitch get ' to see all supported VCP codes" }; } @@ -253,7 +253,7 @@ private static string GetRangeValidationSuggestion(VcpFeature feature, uint maxV return feature.Code switch { 0x60 => "For input sources, use names like 'HDMI1', 'DP1', or hex codes like '0x11'", - _ => $"Use 'DDCSwitch get {feature.Name}' to see the current value and valid range" + _ => $"Use 'ddcswitch get {feature.Name}' to see the current value and valid range" }; } } \ No newline at end of file diff --git a/EXAMPLES.md b/EXAMPLES.md index 11c6f03..d013532 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -1,17 +1,17 @@ -# DDCSwitch Examples +# ddcswitch Examples -This document contains detailed examples and use cases for DDCSwitch, including input switching, brightness/contrast control, comprehensive VCP feature access, and automation. +This document contains detailed examples and use cases for ddcswitch, including input switching, brightness/contrast control, comprehensive VCP feature access, and automation. ## Comprehensive VCP Feature Examples -DDCSwitch now supports all MCCS (Monitor Control Command Set) standardized VCP features, organized by categories for easy discovery. +ddcswitch now supports all MCCS (Monitor Control Command Set) standardized VCP features, organized by categories for easy discovery. ### VCP Feature Categories List all available feature categories: ```powershell -DDCSwitch list --categories +ddcswitch list --categories ``` Output: @@ -29,13 +29,13 @@ Available VCP Feature Categories: ```powershell # Image adjustment features -DDCSwitch list --category image +ddcswitch list --category image # Color control features -DDCSwitch list --category color +ddcswitch list --category color # Audio features -DDCSwitch list --category audio +ddcswitch list --category audio ``` ### Color Control Examples @@ -44,26 +44,26 @@ Control RGB gains for color calibration: ```powershell # Set individual RGB gains (percentage values) -DDCSwitch set 0 red-gain 95% -DDCSwitch set 0 green-gain 90% -DDCSwitch set 0 blue-gain 85% +ddcswitch set 0 red-gain 95% +ddcswitch set 0 green-gain 90% +ddcswitch set 0 blue-gain 85% # Get current RGB values -DDCSwitch get 0 red-gain -DDCSwitch get 0 green-gain -DDCSwitch get 0 blue-gain +ddcswitch get 0 red-gain +ddcswitch get 0 green-gain +ddcswitch get 0 blue-gain # Color temperature control (if supported) -DDCSwitch set 0 color-temp-request 6500 -DDCSwitch get 0 color-temp-request +ddcswitch set 0 color-temp-request 6500 +ddcswitch get 0 color-temp-request # Gamma control -DDCSwitch set 0 gamma 2.2 -DDCSwitch get 0 gamma +ddcswitch set 0 gamma 2.2 +ddcswitch get 0 gamma # Hue and saturation -DDCSwitch set 0 hue 50% -DDCSwitch set 0 saturation 80% +ddcswitch set 0 hue 50% +ddcswitch set 0 saturation 80% ``` ### Audio Control Examples @@ -72,19 +72,19 @@ Control monitor speakers (if supported): ```powershell # Volume control (percentage) -DDCSwitch set 0 volume 75% -DDCSwitch get 0 volume +ddcswitch set 0 volume 75% +ddcswitch get 0 volume # Mute/unmute -DDCSwitch set 0 mute 1 # Mute -DDCSwitch set 0 mute 0 # Unmute +ddcswitch set 0 mute 1 # Mute +ddcswitch set 0 mute 0 # Unmute # Audio balance (if supported) -DDCSwitch set 0 audio-balance 50% # Centered +ddcswitch set 0 audio-balance 50% # Centered # Treble and bass (if supported) -DDCSwitch set 0 audio-treble 60% -DDCSwitch set 0 audio-bass 70% +ddcswitch set 0 audio-treble 60% +ddcswitch set 0 audio-bass 70% ``` ### Advanced Image Controls @@ -93,40 +93,40 @@ Beyond basic brightness and contrast: ```powershell # Sharpness control -DDCSwitch set 0 sharpness 75% -DDCSwitch get 0 sharpness +ddcswitch set 0 sharpness 75% +ddcswitch get 0 sharpness # Backlight control (LED monitors) -DDCSwitch set 0 backlight 80% -DDCSwitch get 0 backlight +ddcswitch set 0 backlight 80% +ddcswitch get 0 backlight # Image orientation (if supported) -DDCSwitch set 0 image-orientation 0 # Normal -DDCSwitch set 0 image-orientation 1 # 90° rotation +ddcswitch set 0 image-orientation 0 # Normal +ddcswitch set 0 image-orientation 1 # 90 rotation # Image mode presets (if supported) -DDCSwitch set 0 image-mode 1 # Standard -DDCSwitch set 0 image-mode 2 # Movie -DDCSwitch set 0 image-mode 3 # Game +ddcswitch set 0 image-mode 1 # Standard +ddcswitch set 0 image-mode 2 # Movie +ddcswitch set 0 image-mode 3 # Game ``` ### Factory Reset and Calibration ```powershell # Restore all factory defaults -DDCSwitch set 0 restore-defaults 1 +ddcswitch set 0 restore-defaults 1 # Restore specific defaults -DDCSwitch set 0 restore-brightness-contrast 1 -DDCSwitch set 0 restore-color 1 -DDCSwitch set 0 restore-geometry 1 +ddcswitch set 0 restore-brightness-contrast 1 +ddcswitch set 0 restore-color 1 +ddcswitch set 0 restore-geometry 1 # Degauss (CRT monitors) -DDCSwitch set 0 degauss 1 +ddcswitch set 0 degauss 1 # Auto calibration features (if supported) -DDCSwitch set 0 auto-color-setup 1 -DDCSwitch set 0 auto-size-center 1 +ddcswitch set 0 auto-color-setup 1 +ddcswitch set 0 auto-size-center 1 ``` ### Complete Monitor Profile Examples @@ -138,20 +138,20 @@ Create comprehensive monitor profiles using all available features: Write-Host "Activating Advanced Gaming Profile..." -ForegroundColor Cyan # Input and basic settings -DDCSwitch set 0 input HDMI1 -DDCSwitch set 0 brightness 90% -DDCSwitch set 0 contrast 85% +ddcswitch set 0 input HDMI1 +ddcswitch set 0 brightness 90% +ddcswitch set 0 contrast 85% # Color optimization for gaming -DDCSwitch set 0 red-gain 100% -DDCSwitch set 0 green-gain 95% -DDCSwitch set 0 blue-gain 90% -DDCSwitch set 0 saturation 110% # Enhanced colors -DDCSwitch set 0 sharpness 80% # Crisp details +ddcswitch set 0 red-gain 100% +ddcswitch set 0 green-gain 95% +ddcswitch set 0 blue-gain 90% +ddcswitch set 0 saturation 110% # Enhanced colors +ddcswitch set 0 sharpness 80% # Crisp details # Audio settings -DDCSwitch set 0 volume 60% -DDCSwitch set 0 mute 0 +ddcswitch set 0 volume 60% +ddcswitch set 0 mute 0 Write-Host "Advanced Gaming Profile Activated!" -ForegroundColor Green ``` @@ -161,20 +161,20 @@ Write-Host "Advanced Gaming Profile Activated!" -ForegroundColor Green Write-Host "Activating Advanced Work Profile..." -ForegroundColor Cyan # Input and basic settings -DDCSwitch set 0 input DP1 -DDCSwitch set 0 brightness 60% -DDCSwitch set 0 contrast 75% +ddcswitch set 0 input DP1 +ddcswitch set 0 brightness 60% +ddcswitch set 0 contrast 75% # Color optimization for text work -DDCSwitch set 0 red-gain 85% -DDCSwitch set 0 green-gain 90% -DDCSwitch set 0 blue-gain 95% -DDCSwitch set 0 saturation 70% # Reduced saturation for comfort -DDCSwitch set 0 sharpness 60% # Softer for long reading +ddcswitch set 0 red-gain 85% +ddcswitch set 0 green-gain 90% +ddcswitch set 0 blue-gain 95% +ddcswitch set 0 saturation 70% # Reduced saturation for comfort +ddcswitch set 0 sharpness 60% # Softer for long reading # Audio settings -DDCSwitch set 0 volume 40% # Lower for office environment -DDCSwitch set 0 mute 0 +ddcswitch set 0 volume 40% # Lower for office environment +ddcswitch set 0 mute 0 Write-Host "Advanced Work Profile Activated!" -ForegroundColor Green ``` @@ -184,20 +184,20 @@ Write-Host "Advanced Work Profile Activated!" -ForegroundColor Green Write-Host "Activating Photo Editing Profile..." -ForegroundColor Cyan # Input and basic settings -DDCSwitch set 0 input DP1 -DDCSwitch set 0 brightness 70% -DDCSwitch set 0 contrast 80% +ddcswitch set 0 input DP1 +ddcswitch set 0 brightness 70% +ddcswitch set 0 contrast 80% # Accurate color reproduction -DDCSwitch set 0 red-gain 90% -DDCSwitch set 0 green-gain 90% -DDCSwitch set 0 blue-gain 90% -DDCSwitch set 0 saturation 100% # Natural saturation -DDCSwitch set 0 gamma 2.2 # Standard gamma -DDCSwitch set 0 color-temp-request 6500 # D65 standard +ddcswitch set 0 red-gain 90% +ddcswitch set 0 green-gain 90% +ddcswitch set 0 blue-gain 90% +ddcswitch set 0 saturation 100% # Natural saturation +ddcswitch set 0 gamma 2.2 # Standard gamma +ddcswitch set 0 color-temp-request 6500 # D65 standard # Disable audio to avoid distractions -DDCSwitch set 0 mute 1 +ddcswitch set 0 mute 1 Write-Host "Photo Editing Profile Activated!" -ForegroundColor Green ``` @@ -207,7 +207,7 @@ Write-Host "Photo Editing Profile Activated!" -ForegroundColor Green ### Check What Monitors Support DDC/CI ```powershell -DDCSwitch list +ddcswitch list ``` This will show all your monitors and indicate which ones support DDC/CI control. Monitors with "OK" status can be controlled. @@ -217,7 +217,7 @@ This will show all your monitors and indicate which ones support DDC/CI control. Get detailed information including brightness and contrast: ```powershell -DDCSwitch list --verbose +ddcswitch list --verbose ``` This shows current brightness and contrast levels for each monitor (displays "N/A" for unsupported features). @@ -226,39 +226,39 @@ This shows current brightness and contrast levels for each monitor (displays "N/ ```powershell # Get all VCP features for primary monitor (scans all supported features) -DDCSwitch get 0 +ddcswitch get 0 # Get all features by monitor name (partial matching supported) -DDCSwitch get "VG270U P" -DDCSwitch get "Generic PnP" +ddcswitch get "VG270U P" +ddcswitch get "Generic PnP" # Get specific features by index -DDCSwitch get 0 input # Current input source -DDCSwitch get 0 brightness # Current brightness -DDCSwitch get 0 contrast # Current contrast +ddcswitch get 0 input # Current input source +ddcswitch get 0 brightness # Current brightness +ddcswitch get 0 contrast # Current contrast # Get specific features by monitor name -DDCSwitch get "Generic PnP" input -DDCSwitch get "LG" brightness +ddcswitch get "Generic PnP" input +ddcswitch get "LG" brightness # Get raw VCP value -DDCSwitch get 0 0x10 # Brightness (raw) +ddcswitch get 0 0x10 # Brightness (raw) ``` ### Set Monitor Settings ```powershell # Switch input -DDCSwitch set 0 HDMI1 +ddcswitch set 0 HDMI1 # Set brightness to 75% -DDCSwitch set 0 brightness 75% +ddcswitch set 0 brightness 75% # Set contrast to 80% -DDCSwitch set 0 contrast 80% +ddcswitch set 0 contrast 80% # Set raw VCP value -DDCSwitch set 0 0x10 120 # Brightness (raw value) +ddcswitch set 0 0x10 120 # Brightness (raw value) ``` ## Brightness and Contrast Control @@ -267,12 +267,12 @@ DDCSwitch set 0 0x10 120 # Brightness (raw value) ```powershell # Set brightness to specific percentage -DDCSwitch set 0 brightness 50% -DDCSwitch set 0 brightness 75% -DDCSwitch set 0 brightness 100% +ddcswitch set 0 brightness 50% +ddcswitch set 0 brightness 75% +ddcswitch set 0 brightness 100% # Get current brightness -DDCSwitch get 0 brightness +ddcswitch get 0 brightness # Output: Monitor: Generic PnP Monitor / Brightness: 75% (120/160) ``` @@ -280,12 +280,12 @@ DDCSwitch get 0 brightness ```powershell # Set contrast to specific percentage -DDCSwitch set 0 contrast 60% -DDCSwitch set 0 contrast 85% -DDCSwitch set 0 contrast 100% +ddcswitch set 0 contrast 60% +ddcswitch set 0 contrast 85% +ddcswitch set 0 contrast 100% # Get current contrast -DDCSwitch get 0 contrast +ddcswitch get 0 contrast # Output: Monitor: Generic PnP Monitor / Contrast: 85% (136/160) ``` @@ -295,19 +295,19 @@ Create quick brightness presets: ```powershell # brightness-low.ps1 -DDCSwitch set 0 brightness 25% +ddcswitch set 0 brightness 25% Write-Host "Brightness set to 25% (Low)" -ForegroundColor Green # brightness-medium.ps1 -DDCSwitch set 0 brightness 50% +ddcswitch set 0 brightness 50% Write-Host "Brightness set to 50% (Medium)" -ForegroundColor Green # brightness-high.ps1 -DDCSwitch set 0 brightness 75% +ddcswitch set 0 brightness 75% Write-Host "Brightness set to 75% (High)" -ForegroundColor Green # brightness-max.ps1 -DDCSwitch set 0 brightness 100% +ddcswitch set 0 brightness 100% Write-Host "Brightness set to 100% (Maximum)" -ForegroundColor Green ``` @@ -321,19 +321,19 @@ $hour = (Get-Date).Hour if ($hour -ge 6 -and $hour -lt 9) { # Morning: Medium brightness - DDCSwitch set 0 brightness 60% + ddcswitch set 0 brightness 60% Write-Host "Morning brightness: 60%" -ForegroundColor Yellow } elseif ($hour -ge 9 -and $hour -lt 18) { # Daytime: High brightness - DDCSwitch set 0 brightness 85% + ddcswitch set 0 brightness 85% Write-Host "Daytime brightness: 85%" -ForegroundColor Green } elseif ($hour -ge 18 -and $hour -lt 22) { # Evening: Medium brightness - DDCSwitch set 0 brightness 50% + ddcswitch set 0 brightness 50% Write-Host "Evening brightness: 50%" -ForegroundColor Orange } else { # Night: Low brightness - DDCSwitch set 0 brightness 25% + ddcswitch set 0 brightness 25% Write-Host "Night brightness: 25%" -ForegroundColor Blue } ``` @@ -345,16 +345,16 @@ Create different brightness/contrast profiles: ```powershell # gaming-profile.ps1 Write-Host "Activating Gaming Profile..." -ForegroundColor Cyan -DDCSwitch set 0 input HDMI1 # Switch to console -DDCSwitch set 0 brightness 90% # High brightness for gaming -DDCSwitch set 0 contrast 85% # High contrast for visibility +ddcswitch set 0 input HDMI1 # Switch to console +ddcswitch set 0 brightness 90% # High brightness for gaming +ddcswitch set 0 contrast 85% # High contrast for visibility Write-Host "Gaming profile activated!" -ForegroundColor Green # work-profile.ps1 Write-Host "Activating Work Profile..." -ForegroundColor Cyan -DDCSwitch set 0 input DP1 # Switch to PC -DDCSwitch set 0 brightness 60% # Comfortable brightness for long work -DDCSwitch set 0 contrast 75% # Balanced contrast for text +ddcswitch set 0 input DP1 # Switch to PC +ddcswitch set 0 brightness 60% # Comfortable brightness for long work +ddcswitch set 0 contrast 75% # Balanced contrast for text Write-Host "Work profile activated!" -ForegroundColor Green ``` @@ -364,33 +364,33 @@ Write-Host "Work profile activated!" -ForegroundColor Green ```powershell # Scan all VCP features for all monitors -DDCSwitch get all +ddcswitch get all # Scan all VCP features for a specific monitor -DDCSwitch get 0 +ddcswitch get 0 # Scan by monitor name -DDCSwitch get "VG270U P" +ddcswitch get "VG270U P" ``` ### Common VCP Codes ```powershell # Brightness (VCP 0x10) -DDCSwitch get 0 0x10 -DDCSwitch set 0 0x10 120 +ddcswitch get 0 0x10 +ddcswitch set 0 0x10 120 # Contrast (VCP 0x12) -DDCSwitch get 0 0x12 -DDCSwitch set 0 0x12 140 +ddcswitch get 0 0x12 +ddcswitch set 0 0x12 140 # Input Source (VCP 0x60) -DDCSwitch get 0 0x60 -DDCSwitch set 0 0x60 0x11 # HDMI1 +ddcswitch get 0 0x60 +ddcswitch set 0 0x60 0x11 # HDMI1 # Color Temperature (VCP 0x14) - if supported -DDCSwitch get 0 0x14 -DDCSwitch set 0 0x14 6500 # 6500K +ddcswitch get 0 0x14 +ddcswitch set 0 0x14 6500 # 6500K ``` ### Test Unknown VCP Codes @@ -404,19 +404,19 @@ $commonCodes = @(0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x20, 0x30, 0x60, 0x62, 0x6 foreach ($code in $commonCodes) { $hexCode = "0x{0:X2}" -f $code try { - $result = DDCSwitch get 0 $hexCode 2>$null + $result = ddcswitch get 0 $hexCode 2>$null if ($result -notmatch "error|failed") { - Write-Host "✓ VCP $hexCode supported: $result" -ForegroundColor Green + Write-Host "? VCP $hexCode supported: $result" -ForegroundColor Green } } catch { - Write-Host "✗ VCP $hexCode not supported" -ForegroundColor Red + Write-Host "? VCP $hexCode not supported" -ForegroundColor Red } } ``` ## JSON Output and Automation -All DDCSwitch commands support the `--json` flag for machine-readable output. This is perfect for scripting, automation, and integration with other tools. +All ddcswitch commands support the `--json` flag for machine-readable output. This is perfect for scripting, automation, and integration with other tools. ### PowerShell JSON Examples @@ -426,12 +426,12 @@ Check the current input and switch only if needed, then adjust brightness: ```powershell # Check if monitor is on HDMI1, switch to DP1 if not, then set work brightness -$result = DDCSwitch get 0 --json | ConvertFrom-Json +$result = ddcswitch get 0 --json | ConvertFrom-Json if ($result.success -and $result.currentInputCode -ne "0x11") { Write-Host "Monitor is on $($result.currentInput), switching to HDMI1..." - DDCSwitch set 0 HDMI1 --json | Out-Null - DDCSwitch set 0 brightness 75% --json | Out-Null + ddcswitch set 0 HDMI1 --json | Out-Null + ddcswitch set 0 brightness 75% --json | Out-Null Write-Host "Switched to HDMI1 and set brightness to 75%" -ForegroundColor Green } else { Write-Host "Monitor already on HDMI1" @@ -442,7 +442,7 @@ if ($result.success -and $result.currentInputCode -ne "0x11") { ```powershell # Switch all available monitors with full configuration -$listResult = DDCSwitch list --json | ConvertFrom-Json +$listResult = ddcswitch list --json | ConvertFrom-Json if ($listResult.success) { foreach ($monitor in $listResult.monitors) { @@ -450,25 +450,25 @@ if ($listResult.success) { Write-Host "Configuring $($monitor.name)..." -ForegroundColor Cyan # Set input - $setResult = DDCSwitch set $monitor.index HDMI1 --json | ConvertFrom-Json + $setResult = ddcswitch set $monitor.index HDMI1 --json | ConvertFrom-Json if ($setResult.success) { - Write-Host " ✓ Input: HDMI1" -ForegroundColor Green + Write-Host " ? Input: HDMI1" -ForegroundColor Green } # Set brightness - $brightnessResult = DDCSwitch set $monitor.index brightness 75% --json | ConvertFrom-Json + $brightnessResult = ddcswitch set $monitor.index brightness 75% --json | ConvertFrom-Json if ($brightnessResult.success) { - Write-Host " ✓ Brightness: 75%" -ForegroundColor Green + Write-Host " ? Brightness: 75%" -ForegroundColor Green } else { - Write-Host " ✗ Brightness not supported" -ForegroundColor Yellow + Write-Host " ? Brightness not supported" -ForegroundColor Yellow } # Set contrast - $contrastResult = DDCSwitch set $monitor.index contrast 80% --json | ConvertFrom-Json + $contrastResult = ddcswitch set $monitor.index contrast 80% --json | ConvertFrom-Json if ($contrastResult.success) { - Write-Host " ✓ Contrast: 80%" -ForegroundColor Green + Write-Host " ? Contrast: 80%" -ForegroundColor Green } else { - Write-Host " ✗ Contrast not supported" -ForegroundColor Yellow + Write-Host " ? Contrast not supported" -ForegroundColor Yellow } } } @@ -483,7 +483,7 @@ Create a comprehensive dashboard showing all monitor states: ```powershell # monitor-dashboard.ps1 -$result = DDCSwitch list --verbose --json | ConvertFrom-Json +$result = ddcswitch list --verbose --json | ConvertFrom-Json if ($result.success) { Write-Host "`n=== Monitor Status Dashboard ===" -ForegroundColor Cyan @@ -518,7 +518,7 @@ if ($result.success) { # smart-brightness-toggle.ps1 param([int]$MonitorIndex = 0) -$result = DDCSwitch get $MonitorIndex brightness --json | ConvertFrom-Json +$result = ddcswitch get $MonitorIndex brightness --json | ConvertFrom-Json if ($result.success) { $currentPercent = $result.percentageValue @@ -531,10 +531,10 @@ if ($result.success) { default { 25 } } - $switchResult = DDCSwitch set $MonitorIndex brightness "$newBrightness%" --json | ConvertFrom-Json + $switchResult = ddcswitch set $MonitorIndex brightness "$newBrightness%" --json | ConvertFrom-Json if ($switchResult.success) { - Write-Host "Brightness: $currentPercent% → $newBrightness%" -ForegroundColor Green + Write-Host "Brightness: $currentPercent% ? $newBrightness%" -ForegroundColor Green } } else { Write-Host "Brightness control not supported on this monitor" -ForegroundColor Yellow @@ -552,7 +552,7 @@ import json import sys def run_ddc(args): - """Run DDCSwitch and return JSON result""" + """Run ddcswitch and return JSON result""" result = subprocess.run( ['DDCSwitch'] + args + ['--json'], capture_output=True, @@ -577,25 +577,25 @@ def set_brightness(monitor_index, percentage): """Set monitor brightness""" data = run_ddc(['set', str(monitor_index), 'brightness', f'{percentage}%']) if data['success']: - print(f"✓ Set brightness to {percentage}% on {data['monitor']['name']}") + print(f"? Set brightness to {percentage}% on {data['monitor']['name']}") else: - print(f"✗ Error: {data['error']}", file=sys.stderr) + print(f"? Error: {data['error']}", file=sys.stderr) def set_contrast(monitor_index, percentage): """Set monitor contrast""" data = run_ddc(['set', str(monitor_index), 'contrast', f'{percentage}%']) if data['success']: - print(f"✓ Set contrast to {percentage}% on {data['monitor']['name']}") + print(f"? Set contrast to {percentage}% on {data['monitor']['name']}") else: - print(f"✗ Error: {data['error']}", file=sys.stderr) + print(f"? Error: {data['error']}", file=sys.stderr) def switch_input(monitor_index, input_name): """Switch monitor input""" data = run_ddc(['set', str(monitor_index), input_name]) if data['success']: - print(f"✓ Switched {data['monitor']['name']} to {data['newInput']}") + print(f"? Switched {data['monitor']['name']} to {data['newInput']}") else: - print(f"✗ Error: {data['error']}", file=sys.stderr) + print(f"? Error: {data['error']}", file=sys.stderr) sys.exit(1) # Example usage @@ -626,7 +626,7 @@ def run_ddc(args): def set_brightness_all(percentage): """Set brightness on all supported monitors""" - print(f"🔆 Setting brightness to {percentage}%...") + print(f"?? Setting brightness to {percentage}%...") data = run_ddc(['list']) if data['success']: @@ -634,9 +634,9 @@ def set_brightness_all(percentage): if monitor['status'] == 'ok': result = run_ddc(['set', str(monitor['index']), 'brightness', f'{percentage}%']) if result['success']: - print(f" ✓ {monitor['name']} → {percentage}%") + print(f" ? {monitor['name']} ? {percentage}%") else: - print(f" ✗ {monitor['name']} → Brightness not supported") + print(f" ? {monitor['name']} ? Brightness not supported") def get_brightness_for_time(): """Get appropriate brightness based on current time""" @@ -653,7 +653,7 @@ def get_brightness_for_time(): def gaming_mode(): """Switch to gaming setup with high brightness""" - print("🎮 Activating gaming mode...") + print("?? Activating gaming mode...") data = run_ddc(['list']) if data['success']: @@ -662,16 +662,16 @@ def gaming_mode(): # Switch to HDMI (console) input_result = run_ddc(['set', str(monitor['index']), 'HDMI1']) if input_result['success']: - print(f" ✓ {monitor['name']} → HDMI1") + print(f" ? {monitor['name']} ? HDMI1") # Set high brightness for gaming brightness_result = run_ddc(['set', str(monitor['index']), 'brightness', '90%']) if brightness_result['success']: - print(f" ✓ {monitor['name']} → 90% brightness") + print(f" ? {monitor['name']} ? 90% brightness") def work_mode(): """Switch to work setup with comfortable brightness""" - print("💼 Activating work mode...") + print("?? Activating work mode...") data = run_ddc(['list']) if data['success']: @@ -680,12 +680,12 @@ def work_mode(): # Switch to DisplayPort (PC) input_result = run_ddc(['set', str(monitor['index']), 'DP1']) if input_result['success']: - print(f" ✓ {monitor['name']} → DP1") + print(f" ? {monitor['name']} ? DP1") # Set comfortable brightness for work brightness_result = run_ddc(['set', str(monitor['index']), 'brightness', '60%']) if brightness_result['success']: - print(f" ✓ {monitor['name']} → 60% brightness") + print(f" ? {monitor['name']} ? 60% brightness") # Auto-brightness based on time if __name__ == '__main__': @@ -701,9 +701,9 @@ if __name__ == '__main__': // monitor-api.js const { execSync } = require('child_process'); -class DDCSwitch { +class ddcswitch { static exec(args) { - const output = execSync(`DDCSwitch ${args.join(' ')} --json`, { + const output = execSync(`ddcswitch ${args.join(' ')} --json`, { encoding: 'utf-8' }); return JSON.parse(output); @@ -757,12 +757,12 @@ monitors.monitors.forEach(monitor => { // Set brightness and contrast const brightnessResult = DDCSwitch.setBrightness(0, 75); if (brightnessResult.success) { - console.log(`✓ Set brightness to 75%`); + console.log(`? Set brightness to 75%`); } const contrastResult = DDCSwitch.setContrast(0, 80); if (contrastResult.success) { - console.log(`✓ Set contrast to 80%`); + console.log(`? Set contrast to 80%`); } ``` @@ -778,7 +778,7 @@ app.use(express.json()); function runDDC(args) { try { - const output = execSync(`DDCSwitch ${args.join(' ')} --json`, { + const output = execSync(`ddcswitch ${args.join(' ')} --json`, { encoding: 'utf-8' }); return JSON.parse(output); @@ -860,7 +860,7 @@ app.post('/monitors/:id/profile', (req, res) => { }); app.listen(3000, () => { - console.log('DDCSwitch API running on http://localhost:3000'); + console.log('ddcswitch API running on http://localhost:3000'); console.log('Endpoints:'); console.log(' GET /monitors?verbose=true'); console.log(' GET /monitors/:id/brightness'); @@ -878,30 +878,30 @@ REM complete-setup.bat - Set input, brightness, and contrast echo Setting up monitor configuration... REM Switch to HDMI1 -for /f "delims=" %%i in ('DDCSwitch set 0 HDMI1 --json') do set INPUT_RESULT=%%i +for /f "delims=" %%i in ('ddcswitch set 0 HDMI1 --json') do set INPUT_RESULT=%%i echo %INPUT_RESULT% | find "\"success\":true" >nul if not errorlevel 1 ( - echo ✓ Switched to HDMI1 + echo ? Switched to HDMI1 ) else ( - echo ✗ Failed to switch input + echo ? Failed to switch input ) REM Set brightness to 75% -for /f "delims=" %%i in ('DDCSwitch set 0 brightness 75%% --json') do set BRIGHTNESS_RESULT=%%i +for /f "delims=" %%i in ('ddcswitch set 0 brightness 75%% --json') do set BRIGHTNESS_RESULT=%%i echo %BRIGHTNESS_RESULT% | find "\"success\":true" >nul if not errorlevel 1 ( - echo ✓ Set brightness to 75%% + echo ? Set brightness to 75%% ) else ( - echo ✗ Brightness not supported or failed + echo ? Brightness not supported or failed ) REM Set contrast to 80% -for /f "delims=" %%i in ('DDCSwitch set 0 contrast 80%% --json') do set CONTRAST_RESULT=%%i +for /f "delims=" %%i in ('ddcswitch set 0 contrast 80%% --json') do set CONTRAST_RESULT=%%i echo %CONTRAST_RESULT% | find "\"success\":true" >nul if not errorlevel 1 ( - echo ✓ Set contrast to 80%% + echo ? Set contrast to 80%% ) else ( - echo ✗ Contrast not supported or failed + echo ? Contrast not supported or failed ) echo Monitor setup complete! @@ -1008,25 +1008,25 @@ fn main() -> Result<(), Box> { match set_brightness(monitor_index, 75) { Ok(response) if response.success => { - println!("✓ Set brightness to 75%"); + println!("? Set brightness to 75%"); } Ok(response) => { - println!("✗ Failed to set brightness: {:?}", response.error); + println!("? Failed to set brightness: {:?}", response.error); } Err(e) => { - println!("✗ Error setting brightness: {}", e); + println!("? Error setting brightness: {}", e); } } match set_contrast(monitor_index, 80) { Ok(response) if response.success => { - println!("✓ Set contrast to 80%"); + println!("? Set contrast to 80%"); } Ok(response) => { - println!("✗ Failed to set contrast: {:?}", response.error); + println!("? Failed to set contrast: {:?}", response.error); } Err(e) => { - println!("✗ Error setting contrast: {}", e); + println!("? Error setting contrast: {}", e); } } } @@ -1049,12 +1049,12 @@ Switch all monitors to PC inputs with comfortable brightness: ```powershell # work-setup.ps1 Write-Host "Switching to work setup..." -ForegroundColor Cyan -DDCSwitch set 0 DP1 -DDCSwitch set 0 brightness 60% -DDCSwitch set 0 contrast 75% -DDCSwitch set 1 DP2 -DDCSwitch set 1 brightness 60% -DDCSwitch set 1 contrast 75% +ddcswitch set 0 DP1 +ddcswitch set 0 brightness 60% +ddcswitch set 0 contrast 75% +ddcswitch set 1 DP2 +ddcswitch set 1 brightness 60% +ddcswitch set 1 contrast 75% Write-Host "Work setup ready!" -ForegroundColor Green ``` @@ -1064,12 +1064,12 @@ Switch monitors to console inputs with high brightness: ```powershell # gaming-setup.ps1 Write-Host "Switching to gaming setup..." -ForegroundColor Cyan -DDCSwitch set 0 HDMI1 # Main monitor to PS5 -DDCSwitch set 0 brightness 90% -DDCSwitch set 0 contrast 85% -DDCSwitch set 1 HDMI2 # Secondary to Switch -DDCSwitch set 1 brightness 85% -DDCSwitch set 1 contrast 80% +ddcswitch set 0 HDMI1 # Main monitor to PS5 +ddcswitch set 0 brightness 90% +ddcswitch set 0 contrast 85% +ddcswitch set 1 HDMI2 # Secondary to Switch +ddcswitch set 1 brightness 85% +ddcswitch set 1 contrast 80% Write-Host "Gaming setup ready!" -ForegroundColor Green ``` @@ -1079,9 +1079,9 @@ Optimize for media consumption: ```powershell # media-setup.ps1 Write-Host "Switching to media setup..." -ForegroundColor Cyan -DDCSwitch set 0 HDMI1 # Media device -DDCSwitch set 0 brightness 40% # Lower brightness for comfortable viewing -DDCSwitch set 0 contrast 90% # High contrast for better blacks +ddcswitch set 0 HDMI1 # Media device +ddcswitch set 0 brightness 40% # Lower brightness for comfortable viewing +ddcswitch set 0 contrast 90% # High contrast for better blacks Write-Host "Media setup ready!" -ForegroundColor Green ``` @@ -1090,11 +1090,11 @@ Write-Host "Media setup ready!" -ForegroundColor Green Create a comprehensive input switching and brightness control system: ```autohotkey -; DDCSwitch AutoHotkey Script with VCP Support -; Place DDCSwitch.exe in C:\Tools\ or update path below +; ddcswitch AutoHotkey Script with VCP Support +; Place ddcswitch.exe in C:\Tools\ or update path below ; Global variables -DDCSwitchPath := "C:\Tools\DDCSwitch.exe" +DDCSwitchPath := "C:\Tools\ddcswitch.exe" ; Function to run DDCSwitch RunDDCSwitch(args) { @@ -1106,19 +1106,19 @@ RunDDCSwitch(args) { ; Ctrl+Alt+1: Switch monitor 0 to HDMI1 ^!1:: RunDDCSwitch("set 0 HDMI1") - TrayTip, DDCSwitch, Switched to HDMI1, 1 + TrayTip, ddcswitch, Switched to HDMI1, 1 return ; Ctrl+Alt+2: Switch monitor 0 to HDMI2 ^!2:: RunDDCSwitch("set 0 HDMI2") - TrayTip, DDCSwitch, Switched to HDMI2, 1 + TrayTip, ddcswitch, Switched to HDMI2, 1 return ; Ctrl+Alt+D: Switch monitor 0 to DisplayPort ^!d:: RunDDCSwitch("set 0 DP1") - TrayTip, DDCSwitch, Switched to DisplayPort, 1 + TrayTip, ddcswitch, Switched to DisplayPort, 1 return ; Brightness control hotkeys @@ -1126,39 +1126,39 @@ RunDDCSwitch(args) { ^!NumpadAdd:: ^!=:: RunDDCSwitch("set 0 brightness +10%") - TrayTip, DDCSwitch, Brightness increased, 1 + TrayTip, ddcswitch, Brightness increased, 1 return ; Ctrl+Alt+Minus: Decrease brightness by 10% ^!NumpadSub:: ^!-:: RunDDCSwitch("set 0 brightness -10%") - TrayTip, DDCSwitch, Brightness decreased, 1 + TrayTip, ddcswitch, Brightness decreased, 1 return ; Brightness presets ; Ctrl+Alt+F1: 25% brightness (night mode) ^!F1:: RunDDCSwitch("set 0 brightness 25%") - TrayTip, DDCSwitch, Night Mode (25%), 1 + TrayTip, ddcswitch, Night Mode (25%), 1 return ; Ctrl+Alt+F2: 50% brightness (comfortable) ^!F2:: RunDDCSwitch("set 0 brightness 50%") - TrayTip, DDCSwitch, Comfortable (50%), 1 + TrayTip, ddcswitch, Comfortable (50%), 1 return ; Ctrl+Alt+F3: 75% brightness (bright) ^!F3:: RunDDCSwitch("set 0 brightness 75%") - TrayTip, DDCSwitch, Bright (75%), 1 + TrayTip, ddcswitch, Bright (75%), 1 return ; Ctrl+Alt+F4: 100% brightness (maximum) ^!F4:: RunDDCSwitch("set 0 brightness 100%") - TrayTip, DDCSwitch, Maximum (100%), 1 + TrayTip, ddcswitch, Maximum (100%), 1 return ; Profile hotkeys @@ -1171,7 +1171,7 @@ RunDDCSwitch(args) { RunDDCSwitch("set 0 contrast 75%") Sleep 500 RunDDCSwitch("set 1 DP2") - TrayTip, DDCSwitch, Work Setup Activated, 1 + TrayTip, ddcswitch, Work Setup Activated, 1 return ; Ctrl+Alt+G: Gaming setup (all monitors to console with high brightness) @@ -1183,7 +1183,7 @@ RunDDCSwitch(args) { RunDDCSwitch("set 0 contrast 85%") Sleep 500 RunDDCSwitch("set 1 HDMI2") - TrayTip, DDCSwitch, Gaming Setup Activated, 1 + TrayTip, ddcswitch, Gaming Setup Activated, 1 return ; Ctrl+Alt+M: Media setup (HDMI with low brightness, high contrast) @@ -1193,17 +1193,17 @@ RunDDCSwitch(args) { RunDDCSwitch("set 0 brightness 40%") Sleep 500 RunDDCSwitch("set 0 contrast 90%") - TrayTip, DDCSwitch, Media Setup Activated, 1 + TrayTip, ddcswitch, Media Setup Activated, 1 return ; Ctrl+Alt+L: List all monitors with verbose info ^!l:: - Run, cmd /k DDCSwitch.exe list --verbose + Run, cmd /k ddcswitch.exe list --verbose return ; Ctrl+Alt+I: Show current monitor info ^!i:: - Run, cmd /k "DDCSwitch.exe get 0 && DDCSwitch.exe get 0 brightness && DDCSwitch.exe get 0 contrast && pause" + Run, cmd /k "ddcswitch.exe get 0 && ddcswitch.exe get 0 brightness && ddcswitch.exe get 0 contrast && pause" return ``` @@ -1214,40 +1214,40 @@ If you use Elgato Stream Deck, create actions for complete monitor control: **Button 1: PC Mode** ``` Title: PC Mode -Command: C:\Tools\DDCSwitch.exe set 0 DP1 -Arguments: && C:\Tools\DDCSwitch.exe set 0 brightness 60% +Command: C:\Tools\ddcswitch.exe set 0 DP1 +Arguments: && C:\Tools\ddcswitch.exe set 0 brightness 60% ``` **Button 2: Console Mode** ``` Title: Console Mode -Command: C:\Tools\DDCSwitch.exe set 0 HDMI1 -Arguments: && C:\Tools\DDCSwitch.exe set 0 brightness 90% +Command: C:\Tools\ddcswitch.exe set 0 HDMI1 +Arguments: && C:\Tools\ddcswitch.exe set 0 brightness 90% ``` **Button 3: Brightness Low** ``` -Title: 🔅 Low -Command: C:\Tools\DDCSwitch.exe set 0 brightness 25% +Title: ?? Low +Command: C:\Tools\ddcswitch.exe set 0 brightness 25% ``` **Button 4: Brightness High** ``` -Title: 🔆 High -Command: C:\Tools\DDCSwitch.exe set 0 brightness 85% +Title: ?? High +Command: C:\Tools\ddcswitch.exe set 0 brightness 85% ``` **Button 5: Monitor Info** ``` Title: Monitor Info -Command: cmd /k C:\Tools\DDCSwitch.exe list --verbose +Command: cmd /k C:\Tools\ddcswitch.exe list --verbose ``` **Button 6: Gaming Profile** ``` -Title: 🎮 Gaming -Command: C:\Tools\DDCSwitch.exe set 0 HDMI1 -Arguments: && timeout /t 1 && C:\Tools\DDCSwitch.exe set 0 brightness 90% && C:\Tools\DDCSwitch.exe set 0 contrast 85% +Title: ?? Gaming +Command: C:\Tools\ddcswitch.exe set 0 HDMI1 +Arguments: && timeout /t 1 && C:\Tools\ddcswitch.exe set 0 brightness 90% && C:\Tools\ddcswitch.exe set 0 contrast 85% ``` ### Task Scheduler Integration @@ -1257,10 +1257,10 @@ Automatically switch inputs at specific times: #### Morning: Switch to Work Setup (8 AM) 1. Open Task Scheduler -2. Create Basic Task → "Morning Work Setup" +2. Create Basic Task ? "Morning Work Setup" 3. Trigger: Daily at 8:00 AM 4. Action: Start a program - - Program: `C:\Tools\DDCSwitch.exe` + - Program: `C:\Tools\ddcswitch.exe` - Arguments: `set 0 DP1` #### Evening: Switch to Gaming Setup (6 PM) @@ -1272,45 +1272,45 @@ Same steps, but with trigger at 6:00 PM and arguments: `set 0 HDMI1` Add to your PowerShell profile (`$PROFILE`): ```powershell -# DDCSwitch aliases for complete monitor control -function ddc-list { DDCSwitch list --verbose } +# ddcswitch aliases for complete monitor control +function ddc-list { ddcswitch list --verbose } function ddc-work { - DDCSwitch set 0 DP1 - DDCSwitch set 0 brightness 60% - DDCSwitch set 0 contrast 75% - DDCSwitch set 1 DP2 - Write-Host "✓ Work setup activated" -ForegroundColor Green + ddcswitch set 0 DP1 + ddcswitch set 0 brightness 60% + ddcswitch set 0 contrast 75% + ddcswitch set 1 DP2 + Write-Host "? Work setup activated" -ForegroundColor Green } function ddc-game { - DDCSwitch set 0 HDMI1 - DDCSwitch set 0 brightness 90% - DDCSwitch set 0 contrast 85% - DDCSwitch set 1 HDMI2 - Write-Host "✓ Gaming setup activated" -ForegroundColor Green + ddcswitch set 0 HDMI1 + ddcswitch set 0 brightness 90% + ddcswitch set 0 contrast 85% + ddcswitch set 1 HDMI2 + Write-Host "? Gaming setup activated" -ForegroundColor Green } function ddc-media { - DDCSwitch set 0 HDMI1 - DDCSwitch set 0 brightness 40% - DDCSwitch set 0 contrast 90% - Write-Host "✓ Media setup activated" -ForegroundColor Green + ddcswitch set 0 HDMI1 + ddcswitch set 0 brightness 40% + ddcswitch set 0 contrast 90% + Write-Host "? Media setup activated" -ForegroundColor Green } -function ddc-hdmi { DDCSwitch set 0 HDMI1 } -function ddc-dp { DDCSwitch set 0 DP1 } -function ddc-bright([int]$level) { DDCSwitch set 0 brightness "$level%" } -function ddc-contrast([int]$level) { DDCSwitch set 0 contrast "$level%" } +function ddc-hdmi { ddcswitch set 0 HDMI1 } +function ddc-dp { ddcswitch set 0 DP1 } +function ddc-bright([int]$level) { ddcswitch set 0 brightness "$level%" } +function ddc-contrast([int]$level) { ddcswitch set 0 contrast "$level%" } # Brightness shortcuts -function ddc-dim { DDCSwitch set 0 brightness 25% } -function ddc-normal { DDCSwitch set 0 brightness 60% } -function ddc-bright { DDCSwitch set 0 brightness 85% } -function ddc-max { DDCSwitch set 0 brightness 100% } +function ddc-dim { ddcswitch set 0 brightness 25% } +function ddc-normal { ddcswitch set 0 brightness 60% } +function ddc-bright { ddcswitch set 0 brightness 85% } +function ddc-max { ddcswitch set 0 brightness 100% } # Then use: ddc-work, ddc-game, ddc-media, ddc-bright 75, ddc-list ``` ### Complete Monitor Control -Use DDCSwitch as a comprehensive monitor management solution: +Use ddcswitch as a comprehensive monitor management solution: ```powershell # complete-monitor-control.ps1 @@ -1327,28 +1327,28 @@ function Apply-Profile { switch ($ProfileName.ToLower()) { "work" { - DDCSwitch set $MonitorIndex DP1 - DDCSwitch set $MonitorIndex brightness 60% - DDCSwitch set $MonitorIndex contrast 75% - Write-Host "✓ Applied work profile" -ForegroundColor Green + ddcswitch set $MonitorIndex DP1 + ddcswitch set $MonitorIndex brightness 60% + ddcswitch set $MonitorIndex contrast 75% + Write-Host "? Applied work profile" -ForegroundColor Green } "gaming" { - DDCSwitch set $MonitorIndex HDMI1 - DDCSwitch set $MonitorIndex brightness 90% - DDCSwitch set $MonitorIndex contrast 85% - Write-Host "✓ Applied gaming profile" -ForegroundColor Green + ddcswitch set $MonitorIndex HDMI1 + ddcswitch set $MonitorIndex brightness 90% + ddcswitch set $MonitorIndex contrast 85% + Write-Host "? Applied gaming profile" -ForegroundColor Green } "media" { - DDCSwitch set $MonitorIndex HDMI1 - DDCSwitch set $MonitorIndex brightness 40% - DDCSwitch set $MonitorIndex contrast 90% - Write-Host "✓ Applied media profile" -ForegroundColor Green + ddcswitch set $MonitorIndex HDMI1 + ddcswitch set $MonitorIndex brightness 40% + ddcswitch set $MonitorIndex contrast 90% + Write-Host "? Applied media profile" -ForegroundColor Green } "custom" { - if ($Input) { DDCSwitch set $MonitorIndex $Input } - if ($Brightness) { DDCSwitch set $MonitorIndex brightness "$Brightness%" } - if ($Contrast) { DDCSwitch set $MonitorIndex contrast "$Contrast%" } - Write-Host "✓ Applied custom settings" -ForegroundColor Green + if ($Input) { ddcswitch set $MonitorIndex $Input } + if ($Brightness) { ddcswitch set $MonitorIndex brightness "$Brightness%" } + if ($Contrast) { ddcswitch set $MonitorIndex contrast "$Contrast%" } + Write-Host "? Applied custom settings" -ForegroundColor Green } } } @@ -1375,39 +1375,39 @@ Write-Host "=" * 50 # Test brightness support Write-Host "`nTesting Brightness (VCP 0x10)..." -ForegroundColor Yellow try { - $brightness = DDCSwitch get $monitor brightness 2>$null + $brightness = ddcswitch get $monitor brightness 2>$null if ($brightness -match "Brightness:") { - Write-Host "✓ Brightness supported: $brightness" -ForegroundColor Green + Write-Host "? Brightness supported: $brightness" -ForegroundColor Green # Test setting brightness - DDCSwitch set $monitor brightness 50% | Out-Null + ddcswitch set $monitor brightness 50% | Out-Null Start-Sleep -Seconds 1 - $newBrightness = DDCSwitch get $monitor brightness - Write-Host "✓ Brightness control works: $newBrightness" -ForegroundColor Green + $newBrightness = ddcswitch get $monitor brightness + Write-Host "? Brightness control works: $newBrightness" -ForegroundColor Green } else { - Write-Host "✗ Brightness not supported" -ForegroundColor Red + Write-Host "? Brightness not supported" -ForegroundColor Red } } catch { - Write-Host "✗ Brightness not supported" -ForegroundColor Red + Write-Host "? Brightness not supported" -ForegroundColor Red } # Test contrast support Write-Host "`nTesting Contrast (VCP 0x12)..." -ForegroundColor Yellow try { - $contrast = DDCSwitch get $monitor contrast 2>$null + $contrast = ddcswitch get $monitor contrast 2>$null if ($contrast -match "Contrast:") { - Write-Host "✓ Contrast supported: $contrast" -ForegroundColor Green + Write-Host "? Contrast supported: $contrast" -ForegroundColor Green # Test setting contrast - DDCSwitch set $monitor contrast 75% | Out-Null + ddcswitch set $monitor contrast 75% | Out-Null Start-Sleep -Seconds 1 - $newContrast = DDCSwitch get $monitor contrast - Write-Host "✓ Contrast control works: $newContrast" -ForegroundColor Green + $newContrast = ddcswitch get $monitor contrast + Write-Host "? Contrast control works: $newContrast" -ForegroundColor Green } else { - Write-Host "✗ Contrast not supported" -ForegroundColor Red + Write-Host "? Contrast not supported" -ForegroundColor Red } } catch { - Write-Host "✗ Contrast not supported" -ForegroundColor Red + Write-Host "? Contrast not supported" -ForegroundColor Red } # Test raw VCP codes @@ -1428,14 +1428,14 @@ $vcpCodes = @{ foreach ($code in $vcpCodes.Keys) { try { - $result = DDCSwitch get $monitor $code 2>$null + $result = ddcswitch get $monitor $code 2>$null if ($result -and $result -notmatch "error|failed|not supported") { - Write-Host "✓ VCP $code ($($vcpCodes[$code])): $result" -ForegroundColor Green + Write-Host "? VCP $code ($($vcpCodes[$code])): $result" -ForegroundColor Green } else { - Write-Host "✗ VCP $code ($($vcpCodes[$code])): Not supported" -ForegroundColor Gray + Write-Host "? VCP $code ($($vcpCodes[$code])): Not supported" -ForegroundColor Gray } } catch { - Write-Host "✗ VCP $code ($($vcpCodes[$code])): Not supported" -ForegroundColor Gray + Write-Host "? VCP $code ($($vcpCodes[$code])): Not supported" -ForegroundColor Gray } } @@ -1461,9 +1461,9 @@ foreach ($brightness in $brightnessLevels) { foreach ($contrast in $contrastLevels) { Write-Host "Setting: Brightness $brightness%, Contrast $contrast%" -ForegroundColor Yellow - DDCSwitch set $monitor brightness "$brightness%" | Out-Null + ddcswitch set $monitor brightness "$brightness%" | Out-Null Start-Sleep -Milliseconds 500 - DDCSwitch set $monitor contrast "$contrast%" | Out-Null + ddcswitch set $monitor contrast "$contrast%" | Out-Null Write-Host "How does this look? (Press Enter to continue, 'q' to quit, 's' to save this setting)" -ForegroundColor Green $input = Read-Host @@ -1473,7 +1473,7 @@ foreach ($brightness in $brightnessLevels) { break } elseif ($input -eq 's') { Write-Host "Saved setting: Brightness $brightness%, Contrast $contrast%" -ForegroundColor Cyan - Write-Host "Command to reproduce: DDCSwitch set $monitor brightness $brightness%; DDCSwitch set $monitor contrast $contrast%" -ForegroundColor White + Write-Host "Command to reproduce: ddcswitch set $monitor brightness $brightness%; ddcswitch set $monitor contrast $contrast%" -ForegroundColor White Read-Host "Press Enter to continue or Ctrl+C to stop" } } @@ -1483,7 +1483,7 @@ foreach ($brightness in $brightnessLevels) { ### Finding Non-Standard VCP Codes -Some monitors use non-standard DDC/CI codes. If DDCSwitch shows the wrong current input or switching doesn't work, you can find the correct codes: +Some monitors use non-standard DDC/CI codes. If ddcswitch shows the wrong current input or switching doesn't work, you can find the correct codes: #### Method 1: Use ControlMyMonitor by NirSoft @@ -1499,15 +1499,15 @@ Some monitors use non-standard DDC/CI codes. If DDCSwitch shows the wrong curren **Example:** ``` VCP Code 60 (Input Source): -- HDMI1 physical input → Shows value 17 (0x11) ✓ Standard -- HDMI2 physical input → Shows value 18 (0x12) ✓ Standard -- DisplayPort physical input → Shows value 15 (0x0F) ✓ Standard -- DisplayPort physical input → Shows value 27 (0x1B) ✗ Non-standard! +- HDMI1 physical input ? Shows value 17 (0x11) ? Standard +- HDMI2 physical input ? Shows value 18 (0x12) ? Standard +- DisplayPort physical input ? Shows value 15 (0x0F) ? Standard +- DisplayPort physical input ? Shows value 27 (0x1B) ? Non-standard! ``` Once you know the correct codes, use them with DDCSwitch: ```powershell -DDCSwitch set 0 0x1B # Use the actual code your monitor responds to +ddcswitch set 0 0x1B # Use the actual code your monitor responds to ``` #### Method 2: Trial and Error @@ -1525,7 +1525,7 @@ $codes = @(0x01, 0x02, 0x03, 0x04, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x1 foreach ($code in $codes) { $hexCode = "0x{0:X2}" -f $code Write-Host "Testing $hexCode..." -ForegroundColor Green - DDCSwitch set 0 $hexCode + ddcswitch set 0 $hexCode Start-Sleep -Seconds 3 } @@ -1539,17 +1539,17 @@ Write-Host "Testing complete! Document which codes worked for your monitor." -Fo ```powershell # Get current input (if this works, DDC/CI is functional) -DDCSwitch get 0 +ddcswitch get 0 # Get by monitor name -DDCSwitch get "VG270U" +ddcswitch get "VG270U" # List all monitors with all VCP values -DDCSwitch get all +ddcswitch get all # Try setting to current input (should succeed instantly) -DDCSwitch list # Note the current input -DDCSwitch set 0 +ddcswitch list # Note the current input +ddcswitch set 0 ``` ### Dealing with Slow Monitors @@ -1558,9 +1558,9 @@ Some monitors are slow to respond to DDC/CI commands. Add delays: ```powershell # slow-switch.ps1 -DDCSwitch set 0 HDMI1 +ddcswitch set 0 HDMI1 Start-Sleep -Seconds 2 # Wait for monitor to switch -DDCSwitch set 1 HDMI2 +ddcswitch set 1 HDMI2 Start-Sleep -Seconds 2 Write-Host "Done" -ForegroundColor Green ``` @@ -1571,25 +1571,25 @@ Useful if monitor order changes: ```powershell # Find monitor with "LG" in the name and switch it -DDCSwitch list # Note the exact name -DDCSwitch set "LG ULTRAGEAR" HDMI1 +ddcswitch list # Note the exact name +ddcswitch set "LG ULTRAGEAR" HDMI1 # Partial name matching works -DDCSwitch set "ULTRAGEAR" HDMI1 +ddcswitch set "ULTRAGEAR" HDMI1 # Get settings by monitor name -DDCSwitch get "VG270U" brightness -DDCSwitch get "Generic PnP" # Gets all VCP values for this monitor +ddcswitch get "VG270U" brightness +ddcswitch get "Generic PnP" # Gets all VCP values for this monitor ``` ## Integration with Other Tools ### PowerToys Run -Add DDCSwitch to your PATH, then use PowerToys Run (Alt+Space): +Add ddcswitch to your PATH, then use PowerToys Run (Alt+Space): ``` -> DDCSwitch set 0 HDMI1 +> ddcswitch set 0 HDMI1 ``` ### Windows Run Dialog @@ -1597,7 +1597,7 @@ Add DDCSwitch to your PATH, then use PowerToys Run (Alt+Space): Press Win+R and type: ``` -DDCSwitch set 0 DP1 +ddcswitch set 0 DP1 ``` ### Batch File Shortcuts @@ -1607,21 +1607,21 @@ Create `.bat` files on your desktop: **switch-to-hdmi.bat:** ```batch @echo off -"C:\Tools\DDCSwitch.exe" set 0 HDMI1 +"C:\Tools\ddcswitch.exe" set 0 HDMI1 ``` **switch-to-dp.bat:** ```batch @echo off -"C:\Tools\DDCSwitch.exe" set 0 DP1 +"C:\Tools\ddcswitch.exe" set 0 DP1 ``` Make them double-clickable for quick access! ## Tips and Tricks -1. **Add to PATH**: Add DDCSwitch.exe location to your Windows PATH for easier access -2. **Create shortcuts**: Right-click DDCSwitch.exe → Send to → Desktop (create shortcut), then edit properties to add arguments +1. **Add to PATH**: Add ddcswitch.exe location to your Windows PATH for easier access +2. **Create shortcuts**: Right-click ddcswitch.exe ? Send to ? Desktop (create shortcut), then edit properties to add arguments 3. **Use monitoring**: Combine with other tools to detect when certain apps launch and switch inputs automatically 4. **Test first**: Always test with `list` and `get` before creating automation scripts 5. **Admin rights**: Some monitors require running as Administrator - right-click and "Run as administrator" @@ -1632,12 +1632,12 @@ Make them double-clickable for quick access! ```powershell # toggle-brightness.ps1 - Toggle between low/medium/high brightness -$current = DDCSwitch get 0 brightness --json | ConvertFrom-Json +$current = ddcswitch get 0 brightness --json | ConvertFrom-Json if ($current.success) { $currentPercent = $current.percentageValue - # Cycle through 25% → 50% → 75% → 100% → 25% + # Cycle through 25% ? 50% ? 75% ? 100% ? 25% $newBrightness = switch ($currentPercent) { {$_ -le 25} { 50 } {$_ -le 50} { 75 } @@ -1645,8 +1645,8 @@ if ($current.success) { default { 25 } } - DDCSwitch set 0 brightness "$newBrightness%" - Write-Host "Brightness: $currentPercent% → $newBrightness%" -ForegroundColor Green + ddcswitch set 0 brightness "$newBrightness%" + Write-Host "Brightness: $currentPercent% ? $newBrightness%" -ForegroundColor Green } else { Write-Host "Brightness control not supported" -ForegroundColor Red } @@ -1659,8 +1659,8 @@ if ($current.success) { param([string]$Profile = "work") # Get current settings -$inputResult = DDCSwitch get 0 --json | ConvertFrom-Json -$brightnessResult = DDCSwitch get 0 brightness --json | ConvertFrom-Json +$inputResult = ddcswitch get 0 --json | ConvertFrom-Json +$brightnessResult = ddcswitch get 0 brightness --json | ConvertFrom-Json if (-not $inputResult.success) { Write-Error "Monitor not accessible" @@ -1683,17 +1683,17 @@ $targetProfile = $profiles[$Profile] # Apply settings only if different if ($inputResult.currentInputCode -ne $targetProfile.input) { - DDCSwitch set 0 $targetProfile.input - Write-Host "✓ Input: $($targetProfile.input)" -ForegroundColor Green + ddcswitch set 0 $targetProfile.input + Write-Host "? Input: $($targetProfile.input)" -ForegroundColor Green } if ($brightnessResult.success -and $brightnessResult.percentageValue -ne $targetProfile.brightness) { - DDCSwitch set 0 brightness "$($targetProfile.brightness)%" - Write-Host "✓ Brightness: $($targetProfile.brightness)%" -ForegroundColor Green + ddcswitch set 0 brightness "$($targetProfile.brightness)%" + Write-Host "? Brightness: $($targetProfile.brightness)%" -ForegroundColor Green } -DDCSwitch set 0 contrast "$($targetProfile.contrast)%" -Write-Host "✓ Profile '$Profile' applied" -ForegroundColor Cyan +ddcswitch set 0 contrast "$($targetProfile.contrast)%" +Write-Host "? Profile '$Profile' applied" -ForegroundColor Cyan ``` ### Pattern 3: Sync All Monitors @@ -1706,7 +1706,7 @@ param( [int]$Contrast = 80 ) -$result = DDCSwitch list --json | ConvertFrom-Json +$result = ddcswitch list --json | ConvertFrom-Json if (-not $result.success) { Write-Error $result.error @@ -1719,27 +1719,27 @@ foreach ($monitor in $okMonitors) { Write-Host "Configuring monitor $($monitor.index) ($($monitor.name))..." -ForegroundColor Cyan # Set input - $inputResult = DDCSwitch set $monitor.index $Input --json | ConvertFrom-Json + $inputResult = ddcswitch set $monitor.index $Input --json | ConvertFrom-Json if ($inputResult.success) { - Write-Host " ✓ Input: $Input" -ForegroundColor Green + Write-Host " ? Input: $Input" -ForegroundColor Green } else { - Write-Host " ✗ Input failed: $($inputResult.error)" -ForegroundColor Red + Write-Host " ? Input failed: $($inputResult.error)" -ForegroundColor Red } # Set brightness - $brightnessResult = DDCSwitch set $monitor.index brightness "$Brightness%" --json | ConvertFrom-Json + $brightnessResult = ddcswitch set $monitor.index brightness "$Brightness%" --json | ConvertFrom-Json if ($brightnessResult.success) { - Write-Host " ✓ Brightness: $Brightness%" -ForegroundColor Green + Write-Host " ? Brightness: $Brightness%" -ForegroundColor Green } else { - Write-Host " ✗ Brightness not supported" -ForegroundColor Yellow + Write-Host " ? Brightness not supported" -ForegroundColor Yellow } # Set contrast - $contrastResult = DDCSwitch set $monitor.index contrast "$Contrast%" --json | ConvertFrom-Json + $contrastResult = ddcswitch set $monitor.index contrast "$Contrast%" --json | ConvertFrom-Json if ($contrastResult.success) { - Write-Host " ✓ Contrast: $Contrast%" -ForegroundColor Green + Write-Host " ? Contrast: $Contrast%" -ForegroundColor Green } else { - Write-Host " ✗ Contrast not supported" -ForegroundColor Yellow + Write-Host " ? Contrast not supported" -ForegroundColor Yellow } Start-Sleep -Milliseconds 500 # Prevent DDC/CI overload diff --git a/README.md b/README.md index 5ccfedd..a6cc656 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# DDCSwitch +# ddcswitch [![GitHub Release](https://img.shields.io/github/v/release/markdwags/DDCSwitch)](https://github.com/markdwags/DDCSwitch/releases) [![License](https://img.shields.io/github/license/markdwags/DDCSwitch)](https://github.com/markdwags/DDCSwitch/blob/main/LICENSE) @@ -28,7 +28,7 @@ A Windows command-line utility to control monitor settings via DDC/CI (Display D ### Pre-built Binary -Download the latest release from the [Releases](../../releases) page and extract `DDCSwitch.exe` to a folder in your PATH. +Download the latest release from the [Releases](../../releases) page and extract `ddcswitch.exe` to a folder in your PATH. ### Build from Source @@ -47,7 +47,7 @@ dotnet publish -c Release The project is pre-configured with NativeAOT (`true`), which produces a ~3-5 MB native executable with instant startup and no .NET runtime dependency. -Executable location: `DDCSwitch/bin/Release/net10.0/win-x64/publish/DDCSwitch.exe` +Executable location: `DDCSwitch/bin/Release/net10.0/win-x64/publish/ddcswitch.exe` ## Usage @@ -56,7 +56,7 @@ Executable location: `DDCSwitch/bin/Release/net10.0/win-x64/publish/DDCSwitch.ex Display all DDC/CI capable monitors with their current input sources: ```powershell -DDCSwitch list +ddcswitch list ``` Example output: @@ -74,7 +74,7 @@ Example output: Add `--verbose` to include brightness and contrast information: ```powershell -DDCSwitch list --verbose +ddcswitch list --verbose ``` Example output: @@ -94,7 +94,7 @@ Add `--json` for machine-readable output (see [EXAMPLES.md](EXAMPLES.md) for aut Get all VCP features for a specific monitor: ```powershell -DDCSwitch get 0 +ddcswitch get 0 ``` This will scan and display all supported VCP features for monitor 0, showing their names, access types, current values, and maximum values. @@ -103,25 +103,25 @@ You can also use the monitor name instead of the index (partial name matching su ```powershell # Get all settings by monitor name -DDCSwitch get "VG270U P" -DDCSwitch get "Generic PnP" +ddcswitch get "VG270U P" +ddcswitch get "Generic PnP" ``` Get a specific feature: ```powershell # Get current input source -DDCSwitch get 0 input +ddcswitch get 0 input # Get brightness as percentage -DDCSwitch get 0 brightness +ddcswitch get 0 brightness # Get contrast as percentage -DDCSwitch get 0 contrast +ddcswitch get 0 contrast # Works with monitor names too -DDCSwitch get "VG270U P" brightness -DDCSwitch get "Generic PnP" input +ddcswitch get "VG270U P" brightness +ddcswitch get "Generic PnP" input ``` Output: `Monitor: Generic PnP Monitor` / `Brightness: 75% (120/160)` @@ -132,20 +132,20 @@ Switch a monitor to a different input: ```powershell # By monitor index -DDCSwitch set 0 HDMI1 +ddcswitch set 0 HDMI1 # By monitor name (partial match) -DDCSwitch set "LG ULTRAGEAR" HDMI2 +ddcswitch set "LG ULTRAGEAR" HDMI2 ``` Set brightness or contrast with percentage values: ```powershell # Set brightness to 75% -DDCSwitch set 0 brightness 75% +ddcswitch set 0 brightness 75% # Set contrast to 80% -DDCSwitch set 0 contrast 80% +ddcswitch set 0 contrast 80% ``` Output: `✓ Successfully set brightness to 75% (120/160)` @@ -156,10 +156,10 @@ For advanced users, access any VCP feature by code: ```powershell # Get raw VCP value (e.g., VCP code 0x10 for brightness) -DDCSwitch get 0 0x10 +ddcswitch get 0 0x10 # Set raw VCP value -DDCSwitch set 0 0x10 120 +ddcswitch set 0 0x10 120 ``` ### VCP Feature Scanning @@ -167,7 +167,7 @@ DDCSwitch set 0 0x10 120 Discover all supported VCP features on all monitors: ```powershell -DDCSwitch get all +ddcswitch get all ``` This scans all VCP codes (0x00-0xFF) for every monitor and displays supported features with their current values, maximum values, and access types (read-only, write-only, read-write). @@ -176,10 +176,10 @@ To scan a specific monitor: ```powershell # Scan specific monitor by index -DDCSwitch get 0 +ddcswitch get 0 # Scan specific monitor by name -DDCSwitch get "VG270U" +ddcswitch get "VG270U" ``` ### VCP Feature Categories and Discovery @@ -188,12 +188,12 @@ Discover and browse VCP features by category: ```powershell # List all available categories -DDCSwitch list --categories +ddcswitch list --categories # List features in a specific category -DDCSwitch list --category image -DDCSwitch list --category color -DDCSwitch list --category audio +ddcswitch list --category image +ddcswitch list --category color +ddcswitch list --category audio ``` Example output: @@ -248,49 +248,49 @@ Color Control Features: **Switch multiple monitors:** ```powershell -DDCSwitch set 0 HDMI1 -DDCSwitch set 1 DP1 +ddcswitch set 0 HDMI1 +ddcswitch set 1 DP1 ``` **Control comprehensive VCP features:** ```powershell -DDCSwitch set 0 brightness 75% -DDCSwitch set 0 contrast 80% -DDCSwitch get 0 brightness +ddcswitch set 0 brightness 75% +ddcswitch set 0 contrast 80% +ddcswitch get 0 brightness # Color controls -DDCSwitch set 0 red-gain 90% -DDCSwitch set 0 green-gain 85% -DDCSwitch set 0 blue-gain 95% +ddcswitch set 0 red-gain 90% +ddcswitch set 0 green-gain 85% +ddcswitch set 0 blue-gain 95% # Audio controls (if supported) -DDCSwitch set 0 volume 50% -DDCSwitch set 0 mute 1 +ddcswitch set 0 volume 50% +ddcswitch set 0 mute 1 ``` **VCP feature discovery:** ```powershell # List all available VCP feature categories -DDCSwitch list --categories +ddcswitch list --categories # List features in a specific category -DDCSwitch list --category color +ddcswitch list --category color # Search for features by name -DDCSwitch get 0 bright # Matches "brightness" +ddcswitch get 0 bright # Matches "brightness" # Or by monitor name -DDCSwitch get "VG270U" bright +ddcswitch get "VG270U" bright ``` **Desktop shortcut:** -Create a shortcut with target: `C:\Path\To\DDCSwitch.exe set 0 brightness 50%` +Create a shortcut with target: `C:\Path\To\ddcswitch.exe set 0 brightness 50%` **AutoHotkey:** ```autohotkey -^!h::Run, DDCSwitch.exe set 0 HDMI1 ; Ctrl+Alt+H for HDMI1 -^!d::Run, DDCSwitch.exe set 0 DP1 ; Ctrl+Alt+D for DisplayPort -^!b::Run, DDCSwitch.exe set 0 brightness 75% ; Ctrl+Alt+B for 75% brightness +^!h::Run, ddcswitch.exe set 0 HDMI1 ; Ctrl+Alt+H for HDMI1 +^!d::Run, ddcswitch.exe set 0 DP1 ; Ctrl+Alt+D for DisplayPort +^!b::Run, ddcswitch.exe set 0 brightness 75% ; Ctrl+Alt+B for 75% brightness ``` ### JSON Output for Automation @@ -299,20 +299,20 @@ All commands support `--json` for machine-readable output: ```powershell # PowerShell: Conditional switching -$result = DDCSwitch get 0 --json | ConvertFrom-Json +$result = ddcswitch get 0 --json | ConvertFrom-Json if ($result.currentInputCode -ne "0x11") { - DDCSwitch set 0 HDMI1 + ddcswitch set 0 HDMI1 } ``` ```python # Python: Switch all monitors import subprocess, json -data = json.loads(subprocess.run(['DDCSwitch', 'list', '--json'], +data = json.loads(subprocess.run(['ddcswitch', 'list', '--json'], capture_output=True, text=True).stdout) for m in data['monitors']: if m['status'] == 'ok': - subprocess.run(['DDCSwitch', 'set', str(m['index']), 'HDMI1']) + subprocess.run(['ddcswitch', 'set', str(m['index']), 'HDMI1']) ``` 📚 **See [EXAMPLES.md](EXAMPLES.md) for comprehensive automation examples** including Stream Deck, Task Scheduler, Python, Node.js, Rust, and more. @@ -345,7 +345,7 @@ If you need to verify DDC/CI values or troubleshoot monitor-specific issues, try ## Technical Details -DDCSwitch uses the Windows DXVA2 API to communicate with monitors via DDC/CI protocol. It reads/writes VCP (Virtual Control Panel) features following the MCCS specification. +ddcswitch uses the Windows DXVA2 API to communicate with monitors via DDC/CI protocol. It reads/writes VCP (Virtual Control Panel) features following the MCCS specification. **Common VCP Codes:** - `0x10` Brightness, `0x12` Contrast, `0x60` Input Source diff --git a/build.cmd b/build.cmd index 0122b57..2b507cc 100644 --- a/build.cmd +++ b/build.cmd @@ -2,7 +2,7 @@ setlocal enabledelayedexpansion echo ======================================== -echo Building DDCSwitch with NativeAOT +echo Building ddcswitch with NativeAOT echo ======================================== echo. @@ -30,7 +30,7 @@ if not exist "dist" mkdir "dist" REM Copy the NativeAOT executable echo Copying executable to dist folder... -copy /Y "DDCSwitch\bin\Release\net10.0\win-x64\publish\DDCSwitch.exe" "dist\DDCSwitch.exe" +copy /Y "DDCSwitch\bin\Release\net10.0\win-x64\publish\ddcswitch.exe" "dist\ddcswitch.exe" if errorlevel 1 ( echo ERROR: Failed to copy executable exit /b 1 @@ -39,11 +39,11 @@ if errorlevel 1 ( echo. echo ======================================== echo Build completed successfully! -echo Output: dist\DDCSwitch.exe +echo Output: dist\ddcswitch.exe echo ======================================== REM Display file size -for %%A in ("dist\DDCSwitch.exe") do ( +for %%A in ("dist\ddcswitch.exe") do ( set size=%%~zA set /a sizeMB=!size! / 1048576 echo File size: !sizeMB! MB From dc319085c95d0bc4ef678c4ed9ad80a712a8dab1 Mon Sep 17 00:00:00 2001 From: Quick <577652+markdwags@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:11:08 -0600 Subject: [PATCH 09/10] normalize casing for 'ddcswitch' in CI/CD configuration; update paths and filenames for consistency --- .github/workflows/ci-cd.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 289b72d..a131780 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -38,7 +38,7 @@ jobs: - name: Verify executable exists shell: pwsh run: | - $exePath = "DDCSwitch/bin/Release/net10.0/win-x64/publish/DDCSwitch.exe" + $exePath = "DDCSwitch/bin/Release/net10.0/win-x64/publish/ddcswitch.exe" if (Test-Path $exePath) { $size = (Get-Item $exePath).Length / 1MB Write-Host "✓ Build successful! Executable size: $([math]::Round($size, 2)) MB" @@ -52,7 +52,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: DDCSwitch-build-${{ github.sha }} - path: DDCSwitch/bin/Release/net10.0/win-x64/publish/DDCSwitch.exe + path: DDCSwitch/bin/Release/net10.0/win-x64/publish/ddcswitch.exe retention-days: 7 release: @@ -92,7 +92,7 @@ jobs: shell: pwsh run: | New-Item -ItemType Directory -Force -Path release - Copy-Item artifact/DDCSwitch.exe release/ + Copy-Item artifact/ddcswitch.exe release/ Copy-Item README.md release/ Copy-Item LICENSE release/ Copy-Item EXAMPLES.md release/ @@ -102,7 +102,7 @@ jobs: shell: pwsh run: | $version = "${{ steps.get_version.outputs.version }}" - Compress-Archive -Path release/* -DestinationPath "DDCSwitch-$version-win-x64.zip" + Compress-Archive -Path release/* -DestinationPath "ddcswitch-$version-win-x64.zip" - name: Generate release notes id: release_notes @@ -150,20 +150,20 @@ jobs: uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.get_version.outputs.tag }} - name: DDCSwitch ${{ steps.get_version.outputs.version }} + name: ddcswitch ${{ steps.get_version.outputs.version }} body: ${{ steps.release_notes.outputs.notes }} draft: false prerelease: false files: | - DDCSwitch-${{ steps.get_version.outputs.version }}-win-x64.zip - release/DDCSwitch.exe + ddcswitch-${{ steps.get_version.outputs.version }}-win-x64.zip + release/ddcswitch.exe env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload build artifacts uses: actions/upload-artifact@v4 with: - name: DDCSwitch-${{ steps.get_version.outputs.version }}-win-x64 + name: ddcswitch-${{ steps.get_version.outputs.version }}-win-x64 path: release/ retention-days: 30 From e8e41427949d506bf7abb2dcc06eadfbbef6d357 Mon Sep 17 00:00:00 2001 From: Quick <577652+markdwags@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:47:09 -0600 Subject: [PATCH 10/10] Add Chocolatey package installation and uninstallation scripts; update CI/CD configuration for package creation and checksum calculation --- .github/workflows/ci-cd.yml | 120 +++++++++++++++++++++++ DDCSwitch/Commands/CommandRouter.cs | 2 +- chocolatey/ddcswitch.nuspec | 48 +++++++++ chocolatey/tools/CHECKSUM | 1 + chocolatey/tools/VERIFICATION.txt | 22 +++++ chocolatey/tools/chocolateyinstall.ps1 | 28 ++++++ chocolatey/tools/chocolateyuninstall.ps1 | 10 ++ 7 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 chocolatey/ddcswitch.nuspec create mode 100644 chocolatey/tools/CHECKSUM create mode 100644 chocolatey/tools/VERIFICATION.txt create mode 100644 chocolatey/tools/chocolateyinstall.ps1 create mode 100644 chocolatey/tools/chocolateyuninstall.ps1 diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index a131780..dcce49a 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -167,3 +167,123 @@ jobs: path: release/ retention-days: 30 + chocolatey-package: + needs: release + runs-on: windows-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get version from changelog + id: get_version + shell: pwsh + run: | + $changelog = Get-Content CHANGELOG.md -Raw + if ($changelog -match '\[(\d+\.\d+\.\d+)\]') { + $version = $matches[1] + echo "version=$version" >> $env:GITHUB_OUTPUT + echo "Found version: $version" + } else { + echo "Error: Could not find version in CHANGELOG.md" + exit 1 + } + + - name: Calculate checksum + id: checksum + shell: pwsh + run: | + $version = "${{ steps.get_version.outputs.version }}" + $url = "https://github.com/markdwags/ddcswitch/releases/download/v$version/ddcswitch-$version-win-x64.zip" + $tempFile = "$env:TEMP\ddcswitch-$version.zip" + + Write-Host "Downloading ZIP from: $url" + Start-Sleep -Seconds 2 # Give GitHub a moment to make the release available + + # Retry logic for download + $maxRetries = 5 + $retryCount = 0 + $downloaded = $false + + while (-not $downloaded -and $retryCount -lt $maxRetries) { + try { + Invoke-WebRequest -Uri $url -OutFile $tempFile -ErrorAction Stop + $downloaded = $true + } catch { + $retryCount++ + Write-Host "Download attempt $retryCount failed. Retrying in 10 seconds..." + Start-Sleep -Seconds 10 + } + } + + if (-not $downloaded) { + Write-Host "Failed to download after $maxRetries attempts" + exit 1 + } + + $hash = Get-FileHash $tempFile -Algorithm SHA256 + $checksum = $hash.Hash + echo "checksum=$checksum" >> $env:GITHUB_OUTPUT + Write-Host "SHA256 Checksum: $checksum" + Remove-Item $tempFile + + - name: Update Chocolatey files with version and checksum + shell: pwsh + run: | + $version = "${{ steps.get_version.outputs.version }}" + $checksum = "${{ steps.checksum.outputs.checksum }}" + + # Update version in nuspec (Chocolatey will pass to scripts via $env:chocolateyPackageVersion) + (Get-Content chocolatey\ddcswitch.nuspec -Raw) -replace '__VERSION__', $version | Set-Content chocolatey\ddcswitch.nuspec -NoNewline + + # Create CHECKSUM file for install script to read + Set-Content chocolatey\tools\CHECKSUM $checksum -NoNewline + + # Update VERIFICATION.txt for moderators + (Get-Content chocolatey\tools\VERIFICATION.txt -Raw) -replace '__VERSION__', $version -replace '__CHECKSUM__', $checksum | Set-Content chocolatey\tools\VERIFICATION.txt -NoNewline + + Write-Host "✓ Updated Chocolatey files" + Write-Host " Version: $version (in nuspec, passed via env to scripts)" + Write-Host " Checksum: $checksum (in CHECKSUM file)" + + - name: Install Chocolatey + shell: pwsh + run: | + if (-not (Get-Command choco -ErrorAction SilentlyContinue)) { + Write-Host "Installing Chocolatey..." + Set-ExecutionPolicy Bypass -Scope Process -Force + [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 + iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) + } else { + Write-Host "Chocolatey is already installed" + } + + - name: Create Chocolatey package + shell: pwsh + run: | + cd chocolatey + choco pack + $version = "${{ steps.get_version.outputs.version }}" + if (Test-Path "ddcswitch.$version.nupkg") { + Write-Host "✓ Successfully created ddcswitch.$version.nupkg" + } else { + Write-Host "✗ Failed to create package" + exit 1 + } + + - name: Upload Chocolatey package + uses: actions/upload-artifact@v4 + with: + name: chocolatey-package-${{ steps.get_version.outputs.version }} + path: chocolatey/*.nupkg + retention-days: 90 + + - name: Upload to GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.get_version.outputs.version }} + files: chocolatey/*.nupkg + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/DDCSwitch/Commands/CommandRouter.cs b/DDCSwitch/Commands/CommandRouter.cs index ff61316..e9761c3 100644 --- a/DDCSwitch/Commands/CommandRouter.cs +++ b/DDCSwitch/Commands/CommandRouter.cs @@ -68,7 +68,7 @@ private static int InvalidCommand(string command, bool jsonOutput) else { ConsoleOutputFormatter.WriteError($"Unknown command: {command}"); - ConsoleOutputFormatter.WriteInfo("Run DDCSwitch help for usage information."); + ConsoleOutputFormatter.WriteInfo("Run ddcswitch help for usage information."); } return 1; diff --git a/chocolatey/ddcswitch.nuspec b/chocolatey/ddcswitch.nuspec new file mode 100644 index 0000000..83e762a --- /dev/null +++ b/chocolatey/ddcswitch.nuspec @@ -0,0 +1,48 @@ + + + + ddcswitch + __VERSION__ + https://github.com/markdwags/DDCSwitch + markdwags + DDCSwitch + markdwags + https://github.com/markdwags/DDCSwitch + https://cdn.jsdelivr.net/gh/markdwags/DDCSwitch@main/icon.png + https://github.com/markdwags/DDCSwitch/blob/main/LICENSE + false + https://github.com/markdwags/DDCSwitch + https://github.com/markdwags/DDCSwitch#readme + https://github.com/markdwags/DDCSwitch/issues + ddcswitch ddc-ci monitor display cli input-switching brightness contrast vcp-features admin + Control monitor settings via DDC/CI from the command line + + https://github.com/markdwags/DDCSwitch/blob/main/CHANGELOG.md + + + + + diff --git a/chocolatey/tools/CHECKSUM b/chocolatey/tools/CHECKSUM new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/chocolatey/tools/CHECKSUM @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/chocolatey/tools/VERIFICATION.txt b/chocolatey/tools/VERIFICATION.txt new file mode 100644 index 0000000..c7fd512 --- /dev/null +++ b/chocolatey/tools/VERIFICATION.txt @@ -0,0 +1,22 @@ +VERIFICATION + +Verification is intended to assist the Chocolatey moderators and community +in verifying that this package's contents are trustworthy. + +Package can be verified like this: + +1. Download the following: + + x64: https://github.com/markdwags/DDCSwitch/releases/download/v__VERSION__/ddcswitch-__VERSION__-win-x64.zip + +2. You can use one of the following methods to obtain the SHA256 checksum: + - Use powershell function 'Get-FileHash' + - Use Chocolatey utility 'checksum.exe' + + checksum64: __CHECKSUM__ + +The binary is downloaded directly from the official GitHub releases. + +Project source: https://github.com/markdwags/ddcswitch +Releases: https://github.com/markdwags/ddcswitch/releases + diff --git a/chocolatey/tools/chocolateyinstall.ps1 b/chocolatey/tools/chocolateyinstall.ps1 new file mode 100644 index 0000000..4ed9379 --- /dev/null +++ b/chocolatey/tools/chocolateyinstall.ps1 @@ -0,0 +1,28 @@ +$ErrorActionPreference = 'Stop' + +$packageName = 'ddcswitch' +$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" + +# Version is automatically provided by Chocolatey from the nuspec +$version = $env:chocolateyPackageVersion +$url64 = "https://github.com/markdwags/ddcswitch/releases/download/v$version/ddcswitch-$version-win-x64.zip" + +# Checksum is stored in a separate file created during package build +$checksumFile = Join-Path $toolsDir "CHECKSUM" +$checksum64 = Get-Content $checksumFile -Raw +$checksum64 = $checksum64.Trim() +$checksumType64 = 'sha256' + +$packageArgs = @{ + packageName = $packageName + unzipLocation = $toolsDir + url64bit = $url64 + checksum64 = $checksum64 + checksumType64 = $checksumType64 +} + +Install-ChocolateyZipPackage @packageArgs + +# The ZIP contains the executable at the root +# Chocolatey will automatically create a shim for ddcswitch.exe + diff --git a/chocolatey/tools/chocolateyuninstall.ps1 b/chocolatey/tools/chocolateyuninstall.ps1 new file mode 100644 index 0000000..9abf76a --- /dev/null +++ b/chocolatey/tools/chocolateyuninstall.ps1 @@ -0,0 +1,10 @@ +$ErrorActionPreference = 'Stop' + +$packageName = 'ddcswitch' +$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" + +# Remove the executable (Chocolatey handles shim removal automatically) +Remove-Item "$toolsDir\ddcswitch.exe" -ErrorAction SilentlyContinue -Force + +Write-Host "$packageName has been uninstalled successfully." +