diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index a13daa3..33e923c 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "csharpier": { - "version": "0.28.0", + "version": "0.29.1", "commands": [ "dotnet-csharpier" ] diff --git a/.github/workflows/dotnet-desktop.yml b/.github/workflows/dotnet-desktop.yml index ae25dc6..b2ba5be 100644 --- a/.github/workflows/dotnet-desktop.yml +++ b/.github/workflows/dotnet-desktop.yml @@ -72,9 +72,9 @@ jobs: -c ${{ env.CONFIGURATION }} \ -r ${{ matrix.rid }} \ -p:PublishSingleFile=true \ - -p:SelfContained=true \ -p:IncludeNativeLibrariesForSelfExtract=true \ -p:IncludeAllContentForSelfExtract=true \ + -p:SelfContained=true \ -p:EnableCompressionInSingleFile=true \ -p:DebugType=embedded \ -p:Version=${{ steps.gitversion.outputs.SemVer }} \ diff --git a/YMouseButtonControl.Core.Tests/KeyboardAndMouse/KeyboardSimulatorWorkerTests.cs b/YMouseButtonControl.Core.Tests/KeyboardAndMouse/KeyboardSimulatorWorkerTests.cs index 4924aa0..f212fa9 100644 --- a/YMouseButtonControl.Core.Tests/KeyboardAndMouse/KeyboardSimulatorWorkerTests.cs +++ b/YMouseButtonControl.Core.Tests/KeyboardAndMouse/KeyboardSimulatorWorkerTests.cs @@ -26,7 +26,7 @@ public void DefaultProfileSendsKeys() { SimulatedKeystrokesType = new MouseButtonPressedActionType(), BlockOriginalMouseInput = true, - Keys = "wee" + Keys = "wee", }; var testProvider = new TestProvider(); using var hook = new SimpleReactiveGlobalHook(globalHookProvider: testProvider); @@ -51,7 +51,7 @@ public void DefaultProfileSendsKeys() var evtDn = new UioHookEvent { Type = EventType.MousePressed, - Mouse = new MouseEventData { Button = MouseButton.Button3, } + Mouse = new MouseEventData { Button = MouseButton.Button3 }, }; // var evtUp = new UioHookEvent // { @@ -78,7 +78,7 @@ public void NonDefaultProfileSendsKeys() { SimulatedKeystrokesType = new MouseButtonPressedActionType(), BlockOriginalMouseInput = true, - Keys = "wee" + Keys = "wee", }; var testProvider = new TestProvider(); using var hook = new SimpleReactiveGlobalHook(globalHookProvider: testProvider); @@ -103,7 +103,7 @@ public void NonDefaultProfileSendsKeys() var evtDn = new UioHookEvent { Type = EventType.MousePressed, - Mouse = new MouseEventData { Button = MouseButton.Button3, } + Mouse = new MouseEventData { Button = MouseButton.Button3 }, }; // var evtUp = new UioHookEvent // { @@ -130,7 +130,7 @@ public void NonDefaultProfileNotSendKeys() { SimulatedKeystrokesType = new MouseButtonPressedActionType(), BlockOriginalMouseInput = true, - Keys = "wee" + Keys = "wee", }; var testProvider = new TestProvider(); using var hook = new SimpleReactiveGlobalHook(globalHookProvider: testProvider); @@ -156,7 +156,7 @@ public void NonDefaultProfileNotSendKeys() var evtDn = new UioHookEvent { Type = EventType.MousePressed, - Mouse = new MouseEventData { Button = MouseButton.Button3, } + Mouse = new MouseEventData { Button = MouseButton.Button3 }, }; // var evtUp = new UioHookEvent // { @@ -181,7 +181,7 @@ public void DefaultProfileSendsKeysReleased() { SimulatedKeystrokesType = new MouseButtonReleasedActionType(), BlockOriginalMouseInput = true, - Keys = "wee" + Keys = "wee", }; var testProvider = new TestProvider(); using var hook = new SimpleReactiveGlobalHook(globalHookProvider: testProvider); @@ -206,7 +206,7 @@ public void DefaultProfileSendsKeysReleased() var evtUp = new UioHookEvent { Type = EventType.MouseReleased, - Mouse = new MouseEventData { Button = MouseButton.Button3, } + Mouse = new MouseEventData { Button = MouseButton.Button3 }, }; RunHookAndWaitForStart(hook, testProvider); diff --git a/YMouseButtonControl.Core.Tests/ViewModels/ProfilesListViewModelTests.cs b/YMouseButtonControl.Core.Tests/ViewModels/ProfilesListViewModelTests.cs index 9199815..b0754dc 100644 --- a/YMouseButtonControl.Core.Tests/ViewModels/ProfilesListViewModelTests.cs +++ b/YMouseButtonControl.Core.Tests/ViewModels/ProfilesListViewModelTests.cs @@ -14,6 +14,7 @@ using YMouseButtonControl.Core.ViewModels.Implementations.Dialogs; using YMouseButtonControl.Core.ViewModels.Interfaces; using YMouseButtonControl.Core.ViewModels.Interfaces.Dialogs; +using YMouseButtonControl.Core.ViewModels.ProfilesList; using YMouseButtonControl.Core.ViewModels.ProfilesList.Features.Add; using YMouseButtonControl.DataAccess.LiteDb; diff --git a/YMouseButtonControl.Core/DataAccess/Models/Enums/ButtonMappings.cs b/YMouseButtonControl.Core/DataAccess/Models/Enums/ButtonMappings.cs index 119a32e..f89d157 100644 --- a/YMouseButtonControl.Core/DataAccess/Models/Enums/ButtonMappings.cs +++ b/YMouseButtonControl.Core/DataAccess/Models/Enums/ButtonMappings.cs @@ -5,5 +5,5 @@ public enum ButtonMappings Nothing, Disabled, SimulatedKeystrokes, - RightClick + RightClick, } diff --git a/YMouseButtonControl.Core/DataAccess/Models/Enums/YMouseButton.cs b/YMouseButtonControl.Core/DataAccess/Models/Enums/YMouseButton.cs index e72d16d..ab4b19f 100644 --- a/YMouseButtonControl.Core/DataAccess/Models/Enums/YMouseButton.cs +++ b/YMouseButtonControl.Core/DataAccess/Models/Enums/YMouseButton.cs @@ -10,5 +10,5 @@ public enum YMouseButton : ushort MouseWheelUp, MouseWheelDown, MouseWheelLeft, - MouseWheelRight + MouseWheelRight, } diff --git a/YMouseButtonControl.Core/DataAccess/Models/Implementations/Setting.cs b/YMouseButtonControl.Core/DataAccess/Models/Implementations/Setting.cs new file mode 100644 index 0000000..d4ed463 --- /dev/null +++ b/YMouseButtonControl.Core/DataAccess/Models/Implementations/Setting.cs @@ -0,0 +1,43 @@ +using System; +using ReactiveUI; + +namespace YMouseButtonControl.Core.DataAccess.Models.Implementations; + +public class Setting : ReactiveObject, IEquatable +{ + private string? _value; + + public int Id { get; set; } + public required string Name { get; set; } + + public string? Value + { + get => _value; + set => this.RaiseAndSetIfChanged(ref _value, value); + } + + public bool Equals(Setting? other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + return Id == other.Id && Name == other.Name && Value == other.Value; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + if (obj.GetType() != this.GetType()) + return false; + return Equals((Setting)obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(Id, Name, Value); + } +} diff --git a/YMouseButtonControl.Core/DataAccess/Repositories/IRepository.cs b/YMouseButtonControl.Core/DataAccess/Repositories/IRepository.cs index e26e947..5622e9e 100644 --- a/YMouseButtonControl.Core/DataAccess/Repositories/IRepository.cs +++ b/YMouseButtonControl.Core/DataAccess/Repositories/IRepository.cs @@ -5,6 +5,7 @@ namespace YMouseButtonControl.Core.DataAccess.Repositories; public interface IRepository { T GetById(string id); + T GetById(int id); IEnumerable GetAll(); void Add(T entity); void Update(string id, T entity); diff --git a/YMouseButtonControl.Core/KeyboardAndMouse/Enums/MouseButtonState.cs b/YMouseButtonControl.Core/KeyboardAndMouse/Enums/MouseButtonState.cs index e13a989..12474e5 100644 --- a/YMouseButtonControl.Core/KeyboardAndMouse/Enums/MouseButtonState.cs +++ b/YMouseButtonControl.Core/KeyboardAndMouse/Enums/MouseButtonState.cs @@ -3,5 +3,5 @@ public enum MouseButtonState { Pressed, - Released + Released, } diff --git a/YMouseButtonControl.Core/Profiles/Implementations/ProfilesService.cs b/YMouseButtonControl.Core/Profiles/Implementations/ProfilesService.cs index f10ddd3..8c0dd8b 100644 --- a/YMouseButtonControl.Core/Profiles/Implementations/ProfilesService.cs +++ b/YMouseButtonControl.Core/Profiles/Implementations/ProfilesService.cs @@ -43,13 +43,12 @@ public ProfilesService(IUnitOfWorkFactory unitOfWorkFactory) CurrentProfile = change.Reason switch { ChangeReason.Add => change.Current, - ChangeReason.Remove - => _profiles - .Items.Where(x => x.DisplayPriority < change.Current.DisplayPriority) - .MaxBy(x => x.DisplayPriority) - ?? throw new Exception("Unable to get next lower priority on remove"), + ChangeReason.Remove => _profiles + .Items.Where(x => x.DisplayPriority < change.Current.DisplayPriority) + .MaxBy(x => x.DisplayPriority) + ?? throw new Exception("Unable to get next lower priority on remove"), ChangeReason.Update => change.Current, - _ => throw new Exception("Unhandled change reason") + _ => throw new Exception("Unhandled change reason"), }; }); @@ -90,7 +89,9 @@ private void CheckDefaultProfile() var repository = unitOfWork.GetRepository(); var model = repository.GetAll(); if (model.Any(x => x.Name == "Default")) + { return; + } var defaultProfile = new Profile { Checked = true, @@ -111,7 +112,7 @@ private void CheckDefaultProfile() MouseWheelUp = new NothingMapping(), MouseWheelDown = new NothingMapping(), MouseWheelLeft = new NothingMapping(), - MouseWheelRight = new NothingMapping() + MouseWheelRight = new NothingMapping(), }; repository.Add(defaultProfile); } diff --git a/YMouseButtonControl.Core/Profiles/Implementations/SettingsService.cs b/YMouseButtonControl.Core/Profiles/Implementations/SettingsService.cs new file mode 100644 index 0000000..59fab38 --- /dev/null +++ b/YMouseButtonControl.Core/Profiles/Implementations/SettingsService.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using System.Xml.Linq; +using DynamicData; +using ReactiveUI; +using YMouseButtonControl.Core.DataAccess.Models.Implementations; +using YMouseButtonControl.Core.DataAccess.UnitOfWork; +using YMouseButtonControl.Core.Profiles.Interfaces; + +namespace YMouseButtonControl.Core.Profiles.Implementations; + +public class SettingsService : ReactiveObject, ISettingsService +{ + private readonly IUnitOfWorkFactory _unitOfWorkFactory; + private readonly SourceCache _settings; + + public SettingsService(IUnitOfWorkFactory unitOfWorkFactory) + { + _unitOfWorkFactory = unitOfWorkFactory; + _settings = new SourceCache(x => x.Id); + CheckDefaultSettings(); + LoadSettingsFromDb(); + } + + public IObservable> Connect() => _settings.Connect(); + + public bool IsUnsavedChanges() + { + using var unitOfWork = _unitOfWorkFactory.Create(); + var repository = unitOfWork.GetRepository(); + var dbSettings = repository.GetAll().ToList(); + return _settings.Items.Count() != dbSettings.Count + || _settings.Items.Where((p, i) => !p.Equals(dbSettings[i])).Any(); + } + + public Setting? GetSetting(string name) + { + using var unitOfWork = _unitOfWorkFactory.Create(); + var repository = unitOfWork.GetRepository(); + return repository.GetAll().ToList().FirstOrDefault(x => x.Name == name); + } + + public Setting UpdateSetting(int id, string value) + { + using var unitOfWork = _unitOfWorkFactory.Create(); + var repository = unitOfWork.GetRepository(); + var dbSetting = repository.GetById(id); + dbSetting.Value = value; + repository.ApplyAction([dbSetting]); + return dbSetting; + } + + private void LoadSettingsFromDb() + { + using var unitOfWork = _unitOfWorkFactory.Create(); + var repository = unitOfWork.GetRepository(); + var model = repository.GetAll(); + _settings.AddOrUpdate(model); + } + + private void CheckDefaultSettings() + { + using var uow = _unitOfWorkFactory.Create(); + var repo = uow.GetRepository(); + var model = repo.GetAll(); + if (model.Any(x => x.Name == "StartMinimized")) + { + return; + } + + var startMinimizedSetting = new Setting { Name = "StartMinimized", Value = "false" }; + repo.Add(startMinimizedSetting); + + uow.SaveChanges(); + } +} diff --git a/YMouseButtonControl.Core/Profiles/Interfaces/ISettingsService.cs b/YMouseButtonControl.Core/Profiles/Interfaces/ISettingsService.cs new file mode 100644 index 0000000..29024c8 --- /dev/null +++ b/YMouseButtonControl.Core/Profiles/Interfaces/ISettingsService.cs @@ -0,0 +1,13 @@ +using System; +using DynamicData; +using YMouseButtonControl.Core.DataAccess.Models.Implementations; + +namespace YMouseButtonControl.Core.Profiles.Interfaces; + +public interface ISettingsService +{ + IObservable> Connect(); + bool IsUnsavedChanges(); + Setting? GetSetting(string name); + Setting UpdateSetting(int id, string value); +} diff --git a/YMouseButtonControl.Core/Services/Abstractions/Enums/WheelScrollDirection.cs b/YMouseButtonControl.Core/Services/Abstractions/Enums/WheelScrollDirection.cs index 95f2fa3..e9d8f4d 100644 --- a/YMouseButtonControl.Core/Services/Abstractions/Enums/WheelScrollDirection.cs +++ b/YMouseButtonControl.Core/Services/Abstractions/Enums/WheelScrollDirection.cs @@ -5,5 +5,5 @@ public enum WheelScrollDirection VerticalUp = 1, VerticalDown = 2, HorizontalRight = 3, - HorizontalLeft = 4 + HorizontalLeft = 4, } diff --git a/YMouseButtonControl.Core/Services/IStartupInstallerService.cs b/YMouseButtonControl.Core/Services/IStartupInstallerService.cs new file mode 100644 index 0000000..de020ad --- /dev/null +++ b/YMouseButtonControl.Core/Services/IStartupInstallerService.cs @@ -0,0 +1,9 @@ +namespace YMouseButtonControl.Core.Services; + +public interface IStartupInstallerService +{ + public bool ButtonEnabled(); + public bool InstallStatus(); + public void Install(); + public void Uninstall(); +} diff --git a/YMouseButtonControl.Core/ViewModels/Implementations/AppViewModel.cs b/YMouseButtonControl.Core/ViewModels/Implementations/AppViewModel.cs index 0efb459..9679d85 100644 --- a/YMouseButtonControl.Core/ViewModels/Implementations/AppViewModel.cs +++ b/YMouseButtonControl.Core/ViewModels/Implementations/AppViewModel.cs @@ -1,15 +1,38 @@ using System.Reactive; using Avalonia; +using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.ReactiveUI; using ReactiveUI; +using YMouseButtonControl.Core.Services; +using YMouseButtonControl.Core.Services.BackgroundTasks; using YMouseButtonControl.Core.ViewModels.Interfaces; +using YMouseButtonControl.Core.ViewModels.MainWindow; +using YMouseButtonControl.Core.Views; namespace YMouseButtonControl.Core.ViewModels.Implementations; public class AppViewModel : ViewModelBase, IAppViewModel { - public AppViewModel() + private bool _runAtStartupIsChecked; + private bool _runAtStartupIsEnabled; + private const string RunAtStartupChecked = "✅ "; + private const string RunAtStartupNotChecked = ""; + private const string RunAtStartupHeaderFmt = "{0}Run at startup"; + private string _runAtStartupHeader = ""; + + public AppViewModel( + IStartupInstallerService startupInstallerService, + IMainWindow mainWindow, + IMainWindowViewModel mainWindowViewModel, + IBackgroundTasksRunner backgroundTasksRunner + ) { + RunAtStartupIsEnabled = startupInstallerService.ButtonEnabled(); + RunAtStartupIsChecked = startupInstallerService.InstallStatus(); + RunAtStartupHeader = RunAtStartupIsChecked + ? string.Format(RunAtStartupHeaderFmt, RunAtStartupChecked) + : string.Format(RunAtStartupHeaderFmt, RunAtStartupNotChecked); ExitCommand = ReactiveCommand.Create(() => { if ( @@ -27,13 +50,62 @@ is IClassicDesktopStyleApplicationLifetime lifetime is IClassicDesktopStyleApplicationLifetime lifetime ) { + if (lifetime.MainWindow is null) + { + var mw = (Window)mainWindow; + mw.DataContext = mainWindowViewModel; + lifetime.MainWindow = mw; + } lifetime.MainWindow?.Show(); } }); + var runAtStartupCanExecute = this.WhenAnyValue(x => x.RunAtStartupIsEnabled); + RunAtStartupCommand = ReactiveCommand.Create( + () => + { + if (startupInstallerService.InstallStatus()) + { + // uninstall + startupInstallerService.Uninstall(); + RunAtStartupIsChecked = false; + RunAtStartupHeader = string.Format( + RunAtStartupHeaderFmt, + RunAtStartupNotChecked + ); + } + else + { + // install + startupInstallerService.Install(); + RunAtStartupIsChecked = true; + RunAtStartupHeader = string.Format(RunAtStartupHeaderFmt, RunAtStartupChecked); + } + }, + runAtStartupCanExecute + ); } public string ToolTipText => $"YMouseButtonControl v{GetType().Assembly.GetName().Version}"; + public bool RunAtStartupIsEnabled + { + get => _runAtStartupIsEnabled; + set => this.RaiseAndSetIfChanged(ref _runAtStartupIsEnabled, value); + } + + public bool RunAtStartupIsChecked + { + get => _runAtStartupIsChecked; + set => this.RaiseAndSetIfChanged(ref _runAtStartupIsChecked, value); + } + + public string RunAtStartupHeader + { + get => _runAtStartupHeader; + set => this.RaiseAndSetIfChanged(ref _runAtStartupHeader, value); + } + public ReactiveCommand ExitCommand { get; } public ReactiveCommand SetupCommand { get; } + public ReactiveCommand RunAtStartupCommand { get; } } diff --git a/YMouseButtonControl.Core/ViewModels/Implementations/Dialogs/GlobalSettingsDialogViewModel.cs b/YMouseButtonControl.Core/ViewModels/Implementations/Dialogs/GlobalSettingsDialogViewModel.cs new file mode 100644 index 0000000..02caeec --- /dev/null +++ b/YMouseButtonControl.Core/ViewModels/Implementations/Dialogs/GlobalSettingsDialogViewModel.cs @@ -0,0 +1,57 @@ +using System; +using System.Reactive; +using System.Reactive.Linq; +using ReactiveUI; +using YMouseButtonControl.Core.DataAccess.Models.Implementations; +using YMouseButtonControl.Core.Profiles.Interfaces; +using YMouseButtonControl.Core.ViewModels.Interfaces.Dialogs; + +namespace YMouseButtonControl.Core.ViewModels.Implementations.Dialogs; + +public class GlobalSettingsDialogViewModel : DialogBase, IGlobalSettingsDialogViewModel +{ + private Setting _startMinimized; + private readonly ObservableAsPropertyHelper? _applyIsExec; + + public GlobalSettingsDialogViewModel(ISettingsService settingsService) + { + _startMinimized = + settingsService.GetSetting("StartMinimized") + ?? throw new Exception($"Error retrieving StartMinimized setting"); + + var startMinimizedChanged = this.WhenAnyValue( + x => x.StartMinimized.Value, + selector: val => + { + var curVal = settingsService.GetSetting("StartMinimized"); + if (curVal is null) + { + return true; + } + + return curVal.Value != val; + } + ); + + var applyIsExecObs = this.WhenAnyValue(x => x.AppIsExec); + var canSave = startMinimizedChanged.Merge(applyIsExecObs); + ApplyCommand = ReactiveCommand.Create( + () => + { + settingsService.UpdateSetting(StartMinimized.Id, StartMinimized.Value!); + }, + canSave + ); + _applyIsExec = ApplyCommand.IsExecuting.ToProperty(this, x => x.AppIsExec); + } + + public Setting StartMinimized + { + get => _startMinimized; + set => this.RaiseAndSetIfChanged(ref _startMinimized, value); + } + + public ReactiveCommand ApplyCommand { get; init; } + + public bool AppIsExec => _applyIsExec?.Value ?? false; +} diff --git a/YMouseButtonControl.Core/ViewModels/Implementations/Dialogs/ProcessSelectorDialogViewModel.cs b/YMouseButtonControl.Core/ViewModels/Implementations/Dialogs/ProcessSelectorDialogViewModel.cs index 39257b7..0338139 100644 --- a/YMouseButtonControl.Core/ViewModels/Implementations/Dialogs/ProcessSelectorDialogViewModel.cs +++ b/YMouseButtonControl.Core/ViewModels/Implementations/Dialogs/ProcessSelectorDialogViewModel.cs @@ -44,7 +44,7 @@ public ProcessSelectorDialogViewModel(IProcessMonitorService processMonitorServi { Name = SelectedProcessModel!.Process.MainModule!.ModuleName, Description = SelectedProcessModel.Process.MainWindowTitle, - Process = SelectedProcessModel.Process.MainModule.ModuleName + Process = SelectedProcessModel.Process.MainModule.ModuleName, }, canExecuteOkCommand ); diff --git a/YMouseButtonControl.Core/ViewModels/Implementations/Dialogs/SimulatedKeystrokesDialogViewModel.cs b/YMouseButtonControl.Core/ViewModels/Implementations/Dialogs/SimulatedKeystrokesDialogViewModel.cs index 0072ea2..09721ae 100644 --- a/YMouseButtonControl.Core/ViewModels/Implementations/Dialogs/SimulatedKeystrokesDialogViewModel.cs +++ b/YMouseButtonControl.Core/ViewModels/Implementations/Dialogs/SimulatedKeystrokesDialogViewModel.cs @@ -68,7 +68,7 @@ public SimulatedKeystrokesDialogViewModel( CustomKeys = CustomKeys, SimulatedKeystrokesType = SimulatedKeystrokesType, Description = Description, - BlockOriginalMouseInput = BlockOriginalMouseInput + BlockOriginalMouseInput = BlockOriginalMouseInput, }, canExecuteOkCmd ); @@ -215,7 +215,7 @@ public bool BlockOriginalMouseInput { "F21", "{F21}" }, { "F22", "{F22}" }, { "F23", "{F23}" }, - { "F24", "{F24}" } + { "F24", "{F24}" }, }; public static Dictionary NumericKeypadKeys => @@ -263,7 +263,7 @@ public bool BlockOriginalMouseInput { "Next Track", "{MEDIANEXT}" }, { "Previous Track", "{MEDIAPREV}" }, { "Select Track", "{MEDIASELECT}" }, - { "Eject Media", "{MEDIAEJECT}" } + { "Eject Media", "{MEDIAEJECT}" }, }; public static Dictionary BrowserKeys => @@ -314,7 +314,7 @@ public int CaretIndex () => new RepeatedlyWhileButtonDownActionType(), () => new StickyRepeatActionType(), () => new StickyHoldActionType(), - () => new AsMousePressedAndReleasedActionType() + () => new AsMousePressedAndReleasedActionType(), ]; private string? _computedXy; diff --git a/YMouseButtonControl.Core/ViewModels/Interfaces/Dialogs/IGlobalSettingsDialogViewModel.cs b/YMouseButtonControl.Core/ViewModels/Interfaces/Dialogs/IGlobalSettingsDialogViewModel.cs new file mode 100644 index 0000000..f0b0294 --- /dev/null +++ b/YMouseButtonControl.Core/ViewModels/Interfaces/Dialogs/IGlobalSettingsDialogViewModel.cs @@ -0,0 +1,8 @@ +using YMouseButtonControl.Core.DataAccess.Models.Implementations; + +namespace YMouseButtonControl.Core.ViewModels.Interfaces.Dialogs; + +public interface IGlobalSettingsDialogViewModel +{ + Setting StartMinimized { get; set; } +} diff --git a/YMouseButtonControl.Core/ViewModels/MainWindow/IMainWindowViewModel.cs b/YMouseButtonControl.Core/ViewModels/MainWindow/IMainWindowViewModel.cs new file mode 100644 index 0000000..5e1d129 --- /dev/null +++ b/YMouseButtonControl.Core/ViewModels/MainWindow/IMainWindowViewModel.cs @@ -0,0 +1,19 @@ +using System.Reactive; +using ReactiveUI; +using YMouseButtonControl.Core.ViewModels.Implementations.Dialogs; +using YMouseButtonControl.Core.ViewModels.Interfaces; +using YMouseButtonControl.Core.ViewModels.Interfaces.Dialogs; + +namespace YMouseButtonControl.Core.ViewModels.MainWindow; + +public interface IMainWindowViewModel +{ + IProfilesInformationViewModel ProfilesInformationViewModel { get; } + IProfilesListViewModel ProfilesListViewModel { get; } + ILayerViewModel LayerViewModel { get; } + ReactiveCommand ApplyCommand { get; } + ReactiveCommand CloseCommand { get; } + ReactiveCommand SettingsCommand { get; } + Interaction ShowSettingsDialogInteraction { get; } + string? ProfileName { get; set; } +} diff --git a/YMouseButtonControl.Core/ViewModels/MainWindow/MainWindowViewModel.cs b/YMouseButtonControl.Core/ViewModels/MainWindow/MainWindowViewModel.cs index 4eea196..2310bdf 100644 --- a/YMouseButtonControl.Core/ViewModels/MainWindow/MainWindowViewModel.cs +++ b/YMouseButtonControl.Core/ViewModels/MainWindow/MainWindowViewModel.cs @@ -2,25 +2,27 @@ using System.Diagnostics; using System.Reactive; using System.Reactive.Linq; +using System.Threading.Tasks; using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using ReactiveUI; using YMouseButtonControl.Core.DataAccess.Models.Implementations; using YMouseButtonControl.Core.Profiles.Interfaces; using YMouseButtonControl.Core.ViewModels.Implementations; +using YMouseButtonControl.Core.ViewModels.Implementations.Dialogs; using YMouseButtonControl.Core.ViewModels.Interfaces; +using YMouseButtonControl.Core.ViewModels.Interfaces.Dialogs; using YMouseButtonControl.Core.ViewModels.MainWindow.Features.Apply; namespace YMouseButtonControl.Core.ViewModels.MainWindow; -public interface IMainWindowViewModel { } - public class MainWindowViewModel : ViewModelBase, IMainWindowViewModel { #region Fields private readonly IProfilesService _ps; private readonly IProfilesListViewModel _profilesListViewModel; + private readonly IGlobalSettingsDialogViewModel _globalSettingsDialogViewModel; private string? _profileName; #endregion @@ -32,14 +34,17 @@ public MainWindowViewModel( ILayerViewModel layerViewModel, IProfilesListViewModel profilesListViewModel, IProfilesInformationViewModel profilesInformationViewModel, + IGlobalSettingsDialogViewModel globalSettingsDialogViewModel, IApply apply ) { _profilesListViewModel = profilesListViewModel; + _globalSettingsDialogViewModel = globalSettingsDialogViewModel; _ps = ps; LayerViewModel = layerViewModel; ProfilesInformationViewModel = profilesInformationViewModel; - var canApply = this.WhenAnyValue(x => x._ps.UnsavedChanges).DistinctUntilChanged(); + SettingsCommand = ReactiveCommand.CreateFromTask(ShowSettingsDialogAsync); + ShowSettingsDialogInteraction = new Interaction(); CloseCommand = ReactiveCommand.Create(() => { if ( @@ -50,6 +55,7 @@ is IClassicDesktopStyleApplicationLifetime lifetime lifetime.MainWindow?.Hide(); } }); + var canApply = this.WhenAnyValue(x => x._ps.UnsavedChanges).DistinctUntilChanged(); ApplyCommand = ReactiveCommand.Create(apply.ApplyProfiles, canApply); ApplyCommand.Subscribe(_ => { @@ -72,6 +78,9 @@ is IClassicDesktopStyleApplicationLifetime lifetime public ReactiveCommand ApplyCommand { get; } public ReactiveCommand CloseCommand { get; } + public ReactiveCommand SettingsCommand { get; } + + public Interaction ShowSettingsDialogInteraction { get; } public string? ProfileName { @@ -81,5 +90,10 @@ public string? ProfileName #endregion + private async Task ShowSettingsDialogAsync() + { + await ShowSettingsDialogInteraction.Handle(_globalSettingsDialogViewModel); + } + private void OnProfileChanged(Profile profile) => ProfileName = profile.Name; } diff --git a/YMouseButtonControl.Core/ViewModels/ProfilesList/ProfilesListViewModel.cs b/YMouseButtonControl.Core/ViewModels/ProfilesList/ProfilesListViewModel.cs index 253e5d9..0dd917c 100644 --- a/YMouseButtonControl.Core/ViewModels/ProfilesList/ProfilesListViewModel.cs +++ b/YMouseButtonControl.Core/ViewModels/ProfilesList/ProfilesListViewModel.cs @@ -7,11 +7,12 @@ using ReactiveUI; using YMouseButtonControl.Core.DataAccess.Models.Implementations; using YMouseButtonControl.Core.Profiles.Interfaces; +using YMouseButtonControl.Core.ViewModels.Implementations; using YMouseButtonControl.Core.ViewModels.Interfaces; using YMouseButtonControl.Core.ViewModels.Interfaces.Dialogs; using YMouseButtonControl.Core.ViewModels.ProfilesList.Features.Add; -namespace YMouseButtonControl.Core.ViewModels.Implementations; +namespace YMouseButtonControl.Core.ViewModels.ProfilesList; public class ProfilesListViewModel : ViewModelBase, IProfilesListViewModel { diff --git a/YMouseButtonControl.Core/ViewModels/Services/ShowSimulatedKeystrokesDialogService.cs b/YMouseButtonControl.Core/ViewModels/Services/ShowSimulatedKeystrokesDialogService.cs index fb7870a..9c25c27 100644 --- a/YMouseButtonControl.Core/ViewModels/Services/ShowSimulatedKeystrokesDialogService.cs +++ b/YMouseButtonControl.Core/ViewModels/Services/ShowSimulatedKeystrokesDialogService.cs @@ -41,7 +41,7 @@ public Interaction< Keys = result.CustomKeys, PriorityDescription = result.Description, SimulatedKeystrokesType = result.SimulatedKeystrokesType, - BlockOriginalMouseInput = result.BlockOriginalMouseInput + BlockOriginalMouseInput = result.BlockOriginalMouseInput, }; } } diff --git a/YMouseButtonControl.Core/Views/IMainWindow.cs b/YMouseButtonControl.Core/Views/IMainWindow.cs new file mode 100644 index 0000000..85c5034 --- /dev/null +++ b/YMouseButtonControl.Core/Views/IMainWindow.cs @@ -0,0 +1,3 @@ +namespace YMouseButtonControl.Core.Views; + +public interface IMainWindow; diff --git a/YMouseButtonControl.Core/YMouseButtonControl.Core.csproj b/YMouseButtonControl.Core/YMouseButtonControl.Core.csproj index c1eb0a6..3dbafa0 100644 --- a/YMouseButtonControl.Core/YMouseButtonControl.Core.csproj +++ b/YMouseButtonControl.Core/YMouseButtonControl.Core.csproj @@ -19,6 +19,7 @@ + diff --git a/YMouseButtonControl.DataAccess.LiteDb/Repository.cs b/YMouseButtonControl.DataAccess.LiteDb/Repository.cs index 2b250f0..0e2512d 100644 --- a/YMouseButtonControl.DataAccess.LiteDb/Repository.cs +++ b/YMouseButtonControl.DataAccess.LiteDb/Repository.cs @@ -9,6 +9,8 @@ public class Repository(ILiteCollection collection) : IRepository { public T GetById(string id) => collection.FindById(id); + public T GetById(int id) => collection.FindById(id); + public IEnumerable GetAll() => collection.FindAll(); public void Add(T entity) => collection.Insert(entity); diff --git a/YMouseButtonControl.KeyboardAndMouse.SharpHook/Implementations/EventSimulatorService.cs b/YMouseButtonControl.KeyboardAndMouse.SharpHook/Implementations/EventSimulatorService.cs index e59a1b1..fa4290c 100644 --- a/YMouseButtonControl.KeyboardAndMouse.SharpHook/Implementations/EventSimulatorService.cs +++ b/YMouseButtonControl.KeyboardAndMouse.SharpHook/Implementations/EventSimulatorService.cs @@ -32,7 +32,7 @@ public SimulateKeyboardResult SimulateKeyPress(string? key) { Result = eventSimulator .SimulateKeyPress(KeyCodes[key ?? throw new NullReferenceException(key)]) - .ToString() + .ToString(), }; } @@ -43,7 +43,7 @@ public SimulateKeyboardResult SimulateKeyRelease(string? key) { Result = eventSimulator .SimulateKeyRelease(KeyCodes[key ?? throw new NullReferenceException(key)]) - .ToString() + .ToString(), }; } @@ -300,7 +300,7 @@ private static List ParseKeys(string? keys) { Key = substr, IsModifier = false, - Value = (ushort)KeyCodes[substr] + Value = (ushort)KeyCodes[substr], } ); i++; @@ -504,6 +504,6 @@ private static List ParseKeys(string? keys) { "rmb", MouseButton.Button2 }, { "mmb", MouseButton.Button3 }, { "mb4", MouseButton.Button4 }, - { "mb5", MouseButton.Button5 } + { "mb5", MouseButton.Button5 }, }; } diff --git a/YMouseButtonControl.KeyboardAndMouse.SharpHook/Implementations/MouseListenerService.cs b/YMouseButtonControl.KeyboardAndMouse.SharpHook/Implementations/MouseListenerService.cs index f4dd3f6..9589d7d 100644 --- a/YMouseButtonControl.KeyboardAndMouse.SharpHook/Implementations/MouseListenerService.cs +++ b/YMouseButtonControl.KeyboardAndMouse.SharpHook/Implementations/MouseListenerService.cs @@ -87,52 +87,43 @@ private void OnMouseWheel(NewMouseWheelEventArgs args) private bool ShouldSuppressEvent(NewMouseHookEventArgs args) => args.Button switch { - YMouseButton.MouseButton1 - => _profilesService.Profiles.Any(p => - p is { Checked: true, MouseButton1.BlockOriginalMouseInput: true } - && (args.ActiveWindow?.Contains(p.Process) ?? false) - ), - YMouseButton.MouseButton2 - => _profilesService.Profiles.Any(p => - p is { Checked: true, MouseButton2.BlockOriginalMouseInput: true } - && (args.ActiveWindow?.Contains(p.Process) ?? false) - ), - YMouseButton.MouseButton3 - => _profilesService.Profiles.Any(p => - p is { Checked: true, MouseButton3.BlockOriginalMouseInput: true } - && (args.ActiveWindow?.Contains(p.Process) ?? false) - ), - YMouseButton.MouseButton4 - => _profilesService.Profiles.Any(p => - p is { Checked: true, MouseButton4.BlockOriginalMouseInput: true } - && (args.ActiveWindow?.Contains(p.Process) ?? false) - ), - YMouseButton.MouseButton5 - => _profilesService.Profiles.Any(p => - p is { Checked: true, MouseButton5.BlockOriginalMouseInput: true } - && (args.ActiveWindow?.Contains(p.Process) ?? false) - ), - YMouseButton.MouseWheelUp - => _profilesService.Profiles.Any(p => - p is { Checked: true, MouseWheelUp.BlockOriginalMouseInput: true } - && (args.ActiveWindow?.Contains(p.Process) ?? false) - ), - YMouseButton.MouseWheelDown - => _profilesService.Profiles.Any(p => - p is { Checked: true, MouseWheelDown.BlockOriginalMouseInput: true } - && (args.ActiveWindow?.Contains(p.Process) ?? false) - ), - YMouseButton.MouseWheelLeft - => _profilesService.Profiles.Any(p => - p is { Checked: true, MouseWheelLeft.BlockOriginalMouseInput: true } - && (args.ActiveWindow?.Contains(p.Process) ?? false) - ), - YMouseButton.MouseWheelRight - => _profilesService.Profiles.Any(p => - p is { Checked: true, MouseWheelRight.BlockOriginalMouseInput: true } - && (args.ActiveWindow?.Contains(p.Process) ?? false) - ), - _ => throw new ArgumentOutOfRangeException() + YMouseButton.MouseButton1 => _profilesService.Profiles.Any(p => + p is { Checked: true, MouseButton1.BlockOriginalMouseInput: true } + && (args.ActiveWindow?.Contains(p.Process) ?? false) + ), + YMouseButton.MouseButton2 => _profilesService.Profiles.Any(p => + p is { Checked: true, MouseButton2.BlockOriginalMouseInput: true } + && (args.ActiveWindow?.Contains(p.Process) ?? false) + ), + YMouseButton.MouseButton3 => _profilesService.Profiles.Any(p => + p is { Checked: true, MouseButton3.BlockOriginalMouseInput: true } + && (args.ActiveWindow?.Contains(p.Process) ?? false) + ), + YMouseButton.MouseButton4 => _profilesService.Profiles.Any(p => + p is { Checked: true, MouseButton4.BlockOriginalMouseInput: true } + && (args.ActiveWindow?.Contains(p.Process) ?? false) + ), + YMouseButton.MouseButton5 => _profilesService.Profiles.Any(p => + p is { Checked: true, MouseButton5.BlockOriginalMouseInput: true } + && (args.ActiveWindow?.Contains(p.Process) ?? false) + ), + YMouseButton.MouseWheelUp => _profilesService.Profiles.Any(p => + p is { Checked: true, MouseWheelUp.BlockOriginalMouseInput: true } + && (args.ActiveWindow?.Contains(p.Process) ?? false) + ), + YMouseButton.MouseWheelDown => _profilesService.Profiles.Any(p => + p is { Checked: true, MouseWheelDown.BlockOriginalMouseInput: true } + && (args.ActiveWindow?.Contains(p.Process) ?? false) + ), + YMouseButton.MouseWheelLeft => _profilesService.Profiles.Any(p => + p is { Checked: true, MouseWheelLeft.BlockOriginalMouseInput: true } + && (args.ActiveWindow?.Contains(p.Process) ?? false) + ), + YMouseButton.MouseWheelRight => _profilesService.Profiles.Any(p => + p is { Checked: true, MouseWheelRight.BlockOriginalMouseInput: true } + && (args.ActiveWindow?.Contains(p.Process) ?? false) + ), + _ => throw new ArgumentOutOfRangeException(), }; private void SubscribeToEvents() diff --git a/YMouseButtonControl.Linux.Tests/GlobalUsings.cs b/YMouseButtonControl.Linux.Tests/GlobalUsings.cs new file mode 100644 index 0000000..3244567 --- /dev/null +++ b/YMouseButtonControl.Linux.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; diff --git a/YMouseButtonControl.Linux.Tests/StartupInstallerTests.cs b/YMouseButtonControl.Linux.Tests/StartupInstallerTests.cs new file mode 100644 index 0000000..1ada3e3 --- /dev/null +++ b/YMouseButtonControl.Linux.Tests/StartupInstallerTests.cs @@ -0,0 +1,44 @@ +using YMouseButtonControl.Services.Linux.Services; + +namespace YMouseButtonControl.Linux.Tests; + +[Explicit] +public class StartupInstallerTests +{ + [Test] + public void I_can_get_startup_install_status() + { + var sut = new StartupInstallerService(); + + Assert.DoesNotThrow(() => + { + sut.InstallStatus(); + }); + } + + [Test] + public void I_can_install() + { + var sut = new StartupInstallerService(); + + Assert.DoesNotThrow(() => + { + sut.Install(); + + Assert.That(sut.InstallStatus(), Is.True); + }); + } + + [Test] + public void I_can_uninstall() + { + var sut = new StartupInstallerService(); + + Assert.DoesNotThrow(() => + { + sut.Uninstall(); + + Assert.That(sut.InstallStatus(), Is.False); + }); + } +} diff --git a/YMouseButtonControl.Linux.Tests/YMouseButtonControl.Linux.Tests.csproj b/YMouseButtonControl.Linux.Tests/YMouseButtonControl.Linux.Tests.csproj new file mode 100644 index 0000000..5a52c48 --- /dev/null +++ b/YMouseButtonControl.Linux.Tests/YMouseButtonControl.Linux.Tests.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + diff --git a/YMouseButtonControl.Services.Linux/Services/StartupInstallerService.cs b/YMouseButtonControl.Services.Linux/Services/StartupInstallerService.cs new file mode 100644 index 0000000..f085323 --- /dev/null +++ b/YMouseButtonControl.Services.Linux/Services/StartupInstallerService.cs @@ -0,0 +1,60 @@ +using System.Reflection; +using YMouseButtonControl.Core.Services; + +namespace YMouseButtonControl.Services.Linux.Services; + +public class StartupInstallerService : IStartupInstallerService +{ + private const string DesktopFile = """ + [Desktop Entry] + Type=Application + Exec={0} + Hidden=false + NoDisplay=false + X-GNOME-Autostart-enabled=true + Name=YMouseButtonControl + Comment=YMouseButtonControl + """; + private readonly string _configDir = Environment.GetFolderPath( + Environment.SpecialFolder.ApplicationData + ); + private readonly string _autostartDir; + private readonly string _desktopFilePath; + + public StartupInstallerService() + { + _autostartDir = Path.Combine(_configDir, "autostart"); + _desktopFilePath = Path.Combine(_autostartDir, "YMouseButtonControl.desktop"); + } + + public bool ButtonEnabled() => Directory.Exists(_autostartDir); + + public bool InstallStatus() + { + if (!Directory.Exists(_autostartDir)) + { + return false; + } + + if (!File.Exists(_desktopFilePath)) + { + return false; + } + + var expectedExecLine = $"Exec={GetCurExePath()}"; + return File.ReadAllText(_desktopFilePath).Contains(expectedExecLine); + } + + public void Install() + { + File.WriteAllText(_desktopFilePath, string.Format(DesktopFile, GetCurExePath())); + } + + public void Uninstall() + { + File.Delete(_desktopFilePath); + } + + private static string GetCurExePath() => + Environment.ProcessPath ?? throw new Exception("Error retrieving process path"); +} diff --git a/YMouseButtonControl.Services.Linux/YMouseButtonControl.Services.Linux.csproj b/YMouseButtonControl.Services.Linux/YMouseButtonControl.Services.Linux.csproj index 3953205..72e9088 100644 --- a/YMouseButtonControl.Services.Linux/YMouseButtonControl.Services.Linux.csproj +++ b/YMouseButtonControl.Services.Linux/YMouseButtonControl.Services.Linux.csproj @@ -4,6 +4,15 @@ net8.0 enable enable + default + + + + true + + + + true diff --git a/YMouseButtonControl.Services.MacOS/CurrentWindowService.cs b/YMouseButtonControl.Services.MacOS/CurrentWindowService.cs index 6bb525f..67be727 100644 --- a/YMouseButtonControl.Services.MacOS/CurrentWindowService.cs +++ b/YMouseButtonControl.Services.MacOS/CurrentWindowService.cs @@ -110,7 +110,7 @@ public enum CFStringEncoding : uint UTF16 = 0x0100, UTF16BE = 0x10000100, UTF16LE = 0x14000100, - ASCII = 0x0600 + ASCII = 0x0600, } [StructLayout(LayoutKind.Sequential)] diff --git a/YMouseButtonControl.Services.MacOS/Services/StartupInstallerService.cs b/YMouseButtonControl.Services.MacOS/Services/StartupInstallerService.cs new file mode 100644 index 0000000..9a3678f --- /dev/null +++ b/YMouseButtonControl.Services.MacOS/Services/StartupInstallerService.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; +using System.Reflection; +using YMouseButtonControl.Core.Services; + +namespace YMouseButtonControl.Services.MacOS.Services; + +public class StartupInstallerService : IStartupInstallerService +{ + private const string PlistData = """ + + + + + Label + com.github.ymousebuttoncontrol + + ProgramArguments + + {0} + + + RunAtLoad + + + KeepAlive + + + + """; + + private readonly string _usrLaunchAgentsDir; + private readonly string _plistPath; + + public StartupInstallerService() + { + _usrLaunchAgentsDir = Path.Join( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + "Library", + "LaunchAgents" + ); + _plistPath = Path.Join(_usrLaunchAgentsDir, "com.github.ymousebuttoncontrol.plist"); + } + + public bool ButtonEnabled() => Directory.Exists(_usrLaunchAgentsDir); + + public bool InstallStatus() => + File.Exists(_plistPath) + && File.ReadAllText(_plistPath).Contains($"{GetCurExePath()}"); + + public void Install() => + File.WriteAllText(_plistPath, string.Format(PlistData, GetCurExePath())); + + public void Uninstall() => File.Delete(_plistPath); + + private static string GetCurExePath() => + Environment.ProcessPath ?? throw new Exception("Error retrieving process path"); +} diff --git a/YMouseButtonControl.Services.Windows/ProcessMonitorService.cs b/YMouseButtonControl.Services.Windows/ProcessMonitorService.cs index e42350e..3bb465a 100644 --- a/YMouseButtonControl.Services.Windows/ProcessMonitorService.cs +++ b/YMouseButtonControl.Services.Windows/ProcessMonitorService.cs @@ -37,7 +37,7 @@ public IEnumerable GetProcesses() return cb.DistinctBy(x => x.MainModule!.FileName) .Select(x => new ProcessModel(x) { - Bitmap = GetBitmapStreamFromPath(x.MainModule!.FileName) + Bitmap = GetBitmapStreamFromPath(x.MainModule!.FileName), }) .ToList(); } diff --git a/YMouseButtonControl.Services.Windows/Services/StartupInstallerService.cs b/YMouseButtonControl.Services.Windows/Services/StartupInstallerService.cs new file mode 100644 index 0000000..d8e875a --- /dev/null +++ b/YMouseButtonControl.Services.Windows/Services/StartupInstallerService.cs @@ -0,0 +1,59 @@ +using System; +using System.IO; +using System.Runtime.Versioning; +using System.Security.AccessControl; +using Microsoft.Win32; +using YMouseButtonControl.Core.Services; + +namespace YMouseButtonControl.Services.Windows.Services; + +[SupportedOSPlatform("windows")] +public class StartupInstallerService : IStartupInstallerService +{ + private const string BaseKeyPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run\"; + private const string ValName = "YMouseButtonControl"; + + public bool ButtonEnabled() => true; + + /// + /// Gets the start-up install status of the program + /// + /// True = installed, False = not installed + public bool InstallStatus() + { + using var key = Registry.CurrentUser.OpenSubKey(BaseKeyPath, RegistryRights.ReadKey); + var keyVal = key?.GetValue(ValName)?.ToString(); + if (string.IsNullOrWhiteSpace(keyVal)) + { + return false; + } + + return keyVal.Trim('"') == GetCurExePath(); + } + + public void Install() + { + using var key = + Registry.CurrentUser.OpenSubKey( + BaseKeyPath, + RegistryKeyPermissionCheck.ReadWriteSubTree, + RegistryRights.FullControl + ) ?? throw new Exception($"Error opening key {BaseKeyPath}"); + var newKeyVal = $"\"{Path.Join(GetCurExePath())}\""; + key.SetValue(ValName, newKeyVal); + } + + public void Uninstall() + { + using var key = + Registry.CurrentUser.OpenSubKey( + BaseKeyPath, + RegistryKeyPermissionCheck.ReadWriteSubTree, + RegistryRights.FullControl + ) ?? throw new Exception($"Error opening key {BaseKeyPath}"); + key.DeleteValue(ValName); + } + + private static string GetCurExePath() => + Environment.ProcessPath ?? throw new Exception("Error retrieving process path"); +} diff --git a/YMouseButtonControl.Windows.Tests/StartupInstallerTests.cs b/YMouseButtonControl.Windows.Tests/StartupInstallerTests.cs new file mode 100644 index 0000000..eb1c778 --- /dev/null +++ b/YMouseButtonControl.Windows.Tests/StartupInstallerTests.cs @@ -0,0 +1,46 @@ +using System.Runtime.Versioning; +using YMouseButtonControl.Services.Windows.Services; + +namespace YMouseButtonControl.Windows.Tests; + +[SupportedOSPlatform("windows")] +[Explicit] +public class StartupInstallerTests +{ + [Test] + public void I_can_get_startup_install_status() + { + var sut = new StartupInstallerService(); + + Assert.DoesNotThrow(() => + { + sut.InstallStatus(); + }); + } + + [Test] + public void I_can_install() + { + var sut = new StartupInstallerService(); + + Assert.DoesNotThrow(() => + { + sut.Install(); + + Assert.That(sut.InstallStatus(), Is.True); + }); + } + + [Test] + public void I_can_uninstall() + { + var sut = new StartupInstallerService(); + + Assert.DoesNotThrow(() => + { + sut.Uninstall(); + + Assert.That(sut.InstallStatus(), Is.False); + }); + } +} diff --git a/YMouseButtonControl.Windows.Tests/YMouseButtonControl.Windows.Tests.csproj b/YMouseButtonControl.Windows.Tests/YMouseButtonControl.Windows.Tests.csproj new file mode 100644 index 0000000..8d292f4 --- /dev/null +++ b/YMouseButtonControl.Windows.Tests/YMouseButtonControl.Windows.Tests.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + diff --git a/YMouseButtonControl.sln b/YMouseButtonControl.sln index 93715d1..d18a7b4 100644 --- a/YMouseButtonControl.sln +++ b/YMouseButtonControl.sln @@ -19,6 +19,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YMouseButtonControl.Core.Te EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YMouseButtonControl.Services.Linux", "YMouseButtonControl.Services.Linux\YMouseButtonControl.Services.Linux.csproj", "{EE958A9F-3768-44CE-A08E-F692CB403BB8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YMouseButtonControl.Windows.Tests", "YMouseButtonControl.Windows.Tests\YMouseButtonControl.Windows.Tests.csproj", "{1BDE4035-2584-48AC-A6CE-56136F589B52}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YMouseButtonControl.Linux.Tests", "YMouseButtonControl.Linux.Tests\YMouseButtonControl.Linux.Tests.csproj", "{41338DC0-4BB9-4776-A976-8F840015DAA3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,6 +61,14 @@ Global {EE958A9F-3768-44CE-A08E-F692CB403BB8}.Debug|Any CPU.Build.0 = Debug|Any CPU {EE958A9F-3768-44CE-A08E-F692CB403BB8}.Release|Any CPU.ActiveCfg = Release|Any CPU {EE958A9F-3768-44CE-A08E-F692CB403BB8}.Release|Any CPU.Build.0 = Release|Any CPU + {1BDE4035-2584-48AC-A6CE-56136F589B52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1BDE4035-2584-48AC-A6CE-56136F589B52}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1BDE4035-2584-48AC-A6CE-56136F589B52}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1BDE4035-2584-48AC-A6CE-56136F589B52}.Release|Any CPU.Build.0 = Release|Any CPU + {41338DC0-4BB9-4776-A976-8F840015DAA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41338DC0-4BB9-4776-A976-8F840015DAA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41338DC0-4BB9-4776-A976-8F840015DAA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41338DC0-4BB9-4776-A976-8F840015DAA3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/YMouseButtonControl/App.axaml b/YMouseButtonControl/App.axaml index de948bd..3a3a2f5 100644 --- a/YMouseButtonControl/App.axaml +++ b/YMouseButtonControl/App.axaml @@ -1,7 +1,8 @@ + x:Class="YMouseButtonControl.App" + Name="YMouseButtonControl"> @@ -17,6 +18,10 @@ + + diff --git a/YMouseButtonControl/App.axaml.cs b/YMouseButtonControl/App.axaml.cs index c9dae2f..3c63f06 100644 --- a/YMouseButtonControl/App.axaml.cs +++ b/YMouseButtonControl/App.axaml.cs @@ -10,10 +10,13 @@ using Splat; using Splat.Microsoft.Extensions.DependencyInjection; using YMouseButtonControl.Configuration; +using YMouseButtonControl.Core; +using YMouseButtonControl.Core.Profiles.Implementations; +using YMouseButtonControl.Core.Profiles.Interfaces; using YMouseButtonControl.Core.Services.BackgroundTasks; -using YMouseButtonControl.Core.ViewModels.Implementations; using YMouseButtonControl.Core.ViewModels.Interfaces; using YMouseButtonControl.Core.ViewModels.MainWindow; +using YMouseButtonControl.Core.Views; using YMouseButtonControl.DependencyInjection; using YMouseButtonControl.Views; @@ -24,15 +27,8 @@ public class App : Application public IServiceProvider? Container { get; private set; } private IBackgroundTasksRunner? _backgroundTasksRunner; - public App() - { - DataContext = new AppViewModel(); - } - public override void Initialize() { - Init(); - _backgroundTasksRunner = Container?.GetRequiredService(); AvaloniaXamlLoader.Load(this); } @@ -63,18 +59,27 @@ private void Init() public override void OnFrameworkInitializationCompleted() { + Init(); + DataContext = Container?.GetRequiredService(); + _backgroundTasksRunner = Container?.GetRequiredService(); + var settingsService = + Container?.GetRequiredService() + ?? throw new Exception($"Error retrieving {nameof(ISettingsService)}"); + var startMinimized = + settingsService.GetSetting("StartMinimized") + ?? throw new Exception($"Error retrieving setting StartMinimized"); + var startMinimizedValue = bool.Parse(startMinimized.Value ?? "false"); + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - desktop.MainWindow = new MainWindow - { - DataContext = Container?.GetRequiredService() - }; - // Prevent the application from exiting and hide the window when the user presses the X button - desktop.MainWindow.Closing += (s, e) => + if (!startMinimizedValue) { - ((Window)s!).Hide(); - e.Cancel = true; - }; + var mainWindow = (Window)Container.GetRequiredService(); + mainWindow.DataContext = Container.GetRequiredService(); + desktop.MainWindow = mainWindow; + } + + desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown; desktop.Exit += (sender, args) => { diff --git a/YMouseButtonControl/Converters/SettingValueConverter.cs b/YMouseButtonControl/Converters/SettingValueConverter.cs new file mode 100644 index 0000000..086bea7 --- /dev/null +++ b/YMouseButtonControl/Converters/SettingValueConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Globalization; +using Avalonia.Data.Converters; + +namespace YMouseButtonControl.Converters; + +public class SettingValueConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is not string) + { + return null; + } + + if (value is not string valStr) + { + return null; + } + return bool.Parse(valStr); + } + + public object? ConvertBack( + object? value, + Type targetType, + object? parameter, + CultureInfo culture + ) + { + return value?.ToString(); + } +} diff --git a/YMouseButtonControl/DependencyInjection/Bootstrapper.cs b/YMouseButtonControl/DependencyInjection/Bootstrapper.cs index 84561bd..13b46b7 100644 --- a/YMouseButtonControl/DependencyInjection/Bootstrapper.cs +++ b/YMouseButtonControl/DependencyInjection/Bootstrapper.cs @@ -16,5 +16,6 @@ DataAccessConfiguration dataAccessConfig KeyboardAndMouseBootstrapper.RegisterKeyboardAndMouse(services); FeaturesBootstrapper.RegisterFeatures(services); ViewModelsBootstrapper.RegisterViewModels(services); + ViewBootstrapper.RegisterViews(services); } } diff --git a/YMouseButtonControl/DependencyInjection/ConfigurationBootstrapper.cs b/YMouseButtonControl/DependencyInjection/ConfigurationBootstrapper.cs index 5f9ff62..46931c6 100644 --- a/YMouseButtonControl/DependencyInjection/ConfigurationBootstrapper.cs +++ b/YMouseButtonControl/DependencyInjection/ConfigurationBootstrapper.cs @@ -32,7 +32,7 @@ DataAccessConfiguration dataAccessConfig services.AddTransient(_ => new DatabaseConfiguration { ConnectionString = GetDatabaseConnectionString(configuration), - UseInMemoryDatabase = dataAccessConfig.UseInMemoryDatabase + UseInMemoryDatabase = dataAccessConfig.UseInMemoryDatabase, }); } diff --git a/YMouseButtonControl/DependencyInjection/ServicesBootstrapper.cs b/YMouseButtonControl/DependencyInjection/ServicesBootstrapper.cs index 4a49911..db70f6f 100644 --- a/YMouseButtonControl/DependencyInjection/ServicesBootstrapper.cs +++ b/YMouseButtonControl/DependencyInjection/ServicesBootstrapper.cs @@ -4,6 +4,7 @@ using YMouseButtonControl.Core.Processes; using YMouseButtonControl.Core.Profiles.Implementations; using YMouseButtonControl.Core.Profiles.Interfaces; +using YMouseButtonControl.Core.Services; using YMouseButtonControl.Core.Services.BackgroundTasks; using YMouseButtonControl.Services.MacOS; using YMouseButtonControl.Services.Windows; @@ -21,6 +22,7 @@ public static void RegisterServices(IServiceCollection services) private static void RegisterCommonServices(IServiceCollection services) { services.AddSingleton(); + services.AddSingleton(); } private static void RegisterPlatformSpecificServices(IServiceCollection services) @@ -45,6 +47,10 @@ private static void RegisterPlatformSpecificServices(IServiceCollection services private static void RegisterLinuxServices(IServiceCollection services) { + services.AddSingleton< + IStartupInstallerService, + Services.Linux.Services.StartupInstallerService + >(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -53,6 +59,10 @@ private static void RegisterLinuxServices(IServiceCollection services) [SupportedOSPlatform("windows5.1.2600")] private static void RegisterWindowsServices(IServiceCollection services) { + services.AddSingleton< + IStartupInstallerService, + Services.Windows.Services.StartupInstallerService + >(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -60,6 +70,10 @@ private static void RegisterWindowsServices(IServiceCollection services) private static void RegisterMacOsServices(IServiceCollection services) { + services.AddSingleton< + IStartupInstallerService, + Services.MacOS.Services.StartupInstallerService + >(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/YMouseButtonControl/DependencyInjection/ViewBootstrapper.cs b/YMouseButtonControl/DependencyInjection/ViewBootstrapper.cs new file mode 100644 index 0000000..71d1a70 --- /dev/null +++ b/YMouseButtonControl/DependencyInjection/ViewBootstrapper.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.DependencyInjection; +using YMouseButtonControl.Core.Views; +using YMouseButtonControl.Views; + +namespace YMouseButtonControl.DependencyInjection; + +public static class ViewBootstrapper +{ + public static void RegisterViews(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + } +} diff --git a/YMouseButtonControl/DependencyInjection/ViewModelsBootstrapper.cs b/YMouseButtonControl/DependencyInjection/ViewModelsBootstrapper.cs index f6f9f4c..65c436c 100644 --- a/YMouseButtonControl/DependencyInjection/ViewModelsBootstrapper.cs +++ b/YMouseButtonControl/DependencyInjection/ViewModelsBootstrapper.cs @@ -4,6 +4,7 @@ using YMouseButtonControl.Core.ViewModels.Interfaces; using YMouseButtonControl.Core.ViewModels.Interfaces.Dialogs; using YMouseButtonControl.Core.ViewModels.MainWindow; +using YMouseButtonControl.Core.ViewModels.ProfilesList; using YMouseButtonControl.Core.ViewModels.Services; namespace YMouseButtonControl.DependencyInjection; @@ -18,6 +19,7 @@ public static void RegisterViewModels(IServiceCollection services) private static void RegisterCommonViewModels(IServiceCollection services) { services.AddTransient(); + services.AddTransient(); services.AddSingleton(); services.AddSingleton< IShowSimulatedKeystrokesDialogService, diff --git a/YMouseButtonControl/Program.cs b/YMouseButtonControl/Program.cs index 96eb7cf..9e4663c 100644 --- a/YMouseButtonControl/Program.cs +++ b/YMouseButtonControl/Program.cs @@ -1,4 +1,5 @@ -using Avalonia; +using System; +using Avalonia; using Avalonia.ReactiveUI; using Serilog; @@ -16,7 +17,15 @@ public static void Main(string[] args) .CreateLogger(); Log.Logger = log; - BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); + try + { + BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); + } + catch (Exception e) + { + Log.Error(e.Message); + throw; + } } private static AppBuilder BuildAvaloniaApp() => diff --git a/YMouseButtonControl/Views/Dialogs/GlobalSettingsDialog.axaml b/YMouseButtonControl/Views/Dialogs/GlobalSettingsDialog.axaml new file mode 100644 index 0000000..f8e893c --- /dev/null +++ b/YMouseButtonControl/Views/Dialogs/GlobalSettingsDialog.axaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/YMouseButtonControl/Views/Dialogs/GlobalSettingsDialog.axaml.cs b/YMouseButtonControl/Views/Dialogs/GlobalSettingsDialog.axaml.cs new file mode 100644 index 0000000..58ee608 --- /dev/null +++ b/YMouseButtonControl/Views/Dialogs/GlobalSettingsDialog.axaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; + +namespace YMouseButtonControl.Views.Dialogs; + +public partial class GlobalSettingsDialog : Window +{ + public GlobalSettingsDialog() + { + InitializeComponent(); + } + + private void Cancel_OnClick(object? sender, RoutedEventArgs e) + { + Close(); + } +} diff --git a/YMouseButtonControl/Views/LayerView.axaml b/YMouseButtonControl/Views/LayerView.axaml index 08f3d9e..2e49845 100644 --- a/YMouseButtonControl/Views/LayerView.axaml +++ b/YMouseButtonControl/Views/LayerView.axaml @@ -16,7 +16,7 @@