What’s Interception?

A programming interface for intercepting input device communication.

What can I do with that?

Currently, with Interception you are able to intercept and transform input data from keyboard and mouse. Support is still Windows only (from Windows XP to Windows 10).

I can use Windows hooks for that, so, what’s up?

Yes, you can, but…

  • You’re not able to identify from which device some event is coming when you connect multiple keyboard/mouse. With generic user-mode hooks you are able to identify that a keyboard/mouse event has arrived, but not from which keyboard/mouse it’s coming.
  • With Windows Raw Input you can identify but cannot intercept (and then mutate) input.
  • You can’t intercept CTRL-ALT-DELETE with user-mode hooks, for example, and do other stuff like peering while at the login screen.
  • You can’t hook old DirectInput games.

Ok, but, how do you achieve this?

The Interception API provides a simple interface of communication with kernel-mode components, and those components are powerful. They intercept data in a early stage, before it reaches the core of OS input processing.

I have a x64 Windows box, I’ve heard that it cannot load kernel-mode components indiscriminately.

Yes, indeed. The Interception core is built upon properly signed drivers, so that’s not a problem!

Ok then, let’s get to work.

First, you must download and extract the Interception assets and then run install-interception. This tool must be run from an administrator command line (you must run cmd as administrator). Just run it without arguments to receive instructions.

Second, you must link against the Interception library, which is provided in the assets, or you may build it from sources.

Third, you must include interception.h in your applications.

The following sample shows how to intercept the x key and turn it into y:

#include <interception.h>
#include "utils.h"

enum ScanCode
{
    SCANCODE_X   = 0x2D,
    SCANCODE_Y   = 0x15,
    SCANCODE_ESC = 0x01
};

int main()
{
    InterceptionContext context;
    InterceptionDevice device;
    InterceptionKeyStroke stroke;

    raise_process_priority();

    context = interception_create_context();

    interception_set_filter(context, interception_is_keyboard, INTERCEPTION_FILTER_KEY_DOWN | INTERCEPTION_FILTER_KEY_UP);

    while(interception_receive(context, device = interception_wait(context), (InterceptionStroke *)&stroke, 1) > 0)
    {
        if(stroke.code == SCANCODE_X) stroke.code = SCANCODE_Y;

        interception_send(context, device, (InterceptionStroke *)&stroke, 1);

        if(stroke.code == SCANCODE_ESC) break;
    }

    interception_destroy_context(context);

    return 0;
}

There are some concepts you need to know to use Interception well:

  • Contexts
    You need to create a context of communication with the core components, most functions will require it.
  • Filters
    Filters are a way to indicate what kind of events you want to intercept, for example, in the last sample we were interested in simple key up’s and key down’s, some special keys would not trigger events through this filter, like the DELETE key, because it needs the E0 flag too. If you do not set a filter for at last one device, you won’t be notified of any events.
  • Device selection predicate
    The interception_set_filter function has three parameters, the context of communication, a function pointer and a desired filter. This second parameter, the function pointer, is a device selection predicate, a function that receives a device id (like INTERCEPTION_KEYBOARD(0), INTERCEPTION_KEYBOARD(1), etc) as parameter and returns true if the device id passed is one of a device that must be filtered through the chosen filter, or false for the devices that are not to be filtered through this filter. So the interception_set_filter works by scanning all possible devices, and using the provided predicate as the criteria to know for which devices the provided filter should be applied.
  • Precedence
    There’s an old explanation here.

The following sample shows how to invert the vertical mouse axis:

#include <interception.h>
#include "utils.h"

enum ScanCode
{
    SCANCODE_ESC = 0x01
};

int main()
{
    InterceptionContext context;
    InterceptionDevice device;
    InterceptionStroke stroke;

    raise_process_priority();

    context = interception_create_context();

    interception_set_filter(context, interception_is_keyboard, INTERCEPTION_FILTER_KEY_DOWN | INTERCEPTION_FILTER_KEY_UP);
    interception_set_filter(context, interception_is_mouse, INTERCEPTION_FILTER_MOUSE_MOVE);

    while(interception_receive(context, device = interception_wait(context), &stroke, 1) > 0)
    {
        if(interception_is_mouse(device))
        {
            InterceptionMouseStroke &mstroke = *(InterceptionMouseStroke *) &stroke;

            if(!(mstroke.flags & INTERCEPTION_MOUSE_MOVE_ABSOLUTE)) mstroke.y *= -1;

            interception_send(context, device, &stroke, 1);
        }

        if(interception_is_keyboard(device))
        {
            InterceptionKeyStroke &kstroke = *(InterceptionKeyStroke *) &stroke;

            interception_send(context, device, &stroke, 1);

            if(kstroke.code == SCANCODE_ESC) break;
        }
    }

    interception_destroy_context(context);

    return 0;
}

The following sample shows how to intercept and block the CTRL-ALT-DEL sequence:

#include <interception.h>
#include "utils.h"

#include <iostream>
#include <deque>

enum ScanCode
{
    SCANCODE_ESC = 0x01
};

