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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +