Skip to content

Commit

Permalink
Hook.Rx - Implementing reactive extensions to mouse and keybord event…
Browse files Browse the repository at this point in the history
…s. First draft.
  • Loading branch information
gmamaladze committed Jan 11, 2018
1 parent 6361a17 commit d8ffc73
Show file tree
Hide file tree
Showing 14 changed files with 532 additions and 2 deletions.
6 changes: 6 additions & 0 deletions ConsoleHook.Rx/App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
</configuration>
81 changes: 81 additions & 0 deletions ConsoleHook.Rx/ConsoleHook.Rx.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{F9B78F1A-F065-4BB4-A15B-393DFFE8DB0B}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>ConsoleHook.Rx</RootNamespace>
<AssemblyName>ConsoleHook.Rx</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Reactive.Core, Version=3.0.1000.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reactive.Core.3.1.1\lib\net45\System.Reactive.Core.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Interfaces, Version=3.0.1000.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reactive.Interfaces.3.1.1\lib\net45\System.Reactive.Interfaces.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Linq, Version=3.0.1000.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reactive.Linq.3.1.1\lib\net45\System.Reactive.Linq.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.PlatformServices, Version=3.0.1000.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reactive.PlatformServices.3.1.1\lib\net45\System.Reactive.PlatformServices.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Windows.Threading, Version=3.0.1000.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reactive.Windows.Threading.3.1.1\lib\net45\System.Reactive.Windows.Threading.dll</HintPath>
</Reference>
<Reference Include="System.Windows" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MouseKeyHook.Rx\MouseKeyHook.Rx.csproj">
<Project>{9754749c-1eea-4468-8c32-b5bac3235f54}</Project>
<Name>MouseKeyHook.Rx</Name>
</ProjectReference>
<ProjectReference Include="..\MouseKeyHook\MouseKeyHook.csproj">
<Project>{f52aa97e-180a-40ed-8f2b-09080171d6c7}</Project>
<Name>MouseKeyHook</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
53 changes: 53 additions & 0 deletions ConsoleHook.Rx/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Gma.System.MouseKeyHook;
using MouseKeyHook;
using System.Reactive.Linq;
using System.Threading;
using MouseKeyHook.Rx;
using System.Windows.Forms;

namespace ConsoleHook.Rx
{
class Program
{
static void Main(string[] args)
{
var map = new Dictionary<Chord, string>
{
{ Chord.Of(Keys.LControlKey.Down(), Keys.Q.Down()), "Ctrl+Q"},
{ Chord.Of(Keys.RControlKey.Down(), Keys.Q.Down()), "Ctrl+Q"},
{ Chord.Of(Keys.LMenu.Down(), Keys.LShiftKey.Down(), Keys.H.Down()), "Alt+Shift+H"},
{ Chord.Of(Keys.LShiftKey.Down(), Keys.Z.Down(), Keys.Z.Up(), Keys.Z.Down()), "Shift+Z,Z"},
{ Chord.Of(Keys.RShiftKey.Down(), Keys.Z.Down(), Keys.Z.Up(), Keys.Z.Down()), "Shift+Z,Z"}
};


var quit = new AutoResetEvent(false);

Hook
.GlobalEvents()
.UpDownObservable()
.ChordsMapped(map)
.ForEachAsync(name =>
{
if (name == "Ctrl+Q") quit.Set();
Console.WriteLine(name);
});

Console.WriteLine("Press Ctrl+Q to quit.");
Console.WriteLine("Monitoring folowing key combinations:");
foreach (var name in map.Values)
{
Console.WriteLine("\t"+name);
}
while (!quit.WaitOne(100))
{
Application.DoEvents();
};
}
}
}
36 changes: 36 additions & 0 deletions ConsoleHook.Rx/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ConsoleHook.Rx")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ConsoleHook.Rx")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("f9b78f1a-f065-4bb4-a15b-393dffe8db0b")]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
9 changes: 9 additions & 0 deletions ConsoleHook.Rx/packages.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="System.Reactive" version="3.1.1" targetFramework="net452" />
<package id="System.Reactive.Core" version="3.1.1" targetFramework="net452" />
<package id="System.Reactive.Interfaces" version="3.1.1" targetFramework="net452" />
<package id="System.Reactive.Linq" version="3.1.1" targetFramework="net452" />
<package id="System.Reactive.PlatformServices" version="3.1.1" targetFramework="net452" />
<package id="System.Reactive.Windows.Threading" version="3.1.1" targetFramework="net452" />
</packages>
54 changes: 54 additions & 0 deletions MouseKeyHook.Rx/Chord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Collections.Generic;
using System.Linq;

