4 Commits

Author SHA1 Message Date
139be6f779 Add debug logging for dropdown selection and power unsupported state
All checks were successful
Build / build (push) Successful in 8h0m11s
Build and Release / build (push) Successful in 8h0m12s
- Added detailed logging in CreateInputRow to help diagnose dropdown
  selection issues after config label changes
- Power button now shows "Power Unsupported" and is disabled for
  monitors that don't support DDC/CI power control

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 12:24:55 -05:00
dfec8c07b5 Show 'Unsupported' in config dialog for monitors without input options
All checks were successful
Build / build (push) Successful in 8h0m10s
Build and Release / build (push) Successful in 9h0m14s
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 12:14:28 -05:00
bd7a58a5fe Fix retry loop - don't retry on timeout
All checks were successful
Build / build (push) Successful in 9h0m11s
Build and Release / build (push) Successful in 8h0m13s
- Fixed buggy recursive retry logic
- Reduced max retries from 5 to 2
- Don't retry if timeout occurred (empty result)
- Monitor is unresponsive, retrying just wastes time

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 12:01:47 -05:00
af8b09cfa2 Fix DDC/CI timeout using Task.WhenAny
All checks were successful
Build / build (push) Successful in 9h0m11s
Build and Release / build (push) Successful in 8h0m12s
Previous timeout using CancellationToken didn't work because
ReadToEndAsync blocks waiting for process output. Now using
Task.WhenAny with Task.Delay to properly timeout and kill
hung processes after 5 seconds.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 11:57:13 -05:00
5 changed files with 87 additions and 45 deletions

View File

@@ -34,6 +34,22 @@ public partial class ConfigWindow : Window
spPorts.Children.Clear();
_portRows.Clear();
// Show unsupported message if no ports available
if (_availablePorts == null || _availablePorts.Count == 0)
{
spPorts.Children.Add(new TextBlock
{
Text = "Unsupported",
Foreground = Brushes.Gray,
FontSize = 16,
FontStyle = FontStyles.Italic,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(0, 40, 0, 40)
});
return;
}
foreach (var port in _availablePorts)
{
var existingPortConfig = config.Ports.FirstOrDefault(p => p.VcpValue == port.Value);

View File

@@ -13,9 +13,9 @@
</PropertyGroup>
<PropertyGroup>
<Version>1.0.13</Version>
<AssemblyVersion>1.0.13.0</AssemblyVersion>
<FileVersion>1.0.13.0</FileVersion>
<Version>1.0.17</Version>
<AssemblyVersion>1.0.17.0</AssemblyVersion>
<FileVersion>1.0.17.0</FileVersion>
</PropertyGroup>
<ItemGroup>

View File

@@ -371,6 +371,10 @@ public partial class MainWindow : Window
private StackPanel CreateInputRow(int? currentInput, List<InputSourceOption> options, string serialNumber)
{
DebugLogger.Log($" CreateInputRow: currentInput={currentInput}, optionCount={options.Count}");
foreach (var opt in options)
DebugLogger.Log($" Option: Value={opt.Value}, Name={opt.Name}");
var row = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 3) };
row.Children.Add(new TextBlock { Text = "Input", Foreground = Brushes.LightGray, Width = 70, FontSize = 12, VerticalAlignment = VerticalAlignment.Center });
@@ -390,9 +394,14 @@ public partial class MainWindow : Window
if (currentInput.HasValue)
{
var index = options.FindIndex(o => o.Value == currentInput.Value);
DebugLogger.Log($" Selection: Looking for Value={currentInput.Value}, found at index={index}");
if (index >= 0)
combo.SelectedIndex = index;
}
else
{
DebugLogger.Log($" Selection: currentInput is null, no selection");
}
// Add event handler AFTER setting the initial selection
combo.SelectionChanged += async (s, e) =>
@@ -410,26 +419,33 @@ public partial class MainWindow : Window
var row = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 3) };
row.Children.Add(new TextBlock { Text = "Power", Foreground = Brushes.LightGray, Width = 70, FontSize = 12, VerticalAlignment = VerticalAlignment.Center });
var isUnsupported = string.IsNullOrEmpty(status) || status == "Unknown";
var btn = new Button
{
Content = status,
Content = isUnsupported ? "Power Unsupported" : status,
Width = 170,
Tag = serialNumber,
Style = (Style)FindResource("DarkButton")
Style = (Style)FindResource("DarkButton"),
IsEnabled = !isUnsupported
};
btn.Click += async (s, e) =>
if (!isUnsupported)
{
if (s is Button b && b.Tag is string sn)
btn.Click += async (s, e) =>
{
var current = await CMMCommand.GetMonPowerStatus(sn);
if (current == "Sleep" || current == "PowerOff")
await CMMCommand.PowerOn(sn);
else
await CMMCommand.Sleep(sn);
await Task.Delay(1000);
b.Content = await CMMCommand.GetMonPowerStatus(sn);
}
};
if (s is Button b && b.Tag is string sn)
{
var current = await CMMCommand.GetMonPowerStatus(sn);
if (current == "Sleep" || current == "PowerOff")
await CMMCommand.PowerOn(sn);
else
await CMMCommand.Sleep(sn);
await Task.Delay(1000);
b.Content = await CMMCommand.GetMonPowerStatus(sn);
}
};
}
row.Children.Add(btn);
return row;

