Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

All notable changes to ddcswitch will be documented in this file.

## [1.0.3] - 2026-01-08

### Added
- Added the toggle command to flip a monitor between two input sources
- `info` command to display detailed EDID (Extended Display Identification Data) information
- Full JSON output support for programmatic access

## [1.0.2] - 2026-01-07

### Added
Expand Down
29 changes: 28 additions & 1 deletion DDCSwitch/Commands/CommandRouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,39 @@ public static int Route(string[] args)
"list" or "ls" => ListCommand.Execute(jsonOutput, verboseOutput),
"get" => GetCommand.Execute(filteredArgs, jsonOutput),
"set" => SetCommand.Execute(filteredArgs, jsonOutput),
"toggle" => ToggleCommand.Execute(filteredArgs, jsonOutput),
"info" => InfoCommand.Execute(filteredArgs, jsonOutput),
"version" or "-v" or "--version" => HelpCommand.ShowVersion(jsonOutput),
"help" or "-h" or "--help" or "/?" => HelpCommand.ShowUsage(),
_ => InvalidCommand(filteredArgs[0], jsonOutput)
};
}
catch (ArgumentException 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;
}
catch (InvalidOperationException 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;
}
catch (Exception ex)
{
if (jsonOutput)
Expand All @@ -53,7 +81,6 @@ public static int Route(string[] args)
{
ConsoleOutputFormatter.WriteError(ex.Message);
}

return 1;
}
}
Expand Down
182 changes: 182 additions & 0 deletions DDCSwitch/Commands/ConsoleOutputFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,187 @@ public static void WriteMonitorInfo(string label, string value, bool highlight =
var color = highlight ? "yellow" : "cyan";
AnsiConsole.MarkupLine($" [bold {color}]{label}:[/] {value}");
}

public static void WriteMonitorDetails(Monitor monitor)
{
// Header with monitor identification
var headerPanel = new Panel(
$"[bold white]{monitor.Name}[/]\n" +
$"[dim]Device:[/] [cyan]{monitor.DeviceName}[/] " +
$"[dim]Primary:[/] {(monitor.IsPrimary ? "[green]Yes[/]" : "[dim]No[/]")}")
{
Header = new PanelHeader($"[bold cyan]Monitor {monitor.Index}[/]", Justify.Left),
Border = BoxBorder.Rounded,
BorderStyle = new Style(Color.Cyan1)
};
AnsiConsole.Write(headerPanel);
AnsiConsole.WriteLine();

// EDID Information in a table
var edidTable = new Table()
.Border(TableBorder.Rounded)
.BorderColor(Color.Grey)
.AddColumn(new TableColumn("[bold yellow]Property[/]").Width(20))
.AddColumn(new TableColumn("[bold yellow]Value[/]"));

if (monitor.EdidVersion != null)
edidTable.AddRow("[cyan]EDID Version[/]", $"[white]{monitor.EdidVersion}[/]");

if (monitor.ManufacturerName != null)
edidTable.AddRow("[cyan]Manufacturer[/]", $"[white]{monitor.ManufacturerName}[/] [dim]({monitor.ManufacturerId})[/]");
else if (monitor.ManufacturerId != null)
edidTable.AddRow("[cyan]Manufacturer ID[/]", $"[white]{monitor.ManufacturerId}[/]");

if (monitor.ModelName != null)
edidTable.AddRow("[cyan]Model Name[/]", $"[white]{monitor.ModelName}[/]");

if (monitor.SerialNumber != null)
edidTable.AddRow("[cyan]Serial Number[/]", $"[white]{monitor.SerialNumber}[/]");

if (monitor.ProductCode.HasValue)
edidTable.AddRow("[cyan]Product Code[/]", $"[white]0x{monitor.ProductCode.Value:X4}[/]");

if (monitor.ManufactureYear.HasValue)
{
var date = monitor.ManufactureWeek.HasValue
? FormatManufactureDate(monitor.ManufactureYear.Value, monitor.ManufactureWeek.Value)
: $"{monitor.ManufactureYear.Value}";
edidTable.AddRow("[cyan]Manufacture Date[/]", $"[white]{date}[/]");
}

if (monitor.VideoInputDefinition != null)
{
var inputColor = monitor.VideoInputDefinition.IsDigital ? "green" : "yellow";
edidTable.AddRow("[cyan]Video Input[/]", $"[{inputColor}]{monitor.VideoInputDefinition}[/]");
}

var edidPanel = new Panel(edidTable)
{
Header = new PanelHeader("[bold yellow]📋 EDID Information[/]", Justify.Left),
Border = BoxBorder.Rounded,
BorderStyle = new Style(Color.Yellow)
};
AnsiConsole.Write(edidPanel);
AnsiConsole.WriteLine();

// Supported Features
if (monitor.SupportedFeatures != null)
{
var featuresGrid = new Grid();
featuresGrid.AddColumn(new GridColumn().Width(25));
featuresGrid.AddColumn(new GridColumn());

featuresGrid.AddRow(
"[cyan]Display Type:[/]",
$"[white]{monitor.SupportedFeatures.DisplayTypeDescription}[/]");

featuresGrid.AddRow(
"[cyan]DPMS Standby:[/]",
FormatFeatureSupport(monitor.SupportedFeatures.DpmsStandby));

featuresGrid.AddRow(
"[cyan]DPMS Suspend:[/]",
FormatFeatureSupport(monitor.SupportedFeatures.DpmsSuspend));

featuresGrid.AddRow(
"[cyan]DPMS Active-Off:[/]",
FormatFeatureSupport(monitor.SupportedFeatures.DpmsActiveOff));

featuresGrid.AddRow(
"[cyan]Default Color Space:[/]",
monitor.SupportedFeatures.DefaultColorSpace ? "[green]Standard[/]" : "[dim]Non-standard[/]");

featuresGrid.AddRow(
"[cyan]Preferred Timing:[/]",
monitor.SupportedFeatures.PreferredTimingMode ? "[green]✓ Included[/]" : "[dim]Not included[/]");

featuresGrid.AddRow(
"[cyan]Continuous Frequency:[/]",
FormatFeatureSupport(monitor.SupportedFeatures.ContinuousFrequency));

var featuresPanel = new Panel(featuresGrid)
{
Header = new PanelHeader("[bold magenta]⚡ Supported Features[/]", Justify.Left),
Border = BoxBorder.Rounded,
BorderStyle = new Style(Color.Magenta)
};
AnsiConsole.Write(featuresPanel);
AnsiConsole.WriteLine();
}

// Chromaticity Coordinates
if (monitor.Chromaticity != null)
{
var chromaTable = new Table()
.Border(TableBorder.Rounded)
.BorderColor(Color.Grey)
.AddColumn(new TableColumn("[bold]Color[/]").Centered().Width(12))
.AddColumn(new TableColumn("[bold]X[/]").Centered().Width(12))
.AddColumn(new TableColumn("[bold]Y[/]").Centered().Width(12));

chromaTable.AddRow(
"[red]● Red[/]",
$"[white]{monitor.Chromaticity.Red.X:F4}[/]",
$"[white]{monitor.Chromaticity.Red.Y:F4}[/]");

chromaTable.AddRow(
"[green]● Green[/]",
$"[white]{monitor.Chromaticity.Green.X:F4}[/]",
$"[white]{monitor.Chromaticity.Green.Y:F4}[/]");

chromaTable.AddRow(
"[blue]● Blue[/]",
$"[white]{monitor.Chromaticity.Blue.X:F4}[/]",
$"[white]{monitor.Chromaticity.Blue.Y:F4}[/]");

chromaTable.AddRow(
"[grey]● White[/]",
$"[white]{monitor.Chromaticity.White.X:F4}[/]",
$"[white]{monitor.Chromaticity.White.Y:F4}[/]");

var chromaPanel = new Panel(chromaTable)
{
Header = new PanelHeader("[bold blue]🎨 Chromaticity Coordinates (CIE 1931)[/]", Justify.Left),
Border = BoxBorder.Rounded,
BorderStyle = new Style(Color.Blue)
};
AnsiConsole.Write(chromaPanel);
AnsiConsole.WriteLine();
}
}

