Skip to content

Commit 3482ee8

Browse files
committed
stabilized hooks
1 parent b7a98c2 commit 3482ee8

File tree

4 files changed

+102
-61
lines changed

4 files changed

+102
-61
lines changed

RetailCoder.VBE/Common/KeyboardHook.cs

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
using System;
22
using System.ComponentModel;
33
using System.Diagnostics;
4-
using System.Runtime.InteropServices;
5-
using System.Windows.Forms;
64
using Microsoft.Vbe.Interop;
75
using Rubberduck.Common.WinAPI;
86

@@ -13,37 +11,50 @@ public class KeyboardHook : IAttachable
1311
private readonly VBE _vbe;
1412
private IntPtr _hookId;
1513

14+
private readonly User32.HookProc _callback;
15+
1616
public KeyboardHook(VBE vbe)
1717
{
1818
_vbe = vbe;
19+
_callback = HookCallback;
1920
}
2021

2122
private int _lastLineIndex;
2223
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
2324
{
24-
var pane = _vbe.ActiveCodePane;
25-
if (pane != null)
25+
try
2626
{
27-
int startLine;
28-
int endLine;
29-
int startColumn;
30-
int endColumn;
31-
32-
// not using extension method because a QualifiedSelection would be overkill:
33-
pane.GetSelection(out startLine, out startColumn, out endLine, out endColumn);
34-
if (startLine != _lastLineIndex)
27+
var pane = _vbe.ActiveCodePane;
28+
if (User32.IsVbeWindowActive((IntPtr)_vbe.MainWindow.HWnd) && pane != null && (WM)wParam == WM.KEYUP)
3529
{
36-
// if the current line has changed, let the KEYDOWN be written to the IDE, and notify on KEYUP:
37-
_lastLineIndex = startLine;
38-
if (nCode >= 0 && (WM)wParam == WM.KEYUP)
30+
Debug.WriteLine("KeyboardHook handles message (wParam:{0}, lParam:{1})", wParam, lParam);
31+
int startLine;
32+
int endLine;
33+
int startColumn;
34+
int endColumn;
35+
36+
// not using extension method because a QualifiedSelection would be overkill:
37+
pane.GetSelection(out startLine, out startColumn, out endLine, out endColumn);
38+
if (startLine != _lastLineIndex)
3939
{
40-
//var key = (Keys)Marshal.ReadInt32(lParam);
41-
OnMessageReceived();
40+
// if the current line has changed, let the KEYDOWN be written to the IDE, and notify on KEYUP:
41+
_lastLineIndex = startLine;
42+
if (nCode >= 0)
43+
{
44+
//var key = (Keys)Marshal.ReadInt32(lParam);
45+
OnMessageReceived();
46+
}
4247
}
4348
}
49+
50+
return User32.CallNextHookEx(_hookId, nCode, wParam, lParam);
51+
}
52+
catch (Exception exception)
53+
{
54+
Debug.WriteLine(exception);
4455
}
4556

46-
return User32.CallNextHookEx(_hookId, nCode, wParam, lParam);
57+
return IntPtr.Zero;
4758
}
4859

