diff --git a/src/AutoCursorLock.App/AutoCursorLockException.cs b/src/AutoCursorLock.App/AutoCursorLockException.cs index 8a4eb58..5998994 100644 --- a/src/AutoCursorLock.App/AutoCursorLockException.cs +++ b/src/AutoCursorLock.App/AutoCursorLockException.cs @@ -18,9 +18,4 @@ public AutoCursorLockException(string message) : base(message) { } - - public AutoCursorLockException(string message, Exception innerException) - : base(message, innerException) - { - } } diff --git a/src/AutoCursorLock.App/Models/ProcessListItem.cs b/src/AutoCursorLock.App/Models/ProcessListItem.cs index 85432a6..e281c8e 100644 --- a/src/AutoCursorLock.App/Models/ProcessListItem.cs +++ b/src/AutoCursorLock.App/Models/ProcessListItem.cs @@ -19,13 +19,15 @@ public record ProcessListItem /// The path of the proceess' executable. /// The type of app lock. /// The margin around the window for the app lock. + /// The interval in milliseconds before reapplying the lock while the window is in focus. /// The icon image of the process. - public ProcessListItem(string name, string? path, AppLockType appLockType, AppLockMargin? margin, BitmapSource? icon) + public ProcessListItem(string name, string? path, AppLockType appLockType, AppLockMargin? margin, int reapplyLockInterval, BitmapSource? icon) { Name = name ?? throw new ArgumentNullException(nameof(name)); Path = path; AppLockType = appLockType; Margin = margin ?? new AppLockMargin(0, 0, 0, 0); + ReapplyLockInterval = reapplyLockInterval; Icon = icon; } @@ -49,6 +51,15 @@ public ProcessListItem(string name, string? path, AppLockType appLockType, AppLo /// public AppLockMargin Margin { get; init; } + /// + /// Gets the interval in milliseconds before reapplying the lock while the window is in focus. + /// + /// + /// A value of 0 disables reapplying the lock. + /// The lock will only be applied once, when the window is first focused. + /// + public int ReapplyLockInterval { get; init; } + /// /// Gets the icon image of the process. /// diff --git a/src/AutoCursorLock.App/Models/ProcessListItemExtensions.cs b/src/AutoCursorLock.App/Models/ProcessListItemExtensions.cs index aff3bce..b1efa98 100644 --- a/src/AutoCursorLock.App/Models/ProcessListItemExtensions.cs +++ b/src/AutoCursorLock.App/Models/ProcessListItemExtensions.cs @@ -29,7 +29,7 @@ public static ProcessListItem FromPath(string path) .FirstOrDefault() ?? throw new AutoCursorLockException($"Could not find process for path: {path}"); - return FromAppLockSettings(new AppLockSettingsModel(process.ProcessName, path, AppLockType.Window, Margin: default)); + return FromAppLockSettings(new AppLockSettingsModel(process.ProcessName, path, AppLockType.Window, Margin: default, ReapplyLockInterval: default)); } /// @@ -52,7 +52,7 @@ public static ProcessListItem FromAppLockSettings(AppLockSettingsModel appLockSe bitmapIcon = new BitmapImage(new Uri("pack://application:,,,/media/question-mark.png", UriKind.Absolute)); } - return new ProcessListItem(appLockSettings.Name, appLockSettings.Path, appLockSettings.Type, appLockSettings.Margin, bitmapIcon); + return new ProcessListItem(appLockSettings.Name, appLockSettings.Path, appLockSettings.Type, appLockSettings.Margin, appLockSettings.ReapplyLockInterval, bitmapIcon); } /// @@ -66,7 +66,8 @@ public static AppLockSettingsModel ToModel(this ProcessListItem processListItem) processListItem.Name, processListItem.Path, processListItem.AppLockType, - processListItem.Margin + processListItem.Margin, + processListItem.ReapplyLockInterval ); } diff --git a/src/AutoCursorLock.App/Views/AppLockSettingsWindow.xaml b/src/AutoCursorLock.App/Views/AppLockSettingsWindow.xaml index 14d03e6..7cffc5b 100644 --- a/src/AutoCursorLock.App/Views/AppLockSettingsWindow.xaml +++ b/src/AutoCursorLock.App/Views/AppLockSettingsWindow.xaml @@ -5,17 +5,18 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:AutoCursorLock.App.Views" mc:Ignorable="d" - Title="App Lock Settings" Height="300" Width="600"> - + Title="App Lock Settings" Height="400" Width="600"> + + - + @@ -46,7 +47,8 @@ SelectedItem="{Binding AppLockSettings.AppLockType}" /> + Grid.Row="1" + Margin="5"> Left-side margin + Grid.Row="2" + Margin="5"> Top-side margin + Grid.Row="3" + Margin="5"> Right-side margin + Grid.Row="4" + Margin="5"> Bottom-side margin + + + Lock re-apply interval. + The interval in milliseconds the cursor lock should be re-applied as long as the application is in focus. + Use this for applications that clear the cursor lock. + + + Set to 0 to disable the re-application. This means the cursor lock will only be applied once, right after the application is focused. + + + Small values may cause performance issues. + Large values may cause the cursor to be unlocked for a short period of time. + 10-100 milliseconds seems to be a good range for most applications. + + + + diff --git a/src/AutoCursorLock.App/Views/MainWindow.xaml.cs b/src/AutoCursorLock.App/Views/MainWindow.xaml.cs index 02f888b..92a68b1 100644 --- a/src/AutoCursorLock.App/Views/MainWindow.xaml.cs +++ b/src/AutoCursorLock.App/Views/MainWindow.xaml.cs @@ -9,6 +9,7 @@ namespace AutoCursorLock.App.Views; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; +using System.Threading; using System.Windows; using System.Windows.Controls; using System.Windows.Input; @@ -29,7 +30,8 @@ public partial class MainWindow : Window, INotifyPropertyChanged private readonly SaveUserSettingsOperation saveUserSettingsOperation; private readonly ILogger logger; - private ApplicationEventSource applicationEventSource; + private readonly ApplicationEventSource applicationEventSource; + private readonly object activeProcessLock = new (); private bool globalLockEnabled = true; private bool applicationLockEnabled = false; @@ -140,21 +142,29 @@ public ProcessListItem? ActiveProcess { get { - return this.activeProcess; + lock (this.activeProcessLock) + { + return this.activeProcess; + } } set { - this.activeProcess = value; - ApplicationLockEnabled = value != null; - NotifyPropertyChanged(nameof(ActiveProcess)); + lock (this.activeProcessLock) + { + this.activeProcess = value; + ApplicationLockEnabled = value != null; + NotifyPropertyChanged(nameof(ActiveProcess)); + } } } private IntPtr Hwnd => this.windowInteropHelper.Handle; + private Timer? ReapplyTimer { get; set; } + /// - /// Gets the list of enabled processes. + /// Runs when the Window handle is available. /// /// Event args. protected override void OnSourceInitialized(EventArgs e) @@ -198,25 +208,35 @@ private void OnApplicationChanged(object? sender, ApplicationEventArgs e) ActiveProcess = UserSettings.EnabledProcesses.FirstOrDefault(x => x.Name == e.ProcessName); + // dispose previous timer + ReapplyTimer?.Dispose(); + + // set up new timer if needed + if (ActiveProcess is not null && ActiveProcess.ReapplyLockInterval != 0) + { + ReapplyTimer = new Timer(_ => AdjustLock(e.Handle), null, ActiveProcess.ReapplyLockInterval, ActiveProcess.ReapplyLockInterval); + } + AdjustLock(e.Handle); } private void AdjustLock(IntPtr hwnd) { - if (GlobalLockEnabled && ActiveProcess is not null) + var activeProcess = ActiveProcess; + if (GlobalLockEnabled && activeProcess is not null) { - this.logger.LogInformation("Locking cursor to {ProcessName} {AppLockType}", ActiveProcess.Name, ActiveProcess.AppLockType); + this.logger.LogInformation("Locking cursor to {ProcessName} {AppLockType}", activeProcess.Name, activeProcess.AppLockType); - var border = ActiveProcess.AppLockType switch + var border = activeProcess.AppLockType switch { AppLockType.Window => ApplicationHandler.GetApplicationBorders(hwnd), AppLockType.Monitor => ApplicationHandler.GetMonitorBorders(hwnd), _ => throw new NotImplementedException("Invalid AppLockType") }; - if (ActiveProcess.Margin is not null) + if (activeProcess.Margin is not null) { - border = border.ApplyMargin(ActiveProcess.Margin); + border = border.ApplyMargin(activeProcess.Margin); } if (!MouseHandler.LockCursorToBorder(border)) @@ -328,7 +348,6 @@ private void RefreshButton_Click(object sender, RoutedEventArgs e) this.logger.LogError(ex2, "Failed to create process list item for process: {ProcessName}", p.ProcessName); } } - } } diff --git a/src/AutoCursorLock.Sdk/Models/AppLockSettingsModel.cs b/src/AutoCursorLock.Sdk/Models/AppLockSettingsModel.cs index a20b021..04de8d9 100644 --- a/src/AutoCursorLock.Sdk/Models/AppLockSettingsModel.cs +++ b/src/AutoCursorLock.Sdk/Models/AppLockSettingsModel.cs @@ -10,11 +10,13 @@ namespace AutoCursorLock.Sdk.Models; /// The path to the application. /// The type of app lock to use. /// The margin around the window for the app lock. +/// The interval in milliseconds before reapplying the lock while the window is in focus. public record AppLockSettingsModel( string Name, string? Path, AppLockType Type, - AppLockMargin? Margin + AppLockMargin? Margin, + int ReapplyLockInterval ) { /// @@ -25,6 +27,6 @@ public record AppLockSettingsModel( /// The model. public static AppLockSettingsModel FromNameAndPath(string name, string? path) { - return new AppLockSettingsModel(name, path, AppLockType.Window, new AppLockMargin(0, 0, 0, 0)); + return new AppLockSettingsModel(name, path, AppLockType.Window, new AppLockMargin(0, 0, 0, 0), ReapplyLockInterval: default); } } diff --git a/src/AutoCursorLock.Sdk/Models/HotKeyModel.cs b/src/AutoCursorLock.Sdk/Models/HotKeyModel.cs index 07d999b..76f8a58 100644 --- a/src/AutoCursorLock.Sdk/Models/HotKeyModel.cs +++ b/src/AutoCursorLock.Sdk/Models/HotKeyModel.cs @@ -11,13 +11,13 @@ namespace AutoCursorLock.Sdk.Models; public record HotKeyModel { /// - /// THe modifiers for the hot key. + /// Gets the modifiers for the hot key. /// [JsonRequired] required public ModifierKey[] Modifiers { get; init; } /// - /// The virtual key code for the hot key. + /// Gets the virtual key code for the hot key. /// [JsonRequired] required public int VirtualKey { get; init; }