diff --git a/MagicChatboxV2/App.xaml b/MagicChatboxV2/App.xaml new file mode 100644 index 0000000..9c15297 --- /dev/null +++ b/MagicChatboxV2/App.xaml @@ -0,0 +1,7 @@ + + + diff --git a/MagicChatboxV2/App.xaml.cs b/MagicChatboxV2/App.xaml.cs new file mode 100644 index 0000000..1144167 --- /dev/null +++ b/MagicChatboxV2/App.xaml.cs @@ -0,0 +1,100 @@ +using Microsoft.Extensions.DependencyInjection; +using System.Windows; +using MagicChatboxV2.Services; +using MagicChatboxV2.Helpers; +using MagicChatboxV2.Extensions; +using System.Reflection; +using Serilog; +using System.Windows.Threading; +using MagicChatboxV2.UIVM.Windows; + +namespace MagicChatboxV2 +{ + public partial class App : Application + { + private IServiceProvider serviceProvider; + public delegate PrimaryInterface PrimaryInterfaceFactory(); + + + public App() + { + // Configure the logger + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.Console() + .WriteTo.File("logs/application.log", rollingInterval: RollingInterval.Day) + .CreateLogger(); + + // Set the shutdown mode to explicit + Current.ShutdownMode = ShutdownMode.OnExplicitShutdown; + + // Handle unhandled exceptions in the Dispatcher + DispatcherUnhandledException += App_DispatcherUnhandledException; + + // Handle unhandled exceptions in the AppDomain + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + } + + // Handler for unhandled exceptions in the Dispatcher + private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + { + // Log the exception + Log.Error(e.Exception, "An unhandled Dispatcher exception occurred"); + + // Show a custom error dialog with the exception details + var errorDialog = new CustomErrorDialog(e.Exception.Message, e.Exception.StackTrace); + errorDialog.ShowDialog(); + + // Mark the exception as handled + e.Handled = true; + } + + // Handler for unhandled exceptions in the AppDomain + private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + // Log the exception + Log.Error((Exception)e.ExceptionObject, "An unhandled Domain exception occurred"); + + // Show a custom error dialog with the exception details + var errorDialog = new CustomErrorDialog(((Exception)e.ExceptionObject).Message, ((Exception)e.ExceptionObject).StackTrace); + errorDialog.ShowDialog(); + } + + // Configure and build the service provider + private IServiceProvider ConfigureServices() + { + var services = new ServiceCollection(); + + // Register services and modules as before + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddModules(Assembly.GetExecutingAssembly()); + + + + // Register ModuleManagerService + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(serviceProvider => () => serviceProvider.GetRequiredService()); + + + return services.BuildServiceProvider(); + } + + + protected override void OnStartup(StartupEventArgs e) + { + base.OnStartup(e); + serviceProvider = ConfigureServices(); + + // Get the StartupHelper service from the service provider + var startupHelper = serviceProvider.GetService(); + + // Start the application logic using the StartupHelper + startupHelper?.Start(); + } + + } +} diff --git a/MagicChatboxV2/AssemblyInfo.cs b/MagicChatboxV2/AssemblyInfo.cs new file mode 100644 index 0000000..b0ec827 --- /dev/null +++ b/MagicChatboxV2/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/MagicChatboxV2/Extensions/ServiceCollectionExtensions.cs b/MagicChatboxV2/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..76e98b6 --- /dev/null +++ b/MagicChatboxV2/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.DependencyInjection; +using System.Reflection; + +namespace MagicChatboxV2.Extensions +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddModules(this IServiceCollection services, Assembly assembly) + { + // Find all types in the assembly that implement IModule and are class types + var moduleTypes = assembly.GetTypes() + .Where(t => typeof(UIVM.Models.IModule).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); + + // Register each found module type with the services collection + foreach (var type in moduleTypes) + { + services.AddTransient(typeof(UIVM.Models.IModule), type); + } + + return services; + } + } +} diff --git a/MagicChatboxV2/Helpers/StartupHelper.cs b/MagicChatboxV2/Helpers/StartupHelper.cs new file mode 100644 index 0000000..2b2ded0 --- /dev/null +++ b/MagicChatboxV2/Helpers/StartupHelper.cs @@ -0,0 +1,117 @@ +using MagicChatboxV2.Services; +using MagicChatboxV2.UIVM.Windows; +using Microsoft.Extensions.DependencyInjection; +using Serilog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using static MagicChatboxV2.App; + +namespace MagicChatboxV2.Helpers +{ + public class StartupHelper + { + private readonly IServiceProvider serviceProvider; + private readonly PrimaryInterfaceFactory _mainWindowFactory; + private LoadingWindow loadingWindow; + + public StartupHelper(IServiceProvider serviceProvider, PrimaryInterfaceFactory mainWindowFactory) + { + this.serviceProvider = serviceProvider; + this._mainWindowFactory = mainWindowFactory; + } + + // Start method to initiate the startup process + public void Start() + { + try + { + ShowLoadingWindow(); + InitializeServices(); + } + catch (Exception ex) + { + Log.Error(ex, "Error during service initialization"); + MessageBox.Show($"Failed to initialize services: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); + CloseLoadingWindow(); + Application.Current.Shutdown(); + return; + } + + CloseLoadingWindow(); + + try + { + var vrChatService = serviceProvider.GetService(); + vrChatService.OnVRChatStarted += OnVRChatStarted; + vrChatService.StartMonitoring(); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to start VRChat monitoring"); + MessageBox.Show($"Failed to start VRChat monitoring: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); + + } + } + + // Show the loading window + private void ShowLoadingWindow() + { + Application.Current.Dispatcher.Invoke(() => + { + loadingWindow = new LoadingWindow(); + loadingWindow.Show(); + }); + } + + // Initialize the services + private void InitializeServices() + { + UpdateProgress(0, "Initializing System Tray..."); + var trayService = serviceProvider.GetService(); + trayService.InitializeTrayIcon(); + UpdateProgress(50, "System Tray Initialized."); + + UpdateProgress(100, "Initialization Complete."); + } + + // Update the progress of the loading window + private void UpdateProgress(double progress, string status) + { + Application.Current.Dispatcher.Invoke(() => + { + loadingWindow.UpdateProgress(progress, status); + }); + } + + // Close the loading window + private void CloseLoadingWindow() + { + Application.Current.Dispatcher.Invoke(() => + { + loadingWindow.Close(); + }); + } + + // Event handler for when VRChat is started + private void OnVRChatStarted() + { + Application.Current.Dispatcher.Invoke(() => + { + var mainWindow = _mainWindowFactory(); + if (!mainWindow.IsVisible) + { + mainWindow.Show(); + } + else + { + mainWindow.Activate(); + } + }); + } + + } +} diff --git a/MagicChatboxV2/MagicChatboxV2.csproj b/MagicChatboxV2/MagicChatboxV2.csproj new file mode 100644 index 0000000..342cb73 --- /dev/null +++ b/MagicChatboxV2/MagicChatboxV2.csproj @@ -0,0 +1,33 @@ + + + + WinExe + net7.0-windows + enable + enable + true + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MagicChatboxV2/PrimaryInterface.xaml b/MagicChatboxV2/PrimaryInterface.xaml new file mode 100644 index 0000000..5699ab7 --- /dev/null +++ b/MagicChatboxV2/PrimaryInterface.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + Show prefix icon in front of the heart rate + + + + + + + Auto change heart rate prefix icons + + + + - Auto change heart rate prefix icons + IsChecked="{Binding EnableHeartRateOfflineCheck, Mode=TwoWay}" + Style="{DynamicResource SettingsCheckbox}" + Unchecked="Update_Click"> + Check for offline heart rate + + + + + + + - Show BPM suffix instead of prefix icons 💖 + Show BPM suffix @@ -1570,6 +1654,32 @@ Show heart-rate title + + + + + + + Show 'My time:' in front of the time integration + + + + + + Text="{Binding HeartRateLastUpdate, StringFormat='{}{0:T}'}" + Visibility="{Binding IntgrHeartRate, Converter={StaticResource InverseBoolToHiddenConverter}, UpdateSourceTrigger=PropertyChanged}" /> + Text="Something went wrong, check settings" + Visibility="{Binding PulsoidAccessError, Converter={StaticResource InverseBoolToHiddenConverter}, UpdateSourceTrigger=PropertyChanged}" /> + + + + _gpuList; + private bool _autoSelectGPU = true; // Default to true for automatically selecting the GPU + private string _selectedGPU; + + public List GPUList + { + get => _gpuList; + set + { + _gpuList = value; + NotifyPropertyChanged(nameof(GPUList)); + } + } + + public bool AutoSelectGPU + { + get => _autoSelectGPU; + set + { + _autoSelectGPU = value; + NotifyPropertyChanged(nameof(AutoSelectGPU)); + } + } + + public string SelectedGPU + { + get => _selectedGPU; + set + { + _selectedGPU = value; + NotifyPropertyChanged(nameof(SelectedGPU)); + } + } + + + private bool _ComponentStatsGPU3DVRAMHook = false; + public bool ComponentStatsGPU3DVRAMHook + { + get { return _ComponentStatsGPU3DVRAMHook; } + set + { + _ComponentStatsGPU3DVRAMHook = value; + NotifyPropertyChanged(nameof(ComponentStatsGPU3DVRAMHook)); + } + } + + + private bool _MagicHeartIconPrefix = true; + public bool MagicHeartIconPrefix + { + get { return _MagicHeartIconPrefix; } + set + { + _MagicHeartIconPrefix = value; + NotifyPropertyChanged(nameof(MagicHeartIconPrefix)); + } + } + + #region ICommand's public ICommand ActivateStatusCommand { get; set; } @@ -4234,6 +4293,53 @@ public bool IntelliChatAutoLang } } + + private bool _PulsoidDeviceOnline = true; + public bool PulsoidDeviceOnline + { + get { return _PulsoidDeviceOnline; } + set + { + _PulsoidDeviceOnline = value; + NotifyPropertyChanged(nameof(PulsoidDeviceOnline)); + } + } + + + private string _CurrentHeartRateTitle = "My heartrate"; + public string CurrentHeartRateTitle + { + get { return _CurrentHeartRateTitle; } + set + { + _CurrentHeartRateTitle = value; + NotifyPropertyChanged(nameof(CurrentHeartRateTitle)); + } + } + + private int _UnchangedHeartRateLimit = 10; + public int UnchangedHeartRateLimit + { + get { return _UnchangedHeartRateLimit; } + set + { + _UnchangedHeartRateLimit = value; + NotifyPropertyChanged(nameof(UnchangedHeartRateLimit)); + } + } + + + private bool _EnableHeartRateOfflineCheck = true; + public bool EnableHeartRateOfflineCheck + { + get { return _EnableHeartRateOfflineCheck; } + set + { + _EnableHeartRateOfflineCheck = value; + NotifyPropertyChanged(nameof(EnableHeartRateOfflineCheck)); + } + } + #endregion #region PropChangedEvent