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

luttje / Key2Joy / 6517266969

14 Oct 2023 11:05AM UTC coverage: 12.469% (+0.2%) from 12.308%
6517266969

push

github

web-flow
Implementing plugins for better separation (#39)

* Start implementing plugins for better separation
* massive refactor in attempt to split appdomains for plugins
* (breaks old mapping profiles)
* Fix error when switching from mouse button trigger to keyboard trigger and clicking in the combobox where the mouse button capture textbox is.
* Simplify code by removing legacy
* SImplify grouping actions
* Fix profile and add helpful opposite mapping generator tool
* Change solution hierarchy
* Restrict AppDomain plugins went from Zone.MyComputer -> .Internet
* create keypair in ci
* Install the .NET framework tools
* Run command in workflow
* Plugin permissions. Plugins disabled by default
* update readme (icon is no longer used)
* Plugin action runs in seperated process
* Remove unused dependencies.
* Fix action name display for mapping
* Fix Lua plugin script calls (NOTE: laggy when using MessageBox)
* convert project to sdk style
* Add editorconfig and start cleaning up
* Fix documentation. Update namespaces to match files (breaks profiles)
* Include all projects in tests, disable building docs on debug
* Add messagebox script action
* Add tests for pluginhost
* Remove administrator from window title test
* add some icons to ui
* Add enabling/disabling plugins
* Close plugins when Key2Joy shuts down
* Fix appcommand failing
* Fix plugin permission form crashing. Fix plugin load exception not showing warning
* Handle plugin host closing better when app has crashed
* Seperate host and client logic in remote event subscriber
* Ensure the PluginHost shuts down if the app crashes
* Better error output for plugins
* Fix cmd interop not working, add some tests
* also generate docs on plugins
* Fix build order with docs
* Fix enum script parameters and ensure actions share environment scopes
* Fix _wpftmp folders being created dotnet/wpf#2930
* Fix sequence action. Add disabled trigger/action for unloaded plugins on start... (continued)

180 of 1703 branches covered (0.0%)

Branch coverage included in aggregate %.

6419 of 6419 new or added lines in 207 files covered. (100.0%)

1035 of 8041 relevant lines covered (12.87%)

8445.05 hits per line

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

0.0
/Core/Key2Joy.Core/LowLevelInput/GlobalInputHook.cs
1
using System;
2
using System.ComponentModel;
3
using System.Diagnostics;
4
using System.Runtime.InteropServices;
5

6
namespace Key2Joy.LowLevelInput;
7

8
// Based on these sources:
9
// - https://gist.github.com/Stasonix/3181083
10
// - https://stackoverflow.com/a/34384189
11
public partial class GlobalInputHook : IDisposable
12
{
13
    public event EventHandler<GlobalKeyboardHookEventArgs> KeyboardInputEvent;
14
    public event EventHandler<GlobalMouseHookEventArgs> MouseInputEvent;
15

16
    private readonly IntPtr[] windowsHookHandles;
17
    private IntPtr user32LibraryHandle;
18
    private HookProc hookProc;
19

20
    private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
21

22
    [DllImport("kernel32.dll")]
23
    private static extern IntPtr LoadLibrary(string lpFileName);
24

25
    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
26
    private static extern bool FreeLibrary(IntPtr hModule);
27

28
    /// <summary>
29
    /// The SetWindowsHookEx function installs an application-defined hook procedure into a hook chain.
30
    /// You would install a hook procedure to monitor the system for certain types of events. These events are
31
    /// associated either with a specific thread or with all threads in the same desktop as the calling thread.
32
    /// </summary>
33
    /// <param name="idHook">hook type</param>
34
    /// <param name="lpfn">hook procedure</param>
35
    /// <param name="hMod">handle to application instance</param>
36
    /// <param name="dwThreadId">thread identifier</param>
37
    /// <returns>If the function succeeds, the return value is the handle to the hook procedure.</returns>
38
    [DllImport("USER32", SetLastError = true)]
39
    private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
40

41
    /// <summary>
42
    /// The UnhookWindowsHookEx function removes a hook procedure installed in a hook chain by the SetWindowsHookEx function.
43
    /// </summary>
44
    /// <param name="hhk">handle to hook procedure</param>
45
    /// <returns>If the function succeeds, the return value is true.</returns>
46
    [DllImport("USER32", SetLastError = true)]
47
    public static extern bool UnhookWindowsHookEx(IntPtr hHook);
48

49
    /// <summary>
50
    /// The CallNextHookEx function passes the hook information to the next hook procedure in the current hook chain.
51
    /// A hook procedure can call this function either before or after processing the hook information.
52
    /// </summary>
53
    /// <param name="hHook">handle to current hook</param>
54
    /// <param name="code">hook code passed to hook procedure</param>
55
    /// <param name="wParam">value passed to hook procedure</param>
56
    /// <param name="lParam">value passed to hook procedure</param>
57
    /// <returns>If the function succeeds, the return value is true.</returns>
58
    [DllImport("USER32", SetLastError = true)]
59
    private static extern IntPtr CallNextHookEx(IntPtr hHook, int code, IntPtr wParam, IntPtr lParam);
60

61
    public GlobalInputHook()
×
62
    {
×
63
        this.windowsHookHandles = new IntPtr[2];
×
64
        this.user32LibraryHandle = IntPtr.Zero;
×
65
        this.hookProc = this.LowLevelInputHook; // we must keep alive hookProc, because GC is not aware about SetWindowsHookEx behaviour.
×
66

67
        this.user32LibraryHandle = LoadLibrary("User32");
×
68
        if (this.user32LibraryHandle == IntPtr.Zero)
×
69
        {
×
70
            var errorCode = Marshal.GetLastWin32Error();
×
71
            throw new Win32Exception(errorCode, $"Failed to load library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
×
72
        }
73

74
        var windowsHooks = new int[] { WH_KEYBOARD_LL, WH_MOUSE_LL };
×
75

76
        for (var i = 0; i < windowsHooks.Length; i++)
×
77
        {
×
78
            var windowsHook = windowsHooks[i];
×
79

80
            this.windowsHookHandles[i] = SetWindowsHookEx(windowsHook, this.hookProc, this.user32LibraryHandle, 0);
×
81

82
            if (this.windowsHookHandles[i] == IntPtr.Zero)
×
83
            {
×
84
                var errorCode = Marshal.GetLastWin32Error();
×
85
                throw new Win32Exception(errorCode, $"Failed to adjust input hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
×
86
            }
87
        }
×
88
    }
×
89

90
    protected virtual void Dispose(bool disposing)
91
    {
×
92
        if (disposing)
×
93
        {
×
94
            // because we can unhook only in the same thread, not in garbage collector thread
95
            for (var i = 0; i < this.windowsHookHandles.Length; i++)
×
96
            {
×
97
                var windowsHookHandle = this.windowsHookHandles[i];
×
98

99
                if (windowsHookHandle != IntPtr.Zero)
×
100
                {
×
101
                    if (!UnhookWindowsHookEx(windowsHookHandle))
×
102
                    {
×
103
                        var errorCode = Marshal.GetLastWin32Error();
×
104
                        throw new Win32Exception(errorCode, $"Failed to remove input hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
×
105
                    }
106
                    this.windowsHookHandles[i] = IntPtr.Zero;
×
107

108
                    // ReSharper disable once DelegateSubtraction
109
                    this.hookProc -= this.LowLevelInputHook;
×
110
                }
×
111
            }
×
112
        }
×
113

114
        if (this.user32LibraryHandle != IntPtr.Zero)
×
115
        {
×
116
            if (!FreeLibrary(this.user32LibraryHandle)) // reduces reference to library by 1.
×
117
            {
×
118
                var errorCode = Marshal.GetLastWin32Error();
×
119
                throw new Win32Exception(errorCode, $"Failed to unload library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
×
120
            }
121
            this.user32LibraryHandle = IntPtr.Zero;
×
122
        }
×
123
    }
×
124

125
    ~GlobalInputHook()
126
    {
×
127
        this.Dispose(false);
×
128
    }
×
129

130
    public void Dispose()
131
    {
×
132
        this.Dispose(true);
×
133
        GC.SuppressFinalize(this);
×
134
    }
×
135

136
    public const int WH_KEYBOARD_LL = 13;
137
    public const int WH_MOUSE_LL = 14;
138

139
    public IntPtr LowLevelInputHook(int nCode, IntPtr wParam, IntPtr lParam)
140
    {
×
141
        var isInputHandled = false;
×
142
        var wparamTyped = wParam.ToInt32();
×
143

144
        if (Enum.IsDefined(typeof(KeyboardState), wparamTyped))
×
145
        {
×
146
            var o = Marshal.PtrToStructure(lParam, typeof(LowLevelKeyboardInputEvent));
×
147
            var p = (LowLevelKeyboardInputEvent)o;
×
148

149
            GlobalKeyboardHookEventArgs eventArguments = new(p, (KeyboardState)wparamTyped);
×
150

151
            var handler = KeyboardInputEvent;
×
152
            handler?.Invoke(this, eventArguments);
×
153

154
            isInputHandled = eventArguments.Handled;
×
155
        }
×
156
        else if (Enum.IsDefined(typeof(MouseState), wparamTyped))
×
157
        {
×
158
            var o = Marshal.PtrToStructure(lParam, typeof(LowLevelMouseInputEvent));
×
159
            var p = (LowLevelMouseInputEvent)o;
×
160

161
            GlobalMouseHookEventArgs eventArguments = new(p, (MouseState)wparamTyped);
×
162

163
            var handler = MouseInputEvent;
×
164
            handler?.Invoke(this, eventArguments);
×
165

166
            isInputHandled = eventArguments.Handled;
×
167
        }
×
168

169
        return isInputHandled ? (IntPtr)1 : CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
×
170
    }
×
171
}
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