2
0

refactor(ci): rename DellMonitorControl to MonitorControl
All checks were successful
Build / build (push) Successful in 9h0m26s
Build and Release / build (push) Successful in 8h0m15s

Rename project from DellMonitorControl to MonitorControl to reflect broader monitor compatibility beyond Dell hardware. Update all references in solution file, workflow, and project paths.

Add DDC/CI rate limiting and throttling logic to prevent command failures:
- Minimum 50ms interval between commands to same monitor
- 8-second grace period after system resume before sending commands
- 3-second cooldown after timeout to allow monitor recovery
- Global semaphore to prevent command collisions

Replace old icon with new generic monitor icon (ico and png formats).
This commit is contained in:
2026-01-29 18:14:58 -05:00
parent 3c6cc15281
commit 404e6f42da
24 changed files with 262 additions and 53 deletions

View File

@@ -24,7 +24,7 @@ jobs:
Write-Host "Setting version to: $version"
# Update csproj (preserve UTF-8 BOM encoding)
$csprojPath = "DellMonitorControl/DellMonitorControl.csproj"
$csprojPath = "MonitorControl/MonitorControl.csproj"
$content = [System.IO.File]::ReadAllText($csprojPath)
$content = $content -replace '<Version>.*</Version>', "<Version>$version</Version>"
$content = $content -replace '<AssemblyVersion>.*</AssemblyVersion>', "<AssemblyVersion>$version.0</AssemblyVersion>"
@@ -32,7 +32,7 @@ jobs:
[System.IO.File]::WriteAllText($csprojPath, $content, [System.Text.UTF8Encoding]::new($true))
# Update ISS
$issPath = "DellMonitorControl/MonitorControl.iss"
$issPath = "MonitorControl/MonitorControl.iss"
$issContent = [System.IO.File]::ReadAllText($issPath)
$issContent = $issContent -replace '#define MyAppVersion ".*"', "#define MyAppVersion `"$version`""
[System.IO.File]::WriteAllText($issPath, $issContent, [System.Text.UTF8Encoding]::new($true))
@@ -48,21 +48,21 @@ jobs:
run: |
echo "Current directory:"
cd
echo "DellMonitorControl contents:"
dir DellMonitorControl
echo "DellMonitorControl\bin contents:"
dir DellMonitorControl\bin
echo "DellMonitorControl\bin\Release contents:"
dir DellMonitorControl\bin\Release
echo "DellMonitorControl\bin\Release\net9.0-windows contents:"
dir DellMonitorControl\bin\Release\net9.0-windows
echo "MonitorControl contents:"
dir MonitorControl
echo "MonitorControl\bin contents:"
dir MonitorControl\bin
echo "MonitorControl\bin\Release contents:"
dir MonitorControl\bin\Release
echo "MonitorControl\bin\Release\net9.0-windows contents:"
dir MonitorControl\bin\Release\net9.0-windows
- name: Build Installer
shell: cmd
run: |
echo Copying to short path to avoid path length issues...
mkdir C:\build 2>nul
xcopy /E /I /Y DellMonitorControl C:\build\app
xcopy /E /I /Y MonitorControl C:\build\app
echo.
echo Working from C:\build\app
cd /d C:\build\app
@@ -86,7 +86,7 @@ jobs:
shell: powershell
run: |
$version = "${{ gitea.ref_name }}".TrimStart("v")
$buildDir = "DellMonitorControl\bin\Release\net9.0-windows"
$buildDir = "MonitorControl\bin\Release\net9.0-windows"
$zipName = "MonitorControl-Portable-$version.zip"
Write-Host "Creating portable zip: $zipName"
@@ -115,7 +115,7 @@ jobs:
$body = @{
tag_name = $tag
name = "Monitor Control $tag"
body = "## Monitor Control $tag`n`n### Installation`n- **Installer**: Download and run the setup exe`n- **Portable**: Download the zip, extract anywhere, and run DellMonitorControl.exe`n`n### Features`n- System tray monitor control`n- Brightness/Contrast adjustment`n- Input source switching`n- Quick-switch toolbar`n`n### Requirements`n- Windows 10/11`n- .NET 9.0 Runtime"
body = "## Monitor Control $tag`n`n### Installation`n- **Installer**: Download and run the setup exe`n- **Portable**: Download the zip, extract anywhere, and run MonitorControl.exe`n`n### Features`n- System tray monitor control`n- Brightness/Contrast adjustment`n- Input source switching`n- Quick-switch toolbar`n`n### Requirements`n- Windows 10/11`n- .NET 9.0 Runtime"
} | ConvertTo-Json
$release = Invoke-RestMethod -Uri "$baseUrl/repos/$repo/releases" -Method Post -Headers @{"Authorization"="token $env:GITEA_TOKEN"; "Content-Type"="application/json"} -Body $body

View File

@@ -11,7 +11,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Language", "Language\Langua
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tester", "Tester\Tester.csproj", "{0D34DD73-3282-40EB-8F59-DF190944BF12}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DellMonitorControl", "DellMonitorControl\DellMonitorControl.csproj", "{64E96610-D431-40B9-A00B-55CE195B4B58}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonitorControl", "MonitorControl\MonitorControl.csproj", "{64E96610-D431-40B9-A00B-55CE195B4B58}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CMMService", "CMMService\CMMService.csproj", "{FEA2B019-32BC-4704-939F-1CD26F373F55}"
EndProject

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,6 +1,7 @@
using CMM.Library.Base;
using CMM.Library.Helpers;
using CMM.Library.ViewModel;
using System.Collections.Concurrent;
using System.IO;
namespace CMM.Library.Method;
@@ -14,6 +15,78 @@ public static class CMMCommand
static readonly string CMMexe = Path.Combine(AppContext.BaseDirectory, "ControlMyMonitor.exe");
static readonly string CMMsMonitors = Path.Combine(CMMTmpFolder, "smonitors.tmp");
// DDC/CI rate limiting: minimum time between commands to the same monitor
private static readonly ConcurrentDictionary<string, DateTime> _lastCommandTime = new();
private static readonly SemaphoreSlim _globalDdcLock = new(1, 1);
private const int MinCommandIntervalMs = 50;
// Post-resume grace period: prevents DDC/CI commands from hitting monitors still waking up
private static DateTime _resumeTime = DateTime.MinValue;
private const int ResumeGracePeriodMs = 8000;
// Timeout cooldown: when a command times out, the monitor is likely struggling.
// Back off for this long before sending another command to that monitor.
private static readonly ConcurrentDictionary<string, DateTime> _timeoutCooldowns = new();
private const int TimeoutCooldownMs = 3000;
/// <summary>
/// Call this when the system resumes from sleep/hibernate to activate the grace period.
/// Monitors need several seconds after resume before their scalers are ready for DDC/CI.
/// </summary>
public static void NotifySystemResumed()
{
_resumeTime = DateTime.UtcNow;
}
/// <summary>
/// Waits for the post-resume grace period to expire, respects timeout cooldowns,
/// and enforces per-monitor command spacing.
/// </summary>
private static async Task ThrottleDdcCommand(string monitorSN)
{
// Wait for post-resume grace period
var elapsed = (DateTime.UtcNow - _resumeTime).TotalMilliseconds;
if (elapsed < ResumeGracePeriodMs)
{
var waitMs = (int)(ResumeGracePeriodMs - elapsed);
await Task.Delay(waitMs);
}
// Wait for timeout cooldown — if this monitor recently timed out, give it time to recover
if (_timeoutCooldowns.TryGetValue(monitorSN, out var cooldownUntil))
{
var remaining = (cooldownUntil - DateTime.UtcNow).TotalMilliseconds;
if (remaining > 0)
await Task.Delay((int)remaining);
_timeoutCooldowns.TryRemove(monitorSN, out _);
}
// Enforce minimum interval between commands to the same monitor
await _globalDdcLock.WaitAsync();
try
{
if (_lastCommandTime.TryGetValue(monitorSN, out var lastTime))
{
var sinceLastMs = (DateTime.UtcNow - lastTime).TotalMilliseconds;
if (sinceLastMs < MinCommandIntervalMs)
await Task.Delay((int)(MinCommandIntervalMs - sinceLastMs));
}
_lastCommandTime[monitorSN] = DateTime.UtcNow;
}
finally
{
_globalDdcLock.Release();
}
}
/// <summary>
/// Records that a command to this monitor timed out, activating a cooldown period.
/// </summary>
private static void RecordTimeout(string monitorSN)
{
_timeoutCooldowns[monitorSN] = DateTime.UtcNow.AddMilliseconds(TimeoutCooldownMs);
}
public static async Task ScanMonitor()
{
// Ensure temp folder exists for output files
@@ -23,26 +96,36 @@ public static class CMMCommand
public static async Task PowerOn(string monitorSN)
{
await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/SetValue {monitorSN} D6 1");
await ThrottleDdcCommand(monitorSN);
var (_, exitCode) = await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/SetValue {monitorSN} D6 1");
if (exitCode == -1) RecordTimeout(monitorSN);
}
public static async Task Sleep(string monitorSN)
{
await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/SetValue {monitorSN} D6 4");
await ThrottleDdcCommand(monitorSN);
var (_, exitCode) = await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/SetValue {monitorSN} D6 4");
if (exitCode == -1) RecordTimeout(monitorSN);
}
private static async Task<string> GetMonitorValue(string monitorSN, string vcpCode = "D6", int maxRetries = 2)
{
for (int attempt = 0; attempt <= maxRetries; attempt++)
{
await ThrottleDdcCommand(monitorSN);
// Execute directly without batch file wrapper
var (output, exitCode) = await ConsoleHelper.ExecuteExeAsync(
CMMexe,
$"/GetValue {monitorSN} {vcpCode}");
// Timeout
// Timeout — monitor is unresponsive, activate cooldown and stop retrying.
// Continuing to send commands to a timed-out monitor risks scaler lockout.
if (exitCode == -1)
{
RecordTimeout(monitorSN);
return string.Empty;
}
// ControlMyMonitor returns the value as the exit code
// Exit code > 0 means success with that value
@@ -66,7 +149,9 @@ public static class CMMCommand
public static async Task SetBrightness(string monitorSN, int value)
{
await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/SetValue {monitorSN} 10 {value}");
await ThrottleDdcCommand(monitorSN);
var (_, exitCode) = await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/SetValue {monitorSN} 10 {value}");
if (exitCode == -1) RecordTimeout(monitorSN);
}
public static async Task<int?> GetBrightness(string monitorSN)
@@ -81,7 +166,9 @@ public static class CMMCommand
public static async Task SetContrast(string monitorSN, int value)
{
await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/SetValue {monitorSN} 12 {value}");
await ThrottleDdcCommand(monitorSN);
var (_, exitCode) = await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/SetValue {monitorSN} 12 {value}");
if (exitCode == -1) RecordTimeout(monitorSN);
}
public static async Task<int?> GetContrast(string monitorSN)
@@ -96,7 +183,9 @@ public static class CMMCommand
public static async Task SetInputSource(string monitorSN, int value)
{
await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/SetValue {monitorSN} 60 {value}");
await ThrottleDdcCommand(monitorSN);
var (_, exitCode) = await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/SetValue {monitorSN} 60 {value}");
if (exitCode == -1) RecordTimeout(monitorSN);
}
public static async Task<int?> GetInputSource(string monitorSN)
@@ -110,7 +199,9 @@ public static class CMMCommand
var options = new List<InputSourceOption>();
var savePath = Path.Combine(CMMTmpFolder, $"{monitorSN}_vcp.tmp");
await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/sjson {savePath} {monitorSN}");
await ThrottleDdcCommand(monitorSN);
var (_, exitCode) = await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/sjson {savePath} {monitorSN}");
if (exitCode == -1) RecordTimeout(monitorSN);
if (!File.Exists(savePath)) return options;
@@ -177,17 +268,19 @@ public static class CMMCommand
public static async Task ScanMonitorStatus(IEnumerable<XMonitor> monitors)
{
var taskList = monitors.Select(x =>
// Sequential scan to avoid overwhelming DDC/CI bus with parallel commands
foreach (var x in monitors)
{
return ScanMonitorStatus($"{CMMTmpFolder}\\{x.SerialNumber}.tmp", x);
});
await Task.WhenAll(taskList);
await ScanMonitorStatus($"{CMMTmpFolder}\\{x.SerialNumber}.tmp", x);
}
}
static async Task ScanMonitorStatus(string savePath, XMonitor mon)
{
await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/sjson {savePath} {mon.MonitorID}");
var sn = mon.SerialNumber ?? mon.MonitorID ?? "unknown";
await ThrottleDdcCommand(sn);
var (_, exitCode) = await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/sjson {savePath} {mon.MonitorID}");
if (exitCode == -1) RecordTimeout(sn);
var monitorModel = JsonHelper.JsonFormFile<IEnumerable<SMonitorModel>>(savePath);
var status = monitorModel.ReadMonitorStatus();

View File

@@ -1,8 +1,8 @@
<Application x:Class="DellMonitorControl.App"
<Application x:Class="MonitorControl.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tb="clr-namespace:Hardcodet.Wpf.TaskbarNotification;assembly=Hardcodet.Wpf.TaskbarNotification.Net6"
xmlns:local="clr-namespace:DellMonitorControl"
xmlns:local="clr-namespace:MonitorControl"
Startup="Application_Startup">
<Application.Resources>
<tb:TaskbarIcon x:Key="TrayIcon"

View File

@@ -1,7 +1,9 @@
using CMM.Library.Method;
using Hardcodet.Wpf.TaskbarNotification;
using Microsoft.Win32;
using System.Windows;
namespace DellMonitorControl
namespace MonitorControl
{
public partial class App : Application
{
@@ -17,10 +19,23 @@ namespace DellMonitorControl
_mainWindow = new MainWindow();
// Listen for system sleep/resume to protect monitors during wake
SystemEvents.PowerModeChanged += OnPowerModeChanged;
// Check for updates in background
await CheckForUpdatesAsync();
}
private void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
if (e.Mode == PowerModes.Resume)
{
// Notify the DDC/CI layer that monitors need time to wake up
CMMCommand.NotifySystemResumed();
DebugLogger.Log("System resumed from sleep - DDC/CI grace period activated");
}
}
private async System.Threading.Tasks.Task CheckForUpdatesAsync()
{
try
@@ -62,6 +77,7 @@ namespace DellMonitorControl
protected override void OnExit(ExitEventArgs e)
{
SystemEvents.PowerModeChanged -= OnPowerModeChanged;
_trayIcon?.Dispose();
base.OnExit(e);
}

View File

@@ -1,4 +1,4 @@
<Window x:Class="DellMonitorControl.ConfigWindow"
<Window x:Class="MonitorControl.ConfigWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Port Configuration"

View File

@@ -4,12 +4,13 @@ using CMM.Library.ViewModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace DellMonitorControl;
namespace MonitorControl;
public partial class ConfigWindow : Window
{
@@ -17,6 +18,7 @@ public partial class ConfigWindow : Window
private readonly string _monitorName;
private readonly List<InputSourceOption> _availablePorts;
private readonly List<PortConfigRow> _portRows = new();
private CancellationTokenSource? _detectCts;
public bool ConfigChanged { get; private set; }
@@ -205,13 +207,35 @@ public partial class ConfigWindow : Window
private async void DetectButton_Click(object sender, RoutedEventArgs e)
{
btnDetect.IsEnabled = false;
btnDetect.Content = "...";
// If detection is already running, cancel it
if (_detectCts != null)
{
_detectCts.Cancel();
return;
}
// Warn the user about what this does
var confirm = MessageBox.Show(
"Port detection will temporarily switch your monitor's input source to probe for available ports. " +
"Your screen may go dark briefly during each probe.\n\n" +
"This is safe for most monitors, but avoid running it repeatedly in a short time.\n\n" +
"You can click the Cancel button to stop detection at any time.\n\n" +
"Continue?",
"Port Detection", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (confirm != MessageBoxResult.Yes)
return;
_detectCts = new CancellationTokenSource();
var ct = _detectCts.Token;
btnDetect.Content = "Cancel";
try
{
// Common VCP 60 values: 15=DP1, 16=DP2, 17=HDMI1, 18=HDMI2, 3=DVI1, 4=DVI2, 1=VGA1, 2=VGA2
var commonPorts = new[] { 15, 16, 17, 18, 3, 4, 1, 2 };
// Only probe modern ports (DP/HDMI) by default — legacy ports (VGA/DVI) are rarely used
// and probing them on monitors without those connectors wastes time and stresses the scaler
var commonPorts = new[] { 15, 16, 17, 18 };
var detectedPorts = new List<int>();
// Get current input so we can restore it
@@ -219,6 +243,8 @@ public partial class ConfigWindow : Window
foreach (var vcpValue in commonPorts)
{
ct.ThrowIfCancellationRequested();
// Skip ports we already know about
if (_availablePorts.Any(p => p.Value == vcpValue))
continue;
@@ -227,7 +253,10 @@ public partial class ConfigWindow : Window
{
// Try to set the input - if it succeeds, the port exists
await CMMCommand.SetInputSource(_serialNumber, vcpValue);
await Task.Delay(500); // Give monitor time to respond
// Give the monitor's scaler time to attempt signal lock on the new input.
// 3 seconds is the minimum safe settle time for most scalers.
await Task.Delay(3000, ct);
// Check if the input actually changed
var newInput = await CMMCommand.GetInputSource(_serialNumber);
@@ -236,21 +265,26 @@ public partial class ConfigWindow : Window
detectedPorts.Add(vcpValue);
}
}
catch (OperationCanceledException) { throw; }
catch
{
// Port doesn't exist or isn't supported
}
// Port doesn't exist or isn't supported — continue to next
}
// Restore original input if we have one
// Extra settle time between probes to let the scaler fully recover
try { await Task.Delay(1000, ct); }
catch (OperationCanceledException) { throw; }
}
// Restore original input — give it extra time to settle after all the switching
if (currentInput.HasValue)
{
await CMMCommand.SetInputSource(_serialNumber, currentInput.Value);
await Task.Delay(2000);
}
if (detectedPorts.Count > 0)
{
// Add detected ports to the available list and config
foreach (var vcpValue in detectedPorts)
{
var name = CMMCommand.GetInputSourceName(vcpValue);
@@ -258,7 +292,6 @@ public partial class ConfigWindow : Window
MonitorConfigManager.AddDiscoveredPort(_serialNumber, _monitorName, vcpValue, name);
}
// Reload to show new ports
MonitorConfigManager.ClearCache();
LoadPortConfiguration();
@@ -272,6 +305,21 @@ public partial class ConfigWindow : Window
MessageBoxButton.OK, MessageBoxImage.Information);
}
}
catch (OperationCanceledException)
{
// User cancelled — try to restore original input before exiting
try
{
var currentInput = await CMMCommand.GetInputSource(_serialNumber);
// Only restore if we can still talk to the monitor
if (currentInput.HasValue)
await CMMCommand.SetInputSource(_serialNumber, currentInput.Value);
}
catch { }
MessageBox.Show("Port detection was cancelled.", "Detection Cancelled",
MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
MessageBox.Show($"Detection failed: {ex.Message}", "Error",
@@ -279,8 +327,9 @@ public partial class ConfigWindow : Window
}
finally
{
_detectCts?.Dispose();
_detectCts = null;
btnDetect.Content = "Detect";
btnDetect.IsEnabled = true;
}
}

View File

@@ -1,4 +1,4 @@
<UserControl x:Class="DellMonitorControl.ControlPanel"
<UserControl x:Class="MonitorControl.ControlPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
mc:Ignorable="d"

View File

@@ -9,7 +9,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace DellMonitorControl;
namespace MonitorControl;
/// <summary>
/// Interaction logic for ControlPanel.xaml

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
namespace DellMonitorControl;
namespace MonitorControl;
public static class DebugLogger
{

View File

@@ -1,4 +1,4 @@
<Window x:Class="DellMonitorControl.MainWindow"
<Window x:Class="MonitorControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Monitor Control"

View File

@@ -7,11 +7,12 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
namespace DellMonitorControl;
namespace MonitorControl;
public partial class MainWindow : Window
{
@@ -21,13 +22,51 @@ public partial class MainWindow : Window
private UpdateInfo? _pendingUpdate;
private DateTime _lastUpdateCheck = DateTime.MinValue;
// WM_DISPLAYCHANGE: sent when display resolution changes or monitors are connected/disconnected
private const int WM_DISPLAYCHANGE = 0x007E;
private bool _displayChangeReloadPending;
public MainWindow()
{
InitializeComponent();
_spinnerStoryboard = (Storyboard)FindResource("SpinnerAnimation");
SourceInitialized += MainWindow_SourceInitialized;
DebugLogger.Log("MainWindow initialized");
}
private void MainWindow_SourceInitialized(object? sender, EventArgs e)
{
var source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
source?.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_DISPLAYCHANGE && !_displayChangeReloadPending)
{
_displayChangeReloadPending = true;
DebugLogger.Log("WM_DISPLAYCHANGE received - scheduling monitor refresh");
// Debounce: display changes often fire multiple messages in quick succession.
// Wait 2 seconds for things to settle, then reload if window is visible.
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
timer.Tick += async (s, args) =>
{
timer.Stop();
_displayChangeReloadPending = false;
if (IsVisible)
{
DebugLogger.Log("Reloading monitors after display change");
await LoadMonitors();
}
};
timer.Start();
}
return IntPtr.Zero;
}
public void ShowNearTray()
{
DebugLogger.Log("ShowNearTray called");
@@ -119,7 +158,7 @@ public partial class MainWindow : Window
{
Title = "About Monitor Control",
Width = 300,
Height = 180,
Height = 240,
WindowStartupLocation = WindowStartupLocation.CenterScreen,
ResizeMode = ResizeMode.NoResize,
WindowStyle = WindowStyle.None,
@@ -138,6 +177,16 @@ public partial class MainWindow : Window
var stack = new StackPanel { VerticalAlignment = VerticalAlignment.Center };
var logo = new System.Windows.Controls.Image
{
Source = new System.Windows.Media.Imaging.BitmapImage(new Uri("pack://application:,,,/MonitorIcon.png")),
Width = 48,
Height = 48,
HorizontalAlignment = HorizontalAlignment.Center,
Margin = new Thickness(0, 0, 0, 10)
};
stack.Children.Add(logo);
stack.Children.Add(new TextBlock
{
Text = "Monitor Control",

View File

@@ -4,8 +4,8 @@
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<AssemblyName>DellMonitorControl</AssemblyName>
<RootNamespace>DellMonitorControl</RootNamespace>
<AssemblyName>MonitorControl</AssemblyName>
<RootNamespace>MonitorControl</RootNamespace>
<Product>ControlMyMonitorManagement</Product>
<UseWPF>true</UseWPF>
<Company>MarketAlly</Company>
@@ -22,10 +22,12 @@
<ItemGroup>
<None Remove="MonitorIcon.ico" />
<None Remove="MonitorIcon.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="MonitorIcon.ico" />
<Resource Include="MonitorIcon.png" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,7 +1,7 @@
#define MyAppName "Monitor Control"
#define MyAppVersion "1.1.5"
#define MyAppPublisher "MarketAlly"
#define MyAppExeName "DellMonitorControl.exe"
#define MyAppExeName "MonitorControl.exe"
#define MyAppIcon "MonitorIcon.ico"
#define SourcePath "bin\Release\net9.0-windows"

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -5,7 +5,7 @@ using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;
namespace DellMonitorControl;
namespace MonitorControl;
public class UpdateChecker
{