Hook into Linux Key Event Handling

Hook Linux Keyboard Events

The answer was just to read the raw event and parse it.

How to Create Global Keyboard Hook with C# in Linux

Answering my own question here because I had to do a lot of research for it and I'm sure it will help someone else later. I went with option 1 because it seemed to be the easiest to implement.

Warning - There is going to be a lot of code in this post

Summary

For my intents and purposes, I wanted to have some code that would publish an event any time the user pressed a key anywhere on the system. While developing this, I found that I could also hook into mouse events as well.

It's important to note, that the code here like the linux OS doesn't really distinguish between a keyboard button press and a mouse button press. To linux, they're both just buttons.

Understanding that you can actually expand this code to work with other items like gamepads and special input peripherals if you desire.

Additional Gotchas - As expressed in the question, this code will not block the device input to other programs. This can be problematic if you want to override the default functionality of say the power button or the volume buttons.

Setting Up Permissions

In order to run this code, the user that runs this program will have to be in the input user group otherwise it will throw an exception. Run this code to add the current user to that group.

 sudo gpasswd -a $USER input

EventType.cs

Since the folder /dev/input is essentially an event bus of a bunch of input/output devices for the linux OS, there are a variety of event types that you may want to consume. Here is the enum that I was able to put together to make deciphering the event types a little easier.

public enum EventType
{
/// <summary>
/// Used as markers to separate events. Events may be separated in time or in space, such as with the multitouch protocol.
/// </summary>
EV_SYN,

/// <summary>
/// Used to describe state changes of keyboards, buttons, or other key-like devices.
/// </summary>
EV_KEY,

/// <summary>
/// Used to describe relative axis value changes, e.g. moving the mouse 5 units to the left.
/// </summary>
EV_REL,

/// <summary>
/// Used to describe absolute axis value changes, e.g. describing the coordinates of a touch on a touchscreen.
/// </summary>
EV_ABS,

/// <summary>
/// Used to describe miscellaneous input data that do not fit into other types.
/// </summary>
EV_MSC,

/// <summary>
/// Used to describe binary state input switches.
/// </summary>
EV_SW,

/// <summary>
/// Used to turn LEDs on devices on and off.
/// </summary>
EV_LED,

/// <summary>
/// Used to output sound to devices.
/// </summary>
EV_SND,

/// <summary>
/// Used for autorepeating devices.
/// </summary>
EV_REP,

/// <summary>
/// Used to send force feedback commands to an input device.
/// </summary>
EV_FF,

/// <summary>
/// A special type for power button and switch input.
/// </summary>
EV_PWR,

/// <summary>
/// Used to receive force feedback device status.
/// </summary>
EV_FF_STATUS,
}

KeyState.cs

Like many other event handling systems there are multiple events that happen each time that the user presses a key. Once when the key is pressed down, another when the key is pressed up, and another if the user decides to hold the key down.

public enum KeyState
{
KeyUp,
KeyDown,
KeyHold
}

EventCode.cs

Each distinct button is associated with an event code. Whether it's a button on a keyboard or a button on a mouse, you'll probably be able to find it here. He is a helper enum class to make deciphering those codes easier.

