1
1
using System ;
2
+ using System . Collections . Generic ;
2
3
using System . Runtime . InteropServices ;
4
+ using System . Text ;
3
5
using System . Threading ;
4
6
5
7
using log4net ;
@@ -24,6 +26,28 @@ public class GlobalKeyPressService {
24
26
/// </summary>
25
27
private static User32 . SafeHookHandle ? s_hook ;
26
28
29
+ /// <summary>
30
+ /// Maps each modifier key to whether or not it's being held down. This is kind overkill but it's at least accurate.
31
+ /// </summary>
32
+ private static readonly Dictionary < Keys , bool > holding = new ( ) {
33
+ { Keys . Control , false } ,
34
+ { Keys . ControlKey , false } ,
35
+ { Keys . LControlKey , false } ,
36
+ { Keys . RControlKey , false } ,
37
+ { Keys . Shift , false } ,
38
+ { Keys . ShiftKey , false } ,
39
+ { Keys . LShiftKey , false } ,
40
+ { Keys . RShiftKey , false }
41
+ } ;
42
+
43
+ /// <summary>
44
+ /// The possible modifier keys for quick comparison at runtime.
45
+ /// </summary>
46
+ private static readonly HashSet < Keys > modifiers = [
47
+ Keys . Control , Keys . ControlKey , Keys . LControlKey , Keys . RControlKey ,
48
+ Keys . Shift , Keys . ShiftKey , Keys . LShiftKey , Keys . RShiftKey
49
+ ] ;
50
+
27
51
/// <summary>
28
52
/// The thread to execute on.
29
53
/// </summary>
@@ -43,7 +67,7 @@ public GlobalKeyPressService() {
43
67
/// <summary>
44
68
/// Gets or sets the callbacks to invoke when a keystroke is pressed.
45
69
/// </summary>
46
- public static Action < string > ? OnKeystroke { get ; set ; }
70
+ public static Action < Keys , bool , bool , bool > ? OnKeystroke { get ; set ; }
47
71
48
72
/// <summary>
49
73
/// The main loop which registers for keystrokes on the system and flushes the message buffer.
@@ -69,13 +93,52 @@ private void Main() {
69
93
private static int KeystrokeCallback ( int nCode , IntPtr wParam , IntPtr lParam ) {
70
94
var keyboardEvent = Marshal . PtrToStructure < KeyboardLowLevelHookStruct > ( lParam ) ;
71
95
var whatHappened = ( KeyboardMessage ) wParam ;
96
+ var key = ( Keys ) keyboardEvent . vkCode ;
72
97
73
98
if ( whatHappened == KeyboardMessage . KEY_DOWN ) {
74
- var key = ( Keys ) keyboardEvent . vkCode ;
75
- LOGGER . Debug ( $ "Key pressed: { key } ") ;
76
- OnKeystroke ? . Invoke ( key . ToString ( ) ) ;
99
+ if ( modifiers . Contains ( key ) ) {
100
+ holding [ key ] = true ;
101
+ }
102
+
103
+ // You don't get a keyboard event for ALT, you have to check it this way.
104
+ bool holdingAlt = ( keyboardEvent . flags & 0b_0001_0000 ) != 0 ;
105
+ bool holdingCtrl = holding [ Keys . Control ] || holding [ Keys . ControlKey ] || holding [ Keys . LControlKey ] || holding [ Keys . RControlKey ] ;
106
+ bool holdingShift = holding [ Keys . Shift ] || holding [ Keys . ShiftKey ] || holding [ Keys . LShiftKey ] || holding [ Keys . RShiftKey ] ;
107
+ LogKey ( key , holdingCtrl , holdingAlt , holdingShift ) ;
108
+ OnKeystroke ? . Invoke ( key , holdingCtrl , holdingAlt , holdingShift ) ;
109
+ }
110
+ else if ( whatHappened == KeyboardMessage . KEY_UP ) {
111
+ if ( modifiers . Contains ( key ) ) {
112
+ holding [ key ] = false ;
113
+ }
77
114
}
78
115
79
116
return User32 . CallNextHookEx ( s_hook ? . DangerousGetHandle ( ) ?? 0 , nCode , wParam , lParam ) ;
80
117
}
118
+
119
+ /// <summary>
120
+ /// Logs the keystroke for debugging.
121
+ /// </summary>
122
+ /// <param name="key">The key pressed.</param>
123
+ /// <param name="holdingCtrl">True if control is held down.</param>
124
+ /// <param name="holdingAlt">True if alt is held down.</param>
125
+ /// <param name="holdingShift">True if shift is held down.</param>
126
+ private static void LogKey ( Keys key , bool holdingCtrl , bool holdingAlt , bool holdingShift ) {
127
+ var sb = new StringBuilder ( "Key Pressed: " ) ;
128
+ if ( holdingCtrl ) {
129
+ sb . Append ( "Ctrl + " ) ;
130
+ }
131
+
132
+ if ( holdingShift ) {
133
+ sb . Append ( "Shift + " ) ;
134
+ }
135
+
136
+ if ( holdingAlt ) {
137
+ sb . Append ( "Alt + " ) ;
138
+ }
139
+
140
+ sb . Append ( key ) ;
141
+
142
+ LOGGER . Debug ( sb . ToString ( ) ) ;
143
+ }
81
144
}
0 commit comments