Reactive extensions: matching complex key press sequence - c#

What I'm trying to achieve is to handle some complex key press and release sequence with Rx. I have some little experience with Rx, but it's clearly not enough for my current undertaking, so I'm here for some help.
My WinForms app is running in the background, only visible in a system tray. By a given key sequence I want to activate one of it's forms. Btw, to hook up to the global key presses I'm using a nice library http://globalmousekeyhook.codeplex.com/ I'm able to receive every key down and key up events, and while key is down multiple KeyDown events are produced (with a standard keyboard repeat rate).
One of example key sequence I want to capture is a quick double Ctrl + Insert key presses (like holding Ctrl key and pressing Insert twice in a given period of time). Here is what I have currently in my code:
var keyDownSeq = Observable.FromEventPattern<KeyEventArgs>(m_KeyboardHookManager, "KeyDown");
var keyUpSeq = Observable.FromEventPattern<KeyEventArgs>(m_KeyboardHookManager, "KeyUp");
var ctrlDown = keyDownSeq.Where(ev => ev.EventArgs.KeyCode == Keys.LControlKey).Select(_ => true);
var ctrlUp = keyUpSeq.Where(ev => ev.EventArgs.KeyCode == Keys.LControlKey).Select(_ => false);
But then I'm stuck. My idea is that I need somehow to keep track of if the Ctrl key is down. One way is to create some global variable for that, and update it in some Merge listener
Observable.Merge(ctrlDown, ctrlUp)
.Do(b => globabl_bool = b)
.Subscribe();
But I think it ruins the whole Rx approach. Any ideas on how to achieve that while staying in Rx paradigm?
Then while the Ctrl is down I need to capture two Insert presses within a given time. I was thinking about using the Buffer:
var insertUp = keyUpSeq.Where(ev => ev.EventArgs.KeyCode == Keys.Insert);
insertUp.Buffer(TimeSpan.FromSeconds(1), 2)
.Do((buffer) => { if (buffer.Count == 2) Debug.WriteLine("happened"); })
.Subscribe();
However I'm not sure if it's most efficient way, because Buffer will produce events every one second, even if there was no any key pressed. Is there a better way? And I also need to combine that with Ctrl down somehow.
So once again, I need to keep track of double Insert press while Ctrl is down. Am I going in the right direction?
P.S. another possible approach is to subscribe to Insert observable only while Ctrl is down. Not sure how to achieve that though. Maybe some ideas on this as well?
EDIT: Another problem I've found is that Buffer doesn't suit my needs exactly. The problem comes from the fact that Buffer produces samples every two seconds, and if my first press belongs to the first buffer, and second to the next one, then nothing happens. How to overcome that?