namespace MouseKeyHook.Rx
{
public class Chord
{
public bool Matches(IEnumerable<KeyEvent> events)
{
return events.SequenceEqual(this.Events);
}

public static Chord Of(params KeyEvent[] events)
{
return new Chord(events);
}

protected bool Equals(Chord other)
{
return Events.Equals(other.Events);
}

public override bool Equals(object obj)
{
var chord = (Chord)obj;
return chord != null && Matches(chord.Events);
}

internal static int CombineHashCodes(int h1, int h2)
{
return (((h1 << 5) + h1) ^ h2);
}

public override int GetHashCode()
{
return this
.Events
.Select(evt => evt.GetHashCode())
.Aggregate(37, CombineHashCodes);
}

public override string ToString()
{
return Events.Aggregate(string.Empty, (s, e) => s+" "+e.ToString());
}

public IEnumerable<KeyEvent> Events { get; }

public Chord(IEnumerable<KeyEvent> events)
{
Events = events;
}
}
}
48 changes: 48 additions & 0 deletions MouseKeyHook.Rx/KeyEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.Windows.Forms;

namespace MouseKeyHook.Rx
{
public class KeyEvent
{

public KeyEventKind Kind { get; set; }
public Keys KeyCode { get; set; }

protected bool Equals(KeyEvent other)
{
return Kind == other.Kind && (KeyCode & other.KeyCode) == other.KeyCode;
}

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((KeyEvent) obj);
}

public override int GetHashCode()
{
unchecked
{
return ((int) Kind * 397) ^ (int) KeyCode;
}
}

public override string ToString()
{
return string.Format("{0}-{1}", KeyCode, Kind);
}

public static bool operator ==(KeyEvent left, KeyEvent right)
{
return Equals(left, right);
}

public static bool operator !=(KeyEvent left, KeyEvent right)
{
return !Equals(left, right);
}
}
}
8 changes: 8 additions & 0 deletions MouseKeyHook.Rx/KeyEventKind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace MouseKeyHook.Rx
{
public enum KeyEventKind
{
Up,
Down
}
}
61 changes: 61 additions & 0 deletions MouseKeyHook.Rx/KeyObserverExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Gma.System.MouseKeyHook;
using srx=System.Reactive.Linq;
using System.Reactive.Linq;
using System.Windows.Forms;

namespace MouseKeyHook.Rx
{
public static class KeyObserverExtensions
{
public static IObservable<KeyEvent> UpDownObservable(this IKeyboardEvents source)
{
var downObserver = srx
.Observable
.FromEventPattern<KeyEventArgs>(source, "KeyDown")
.Select(ep=>ep.EventArgs.KeyCode)
.Select(code => new KeyEvent {KeyCode = code, Kind = KeyEventKind.Down});

var upObserver = srx
.Observable
.FromEventPattern<KeyEventArgs>(source, "KeyUp")
.Select(ep => ep.EventArgs.KeyCode)
.Select(code => new KeyEvent { KeyCode = code, Kind = KeyEventKind.Up });

return downObserver
.Merge(upObserver);
}

public static IObservable<Chord> Chords(this IObservable<KeyEvent> upDownObservable, int minChodLength=2, int maxChordLength = 3)
{
return Enumerable
.Range(minChodLength, maxChordLength)
.Select(n => upDownObservable
.Buffer(n, 1)
.Select(buffer => new Chord(buffer)))
.Merge();
}

public static IObservable<T> ChordsMapped<T>(this IObservable<KeyEvent> upDownObservable,
IDictionary<Chord, T> map)
{
var min = map
.Keys
.Select(k => k.Events.Count())
.Min();

var max = map
.Keys
.Select(k => k.Events.Count())
.Max();

return upDownObservable
.Chords(min, max)
.Where(map.ContainsKey)
.Select(chord => map[chord]);
}
}
}
17 changes: 17 additions & 0 deletions MouseKeyHook.Rx/KeysExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Windows.Forms;

namespace MouseKeyHook.Rx
{
public static class KeysExtensions
{
public static KeyEvent Up(this Keys key)
{
return new KeyEvent {KeyCode = key, Kind = KeyEventKind.Up};
}

public static KeyEvent Down(this Keys key)
{
return new KeyEvent { KeyCode = key, Kind = KeyEventKind.Down };
}
}
}
Loading

0 comments on commit d8ffc73

Please sign in to comment.