private static string FormatFeatureSupport(bool supported)
{
return supported ? "[green]✓ Supported[/]" : "[dim]✗ Not supported[/]";
}

private static string FormatManufactureDate(int year, int week)
{
// Calculate approximate month from week number
// Week 1-4: January, Week 5-8: February, etc.
string? monthName = week switch
{
>= 1 and <= 4 => "January",
>= 5 and <= 8 => "February",
>= 9 and <= 13 => "March",
>= 14 and <= 17 => "April",
>= 18 and <= 22 => "May",
>= 23 and <= 26 => "June",
>= 27 and <= 30 => "July",
>= 31 and <= 35 => "August",
>= 36 and <= 39 => "September",
>= 40 and <= 43 => "October",
>= 44 and <= 48 => "November",
>= 49 and <= 53 => "December",
_ => null
};
Comment on lines +197 to +214
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The week-to-month conversion is inaccurate. This logic assumes 4 weeks per month, but months have different numbers of weeks. For example, week 13 would be in late March, not the end of March as implied by "March". ISO 8601 week dates don't align with calendar months. Consider either removing the month approximation and just showing the week number, or using a more accurate calculation based on the actual calendar.

Copilot uses AI. Check for mistakes.

if (monthName != null)
{
return $"{monthName} {year} (Week {week})";
}

return $"{year} Week {week}";
}
}

10 changes: 10 additions & 0 deletions DDCSwitch/Commands/HelpCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ public static int ShowUsage()
commandsTable.AddRow(
"[cyan]set[/] [green]<monitor>[/] [blue]<feature>[/] [magenta]<value>[/]",
"Set value for a monitor feature");
commandsTable.AddRow(
"[cyan]toggle[/] [green]<monitor>[/] [blue]<input1>[/] [blue]<input2>[/]",
"Toggle between two input sources automatically");
commandsTable.AddRow(
"[cyan]info[/] [green]<monitor>[/]",
"Show detailed EDID information for a specific monitor");
commandsTable.AddRow(
"[cyan]version[/] [dim]or[/] [cyan]-v[/]",
"Display version information");
Expand Down Expand Up @@ -90,6 +96,10 @@ public static int ShowUsage()
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 toggle 0 HDMI1 DP1");
AnsiConsole.MarkupLine(" [grey]$[/] ddcswitch toggle \"Dell Monitor\" HDMI1 HDMI2");
AnsiConsole.MarkupLine(" [grey]$[/] ddcswitch info 0");
AnsiConsole.MarkupLine(" [grey]$[/] ddcswitch info 0 --json");

return 0;
}
Expand Down
Loading