Keyboard 2014-11-05
I have recently been investigating building or purchasing a 60% keyboard. Before committing to this investment, I wanted to get an idea of how much I actually use the keys outside of the 60% layout. There may have been some kind of tool to show me this but I decided to whip up a very rudimentary keylogger and let it run for a few hours while I used my computer.
#!/bin/sh
id=$(xinput --list |grep 'USB Keyboard' |head -1 |grep -oP '(?<=id=)\d+')
xinput --test $id >> ~/Documents/keylog.txt
I did say it was rudimentary. Better data might be attained with a better method of logging keystrokes, as xinput seems to regard holding a key down as a long series of press and release events rather than a single one. After collecting the data for a few hours I created a script to put the data into a useful format for me to digest.
#!/usr/bin/env perl
package KeyMap;
use 5.010;
use strict;
use warnings;
use SVG;
sub new {
my ($class, @args) = @_;
my $self = bless {
presses => [],
total => 0,
svg => SVG->new(
width => 940,
height => 300,
@args,
),
}, $class;
}
sub svg { shift->{svg} }
sub make_key {
my ($self, $x, $y, $w, $h, $text, $color) = @_;
my ($r, $g, $b) = @$color;
my $tcol = $color->[3] ? '#fff' : '#000';
$self->svg->rectangle(
x => $x + 2,
y => $y + 2,
width => $w - 4,
height => $h - 4,
rx => 5,
ry => 5,
style => {
stroke => '#000',
fill => "rgb($r,$g,$b)",
});
$self->svg->text(
x => $x + $w / 2,
y => $y + $h / 2 + 3,
style => {
'fill' => $tcol,
'text-align' => 'center',
'text-anchor' => 'middle',
'font-family' => 'Noto Sans',
'font-size' => '10px',
},
)->cdata($text);
}
sub make_row {
my ($self, $x, $y, @keys) = @_;
foreach (@keys) {
$_ = [ $_, 1, 1 ] unless ref $_;
my ($code, $w, $h) = @$_;
$w ||= 1;
$h ||= 1;
$self->make_key($x, $y, $w * 40, $h * 40,
sprintf('%0.2f', $self->percent($code)),
$self->color($code));
$x += $w * 40;
}
}
sub add_key_press {
my ($self, $code) = @_;
$self->{presses}[$code]++;
$self->{total}++;
delete $self->{max_percent};
}
sub presses { shift->{presses}[shift] || 0 }
sub total { shift->{total} }
sub max_percent {
my ($self) = @_;
return $self->{max_percent} if exists $self->{max_percent};
my $max = 0;
for (@{ $self->{presses} }) {
$max = $_ if $_ and $_ > $max;
}
return $self->{max_percent} = $max / $self->total * 100;
}
sub percent {
my ($self, $code) = @_;
return $self->presses($code) / $self->total * 100;
}
sub color {
my ($self, $code) = @_;
# Crazy exponential scaling gotten experimentally
my $scale = ($self->percent($code) / $self->max_percent) ** 0.3;
my ($r, $gb, $t);
if ($scale > 0.66) {
$r = 255 - int(128 * ($scale - 0.66) / 0.33);
$gb = 0;
$t = 1;
} else {
$r = 255;
$gb = 255 - int(255 * $scale / 0.66);
$t = 0;
}
return [ $r, $gb, $gb, $t ];
}
sub render {
my ($self) = @_;
# base keys
$self->make_row(20, 80, 49, 10 .. 21, [ 22, 2 ]);
$self->make_row(20, 120, [ 23, 1.5 ], 24 .. 35, [ 51, 1.5 ]);
$self->make_row(20, 160, [ 66, 1.75 ], 38 .. 48, [ 36, 2.25 ]);
$self->make_row(20, 200, [ 50, 2.25 ], 52 .. 61, [ 62, 2.75 ]);
$self->make_row(
20,
240,
[ 37, 1.25 ],
[ 133, 1.25 ],
[ 64, 1.25 ],
[ 65, 6.25 ],
[ 108, 1.25 ],
[ 134, 1.25 ],
[ 135, 1.25 ],
[ 105, 1.25 ]);
# function keys
$self->make_row(20, 20, 9);
$self->make_row(100, 20, 67 .. 70);
$self->make_row(280, 20, 71 .. 74);
$self->make_row(460, 20, 75, 76, 95, 96);
# navigation
$self->make_row(630, 20, 107, 78, 127);
$self->make_row(630, 80, 118, 110, 112);
$self->make_row(630, 120, 119, 115, 117);
$self->make_row(670, 200, 111);
$self->make_row(630, 240, 113, 116, 114);
# number pad
$self->make_row(760, 80, 77, 106, 63, 82);
$self->make_row(760, 120, 79 .. 81, [ 86, 1, 2 ]);
$self->make_row(760, 160, 83 .. 85);
$self->make_row(760, 200, 87 .. 89, [ 104, 1, 2 ]);
$self->make_row(760, 240, [ 90, 2 ], 91);
}
sub xmlify {
my ($self, @args) = @_;
return $self->svg->xmlify(@args);
}
package main;
use strict;
use warnings;
use autodie;
my $INPUT = "$ENV{HOME}/Documents/keylog.txt";
my $OUTPUT = "$ENV{HOME}/Documents/keyfreq.svg";
open my $output, '>', $OUTPUT;
open my $input, '<', $INPUT;
my $map = KeyMap->new();
while (<$input>) {
$map->add_key_press($1) if /^key release\s+(\d+)\s+$/;
}
close $input;
$map->render;
print $output $map->xmlify;
close $output;
This script reads the keylog and creates a "heat map" of which keys I actually pressed. Here is the generated image:
Update: I have regenerated the map with a few days of data to give a better view of my usage.
Some notes before interpreting the data:
I have my caps lock mapped to super (windows key) because it is the modifier I use for my window manager functions. I do not actually turn caps lock on and off that frequently.
I have my escape and tilde (left of number 1) keys swapped. I'm not sure I like this just yet as I type ~ more often than I had realized.
I only had the keylogger running while I was doing work-type things. I boot into Windows if I am going to play games and I did not log any data during those times although I'm not sure the differences would be significant.
All in all I think this really shows how little keys are used outside of the 60% layout area (at least by me during the time of this experiment). In any case, this has strengthened my resolve to acquire a 60% keyboard.