Firstly, welcome to the brain-bending magic of the Reactive Framework! :)
Try this out, it should get you started on what you're after - comments in line to describe whats going on:
using(var hook = new KeyboardHookListener(new GlobalHooker()))
{
hook.Enabled = true;
var keyDownSeq = Observable.FromEventPattern<KeyEventArgs>(hook, "KeyDown");
var keyUpSeq = Observable.FromEventPattern<KeyEventArgs>(hook, "KeyUp");
var ctrlPlus =
// Start with a key press...
from keyDown in keyDownSeq
// and that key is the lctrl key...
where keyDown.EventArgs.KeyCode == Keys.LControlKey
from otherKeyDown in keyDownSeq
// sample until we get a keyup of lctrl...
.TakeUntil(keyUpSeq
.Where(e => e.EventArgs.KeyCode == Keys.LControlKey))
// but ignore the fact we're pressing lctrl down
.Where(e => e.EventArgs.KeyCode != Keys.LControlKey)
select otherKeyDown;
using(var sub = ctrlPlus
.Subscribe(e => Console.WriteLine("CTRL+" + e.EventArgs.KeyCode)))
{
Console.ReadLine();
}
}
Now this doesn't do exactly what you specified, but with a little tweaking, it could be easily adapted. The key bit is the implicit SelectMany calls in the sequential from clauses of the combined linq query - as a result, a query like:
var alphamabits =
from keyA in keyDown.Where(e => e.EventArgs.KeyCode == Keys.A)
from keyB in keyDown.Where(e => e.EventArgs.KeyCode == Keys.B)
from keyC in keyDown.Where(e => e.EventArgs.KeyCode == Keys.C)
from keyD in keyDown.Where(e => e.EventArgs.KeyCode == Keys.D)
from keyE in keyDown.Where(e => e.EventArgs.KeyCode == Keys.E)
from keyF in keyDown.Where(e => e.EventArgs.KeyCode == Keys.F)
select new {keyA,keyB,keyC,keyD,keyE,keyF};
translates (very) roughly into:
if A, then B, then C, then..., then F -> return one {a,b,c,d,e,f}
Make sense?
(ok, since you've read this far...)
var ctrlinsins =
from keyDown in keyDownSeq
where keyDown.EventArgs.KeyCode == Keys.LControlKey
from firstIns in keyDownSeq
// optional; abort sequence if you leggo of left ctrl
.TakeUntil(keyUpSeq.Where(e => e.EventArgs.KeyCode == Keys.LControlKey))
.Where(e => e.EventArgs.KeyCode == Keys.Insert)
from secondIns in keyDownSeq
// optional; abort sequence if you leggo of left ctrl
.TakeUntil(keyUpSeq.Where(e => e.EventArgs.KeyCode == Keys.LControlKey))
.Where(e => e.EventArgs.KeyCode == Keys.Insert)
select "Dude, it happened!";

All right, I've come up with some solution. It works, but has some limits which I'll explain further. I'll not accept the answer for some time, maybe somebody else will offer a better and more generic way to solve this problem. Anyway, here's the current solution:
private IDisposable SetupKeySequenceListener(Keys modifierKey, Keys doubleClickKey, TimeSpan doubleClickDelay, Action<Unit> actionHandler)
{
var keyDownSeq = Observable.FromEventPattern<KeyEventArgs>(m_KeyboardHookManager, "KeyDown");
var keyUpSeq = Observable.FromEventPattern<KeyEventArgs>(m_KeyboardHookManager, "KeyUp");
var modifierIsPressed = Observable
.Merge(keyDownSeq.Where(ev => (ev.EventArgs.KeyCode | modifierKey) == modifierKey).Select(_ => true),
keyUpSeq.Where(ev => (ev.EventArgs.KeyCode | modifierKey) == modifierKey).Select(_ => false))
.DistinctUntilChanged()
.Do(b => Debug.WriteLine("Ctrl is pressed: " + b.ToString()));
var mainKeyDoublePressed = Observable
.TimeInterval(keyDownSeq.Where(ev => (ev.EventArgs.KeyCode | doubleClickKey) == doubleClickKey))
.Select((val) => val.Interval)
.Scan((ti1, ti2) => ti2)
.Do(ti => Debug.WriteLine(ti.ToString()))
.Select(ti => ti < doubleClickDelay)
.Merge(keyUpSeq.Where(ev => (ev.EventArgs.KeyCode | doubleClickKey) == doubleClickKey).Select(_ => false))
.Do(b => Debug.WriteLine("Insert double pressed: " + b.ToString()));
return Observable.CombineLatest(modifierIsPressed, mainKeyDoublePressed)
.ObserveOn(WindowsFormsSynchronizationContext.Current)
.Where((list) => list.All(elem => elem))
.Select(_ => Unit.Default)
.Do(actionHandler)
.Subscribe();
}
Usage:
var subscriptionHandler = SetupKeySequenceListener(
Keys.LControlKey | Keys.RControlKey,
Keys.Insert | Keys.C,
TimeSpan.FromSeconds(0.5),
_ => { WindowState = FormWindowState.Normal; Show(); Debug.WriteLine("IT HAPPENED"); });
Let me explain what's going on here, maybe it will be useful for some. I'm essentially setting up 3 Observables, one is for modifier key (modifierIsPressed), another for key which needs to be double-clicked when modifier is pressed to activate the sequence (mainKeyDoublePressed), and the last that combines the two first.
First one is pretty straightforward: just convert key presses and releases to bool (using the Select). DistinctUntilChanged is needed because of if user press and holds some key, multiple events are generated. What I'm getting in this observable is a sequence of booleans, saying if modifier key is down.
Then the most tricky one, where the main key is handled. Let's go step by step:
I'm using TimeInterval to replace key down (it's important) events with the timespans
Then I'm getting the actual timespans out with Select function (to prepare for the next step)
Then comes the most tricky thing, the Scan. What it does is takes each two consecutive elements from previous sequence (timespans in our case) and passes them into a function as two parameters. Output of that function (which has to be of the same type as the parameters, a timespan) is passed further. The function in my case does very simple thing: just returns the second parameter.
Why? It's time to remember my actual task here: to catch double press of some button which are close enough to each other in time (like in half of a second in my example). My input is a sequence of timespans which are saying how much time passed since the previous event has happened. That's why I need to wait for two events: first one will be usually long enough, because it will tell since when user pressed the key last time, which could be minutes or more. But if the user presses the key two times quickly, then the second timespan will be small, since it will tell the difference between these two last quick presses.
Sounds complicated, right? Then think about it in a simple way: Scan always combines two latest events. That's why it fits my needs in this case: I need to listen to double-click. If I'd need to wait for 3 consecutive presses, I'd be at a loss here. That's why I call this approach limited, and still wait if somebody will offer a better and more generic solution, to handle potentially any key combination.
Anyway, let's continue the explanation:
4.Select(ti => ti < doubleClickDelay): here I just convert the sequence from timestamps to booleans, passing true for quick enough consecutive events, and false for not quick enough ones.
5.Here's another trick: I'm merging boolean sequence from step 4 to the new one, where I listen to the key up events. Remember that the original sequence one was built from key down events, right? So here I'm essentially taking the same approach as with observable number one: passing true for key down and false for key up.
Then it becomes super-easy to use CombineLatest function, which takes the last events from each sequence and pass them further, as a List, to the Where function, which checks if all of them are true. That's how I achieve my goal: now I know when main key was pressed twice while modifier key is held down. Merging in the main key up event ensures that I clear the state, so the next presses of modifier key will not trigger the sequence.
So here we go, that's pretty much it. I'll post this, but will not accept, as I said before. I hope somebody will chime in and enlighten me. :)
Thanks in advance!

