Skip to content

Instantly share code, notes, and snippets.

@daniellandau
Created July 28, 2016 19:16
Show Gist options
  • Save daniellandau/7679741bf8bbc5c345591593ca05e9f6 to your computer and use it in GitHub Desktop.
Save daniellandau/7679741bf8bbc5c345591593ca05e9f6 to your computer and use it in GitHub Desktop.
Gnome Shell extension code for grabbing key events from X
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
let initialized = false;
let keymap = {}, xinputPids = [], xinputs = [];
function trackKeys(callback, out_reader, res) {
if (!initialized) initialize();
const [out, length] = out_reader.read_upto_finish(res);
if (length > 0) {
if (/key *press *(\d+)/.test(out)) {
callback((keymap[RegExp.$1] || "unknown key") + ';' + new Date());
}
out_reader.read_upto_async("", 0, 0, null, trackKeys.bind(null, callback), "");
}
}
let nonAsciiWordRegex = /(grave|acute|circumflex|tilde|diaeresis|ring|E|cedilla|caron|ogonek|breve|abovedot|stroke|doubleacute|macron|slash)/;
let nonLatinLanguageRegex = /(hebrew|thai)/;
function initialize() {
const [ret, stdout, stderr, stat] = GLib.spawn_sync(null, ['/usr/bin/xmodmap', '-pke'], null, 0, null);
(stdout+'').split('\n').forEach((line) => {
if (/keycode *(\d+) = (\w+)/.test(line)) {
const scancode = RegExp.$1,
symbol = RegExp.$2;
let type;
if (symbol.startsWith("XF86")) type = 'media';
else if (/^F\d+$/.test(symbol)) type = 'function';
// TODO make this list more complete and inclusive of non-western-europian languages
// (current generated by hand from /usr/include/X11/keysymdef.h)
else if (/^\w$/.test(symbol.replace(nonAsciiWordRegex, '')) || nonLatinLanguageRegex.test(line)) type = 'alphanum';
else type = symbol;
keymap[scancode] = type;
}
});
initialized = true;
}
const xinput = {
poll: pollXinputs,
respawn: respawnXinputs,
spawn: spawnXinputs,
kill: killXinputs
};
function pollXinputs(callback) {
// TODO, don't compare lengths, compare actual contents
if (xinputs.length !== listXinputs().length) {
respawnXinputs(callback);
}
}
function respawnXinputs(callback) {
killXinputs();
spawnXinputs(callback);
}
function spawnXinputs(callback) {
// TODO handle xinput not existing
xinputs = listXinputs();
xinputs.forEach((xinput) => {
const [res, pid, in_fd, out_fd, err_fd] = GLib.spawn_async_with_pipes(null, ['/usr/bin/xinput', 'test', xinput.id], null, 0, null);
xinputPids.push(pid);
const out_reader = new Gio.DataInputStream({
base_stream: new Gio.UnixInputStream({fd: out_fd})
});
out_reader.read_upto_async(
"", 0, 0, null,
trackKeys.bind(null, (line) => callback(line + ';' + xinput.name + ';' + xinput.id)), '');
});
}
function killXinputs() {
xinputPids.forEach((xinputPid) => {
GLib.spawn_close_pid(xinputPid);
// TODO: is there a safer way to kill xinput?
});
GLib.spawn_command_line_sync('killall xinput');
xinputPids = [];
}
function listXinputs() {
const [ret, stdout, stderr, stat] = GLib.spawn_sync(null, ['/usr/bin/xinput', 'list'], null, 0, null);
return (stdout+'')
.split('\n')
.map((line) => {
if (/↳ (.*)\tid=(\d+)/.test(line)) {
return {
'name': RegExp.$1.trim(),
'id': RegExp.$2
};
} else {
return null;
}
})
.filter((x) => !!x);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment