Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd7a58a5fe | |||
| af8b09cfa2 | |||
| 77e9b0505a | |||
| 526974da24 | |||
| b7597fd4d0 | |||
| 260379a330 | |||
| e189731d24 | |||
| c6a2b5b22f | |||
| c394320a3e |
@@ -17,19 +17,32 @@ jobs:
|
||||
with:
|
||||
dotnet-version: '9.0.x'
|
||||
|
||||
- name: Update version in project files
|
||||
shell: powershell
|
||||
run: |
|
||||
$version = "${{ gitea.ref_name }}".TrimStart("v")
|
||||
Write-Host "Setting version to: $version"
|
||||
|
||||
# Update csproj (preserve UTF-8 BOM encoding)
|
||||
$csprojPath = "DellMonitorControl/DellMonitorControl.csproj"
|
||||
$content = [System.IO.File]::ReadAllText($csprojPath)
|
||||
$content = $content -replace '<Version>.*</Version>', "<Version>$version</Version>"
|
||||
$content = $content -replace '<AssemblyVersion>.*</AssemblyVersion>', "<AssemblyVersion>$version.0</AssemblyVersion>"
|
||||
$content = $content -replace '<FileVersion>.*</FileVersion>', "<FileVersion>$version.0</FileVersion>"
|
||||
[System.IO.File]::WriteAllText($csprojPath, $content, [System.Text.UTF8Encoding]::new($true))
|
||||
|
||||
# Update ISS
|
||||
$issPath = "DellMonitorControl/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))
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build Release
|
||||
run: dotnet build --configuration Release --no-restore
|
||||
|
||||
- name: Update version in ISS
|
||||
shell: powershell
|
||||
run: |
|
||||
$version = "${{ gitea.ref_name }}".TrimStart("v")
|
||||
$issPath = "DellMonitorControl/MonitorControl.iss"
|
||||
(Get-Content $issPath) -replace '#define MyAppVersion ".*"', "#define MyAppVersion `"$version`"" | Set-Content $issPath
|
||||
|
||||
- name: List build output
|
||||
shell: cmd
|
||||
run: |
|
||||
|
||||
@@ -7,13 +7,42 @@ namespace DellMonitorControl
|
||||
{
|
||||
private TaskbarIcon? _trayIcon;
|
||||
private MainWindow? _mainWindow;
|
||||
private UpdateInfo? _pendingUpdate;
|
||||
|
||||
private void Application_Startup(object sender, StartupEventArgs e)
|
||||
private async void Application_Startup(object sender, StartupEventArgs e)
|
||||
{
|
||||
_trayIcon = (TaskbarIcon)FindResource("TrayIcon");
|
||||
_trayIcon.TrayLeftMouseUp += TrayIcon_Click;
|
||||
_trayIcon.TrayBalloonTipClicked += TrayIcon_BalloonTipClicked;
|
||||
|
||||
_mainWindow = new MainWindow();
|
||||
|
||||
// Check for updates in background
|
||||
await CheckForUpdatesAsync();
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task CheckForUpdatesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_pendingUpdate = await UpdateChecker.CheckForUpdateAsync();
|
||||
if (_pendingUpdate != null && _trayIcon != null)
|
||||
{
|
||||
_trayIcon.ShowBalloonTip(
|
||||
"Update Available",
|
||||
$"Monitor Control v{_pendingUpdate.LatestVersion} is available.\nClick to download.",
|
||||
BalloonIcon.Info);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void TrayIcon_BalloonTipClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_pendingUpdate != null && !string.IsNullOrEmpty(_pendingUpdate.DownloadUrl))
|
||||
{
|
||||
UpdateChecker.OpenDownloadPage(_pendingUpdate.DownloadUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private async void TrayIcon_Click(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -12,17 +12,10 @@
|
||||
<Copyright>Copyright © DangWang $([System.DateTime]::Now.ToString(yyyy))</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<Major>1</Major>
|
||||
<Minor>0</Minor>
|
||||
<ProjectStartedDate>$([System.DateTime]::op_Subtraction($([System.DateTime]::get_Now().get_Date()),$([System.DateTime]::new(2023,7,2))).get_TotalDays())</ProjectStartedDate>
|
||||
<DaysSinceProjectStarted>$([System.DateTime]::Now.ToString(Hmm))</DaysSinceProjectStarted>
|
||||
<DateTimeSuffix>$([System.DateTime]::Now.ToString(yyyyMMdd))</DateTimeSuffix>
|
||||
<VersionSuffix>$(Major).$(Minor).$(ProjectStartedDate).$(DaysSinceProjectStarted)</VersionSuffix>
|
||||
<AssemblyVersion Condition=" '$(DateTimeSuffix)' == '' ">0.0.0.1</AssemblyVersion>
|
||||
<AssemblyVersion Condition=" '$(DateTimeSuffix)' != '' ">$(VersionSuffix)</AssemblyVersion>
|
||||
<Version Condition=" '$(DateTimeSuffix)' == '' ">0.0.0.1</Version>
|
||||
<Version Condition=" '$(DateTimeSuffix)' != '' ">$(DateTimeSuffix)</Version>
|
||||
<PropertyGroup>
|
||||
<Version>1.0.15</Version>
|
||||
<AssemblyVersion>1.0.15.0</AssemblyVersion>
|
||||
<FileVersion>1.0.15.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
119
DellMonitorControl/UpdateChecker.cs
Normal file
119
DellMonitorControl/UpdateChecker.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DellMonitorControl;
|
||||
|
||||
public class UpdateChecker
|
||||
{
|
||||
private const string ReleasesApiUrl = "https://git.marketally.com/api/v1/repos/misc/ControlMyMonitorManagement/releases/latest";
|
||||
private static readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(10) };
|
||||
|
||||
public static Version CurrentVersion => Assembly.GetExecutingAssembly().GetName().Version ?? new Version(1, 0, 0);
|
||||
|
||||
public static async Task<UpdateInfo?> CheckForUpdateAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
DebugLogger.Log($"Checking for updates... Current version: {CurrentVersion}");
|
||||
|
||||
var response = await _httpClient.GetStringAsync(ReleasesApiUrl);
|
||||
var release = JsonSerializer.Deserialize<GiteaRelease>(response);
|
||||
|
||||
if (release == null || string.IsNullOrEmpty(release.tag_name))
|
||||
{
|
||||
DebugLogger.Log("No release info found");
|
||||
return null;
|
||||
}
|
||||
|
||||
var latestVersionStr = release.tag_name.TrimStart('v', 'V');
|
||||
if (!Version.TryParse(latestVersionStr, out var latestVersion))
|
||||
{
|
||||
DebugLogger.Log($"Could not parse version: {release.tag_name}");
|
||||
return null;
|
||||
}
|
||||
|
||||
DebugLogger.Log($"Latest version: {latestVersion}");
|
||||
|
||||
if (latestVersion > CurrentVersion)
|
||||
{
|
||||
// Find the installer asset
|
||||
string? downloadUrl = null;
|
||||
if (release.assets != null)
|
||||
{
|
||||
foreach (var asset in release.assets)
|
||||
{
|
||||
if (asset.name?.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
downloadUrl = asset.browser_download_url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to release page if no direct download
|
||||
downloadUrl ??= release.html_url;
|
||||
|
||||
DebugLogger.Log($"Update available: {latestVersion}, URL: {downloadUrl}");
|
||||
|
||||
return new UpdateInfo
|
||||
{
|
||||
CurrentVersion = CurrentVersion,
|
||||
LatestVersion = latestVersion,
|
||||
DownloadUrl = downloadUrl ?? "",
|
||||
ReleaseNotes = release.body ?? ""
|
||||
};
|
||||
}
|
||||
|
||||
DebugLogger.Log("No update available");
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugLogger.LogError("Update check failed", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void OpenDownloadPage(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = url,
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugLogger.LogError("Failed to open download page", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateInfo
|
||||
{
|
||||
public Version CurrentVersion { get; set; } = new(1, 0, 0);
|
||||
public Version LatestVersion { get; set; } = new(1, 0, 0);
|
||||
public string DownloadUrl { get; set; } = "";
|
||||
public string ReleaseNotes { get; set; } = "";
|
||||
}
|
||||
|
||||
public class GiteaRelease
|
||||
{
|
||||
public string? tag_name { get; set; }
|
||||
public string? name { get; set; }
|
||||
public string? body { get; set; }
|
||||
public string? html_url { get; set; }
|
||||
public GiteaAsset[]? assets { get; set; }
|
||||
}
|
||||
|
||||
public class GiteaAsset
|
||||
{
|
||||
public string? name { get; set; }
|
||||
public string? browser_download_url { get; set; }
|
||||
}
|
||||
@@ -19,7 +19,7 @@ internal class ConsoleHelper
|
||||
}
|
||||
};
|
||||
|
||||
public static async Task<string> ExecuteCommand(string command)
|
||||
public static async Task<string> ExecuteCommand(string command, int timeoutMs = 5000)
|
||||
{
|
||||
Process p = new Process();
|
||||
p.StartInfo.UseShellExecute = false;
|
||||
@@ -27,10 +27,18 @@ internal class ConsoleHelper
|
||||
p.StartInfo.RedirectStandardOutput = true;
|
||||
p.StartInfo.FileName = command;
|
||||
p.Start();
|
||||
var output = await p.StandardOutput.ReadToEndAsync();
|
||||
await p.WaitForExitAsync();
|
||||
|
||||
return output;
|
||||
var readTask = p.StandardOutput.ReadToEndAsync();
|
||||
var completedTask = await Task.WhenAny(readTask, Task.Delay(timeoutMs));
|
||||
|
||||
if (completedTask != readTask)
|
||||
{
|
||||
// Timeout - kill the process
|
||||
try { p.Kill(true); } catch { }
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return await readTask;
|
||||
}
|
||||
|
||||
public static async Task<string> CmdCommandAsync(params string[] cmds) =>
|
||||
@@ -40,6 +48,11 @@ internal class ConsoleHelper
|
||||
Command(cmdFileName, cmds);
|
||||
|
||||
public static async Task<string> CommandAsync(string fileName, params string[] cmds)
|
||||
{
|
||||
return await CommandAsync(fileName, 5000, cmds);
|
||||
}
|
||||
|
||||
public static async Task<string> CommandAsync(string fileName, int timeoutMs, params string[] cmds)
|
||||
{
|
||||
var p = CreatProcess(fileName);
|
||||
p.Start();
|
||||
@@ -48,14 +61,29 @@ internal class ConsoleHelper
|
||||
p.StandardInput.WriteLine(cmd);
|
||||
}
|
||||
p.StandardInput.WriteLine("exit");
|
||||
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();
|
||||
|
||||
var readTask = Task.Run(async () =>
|
||||
{
|
||||
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;
|
||||
return result;
|
||||
});
|
||||
|
||||
var completedTask = await Task.WhenAny(readTask, Task.Delay(timeoutMs));
|
||||
|
||||
if (completedTask != readTask)
|
||||
{
|
||||
// Timeout - kill the process
|
||||
try { p.Kill(true); } catch { }
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var output = await readTask;
|
||||
p.Close();
|
||||
Debug.WriteLine(result);
|
||||
return result;
|
||||
Debug.WriteLine(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
public static string Command(string fileName, params string[] cmds)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user