Skip to content

Instantly share code, notes, and snippets.

@tron1point0
Created September 24, 2012 04:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tron1point0/3774231 to your computer and use it in GitHub Desktop.
Save tron1point0/3774231 to your computer and use it in GitHub Desktop.
Generates a keyboard map given a list of keybinds
#!/usr/bin/env perl
use strict;
use warnings;
use Template::Toolkit::Simple;
use YAML qw(Load);
my %config = Load do {local $/;<DATA>};
die $config{usage} unless @ARGV;
my $title = join ' ', grep {not -r} @ARGV;
@ARGV = grep {-r} @ARGV;
my %binds; map {
chomp;
my ($key,$action) = /([^-\s]+)\s+(.+)$/;
my ($mod) = /^(.)?-/;
push @{$binds{uc $key}}, {
($mod ? (modifiers => [$config{modifiers}{uc $mod}]) : ()),
action => $action,
}
} <>;
sub key {
my ($key) = (@_,$_);
scalar {
%{${config}{keys}{uc $key}},
split => 0,
(ref $binds{uc $key} ? (functions => $binds{uc $key}) : ()),
}
}
my @rows = map {scalar {
classes => [qw(row)],
keys => [map {(/\|/ ? scalar {
split => 1,
classes => [qw(key)],
keys => [map {key} split /\|/],
} : key)} split /\s+/],
}} split "\n", $config{layout};
push @{$rows[0]{classes}}, qw(small);
tt->process(
\$config{template},
{ title => $title,
rows => \@rows,
stylesheet => $config{stylesheet},
},\*STDOUT,
{binmode => ':utf8'});
=head1 NAME
generate.pl - Generate a graphical keyboard map given an optional set of
keybinds.
=head1 SYNOPSIS
$ ./generate.pl binds.txt Vim Keybinds
$ ./generate.pl Keyboard Layout
$ ./generate.pl Vim Keybinds < binds.txt
$ cat binds.txt
i Insert Mode
colon Command Mode
C-w Window Mode
=cut
__DATA__
--- layout
--- |-
escape f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 power
grave 1 2 3 4 5 6 7 8 9 0 lbracket rbracket back
tab apostrophe comma period p y f g c r l slash equals backslash
lcontrol a o e u i d h t n s minus return
lshift semicolon q j k x b m w v z rshift
fn capital lmenu lmeta space rmeta rmenu left up|down right
--- usage
--- |
generate.pl: generate a graphical keyboard map given a set of keybinds
Usage: generate.pl [keybinds...] [title...]
keybinds: file or files that specify keybinds, one per line
title: arguments that are not readable files are assumed to be the title of
the keymap
--- template
--- |
[%- MACRO key_t(key) BLOCK %]
[%- IF key.split == 1 %]
<span class="split[% IF key.classes %] [% key.classes.join(' ') %][% END %]">
[%- FOR k IN key.keys %]
[%- key_t(k) %]
[%- END %]
[% ELSE %]
<span class="key[% IF key.classes %] [% key.classes.join(' ') %][% END %]">
<span class="cap">[% key.cap %]</span>
[%- FOR function IN key.functions %]
<span class="function[% IF function.modifiers %] [% function.modifiers.join(' ') %][% END %]">[% function.action %]</span>
[%- END %]
[%- END %]
<span class="code">[% key.code %]</span>
</span>
[%- END -%]
<!DOCTYPE html>
<html>
<head>
<title>[% title %]</title>
<style type="text/css">
[%- stylesheet %]
</style>
</head>
<body>
<div class="keyboard">
[%- FOR row IN rows %]
<div class="[% row.classes.join(' ') %]">
[%- FOR k IN row.keys %]
[%- key_t(k) %]
[%- END %]
</div>
[%- END %]
</div>
</body>
</html>
--- stylesheet
--- |-
body {
font: 12pt Helvetica;
padding: 1em;
}
.row {
width: 65em;
height: 4em;
box-sizing: border-box;
display: block;
padding: 0;
margin: 0 0 0.4em 0;
position: relative;
}
.row .key {
position: relative;
display: inline-block;
box-sizing: border-box;
width: 4em;
height: 4em;
padding: 0.3em 0.3em 0.3em 0.3em;
margin: 0 0.2em 0 0;
border: 0.1em solid #06e;
border-radius: 0.4em;
background-color: #eee;
background: -webkit-linear-gradient(90deg, #eee 0%, #ddd 100%);
box-shadow: 0 0 0.2em #08e;
color: #111;
vertical-align: middle;
text-transform: capitalize;
text-shadow: 0 0 0.1em #fff;
}
.row .key > .code {
position: absolute;
color: rgba(0,0,128,0.2);
text-shadow: 0 0 0.1em rgba(0,0,128,0.1);
font-size: 50%;
right: 0.3em;
bottom: 0.1em;
}
.row .key > .cap {
position: absolute;
font-size: 2.5em;
color: rgba(0,0,0,0.1);
text-shadow: 0 0 0.2em rgba(0,0,0,0.05);
left: 0;
top: 0.2em;
width: 100%;
height: 100%;
text-align: center;
}
.row .key:before {
content: '';
position: absolute;
width: 100%;
height: 100%;
left: -0.1em;
top: -0.1em;
border-radius: 0.4em;
box-shadow: inset 0 0 0.2em #fff;
border: 0.1em solid transparent;
}
.row .key > .function {
display: block;
font-size: 80%;
padding: 0.1em 0.2em 0.1em 0.2em;
margin-left: -0.4em;
margin-right: -0.2em;
text-shadow: 0 0 0.7em rgba(128,128,128,0.5);
text-align: center;
}
/* Special key coloring */
.row .key.special:before {
background-color: rgba(128,128,128,0.3);
}
.row .key.shift:before {
background-color: rgba(128,0,0,0.3);
}
.row .key > .function.shift {
text-shadow: 0 0 0.7em rgba(128,0,0,0.5);
color: rgba(128,0,0,1);
}
.row .key.ctrl:before {
background: rgba(0,128,0,0.3);
}
.row .key > .function.ctrl {
text-shadow: 0 0 0.7em rgba(0,128,0,0.5);
color: rgba(0,128,0,1);
}
.row .key.command:before {
background: rgba(0,0,128,0.3);
}
.row .key > .function.command {
text-shadow: 0 0 0.7em rgba(0,0,128,0.5);
color: rgba(0,0,128,1);
}
.row .key > .function.option,
.row .key.option:before {
background: rgba(128,128,0,0.3);
}
.row .key > .function.option {
text-shadow: 0 0 0.7em rgba(128,128,0,0.5);
color: rgba(128,128,0,1);
}
.row .key.fn:before {
background: rgba(0,128,128,0.3);
}
.row .key > .function.fn {
text-shadow: 0 0 0.7em rgba(0,128,128,0.5);
color: rgba(0,128,128,1);
}
/* Special Key Sizing */
.row .key.tab,
.row .key.delete {
width: 6em;
}
.row .key.ctrl,
.row .key.return {
width: 7.25em;
}
.row .key.shift {
width: 9.55em;
}
.row .key.fn,
.row .key.caps,
.row .key.option {
width: 4em;
}
.row .key.command {
width: 4.6em;
}
.row .key.space {
width: 22.8em;
}
/* Small Row Sizing & Positioning */
.row.small {
height: 2.1em;
}
.row.small .key {
height: 2em;
width: 4.145em;
padding: 0.2em 0.15em 0.2em 0.15em;
}
.row.small .key > .cap {
font-size: 120%;
}
.row.small .key > .function {
font-size: 66%;
margin-left: -0.2em;
}
.row.small .key > .function:first-child {
padding-top: 0;
}
/* Half-keys */
.row .key.top,
.row .key.bottom {
height: 2em;
}
.row .key.top > .cap,
.row .key.bottom > .cap {
font-size: 100%;
vertical-align: middle;
}
.row > .key.top {
margin-bottom: 2em;
}
.row > .key.bottom {
margin-top: 2em;
}
.row .split {
background: none;
border: 0.1em transparent;
box-shadow: none;
padding: 0;
}
.row .split > .bottom {
border-top-width: 0;
}
.row .split > .top,
.row .split > .top:before {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.row .split > .bottom,
.row .split > .bottom:before {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
--- modifiers
---
S: shift
C: ctrl
F: fn
D: command
M: option
--- keys
---
0:
cap: 0
code: 11
1:
cap: 1
code: 2
2:
cap: 2
code: 3
3:
cap: 3
code: 4
4:
cap: 4
code: 5
5:
cap: 5
code: 6
6:
cap: 6
code: 7
7:
cap: 7
code: 8
8:
cap: 8
code: 9
9:
cap: 9
code: 10
A:
cap: A
code: 30
ADD:
cap: +
code: 78
APOSTROPHE:
cap: "'"
code: 40
APPS:
cap: APPS
code: 221
AT:
cap: '@'
code: 145
AX:
cap: AX
code: 150
B:
cap: B
code: 48
BACK:
cap: ⌫
classes:
- special
- delete
code: 14
BACKSLASH:
cap: \
code: 43
C:
cap: C
code: 46
CAPITAL:
cap: ⇪
classes:
- special
code: 58
CIRCUMFLEX:
cap: '^'
code: 144
COLON:
cap: ':'
code: 146
COMMA:
cap: ','
code: 51
CONVERT:
cap: CONVERT
code: 121
D:
cap: D
code: 32
DECIMAL:
cap: .
code: 83
DELETE:
cap: ⌦
code: 211
DIVIDE:
cap: ÷
code: 181
DOWN:
cap: ▼
classes:
- special
- bottom
code: 208
E:
cap: E
code: 18
END:
cap: ↘
code: 207
EQUALS:
cap: =
code: 13
ESCAPE:
cap: ⎋
code: 1
F:
cap: F
code: 33
F1:
cap: F1
code: 59
F10:
cap: F10
code: 68
F11:
cap: F11
code: 87
F12:
cap: F12
code: 88
F13:
cap: F13
code: 100
F14:
cap: F14
code: 101
F15:
cap: F15
code: 102
F2:
cap: F2
code: 60
F3:
cap: F3
code: 61
F4:
cap: F4
code: 62
F5:
cap: F5
code: 63
F6:
cap: F6
code: 64
F7:
cap: F7
code: 65
F8:
cap: F8
code: 66
F9:
cap: F9
code: 67
FN:
cap: Fn
classes:
- fn
code: x01
G:
cap: G
code: 34
GRAVE:
cap: '`'
code: 41
H:
cap: H
code: 35
HOME:
cap: ↖
code: 199
I:
cap: I
code: 23
INSERT:
cap: Ins.
code: 210
J:
cap: J
code: 36
K:
cap: K
code: 37
KANA:
cap: KANA
code: 112
KANJI:
cap: KANJI
code: 148
L:
cap: L
code: 38
LBRACKET:
cap: '['
code: 26
LCONTROL:
cap: ⌃
classes:
- ctrl
code: 29
LEFT:
cap: ◀
classes:
- special
- bottom
code: 203
LMENU:
cap: ⌥
classes:
- option
code: 56
LMETA:
cap: ⌘
classes:
- command
code: 219
LSHIFT:
cap: ⇧
classes:
- shift
code: 42
M:
cap: M
code: 50
MINUS:
cap: -
code: 12
MULTIPLY:
cap: ×
code: 55
N:
cap: N
code: 49
NEXT:
cap: ⇟
code: 209
NOCONVERT:
cap: NOCONVERT
code: 123
NONE:
cap: NONE
code: 0
NUMLOCK:
cap: ⌗
code: 69
NUMPAD0:
cap: NUMPAD0
code: 82
NUMPAD1:
cap: 1
code: 79
NUMPAD2:
cap: 2
code: 80
NUMPAD3:
cap: 3
code: 81
NUMPAD4:
cap: 4
code: 75
NUMPAD5:
cap: 5
code: 76
NUMPAD6:
cap: 6
code: 77
NUMPAD7:
cap: 7
code: 71
NUMPAD8:
cap: 8
code: 72
NUMPAD9:
cap: 9
code: 73
NUMPADCOMMA:
cap: ','
code: 179
NUMPADENTER:
cap: ⎆
code: 156
NUMPADEQUALS:
cap: =
code: 141
O:
cap: O
code: 24
P:
cap: P
code: 25
PAUSE:
cap: Pause
code: 197
PERIOD:
cap: .
code: 52
POWER:
cap: ⌽
classes:
- special
code: 222
PRIOR:
cap: ⇞
code: 201
Q:
cap: Q
code: 16
R:
cap: R
code: 19
RBRACKET:
cap: ']'
code: 27
RCONTROL:
cap: ⌃
classes:
- ctrl
code: 157
RETURN:
cap: ⏎
classes:
- special
- return
code: 28
RIGHT:
cap: ▶
classes:
- special
- bottom
code: 205
RMENU:
cap: ⌥
classes:
- option
code: 184
RMETA:
cap: ⌘
classes:
- command
code: 220
RSHIFT:
cap: ⇧
classes:
- shift
code: 54
S:
cap: S
code: 31
SCROLL:
cap: Scr. Lock
code: 70
SEMICOLON:
cap: ;
code: 39
SLASH:
cap: /
code: 53
SLEEP:
cap: SLEEP
code: 223
SPACE:
cap: Space
classes:
- special
- space
code: 57
STOP:
cap: STOP
code: 149
SUBTRACT:
cap: −
code: 74
SYSRQ:
cap: Sys. Rq.
code: 183
T:
cap: T
code: 20
TAB:
cap: ⇥
classes:
- special
- tab
code: 15
U:
cap: U
code: 22
UNDERLINE:
cap: _
code: 147
UNLABLED:
cap: UNLABLED
code: 151
UP:
cap: ▲
classes:
- special
- top
code: 200
V:
cap: V
code: 47
W:
cap: W
code: 17
X:
cap: X
code: 45
Y:
cap: Y
code: 21
YEN:
cap: ¥
code: 125
Z:
cap: Z
code: 44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment