Skip to content

Commit

Permalink
Implemented ordinal (non reactive) sequnce and chord detection.
Browse files Browse the repository at this point in the history
  • Loading branch information
gmamaladze committed Jan 22, 2018
1 parent 53ffe07 commit 8067522
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 104 deletions.
31 changes: 1 addition & 30 deletions MouseKeyHook.Rx/KeyObserverExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public static IObservable<TriggerChord> MatchingLongest(this IObservable<Keys> s
{
var sortedTriggers = triggers
.GroupBy(t => t.TriggerKey)
.Select(group => new KeyValuePair<Keys, IEnumerable<TriggerChord>>(group.Key, group.OrderBy(t => -t.Length)))
.Select(group => new KeyValuePair<Keys, IEnumerable<TriggerChord>>(group.Key, group.OrderBy(t => -t.Count())))
.ToDictionary(pair => pair.Key, pair => pair.Value);

return source
Expand All @@ -67,34 +67,5 @@ public static IObservable<TriggerChord> MatchingLongest(this IObservable<Keys> s
.Select(se => sortedTriggers[se.KeyCode].First(se.Matches));
}

public static IObservable<Sequence<T>> Sequences<T>(this IObservable<T> source, int minLength, int maxLength)
{
return Enumerable
.Range(minLength, maxLength-1)
.Select(n => source
.Buffer(n, 1))
.Merge()
.Select(s => new Sequence<T>(s.ToArray()));
}

public static IObservable<IEnumerable<T>> Matching<T>(this IObservable<T> source,
IEnumerable<IEnumerable<T>> whitelist)
{
var min = whitelist.Select(e => e.Count()).Min();
var max = whitelist.Select(e => e.Count()).Max();

return source.Sequences(min, max)
.SelectMany(sequence => whitelist.Where(w => w.SequenceEqual(sequence)));
}

public static IObservable<Sequence<T>> Matching<T>(this IObservable<T> source, ISet<Sequence<T>> whitelist)
{
var min = whitelist.Select(e => e.Count()).Min();
var max = whitelist.Select(e => e.Count()).Max();

return source
.Sequences(min, max)
.Where(whitelist.Contains);
}
}
}
17 changes: 0 additions & 17 deletions MouseKeyHook.Rx/KeySequence.cs

This file was deleted.

2 changes: 0 additions & 2 deletions MouseKeyHook.Rx/MouseKeyHook.Rx.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="KeySequence.cs" />
<Compile Include="KeyWithState.cs" />
<Compile Include="Sequence.cs" />
<Compile Include="KeyObserverExtensions.cs" />
<Compile Include="KeyEvent.cs" />
<Compile Include="KeyEventKind.cs" />
Expand Down
10 changes: 1 addition & 9 deletions MouseKeyHook/Chord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,7 @@ public class Chord : IEnumerable<Keys>

internal Chord(IEnumerable<Keys> additionalKeys)
{
_keys = new Stack<Keys>(additionalKeys.OrderBy(k => k));
}

/// <summary>
/// Number of keys
/// </summary>
public int Length
{
get { return _keys.Count; }
_keys = new Stack<Keys>(additionalKeys.Select(k=>k.Normalize()).OrderBy(k => k));
}

/// <summary>
Expand Down
71 changes: 61 additions & 10 deletions MouseKeyHook/KeyCombinationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,28 @@ public static class KeyCombinationExtensions
public static void OnCombination(this IKeyboardEvents source, IEnumerable<KeyValuePair<Chord, Action>> map)
{
var watchlists = new HashSet<Keys>(map.SelectMany(p=>p.Key));
OnCombination<Chord>(source, map, watchlists, null);
}

private static void OnCombination<T>(
IKeyboardEvents source,
IEnumerable<KeyValuePair<T, Action>> map,
IEnumerable<Keys> watchlists,
Action reset)
where T:IEnumerable<Keys>
{
source.KeyDown += (sender, e) =>
{
if (!watchlists.Contains(e.KeyCode)) return;
var state = KeyboardState.GetCurrent();
var matches = map.Where(pair => state.AreAllDown(pair.Key));
foreach (var current in matches)
var matches = map.Where(pair => state.AreAllDown(pair.Key)).Select(pair=>pair.Value);
var isEmpyty = true;
foreach (var current in matches)
{
current.Value();
current();
isEmpyty = false;
}
if (isEmpyty) reset?.Invoke();
};
}

