How can I make a global keyboard hook for an Electron.NET app in C#? I believe as long as it works in a console app it should work properly in an Electron.Net app.
I made a 'solution' for this problem, but It tends to use up a lot of CPU (7-10%). Maybe someone is somehow able to make it actually efficient if there is no other option:
using System;
using System.Runtime.InteropServices;
using System.Threading;
[DllImport("User32.dll")]
public static extern short GetAsyncKeyState(int vKey);
// Other VKey codes: https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
public enum VKeys {
LBUTTON = 0x01, // Left mouse button
RBUTTON = 0x02, // Right mouse button
KEY_0 = 0x30, // 0 key
KEY_1 = 0x31, // 1 key
KEY_2 = 0x32, // 2 key
KEY_3 = 0x33, // 3 key
KEY_4 = 0x34, // 4 key
KEY_5 = 0x35, // 5 key
KEY_6 = 0x36, // 6 key
KEY_7 = 0x37, // 7 key
KEY_8 = 0x38, // 8 key
KEY_9 = 0x39 // 9 key
}
public void Start()
{
Thread HookThread = new Thread(delegate ()
{
var keys = Enum.GetValues(typeof(VKeys));
while (true)
{
foreach (int key in keys)
{
var ks = GetAsyncKeyState(key);
if (ks < 0)
{
Console.WriteLine($"pressed {key}");
//Thread.Sleep(100);
}
//Thread.Sleep(1); // Even sleeping for '1ms' will delay it too much
}
}
});
HookThread.Start();
}
A lot of things I found would only work if I was using WinForms or WPF.
Edit:
I tried this answer by hanabanashiku, and a lot of others that I found online, but all of them seemed to just lag the keyboards input and their callback functions would seem to be never called.
I decided to write the keyboard hook in C++, compile as a DLL and then reference that DLL in my C# code to hopefully make a keyboard hook that functioned properly and didn't cause any noticeable input lag, but that didn't work either.
The keyboard hook ran perfectly when run as an .exe in C++, but when I compiled it as a DLL and ran it in C# it caused the same issue as before - a lot of input lag and the callback function seemingly not being called.
Heres the code if anyone wants to try it:
KeyboardHook.cpp
#include "KeyboardHook.h"
#include <iostream>
#define __event void KeyDown(int key), KeyUp(int key);
using namespace Hooks;
void KeyDown(int key)
{
std::cout << "KeyDown\n";
}
void KeyUp(int key)
{
std::cout << "KeyUp\n";
}
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HC_ACTION)
{
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam;
switch (wParam)
{
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
KeyDown(p->vkCode);
break;
case WM_KEYUP:
case WM_SYSKEYUP:
KeyUp(p->vkCode);
break;
}
}
// Not processing keys so always return CallNextHookEx
return(CallNextHookEx(NULL, nCode, wParam, lParam));
}
void KeyboardHook::Install() {
// Install keyboard hook
keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, 0, 0);
std::cout << "Installed\n";
}
void KeyboardHook::Uninstall() {
// Unhook keyboard hook
UnhookWindowsHookEx(keyboardHook);
}
KeyboardHook.h
#include <Windows.h>
HHOOK keyboardHook;
namespace Hooks
{
class KeyboardHook
{
public:
__declspec(dllexport) void Install();
__declspec(dllexport) void Uninstall();
};
}
Program.cs
using System;
using System.Runtime.InteropServices;
namespace HelloMyNameIsSpindiNiceToMeetYou
{
class Program
{
private const string hooksPath = @"C:\Path\To\Hooks.dll";
// If EntryPoint doesn't work, yours might be different
// https://learn.microsoft.com/en-us/dotnet/framework/interop/identifying-functions-in-dlls
//
// "For example, you can use dumpbin /exports Hooks.dll [...] to obtain function names."
// you need to be in the folder with the dll for above to work in Command Prompt for VS
[DllImport(hooksPath, EntryPoint = "?Install@KeyboardHook@Hooks@@QEAAXXZ", CallingConvention = CallingConvention.Cdecl)]
private extern static void Install();
static void Main(string[] args)
{
Install();
// keep console app running
while (true)
{
continue;
}
// or keep it running with this
// Console.ReadKey();
}
}
}
I am testing this stuff outside of an electron.net app now, just in a console app and things still don't work. Everything I have found just leads back to using winforms, which I cannot use.