/// <summary>
/// Mapping for this can be found here: https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h
/// </summary>
public enum EventCode
{
Reserved = 0,
Esc = 1,
Num1 = 2,
Num2 = 3,
Num3 = 4,
Num4 = 5,
Num5 = 6,
Num6 = 7,
Num7 = 8,
Num8 = 9,
Num9 = 10,
Num0 = 11,
Minus = 12,
Equal = 13,
Backspace = 14,
Tab = 15,
Q = 16,
W = 17,
E = 18,
R = 19,
T = 20,
Y = 21,
U = 22,
I = 23,
O = 24,
P = 25,
LeftBrace = 26,
RightBrace = 27,
Enter = 28,
LeftCtrl = 29,
A = 30,
S = 31,
D = 32,
F = 33,
G = 34,
H = 35,
J = 36,
K = 37,
L = 38,
Semicolon = 39,
Apostrophe = 40,
Grave = 41,
LeftShift = 42,
Backslash = 43,
Z = 44,
X = 45,
C = 46,
V = 47,
B = 48,
N = 49,
M = 50,
Comma = 51,
Dot = 52,
Slash = 53,
RightShift = 54,
KpAsterisk = 55,
LeftAlt = 56,
Space = 57,
Capslock = 58,
F1 = 59,
Pf2 = 60,
F3 = 61,
F4 = 62,
F5 = 63,
F6 = 64,
F7 = 65,
F8 = 66,
Pf9 = 67,
F10 = 68,
Numlock = 69,
ScrollLock = 70,
Kp7 = 71,
Kp8 = 72,
Kp9 = 73,
PkpMinus = 74,
Kp4 = 75,
Kp5 = 76,
Kp6 = 77,
KpPlus = 78,
Kp1 = 79,
Kp2 = 80,
Kp3 = 81,
Kp0 = 82,
KpDot = 83,

Zenkakuhankaku = 85,
//102ND = 86,
F11 = 87,
F12 = 88,
Ro = 89,
Katakana = 90,
Hiragana = 91,
Henkan = 92,
Katakanahiragana = 93,
Muhenkan = 94,
KpJpComma = 95,
KpEnter = 96,
RightCtrl = 97,
KpSlash = 98,
SysRq = 99,
RightAlt = 100,
LineFeed = 101,
Home = 102,
Up = 103,
Pageup = 104,
Left = 105,
Right = 106,
End = 107,
Down = 108,
Pagedown = 109,
Insert = 110,
Delete = 111,
Macro = 112,
Mute = 113,
VolumeDown = 114,
VolumeUp = 115,
Power = 116, // SC System Power Down
KpEqual = 117,
KpPlusMinus = 118,
Pause = 119,
Scale = 120, // AL Compiz Scale (Expose)

KpComma = 121,
Hangeul = 122,
Hanja = 123,
Yen = 124,
LeftMeta = 125,
RightMeta = 126,
Compose = 127,

Stop = 128, // AC Stop
Again = 129,
Props = 130, // AC Properties
Undo = 131, // AC Undo
Front = 132,
Copy = 133, // AC Copy
Open = 134, // AC Open
Paste = 135, // AC Paste
Find = 136, // AC Search
Cut = 137, // AC Cut
Help = 138, // AL Integrated Help Center
Menu = 139, // Menu (show menu)
Calc = 140, // AL Calculator
Setup = 141,
Sleep = 142, // SC System Sleep
Wakeup = 143, // System Wake Up
File = 144, // AL Local Machine Browser
Sendfile = 145,
DeleteFile = 146,
Xfer = 147,
Prog1 = 148,
Prog2 = 149,
Www = 150, // AL Internet Browser
MsDos = 151,
Coffee = 152, // AL Terminal Lock/Screensaver
RotateDisplay = 153, // Display orientation for e.g. tablets
CycleWindows = 154,
Mail = 155,
Bookmarks = 156, // AC Bookmarks
Computer = 157,
Back = 158, // AC Back
Forward = 159, // AC Forward
CloseCd = 160,
EjectCd = 161,
EjectCloseCd = 162,
NextSong = 163,
PlayPause = 164,
PreviousSong = 165,
StopCd = 166,
Record = 167,
Rewind = 168,
Phone = 169, // Media Select Telephone
Iso = 170,
Config = 171, // AL Consumer Control Configuration
Homepage = 172, // AC Home
Refresh = 173, // AC Refresh
Exit = 174, // AC Exit
Move = 175,
Edit = 176,
ScrollUp = 177,
ScrollDown = 178,
KpLeftParen = 179,
KpRightParen = 180,
New = 181, // AC New
Redo = 182, // AC Redo/Repeat

F13 = 183,
F14 = 184,
F15 = 185,
F16 = 186,
F17 = 187,
F18 = 188,
F19 = 189,
F20 = 190,
F21 = 191,
F22 = 192,
F23 = 193,
F24 = 194,

PlayCd = 200,
PauseCd = 201,
Prog3 = 202,
Prog4 = 203,
Dashboard = 204, // AL Dashboard
Suspend = 205,
Close = 206, // AC Close
Play = 207,
FastForward = 208,
BassBoost = 209,
Print = 210, // AC Print
Hp = 211,
Camera = 212,
Sound = 213,
Question = 214,
Email = 215,
Chat = 216,
Search = 217,
Connect = 218,
Finance = 219, // AL Checkbook/Finance
Sport = 220,
Shop = 221,
AltErase = 222,
Cancel = 223, // AC Cancel
BrightnessDown = 224,
BrightnessUp = 225,
Media = 226,

SwitchVideoMode = 227, // Cycle between available video outputs (Monitor/LCD/TV-out/etc)
KbdIllumToggle = 228,
KbdIllumDown = 229,
KbdIllumUp = 230,

Send = 231, // AC Send
Reply = 232, // AC Reply
ForwardMail = 233, // AC Forward Msg
Save = 234, // AC Save
Documents = 235,

Battery = 236,

Bluetooth = 237,
Wlan = 238,
Uwb = 239,

Unknown = 240,

VideoNext = 241, // drive next video source
VideoPrev = 242, // drive previous video source
BrightnessCycle = 243, // brightness up, after max is min
BrightnessAuto = 244, // Set Auto Brightness: manual brightness control is off, rely on ambient
DisplayOff = 245, // display device to off state

Wwan = 246, // Wireless WAN (LTE, UMTS, GSM, etc.)
RfKill = 247, // Key that controls all radios

MicMute = 248, // Mute / unmute the microphone
LeftMouse = 272,
RightMouse = 273,
MiddleMouse = 274,
MouseBack = 275,
MouseForward = 276,

ToolFinger = 325,
ToolQuintTap = 328,
Touch = 330,
ToolDoubleTap = 333,
ToolTripleTap = 334,
ToolQuadTap = 335,
Mic = 582
}

MouseAxis.cs

Mouse movements are expressed in an amount moved and an axis associated with that change. 0 represents movements on the X axis and 1 represents movements on the Y axis.

public enum MouseAxis
{
X,
Y
}

KeypressEvent.cs

Here is the event that I use to process key press events.

public class KeyPressEvent : EventArgs
{
public KeyPressEvent(EventCode code, KeyState state)
{
Code = code;
State = state;
}

public EventCode Code { get; }

public KeyState State { get; }
}

MouseMoveEvent.cs

Here is the event that I use process mouse movement change updates.

public class MouseMoveEvent : EventArgs
{
public MouseMoveEvent(MouseAxis axis, int amount)
{
Axis = axis;
Amount = amount;
}

public MouseAxis Axis { get; }

public int Amount { get; set; }
}

InputReader.cs

This is where the bulk of the work happens. Here we have a class, where you provide the path to one of the event files and it publishes updates whenever it comes in. An example file that does this would be "/dev/input/event0".

More research would be needed to support more events types, but I was only interested in keyboard and mouse input so it serves my purposes. I also opted to drop the timestamp that is included with each button event, but if you're interested, you can find it on the first 16 bits on the buffer.

public class InputReader : IDisposable
{
public delegate void RaiseKeyPress(KeyPressEvent e);

public delegate void RaiseMouseMove(MouseMoveEvent e);

public event RaiseKeyPress OnKeyPress;
public event RaiseMouseMove OnMouseMove;

private const int BufferLength = 24;

private readonly byte[] _buffer = new byte[BufferLength];

private FileStream _stream;
private bool _disposing;

public InputReader(string path)
{
_stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

Task.Run(Run);
}

private void Run()
{
while (true)
{
if (_disposing)
break;

_stream.Read(_buffer, 0, BufferLength);

var type = BitConverter.ToInt16(new[] {_buffer[16], _buffer[17]}, 0);
var code = BitConverter.ToInt16(new[] {_buffer[18], _buffer[19]}, 0);
var value = BitConverter.ToInt32(new[] {_buffer[20], _buffer[21], _buffer[22], _buffer[23]}, 0);

var eventType = (EventType) type;

switch (eventType)
{
case EventType.EV_KEY:
HandleKeyPressEvent(code, value);
break;
case EventType.EV_REL:
var axis = (MouseAxis) code;
var e = new MouseMoveEvent(axis, value);
OnMouseMove?.Invoke(e);
break;
}
}
}

private void HandleKeyPressEvent(short code, int value)
{
var c = (EventCode) code;
var s = (KeyState) value;
var e = new KeyPressEvent(c, s);
OnKeyPress?.Invoke(e);
}

public void Dispose()
{
_disposing = true;
_stream.Dispose();
_stream = null;
}
}

