diff --git a/Cursor_Installer_Creator.csproj b/Cursor_Installer_Creator.csproj
index a9e86c4..a5b6239 100644
--- a/Cursor_Installer_Creator.csproj
+++ b/Cursor_Installer_Creator.csproj
@@ -1,7 +1,7 @@
WinExe
- net8.0-windows
+ net9.0-windows7.0
enable
true
app.manifest
@@ -24,15 +24,15 @@
-
-
-
-
+
+
+
+
-
+
-
+
\ No newline at end of file
diff --git a/CCursor.cs b/Data/CCursor.cs
similarity index 84%
rename from CCursor.cs
rename to Data/CCursor.cs
index 45a4242..4f3d825 100644
--- a/CCursor.cs
+++ b/Data/CCursor.cs
@@ -1,6 +1,7 @@
-using System.IO;
+using Cursor_Installer_Creator.Utils;
+using System.IO;
-namespace Cursor_Installer_Creator;
+namespace Cursor_Installer_Creator.Data;
public enum CCursorType
{
@@ -16,7 +17,7 @@ public sealed class CCursor
public CCursorType GetCursorType()
{
- if (string.IsNullOrEmpty(CursorPath))
+ if (string.IsNullOrWhiteSpace(CursorPath))
return CCursorType.unknown;
if (CursorPath.EndsWith(".ani"))
diff --git a/CursorAssignment.cs b/Data/CursorAssignment.cs
similarity index 97%
rename from CursorAssignment.cs
rename to Data/CursorAssignment.cs
index 7c37f4a..56c51e6 100644
--- a/CursorAssignment.cs
+++ b/Data/CursorAssignment.cs
@@ -5,7 +5,7 @@
using System.IO;
using System.Linq;
-namespace Cursor_Installer_Creator;
+namespace Cursor_Installer_Creator.Data;
public enum CursorAssignmentType
{
@@ -40,7 +40,7 @@ public static Dictionary ReadCsvString(string fileContent
var dictionary = new Dictionary();
foreach (var record in records)
{
- if (string.IsNullOrEmpty(record.Name))
+ if (string.IsNullOrWhiteSpace(record.Name))
continue;
dictionary[record.ID] = record;
}
diff --git a/Data/Version.cs b/Data/Version.cs
new file mode 100644
index 0000000..9059113
--- /dev/null
+++ b/Data/Version.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Linq;
+
+namespace Cursor_Installer_Creator.Data;
+
+public sealed class Version
+{
+ public int Major { get; set; } = -1;
+ public int Minor { get; set; } = -1;
+ public int Patch { get; set; } = -1;
+ public bool IsPrerelease { get; set; }
+
+ public Version() { }
+
+ public Version(int major, int minor, int patch, bool isPrerelease = false)
+ {
+ Major = major;
+ Minor = minor;
+ Patch = patch;
+ IsPrerelease = isPrerelease;
+ }
+
+ public Version(string? version)
+ {
+ if (string.IsNullOrWhiteSpace(version))
+ return;
+
+ version = version.ToLower();
+ version = version.TrimStart('v');
+
+ var parts = version.Split('-');
+ IsPrerelease = parts.Length > 1;
+
+ var versionParts = parts[0].Split('.').Select(int.Parse).ToArray();
+ Major = versionParts[0];
+ Minor = versionParts[1];
+ Patch = versionParts[2];
+ }
+
+ public override string ToString() => IsPrerelease ? $"{Major}.{Minor}.{Patch}-alpha" : $"{Major}.{Minor}.{Patch}";
+
+ public override bool Equals(object? obj)
+ {
+ if (obj is Version otherVersion)
+ {
+ return Major == otherVersion.Major
+ && Minor == otherVersion.Minor
+ && Patch == otherVersion.Patch
+ && IsPrerelease == otherVersion.IsPrerelease;
+ }
+ return base.Equals(obj);
+ }
+
+ public override int GetHashCode() => HashCode.Combine(Major, Minor, Patch, IsPrerelease);
+
+ public static bool operator >(Version v1, Version v2)
+ {
+ if (v1.Major != v2.Major)
+ return v1.Major > v2.Major;
+ if (v1.Minor != v2.Minor)
+ return v1.Minor > v2.Minor;
+ if (v1.Patch != v2.Patch)
+ return v1.Patch > v2.Patch;
+ return !v1.IsPrerelease && v2.IsPrerelease;
+ }
+
+ public static bool operator <(Version v1, Version v2)
+ {
+ return !(v1 > v2) && !v1.Equals(v2);
+ }
+
+ public static bool operator >=(Version v1, Version v2)
+ {
+ return v1 > v2 || v1.Equals(v2);
+ }
+
+ public static bool operator <=(Version v1, Version v2)
+ {
+ return v1 < v2 || v1.Equals(v2);
+ }
+
+ public static Version operator +(Version v1, Version v2)
+ {
+ return new Version(
+ v1.Major + v2.Major,
+ v1.Minor + v2.Minor,
+ v1.Patch + v2.Patch,
+ v1.IsPrerelease || v2.IsPrerelease
+ );
+ }
+
+ public static Version operator -(Version v1, Version v2)
+ {
+ return new Version(
+ Math.Max(v1.Major - v2.Major, 0),
+ Math.Max(v1.Minor - v2.Minor, 0),
+ Math.Max(v1.Patch - v2.Patch, 0),
+ v1.IsPrerelease
+ );
+ }
+}
diff --git a/CursorHelper.cs b/Utils/CursorHelper.cs
similarity index 93%
rename from CursorHelper.cs
rename to Utils/CursorHelper.cs
index c2c87db..2604c9b 100644
--- a/CursorHelper.cs
+++ b/Utils/CursorHelper.cs
@@ -1,4 +1,5 @@
-using Microsoft.Win32;
+using Cursor_Installer_Creator.Data;
+using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -11,7 +12,7 @@
using System.Text;
using Cursor = System.Windows.Forms.Cursor;
-namespace Cursor_Installer_Creator;
+namespace Cursor_Installer_Creator.Utils;
public static class CursorHelper
{
@@ -28,19 +29,17 @@ public static List GetSelectedCursors()
foreach (var valueName in valueNames)
{
var cursorPath = key.GetValue(valueName)?.ToString();
- if (string.IsNullOrEmpty(cursorPath) || !File.Exists(cursorPath))
+ if (string.IsNullOrWhiteSpace(cursorPath) || !File.Exists(cursorPath))
{
var assignment = CursorAssignment.FromName(valueName, CursorAssignmentType.WindowsReg);
- cursorPath = $"C:/Windows/Cursors/{assignment?.Windows}.cur";
+ cursorPath = @$"C:\Windows\Cursors\{assignment?.Windows}.cur";
}
if (File.Exists(cursorPath))
{
var ccursor = ConvertCursorFile(cursorPath, valueName);
if (ccursor is not null)
- {
ccursors.Add(ccursor);
- }
}
}
}
@@ -58,9 +57,7 @@ public static List GetSelectedCursors()
};
ccursor.CursorPath = ConvertCursorFile(ccursor.CursorPath, ccursor.Assignment)?.CursorPath ?? ccursor.CursorPath;
if (File.Exists(ccursor.CursorPath))
- {
ccursors.Add(ccursor);
- }
}
}
@@ -74,9 +71,7 @@ public static List GetSelectedCursors()
{
var cursorPath = key.GetValue(assignment.WindowsReg)?.ToString();
if (!File.Exists(cursorPath))
- {
cursorPath = @$"C:\Windows\Cursors\{assignment.Windows}.cur";
- }
return ConvertCursorFile(cursorPath, assignment.ID);
}
@@ -108,9 +103,7 @@ private static Dictionary ParseInstallerInfStrings(string filePa
var value = parts[1].Trim().TrimStart('\"').TrimEnd('\"');
var assignment = CursorAssignment.FromName(key, CursorAssignmentType.WindowsInstall);
if (assignment is not null)
- {
stringsDictionary[assignment.WindowsReg] = value;
- }
}
}
}
@@ -129,9 +122,7 @@ public static IEnumerable CursorsFromInstallerInf(string filePath)
{
var ccursor = ConvertCursorFile(Path.Combine(Path.GetDirectoryName(filePath)!, kvp.Value), assignment);
if (ccursor is not null)
- {
ccursors.Add(ccursor);
- }
}
}
return ccursors;
@@ -163,9 +154,7 @@ public static void RemoveCursorDisplayImage(CCursor ccursor)
{
var prevCursorImagePath = Path.ChangeExtension(ccursor.CursorPath, ".png");
if (File.Exists(prevCursorImagePath))
- {
File.Delete(prevCursorImagePath);
- }
}
public static CCursor? ConvertCursorFile(string cursorPath, int cursorID) => ConvertCursorFile(cursorPath, CursorAssignment.CursorAssignments[cursorID]);
@@ -188,9 +177,7 @@ public static void RemoveCursorDisplayImage(CCursor ccursor)
var prevCursorImagePath = Path.ChangeExtension(destinationFullPath, ".png");
if (File.Exists(prevCursorImagePath))
- {
File.Delete(prevCursorImagePath);
- }
var ccursor = new CCursor
{
@@ -207,24 +194,24 @@ public static void RemoveCursorDisplayImage(CCursor ccursor)
}
[DllImport("User32.dll", CharSet = CharSet.Unicode)]
- private static extern IntPtr LoadCursorFromFile(string str);
+ private static extern nint LoadCursorFromFile(string str);
private static Cursor GetCursorFromFile(string filename)
{
var hCursor = LoadCursorFromFile(filename);
- return !IntPtr.Zero.Equals(hCursor)
+ return !nint.Zero.Equals(hCursor)
? new Cursor(hCursor)
: throw new ApplicationException("Could not create cursor from file " + filename);
}
public static void CreateInstaller(string packageName, string folderPath, IEnumerable ccursors, bool createZip = true)
{
- using (var writer = new StreamWriter($"{Program.TempPath}/installer.inf"))
+ using (var writer = new StreamWriter(@$"{Program.TempPath}\installer.inf"))
{
writer.Write(CreateInstallerInfString(packageName, ccursors));
}
var files = ccursors.Select(x => x.CursorPath).ToList();
- files.Add($"{Program.TempPath}/installer.inf");
+ files.Add(@$"{Program.TempPath}\installer.inf");
if (createZip)
{
@@ -291,9 +278,7 @@ private static string CreateInstallerInfString(string packageName, IEnumerable files)
{
if (File.Exists(zipPath))
- {
File.Delete(zipPath);
- }
using var archive = ZipFile.Open(zipPath, ZipArchiveMode.Create);
var folder = Path.GetFileNameWithoutExtension(zipPath);
foreach (var file in files)
@@ -331,9 +316,7 @@ public static void InstallCursor(string installerFilePath)
catch (System.ComponentModel.Win32Exception ex)
{
if (ex.NativeErrorCode != 1223)
- {
throw new Exception(ex.Message + Environment.NewLine + ex.ErrorCode);
- }
}
}
}
diff --git a/Utils/GitHubUpdater.cs b/Utils/GitHubUpdater.cs
new file mode 100644
index 0000000..c132db8
--- /dev/null
+++ b/Utils/GitHubUpdater.cs
@@ -0,0 +1,45 @@
+using Cursor_Installer_Creator.Data;
+using System.Net.Http;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace Cursor_Installer_Creator.Utils;
+
+public sealed class GitHubUpdater
+{
+ public const string RepoOwner = "Der-Floh";
+ public const string RepoName = "Cursor-Installer-Creator";
+
+ public static Version CurrentVersion { get; } = new Version(2, 1, 0);
+ public static string RepoUrl => $"https://github.com/{RepoOwner}/{RepoName}";
+ public static string LatestReleaseUrl => $"https://github.com/{RepoOwner}/{RepoName}/releases/latest";
+
+ private static readonly HttpClient _client = new HttpClient();
+
+ public static async Task HasUpdateAsync()
+ {
+ try
+ {
+ // GitHub API endpoint for the latest release
+ var url = $"https://api.github.com/repos/{RepoOwner}/{RepoName}/releases/latest";
+ _client.DefaultRequestHeaders.UserAgent.ParseAdd(RepoName.Replace('-', '_'));
+
+ // Fetch latest release
+ var response = await _client.GetAsync(url);
+ response.EnsureSuccessStatusCode();
+
+ // Deserialize response
+ var jsonString = await response.Content.ReadAsStringAsync();
+ var jsonDocument = JsonDocument.Parse(jsonString);
+ var latestVersionString = jsonDocument.RootElement.GetProperty("tag_name").GetString();
+ var latestVersion = new Version(latestVersionString);
+
+ // Compare versions
+ return latestVersion > CurrentVersion;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+}
diff --git a/Views/CursorInstallerMainView.axaml b/Views/CursorInstallerMainView.axaml
index 82840c3..1b9fdb0 100644
--- a/Views/CursorInstallerMainView.axaml
+++ b/Views/CursorInstallerMainView.axaml
@@ -13,7 +13,8 @@
-
+
+
@@ -23,11 +24,11 @@
-
+
-
+
-
+
@@ -36,6 +37,8 @@
+
+
diff --git a/Views/CursorInstallerMainView.axaml.cs b/Views/CursorInstallerMainView.axaml.cs
index 95192ac..64f7c9b 100644
--- a/Views/CursorInstallerMainView.axaml.cs
+++ b/Views/CursorInstallerMainView.axaml.cs
@@ -1,5 +1,6 @@
using Avalonia.Controls;
using Avalonia.Platform.Storage;
+using Cursor_Installer_Creator.Utils;
using System;
using System.IO;
using System.Linq;
@@ -7,13 +8,24 @@
namespace Cursor_Installer_Creator.Views;
-public partial class CursorInstallerMainView : UserControl
+public sealed partial class CursorInstallerMainView : UserControl
{
public CursorInstallerMainView()
{
InitializeComponent();
CursorListViewElem.OnImportInfFile += CursorListViewElem_OnImportInfFile;
+ Task.Run(CheckUpdate);
+ }
+
+ private async Task CheckUpdate()
+ {
+ var hasUpdate = await UpdateNotifyViewElem.HasUpdateAsync();
+ if (hasUpdate)
+ {
+ await Task.Delay(1000);
+ await UpdateNotifyViewElem.ShowUpdateNotify();
+ }
}
private void CreateCursorInstaller(string packageName, string location)
@@ -51,7 +63,7 @@ private void CursorListViewElem_OnImportInfFile(object? sender, string filePath)
private void CursorInstallButton_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var packageName = CursorPackagenameTextBox.Text;
- if (string.IsNullOrEmpty(packageName))
+ if (string.IsNullOrWhiteSpace(packageName))
packageName = CursorPackagenameTextBox.Watermark!;
CursorHelper.CreateInstaller(packageName, Program.TempPath, CursorListViewElem.Cursors.Select(x => x.CCursor), false);
@@ -63,7 +75,7 @@ private async void CreateCursorPackageButton_Click(object? sender, Avalonia.Inte
{
CreateCursorPackageButton.IsEnabled = false;
var packageName = CursorPackagenameTextBox.Text;
- if (string.IsNullOrEmpty(packageName))
+ if (string.IsNullOrWhiteSpace(packageName))
packageName = CursorPackagenameTextBox.Watermark!;
var location = await PickFolderLocation();
diff --git a/Views/CursorItemView.axaml.cs b/Views/CursorItemView.axaml.cs
index d560f5e..7a75f95 100644
--- a/Views/CursorItemView.axaml.cs
+++ b/Views/CursorItemView.axaml.cs
@@ -1,7 +1,10 @@
using Avalonia.Controls;
using Avalonia.Input;
+using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform.Storage;
+using Cursor_Installer_Creator.Data;
+using Cursor_Installer_Creator.Utils;
using System;
using System.IO;
using System.Linq;
@@ -10,7 +13,7 @@
namespace Cursor_Installer_Creator.Views;
-public partial class CursorItemView : UserControl
+public sealed partial class CursorItemView : UserControl
{
public CCursor CCursor
{
@@ -38,9 +41,9 @@ public CursorItemView()
AddHandler(DragDrop.DropEvent, Drop);
DragDrop.SetAllowDrop(this, true);
- Avalonia.Media.RenderOptions.SetBitmapInterpolationMode(CursorImage, BitmapInterpolationMode.HighQuality);
- Avalonia.Media.RenderOptions.SetBitmapInterpolationMode(ResetImage, BitmapInterpolationMode.HighQuality);
- Avalonia.Media.RenderOptions.SetBitmapInterpolationMode(FileOpenImage, BitmapInterpolationMode.HighQuality);
+ RenderOptions.SetBitmapInterpolationMode(CursorImage, BitmapInterpolationMode.HighQuality);
+ RenderOptions.SetBitmapInterpolationMode(ResetImage, BitmapInterpolationMode.HighQuality);
+ RenderOptions.SetBitmapInterpolationMode(FileOpenImage, BitmapInterpolationMode.HighQuality);
_cCursor = _defaultCursor;
UpdateCursorFromFile(CCursor.CursorPath);
@@ -53,14 +56,14 @@ private void UpdateCCursorDisplay()
CursorNameTextBlock.Text = CCursor.Assignment.WindowsReg;
var avaloniaCursorName = CCursor.Assignment.Avalonia;
- if (string.IsNullOrEmpty(avaloniaCursorName))
+ if (string.IsNullOrWhiteSpace(avaloniaCursorName))
{
avaloniaCursorName = "Arrow";
}
CursorItemGrid.Cursor = Cursor.Parse(avaloniaCursorName);
var imagePath = CCursor.GetImagePath();
- if (!string.IsNullOrEmpty(imagePath))
+ if (!string.IsNullOrWhiteSpace(imagePath))
{
_cursorImage?.Dispose();
_cursorImage = new Bitmap(imagePath);
diff --git a/Views/CursorListView.axaml.cs b/Views/CursorListView.axaml.cs
index d1f3f0b..92b8842 100644
--- a/Views/CursorListView.axaml.cs
+++ b/Views/CursorListView.axaml.cs
@@ -1,6 +1,7 @@
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using Cursor_Installer_Creator.Extensions;
+using Cursor_Installer_Creator.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -9,9 +10,9 @@
namespace Cursor_Installer_Creator.Views;
-public partial class CursorListView : UserControl
+public sealed partial class CursorListView : UserControl
{
- public event EventHandler OnImportInfFile;
+ public event EventHandler? OnImportInfFile;
public List Cursors { get; set; } = [];
public CursorListView()
diff --git a/Views/UpdateNotifyView.axaml b/Views/UpdateNotifyView.axaml
new file mode 100644
index 0000000..daee0b7
--- /dev/null
+++ b/Views/UpdateNotifyView.axaml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Views/UpdateNotifyView.axaml.cs b/Views/UpdateNotifyView.axaml.cs
new file mode 100644
index 0000000..c323e2a
--- /dev/null
+++ b/Views/UpdateNotifyView.axaml.cs
@@ -0,0 +1,45 @@
+using Avalonia.Controls;
+using Avalonia.Threading;
+using Cursor_Installer_Creator.Utils;
+using System.Diagnostics;
+using System.Threading.Tasks;
+
+namespace Cursor_Installer_Creator.Views;
+
+public partial class UpdateNotifyView : UserControl
+{
+ public UpdateNotifyView()
+ {
+ InitializeComponent();
+ }
+
+ public async Task HasUpdateAsync() => await GitHubUpdater.HasUpdateAsync();
+
+ public async Task ShowUpdateNotify()
+ {
+ await Dispatcher.UIThread.InvokeAsync(() => IsVisible = true);
+ await Dispatcher.UIThread.InvokeAsync(() => UpdateBorderElem.Classes.Add("update"));
+ }
+
+ public async Task HideUpdateNotify()
+ {
+ await Dispatcher.UIThread.InvokeAsync(() => UpdateBorderElem.Classes.Remove("update"));
+ await Task.Delay(1000);
+ await Dispatcher.UIThread.InvokeAsync(() => IsVisible = false);
+ }
+
+ private void ViewOnGitHubButton_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = GitHubUpdater.LatestReleaseUrl,
+ UseShellExecute = true
+ });
+ Task.Run(HideUpdateNotify);
+ }
+
+ private void DismissButton_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ Task.Run(HideUpdateNotify);
+ }
+}
\ No newline at end of file