diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml
index b686947..2b6ecbe 100644
--- a/.gitea/workflows/release.yaml
+++ b/.gitea/workflows/release.yaml
@@ -17,19 +17,28 @@ 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
+ $csprojPath = "DellMonitorControl/DellMonitorControl.csproj"
+ (Get-Content $csprojPath) -replace '.*', "$version" | Set-Content $csprojPath
+ (Get-Content $csprojPath) -replace '.*', "$version.0" | Set-Content $csprojPath
+ (Get-Content $csprojPath) -replace '.*', "$version.0" | Set-Content $csprojPath
+
+ # Update ISS
+ $issPath = "DellMonitorControl/MonitorControl.iss"
+ (Get-Content $issPath) -replace '#define MyAppVersion ".*"', "#define MyAppVersion `"$version`"" | Set-Content $issPath
+
- 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: |
diff --git a/DellMonitorControl/App.xaml.cs b/DellMonitorControl/App.xaml.cs
index 7fcae20..fb7f718 100644
--- a/DellMonitorControl/App.xaml.cs
+++ b/DellMonitorControl/App.xaml.cs
@@ -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)
diff --git a/DellMonitorControl/DellMonitorControl.csproj b/DellMonitorControl/DellMonitorControl.csproj
index 97da5cb..811b3bd 100644
--- a/DellMonitorControl/DellMonitorControl.csproj
+++ b/DellMonitorControl/DellMonitorControl.csproj
@@ -12,17 +12,10 @@
Copyright © DangWang $([System.DateTime]::Now.ToString(yyyy))
-
- 1
- 0
- $([System.DateTime]::op_Subtraction($([System.DateTime]::get_Now().get_Date()),$([System.DateTime]::new(2023,7,2))).get_TotalDays())
- $([System.DateTime]::Now.ToString(Hmm))
- $([System.DateTime]::Now.ToString(yyyyMMdd))
- $(Major).$(Minor).$(ProjectStartedDate).$(DaysSinceProjectStarted)
- 0.0.0.1
- $(VersionSuffix)
- 0.0.0.1
- $(DateTimeSuffix)
+
+ 1.0.13
+ 1.0.13.0
+ 1.0.13.0
diff --git a/DellMonitorControl/UpdateChecker.cs b/DellMonitorControl/UpdateChecker.cs
new file mode 100644
index 0000000..c71a773
--- /dev/null
+++ b/DellMonitorControl/UpdateChecker.cs
@@ -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 CheckForUpdateAsync()
+ {
+ try
+ {
+ DebugLogger.Log($"Checking for updates... Current version: {CurrentVersion}");
+
+ var response = await _httpClient.GetStringAsync(ReleasesApiUrl);
+ var release = JsonSerializer.Deserialize(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; }
+}
diff --git a/Library/Helpers/ConsoleHelper.cs b/Library/Helpers/ConsoleHelper.cs
index f6c50b7..c8fefc1 100644
--- a/Library/Helpers/ConsoleHelper.cs
+++ b/Library/Helpers/ConsoleHelper.cs
@@ -19,7 +19,7 @@ internal class ConsoleHelper
}
};
- public static async Task ExecuteCommand(string command)
+ public static async Task ExecuteCommand(string command, int timeoutMs = 5000)
{
Process p = new Process();
p.StartInfo.UseShellExecute = false;
@@ -27,10 +27,19 @@ internal class ConsoleHelper
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = command;
p.Start();
- var output = await p.StandardOutput.ReadToEndAsync();
- await p.WaitForExitAsync();
- return output;
+ using var cts = new CancellationTokenSource(timeoutMs);
+ try
+ {
+ var output = await p.StandardOutput.ReadToEndAsync(cts.Token);
+ await p.WaitForExitAsync(cts.Token);
+ return output;
+ }
+ catch (OperationCanceledException)
+ {
+ try { p.Kill(); } catch { }
+ return string.Empty;
+ }
}
public static async Task CmdCommandAsync(params string[] cmds) =>
@@ -40,6 +49,11 @@ internal class ConsoleHelper
Command(cmdFileName, cmds);
public static async Task CommandAsync(string fileName, params string[] cmds)
+ {
+ return await CommandAsync(fileName, 5000, cmds);
+ }
+
+ public static async Task CommandAsync(string fileName, int timeoutMs, params string[] cmds)
{
var p = CreatProcess(fileName);
p.Start();
@@ -48,14 +62,24 @@ 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:\r\n" + error;
- await p.WaitForExitAsync();
- p.Close();
- Debug.WriteLine(result);
- return result;
+
+ using var cts = new CancellationTokenSource(timeoutMs);
+ try
+ {
+ var result = await p.StandardOutput.ReadToEndAsync(cts.Token);
+ var error = await p.StandardError.ReadToEndAsync(cts.Token);
+ if (!string.IsNullOrWhiteSpace(error))
+ result = result + "\r\n:\r\n" + error;
+ await p.WaitForExitAsync(cts.Token);
+ p.Close();
+ Debug.WriteLine(result);
+ return result;
+ }
+ catch (OperationCanceledException)
+ {
+ try { p.Kill(); } catch { }
+ return string.Empty;
+ }
}
public static string Command(string fileName, params string[] cmds)