diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index abf4b57..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(dir \"C:\\\\Users\\\\logik\\\\source\\\\repos\\\\ControlMyMonitorManagement\\\\Language\")", - "Bash(find:*)", - "Bash(dir \"C:\\\\Users\\\\logik\\\\source\\\\repos\\\\ControlMyMonitorManagement\\\\Language\" /S)", - "Bash(cat:*)", - "Bash(dotnet build:*)", - "Bash(dotnet --list-sdks:*)", - "Bash(dotnet:*)", - "Bash(timeout /t 5 /nobreak)", - "Bash(taskkill:*)", - "Bash(start \"\" \"C:\\\\Users\\\\logik\\\\source\\\\repos\\\\ControlMyMonitorManagement\\\\DellMonitorControl\\\\bin\\\\Debug\\\\net9.0-windows\\\\DellMonitorControl.exe\")", - "Bash(ping:*)", - "Bash(git remote:*)" - ] - } -} diff --git a/.gitignore b/.gitignore index 9491a2f..8f66922 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,10 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd + +# Claude Code +.claude/ + +# Windows special files +nul \ No newline at end of file diff --git a/DellMonitorControl/App.xaml.cs b/DellMonitorControl/App.xaml.cs index b5cad1b..7fcae20 100644 --- a/DellMonitorControl/App.xaml.cs +++ b/DellMonitorControl/App.xaml.cs @@ -14,19 +14,20 @@ namespace DellMonitorControl _trayIcon.TrayLeftMouseUp += TrayIcon_Click; _mainWindow = new MainWindow(); - _mainWindow.Show(); } - private void TrayIcon_Click(object sender, RoutedEventArgs e) + private async void TrayIcon_Click(object sender, RoutedEventArgs e) { if (_mainWindow == null) return; if (_mainWindow.IsVisible) + { _mainWindow.Hide(); + } else { - _mainWindow.Show(); - _mainWindow.Activate(); + _mainWindow.ShowNearTray(); + await _mainWindow.LoadMonitors(); } } diff --git a/DellMonitorControl/MainWindow.xaml b/DellMonitorControl/MainWindow.xaml index ceda82f..7e69143 100644 --- a/DellMonitorControl/MainWindow.xaml +++ b/DellMonitorControl/MainWindow.xaml @@ -1,15 +1,40 @@  + Title="Monitor Control" + Width="300" MinHeight="200" MaxHeight="500" + SizeToContent="Height" + WindowStyle="None" + AllowsTransparency="True" + Background="Transparent" + ResizeMode="NoResize" + ShowInTaskbar="False" + Topmost="True" + Deactivated="Window_Deactivated"> - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DellMonitorControl/MainWindow.xaml.cs b/DellMonitorControl/MainWindow.xaml.cs index 44ff5b4..91d87f5 100644 --- a/DellMonitorControl/MainWindow.xaml.cs +++ b/DellMonitorControl/MainWindow.xaml.cs @@ -15,70 +15,67 @@ public partial class MainWindow : Window public MainWindow() { InitializeComponent(); - PositionWindowNearTray(); - Loaded += async (s, e) => await LoadMonitors(); } - private void PositionWindowNearTray() + public void ShowNearTray() { var workArea = SystemParameters.WorkArea; Left = workArea.Right - Width - 10; - Top = workArea.Bottom - Height - 10; + // Use estimated height since ActualHeight is 0 before render + Top = workArea.Bottom - 350; + Show(); + Activate(); + + // Reposition after layout is complete + Dispatcher.BeginInvoke(new Action(() => + { + Top = workArea.Bottom - ActualHeight - 10; + }), System.Windows.Threading.DispatcherPriority.Loaded); } - private static readonly string LogFile = System.IO.Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "MonitorControl.log"); - - private static void Log(string msg) + private void Window_Deactivated(object sender, EventArgs e) { - try { System.IO.File.AppendAllText(LogFile, $"{DateTime.Now:HH:mm:ss.fff} {msg}\n"); } - catch { } + Hide(); } - private async Task LoadMonitors() + public async Task LoadMonitors() { var newChildren = new List(); - Log("LoadMonitors started"); try { - Log("Calling ScanMonitor..."); await CMMCommand.ScanMonitor(); - Log("ScanMonitor complete"); - - Log("Calling ReadMonitorsData..."); var monitors = (await CMMCommand.ReadMonitorsData()).ToList(); - Log($"ReadMonitorsData complete, found {monitors.Count} monitors"); if (!monitors.Any()) { - Log("No monitors found"); - newChildren.Add(new TextBlock { Text = "No DDC/CI monitors detected", Foreground = Brushes.White, FontSize = 14 }); + newChildren.Add(new TextBlock + { + Text = "No DDC/CI monitors detected", + Foreground = Brushes.LightGray, + FontSize = 12, + HorizontalAlignment = HorizontalAlignment.Center, + Margin = new Thickness(0, 30, 0, 30) + }); } else { foreach (var m in monitors) { - Log($"Processing monitor: {m.MonitorName} ({m.SerialNumber})"); - var brightness = await CMMCommand.GetBrightness(m.SerialNumber) ?? 50; - Log($" Brightness: {brightness}"); var contrast = await CMMCommand.GetContrast(m.SerialNumber) ?? 50; - Log($" Contrast: {contrast}"); var inputSource = await CMMCommand.GetInputSource(m.SerialNumber); - Log($" InputSource: {inputSource}"); var inputOptions = await CMMCommand.GetInputSourceOptions(m.SerialNumber); - Log($" InputOptions count: {inputOptions.Count}"); var powerStatus = await CMMCommand.GetMonPowerStatus(m.SerialNumber) ?? "Unknown"; - Log($" PowerStatus: {powerStatus}"); + // Monitor name header newChildren.Add(new TextBlock { Text = m.MonitorName, Foreground = Brushes.White, - FontSize = 14, - FontWeight = FontWeights.Bold, - Margin = new Thickness(0, 10, 0, 5) + FontSize = 13, + FontWeight = FontWeights.SemiBold, + Margin = new Thickness(0, 8, 0, 6) }); newChildren.Add(CreateSliderRow("Brightness", brightness, m.SerialNumber, CMMCommand.SetBrightness)); @@ -88,22 +85,33 @@ public partial class MainWindow : Window newChildren.Add(CreateInputRow(inputSource, inputOptions, m.SerialNumber)); newChildren.Add(CreatePowerRow(powerStatus, m.SerialNumber)); + + // Separator + newChildren.Add(new Border { Height = 1, Background = new SolidColorBrush(Color.FromRgb(80, 80, 80)), Margin = new Thickness(0, 10, 0, 2) }); } + + // Remove last separator + if (newChildren.Count > 0 && newChildren.Last() is Border) + newChildren.RemoveAt(newChildren.Count - 1); } - Log($"Built {newChildren.Count} UI elements"); } catch (Exception ex) { - Log($"ERROR: {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}"); newChildren.Clear(); - newChildren.Add(new TextBlock { Text = $"Error: {ex.Message}", Foreground = Brushes.Red, FontSize = 12, TextWrapping = TextWrapping.Wrap }); + newChildren.Add(new TextBlock { Text = $"Error: {ex.Message}", Foreground = Brushes.OrangeRed, FontSize = 11, TextWrapping = TextWrapping.Wrap }); } - Log("Updating UI..."); + sp.VerticalAlignment = VerticalAlignment.Top; sp.Children.Clear(); foreach (var child in newChildren) sp.Children.Add(child); - Log($"UI updated with {sp.Children.Count} children"); + + // Reposition after content changes + Dispatcher.BeginInvoke(new Action(() => + { + var workArea = SystemParameters.WorkArea; + Top = workArea.Bottom - ActualHeight - 10; + }), System.Windows.Threading.DispatcherPriority.Loaded); } private StackPanel CreateSliderRow(string label, int value, string serialNumber, Func setCommand) diff --git a/Library/Method/CMMCommand.cs b/Library/Method/CMMCommand.cs index 2c94ac3..7ebcdee 100644 --- a/Library/Method/CMMCommand.cs +++ b/Library/Method/CMMCommand.cs @@ -220,9 +220,6 @@ public static class CMMCommand } } - private static readonly string DebugLog = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "MonitorParse.log"); - /// /// 取得螢幕清單 /// @@ -232,27 +229,19 @@ public static class CMMCommand if (!File.Exists(CMMsMonitors)) return monitors; - // Try reading raw bytes to understand encoding + // Read with UTF-16 LE encoding (ControlMyMonitor outputs UTF-16) var rawBytes = await File.ReadAllBytesAsync(CMMsMonitors); - File.WriteAllText(DebugLog, $"File size: {rawBytes.Length} bytes\nFirst 20 bytes: {BitConverter.ToString(rawBytes.Take(20).ToArray())}\n\n"); - - // Try UTF-16 LE (common Windows Unicode) var content = System.Text.Encoding.Unicode.GetString(rawBytes); - File.AppendAllText(DebugLog, $"Content preview:\n{content.Substring(0, Math.Min(500, content.Length))}\n\n"); XMonitor? mon = null; foreach (var line in content.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) { - File.AppendAllText(DebugLog, $"LINE: [{line}]\n"); - var colonIdx = line.IndexOf(':'); if (colonIdx < 0) continue; var key = line.Substring(0, colonIdx).Trim(); var val = line.Substring(colonIdx + 1).Trim().Trim('"'); - File.AppendAllText(DebugLog, $" KEY=[{key}] VAL=[{val}]\n"); - if (key.Contains("Monitor Device Name")) { mon = new XMonitor { MonitorDeviceName = val }; @@ -272,14 +261,12 @@ public static class CMMCommand else if (mon != null && key.Contains("Monitor ID")) { mon.MonitorID = val; - File.AppendAllText(DebugLog, $" -> Adding monitor: Name=[{mon.MonitorName}] SN=[{mon.SerialNumber}]\n"); if (!string.IsNullOrEmpty(mon.SerialNumber)) monitors.Add(mon); mon = null; } } - File.AppendAllText(DebugLog, $"\nTotal monitors with serial: {monitors.Count}\n"); return monitors; } diff --git a/README.md b/README.md index f9786a3..d64ad86 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,82 @@ -# ControlMyMonitorManagement +# Monitor Control Manager -A Windows desktop application for controlling monitor settings via DDC/CI (Display Data Channel/Command Interface). Adjust brightness, contrast, power state, and other display parameters across multiple monitors. +A lightweight Windows utility for controlling monitor settings via DDC/CI. Adjust brightness, contrast, input source, and power settings directly from your system tray. + +![.NET 9.0](https://img.shields.io/badge/.NET-9.0-blue) +![Platform](https://img.shields.io/badge/Platform-Windows-lightgrey) +![License](https://img.shields.io/badge/License-MIT-green) ## Features -- **Multi-Monitor Support** - Detect and control multiple connected displays -- **Brightness & Contrast Sliders** - Easy adjustment with real-time feedback -- **Input Source Switching** - Switch between VGA, DVI, HDMI, DisplayPort (auto-detected from monitor) -- **Power Management** - Turn monitors on, off, or into sleep mode -- **System Tray Integration** - DellMonitorControl app with taskbar tray popup -- **9 Languages** - Auto-detects system language (en, es, fr, de, zh, ja, pt, it, hi) +- **System Tray Integration** - Clean popup interface accessible from taskbar +- **Multi-Monitor Support** - Control all DDC/CI compatible displays +- **Brightness & Contrast** - Slider controls with real-time feedback +- **Input Source Switching** - Auto-detects available inputs (HDMI, DisplayPort, DVI, VGA) +- **Power Management** - On, Sleep, and Off controls +- **Multi-Language** - Auto-detects system language (English, Spanish, French, German, Chinese, Japanese, Portuguese, Italian, Hindi) ## Requirements -- Windows OS -- .NET 7.0 SDK +- Windows 10/11 +- .NET 9.0 Runtime - DDC/CI compatible monitor(s) -## Build +> **Note:** Most external monitors support DDC/CI, but some laptop internal displays and certain monitors may not support this protocol. -```bash -# Clone the repository -git clone https://github.com/yourusername/ControlMyMonitorManagement.git +## Installation + +### From Release +Download the latest release and run the executable. The app will appear in your system tray. + +### Build from Source +```powershell +git clone https://git.marketally.com/misc/ControlMyMonitorManagement.git cd ControlMyMonitorManagement - -# Build the solution -dotnet build ControlMyMonitorManagement.sln - -# Run the main application -dotnet run --project ControlMyMonitorManagement/ControlMyMonitorManagement.csproj - -# Run the Dell-specific application (with system tray) -dotnet run --project DellMonitorControl/DellMonitorControl.csproj +dotnet build +dotnet run --project DellMonitorControl ``` +## Usage + +1. Launch the application - it minimizes to system tray +2. Click the tray icon to open the control panel +3. Adjust brightness/contrast with sliders +4. Select input source from dropdown (if multiple available) +5. Toggle power state with the power button +6. Click outside the popup to close + ## Project Structure | Project | Description | |---------|-------------| -| **ControlMyMonitorManagement** | Main WPF application with full UI | -| **DellMonitorControl** | Lightweight app with system tray integration | -| **Library** | Core business logic and DDC/CI operations | -| **CMMModel** | Data models for monitor status | -| **CMMService** | Service layer interfaces | -| **Language** | Localization support | -| **Tester** | NUnit test project | +| `DellMonitorControl` | System tray application with popup UI | +| `ControlMyMonitorManagement` | Full WPF application (alternative) | +| `Library` | Core DDC/CI operations via ControlMyMonitor.exe | +| `Language` | Localization resources (9 languages) | +| `CMMModel` | Data models | +| `Tester` | Unit tests | -## Common VCP Codes +## Technical Details -| Code | Name | Values | -|------|------|--------| -| D6 | Power Mode | 1=On, 4=Sleep, 5=Off | -| 10 | Brightness | 0-100 | -| 12 | Contrast | 0-100 | -| 60 | Input Source | 1=VGA, 3=DVI, 15=DisplayPort, 17=HDMI | +### DDC/CI Protocol +The application uses DDC/CI (Display Data Channel/Command Interface) to communicate with monitors through the display cable. This allows software control of settings normally accessed via physical monitor buttons. -## How It Works - -This application uses DDC/CI protocol to communicate with monitors through the display cable (HDMI, DisplayPort, DVI, VGA). DDC/CI allows software to send commands directly to the monitor's firmware to adjust settings that would normally require using the monitor's physical buttons. +### VCP Codes Used +| Code | Function | Values | +|------|----------|--------| +| `10` | Brightness | 0-100 | +| `12` | Contrast | 0-100 | +| `60` | Input Source | 1=VGA, 15=DP, 17=HDMI | +| `D6` | Power Mode | 1=On, 4=Sleep, 5=Off | ## Credits -- **Original Project**: [DangWang](https://github.com/poyingHAHA) - ControlMyMonitorManagement -- **DDC/CI Tool**: [Nir Sofer](https://www.nirsoft.net) - ControlMyMonitor.exe (freeware) -- **Enhancements by David H Friedel Jr**: Brightness/contrast sliders, input source switching, 9-language localization +- **Original Project**: [rictirse](https://github.com/rictirse) - ControlMyMonitorManagement base +- **DDC/CI Engine**: [Nir Sofer](https://www.nirsoft.net/utils/control_my_monitor.html) - ControlMyMonitor (freeware) +- **Enhancements**: David H Friedel Jr - UI improvements, input switching, localization ## License -This project contains multiple components with different terms: +MIT License - See [LICENSE](LICENSE) for details. -- **Original code by DangWang**: No explicit license provided -- **ControlMyMonitor.exe**: Freeware by Nir Sofer - free distribution allowed, no commercial use, no modification -- **New contributions (sliders, localization, etc.)**: MIT License +Note: ControlMyMonitor.exe is freeware by Nir Sofer (free distribution, no modification).