AggregateInputReader.cs

Since I'm looking to handle input from every device anywhere on the system, I've put together this classes to aggregate the input events from all the files in the "/dev/input" folder.

Known issue - This code will throw an exception if a usb device is removed while it's running. I do intend to fix it in my own app implementation, but I don't have time to take care of it now.

public class AggregateInputReader : IDisposable
{
private List<InputReader> _readers = new();

public event InputReader.RaiseKeyPress OnKeyPress;

public AggregateInputReader()
{
var files = Directory.GetFiles("/dev/input/", "event*");

foreach (var file in files)
{
var reader = new InputReader(file);

reader.OnKeyPress += ReaderOnOnKeyPress;

_readers.Add(reader);
}
}

private void ReaderOnOnKeyPress(KeyPressEvent e)
{
OnKeyPress?.Invoke(e);
}

public void Dispose()
{
foreach (var d in _readers)
{
d.OnKeyPress -= ReaderOnOnKeyPress;
d.Dispose();
}

_readers = null;
}
}

Example Usage

Not bad that this can now be accomplished in two line of code.

public class Program
{
public static void Main(string[] args)
{
using var aggHandler = new AggregateInputReader();

aggHandler.OnKeyPress += (e) => { System.Console.WriteLine($"Code:{e.Code} State:{e.State}"); };

System.Console.ReadLine();
}
}

Thanks for sticking with this. I hope it works out for you!

Listening to keyboard events without consuming them in X11 - Keyboard hooking

Here's my quick and dirty example

#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <ctype.h>


int main ()
{
Display* d = XOpenDisplay(NULL);
Window root = DefaultRootWindow(d);
Window curFocus;
char buf[17];
KeySym ks;
XComposeStatus comp;
int len;
int revert;

XGetInputFocus (d, &curFocus, &revert);
XSelectInput(d, curFocus, KeyPressMask|KeyReleaseMask|FocusChangeMask);

while (1)
{
XEvent ev;
XNextEvent(d, &ev);
switch (ev.type)
{
case FocusOut:
printf ("Focus changed!\n");
printf ("Old focus is %d\n", (int)curFocus);
if (curFocus != root)
XSelectInput(d, curFocus, 0);
XGetInputFocus (d, &curFocus, &revert);
printf ("New focus is %d\n", (int)curFocus);
if (curFocus == PointerRoot)
curFocus = root;
XSelectInput(d, curFocus, KeyPressMask|KeyReleaseMask|FocusChangeMask);
break;

case KeyPress:
printf ("Got key!\n");
len = XLookupString(&ev.xkey, buf, 16, &ks, &comp);
if (len > 0 && isprint(buf[0]))
{
buf[len]=0;
printf("String is: %s\n", buf);
}
else
{
printf ("Key is: %d\n", (int)ks);
}
}

}
}