InterceptionKeyStroke nothing = {};
InterceptionKeyStroke ctrl_down = {0x1D, INTERCEPTION_KEY_DOWN};
InterceptionKeyStroke alt_down  = {0x38, INTERCEPTION_KEY_DOWN};
InterceptionKeyStroke del_down  = {0x53, INTERCEPTION_KEY_DOWN | INTERCEPTION_KEY_E0};

bool operator == (const InterceptionKeyStroke &first, const InterceptionKeyStroke &second)
{
    return first.code == second.code && first.state == second.state;
}

bool operator != (const InterceptionKeyStroke &first, const InterceptionKeyStroke &second)
{
    return !(first == second);
}

int main()
{
    using namespace std;

    InterceptionContext context;
    InterceptionDevice device;
    InterceptionKeyStroke new_stroke, last_stroke;

    deque<InterceptionKeyStroke> stroke_sequence;

    stroke_sequence.push_back(nothing);
    stroke_sequence.push_back(nothing);
    stroke_sequence.push_back(nothing);

    raise_process_priority();

    context = interception_create_context();

    interception_set_filter(context, interception_is_keyboard, INTERCEPTION_FILTER_KEY_ALL);

    while(interception_receive(context, device = interception_wait(context), (InterceptionStroke *)&new_stroke, 1) > 0)
    {
        if(new_stroke != last_stroke)
        {
            stroke_sequence.pop_front();
            stroke_sequence.push_back(new_stroke);
        }

        if(stroke_sequence[0] == ctrl_down && stroke_sequence[1] == alt_down && stroke_sequence[2] == del_down)
            cout << "ctrl-alt-del pressed" << endl;
        else if(stroke_sequence[0] == alt_down && stroke_sequence[1] == ctrl_down && stroke_sequence[2] == del_down)
            cout << "alt-ctrl-del pressed" << endl;
        else
            interception_send(context, device, (InterceptionStroke *)&new_stroke, 1);

        if(new_stroke.code == SCANCODE_ESC) break;

        last_stroke = new_stroke;
    }

    interception_destroy_context(context);

    return 0;
}

The following sample identify multiple mice through left clicking (worked well for my magic mouse and touch pad):

#include <interception.h>
#include "utils.h"

#include <iostream>

enum ScanCode
{
    SCANCODE_ESC = 0x01
};

int main()
{
    using namespace std;

    InterceptionContext context;
    InterceptionDevice device;
    InterceptionStroke stroke;

    raise_process_priority();

    context = interception_create_context();

    interception_set_filter(context, interception_is_keyboard, INTERCEPTION_FILTER_KEY_DOWN | INTERCEPTION_FILTER_KEY_UP);
    interception_set_filter(context, interception_is_mouse, INTERCEPTION_FILTER_MOUSE_LEFT_BUTTON_DOWN);

    while(interception_receive(context, device = interception_wait(context), &stroke, 1) > 0)
    {
        if(interception_is_keyboard(device))
        {
            InterceptionKeyStroke &keystroke = *(InterceptionKeyStroke *) &stroke;

            if(keystroke.code == SCANCODE_ESC) break;
        }

        if(interception_is_mouse(device))
        {
            InterceptionMouseStroke &mousestroke = *(InterceptionMouseStroke *) &stroke;

            cout << "INTERCEPTION_MOUSE(" << device - INTERCEPTION_MOUSE(0) << ")" << endl;
        }

        interception_send(context, device, &stroke, 1);
    }

    interception_destroy_context(context);

    return 0;
}

As you can see interception_is_keyboard and interception_is_mouse are convenience device selection predicates, but you can implement your own.

The following sample allows one to query for a device’s “hardware id”, which may help on disambiguation of device input.
Just remember this hardware id’s are not required to be unique, but mostly will when you have at last two different device models.

#include <interception.h>
#include "utils.h"

#include <iostream>

enum ScanCode
{
    SCANCODE_ESC = 0x01
};

int main()
{
    using namespace std;

    InterceptionContext context;
    InterceptionDevice device;
    InterceptionStroke stroke;

    wchar_t hardware_id[500];

    raise_process_priority();

    context = interception_create_context();

    interception_set_filter(context, interception_is_keyboard, INTERCEPTION_FILTER_KEY_DOWN | INTERCEPTION_FILTER_KEY_UP);
    interception_set_filter(context, interception_is_mouse, INTERCEPTION_FILTER_MOUSE_LEFT_BUTTON_DOWN);

    while(interception_receive(context, device = interception_wait(context), &stroke, 1) > 0)
    {
        if(interception_is_keyboard(device))
        {
            InterceptionKeyStroke &keystroke = *(InterceptionKeyStroke *) &stroke;

            if(keystroke.code == SCANCODE_ESC) break;
        }

        size_t length = interception_get_hardware_id(context, device, hardware_id, sizeof(hardware_id));

        if(length > 0 && length < sizeof(hardware_id))
            wcout << hardware_id << endl;

        interception_send(context, device, &stroke, 1);
    }

    interception_destroy_context(context);

    return 0;
}