diff --git a/src/Carnac.Logic/Carnac.Logic.csproj b/src/Carnac.Logic/Carnac.Logic.csproj
index 3fc10979..f66d0c33 100644
--- a/src/Carnac.Logic/Carnac.Logic.csproj
+++ b/src/Carnac.Logic/Carnac.Logic.csproj
@@ -38,6 +38,9 @@
false
+
+ ..\packages\MouseKeyHook.5.4.0\lib\net40\Gma.System.MouseKeyHook.dll
+
@@ -92,6 +95,7 @@
+
diff --git a/src/Carnac.Logic/KeyProvider.cs b/src/Carnac.Logic/KeyProvider.cs
index d5272adb..76a1dd01 100644
--- a/src/Carnac.Logic/KeyProvider.cs
+++ b/src/Carnac.Logic/KeyProvider.cs
@@ -10,6 +10,8 @@
using Carnac.Logic.Models;
using Microsoft.Win32;
using System.Windows.Media;
+using Carnac.Logic.MouseMonitor;
+
namespace Carnac.Logic
{
@@ -66,7 +68,10 @@ public IObservable GetKeyStream()
winKeyPressed = false;
}, observer.OnError);
- var keyStreamSubsription = interceptKeysSource.GetKeyStream()
+ var keyStreamSubsription = Observable.Merge(
+ new IObservable[2] {
+ interceptKeysSource.GetKeyStream(),
+ InterceptMouse.Current.GetKeyStream() })
.Select(DetectWindowsKey)
.Where(k => !IsModifierKeyPress(k) && k.KeyDirection == KeyDirection.Down)
.Select(ToCarnacKeyPress)
@@ -123,6 +128,7 @@ static IEnumerable ToInputs(bool isLetter, bool isWinKeyPressed, Interce
var controlPressed = interceptKeyEventArgs.ControlPressed;
var altPressed = interceptKeyEventArgs.AltPressed;
var shiftPressed = interceptKeyEventArgs.ShiftPressed;
+ var mouseAction = InterceptMouse.MouseKeys.Contains(interceptKeyEventArgs.Key);
if (controlPressed)
yield return "Ctrl";
if (altPressed)
@@ -130,7 +136,7 @@ static IEnumerable ToInputs(bool isLetter, bool isWinKeyPressed, Interce
if (isWinKeyPressed)
yield return "Win";
- if (controlPressed || altPressed)
+ if (controlPressed || altPressed || mouseAction)
{
//Treat as a shortcut, don't be too smart
if (shiftPressed)
diff --git a/src/Carnac.Logic/Models/Message.cs b/src/Carnac.Logic/Models/Message.cs
index 415492b0..ca77bfc5 100644
--- a/src/Carnac.Logic/Models/Message.cs
+++ b/src/Carnac.Logic/Models/Message.cs
@@ -1,3 +1,4 @@
+using Carnac.Logic.MouseMonitor;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -29,7 +30,8 @@ public Message(KeyPress key)
{
processName = key.Process.ProcessName;
processIcon = key.Process.ProcessIcon;
- canBeMerged = !key.HasModifierPressed;
+ // allow to aggregate all key combination as to not spam if ctrl + mousewheel is used.
+ canBeMerged = true; // !key.HasModifierPressed;
keys = new ReadOnlyCollection(new[] { key });
textCollection = new ReadOnlyCollection(CreateTextSequence(key).ToArray());
@@ -104,6 +106,26 @@ public static Message MergeIfNeeded(Message previousMessage, Message newMessage)
return ShouldCreateNewMessage(previousMessage, newMessage)
? newMessage
: previousMessage.Merge(newMessage);
+ /*
+ * Code used when mouse and show modifiers standalone are mixed together
+ // replace key was after standalone modifier keypress, replace by new Message
+ + if (previousMessage.keys != null && KeyProvider.IsModifierKeyPress(previousMessage.keys[0].InterceptKeyEventArgs))
+ + {
+ + return previousMessage.Replace(newMessage);
+ + }
+ + // if current is modifier and previous is a mouse action ignore modifierkeypress
+ + if (previousMessage.keys != null && KeyProvider.IsModifierKeyPress(newMessage.keys[0].InterceptKeyEventArgs)
+ + && InterceptMouse.MouseKeys.Contains(previousMessage.keys[0].Key))
+ + {
+ + return previousMessage.Replace(previousMessage);
+ + }
+ +
+ + if (ShouldCreateNewMessage(previousMessage, newMessage))
+ + {
+ + return newMessage;
+ + }
+ + return previousMessage.Merge(newMessage);
+ */
}
static bool ShouldCreateNewMessage(Message previous, Message current)
@@ -111,7 +133,11 @@ static bool ShouldCreateNewMessage(Message previous, Message current)
return previous.ProcessName != current.ProcessName ||
current.LastMessage.Subtract(previous.LastMessage) > OneSecond ||
!previous.CanBeMerged ||
- !current.CanBeMerged;
+ !current.CanBeMerged ||
+ // new message for different mouse keys;
+ ((InterceptMouse.MouseKeys.Contains(current.keys[0].Key) ||
+ (previous.keys != null && InterceptMouse.MouseKeys.Contains(previous.keys[0].Key)))
+ && !previous.keys[0].Input.SequenceEqual(current.keys[0].Input));;
}
public Message FadeOut()
diff --git a/src/Carnac.Logic/Models/PopupSettings.cs b/src/Carnac.Logic/Models/PopupSettings.cs
index 8331f73f..19a8552d 100644
--- a/src/Carnac.Logic/Models/PopupSettings.cs
+++ b/src/Carnac.Logic/Models/PopupSettings.cs
@@ -85,5 +85,36 @@ public string SortDescription
public bool DetectShortcutsOnly { get; set; }
public bool ShowApplicationIcon { get; set; }
public bool SettingsConfigured { get; set; }
+
+ [DefaultValue(true)]
+ public bool ShowMouseClicks { get; set; }
+
+ [DefaultValue("OrangeRed")]
+ public string LeftClickColor { get; set; }
+
+ [DefaultValue("RoyalBlue")]
+ public string RightClickColor { get; set; }
+
+ [DefaultValue(1)]
+ public double ClickStartScale { get; set; }
+
+ [DefaultValue(4)]
+ public double ClickStopScale { get; set; }
+
+ [DefaultValue(3700)]
+ public int ClickFadeDelay { get; set; }
+
+ [DefaultValue(1)]
+ public double ClickStartBorder { get; set; }
+
+ [DefaultValue(0.8)]
+ public double ClickStartOpacity { get; set; }
+
+ [DefaultValue(2)]
+ public double ClickStopBorder { get; set; }
+
+ [DefaultValue(0)]
+ public double ClickStopOpacity { get; set; }
+ public string ClickColor { get; set; }
}
}
diff --git a/src/Carnac.Logic/MouseMonitor/InterceptMouse.cs b/src/Carnac.Logic/MouseMonitor/InterceptMouse.cs
new file mode 100644
index 00000000..fc9c9728
--- /dev/null
+++ b/src/Carnac.Logic/MouseMonitor/InterceptMouse.cs
@@ -0,0 +1,112 @@
+using Carnac.Logic.KeyMonitor;
+using System;
+using System.Diagnostics;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using System.Windows.Forms;
+using Gma.System.MouseKeyHook;
+using System.Collections.Generic;
+
+namespace Carnac.Logic.MouseMonitor
+{
+ public class InterceptMouse : IInterceptKeys
+ {
+
+ public static readonly InterceptMouse Current = new InterceptMouse();
+ readonly IKeyboardMouseEvents m_GlobalHook = Hook.GlobalEvents();
+ readonly IObservable keyStream;
+ private IObserver observer;
+ private readonly KeysConverter kc = new KeysConverter();
+
+ public static readonly List MouseKeys = new List()
+ {
+ Keys.LButton,
+ Keys.MButton,
+ Keys.RButton,
+ Keys.XButton1,
+ Keys.XButton2,
+ Keys.VolumeUp,
+ Keys.VolumeDown
+ };
+
+ InterceptMouse()
+ {
+ keyStream = Observable.Create(observer =>
+ {
+ this.observer = observer;
+ m_GlobalHook.MouseClick += OnMouseClick;
+ m_GlobalHook.MouseDoubleClick += OnMouseDoubleClick;
+ m_GlobalHook.MouseWheel += HookManager_MouseWheel;
+ Debug.Write("Subscribed to mouse");
+
+ return Disposable.Create(() =>
+ {
+ m_GlobalHook.MouseClick -= OnMouseClick;
+ m_GlobalHook.MouseDoubleClick -= OnMouseDoubleClick;
+ m_GlobalHook.MouseWheel -= HookManager_MouseWheel;
+ m_GlobalHook.Dispose();
+ Debug.Write("Unsubscribed from mouse");
+ });
+ })
+ .Publish().RefCount();
+
+ }
+
+ private Keys MouseButtonsToKeys(MouseButtons button)
+ {
+ switch(button)
+ {
+ case MouseButtons.Left:
+ return Keys.LButton;
+ case MouseButtons.Middle:
+ return Keys.MButton;
+ case MouseButtons.Right:
+ return Keys.RButton;
+ case MouseButtons.XButton1:
+ return Keys.XButton1;
+ case MouseButtons.XButton2:
+ return Keys.XButton2;
+ default:
+ return Keys.None;
+ }
+ }
+
+ private void OnMouseClick(object sender, MouseEventArgs e)
+ {
+ observer.OnNext(new InterceptKeyEventArgs(
+ MouseButtonsToKeys(e.Button),
+ KeyDirection.Down,
+ Control.ModifierKeys == Keys.Alt,
+ Control.ModifierKeys == Keys.Control,
+ Control.ModifierKeys == Keys.Shift));
+ }
+
+ private void OnMouseDoubleClick(object sender, MouseEventArgs e)
+ {
+ observer.OnNext(new InterceptKeyEventArgs(
+ MouseButtonsToKeys(e.Button),
+ KeyDirection.Down,
+ Control.ModifierKeys == Keys.Alt,
+ Control.ModifierKeys == Keys.Control,
+ Control.ModifierKeys == Keys.Shift));
+ }
+
+ private void HookManager_MouseWheel(object sender, MouseEventArgs e)
+ {
+ // for now using VolumeDown and Up as proxy could be refactored
+ observer.OnNext(new InterceptKeyEventArgs(
+ e.Delta > 0 ? Keys.VolumeUp : Keys.VolumeDown,
+ KeyDirection.Down,
+ Control.ModifierKeys == Keys.Alt,
+ Control.ModifierKeys == Keys.Control,
+ Control.ModifierKeys == Keys.Shift));
+ }
+
+ public IObservable GetKeyStream()
+ {
+ return keyStream;
+ }
+
+ }
+}
+
diff --git a/src/Carnac.Logic/packages.config b/src/Carnac.Logic/packages.config
index acb9c233..511e3dcb 100644
--- a/src/Carnac.Logic/packages.config
+++ b/src/Carnac.Logic/packages.config
@@ -1,6 +1,7 @@
+
diff --git a/src/Carnac/App.xaml.cs b/src/Carnac/App.xaml.cs
index 98be0c52..440591c3 100644
--- a/src/Carnac/App.xaml.cs
+++ b/src/Carnac/App.xaml.cs
@@ -78,6 +78,7 @@ protected override void OnExit(ExitEventArgs e)
{
trayIcon.Dispose();
carnac.Dispose();
+ keyShowView.Dispose();
ProcessUtilities.DestroyMutex();
base.OnExit(e);
diff --git a/src/Carnac/Carnac.csproj b/src/Carnac/Carnac.csproj
index 04636d02..6cbc23f8 100644
--- a/src/Carnac/Carnac.csproj
+++ b/src/Carnac/Carnac.csproj
@@ -87,6 +87,9 @@
..\packages\DeltaCompressionDotNet.1.0.0\lib\net45\DeltaCompressionDotNet.PatchApi.dll
+
+ ..\packages\MouseKeyHook.5.4.0\lib\net40\Gma.System.MouseKeyHook.dll
+
..\packages\squirrel.windows.1.5.2\lib\Net45\ICSharpCode.SharpZipLib.dll
diff --git a/src/Carnac/UI/KeyShowView.xaml b/src/Carnac/UI/KeyShowView.xaml
index 914ce248..03e05521 100644
--- a/src/Carnac/UI/KeyShowView.xaml
+++ b/src/Carnac/UI/KeyShowView.xaml
@@ -16,7 +16,6 @@
-
@@ -55,9 +54,304 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -191,4 +548,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Carnac/UI/KeyShowView.xaml.cs b/src/Carnac/UI/KeyShowView.xaml.cs
index 149362be..928ca6f7 100644
--- a/src/Carnac/UI/KeyShowView.xaml.cs
+++ b/src/Carnac/UI/KeyShowView.xaml.cs
@@ -3,16 +3,22 @@
using System.Timers;
using System.Windows;
using System.Windows.Interop;
+using System.Windows.Media.Animation;
using Carnac.Logic;
+using Gma.System.MouseKeyHook;
namespace Carnac.UI
{
- public partial class KeyShowView
+ public partial class KeyShowView: IDisposable
{
+ private Storyboard sb;
+ IKeyboardMouseEvents m_GlobalHook = null;
+
public KeyShowView(KeyShowViewModel keyShowViewModel)
{
DataContext = keyShowViewModel;
InitializeComponent();
+ keyShowViewModel.Settings.PropertyChanged += Settings_PropertyChanged;
}
protected override void OnSourceInitialized(EventArgs e)
@@ -38,6 +44,18 @@ protected override void OnSourceInitialized(EventArgs e)
Left = vm.Settings.Left;
vm.Settings.LeftChanged += SettingsLeftChanged;
WindowState = WindowState.Maximized;
+ if (vm.Settings.ShowMouseClicks)
+ {
+ SetupMouseEvents();
+ }
+ }
+
+ public void Dispose()
+ {
+ if (m_GlobalHook != null)
+ {
+ m_GlobalHook.Dispose();
+ }
}
[DllImport("user32.dll", SetLastError = true)]
@@ -83,7 +101,7 @@ public static readonly int
private void WindowLoaded(object sender, RoutedEventArgs e)
{
-
+ sb = this.FindResource("clickHighlighterStoryboard") as Storyboard;
}
void SettingsLeftChanged(object sender, EventArgs e)
@@ -93,5 +111,69 @@ void SettingsLeftChanged(object sender, EventArgs e)
Left = vm.Settings.Left;
WindowState = WindowState.Maximized;
}
+
+ void Settings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ var vm = ((KeyShowViewModel)DataContext);
+ switch (e.PropertyName)
+ {
+ case "ClickFadeDelay":
+ Duration d = TimeSpan.FromMilliseconds(vm.Settings.ClickFadeDelay);
+ foreach(DoubleAnimation da in sb.Children)
+ {
+ da.Duration = d;
+ }
+ break;
+ case "ShowMouseClicks":
+ if (vm.Settings.ShowMouseClicks)
+ {
+ SetupMouseEvents();
+ }
+ else
+ {
+ DestroyMouseEvents();
+ }
+ break;
+ }
+ }
+
+ void SetupMouseEvents()
+ {
+ if (m_GlobalHook == null)
+ {
+ m_GlobalHook = Hook.GlobalEvents();
+ }
+ m_GlobalHook.MouseDown += OnMouseDown;
+ m_GlobalHook.MouseMove += OnMouseMove;
+ }
+
+ void DestroyMouseEvents()
+ {
+ if (m_GlobalHook == null)
+ {
+ return;
+ }
+ m_GlobalHook.MouseDown -= OnMouseDown;
+ m_GlobalHook.MouseMove -= OnMouseMove;
+ m_GlobalHook.Dispose();
+ m_GlobalHook = null;
+ }
+
+ private void OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
+ {
+ var vm = ((KeyShowViewModel)DataContext);
+ vm.Settings.ClickColor = vm.Settings.LeftClickColor;
+ if (e.Button == System.Windows.Forms.MouseButtons.Right)
+ {
+ vm.Settings.ClickColor = vm.Settings.RightClickColor;
+ }
+ sb.Begin();
+ }
+
+ private void OnMouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
+ {
+ var vm = ((KeyShowViewModel)DataContext);
+ vm.CursorPosition = PointFromScreen(new Point(e.X, e.Y));
+ }
}
}
diff --git a/src/Carnac/UI/KeyShowViewModel.cs b/src/Carnac/UI/KeyShowViewModel.cs
index 590608cf..98c8a412 100644
--- a/src/Carnac/UI/KeyShowViewModel.cs
+++ b/src/Carnac/UI/KeyShowViewModel.cs
@@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
+using System.Windows;
using Carnac.Logic;
using Carnac.Logic.Models;
@@ -15,5 +16,12 @@ public KeyShowViewModel(PopupSettings popupSettings)
public ObservableCollection Messages { get; private set; }
public PopupSettings Settings { get; set; }
+
+ public Point CursorPosition { get; set; }
+
+ public Thickness CursorMargins
+ {
+ get { return new Thickness(CursorPosition.X - 10, CursorPosition.Y - 10 , 0, 0); }
+ }
}
}
diff --git a/src/Carnac/UI/PreferencesView.xaml b/src/Carnac/UI/PreferencesView.xaml
index bcbcecac..47f9af43 100644
--- a/src/Carnac/UI/PreferencesView.xaml
+++ b/src/Carnac/UI/PreferencesView.xaml
@@ -91,7 +91,7 @@
-
+
@@ -151,6 +151,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Carnac/UI/PreferencesViewModel.cs b/src/Carnac/UI/PreferencesViewModel.cs
index 7eb14b5d..252a6e65 100644
--- a/src/Carnac/UI/PreferencesViewModel.cs
+++ b/src/Carnac/UI/PreferencesViewModel.cs
@@ -40,9 +40,24 @@ public PreferencesViewModel(ISettingsProvider settingsProvider, IScreenManager s
if (Settings.ItemBackgroundColor == name)
ItemBackgroundColor = availableColor;
+ if (Settings.LeftClickColor == name)
+ LeftClickColor = availableColor;
+ if (Settings.RightClickColor == name)
+ RightClickColor = availableColor;
+
AvailableColors.Add(availableColor);
}
+ if (LeftClickColor == null)
+ {
+ LeftClickColor = new AvailableColor("OrangeRed", Colors.OrangeRed);
+ }
+ if (RightClickColor == null)
+ {
+ RightClickColor = new AvailableColor("RoyalBlue", Colors.RoyalBlue);
+ }
+
+
SaveCommand = new DelegateCommand(SaveSettings);
ResetToDefaultsCommand = new DelegateCommand(() => settingsProvider.ResetToDefaults());
VisitCommand = new DelegateCommand(Visit);
@@ -77,7 +92,8 @@ public string Version
"Dmitry Pursanov",
"Chris Sainty",
"Andrew Tobin",
- "Henrik Andersson"
+ "Henrik Andersson",
+ "Boris Fritscher"
};
readonly List components = new List
{
@@ -85,7 +101,8 @@ public string Version
"Fody",
"NSubstitute",
"Reactive Extensions",
- "Squirrel.Windows"
+ "Squirrel.Windows",
+ "MouseKeyHook"
};
public string Authors
{
@@ -101,6 +118,10 @@ public string Components
public AvailableColor ItemBackgroundColor { get; set; }
+ public AvailableColor LeftClickColor { get; set; }
+
+ public AvailableColor RightClickColor { get; set; }
+
void Visit()
{
try
@@ -139,6 +160,8 @@ void SaveSettings()
Settings.SettingsConfigured = true;
Settings.FontColor = FontColor.Name;
Settings.ItemBackgroundColor = ItemBackgroundColor.Name;
+ Settings.LeftClickColor = LeftClickColor.Name;
+ Settings.RightClickColor = RightClickColor.Name;
settingsProvider.SaveSettings(Settings);
}
diff --git a/src/Carnac/packages.config b/src/Carnac/packages.config
index f61f5d46..dcd43146 100644
--- a/src/Carnac/packages.config
+++ b/src/Carnac/packages.config
@@ -6,6 +6,7 @@
+