11using System ;
22using System . Collections . Generic ;
33using System . Runtime . InteropServices ;
4- using System . Text ;
54using System . Threading ;
65
76namespace 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