4960
private void OnMessageReceived()
@@ -70,7 +81,8 @@ public void Attach()
7081
{
7182
throw new Win32Exception();
7283
}
73-
_hookId = User32.SetWindowsHookEx(WindowsHook.KEYBOARD, HookCallback, handle, 0);
84+
85+
_hookId = User32.SetWindowsHookEx(WindowsHook.KEYBOARD_LL, _callback, handle, 0);
7486
if (_hookId == IntPtr.Zero)
7587
{
7688
throw new Win32Exception();

RetailCoder.VBE/Common/MouseHook.cs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,48 @@
11
using System;
22
using System.ComponentModel;
33
using System.Diagnostics;
4+
using Microsoft.Vbe.Interop;
45
using Rubberduck.Common.WinAPI;
56

67
namespace Rubberduck.Common
78
{
89
public class MouseHook : IAttachable
910
{
11+
private readonly VBE _vbe;
1012
private IntPtr _hookId;
13+
private readonly User32.HookProc _callback;
14+
15+
public MouseHook(VBE vbe)
16+
{
17+
_vbe = vbe;
18+
_callback = HookCallback;
19+
}
1120

1221
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
1322
{
14-
if (nCode >= 0)
23+
try
1524
{
16-
var button = (WM)wParam;
17-
if (button == WM.RBUTTONDOWN || button == WM.LBUTTONDOWN)
25+
var pane = _vbe.ActiveCodePane;
26+
if (User32.IsVbeWindowActive((IntPtr)_vbe.MainWindow.HWnd) && nCode >= 0 && pane != null)
1827
{
19-
// handle right-click to evaluate commands' CanExecute before the context menu is shown;
20-
// handle left-click to do the same before the Rubberduck menu is drawn, too.
21-
OnMessageReceived();
28+
Debug.WriteLine("MouseHook handles message (wParam:{0}, lParam:{1})", wParam, lParam);
29+
var button = (WM)wParam;
30+
if (button == WM.RBUTTONDOWN || button == WM.LBUTTONDOWN)
31+
{
32+
// handle right-click to evaluate commands' CanExecute before the context menu is shown;
33+
// handle left-click to do the same before the Rubberduck menu is drawn, too.
34+
OnMessageReceived();
35+
}
2236
}
37+
38+
return User32.CallNextHookEx(_hookId, nCode, wParam, lParam);
39+
}
40+
catch (Exception exception)
41+
{
42+
Debug.WriteLine(exception);
2343
}
2444

25-
return User32.CallNextHookEx(_hookId, nCode, wParam, lParam);
45+
return IntPtr.Zero;
2646
}
2747

2848
private void OnMessageReceived()
@@ -49,7 +69,7 @@ public void Attach()
4969
{
5070
throw new Win32Exception();
5171
}
52-
_hookId = User32.SetWindowsHookEx(WindowsHook.MOUSE, HookCallback, handle, 0);
72+
_hookId = User32.SetWindowsHookEx(WindowsHook.MOUSE_LL, _callback, handle, 0);
5373
if (_hookId == IntPtr.Zero)
5474
{
5575
throw new Win32Exception();

RetailCoder.VBE/Common/RubberduckHooks.cs

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ public class RubberduckHooks : IRubberduckHooks
2222
//private readonly IAttachable _timerHook;
2323
private readonly IGeneralConfigService _config;
2424
private readonly IList<IAttachable> _hooks = new List<IAttachable>();
25-
private MouseHook _mouseHook;
26-
private KeyboardHook _keyHook;
2725

2826
public RubberduckHooks(VBE vbe, IGeneralConfigService config)
2927
{
@@ -36,18 +34,18 @@ public RubberduckHooks(VBE vbe, IGeneralConfigService config)
3634

3735
_config = config;
3836

39-
_mouseHook = new MouseHook();
40-
_keyHook = new KeyboardHook(_vbe);
4137
}
4238

43-
4439
public void HookHotkeys()
4540
{
4641
Detach();
4742
_hooks.Clear();
4843

4944
var config = _config.LoadConfiguration();
5045
var settings = config.UserSettings.GeneralSettings.HotkeySettings;
46+
47+
AddHook(new KeyboardHook(_vbe));
48+
AddHook(new MouseHook(_vbe));
5149
foreach (var hotkey in settings.Where(hotkey => hotkey.IsEnabled))
5250
{
5351
AddHook(new Hotkey(_mainWindowHandle, hotkey.ToString(), hotkey.Command));
@@ -85,9 +83,6 @@ public void Attach()
8583

8684
try
8785
{
88-
_mouseHook.Attach();
89-
_keyHook.Attach();
90-
9186
foreach (var hook in Hooks)
9287
{
9388
hook.Attach();
@@ -96,9 +91,9 @@ public void Attach()
9691

9792
IsAttached = true;
9893
}
99-
catch (Exception e)
94+
catch (Exception exception)
10095
{
101-
Console.WriteLine(e);
96+
Debug.WriteLine(exception);
10297
}
10398
}
10499

@@ -119,9 +114,9 @@ public void Detach()
119114

120115
IsAttached = false;
121116
}
122-
catch (Exception e)
117+
catch (Exception exception)
123118
{
124-
Console.WriteLine(e);
119+
Debug.WriteLine(exception);
125120
}
126121
}
127122

@@ -144,10 +139,12 @@ private void hook_MessageReceived(object sender, HookEventArgs e)
144139
var hotkey = sender as IHotkey;
145140
if (hotkey != null)
146141
{
142+
Debug.WriteLine("Hotkey message received");
147143
hotkey.Command.Execute(null);
148144
return;
149145
}
150146

147+
Debug.WriteLine("Unknown message received");
151148
OnMessageReceived(sender, e);
152149
}
153150

@@ -169,37 +166,43 @@ private IntPtr WindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam)
169166
suppress = HandleHotkeyMessage(wParam);
170167
break;
171168

172-
case WM.ACTIVATEAPP:
173-
HandleActivateAppMessage(wParam);
174-
break;
169+
//case WM.ACTIVATEAPP:
170+
// HandleActivateAppMessage(wParam);
171+
// break;
175172
}
176173
}
177174

