Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
178 lines (160 sloc) 5.48 KB
#! /usr/bin/env stap
# Copyright (c) 2017 Cloudflare, Inc.
#
# Licensed under the MIT License:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# A systemtap script which implements "Dvorak with Qwerty hotkeys" by MITMing
# key events in the kernel.
#
# You must install systemtap and your kernel's debug symbols. On Debian:
# sudo apt install systemtap linux-headers-$(uname -r) linux-image-$(uname -r)-dbg
#
# Warning: The debug symbols are a HUGE package and will need to be updated each
# time your kernel updates.
#
# Usage:
# sudo stap -g dvorak-qwerty.stp
#
# Ctrl-C to stop intercepting.
%{
typedef struct {
int modifiers;
// Bitmask of modifier keys that are currently pressed (e.g. ctrl, alt).
int remapped_key;
// If non-zero, indicates a key which is currently down which was remapped
// to Qwerty. We must store this in case the user releases the modifiers
// before releasing the key that was modified, because we need to make sure
// the eventual key-up event matches the remapped key-down event.
} DqState;
static DqState dq_states[2] = {{0, 0}, {0, 0}};
// We need two states, one for each kernel input codepath we're modifying
// (evdev and kbd).
static int modifier_bit(int key) {
switch (key) {
case 29: return 1; // l-ctrl
case 97: return 2; // r-ctrl
case 56: return 4; // l-alt
case 100: return 8; // r-alt
case 219: return 16; // meta ("windows")
}
return 0;
}
static int qwerty2dvorak(int key) {
switch (key) {
case 12: return 40;
case 13: return 27;
case 16: return 45;
case 17: return 51;
case 18: return 32;
case 19: return 24;
case 20: return 37;
case 21: return 20;
case 22: return 33;
case 23: return 34;
case 24: return 31;
case 25: return 19;
case 26: return 12;
case 27: return 13;
case 30: return 30;
case 31: return 39;
case 32: return 35;
case 33: return 21;
case 34: return 22;
case 35: return 36;
case 36: return 46;
case 37: return 47;
case 38: return 25;
case 39: return 44;
case 40: return 16;
case 44: return 53;
case 45: return 48;
case 46: return 23;
case 47: return 52;
case 48: return 49;
case 49: return 38;
case 50: return 50;
case 51: return 17;
case 52: return 18;
case 53: return 26;
}
return key;
}
%}
function handle_event:long(code:long, down:long, index:long) %{
DqState* state = dq_states + STAP_ARG_index;
int mod = modifier_bit(STAP_ARG_code);
if (mod) {
// This is a modifier key. Update the modifier state.
if (STAP_ARG_down) {
state->modifiers |= mod;
} else {
state->modifiers &= ~mod;
}
STAP_RETVALUE = STAP_ARG_code;
} else if (STAP_ARG_code == state->remapped_key) {
// The key is currently down and was remapped at the time it was initially
// pressed. We need to remap it even if the modifier keys are no longer
// pressed, since the eventual "up" event has to have the same key code as
// the original "down" event.
if (!STAP_ARG_down) {
// This is a keyup event, so after this we don't need to track this key
// anymore.
state->remapped_key = 0;
}
STAP_RETVALUE = qwerty2dvorak(STAP_ARG_code);
} else if (STAP_ARG_down == 1 && state->modifiers && !state->remapped_key) {
// This is a keydown event for a newly-pressed key, and at least one
// modifier key is already down, and no other key is already down. So,
// record that this key is the remapped key, and remap the keypress.
STAP_RETVALUE = qwerty2dvorak(STAP_ARG_code);
// Only bother recording this key as remapped if the key code in fact
// changed. (In particular we don't want to record, say, the shift key as
// the remapped key, since this breaks ctrl+shift+whatever hotkeys.)
if (STAP_RETVALUE != STAP_ARG_code) {
state->remapped_key = STAP_ARG_code;
}
} else {
// Just a regular keypress.
STAP_RETVALUE = STAP_ARG_code;
}
%}
# X / Wayland events
probe module("evdev").function("evdev_events") {
for (i = 0; i < $count; i++) {
if ($vals[i]->type == 1) {
$vals[i]->code = handle_event($vals[i]->code, $vals[i]->value, 0)
}
}
}
# Text terminal events
probe kernel.function("kbd_event") {
if ($event_type == 1) {
$event_code = handle_event($event_code, $value, 1)
}
}
# It can take a while for the module to compile and load, so print a message
# when it does.
probe begin {
printf("STARTED\n")
}
# For good measure, print on unload as well.
probe end {
printf("STOPPED\n")
}