Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filtered Processes #177

Merged
merged 3 commits into from
Apr 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ src/Carnac.sln.ide/

# Cake - Uncomment if you are using it
tools/**
!tools/packages.config
!tools/packages.config
/src/.vs/Carnac/v15/Server/sqlite3
49 changes: 48 additions & 1 deletion src/Carnac.Logic/KeyProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using Carnac.Logic.Models;
using Microsoft.Win32;
using System.Windows.Media;
using SettingsProviderNet;
using System.Text.RegularExpressions;

namespace Carnac.Logic
{
Expand All @@ -19,6 +21,9 @@ public class KeyProvider : IKeyProvider
readonly Dictionary<int, Process> processes;
readonly IPasswordModeService passwordModeService;
readonly IDesktopLockEventService desktopLockEventService;
readonly PopupSettings settings;
string currentFilter = null;
Regex filterRegex;

private readonly IList<Keys> modifierKeys =
new List<Keys>
Expand All @@ -44,12 +49,45 @@ public class KeyProvider : IKeyProvider
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

public KeyProvider(IInterceptKeys interceptKeysSource, IPasswordModeService passwordModeService, IDesktopLockEventService desktopLockEventService)
public KeyProvider(IInterceptKeys interceptKeysSource, IPasswordModeService passwordModeService, IDesktopLockEventService desktopLockEventService, ISettingsProvider settingsProvider)
{
if (settingsProvider == null)
{
throw new ArgumentNullException(nameof(settingsProvider));
}

processes = new Dictionary<int, Process>();
this.interceptKeysSource = interceptKeysSource;
this.passwordModeService = passwordModeService;
this.desktopLockEventService = desktopLockEventService;

settings = settingsProvider.GetSettings<PopupSettings>();
}

private bool EnsureProcessFilter()
{
if (settings?.ProcessFilterExpression != currentFilter)
{
currentFilter = settings.ProcessFilterExpression;

if (!String.IsNullOrEmpty(currentFilter))
{
try
{
filterRegex = new Regex(currentFilter, RegexOptions.IgnoreCase | RegexOptions.Compiled, TimeSpan.FromSeconds(1));
}
catch
{
filterRegex = null;
}
}
else
{
filterRegex = null;
}
}

return (filterRegex != null);
}

public IObservable<KeyPress> GetKeyStream()
Expand Down Expand Up @@ -104,6 +142,15 @@ KeyPress ToCarnacKeyPress(InterceptKeyEventArgs interceptKeyEventArgs)
return null;
}

// see if this process is one being filtered for
if (EnsureProcessFilter())
{
if (!filterRegex.IsMatch(process.ProcessName))
{
return null;
}
}

var isLetter = interceptKeyEventArgs.IsLetter();
var inputs = ToInputs(isLetter, winKeyPressed, interceptKeyEventArgs).ToArray();
try
Expand Down
3 changes: 3 additions & 0 deletions src/Carnac.Logic/Models/PopupSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ protected void OnLeftChanged(EventArgs e)
[NotifyProperty(AlsoNotifyFor = new[] { "Margins" })]
public int RightOffset { get; set; }

[DefaultValue("")]
public string ProcessFilterExpression { get; set; }

public double ScaleTransform
{
get { return Placement == NotificationPlacement.TopLeft || Placement == NotificationPlacement.TopRight ? 1 : -1; }
Expand Down
50 changes: 43 additions & 7 deletions src/Carnac.Tests/KeyProviderTests.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System.Linq;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Carnac.Logic;
using Carnac.Logic.KeyMonitor;
using Carnac.Logic.Models;
using Microsoft.Win32;
using NSubstitute;
using SettingsProviderNet;
using Xunit;

namespace Carnac.Tests
Expand All @@ -13,20 +16,22 @@ public class KeyProviderTests
{
readonly IPasswordModeService passwordModeService;
readonly IDesktopLockEventService desktopLockEventService;
readonly ISettingsProvider settingsProvider;

public KeyProviderTests()
{
passwordModeService = new PasswordModeService();
desktopLockEventService = Substitute.For<IDesktopLockEventService>();
desktopLockEventService.GetSessionSwitchStream().Returns(Observable.Never<SessionSwitchEventArgs>());
settingsProvider = Substitute.For<ISettingsProvider>();
}

[Fact]
public async Task ctrlshiftl_is_processed_correctly()
{
// arrange
var player = KeyStreams.CtrlShiftL();
var provider = new KeyProvider(player, passwordModeService, desktopLockEventService);
var provider = new KeyProvider(player, passwordModeService, desktopLockEventService, settingsProvider);

// act
var processedKeys = await provider.GetKeyStream().ToList();
Expand All @@ -40,7 +45,7 @@ public async Task shift_is_not_outputted_when_is_being_used_as_a_modifier_key()
{
// arrange
var player = KeyStreams.ShiftL();
var provider = new KeyProvider(player, passwordModeService, desktopLockEventService);
var provider = new KeyProvider(player, passwordModeService, desktopLockEventService, settingsProvider);

// act
var processedKeys = await provider.GetKeyStream().ToList();
Expand All @@ -55,7 +60,7 @@ public async Task key_without_shift_is_lowercase()
{
// arrange
var player = KeyStreams.LetterL();
var provider = new KeyProvider(player, passwordModeService, desktopLockEventService);
var provider = new KeyProvider(player, passwordModeService, desktopLockEventService, settingsProvider);

// act
var processedKeys = await provider.GetKeyStream().ToList();
Expand All @@ -69,7 +74,7 @@ public async Task verify_number()
{
// arrange
var player = KeyStreams.Number1();
var provider = new KeyProvider(player, passwordModeService, desktopLockEventService);
var provider = new KeyProvider(player, passwordModeService, desktopLockEventService, settingsProvider);

// act
var processedKeys = await provider.GetKeyStream().ToList();
Expand All @@ -83,7 +88,7 @@ public async Task verify_shift_number()
{
// arrange
var player = KeyStreams.ExclaimationMark();
var provider = new KeyProvider(player, passwordModeService, desktopLockEventService);
var provider = new KeyProvider(player, passwordModeService, desktopLockEventService, settingsProvider);

// act
var processedKeys = await provider.GetKeyStream().ToList();
Expand All @@ -97,13 +102,44 @@ public async Task keyprovider_detects_windows_key_presses()
{
// arrange
var player = KeyStreams.WinkeyE();
var provider = new KeyProvider(player, passwordModeService, desktopLockEventService);
var provider = new KeyProvider(player, passwordModeService, desktopLockEventService, settingsProvider);

// act
var processedKeys = await provider.GetKeyStream().ToList();

// assert
Assert.Equal(new[] { "Win", "e" }, processedKeys.Single().Input);
}

[Fact]
public async Task output_with_matching_filter()
{
// arrange
string currentProcessName = Process.GetCurrentProcess().ProcessName;
settingsProvider.GetSettings<PopupSettings>().Returns(new PopupSettings() { ProcessFilterExpression = currentProcessName });
var player = KeyStreams.LetterL();
var provider = new KeyProvider(player, passwordModeService, desktopLockEventService, settingsProvider);

// act
var processedKeys = await provider.GetKeyStream().ToList();

// assert
Assert.Equal(new[] { "l" }, processedKeys.Single().Input);
}

[Fact]
public async Task no_output_with_no_match_filter()
{
// arrange
settingsProvider.GetSettings<PopupSettings>().Returns(new PopupSettings() { ProcessFilterExpression = "notepad" });
var player = KeyStreams.LetterL();
var provider = new KeyProvider(player, passwordModeService, desktopLockEventService, settingsProvider);

// act
var processedKeys = await provider.GetKeyStream().ToList();

// assert
Assert.Equal(0, processedKeys.Count);
}
}
}
4 changes: 3 additions & 1 deletion src/Carnac.Tests/MessageProviderFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Carnac.Logic.Models;
using Microsoft.Win32;
using NSubstitute;
using SettingsProviderNet;
using Xunit;

namespace Carnac.Tests
Expand All @@ -30,8 +31,9 @@ MessageProvider CreateMessageProvider(IObservable<InterceptKeyEventArgs> keysStr
var source = Substitute.For<IInterceptKeys>();
source.GetKeyStream().Returns(keysStreamSource);
var desktopLockEventService = Substitute.For<IDesktopLockEventService>();
var settingsProvider = Substitute.For<ISettingsProvider>();
desktopLockEventService.GetSessionSwitchStream().Returns(Observable.Never<SessionSwitchEventArgs>());
var keyProvider = new KeyProvider(source, new PasswordModeService(), desktopLockEventService);
var keyProvider = new KeyProvider(source, new PasswordModeService(), desktopLockEventService, settingsProvider);
return new MessageProvider(shortcutProvider, keyProvider, new PopupSettings());
}

Expand Down
2 changes: 1 addition & 1 deletion src/Carnac/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ public partial class App

public App()
{
var keyProvider = new KeyProvider(InterceptKeys.Current, new PasswordModeService(), new DesktopLockEventService());
settingsProvider = new SettingsProvider(new RoamingAppDataStorage("Carnac"));
settings = settingsProvider.GetSettings<PopupSettings>();
var keyProvider = new KeyProvider(InterceptKeys.Current, new PasswordModeService(), new DesktopLockEventService(), settingsProvider);
messageProvider = new MessageProvider(new ShortcutProvider(), keyProvider, settings);
}

Expand Down
8 changes: 7 additions & 1 deletion src/Carnac/UI/PreferencesView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
xmlns:utilities="clr-namespace:Carnac.Utilities"
xmlns:carnac="clr-namespace:Carnac"
x:Class="Carnac.UI.PreferencesView"
Width="610" Height="420" Icon="../icon.ico"
Width="610" Height="430" Icon="../icon.ico"
Foreground="{DynamicResource BlackBrush}"
d:DataContext="{d:DesignInstance ui:PreferencesViewModel}" mc:Ignorable="d"
ShowTitleBar="False" ShowMinButton="False" ShowMaxRestoreButton="False"
Expand Down Expand Up @@ -150,6 +150,12 @@
<ui:PreferencesField Header="Show Application Icon">
<CheckBox IsChecked="{Binding Settings.ShowApplicationIcon}" />
</ui:PreferencesField>
<ui:PreferencesField Header="Process Filter">
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding Settings.ProcessFilterExpression}"></TextBox>
<TextBlock HorizontalAlignment="Right">Only show keys from processes matching this regular expression</TextBlock>
</StackPanel>
</ui:PreferencesField>
</StackPanel>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" Grid.Row="1" Margin="5">
<Button Width="150" Margin="0 0 5 0" Content="Reset to Defaults" Command="{Binding ResetToDefaultsCommand}" />
Expand Down