View File

@@ -28,18 +28,17 @@ internal class ConsoleHelper
p.StartInfo.FileName = command;
p.Start();
using var cts = new CancellationTokenSource(timeoutMs);
try
var readTask = p.StandardOutput.ReadToEndAsync();
var completedTask = await Task.WhenAny(readTask, Task.Delay(timeoutMs));
if (completedTask != readTask)
{
var output = await p.StandardOutput.ReadToEndAsync(cts.Token);
await p.WaitForExitAsync(cts.Token);
return output;
}
catch (OperationCanceledException)
{
try { p.Kill(); } catch { }
// Timeout - kill the process
try { p.Kill(true); } catch { }
return string.Empty;
}
return await readTask;
}
public static async Task<string> CmdCommandAsync(params string[] cmds) =>
@@ -63,23 +62,28 @@ internal class ConsoleHelper
}
p.StandardInput.WriteLine("exit");
using var cts = new CancellationTokenSource(timeoutMs);
try
var readTask = Task.Run(async () =>
{
var result = await p.StandardOutput.ReadToEndAsync(cts.Token);
var error = await p.StandardError.ReadToEndAsync(cts.Token);
var result = await p.StandardOutput.ReadToEndAsync();
var error = await p.StandardError.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(error))
result = result + "\r\n<Error Message>:\r\n" + error;
await p.WaitForExitAsync(cts.Token);
p.Close();
Debug.WriteLine(result);
return result;
}
catch (OperationCanceledException)
});
var completedTask = await Task.WhenAny(readTask, Task.Delay(timeoutMs));
if (completedTask != readTask)
{
try { p.Kill(); } catch { }
// Timeout - kill the process
try { p.Kill(true); } catch { }
return string.Empty;
}
var output = await readTask;
p.Close();
Debug.WriteLine(output);
return output;
}
public static string Command(string fileName, params string[] cmds)

View File

@@ -30,26 +30,32 @@ public static class CMMCommand
return ConsoleHelper.CmdCommandAsync($"{CMMexe} /SetValue {monitorSN} D6 4");
}
private static async Task<string> GetMonitorValue(string monitorSN, string vcpCode = "D6", int? reTry = 0)
private static async Task<string> GetMonitorValue(string monitorSN, string vcpCode = "D6", int maxRetries = 2)
{
var value = string.Empty;
while (reTry <= 5)
for (int attempt = 0; attempt <= maxRetries; attempt++)
{
var cmdFileName = Path.Combine(CMMTmpFolder, $"{Guid.NewGuid()}.bat");
var cmd = $"{CMMexe} /GetValue {monitorSN} {vcpCode}\r\n" +
$"echo %errorlevel%";
File.WriteAllText(cmdFileName, cmd);
var values = await ConsoleHelper.ExecuteCommand(cmdFileName);
File.Delete(cmdFileName);
try { File.Delete(cmdFileName); } catch { }
value = values.Split("\r\n", StringSplitOptions.RemoveEmptyEntries).LastOrDefault();
// Empty result means timeout - don't retry, monitor is unresponsive
if (string.IsNullOrEmpty(values))
return string.Empty;
if (!string.IsNullOrEmpty(value) && value != "0") return value;
await Task.Delay(500);
await GetMonitorValue(monitorSN, vcpCode, reTry++);
};
var value = values.Split("\r\n", StringSplitOptions.RemoveEmptyEntries).LastOrDefault();
return value;
if (!string.IsNullOrEmpty(value) && value != "0")
return value;
// Only retry on non-timeout failures
if (attempt < maxRetries)
await Task.Delay(300);
}
return string.Empty;
}
#region Brightness (VCP Code 10)