Expand All @@ -41,16 +54,54 @@ public static void OnCombination(this IKeyboardEvents source, IEnumerable<KeyVal
public static void OnCombination(this IKeyboardEvents source, IEnumerable<KeyValuePair<TriggerChord, Action>> map)
{
var watchlists = new HashSet<Keys>(map.Select(p => p.Key.TriggerKey));
source.KeyDown += (sender, e) =>
OnCombination<TriggerChord>(source, map, watchlists, null);
}


public static void OnSequence(this IKeyboardEvents source, IDictionary<Sequence, Action> map)
{

var endsWith = new Func<Queue<TriggerChord>, Sequence, bool>((chords, sequence) =>
{
if (!watchlists.Contains(e.KeyCode)) return;
var state = KeyboardState.GetCurrent();
var matches = map.Where(pair => state.AreAllDown(pair.Key));
foreach (var current in matches)
var skipCount = chords.Count - sequence.Length;
return skipCount >= 0 && chords.Skip(skipCount).SequenceEqual(sequence);
});

var max = map.Select(p => p.Key).Max(c => c.Length);
var min = map.Select(p => p.Key).Min(c => c.Length);
Queue<TriggerChord> buffer = new Queue<TriggerChord>(max);

var wrapMap = map.SelectMany(p => p.Key).ToDictionary(c => c, c => new Action(() =>
{
buffer.Enqueue(c);
if (buffer.Count > max) buffer.Dequeue();
if (buffer.Count < min) return;
foreach (var pair in map)
{
current.Value();
var sequence = pair.Key;
if (!endsWith(buffer, sequence)) continue;
var action = pair.Value;
action();
}
};
}));


var watchlists = new HashSet<Keys>(
map.Select(p => p.Key).SelectMany(s => s.SelectMany(k => k.GetAllKeys)));
OnCombination(source, wrapMap, watchlists, buffer.Clear);
}



public static Keys Normalize(this Keys key)
{
if ((key & Keys.LControlKey) == Keys.LControlKey ||
(key & Keys.RControlKey) == Keys.RControlKey) return Keys.Control;
if ((key & Keys.LShiftKey) == Keys.LShiftKey ||
(key & Keys.RShiftKey) == Keys.RShiftKey) return Keys.Shift;
if ((key & Keys.LMenu) == Keys.LMenu ||
(key & Keys.RMenu) == Keys.RMenu) return Keys.Alt;
return key;
}
}
}
1 change: 1 addition & 0 deletions MouseKeyHook/MouseKeyHook.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@
<Compile Include="Implementation\KeyboardState.cs" />
<Compile Include="KeyPressEventArgsExt.cs" />
<Compile Include="Implementation\Subscribe.cs" />
<Compile Include="SequenceBase.cs" />
<Compile Include="TriggerChord.cs" />
<Compile Include="WinApi\AppMouseStruct.cs" />
<Compile Include="WinApi\CallbackData.cs" />
Expand Down
22 changes: 15 additions & 7 deletions MouseKeyHook.Rx/Sequence.cs → MouseKeyHook/SequenceBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Gma.System.MouseKeyHook;

namespace MouseKeyHook.Rx
namespace Gma.System.MouseKeyHook
{
public class Sequence<T> : IEnumerable<T>
public class Sequence : SequenceBase<TriggerChord>
{
public Sequence(params TriggerChord[] triggerChords) : base(triggerChords)
{

}
}


public abstract class SequenceBase<T> : IEnumerable<T>
{
private readonly T[] _elements;

public Sequence(params T[] elements)
protected SequenceBase(params T[] elements)
{
_elements = elements;
}
Expand All @@ -39,7 +47,7 @@ public override string ToString()
}


protected bool Equals(Sequence<T> other)
protected bool Equals(SequenceBase<T> other)
{
if (_elements.Length != other._elements.Length) return false;
return _elements.SequenceEqual(other._elements);
Expand All @@ -64,12 +72,12 @@ public override int GetHashCode()
}
}

public static bool operator ==(Sequence<T> left, Sequence<T> right)
public static bool operator ==(SequenceBase<T> left, SequenceBase<T> right)
{
return Equals(left, right);
}

public static bool operator !=(Sequence<T> left, Sequence<T> right)
public static bool operator !=(SequenceBase<T> left, SequenceBase<T> right)
{
return !Equals(left, right);
}
Expand Down
10 changes: 1 addition & 9 deletions MouseKeyHook/TriggerChord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ private TriggerChord(Keys triggerKey, IEnumerable<Keys> chordKeys)

private TriggerChord(Keys triggerKey, Chord chord)
{
TriggerKey = triggerKey;
TriggerKey = triggerKey.Normalize();
_chord =chord;
}

Expand All @@ -35,14 +35,6 @@ internal IEnumerable<Keys> GetAllKeys
get { return _chord.Concat(Enumerable.Repeat(TriggerKey, 1)); }
}

/// <summary>
/// Length of the key combination.
/// </summary>
public int Length
{
get { return _chord.Length + 1; }
}

/// <summary>
/// A chainable builder method to simplify chord creation. Used along with <see cref="Create"/>, <see cref="And"/>, <see cref="Control"/>, <see cref="Shift"/>, <see cref="Alt"/>.
/// </summary>
Expand Down
20 changes: 0 additions & 20 deletions examples/ConsoleHook.Rx/DetectSequences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,7 @@ internal class DetectSequences
{
public static void Do(AutoResetEvent quit)
{
var expected = new HashSet<KeySequence>
{
new KeySequence(Keys.A, Keys.B, Keys.C),
new KeySequence(Keys.Q, Keys.Q)
};

Hook
.GlobalEvents()
.KeyDownObservable()
.Sequences(2,3)
//.Matching(expected)
.ForEachAsync(sequence =>
{
if (sequence.SequenceEqual(new []{Keys.Q, Keys.Q})) quit.Set();
Console.WriteLine(sequence);
});

Console.WriteLine("Press Control+Q to quit.");
Console.WriteLine("Monitoring folowing sequences:");
foreach (var name in expected)
Console.WriteLine("\t" + name);
}
}
}

0 comments on commit 8067522

Please sign in to comment.