It's not reliable but most of the time it works. (It is showing keys I'm typing into this box right now). You may investigate why it does fail sometimes ;) Also it cannot show hotkeys in principle. Hotkeys are grabbed keys, and only one client can get a grabbed key. Absolutely nothing can be done here, short of loading a special X11 extension designed for this purpose (e.g. XEvIE).

Linux keyboard hook in c#

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
}

Linux Kernel: How to capture a key press and replace it with another key?

Consider next simple kernel module:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <asm/io.h>

#define KBD_IRQ 1 /* IRQ number for keyboard (i8042) */
#define KBD_DATA_REG 0x60 /* I/O port for keyboard data */
#define KBD_SCANCODE_MASK 0x7f
#define KBD_STATUS_MASK 0x80

static irqreturn_t kbd2_isr(int irq, void *dev_id)
{
char scancode;

scancode = inb(KBD_DATA_REG);
/* NOTE: i/o ops take a lot of time thus must be avoided in HW ISRs */
pr_info("Scan Code %x %s\n",
scancode & KBD_SCANCODE_MASK,
scancode & KBD_STATUS_MASK ? "Released" : "Pressed");

return IRQ_HANDLED;
}

static int __init kbd2_init(void)
{
return request_irq(KBD_IRQ, kbd2_isr, IRQF_SHARED, "kbd2", (void *)kbd2_isr);
}

static void __exit kbd2_exit(void)
{
free_irq(KBD_IRQ, (void *)kbd2_isr);
}

module_init(kbd2_init);
module_exit(kbd2_exit);

MODULE_LICENSE("GPL");

This is most minimal and primitive key-logger. It can be easily reworked for replacing of scan code.

Disclaimers

  • This module is not cross-platform (will work only on x86 architecture, because it's using inb() function)
  • I believe it only works with PS/2 keyboard (won't work with USB keyboard)
  • It's performing slow I/O operation (I mean pr_info()) in hardware IRQ handler, which should be avoided (ideally threaded IRQs should be used)).

But I think it's good for educational purposes -- it's really small and demonstrates the idea pretty well (without messing with API like input_dev, input_register_device(), serio_write(), input_event(), input_report_key(), etc).

Details

Real interrupt handler (in keyboard driver) requested as shared interrupt, which allows us also request that interrupt and thus handle it also in our ISR (in addition to ISR in original keyboard driver). Interrupt requesting is done in kbd2_init().

This module works as follows:

  1. Catches key press event (hardware interrupt handler kbd2_isr() is called for each key press event)
  2. Reads scan code of pressed key (via inb() function)
  3. And prints it via pr_info()

Now, you want to replace that scan code. I believe you can use outb() function for this (on x86). So I leave it for you.

If you wonder why we are requesting IRQ with number 1, see at drivers/input/serio/i8042-io.h:

#else
# define I8042_KBD_IRQ 1

Also be sure to check that this IRQ is shared in drivers/input/serio/i8042.c:

error = request_irq(I8042_KBD_IRQ, i8042_interrupt, IRQF_SHARED,
"i8042", i8042_platform_device);

Here is documentation for i8042 keyboard controller: AT keyboard controller.

Useful constants

To avoid magic numbers, you can use next definitions.

From drivers/input/serio/i8042-io.h:

/*
* Register numbers.
*/

#define I8042_COMMAND_REG 0x64
#define I8042_STATUS_REG 0x64
#define I8042_DATA_REG 0x60

From include/linux/i8042.h:

/*
* Status register bits.
*/

#define I8042_STR_PARITY 0x80
#define I8042_STR_TIMEOUT 0x40
#define I8042_STR_AUXDATA 0x20
#define I8042_STR_KEYLOCK 0x10
#define I8042_STR_CMDDAT 0x08
#define I8042_STR_MUXERR 0x04
#define I8042_STR_IBF 0x02
#define I8042_STR_OBF 0x01


Related Topics



Leave a reply



Submit