• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dapplo / Dapplo.Windows / 22325004088

23 Feb 2026 09:11PM UTC coverage: 32.847% (-0.2%) from 33.028%
22325004088

push

github

Lakritzator
Merge branch 'master' of https://github.com/dapplo/Dapplo.Windows

619 of 2002 branches covered (30.92%)

Branch coverage included in aggregate %.

1702 of 5064 relevant lines covered (33.61%)

29.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.73
/src/Dapplo.Windows.Input/Keyboard/KeyboardHook.cs
1
// Copyright (c) Dapplo and contributors. All rights reserved.
2
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3

4
using System;
5
using System.Reactive.Concurrency;
6
using System.Reactive.Disposables;
7
using System.Reactive.Linq;
8
using System.Runtime.InteropServices;
9
using System.Runtime.Versioning;
10
using Dapplo.Windows.Input.Enums;
11
using Dapplo.Windows.Input.Structs;
12

13
namespace Dapplo.Windows.Input.Keyboard;
14

15
/// <summary>
16
///     A global keyboard hook, using System.Reactive
17
/// </summary>
18
public sealed class KeyboardHook
19
{
20
    /// <summary>
21
    ///     The singleton of the KeyboardHook
22
    /// </summary>
23
    private static readonly Lazy<KeyboardHook> Singleton = new Lazy<KeyboardHook>(() => new KeyboardHook());
2✔
24

25
    /// <summary>
26
    ///     Used to store the observable
27
    /// </summary>
28
    private readonly IObservable<KeyboardHookEventArgs> _keyObservable;
29

30
    /// <summary>
31
    ///     Store the handler, otherwise it might be GCed
32
    /// </summary>
33
    // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
34
    private LowLevelKeyboardProc _callback;
35

36
    /// <summary>
37
    ///     Private constructor to create the observable
38
    /// </summary>
39
    private KeyboardHook()
1✔
40
    {
41
        _keyObservable = Observable.Create<KeyboardHookEventArgs>(observer =>
1✔
42
            {
1✔
43
                // Make sure the current state of the lock keys is retrieved
1✔
44
                SyncLockState();
4✔
45

1✔
46
                var hookId = IntPtr.Zero;
4✔
47
                // Need to hold onto this callback, otherwise it will get garbage collected as it is an un-manged callback
1✔
48
                _callback = (nCode, wParam, lParam) =>
4✔
49
                {
4✔
50
                    if (nCode >= 0)
46✔
51
                    {
4✔
52
                        var eventArgs = CreateKeyboardEventArgs(wParam, lParam);
46✔
53
                        observer.OnNext(eventArgs);
46✔
54
                        if (eventArgs.Handled)
46✔
55
                        {
4✔
56
                            return (IntPtr) 1;
13✔
57
                        }
4✔
58
                    }
4✔
59
                    // ReSharper disable once AccessToModifiedClosure
4✔
60
                    return CallNextHookEx(hookId, nCode, wParam, lParam);
33✔
61
                };
4✔
62

1✔
63
                hookId = SetWindowsHookEx(HookTypes.WH_KEYBOARD_LL, _callback, IntPtr.Zero, 0);
4✔
64

1✔
65
                return Disposable.Create(() =>
4✔
66
                {
4✔
67
                    UnhookWindowsHookEx(hookId);
4✔
68
                    _callback = null;
4✔
69
                });
8✔
70
            })
1✔
71
            // Make sure the key presses come in sequentially
1✔
72
            .Synchronize()
1✔
73
            // Make sure the subscribed logic runs on the current thread, so we can process the "handled" property
1✔
74
            .ObserveOn(Scheduler.CurrentThread)
1✔
75
            .Publish()
1✔
76
            .RefCount();
1✔
77
    }
1✔
78

79
    /// <summary>
80
    ///     The actual keyboard hook observable
81
    /// </summary>
82
    public static IObservable<KeyboardHookEventArgs> KeyboardEvents => Singleton.Value._keyObservable;
4✔
83

84
    /// <summary>
85
    ///     Create the KeyboardHookEventArgs from the parameters which where in the event
86
    /// </summary>
87
    /// <param name="wParam">IntPtr</param>
88
    /// <param name="lParam">IntPtr</param>
89
    /// <returns>KeyboardHookEventArgs</returns>
90
    private static KeyboardHookEventArgs CreateKeyboardEventArgs(IntPtr wParam, IntPtr lParam)
91
    {
92
        var isKeyDown = wParam == (IntPtr) WmKeyDown || wParam == (IntPtr) WmSysKeyDown;
46✔
93

94
        var keyboardLowLevelHookStruct = (KeyboardLowLevelHookStruct) Marshal.PtrToStructure(lParam, typeof(KeyboardLowLevelHookStruct));
46✔
95
        bool isModifier = keyboardLowLevelHookStruct.VirtualKeyCode.IsModifier();
46✔
96

97
        // Check the key to find if there any modifiers, store these in the global values.
98
        switch (keyboardLowLevelHookStruct.VirtualKeyCode)
46!
99
        {
100
            case VirtualKeyCode.Capital:
101
                if (isKeyDown)
×
102
                {
103
                    _capsLock = !_capsLock;
×
104
                }
105
                break;
×
106
            case VirtualKeyCode.NumLock:
107
                if (isKeyDown)
×
108
                {
109
                    _numLock = !_numLock;
×
110
                }
111
                break;
×
112
            case VirtualKeyCode.Scroll:
113
                if (isKeyDown)
×
114
                {
115
                    _scrollLock = !_scrollLock;
×
116
                }
117
                break;
×
118
            case VirtualKeyCode.LeftShift:
119
                _leftShift = isKeyDown;
14✔
120
                break;
14✔
121
            case VirtualKeyCode.RightShift:
122
                _rightShift = isKeyDown;
4✔
123
                break;
4✔
124
            case VirtualKeyCode.LeftControl:
125
                _leftCtrl = isKeyDown;
×
126
                break;
×
127
            case VirtualKeyCode.RightControl:
128
                _rightCtrl = isKeyDown;
×
129
                break;
×
130
            case VirtualKeyCode.LeftMenu:
131
                _leftAlt = isKeyDown;
×
132
                break;
×
133
            case VirtualKeyCode.RightMenu:
134
                _rightAlt = isKeyDown;
×
135
                break;
×
136
            case VirtualKeyCode.LeftWin:
137
                _leftWin = isKeyDown;
×
138
                break;
×
139
            case VirtualKeyCode.RightWin:
140
                _rightWin = isKeyDown;
×
141
                break;
142
        }
143

144
        var keyEventArgs = new KeyboardHookEventArgs
46✔
145
        {
46✔
146
            TimeStamp = keyboardLowLevelHookStruct.TimeStamp,
46✔
147
            Key = keyboardLowLevelHookStruct.VirtualKeyCode,
46✔
148
            Flags = keyboardLowLevelHookStruct.Flags,
46✔
149
            IsModifier = isModifier,
46✔
150
            IsKeyDown = isKeyDown,
46✔
151
            IsLeftShift = _leftShift,
46✔
152
            IsRightShift = _rightShift,
46✔
153
            IsLeftAlt = _leftAlt,
46✔
154
            IsRightAlt = _rightAlt,
46✔
155
            IsLeftControl = _leftCtrl,
46✔
156
            IsRightControl = _rightCtrl,
46✔
157
            IsLeftWindows = _leftWin,
46✔
158
            IsRightWindows = _rightWin,
46✔
159
            IsScrollLockActive = _scrollLock,
46✔
160
            IsNumLockActive = _numLock,
46✔
161
            IsCapsLockActive = _capsLock
46✔
162
        };
46✔
163

164
        // Do we need this??
165
        //http://msdn.microsoft.com/en-us/library/windows/desktop/ms646286(v=vs.85).aspx
166
        if (!keyEventArgs.IsAlt && (wParam == (IntPtr) WmSysKeyDown || wParam == (IntPtr)WmSysKeyUp))
46!
167
        {
168
            keyEventArgs.IsLeftAlt = true;
×
169
            keyEventArgs.IsSystemKey = true;
×
170
        }
171
        return keyEventArgs;
46✔
172
    }
173

174
    /// <summary>
175
    ///     Flags for the current state
176
    /// </summary>
177
    private static bool _leftShift;
178

179
    private static bool _rightShift;
180
    private static bool _leftAlt;
181
    private static bool _rightAlt;
182
    private static bool _leftCtrl;
183
    private static bool _rightCtrl;
184
    private static bool _leftWin;
185
    private static bool _rightWin;
186

187
    // Flags for the lock keys, initialize the locking keys state one time, these will be updated later
188
    private static bool _capsLock;
189

190
    private static bool _numLock;
191
    private static bool _scrollLock;
192

193
    /// <summary>
194
    ///     Sync the lock key state
195
    /// </summary>
196
    private static void SyncLockState()
197
    {
198
        _capsLock = GetKeyState(VirtualKeyCode.Capital) > 0;
4✔
199
        _numLock = GetKeyState(VirtualKeyCode.NumLock) > 0;
4✔
200
        _scrollLock = GetKeyState(VirtualKeyCode.Scroll) > 0;
4✔
201
    }
4✔
202

203
    private const int WmKeyDown = 256;
204
    private const int WmSysKeyUp = 261;
205
    private const int WmSysKeyDown = 260;
206

207
    /// <summary>
208
    ///     Retrieve the state of a key
209
    /// </summary>
210
    /// <param name="keyCode"></param>
211
    /// <returns></returns>
212
    [DllImport("user32.dll", ExactSpelling = true)]
213
    [ResourceExposure(ResourceScope.None)]
214
    private static extern ushort GetKeyState(VirtualKeyCode keyCode);
215

216
    /// <summary>
217
    ///     The actual delegate for the p
218
    /// </summary>
219
    /// <param name="nCode"></param>
220
    /// <param name="wParam"></param>
221
    /// <param name="lParam"></param>
222
    /// <returns></returns>
223
    private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
224

225
    /// <summary>
226
    ///     Register a windows hook
227
    /// </summary>
228
    /// <param name="hookType">HookTypes</param>
229
    /// <param name="lowLevelKeyboardProc">LowLevelKeyboardProc</param>
230
    /// <param name="hMod">IntPtr</param>
231
    /// <param name="dwThreadId">uint</param>
232
    /// <returns>ID to be able to unhook it again</returns>
233
    [DllImport("user32.dll", SetLastError = true)]
234
    private static extern IntPtr SetWindowsHookEx(HookTypes hookType, LowLevelKeyboardProc lowLevelKeyboardProc, IntPtr hMod, uint dwThreadId);
235

236
    /// <summary>
237
    ///     Used to remove a hook which was set with SetWindowsHookEx
238
    /// </summary>
239
    /// <param name="hhk"></param>
240
    /// <returns></returns>
241
    [DllImport("user32.dll", SetLastError = true)]
242
    [return: MarshalAs(UnmanagedType.Bool)]
243
    private static extern bool UnhookWindowsHookEx(IntPtr hhk);
244

245
    /// <summary>
246
    ///     Used to call the next hook in the list, if there was any
247
    /// </summary>
248
    /// <param name="hhk"></param>
249
    /// <param name="nCode"></param>
250
    /// <param name="wParam"></param>
251
    /// <param name="lParam"></param>
252
    /// <returns>IntPtr</returns>
253
    [DllImport("user32.dll", SetLastError = true)]
254
    private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
255
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc