diff --git a/App.axaml b/App.axaml
index 29c910c..970ad4b 100644
--- a/App.axaml
+++ b/App.axaml
@@ -3,14 +3,14 @@
x:Class="SupportCompanion.App"
x:DataType="vm:MainWindowViewModel"
Name="Support Companion"
- xmlns:local="using:SupportCompanion"
+ xmlns:common="using:SupportCompanion.Common"
xmlns:sukiUi="clr-namespace:SukiUI;assembly=SukiUI"
xmlns:vm="clr-namespace:SupportCompanion.ViewModels"
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
RequestedThemeVariant="Default">
-
+
diff --git a/App.axaml.cs b/App.axaml.cs
index 57373d2..6432003 100644
--- a/App.axaml.cs
+++ b/App.axaml.cs
@@ -6,6 +6,8 @@
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.DependencyInjection;
using ObjCRuntime;
+using SukiUI.Dialogs;
+using SukiUI.Toasts;
using SupportCompanion.Helpers;
using SupportCompanion.Models;
using SupportCompanion.Services;
@@ -27,6 +29,7 @@ public App()
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
+ RegisterAppServices();
var prefs = new AppConfigHelper();
try
{
@@ -76,7 +79,7 @@ private async Task InitializeCultureAsync()
public override async void OnFrameworkInitializationCompleted()
{
- RegisterAppServices();
+ //RegisterAppServices();
await InitializeCultureAsync();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
@@ -161,6 +164,8 @@ private void RegisterAppServices()
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d630d98..28420b2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [1.4.0] - 2024-11-06
+### Changed
+- Avalonia and SukiUI has been updated.
+- As part of the SukiUI update, the SukiHost has been updated to use the new scalable style of hosts.
+- Mainwindow height has been slightly increased.
+- Uninstall script updated with a check for root and use of Apple best practices. #50 thanks @pboushy
+
+### Fixed
+- When a app or system update notification was clicked, the command was not run resulting in nothing happening.
+- ToolTips were not being shown.
+
## [1.3.0] - 2024-09-19
### Added
- A new mode for the app called `SystemProfilerApps` which allows for the app to display applications installed under `/Applications` and their version numbers as well as Architecture. This mode is useful for admins who want to see what applications are installed on the device and their version numbers. To enable this mode, set `SystemProfilerApps` to `true` in the configuration. Example configuration:
diff --git a/Common/ViewLocator.cs b/Common/ViewLocator.cs
new file mode 100644
index 0000000..f26b09d
--- /dev/null
+++ b/Common/ViewLocator.cs
@@ -0,0 +1,40 @@
+using System.ComponentModel;
+using Avalonia.Controls;
+using Avalonia.Controls.Templates;
+
+namespace SupportCompanion.Common;
+
+public class ViewLocator : IDataTemplate
+{
+ private readonly Dictionary _controlCache = new();
+
+ public Control Build(object? data)
+ {
+ if (data is null)
+ return new TextBlock { Text = "Data is null." };
+
+ var fullName = data.GetType().FullName;
+
+ if (string.IsNullOrWhiteSpace(fullName))
+ return new TextBlock { Text = "Type has no name, or name is empty." };
+
+ var name = fullName.Replace("ViewModel", "View");
+ var type = Type.GetType(name);
+ if (type is null)
+ return new TextBlock { Text = $"No View For {name}." };
+
+ if (!_controlCache.TryGetValue(data, out var res))
+ {
+ res ??= (Control)Activator.CreateInstance(type)!;
+ _controlCache[data] = res;
+ }
+
+ res.DataContext = data;
+ return res;
+ }
+
+ public bool Match(object? data)
+ {
+ return data is INotifyPropertyChanged;
+ }
+}
\ No newline at end of file
diff --git a/Info.plist b/Info.plist
index 55bbc1f..2d2195e 100644
--- a/Info.plist
+++ b/Info.plist
@@ -22,7 +22,7 @@
CFBundleVersion
- 1.3.0
+ 1.4.0
LSMinimumSystemVersion
10.15
CFBundleExecutable
@@ -32,7 +32,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.3.0
+ 1.4.0
NSHighResolutionCapable
LSUIElement
diff --git a/Program.cs b/Program.cs
index 15cc95f..dc26dc9 100644
--- a/Program.cs
+++ b/Program.cs
@@ -8,7 +8,7 @@ internal sealed class Program
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
- [STAThread]
+ //[STAThread]
public static void Main(string[] args)
{
BuildAvaloniaApp()
@@ -25,7 +25,12 @@ public static AppBuilder BuildAvaloniaApp()
.UseReactiveUI()
.With(new MacOSPlatformOptions
{
- ShowInDock = false, DisableDefaultApplicationMenuItems = true
+ ShowInDock = false,
+ DisableDefaultApplicationMenuItems = true
+ })
+ .With(new AvaloniaNativePlatformOptions
+ {
+ RenderingMode = new[] { AvaloniaNativeRenderingMode.OpenGl, AvaloniaNativeRenderingMode.Metal }
});
}
}
\ No newline at end of file
diff --git a/Services/ActionsService.cs b/Services/ActionsService.cs
index 37614f7..477dd4e 100644
--- a/Services/ActionsService.cs
+++ b/Services/ActionsService.cs
@@ -1,6 +1,7 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
-using SukiUI.Controls;
+using Avalonia.Controls.Notifications;
+using SukiUI.Toasts;
using SupportCompanion.Helpers;
using SupportCompanion.Interfaces;
@@ -12,11 +13,14 @@ public class ActionsService : IActions
private const string OpenMmcUpdates = "open munki://updates.html";
private readonly LoggerService _logger;
- public ActionsService(LoggerService loggerService)
+ public ActionsService(LoggerService loggerService, ISukiToastManager toastManager)
{
_logger = loggerService;
+ ToastManager = toastManager;
}
+ public ISukiToastManager ToastManager { get; }
+
public async Task KillAgent()
{
var startInfo = new ProcessStartInfo
@@ -37,10 +41,20 @@ public async Task KillAgent()
if (process.ExitCode != 0)
{
_logger.Log("ActionsService:KillAgent", $"Failed to kill agent: {error}", 2);
- await SukiHost.ShowToast("Kill Agent", "Failed to kill agent");
+ ToastManager.CreateSimpleInfoToast()
+ .WithTitle("Kill Agent")
+ .OfType(NotificationType.Error)
+ .WithContent("Failed to kill agent")
+ .Queue();
+ }
+ else
+ {
+ ToastManager.CreateSimpleInfoToast()
+ .WithTitle("Kill Agent")
+ .OfType(NotificationType.Success)
+ .WithContent("Agent successfully killed")
+ .Queue();
}
-
- await SukiHost.ShowToast("Kill Agent", "Agent successfully killed");
}
public async Task Reboot()
@@ -63,7 +77,11 @@ public async Task Reboot()
if (process.ExitCode != 0)
{
_logger.Log("ActionsService:Reboot", $"Reboot failed: {error}", 2);
- await SukiHost.ShowToast("Reboot", "Reboot failed");
+ ToastManager.CreateSimpleInfoToast()
+ .WithTitle("Reboot")
+ .OfType(NotificationType.Error)
+ .WithContent("Reboot failed")
+ .Queue();
}
}
diff --git a/Services/NotificationService.cs b/Services/NotificationService.cs
index 46328ab..3396814 100644
--- a/Services/NotificationService.cs
+++ b/Services/NotificationService.cs
@@ -7,11 +7,10 @@ public class NotificationService : INotification
{
private readonly LoggerService _logger;
- public NotificationService(LoggerService loggerService)
+ public NotificationService(LoggerService loggerService) : this()
{
_logger = loggerService;
}
-
public NotificationService()
{
NSUserNotificationCenter.DefaultUserNotificationCenter.DidActivateNotification += (sender, args) =>
diff --git a/SupportCompanion.csproj b/SupportCompanion.csproj
index 990790e..3fab036 100644
--- a/SupportCompanion.csproj
+++ b/SupportCompanion.csproj
@@ -27,19 +27,19 @@
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
+
diff --git a/ViewModels/ActionsViewModel.cs b/ViewModels/ActionsViewModel.cs
index 5d9e997..7a7fca2 100644
--- a/ViewModels/ActionsViewModel.cs
+++ b/ViewModels/ActionsViewModel.cs
@@ -3,10 +3,12 @@
using System.Text.Json;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Controls.Notifications;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
-using SukiUI.Controls;
+using SukiUI.Dialogs;
+using SukiUI.Toasts;
using SupportCompanion.Interfaces;
using SupportCompanion.Services;
@@ -24,10 +26,13 @@ public partial class ActionsViewModel : ObservableObject, IWindowStateAware
[ObservableProperty] private bool _hasUpdates;
[ObservableProperty] private string _updateCount = "0";
- public ActionsViewModel(ActionsService actionsService, LoggerService loggerService)
+ public ActionsViewModel(ActionsService actionsService, LoggerService loggerService,
+ ISukiDialogManager dialogManager, ISukiToastManager toastManager)
{
_actionsService = actionsService;
_logger = loggerService;
+ ToastManager = toastManager;
+ DialogManager = dialogManager;
HideSupportButton = !App.Config.HiddenActions.Contains("Support");
HideMmcButton = !App.Config.HiddenActions.Contains("ManagedSoftwareCenter") && App.Config.MunkiMode;
HideChangePasswordButton = !App.Config.HiddenActions.Contains("ChangePassword");
@@ -38,6 +43,9 @@ public ActionsViewModel(ActionsService actionsService, LoggerService loggerServi
if (!App.Config.HiddenWidgets.Contains("Actions")) Dispatcher.UIThread.Post(InitializeAsync);
}
+ public ISukiToastManager ToastManager { get; }
+ public ISukiDialogManager DialogManager { get; }
+
public bool HideSupportButton { get; private set; }
public bool HideChangePasswordButton { get; private set; }
public bool HideMmcButton { get; private set; }
@@ -134,8 +142,11 @@ public async Task OpenChangePasswordPage()
// Do we have a network connection?
if (!await CheckForInternetConnection())
{
- await SukiHost.ShowToast("Change Password",
- "No network connection");
+ ToastManager.CreateSimpleInfoToast()
+ .WithTitle("Change Password")
+ .OfType(NotificationType.Warning)
+ .WithContent("No network connection")
+ .Queue();
return;
}
@@ -158,19 +169,30 @@ await SukiHost.ShowToast("Change Password",
// open the SSO extension with the realm name
await _actionsService.RunCommandWithoutOutput($"/usr/bin/app-sso -c {realmName}");
else
- await SukiHost.ShowToast("Change Password",
- $"Cannot reach {realmName}, make sure to connect to VPN or corporate network.");
+ ToastManager.CreateSimpleInfoToast()
+ .WithTitle("Change Password")
+ .OfType(NotificationType.Warning)
+ .WithContent($"Cannot reach {realmName}, make sure to connect to VPN or corporate network.")
+ .Queue();
}
catch (Exception)
{
- await SukiHost.ShowToast("Change Password",
- "Change request failed for unknown reason");
+ //await SukiHost.ShowToast("Change Password",
+ // "Change request failed for unknown reason");
+ ToastManager.CreateSimpleInfoToast()
+ .WithTitle("Change Password")
+ .OfType(NotificationType.Error)
+ .WithContent("Change request failed for unknown reason")
+ .Queue();
}
}
else
{
- await SukiHost.ShowToast("Change Password",
- "Change password mode not configured");
+ ToastManager.CreateSimpleInfoToast()
+ .WithTitle("Change Password")
+ .OfType(NotificationType.Warning)
+ .WithContent("Change password mode not configured")
+ .Queue();
}
}
}
@@ -187,8 +209,11 @@ public async Task GatherLogs()
// Check if the zip command was successful
if (!File.Exists(archivePath))
{
- await SukiHost.ShowToast("Gather Logs",
- "Failed to gather logs");
+ ToastManager.CreateSimpleInfoToast()
+ .WithTitle("Gather Logs")
+ .OfType(NotificationType.Error)
+ .WithContent("Failed to gather logs")
+ .Queue();
return;
}
@@ -216,20 +241,29 @@ await SukiHost.ShowToast("Gather Logs",
File.Delete(archivePath); // Delete the source file after successful copy
- await SukiHost.ShowToast("Gather Logs",
- "Logs saved successfully");
+ ToastManager.CreateSimpleInfoToast()
+ .WithTitle("Gather Logs")
+ .OfType(NotificationType.Success)
+ .WithContent("Logs saved successfully")
+ .Queue();
}
else
{
- await SukiHost.ShowToast("Gather Logs",
- "Logs not saved");
+ ToastManager.CreateSimpleInfoToast()
+ .WithTitle("Gather Logs")
+ .OfType(NotificationType.Warning)
+ .WithContent("Logs not saved")
+ .Queue();
}
}
}
public void ShowSupportInfoDialog()
{
- SukiHost.ShowDialog(new SupportDialogViewModel(), allowBackgroundClose: true);
+ DialogManager.CreateDialog()
+ .WithViewModel(dialog => new SupportDialogViewModel())
+ .Dismiss().ByClickingBackground()
+ .TryShow();
}
private void CleanUp()
diff --git a/ViewModels/DeviceWidgetViewModel.cs b/ViewModels/DeviceWidgetViewModel.cs
index 1d648d5..5c32232 100644
--- a/ViewModels/DeviceWidgetViewModel.cs
+++ b/ViewModels/DeviceWidgetViewModel.cs
@@ -1,7 +1,8 @@
using System.Net;
+using Avalonia.Controls.Notifications;
using Avalonia.Threading;
using ReactiveUI;
-using SukiUI.Controls;
+using SukiUI.Toasts;
using SupportCompanion.Assets;
using SupportCompanion.Helpers;
using SupportCompanion.Interfaces;
@@ -15,19 +16,22 @@ public class DeviceWidgetViewModel : ViewModelBase, IWindowStateAware
private readonly ClipboardService _clipboard;
private readonly IOKitService _iioKit;
private readonly SystemInfoService _systemInfo;
-
+ private readonly ISukiToastManager _toastManager;
private DeviceInfoModel? _deviceInfo;
public DeviceWidgetViewModel(SystemInfoService systemInfo,
- ClipboardService clipboard, IOKitService iioKit)
+ ClipboardService clipboard, IOKitService iioKit, ISukiToastManager toastManager)
{
_iioKit = iioKit;
_systemInfo = systemInfo;
_clipboard = clipboard;
+ ToastManager = toastManager;
DeviceInfo = new DeviceInfoModel();
Dispatcher.UIThread.Post(InitializeAsync);
}
+ public ISukiToastManager ToastManager { get; }
+
public DeviceInfoModel? DeviceInfo
{
get => _deviceInfo;
@@ -89,11 +93,19 @@ public async Task CopyToClipboard()
try
{
await _clipboard.SetClipboardTextAsync(systemInfo);
- await SukiHost.ShowToast("Copy System Info", "System Info successfully copied");
+ ToastManager.CreateSimpleInfoToast()
+ .WithTitle("Copy System Info")
+ .OfType(NotificationType.Success)
+ .WithContent("System Info successfully copied")
+ .Queue();
}
catch (Exception e)
{
- await SukiHost.ShowToast("Copy System Info", "Failed to copy System Info");
+ ToastManager.CreateSimpleInfoToast()
+ .WithTitle("Copy System Info")
+ .OfType(NotificationType.Error)
+ .WithContent("Failed to copy System Info")
+ .Queue();
}
}
diff --git a/ViewModels/MainWindowViewModel.cs b/ViewModels/MainWindowViewModel.cs
index 767ed2f..7dbba60 100644
--- a/ViewModels/MainWindowViewModel.cs
+++ b/ViewModels/MainWindowViewModel.cs
@@ -7,6 +7,8 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.DependencyInjection;
+using SukiUI.Dialogs;
+using SukiUI.Toasts;
using SupportCompanion.Services;
using SupportCompanion.Views;
@@ -21,10 +23,13 @@ public partial class MainWindowViewModel : ObservableObject
[ObservableProperty] private string _nativeMenuQuitAppText;
[ObservableProperty] private string _nativeMenuSystemUpdatesText;
- public MainWindowViewModel(ActionsService actionsService, LoggerService loggerService)
+ public MainWindowViewModel(ActionsService actionsService, LoggerService loggerService,
+ ISukiToastManager toastManager, ISukiDialogManager dialogManager)
{
_actionsService = actionsService;
_logger = loggerService;
+ ToastManager = toastManager;
+ DialogManager = dialogManager;
ShowHeader = !string.IsNullOrEmpty(App.Config.BrandName);
SelfServiceVisible = App.Config.Actions.Count > 0;
BrandName = App.Config.BrandName;
@@ -62,6 +67,9 @@ public MainWindowViewModel(ActionsService actionsService, LoggerService loggerSe
ShowMenuToggle = App.Config.ShowMenuToggle;
}
+ public ISukiToastManager ToastManager { get; }
+ public ISukiDialogManager DialogManager { get; }
+
public bool SelfServiceVisible { get; set; }
public bool ShowHeader { get; private set; }
diff --git a/ViewModels/SelfServiceViewModel.cs b/ViewModels/SelfServiceViewModel.cs
index 7f8b8ca..d92685f 100644
--- a/ViewModels/SelfServiceViewModel.cs
+++ b/ViewModels/SelfServiceViewModel.cs
@@ -1,8 +1,9 @@
using System.Collections.ObjectModel;
+using Avalonia.Controls.Notifications;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.Input;
using ReactiveUI;
-using SukiUI.Controls;
+using SukiUI.Toasts;
using SupportCompanion.Interfaces;
using SupportCompanion.Models;
using SupportCompanion.Services;
@@ -16,14 +17,18 @@ public class SelfServiceViewModel : ViewModelBase, IWindowStateAware
private readonly LoggerService _logger;
private ActionsModel? _actionsList;
- public SelfServiceViewModel(LoggerService loggerService, ActionsService actionsService)
+ public SelfServiceViewModel(LoggerService loggerService, ActionsService actionsService,
+ ISukiToastManager toastManager)
{
_logger = loggerService;
_actionsService = actionsService;
+ ToastManager = toastManager;
ActionsList = new ActionsModel { ConfigActions = new ObservableCollection() };
if (App.Config.Actions.Count > 0) Dispatcher.UIThread.Post(InitializeAsync);
}
+ public ISukiToastManager ToastManager { get; }
+
public ActionsModel? ActionsList
{
get => _actionsList;
@@ -55,7 +60,6 @@ private async Task GetActions()
ActionsList?.ConfigActions.Clear();
foreach (var action in App.Config.Actions)
- {
if (action.Value.TryGetValue("Name", out var name) &&
action.Value.TryGetValue("Command", out var command))
{
@@ -72,7 +76,6 @@ private async Task GetActions()
Icon = icon
});
}
- }
}
private async Task RunCommand(string command)
@@ -84,14 +87,24 @@ private async Task RunCommand(string command)
{
action.IsRunning = true;
await _actionsService.RunCommandWithoutOutput(command);
- await SukiHost.ShowToast("Self Service", "Command executed successfully!");
+ //await SukiHost.ShowToast("Self Service", "Command executed successfully!");
+ ToastManager.CreateSimpleInfoToast()
+ .WithTitle("Self Service")
+ .OfType(NotificationType.Success)
+ .WithContent("Command executed successfully!")
+ .Queue();
action.IsRunning = false;
}
catch (Exception e)
{
action.IsRunning = false;
_logger.Log("SelfServiceViewModel", e.Message, 3);
- await SukiHost.ShowToast("Self Service", "Command failed to execute!");
+ //await SukiHost.ShowToast("Self Service", "Command failed to execute!");
+ ToastManager.CreateSimpleInfoToast()
+ .WithTitle("Self Service")
+ .OfType(NotificationType.Error)
+ .WithContent("Command failed to execute!")
+ .Queue();
}
}
diff --git a/ViewModels/SupportDialogViewModel.cs b/ViewModels/SupportDialogViewModel.cs
index c41ebc3..a31057c 100644
--- a/ViewModels/SupportDialogViewModel.cs
+++ b/ViewModels/SupportDialogViewModel.cs
@@ -1,10 +1,8 @@
using CommunityToolkit.Mvvm.ComponentModel;
-using CommunityToolkit.Mvvm.Input;
-using SukiUI.Controls;
namespace SupportCompanion.ViewModels;
-public partial class SupportDialogViewModel : ObservableObject
+public class SupportDialogViewModel : ObservableObject
{
public SupportDialogViewModel()
{
@@ -14,10 +12,4 @@ public SupportDialogViewModel()
public string SupportEmail { get; set; }
public string SupportPhone { get; set; }
-
- [RelayCommand]
- private static void CloseDialog()
- {
- SukiHost.CloseDialog();
- }
}
\ No newline at end of file
diff --git a/ViewModels/TransparentWindowViewModel.cs b/ViewModels/TransparentWindowViewModel.cs
index cf7b780..4ae4411 100644
--- a/ViewModels/TransparentWindowViewModel.cs
+++ b/ViewModels/TransparentWindowViewModel.cs
@@ -12,12 +12,12 @@ namespace SupportCompanion.ViewModels;
public class TransparentWindowViewModel : ViewModelBase
{
+ private readonly IOKitService _iioKit;
private readonly LoggerService _logger;
private readonly StorageService _storage;
+ private readonly SystemInfoService _systemInfo;
private DeviceInfoModel? _deviceInfo;
- private readonly IOKitService _iioKit;
private StorageModel? _storageInfo;
- private readonly SystemInfoService _systemInfo;
private Timer? _timer;
public TransparentWindowViewModel(IOKitService iioKit, SystemInfoService systemInfo, StorageService storage,
diff --git a/Views/ActionsWidgetView.axaml b/Views/ActionsWidgetView.axaml
index 68c860b..eb3746b 100644
--- a/Views/ActionsWidgetView.axaml
+++ b/Views/ActionsWidgetView.axaml
@@ -15,6 +15,10 @@
+
+
+
+
diff --git a/Views/ApplicationsView.axaml b/Views/ApplicationsView.axaml
index 0599001..a3ca1bb 100644
--- a/Views/ApplicationsView.axaml
+++ b/Views/ApplicationsView.axaml
@@ -23,7 +23,7 @@
-
+
@@ -35,19 +35,19 @@
+ ColumnWidth="350">
-
+
+ IsVisible="{Binding ShowActionButton}"
+ Width="Auto">
-
+
diff --git a/Views/DeviceInfoWidgetView.axaml b/Views/DeviceInfoWidgetView.axaml
index 3f5b92b..7d08fe9 100644
--- a/Views/DeviceInfoWidgetView.axaml
+++ b/Views/DeviceInfoWidgetView.axaml
@@ -13,6 +13,9 @@
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
+
+
+
diff --git a/Views/EvergreenInfoWidgetView.axaml b/Views/EvergreenInfoWidgetView.axaml
index a3c33cb..29afb64 100644
--- a/Views/EvergreenInfoWidgetView.axaml
+++ b/Views/EvergreenInfoWidgetView.axaml
@@ -10,11 +10,11 @@
x:Class="SupportCompanion.Views.EvergreenInfoWidgetView"
x:DataType="vm:EvergreenWidgetViewModel"
x:CompileBindings="True">
+
-
+
@@ -22,21 +22,28 @@
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Views/HomeView.axaml b/Views/HomeView.axaml
index faccaf2..03e0771 100644
--- a/Views/HomeView.axaml
+++ b/Views/HomeView.axaml
@@ -12,9 +12,7 @@
-
+
diff --git a/Views/MainWindow.axaml b/Views/MainWindow.axaml
index 038f699..fa48584 100644
--- a/Views/MainWindow.axaml
+++ b/Views/MainWindow.axaml
@@ -13,9 +13,7 @@
x:CompileBindings="True"
Title=""
MinWidth="1300" MaxWidth="1300"
- MinHeight="765" MaxHeight="800"
- controls:SukiHost.ToastLocation="BottomRight"
- controls:SukiHost.ToastLimit="4"
+ MinHeight="830" MaxHeight="850"
ExtendClientAreaToDecorationsHint="False"
IsTitleBarVisible="False"
WindowStartupLocation="CenterScreen">
@@ -24,6 +22,11 @@
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
+
+
+
+
+
+
+
+
diff --git a/Views/SupportDialogView.axaml b/Views/SupportDialogView.axaml
index 0c746b9..401489c 100644
--- a/Views/SupportDialogView.axaml
+++ b/Views/SupportDialogView.axaml
@@ -29,14 +29,14 @@
-
-
+ -->
\ No newline at end of file
diff --git a/Views/TransparentWindow.axaml.cs b/Views/TransparentWindow.axaml.cs
index b94bc75..8e28920 100644
--- a/Views/TransparentWindow.axaml.cs
+++ b/Views/TransparentWindow.axaml.cs
@@ -5,129 +5,129 @@
using SupportCompanion.Interfaces;
using SupportCompanion.ViewModels;
-namespace SupportCompanion.Views
+namespace SupportCompanion.Views;
+
+public partial class TransparentWindow : Window, ITransparentWindow
{
- public partial class TransparentWindow : Window, ITransparentWindow
+ private static readonly string DesktopPosition = App.Config.DesktopPosition;
+
+ public TransparentWindow()
{
- private static readonly string DesktopPosition = App.Config.DesktopPosition;
- public TransparentWindow()
- {
- InitializeComponent();
- var viewModel = ((App)Application.Current).ServiceProvider.GetRequiredService();
- viewModel.TransparentWindow = this;
- DataContext = viewModel;
- PositionWindowInCorner(DesktopPosition);
- this.Opened += OnWindowOpened;
- this.ShowActivated = false;
- }
-
- public void hide()
- {
- base.Hide();
- }
-
- public void show()
- {
- base.Show();
- }
-
- public void invalidateVisual()
- {
- InvalidateVisual();
- }
-
- private void OnWindowOpened(object sender, EventArgs e)
- {
- SetWindowProperties();
- }
-
- private void SetWindowProperties()
+ InitializeComponent();
+ var viewModel = ((App)Application.Current).ServiceProvider.GetRequiredService();
+ viewModel.TransparentWindow = this;
+ DataContext = viewModel;
+ PositionWindowInCorner(DesktopPosition);
+ Opened += OnWindowOpened;
+ ShowActivated = false;
+ }
+
+ public void hide()
+ {
+ base.Hide();
+ }
+
+ public void show()
+ {
+ base.Show();
+ }
+
+ public void invalidateVisual()
+ {
+ InvalidateVisual();
+ }
+
+ private void OnWindowOpened(object sender, EventArgs e)
+ {
+ SetWindowProperties();
+ }
+
+ private void SetWindowProperties()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ var handle = GetNativeWindowHandle();
+ if (handle != IntPtr.Zero)
{
- var handle = GetNativeWindowHandle();
- if (handle != IntPtr.Zero)
- {
- // Set the window level to desktop, behind all other windows
- MacOSInterop.SetWindowLevel(handle, (int)NSWindowLevel.BelowDesktop);
-
- // Make the window ignore mouse events to prevent it from coming to the foreground
- MacOSInterop.SetIgnoresMouseEvents(handle, true);
-
- // Set collection behavior to stick the window to the desktop
- const int NSWindowCollectionBehaviorCanJoinAllSpaces = 1 << 0;
- const int NSWindowCollectionBehaviorStationary = 1 << 4;
- MacOSInterop.SetCollectionBehavior(handle,
- NSWindowCollectionBehaviorCanJoinAllSpaces | NSWindowCollectionBehaviorStationary);
- }
+ // Set the window level to desktop, behind all other windows
+ MacOSInterop.SetWindowLevel(handle, (int)NSWindowLevel.BelowDesktop);
+
+ // Make the window ignore mouse events to prevent it from coming to the foreground
+ MacOSInterop.SetIgnoresMouseEvents(handle, true);
+
+ // Set collection behavior to stick the window to the desktop
+ const int NSWindowCollectionBehaviorCanJoinAllSpaces = 1 << 0;
+ const int NSWindowCollectionBehaviorStationary = 1 << 4;
+ MacOSInterop.SetCollectionBehavior(handle,
+ NSWindowCollectionBehaviorCanJoinAllSpaces | NSWindowCollectionBehaviorStationary);
}
}
+ }
- private IntPtr GetNativeWindowHandle()
- {
- var platformHandle = this.TryGetPlatformHandle();
- return platformHandle?.Handle ?? IntPtr.Zero;
- }
+ private IntPtr GetNativeWindowHandle()
+ {
+ var platformHandle = TryGetPlatformHandle();
+ return platformHandle?.Handle ?? IntPtr.Zero;
+ }
- private enum NSWindowLevel
- {
- Normal = 0,
- Desktop = -1,
- BelowDesktop = -2
- }
+ private void PositionWindowInCorner(string corner)
+ {
+ var screen = Screens.Primary.WorkingArea;
- private void PositionWindowInCorner(string corner)
+ switch (corner)
{
- var screen = Screens.Primary.WorkingArea;
-
- switch (corner)
- {
- case "BottomLeft":
- Position = new PixelPoint(screen.X, screen.Y + screen.Height - (int)Height);
- break;
- case "BottomRight":
- Position = new PixelPoint(screen.X + screen.Width - (int)Width,
- screen.Y + screen.Height - (int)Height);
- break;
- case "TopLeft":
- Position = new PixelPoint(screen.X, screen.Y);
- break;
- case "TopRight":
- Position = new PixelPoint(screen.X + screen.Width - (int)Width, screen.Y);
- break;
- default:
- throw new ArgumentException("Invalid corner specified");
- }
+ case "BottomLeft":
+ Position = new PixelPoint(screen.X, screen.Y + screen.Height - (int)Height);
+ break;
+ case "BottomRight":
+ Position = new PixelPoint(screen.X + screen.Width - (int)Width,
+ screen.Y + screen.Height - (int)Height);
+ break;
+ case "TopLeft":
+ Position = new PixelPoint(screen.X, screen.Y);
+ break;
+ case "TopRight":
+ Position = new PixelPoint(screen.X + screen.Width - (int)Width, screen.Y);
+ break;
+ default:
+ throw new ArgumentException("Invalid corner specified");
}
}
-
- public static class MacOSInterop
+
+ private enum NSWindowLevel
{
- [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")]
- public static extern IntPtr objc_getClass(string className);
+ Normal = 0,
+ Desktop = -1,
+ BelowDesktop = -2
+ }
+}
+
+public static class MacOSInterop
+{
+ [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")]
+ public static extern IntPtr objc_getClass(string className);
- [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")]
- public static extern IntPtr sel_registerName(string selector);
+ [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")]
+ public static extern IntPtr sel_registerName(string selector);
- [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")]
- public static extern void objc_msgSend(IntPtr receiver, IntPtr selector, IntPtr arg);
+ [DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")]
+ public static extern void objc_msgSend(IntPtr receiver, IntPtr selector, IntPtr arg);
- public static void SetWindowLevel(IntPtr nsWindow, int level)
- {
- IntPtr setLevelSelector = sel_registerName("setLevel:");
- objc_msgSend(nsWindow, setLevelSelector, (IntPtr)level);
- }
-
- public static void SetIgnoresMouseEvents(IntPtr nsWindow, bool ignores)
- {
- IntPtr setIgnoresMouseEventsSelector = sel_registerName("setIgnoresMouseEvents:");
- objc_msgSend(nsWindow, setIgnoresMouseEventsSelector, (IntPtr)(ignores ? 1 : 0));
- }
-
- public static void SetCollectionBehavior(IntPtr nsWindow, int behavior)
- {
- IntPtr setCollectionBehaviorSelector = sel_registerName("setCollectionBehavior:");
- objc_msgSend(nsWindow, setCollectionBehaviorSelector, (IntPtr)behavior);
- }
+ public static void SetWindowLevel(IntPtr nsWindow, int level)
+ {
+ var setLevelSelector = sel_registerName("setLevel:");
+ objc_msgSend(nsWindow, setLevelSelector, level);
+ }
+
+ public static void SetIgnoresMouseEvents(IntPtr nsWindow, bool ignores)
+ {
+ var setIgnoresMouseEventsSelector = sel_registerName("setIgnoresMouseEvents:");
+ objc_msgSend(nsWindow, setIgnoresMouseEventsSelector, ignores ? 1 : 0);
+ }
+
+ public static void SetCollectionBehavior(IntPtr nsWindow, int behavior)
+ {
+ var setCollectionBehaviorSelector = sel_registerName("setCollectionBehavior:");
+ objc_msgSend(nsWindow, setCollectionBehaviorSelector, behavior);
}
}
\ No newline at end of file
diff --git a/build.sh b/build.sh
index 6bd338e..39cbbf7 100755
--- a/build.sh
+++ b/build.sh
@@ -7,10 +7,11 @@ PUBLISH_OUTPUT_DIRECTORY="${PROJECT_PATH}/bin/Release/net8.0-macos/SupportCompan
PUBLISH_OUTPUT_APP="${PROJECT_PATH}/bin/Release/net8.0-macos/SupportCompanion.app"
ICON_FILE="${PROJECT_PATH}/Assets/appicon.icns"
SUKIUI_LICENSE_FILE="${PROJECT_PATH}/SUKIUI_LICENSE"
+AVALONIA_LICENSE_FILE="${PROJECT_PATH}/LICENSE"
UNINSTALL_SCRIPT="${PROJECT_PATH}/Assets/Uninstall.sh"
APP_SIGNING_IDENTITY="Developer ID Application: Mac Admins Open Source (T4SK8ZXCXG)"
INSTALLER_SIGNING_IDENTITY="Developer ID Installer: Mac Admins Open Source (T4SK8ZXCXG)"
-XCODE_PATH="/Applications/Xcode_15.4.app"
+XCODE_PATH="/Applications/Xcode_16.app"
XCODE_NOTARY_PATH="$XCODE_PATH/Contents/Developer/usr/bin/notarytool"
XCODE_STAPLER_PATH="$XCODE_PATH/Contents/Developer/usr/bin/stapler"
VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "${PROJECT_PATH}/Info.plist")
@@ -83,10 +84,14 @@ dotnet publish --configuration Release -p:UseAppHost=true
# Create the Applications directory and utilities directory
mkdir -p "${BUILD_PATH}/payload/Applications/Utilities"
+# Create resources directory
+mkdir -p "$PUBLISH_OUTPUT_DIRECTORY/Contents/Resources"
+
rm -f "$PUBLISH_OUTPUT_DIRECTORY/Contents/Resources/suki_photo.ico"
cp "$ICON_FILE" "$PUBLISH_OUTPUT_DIRECTORY/Contents/Resources/"
cp "$UNINSTALL_SCRIPT" "$PUBLISH_OUTPUT_DIRECTORY/Contents/Resources/"
cp "$SUKIUI_LICENSE_FILE" "$PUBLISH_OUTPUT_DIRECTORY/Contents/Resources/"
+cp "$AVALONIA_LICENSE_FILE" "$PUBLISH_OUTPUT_DIRECTORY/Contents/Resources/"
chmod +x "$PUBLISH_OUTPUT_DIRECTORY/Contents/Resources/Uninstall.sh"
cp -a "${PROJECT_PATH}/Assets/scripts" "${BUILD_PATH}/payload/Library/Application Support/SupportCompanion/"
diff --git a/build_dev.zsh b/build_dev.zsh
index 8fe0442..1b2b8b5 100755
--- a/build_dev.zsh
+++ b/build_dev.zsh
@@ -7,6 +7,7 @@ PUBLISH_OUTPUT_DIRECTORY="${PROJECT_PATH}/bin/Release/net8.0-macos/SupportCompan
PUBLISH_OUTPUT_APP="${PROJECT_PATH}/bin/Release/net8.0-macos/SupportCompanion.app"
ICON_FILE="${PROJECT_PATH}/Assets/appicon.icns"
SUKIUI_LICENSE_FILE="${PROJECT_PATH}/SUKIUI_LICENSE"
+AVALONIA_LICENSE_FILE="${PROJECT_PATH}/LICENSE"
UNINSTALL_SCRIPT="${PROJECT_PATH}/Assets/Uninstall.sh"
VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "${PROJECT_PATH}/Info.plist")
CURRENT_SC_MAIN_BUILD_VERSION=$VERSION
@@ -47,10 +48,14 @@ dotnet publish --configuration Release -p:UseAppHost=true
# Create the Applications directory and utilities directory
mkdir -p "${BUILD_PATH}/payload/Applications/Utilities"
+# Create resources directory
+mkdir -p "$PUBLISH_OUTPUT_DIRECTORY/Contents/Resources"
+
rm -f "$PUBLISH_OUTPUT_DIRECTORY/Contents/Resources/suki_photo.ico"
cp "$ICON_FILE" "$PUBLISH_OUTPUT_DIRECTORY/Contents/Resources/"
cp "$UNINSTALL_SCRIPT" "$PUBLISH_OUTPUT_DIRECTORY/Contents/Resources/"
cp "$SUKIUI_LICENSE_FILE" "$PUBLISH_OUTPUT_DIRECTORY/Contents/Resources/"
+cp "$AVALONIA_LICENSE_FILE" "$PUBLISH_OUTPUT_DIRECTORY/Contents/Resources/"
chmod +x "$PUBLISH_OUTPUT_DIRECTORY/Contents/Resources/Uninstall.sh"
cp -a "${PROJECT_PATH}/Assets/scripts" "${BUILD_PATH}/payload/Library/Application Support/SupportCompanion/"