About
Keyboards are still one of the most important input devices to a computer. As a software developer, I am always trying to learn new keyboard shortcuts that make my work routines more efficient. The problem I constantly face is having to switch or re-learn keyboard shortcuts when switching from one application to another.
I recently became acquainted with touchcursor-mac (github.com). It is an open source keyboard mapper that allows users to remap keys when a key is held. In other words, it is possible to create a set of universal shortcuts to be used across applications (to some extent). What makes touchcursor stand out from other keyboard mappers is its lightweight design, zero external dependency (except low level system libraries), and availability in multiple platforms. There are versions for Windows, Linux, and MacOS... MacOS being the version I'm using. Touchcursor (github.com) was originally developed by Martin Stone, and the core functionality simplified and ported to other platforms by @donniebreve. touchcursor-mac was inspired by Touchcursor, and other keyboard movement schemes such as SpaceFN (geekhack.org) and VIM (vim.org).
This article will not cover how to install and configure touchcursor-mac. That would not qualify as deep know (deepknow.io). If you are interested in using or developing touchcursor-mac, you are welcome to visit its Github repo and participate. This article will focus on the thing that I took away from deep looking into its source code — how low level input device messaging works.
How Touchcursor Works
Since I am using touchcursor-mac, this article will focus on the MacOS implementation. However, the overall architecture (as depicted in figure 1) applies mostly to the other versions as well. As shown in figure 1, the key idea is to intercept low level keyboard inputs, and then send manipulated events to the foreground applications. The libraries to do that are different from system to system. For MacOS, the libraries are IOKit and Core Graphics. When porting over to another system, it is possible to replace the input/output libraries with system specific ones while keeping the application logic mostly the same.
Intercepting Keyboard Input
Input device drivers in MacOS typically use the IOKit library to originate low-level input device events. These events get picked up by the system, and will eventually reach the foreground application. The idea behind intercepting keyboard input, therefore, is to replace the device event's origination function with a custom function.
The process to replace the function is divided into two parts. The first part is to find the keyboard device driver. This can be accomplished using IOHIDManagerCreate and IOHIDManagerSetDeviceMatching function as shown in snippet 1. Once you have all device drivers in hidManager, loop through the device drivers until the desired one is found.
The second part is to attach a custom callback function to the keyboard device driver via IOHIDDeviceRegisterInputValueCallback function, as shown in snippet 2. From now on, when the a keystroke is triggered, instead of originating a low-level input device event, the callback function will be triggered instead.
Sending Events
Normally, when a low-level input device event is created, it gets picked up by the event server which will generate a corresponding quartz event to be sent to the foreground application. In the previous step, the original low-level input device event gets captured, and so a custom quartz event needs to be generated in its place. The code to generate a quartz event is straight forward (snippet 3). First, create an event source. Second, convert the key code to a quartz key code. Finally, a quartz event can be generated via CGEventPost function.
The Run Loop
Finally, the code must schedule the HID manager with the current system run loop, and start the loop to start receiving input events from the keyboard (snippet 4).
Conclusion
That's all there is about low-level input device messaging. Touchcursor-mac, on the other hand, is obviously a lot more than just low-level input device messaging. It has clever codes (a state machine) that handles keystrokes in different combinations and settings. If you want to know more, the only way is to take a deep look into touchcursor-mac yourself.