Related

How can I fix race condition in this "reset after time" observable?

Input is an observable that produces a value each time a problem occurs.
As output I want an observable that produces a value if problems exist for a longer time. In other words I want to "reset" the output observable (not produce values) if the last problem is outdated.
My solution:
// first get an observable producing statusOk values (true = ok, false = not ok)
var okStatusObservable = input.Select(_ => true).Throttle(longerTime)
.Merge(input.Select(_ => false));
// we only want event if statusOk=false for a longer time
var outputObservable = okStatusObservable
.DistinctUntilChanged() // only changes
.Throttle(evenLongerTime) // wait for stable status
.Where(_ => _ == false); // only interested in bad status
I think the okStatusObservable might contain a race condition: If input receives events at time interval of exactly longerTime and second merge part (Select / false) would produce a boolean before the first part (Select+Throttle / true) then this would result in okStatus to be true 99.9% of the time where the opposite would be correct.
(PS: to have status value from beginning, we might add .StartWith(true) but that doesn't matter regarding race condition.)
A cleaner way to do the first observable is as follows:
var okStatusObservable2 = input
.Select(_ => Observable.Return(true).Delay(longerTime).StartWith(false))
.Switch();
Explanation: For each input message, produce an observable that starts with a false, and after longerTime produces a true. The Switch means that if you have a new observable, just switch to it, which would exclude the all-clear true at the end.
For your second observable, unless longerTime differs between the two observables, every first false in the first observable will result in a false in the second. Is that your intention?
Also, your Where is messed up (should be .Where(b => !b) or .Where(b => b == false). .Where(_ => false) will always evaluate to false returning nothing.
Other than that, I think your solution is sound.

Rx sequential groupBy (partition stream)

I have a stream of events:
event.EventTime: 1s-----2s----3s----4s----5s----6s---
stream: A-B-C--D-----------------E-F---G-H--
An event looks like this:
public class Event
{
public DateTime EventTime { get; set; }
public int Value { get; set; }
}
EventTime should correspond to a time at which the event arrives, but there can be a small delay. The events are not supposed to arrive out-of-order, though.
Now, when I specify an grouping interval, say 1 second, I expect the stream to be grouped like this
1s-------2s----3s----4s----5s-----6s---
[A-B-C]--[D]---[ ]---[ ]---[E-F]--[G-H]
(notice the empty intervals)
I have tried using Buffer, but sadly I need to partition by EventTime, not System.DateTime.Now. Even with boundaries, I'd need some kind of look-ahead since when I use Buffer(2,1) as boundary and compare [0] and [1], even though [1] succesfully breaks the buffer, it still gets inserted into the old one instead of the new one. I also tried GroupBy, but that yielded groups only after the input stream finished. Which should never happen. Then I tried some this thing:
var intervalStart = GetIntervalStartLocal(DateTime.Now) + intervalLength;
var intervals = Observable.Timer(intervalStart, intervalLength);
var eventsAsObservables = intervals.GroupJoin<long, Event, long, Event, (DateTime, IObservable<Event>)>(
data,
_ => Observable.Never<long>(),
_ => Observable.Never<Event>(),
(intervalNumber, events) => {
var currentIntervalStart = intervalStart + intervalNumber*intervalLength;
var eventsInInterval = events
.SkipWhile(e => GetIntervalStartLocal(e.EventTime) < currentIntervalStart)
.TakeWhile(e => GetIntervalStartLocal(e.EventTime) == currentIntervalStart);
return (currentIntervalStart, eventsInInterval);
});
var eventsForIntervalsAsObservables = eventsAsObservables.SelectMany(g => {
var lists = g.Item2.Aggregate(new List<Event>(), (es, e) => { es.Add(e); return es; });
return lists.Select(l => (intervalStart: g.Item1, events: l));
});
var task = eventsForIntervalsAsObservables.ForEachAsync(es => System.Console.WriteLine(
$"=[{es.intervalStart.TimeOfDay}]= " + string.Join("; ", es.events.Select(e => e.EventTime.TimeOfDay))));
await task;
I was thinking that I'd use GroupJoin which joins based on values. So first, I'll emit interval timestamps. Then, inside GroupJoins resultSelector, I'll compute a matching interval from each Event, using GetIntervalStartLocal function (truncates the date to an interval length). After that, I'll skip all the potential leftovers from a previous interval (SkipWhile expected interval is higher then actually computed from Event). Finally, I'll TakeWhile event computed interval matches expected.
However, there must be a problem before I even get to SkipWhile and TakeWhile, because resultSelector actually does not operate on all data from data, but ignores some, e.g. like this:
event.EventTime: 1s-----2s----3s----4s----5s----6s---
stream: A---C--D-------------------F-----H--
and then constructs (from what it operates on, correctly):
1s-----2s----3s----4s----5s---6s---
[A-C]--[D]---[ ]---[ ]---[F]--[H]--
I think I must be doing something terribly wrong here, because it shouldn't be that hard to do partitioning on a stream based on a stream event value.
You need to clarify what you want. Given this:
time : 1s-------2s----3s----4s----5s-----6s---
stream: A-B-C----D-----------------E-F----G-H-- (actual)
group : [A-B-C]--[D]---[ ]---[ ]---[E-F]--[G-H] (desired result)
It's not clear whether 'time' here is your event time-stamp, or actual time. If it's actual time, then that is of course impossible: You can't pass a list of ABC before C has arrived. If you're referring to your event time-stamp, then Buffer or perhaps Window will have to know when to stop, which isn't that easy to do.
GroupBy does work for me as follows:
var sampleSource = Observable.Interval(TimeSpan.FromMilliseconds(400))
.Timestamp()
.Select(t => new Event { EventTime = t.Timestamp.DateTime, Value = (int)t.Value });
sampleSource
.GroupBy(e => e.EventTime.Ticks / 10000000) //10M ticks per second
.Dump(); //LinqPad
The only problem with this is that each group doesn't have a close criteria, so it's a giant memory leak. So you can add a timer to close the groups:
sampleSource
.GroupBy(e => e.EventTime.Ticks / 10000000) //10M ticks per second
.Select(g => g.TakeUntil(Observable.Timer(TimeSpan.FromSeconds(2)))) //group closes 2 seconds after opening
.Dump(); //LinqPad
This closing also allows us to return lists with .ToList(), rather than Observables:
sampleSource
.GroupBy(e => e.EventTime.Ticks / 10000000) //10M ticks per second
.SelectMany(g => g.TakeUntil(Observable.Timer(TimeSpan.FromSeconds(2))).ToList())
.Dump(); //LinqPad

LINQ query for finding one item in list AND verifying list does not contain another item

So far I have come up with this:
list.Any(s => s.EnumIdValue == Close) && !list.Any(s => s.EnumIdValue == Reset || s.EnumIdValue == Cancel);
EnumIdValue contains several different possible values like Close, Reset, Cancel, SomeOtherState, etc. There should never be duplicates but it's still possible.
This does exactly what I want. But would there be a better (shorter) way to write this query?
Your original is fine. The other variant that would work is this:
var newQuery =
list.Any(s => s.EnumIdValue == EnumIdValue.Close) &&
list.All(s => s.EnumIdValue != EnumIdValue.Reset &&
s.EnumIdValue != EnumIdValue.Cancel);
In English: does the list have at least one Close and is every item in the list not Reset and not Cancel?
By the way, sometimes formatting your code nicely makes a big difference in terms of readability.
The answer is: There is no better way to write it.
If you try to write it in one lambda, you can think of the s as a single item in the list. If the item is a Close, it will of course not be something else so it's useless to check for that in the same lambda. If you want to check if the list doesn't contain some other values, you're forced to do it with another expression.
You can use a HashSet to test if the collection contains at least 1 of each EnumIdValue:
var enumIds = list.Select(s => s.EnumIdValue).ToHashSet();
return enumIds.Contains(Close) && !(enumIds.Contains(Cancel) || enumIds.Contains(Reset));
// or
return enumIds
.Intersect(new[] { Cancel, Reset, Close })
.SequenceEqual(new [] { Close });
// or (throws exception)
enumIds.IntersectWith(new[] { Cancel, Reset, Close })
return enumIds.Single() == Close;
This would be useful if you need to do different types of checks based on whether or not enumIds contains Close.
How about
var items = list.Select(s => s.EnumIdValue == Close)
if(items.Count==1)
{
//todo
}
Here's a fairly readable alternative:
var opts = new [] { Close, Reset, Cancel };
var desired = new [] { Close };
return list
.Select(s => s.EnumIdValue)
.Distinct()
.Where(opts.Contains)
.SequenceEqual(desired);

What is the functional way to properly set a dependent predicate for Observable sequence without side effect?

I have three observables oGotFocusOrDocumentSaved, oGotFocus and oLostFocus. I would like oGotFocusOrDocumentSaved to push sequences only when _active is true. My implementation below works as needed, but it introduces a side-effect on _active. Is there anyway to remove side-effect but still get the same functionality?
class TestClass
{
private bool _active = true;
public TestClass(..)
{
...
var oLostFocus = Observable
.FromEventPattern<EventArgs>(_view, "LostFocus")
.Throttle(TimeSpan.FromMilliseconds(500));
var oGotFocus = Observable
.FromEventPattern<EventArgs>(_view, "GotFocus")
.Throttle(TimeSpan.FromMilliseconds(500));
var oGotFocusOrDocumentSaved = oDocumentSaved // some other observable
.Merge<CustomEvtArgs>(oGotFocus)
.Where(_ => _active)
.Publish();
var lostFocusDisposable = oLostFocus.Subscribe(_ => _active = false);
var gotFocusDisposable = oGotFocus.Subscribe(_ => _active = true);
// use case
oGotFocusOrDocumentSaved.Subscribe(x => DoSomethingWith(x));
...
}
...
}
It does sound like you really want a oDocumentSavedWhenHasFocus rather than a oGotFocusOrDocumentSaved observable.
So try using the .Switch() operator, like this:
var oDocumentSavedWhenHasFocus =
oGotFocus
.Select(x => oDocumentSaved.TakeUntil(oLostFocus))
.Switch();
This should be fairly obvious as to how it works, once you know how .Switch() works.
A combination of SelectMany and TakeUntil should get you where you need to be.
from g in oGotFocus
from d in oDocumentSaved
.Merge<CustomEvtArgs>(oGotFocus)
.TakeUntil(oLostFocus)
It seems that you want to be notified when the document is saved, but only if the document currently has focus. Correct? (And you also want to be notified when the document gets focus, but that can easily be merged in later.)
Think in terms of windows instead of point events; i.e., join by coincidence.
Your requirement can be represented as a Join query whereby document saves are joined to focus windows, thus yielding notifications only when both overlap; i.e., when both are "active".
var oGotFocusOrDocumentSaved =
(from saved in oDocumentSaved
join focused in oGotFocus
on Observable.Empty<CustomEventArgs>() // oDocumentSave has no duration
equals oLostFocus // oGotFocus duration lasts until oLostFocus
select saved)
.Merge(oGotFocus);

Using Reactive Extension for certain KeyPress sequences?

I am new .. or more precisely.. never used RX so I was wondering whether I can use it for this situation: I want to add a sort of Resharper Live Templates functionality to my app that allows users to enter short sequences of characters followed by a [Tab] and my app would replace the previously typed characters with the elsewhere specified, full text.
Now I have a list of character arrays, each of them representing one possible sequence. I want some sort of stopwords/-keys that break the chain (e.g. space).
I have an event that is raised on each KeyPress in my application, now (how) can I use RX to observe this event and check against that aforementioned list whether one of the sequences has been fulfilled and finally [Tab] has been pressed?
I don't know if it's too late, but I have an answer for you.
The Rx extension method you need to use is BufferWithCount.
I'll assume you know how to turn key press events into an IObservable<char>.
So given you have a list of character sequences that you want to detect and then perform an action I suggest using a Dictionary<string, Action> to hold this data, like so:
var matches = new Dictionary<string, Action>()
{
{ "ba", () => Console.WriteLine("ba") },
{ "aba", () => Console.WriteLine("aba") },
{ "baa", () => Console.WriteLine("baa") },
{ "abc\t", () => Console.WriteLine("abc\\t") },
};
So here's the Rx (and IEnumerable) queries required:
int max =
matches
.Select(m => m.Key.Length)
.Max();
IObservable<string> chords =
Enumerable
.Range(2, max - 1)
.Select(n => keys
.BufferWithCount(n, 1)
.Select(cs => new string(cs.ToArray())))
.Merge();
IObservable<Action> actions =
chords
.Where(s => matches.ContainsKey(s))
.Select(s => matches[s]);
So finally you just have an IObservable<Action> that you can subscribe to and you just invoke the Action.
If you want to test out that this works use the following code:
IConnectableObservable<char> keys = "ababc\tdabaababc\tebad"
.ToObservable()
.Publish();
//`.Publish()` makes a cold observable become hot,
// but you must call `Connect()` to start producing values.
//insert above `matches` definition here.
//insert above queries here.
actions.Subscribe(a => a());
keys.Connect();
The result should be:
ba
aba
abc\t
ba
aba
baa
ba
aba
abc\t
ba
Enjoy!

Categories