178-
if (suppress)
179-
{
180-
return IntPtr.Zero;
181-
}
175+
return suppress
176+
? IntPtr.Zero
177+
: User32.CallWindowProc(_oldWndProc, hWnd, uMsg, wParam, lParam);
182178
}
183179
catch (Exception exception)
184180
{
185-
Console.WriteLine(exception);
181+
Debug.WriteLine(exception);
186182
}
187183

188-
return User32.CallWindowProc(_oldWndProc, hWnd, uMsg, wParam, lParam);
184+
return IntPtr.Zero;
189185
}
190186

191187
private bool HandleHotkeyMessage(IntPtr wParam)
192188
{
193189
var processed = false;
194-
if (GetWindowThread(User32.GetForegroundWindow()) == GetWindowThread(_mainWindowHandle))
190+
try
195191
{
196-
var hook = Hooks.OfType<Hotkey>().SingleOrDefault(k => k.HotkeyInfo.HookId == wParam);
197-
if (hook != null)
192+
if (User32.IsVbeWindowActive(_mainWindowHandle))
198193
{
199-
hook.OnMessageReceived();
200-
processed = true;
194+
var hook = Hooks.OfType<Hotkey>().SingleOrDefault(k => k.HotkeyInfo.HookId == wParam);
195+
if (hook != null)
196+
{
197+
hook.OnMessageReceived();
198+
processed = true;
199+
}
201200
}
202201
}
202+
catch (Exception exception)
203+
{
204+
Debug.WriteLine(exception);
205+
}
203206
return processed;
204207
}
205208

@@ -226,13 +229,5 @@ private static int LoWord(IntPtr dw)
226229
{
227230
return unchecked((short)(uint)dw);
228231
}
229-
230-
private static IntPtr GetWindowThread(IntPtr hWnd)
231-
{
232-
uint hThread;
233-
User32.GetWindowThreadProcessId(hWnd, out hThread);
234-
235-
return (IntPtr)hThread;
236-
}
237232
}
238233
}

RetailCoder.VBE/Common/WinAPI/User32.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,20 @@ public static class User32
202202
[return: MarshalAs(UnmanagedType.Bool)]
203203
public static extern bool KillTimer(IntPtr hWnd, IntPtr uIDEvent);
204204

205+
/// <summary>
206+
/// A helper function that returns <c>true</c> when the specified handle is that of the foreground window.
207+
/// </summary>
208+
/// <param name="mainWindowHandle">The handle for the VBE's MainWindow.</param>
209+
/// <returns></returns>
210+
public static bool IsVbeWindowActive(IntPtr mainWindowHandle)
211+
{
212+
uint vbeThread;
213+
GetWindowThreadProcessId(mainWindowHandle, out vbeThread);
214+
215+
uint hThread;
216+
GetWindowThreadProcessId(GetForegroundWindow(), out hThread);
205217

218+
return (IntPtr)hThread == (IntPtr)vbeThread;
219+
}
206220
}
207221
}

0 commit comments

Comments
 (0)