Luis A.
Luis A.

Reputation: 129

Linux keyboard hook in c#

My problem is that I'm trying to run a self-contained c# console application specifically published for linux, intended to run on a Raspberry.

The use scenario is in public transport where passengers will use a RFID keycard, I'll read the ID via a sensor and this sensor is recognized as a keyboard.

Since this application must run all time it will be running as a service, that's why I need a keyboard hook so no matter what happens, the service will read the sensor.

I was wondering if there is something like this example that would work for linux (warning: its an http website): Low Level Global Keyboard Hook

Here is the code so you don't need to go to the website:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Input;
 
namespace DesktopWPFAppLowLevelKeyboardHook
{
    public class LowLevelKeyboardListener
    {
        private const int WH_KEYBOARD_LL = 13;
        private const int WM_KEYDOWN = 0x0100;
        private const int WM_SYSKEYDOWN = 0x0104;
 
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
 
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);
 
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
 
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);
 
        public delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
 
        public event EventHandler<KeyPressedArgs> OnKeyPressed;
 
        private LowLevelKeyboardProc _proc;
        private IntPtr _hookID = IntPtr.Zero;
 
        public LowLevelKeyboardListener()
        {
            _proc = HookCallback;
        }
 
        public void HookKeyboard()
        {
            _hookID = SetHook(_proc);
        }
 
        public void UnHookKeyboard()
        {
            UnhookWindowsHookEx(_hookID);
        }
 
        private IntPtr SetHook(LowLevelKeyboardProc proc)
        {
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            {
                return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
            }
        }
 
        private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN)
            {
                int vkCode = Marshal.ReadInt32(lParam);
 
                if (OnKeyPressed != null) { OnKeyPressed(this, new KeyPressedArgs(KeyInterop.KeyFromVirtualKey(vkCode))); }
            }
 
            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        }
    }
 
    public class KeyPressedArgs : EventArgs
    {
        public Key KeyPressed { get; private set; }
 
        public KeyPressedArgs(Key key)
        {
            KeyPressed = key;
        }
    }
}

Upvotes: 5

Views: 1505

Answers (1)

Luis A.
Luis A.

Reputation: 129

I found a way to do this without using any dll files, instead I read /dev/input/eventX which is a file that's generated when a keyboard or any other peripheral device is connected, it is used by the system to know the events generated by the device.

Here is the code in c#

public static string EvdevReader()
    {
        string readMessage = "";
        try
        {
            FileStream stream = new FileStream("/dev/input/event0", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            byte[] buffer = new byte[24];

            while (true)
            {
                stream.Read(buffer, 0, buffer.Length);

                // parse timeval (8 bytes)
                int offset = 8;
                short type = BitConverter.ToInt16(new byte[] { buffer[offset], buffer[++offset] }, 0);
                short code = BitConverter.ToInt16(new byte[] { buffer[++offset], buffer[++offset] }, 0);
                int value = BitConverter.ToInt32(
                    new byte[] { buffer[++offset], buffer[++offset], buffer[++offset], buffer[++offset] }, 0);

                if (value == 1 && code != 28)
                {
                    Console.WriteLine("Code={1}, Value={2}", type, code, value);

                    var key = (((KEY_CODE)code).ToString()).Replace("KEY_", "");
                    key = key.Replace("MINUS", "-");
                    key = key.Replace("EQUAL", "=");
                    key = key.Replace("SEMICOLON", ";");
                    key = key.Replace("COMMA", ",");
                    key = key.Replace("SLASH", "/");

                    Console.WriteLine(key);

                    readMessage += key;
                }

                if (code == 28)
                {
                    return readMessage;
                }

            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.ToString());
            Main();
        }
        return readMessage;
    }

The code opens a FileStream of the event0 which is normally where you want to listen for the input, the events that are generated have a standard structure (you can find more info here: https://thehackerdiary.wordpress.com/2017/04/21/exploring-devinput-1/), according with the documentation I found it's supposed that the timeval is 16 bytes but In this case it works with 8.

Events have type which is the type of event, code of the pressed key and value, this one is the state of the key pressed = 1, unpressed = 0 (Find more info here: https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h#L38-L51). For each of this codes we need to find it's readable form, for this I created an enumerator with the keys that I need to read (28 is the enter key). This codes can be found in the link above.

 public enum KEY_CODE
{
    KEY_1 = 2,
    KEY_2,
    KEY_3,
    KEY_4,
    KEY_5,
    KEY_6,
    KEY_7,
    KEY_8,
    KEY_9,
    KEY_0,
    KEY_MINUS,
    KEY_EQUAL,
    KEY_BACKSPACE,
    KEY_TAB,
    KEY_Q,
    KEY_W,
    KEY_E,
    KEY_R,
    KEY_T,
    KEY_Y,
    KEY_U,
    KEY_I,
    KEY_O,
    KEY_P,
    KEY_LEFTBRACE,
    KEY_RIGHTBRACE,
    KEY_ENTER,
    KEY_LEFTCTRL,
    KEY_A,
    KEY_S,
    KEY_D,
    KEY_F,
    KEY_G,
    KEY_H,
    KEY_J,
    KEY_K,
    KEY_L,
    KEY_SEMICOLON,
    KEY_APOSTROPHE,
    KEY_GRAVE,
    KEY_LEFTSHIFT,
    KEY_BACKSLASH,
    KEY_Z,
    KEY_X,
    KEY_C,
    KEY_V,
    KEY_B,
    KEY_N,
    KEY_M,
    KEY_COMMA,
    KEY_DOT,
    KEY_SLASH,
    KEY_RIGHTSHIFT,
    KEY_KPASTERISK,
    KEY_LEFTALT,
    KEY_SPACE,
    KEY_CAPSLOCK,
    KEY_F1,
    KEY_F2,
    KEY_F3,
    KEY_F4,
    KEY_F5,
    KEY_F6,
    KEY_F7,
    KEY_F8,
    KEY_F9,
    KEY_F10,
    KEY_NUMLOCK,
    KEY_SCROLLLOCK,
    KEY_KP7,
    KEY_KP8,
    KEY_KP9,
    KEY_KPMINUS,
    KEY_KP4,
    KEY_KP5,
    KEY_KP6,
    KEY_KPPLUS,
    KEY_KP1,
    KEY_KP2,
    KEY_KP3,
    KEY_KP0,
    KEY_KPDOT
}

Upvotes: 3

Related Questions