Skip to content

Commit 98fa0d4

Browse files
unknownunknown
authored andcommitted
Fixes florentbr#43: Hotkey <Escape> still locked when not executing.
Other apps were not responding to the escape key because Windows allows only one registration per hotkey. Fixes it by unsubcribing the hotkey when Quit is called. Also simplifies the code by using a global wait handle.
1 parent f52713f commit 98fa0d4

File tree

2 files changed

+83
-187
lines changed

2 files changed

+83
-187
lines changed

Selenium/Core/SysWaiter.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Selenium.Internal;
22
using System;
3+
using System.Security.AccessControl;
34
using System.Threading;
45

56
namespace Selenium.Core {
@@ -27,7 +28,20 @@ class SysWaiter {
2728
public static Action OnInterrupt;
2829

2930
static SysWaiter() {
30-
_signal_interrupt = new EventWaitHandle(false, EventResetMode.ManualReset);
31+
string user = Environment.UserDomainName + "\\" + Environment.UserName;
32+
var rule = new EventWaitHandleAccessRule(user,
33+
EventWaitHandleRights.FullControl,
34+
AccessControlType.Allow);
35+
var security = new EventWaitHandleSecurity();
36+
security.AddAccessRule(rule);
37+
38+
bool createdNew;
39+
_signal_interrupt = new EventWaitHandle(false,
40+
EventResetMode.ManualReset,
41+
@"Global\InterruptKey",
42+
out createdNew,
43+
security);
44+
3145
HotKeyGlobal.Subscribe(MOD_NONE, VK_ESCAPE, Interrupt);
3246
HotKeyGlobal.Subscribe(MOD_NONE, VK_PAUSE, Interrupt);
3347
}
@@ -36,6 +50,10 @@ public static void Initialize() {
3650
HotKeyGlobal.SubscribeAgain();
3751
}
3852

53+
public static void Terminate() {
54+
HotKeyGlobal.UnsubscribeAll();
55+
}
56+
3957
private static void Interrupt() {
4058
_signal_interrupt.Set();
4159
if (OnInterrupt != null)

Selenium/Internal/HotKeyGlobal.cs

Lines changed: 64 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -1,225 +1,124 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Runtime.InteropServices;
4-
using System.Text;
54
using System.Threading;
65

76
namespace Selenium.Internal {
87

98
/// <summary>
10-
/// A class to register hot keys globally.
11-
/// Creates a message pump and uses RegisterHotKey from the windows API to register the hot key.
12-
/// Also creates a hidden window to track the thread as the same hot key can't be registered twice.
13-
/// A thread that successfully registered a hot key will once triggered, trigger all the other
14-
/// threads having the same class name (hotkey952873).
9+
/// A class to register hot keys globally with the RegisterHotKey API.
1510
/// </summary>
1611
class HotKeyGlobal {
1712

18-
static HotKeyGlobal _instance_;
19-
20-
public static void Subscribe(int modifierCode, int virtualKeyCode, Action action) {
21-
if (_instance_ == null)
22-
_instance_ = new HotKeyGlobal();
23-
_instance_.RegisterHotKey(modifierCode, virtualKeyCode, action);
24-
}
25-
26-
public static void SubscribeAgain() {
27-
_instance_.RegisterAgain();
28-
}
29-
30-
public static void UnsubscribeAll() {
31-
_instance_.UnregisterAll();
32-
}
33-
34-
35-
private const string CLASS_NAME = "hotkey952873";
36-
private const int WM_USER_HOTKEY_REGISTER = 0x0401;
37-
private const int WM_USER_HOTKEY_UNREGISTER_ALL = 0x0402;
38-
39-
private Thread _thread;
40-
private uint _threadId;
41-
private readonly object _threadLock = new object();
42-
private readonly List<int> _hotKeys = new List<int>(5);
43-
private readonly List<Action> _actions = new List<Action>(5);
44-
private volatile int _registeredCount = 0;
45-
private bool _disposed = false;
46-
47-
public HotKeyGlobal() {
48-
_thread = new Thread(RunMessagePump);
49-
_thread.IsBackground = true;
50-
_thread.SetApartmentState(ApartmentState.STA);
51-
lock (_threadLock){
52-
_thread.Start();
53-
Monitor.Wait(_threadLock);
13+
const int WM_HOTKEY = 0x0312;
14+
const int WM_USER_HOTKEY_REGISTER = 0x0401;
15+
const int WM_USER_HOTKEY_UNREGISTER_ALL = 0x0402;
16+
17+
static Thread _thread_;
18+
static uint _threadId_;
19+
static int _baseId_;
20+
static List<int> _hotKeys_ = new List<int>(5);
21+
static List<Action> _actions_ = new List<Action>(5);
22+
static int _registeredCount_ = 0;
23+
24+
public static void Subscribe(int modifier, int virtualKey, Action action) {
25+
if (_thread_ == null) {
26+
_thread_ = new Thread(RunMessagePump);
27+
_thread_.SetApartmentState(ApartmentState.STA);
28+
_thread_.IsBackground = true;
29+
lock (_thread_) {
30+
_thread_.Start();
31+
Monitor.Wait(_thread_);
32+
}
5433
}
55-
}
5634

57-
~HotKeyGlobal() {
58-
this.Dispose();
59-
}
35+
int hotKey = (virtualKey << 16) | modifier;
36+
_hotKeys_.Add(hotKey);
37+
_actions_.Add(action);
6038

61-
public void Dispose() {
62-
if (_disposed)
63-
return;
64-
_thread.Interrupt();
65-
_disposed = true;
39+
PostThreadMessage(WM_USER_HOTKEY_REGISTER, modifier, virtualKey);
6640
}
6741

68-
private void RegisterHotKey(int modifierCode, int virtualKeyCode, Action action) {
69-
int hotKey = (virtualKeyCode << 16) | modifierCode;
70-
_hotKeys.Add(hotKey);
71-
_actions.Add(action);
72-
PostThreadMessage(WM_USER_HOTKEY_REGISTER, modifierCode, virtualKeyCode);
73-
}
74-
75-
private void RegisterAgain() {
76-
_registeredCount = 0;
77-
for (int i = _actions.Count; i-- > 0; ) {
78-
int modifierCode = _hotKeys[i] & 0xFFFF;
79-
int virtualKeyCode = _hotKeys[i] >> 16;
42+
public static void SubscribeAgain() {
43+
_registeredCount_ = 0;
44+
for (int i = _actions_.Count; i-- > 0; ) {
45+
int modifierCode = _hotKeys_[i] & 0xFFFF;
46+
int virtualKeyCode = _hotKeys_[i] >> 16;
8047
PostThreadMessage(WM_USER_HOTKEY_REGISTER, modifierCode, virtualKeyCode);
8148
}
8249
}
8350

84-
private void UnregisterAll() {
85-
_hotKeys.Clear();
86-
_actions.Clear();
51+
public static void UnsubscribeAll() {
8752
PostThreadMessage(WM_USER_HOTKEY_UNREGISTER_ALL, 0, 0);
8853
}
8954

90-
private void EvalHotKey(uint hotkey) {
91-
for (int i = _hotKeys.Count; i-- > 0; ) {
92-
if (_hotKeys[i] == hotkey)
93-
_actions[i]();
55+
static void EvalHotKey(uint hotkey) {
56+
for (int i = _hotKeys_.Count; i-- > 0; ) {
57+
if (_hotKeys_[i] == hotkey) {
58+
_actions_[i]();
59+
}
9460
}
9561
}
9662

97-
private bool PostThreadMessage(int msg, int wParam, int lParam) {
98-
return NativeMethods.PostThreadMessage(_threadId, msg, (IntPtr)wParam, (IntPtr)lParam);
99-
}
100-
101-
private void RunMessagePump() {
102-
_threadId = NativeMethods.GetCurrentThreadId();
103-
104-
//create the window to track sibling threads
105-
IntPtr user32 = NativeMethods.GetModuleHandle(NativeMethods.USER32);
106-
107-
string name_DefWindowProc = Marshal.SystemDefaultCharSize == 1 ?
108-
"DefWindowProcA" : "DefWindowProcW";
109-
110-
NativeMethods.WNDCLASSEX classEx = new NativeMethods.WNDCLASSEX();
111-
classEx.lpszClassName = CLASS_NAME;
112-
classEx.lpfnWndProc = NativeMethods.GetProcAddress(user32, name_DefWindowProc);
113-
classEx.cbSize = Marshal.SizeOf(typeof(NativeMethods.WNDCLASSEX));
114-
115-
IntPtr classHandle = (IntPtr)NativeMethods.RegisterClassEx(classEx);
116-
IntPtr winHandle = NativeMethods.CreateWindowEx(0, classHandle, string.Empty
117-
, 0, 0, 0, 0, 0, (IntPtr)0, (IntPtr)0, (IntPtr)0, (IntPtr)0);
118-
63+
static void RunMessagePump() {
64+
_threadId_ = Native.GetCurrentThreadId();
65+
_baseId_ = (int)(_threadId_ & 0x0FFF);
11966
//signal the message pump is up and running
120-
lock (_threadLock)
121-
Monitor.Pulse(_threadLock);
67+
lock (_thread_) {
68+
Monitor.Pulse(_thread_);
69+
}
12270
//run message pump
12371
try {
12472
DispatchThreadMessages();
12573
} catch { }
12674
}
12775

128-
private void DispatchThreadMessages() {
129-
var msg = new NativeMethods.Message();
130-
while (NativeMethods.GetMessage(ref msg, (IntPtr)0, 0, 0) > 0) {
76+
static void DispatchThreadMessages() {
77+
var msg = new Native.Message();
78+
while (Native.GetMessage(ref msg, (IntPtr)0, 0, 0) > 0) {
13179
switch (msg.Msg) {
132-
case NativeMethods.WM_HOTKEY:
133-
if (_registeredCount > 0) {
134-
//Notify the sibling processes as only one regitration per hot key is allowed
135-
PostThreadsMessageToSibling(msg.Msg, (IntPtr)msg.WParam, (IntPtr)msg.LParam);
136-
}
137-
EvalHotKey((uint)msg.LParam); // lParam: LowerInt16=modifiers, UpperInt16=vkCode
80+
case WM_HOTKEY:
81+
EvalHotKey((uint)msg.LParam); // lword=modifiers, hword=virtualKey
13882
break;
13983
case WM_USER_HOTKEY_REGISTER:
140-
if (NativeMethods.RegisterHotKey(IntPtr.Zero, _registeredCount + 1, (uint)msg.WParam, (uint)msg.LParam))
141-
_registeredCount++;
84+
bool rhk = Native.RegisterHotKey(IntPtr.Zero,
85+
_baseId_ + _registeredCount_ + 1,
86+
(uint)msg.WParam,
87+
(uint)msg.LParam);
88+
if (rhk) {
89+
_registeredCount_++;
90+
}
14291
break;
14392
case WM_USER_HOTKEY_UNREGISTER_ALL:
144-
while (_registeredCount > 0)
145-
NativeMethods.UnregisterHotKey(IntPtr.Zero, _registeredCount--);
93+
while (_registeredCount_ > 0) {
94+
Native.UnregisterHotKey(IntPtr.Zero, _baseId_ + _registeredCount_);
95+
_registeredCount_--;
96+
}
14697
break;
14798
}
14899
}
149100
}
150101

151-
/// <summary>
152-
/// Post a message to all the sibling threads:
153-
/// Loops through all the top windows to find the matching class.
154-
/// </summary>
155-
/// <param name="msg"></param>
156-
/// <param name="wParam"></param>
157-
/// <param name="lParam"></param>
158-
private void PostThreadsMessageToSibling(int msg, IntPtr wParam, IntPtr lParam) {
159-
int length = CLASS_NAME.Length + 1;
160-
var buffer = new StringBuilder(length);
161-
NativeMethods.EnumWindows((IntPtr hwnd, IntPtr param) => {
162-
NativeMethods.GetClassName(hwnd, buffer, length);
163-
if (CLASS_NAME.Equals(buffer.ToString())) {
164-
uint threadId = NativeMethods.GetWindowThreadProcessId(hwnd, (IntPtr)0);
165-
if (threadId != _threadId)
166-
NativeMethods.PostThreadMessage(threadId, msg, wParam, lParam);
167-
}
168-
return true;
169-
}, IntPtr.Zero);
102+
static bool PostThreadMessage(int msg, int wParam, int lParam) {
103+
return Native.PostThreadMessage(_threadId_, msg, (IntPtr)wParam, (IntPtr)lParam);
170104
}
171105

172106

173107
#region Imports
174108

175-
static class NativeMethods {
109+
static class Native {
176110

177111
public const string KERNEL32 = "kernel32.dll";
178112
public const string USER32 = "user32.dll";
179113

180-
public const int WM_CREATE = 0x01;
181-
public const int WM_DESTROY = 0x02;
182-
public const int WM_QUIT = 0x0012;
183-
public const int WM_CLOSE = 0x0010;
184-
public const int WM_HOTKEY = 0x0312;
185-
186-
187-
[DllImport(USER32, CharSet = CharSet.Ansi, SetLastError = true)]
188-
public static extern IntPtr CreateWindowEx(
189-
int dwExStyle, IntPtr atomId, string lpszWindowName,
190-
int style, int x, int y, int width, int height,
191-
IntPtr hWndParent, IntPtr hMenu, IntPtr hInst, IntPtr pvParam);
192-
193-
[DllImport(USER32)]
194-
public static extern bool DestroyWindow(IntPtr hWnd);
195-
196114
[DllImport(USER32)]
197115
public static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
198116

199117
[DllImport(USER32)]
200118
public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
201119

202-
[DllImport(USER32)]
203-
public static extern ushort RegisterClassEx(WNDCLASSEX wc_d);
204-
205-
[DllImport(USER32)]
206-
public static extern int UnregisterClass(IntPtr atomId, IntPtr hInstance);
207-
208-
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
209-
public class WNDCLASSEX {
210-
public int cbSize = 0;
211-
public int style = 0;
212-
public IntPtr lpfnWndProc;
213-
public int cbClsExtra = 0;
214-
public int cbWndExtra = 0;
215-
public IntPtr hInstance = IntPtr.Zero;
216-
public IntPtr hIcon = IntPtr.Zero;
217-
public IntPtr hCursor = IntPtr.Zero;
218-
public IntPtr hbrBackground = IntPtr.Zero;
219-
public string lpszMenuName = null;
220-
public string lpszClassName = null;
221-
public IntPtr hIconSm = IntPtr.Zero;
222-
}
120+
[DllImport(KERNEL32, SetLastError = false)]
121+
public static extern uint GetCurrentThreadId();
223122

224123
[StructLayout(LayoutKind.Sequential)]
225124
public struct Message {
@@ -232,33 +131,12 @@ public struct Message {
232131
public int PointY;
233132
}
234133

235-
236-
[DllImport(USER32, SetLastError = true, EntryPoint = "GetMessageW", ExactSpelling = true, CharSet = CharSet.Unicode)]
134+
[DllImport(USER32, SetLastError = false)]
237135
public static extern int GetMessage([In, Out] ref Message msg, IntPtr hWnd, int uMsgFilterMin, int uMsgFilterMax);
238136

239137
[DllImport(USER32, SetLastError = false)]
240138
public static extern bool PostThreadMessage(uint threadId, int msg, IntPtr wParam, IntPtr lParam);
241139

242-
public delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);
243-
244-
[DllImport(USER32, SetLastError = false)]
245-
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
246-
247-
[DllImport(USER32, SetLastError = false)]
248-
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId);
249-
250-
[DllImport(USER32, SetLastError = false)]
251-
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
252-
253-
[DllImport(KERNEL32, SetLastError = false)]
254-
public static extern uint GetCurrentThreadId();
255-
256-
[DllImport(KERNEL32, SetLastError = false, CharSet = CharSet.Auto, BestFitMapping = false)]
257-
public static extern IntPtr GetModuleHandle(string modName);
258-
259-
[DllImport(KERNEL32, SetLastError = false, CharSet = CharSet.Ansi, BestFitMapping = false)]
260-
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
261-
262140
}
263141

264142
#endregion

0 commit comments

Comments
 (0)