Created
March 7, 2014 20:16
-
-
Save cstrahan/9419138 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env perl | |
=head1 NAME generate_fixterms-mappings - create an xml output file suitable for | |
loading into iTerm as a preset list conforming to the fixterms specification. | |
=cut | |
use strict; | |
use warnings; | |
use Data::Dumper; | |
use feature qw/say/; | |
use Getopt::Long; | |
use File::Spec; | |
use File::Copy; | |
my $presets_plist = "PresetKeyMappings.plist"; | |
my $presets_plist_tmp = "PresetKeyMappings.plist.tmp"; | |
my $install_target = File::Spec->catfile('/', 'Applications', 'iTerm.app', | |
'Contents', 'Resources', $presets_plist); | |
# some handy constants. | |
sub CSI () { "\e[" } | |
sub SHIFT () { 1 } | |
sub META () { 2 } | |
sub CTRL () { 4 } | |
# all combinations of modifiers. | |
# not all should be used for all characters. | |
sub MODIFIERS () { | |
return ( | |
SHIFT, | |
META, | |
CTRL, | |
SHIFT + META, | |
SHIFT + CTRL, | |
SHIFT + META + CTRL, | |
META + CTRL | |
); | |
} | |
# Modifiers: | |
# | |
# Shift : 0x020000 (Also affects the actual char) | |
# Ctrl : 0x040000 | |
# Option : 0x080000 | |
# Cmd : 0x100000 | |
sub I_ARROW () { 0x200000 } | |
sub I_SHIFT () { 0x020000 } | |
sub I_CTRL () { 0x040000 } | |
sub I_OPT () { 0x080000 } | |
sub I_CMD () { 0x100000 } | |
sub main { | |
my $install = 0; | |
my ($ctrl_i, $ctrl_h, $ctrl_m, $ctrl_j); | |
# preload from env hash slice. | |
($ctrl_i, $ctrl_h, $ctrl_m, $ctrl_j) = @ENV{qw/USE_CTRL_I USE_CTRL_H | |
USE_CTRL_M USE_CTRL_J/}; | |
my @options = ( | |
'ctrl-i!' => \$ctrl_i, 'ctrl-h!' => \$ctrl_h, | |
'ctrl-m!' => \$ctrl_m, 'ctrl-j!' => \$ctrl_j, | |
'install!' => \$install | |
); | |
GetOptions(@options); | |
my @which; | |
push @which, 'i' if $ctrl_i; | |
push @which, 'h' if $ctrl_h; | |
push @which, 'm' if $ctrl_m; | |
push @which, 'j' if $ctrl_j; | |
my @entries; | |
push @entries, @{ generate_numbers() }; | |
push @entries, @{ generate_lower_case_entries() }; | |
push @entries, @{ generate_upper_case_entries() }; | |
push @entries, @{ generate_symbols() }; | |
push @entries, @{ generate_shifted_symbols() }; | |
push @entries, @{ generate_very_specials() }; | |
push @entries, @{ generate_ctrl_contested(@which) }; | |
save_to_plist(\@entries); | |
if ($install) { | |
copy($presets_plist_tmp, $install_target) | |
or die "Failed to copy to install dir: $install_target: $!"; | |
say "Installed into '$install_target'"; | |
} | |
} | |
sub generate_lower_case_entries { | |
my @output; | |
my @mods = ( | |
META, | |
META + CTRL, | |
); | |
for my $modifiers (@mods) { | |
for my $char ('a'..'z') { | |
my $keycode = ord($char); | |
my $i_key = construct_iterm_key($keycode, $modifiers); | |
my $csi_u = construct_fixterm_csi_u($keycode, $modifiers); | |
push @output, [$i_key, $csi_u]; | |
say 'LC: creating: ' . to_string($keycode, $modifiers, $i_key, $csi_u); | |
} | |
} | |
return \@output; | |
} | |
sub generate_ctrl_contested { | |
my (@contested) = @_; | |
my @output; | |
my @mods = ( | |
CTRL | |
); | |
for my $modifiers (@mods) { | |
for my $char (@contested) { | |
my $keycode = ord($char); | |
my $i_key = construct_iterm_key($keycode, $modifiers); | |
my $csi_u = construct_fixterm_csi_u($keycode, $modifiers); | |
push @output, [$i_key, $csi_u]; | |
say '!!: creating: ' . to_string($keycode, $modifiers, $i_key, $csi_u); | |
} | |
} | |
return \@output; | |
} | |
sub generate_numbers { | |
my @output; | |
my @mods = ( | |
CTRL, | |
META, | |
META + CTRL, | |
); | |
for my $modifiers (@mods) { | |
for my $char (0..9) { | |
my $keycode = ord($char); | |
my $i_key = construct_iterm_key($keycode, $modifiers); | |
my $csi_u = construct_fixterm_csi_u($keycode, $modifiers); | |
push @output, [$i_key, $csi_u]; | |
say '09: creating: ' . to_string($keycode, $modifiers, $i_key, $csi_u); | |
} | |
} | |
return \@output; | |
} | |
sub generate_upper_case_entries { | |
my @output; | |
my @mods = ( | |
SHIFT + CTRL, | |
SHIFT + META, | |
SHIFT + META + CTRL, | |
); | |
for my $modifiers (@mods) { | |
for my $char ('A'..'Z') { | |
my $keycode = ord($char); | |
my $i_key = construct_iterm_key($keycode, $modifiers); | |
my $remove_shift = $modifiers & ~SHIFT; | |
my $csi_u = construct_fixterm_csi_u($keycode, $remove_shift); | |
push @output, [$i_key, $csi_u]; | |
say 'UC: creating: ' . to_string($keycode, $modifiers, $i_key, $csi_u); | |
} | |
} | |
return \@output; | |
} | |
sub generate_symbols { | |
#Ctrl-H = CSI 104;5 u Ctrl-Shift-H = CSI 72;5 u Backspace = 0x08 | |
#Ctrl-I = CSI 105;5 u Ctrl-Shift-I = CSI 73;5 u Tab = 0x09 | |
#Ctrl-M = CSI 109;5 u Ctrl-Shift-M = CSI 77;5 u Enter = 0x0d | |
#Ctrl-[ = CSI 91;5 u Ctrl-{ = CSI 123;5 u Escape = 0x1b | |
my @output; | |
my @chars = ( | |
'[', ']', "\\", '/', "'", | |
'=', '-', ';', ',', '.', '`', | |
); | |
my @mods = ( | |
CTRL, | |
META, | |
CTRL + META, | |
); | |
for my $modifiers (@mods) { | |
for my $char (@chars) { | |
my $keycode = ord($char); | |
my $i_key = construct_iterm_key($keycode, $modifiers); | |
my $csi_u = construct_fixterm_csi_u($keycode, $modifiers); | |
push @output, [$i_key, $csi_u]; | |
say '[]: creating: ' . to_string($keycode, $modifiers, $i_key, $csi_u); | |
} | |
} | |
return \@output; | |
} | |
sub generate_shifted_symbols { | |
my @output; | |
my @shifted = ( | |
'{', '}', '?', '<', '>', '+', '~', '#', ':', | |
'|', '"', '_', '!' | |
); | |
my @mods = ( | |
SHIFT + CTRL, | |
SHIFT + CTRL + META, | |
SHIFT + META, | |
CTRL, | |
META + CTRL, | |
); | |
for my $modifiers (@mods) { | |
for my $char (@shifted) { | |
my $keycode = ord($char); | |
my $i_key = construct_iterm_key($keycode, $modifiers); | |
my $csi_u = construct_fixterm_csi_u($keycode, $modifiers); | |
push @output, [$i_key, $csi_u]; | |
say '<>: creating: ' . to_string($keycode, $modifiers, $i_key, $csi_u); | |
} | |
} | |
return \@output; | |
} | |
sub generate_very_specials { | |
my @output; | |
my $specials_iterm | |
= { | |
Up => 0xf700, # arrows need I_ARROW added to their | |
Down => 0xf701, # modifiers in the <key> field. | |
Left => 0xf702, # eg: '0xf702-0x200000' | |
Right => 0xf703, | |
End => 0xf72b, | |
Home => 0xf729, | |
F1 => 0xf704, | |
F2 => 0xf705, | |
F3 => 0xf706, | |
F4 => 0xf707, # F-keys appear to keep incrementing, F6 = f709... | |
PgUp => 0xf72c, | |
PgDn => 0xf72d, | |
}; | |
my $specials_fixterm | |
= { | |
Up => 'A', | |
Down => 'B', | |
Right => 'C', | |
Left => 'D', | |
End => 'F', | |
Home => 'H', | |
F1 => 'P', | |
F2 => 'Q', | |
F3 => 'R', | |
F4 => 'S', | |
}; | |
my @mods = ( | |
SHIFT, | |
META, | |
CTRL, | |
SHIFT + META, | |
SHIFT + CTRL, | |
SHIFT + CTRL + META, | |
CTRL + META | |
); | |
# format for these specials is: CSI 1;[modifier] {ABCDFHPQRS} | |
# where 1;1 is redundant. All others apply. | |
foreach my $modifiers (@mods) { | |
foreach my $key (sort keys %$specials_fixterm) { | |
my $fix_val = $specials_fixterm->{$key}; | |
my $i_val = $specials_iterm->{$key}; | |
my $i_key = construct_iterm_key($i_val, $modifiers); | |
#my $remove_shift = $modifiers & ~SHIFT; | |
my $fix_seq = construct_csi_very_special($fix_val, $modifiers); | |
push @output, [$i_key, $fix_seq]; | |
say 'VS: creating: ' . to_string(ord($fix_val), | |
$modifiers, $i_key, $fix_seq); | |
} | |
} | |
return \@output; | |
} | |
sub save_to_plist { | |
my ($entries) = @_; | |
#say Dumper($entries); | |
#return; | |
open my $in_fh, '<', $presets_plist | |
or die "Couldn't open $presets_plist for reading: $!"; | |
open my $out_fh, '>', $presets_plist_tmp | |
or die "Couldn't open $presets_plist_tmp for writing: $!"; | |
my $inserted = 0; | |
while (my $line = <$in_fh>) { | |
print $out_fh $line; | |
if (($inserted == 0) and ($line =~ m/<dict>/)) { | |
print $out_fh header(); | |
print $out_fh entry( @{ $_ } ) for @$entries; | |
print $out_fh footer(); | |
$inserted = 1; | |
} | |
} | |
close $in_fh or die "Couldn't close input filehandle: $!"; | |
close $out_fh or die "Couldn't close output filehandle: $!"; | |
} | |
sub is_arrow { | |
my ($key) = @_; | |
if (($key >= 0xf700) and ($key <= 0xf703)) { | |
return 1; | |
} else { | |
return 0; | |
} | |
} | |
sub contains_shift { | |
my ($mod) = @_; | |
return ($mod & SHIFT) | |
? 1 | |
: 0; | |
} | |
sub contains_meta { | |
my ($mod) = @_; | |
return ($mod & META) | |
? 1 | |
: 0; | |
} | |
sub contains_ctrl { | |
my ($mod) = @_; | |
return ($mod & CTRL) | |
? 1 | |
: 0; | |
} | |
sub modifiers_to_string { | |
my ($mod) = @_; | |
my @mods; | |
push @mods, 'SHIFT' if (contains_shift($mod)); | |
push @mods, 'META' if (contains_meta($mod)); | |
push @mods, 'CTRL' if (contains_ctrl($mod)); | |
my $str = join(" | ", @mods); | |
return $str; | |
} | |
sub construct_iterm_key { | |
my ($keycode, $modifiers) = @_; | |
my $mod_num = 0; | |
$mod_num += I_SHIFT if contains_shift($modifiers); | |
$mod_num += I_OPT if contains_meta ($modifiers); | |
$mod_num += I_CTRL if contains_ctrl ($modifiers); | |
$mod_num += I_ARROW if is_arrow($keycode); | |
return sprintf('0x%x-0x%x', $keycode, $mod_num); | |
} | |
sub construct_fixterm_csi_u { | |
my ($keycode, $modifiers) = @_; | |
return construct_fixterm_csi($keycode, $modifiers, 'u'); | |
} | |
sub construct_csi_very_special { | |
my ($keystr, $modifiers) = @_; | |
my $mod_value = fixterm_modvalue($modifiers); | |
$mod_value = '1' . $mod_value if $mod_value; | |
return '[' . $mod_value . $keystr; | |
} | |
sub fixterm_modvalue { | |
my ($modifiers) = @_; | |
my $mod_value = 1; | |
if ($modifiers) { | |
$mod_value += SHIFT if contains_shift($modifiers); | |
$mod_value += META if contains_meta ($modifiers); | |
$mod_value += CTRL if contains_ctrl ($modifiers); | |
$mod_value = ';' . $mod_value; | |
} else { | |
$mod_value = ''; | |
} | |
return $mod_value; | |
} | |
sub construct_fixterm_csi { | |
my ($keycode, $modifiers, $term_char) = @_; | |
$term_char //= ''; | |
my $mod_value = fixterm_modvalue($modifiers); | |
return '[' . $keycode . $mod_value . $term_char; | |
} | |
sub to_string { | |
my ($keycode, $modifiers, $i_key, $csi_u) = @_; | |
return sprintf('key: %s keycode: %-3d %-25s %-16s %-10s', | |
chr($keycode), $keycode, | |
modifiers_to_string($modifiers), | |
$i_key, $csi_u); | |
} | |
################################################################################ | |
sub header { | |
return | |
" <key>Fixterm</key>\n". | |
" <dict>\n"; | |
} | |
sub entry { | |
my ($iterm_key, $fixterm_val) = @_; | |
my @output | |
= ( | |
" <key>$iterm_key</key>\n", | |
" <dict>\n", | |
" <key>Action</key>\n", | |
" <integer>10</integer>\n", | |
" <key>Text</key>\n", | |
" <string>$fixterm_val</string>\n", | |
" </dict>\n", | |
); | |
return join '', @output; | |
} | |
sub footer { | |
return " </dict>\n"; | |
} | |
################################################################################ | |
say 'Running fixterm generator script...'; | |
main(); | |
say 'Done'; | |
################################################################################ | |
# sub generate_alpha { | |
# my @output; | |
# my $hash = {}; | |
# foreach my $modifier (@modifiers) { | |
# foreach my $char (('A'..'Z'),('a' .. 'z'),(0..9)) { | |
# my $fixterm_modifier = $modifier + 1; | |
# my $uc_char = uc($char); | |
# my $keycode = ord($char); | |
# my $uc_keycode = ord($uc_char); | |
# my $iterm_charcode; | |
# my $iterm_modifier = 0; | |
# if (contains_shift($modifier)) { | |
# $iterm_modifier |= I_SHIFT; | |
# } | |
# if (contains_meta($modifier)) { | |
# $iterm_modifier |= I_OPT; | |
# } | |
# if (contains_ctrl($modifier)) { | |
# $iterm_modifier |= I_CTRL; | |
# } | |
# # skip ctrl-only for lower-case letters. | |
# if ($modifier == CTRL) { | |
# next if ($char ne $uc_char); | |
# say "ctrl-only ok for: $char"; | |
# } | |
# $iterm_modifier = sprintf('0x%x', $iterm_modifier); | |
# if (contains_shift($modifier)) { | |
# $iterm_charcode = sprintf('0x%x', $uc_keycode); | |
# } else { | |
# $iterm_charcode = sprintf('0x%x', $keycode); | |
# } | |
# my $iterm_id = "$iterm_charcode-$iterm_modifier"; | |
# # iTerm action 10 provides the \e escape for the CSI. | |
# my $fixterm_csi = '[' . $keycode; | |
# if ($fixterm_modifier != 1) { | |
# $fixterm_csi .= ";$fixterm_modifier"; | |
# } | |
# $fixterm_csi .= 'u'; | |
# print STDERR sprintf("Processing: %-3s %-3s %s, %s %d\n", | |
# $char, $keycode, | |
# $fixterm_csi, mods_to_str($modifier), | |
# contains_shift($modifier)); | |
# $hash->{$iterm_id} = $fixterm_csi; | |
# } | |
# } | |
# foreach my $key (sort keys %$hash) { | |
# my $val = $hash->{$key}; | |
# push @output, [$key, $val]; | |
# push @fixterm_entries, $output; | |
# } | |
# } | |
__END__ | |
# TODO: | |
# | |
# sample of PresetKeyMappings plist | |
# <key>xterm Defaults</key> <-- name of preset | |
# <dict> | |
# <key>0xf700-0x260000</key> <- key, plus modifiers | |
# <dict> | |
# <key>Action</key> | |
# <integer>10</integer> <- 10 is 'send escape' | |
# <key>Text</key> | |
# <string>[1;6A</string> <- and the remaining sequence | |
# </dict> | |
# <key>0xf701-0x260000</key> | |
# <dict> | |
# <key>Action</key> | |
# <integer>10</integer> | |
# <key>Text</key> | |
# <string>[1;6B</string> | |
# </dict> | |
# <key>0xf702-0x260000</key> | |
# <dict> | |
# <key>Action</key> | |
# <integer>10</integer> | |
# <key>Text</key> | |
# <string>[1;6D</string> | |
# </dict> | |
# <dict> | |
# Modifiers: | |
# | |
# Shift : 0x020000 (Also affects the actual char) | |
# Ctrl : 0x040000 | |
# Option : 0x080000 | |
# Cmd : 0x100000 | |
# Keys: | |
#"Keyboard Map" = { | |
# "0x41-0x20000" = shift a { | |
# Action = 11; | |
# Text = "0x1 0x1"; | |
# }; | |
# "0x41-0xa0000" = 'shift meta a' { | |
# Action = 11; | |
# Text = "0x3 0x1"; | |
# }; | |
# "0x61-0x0" = 'a' { | |
# Action = 11; | |
# Text = 0x01; | |
# }; | |
# "0x61-0x100000" = 'cmd a' { | |
# Action = 11; | |
# Text = "0x8 0x1"; | |
# }; | |
# "0x61-0x40000" = 'ctrl a' { | |
# Action = 11; | |
# Text = "0x04 0x01"; | |
# }; | |
# "0x61-0x80000" = 'opt a' { | |
# Action = 11; | |
# Text = "0x2 0x1"; | |
# }; | |
# "0x62-0x0" = 'b' { | |
# Action = 11; | |
# Text = 0x2; | |
# }; | |
# }; | |
"Keyboard Map" = { | |
"0x1b-0x0" = { | |
Action = 12; | |
Text = escape; | |
}; | |
"0x41-0x20000" = { | |
Action = 11; | |
Text = "0x1 0x1"; | |
}; | |
"0x41-0xa0000" = { | |
Action = 11; | |
Text = "0x3 0x1"; | |
}; | |
"0x61-0x0" = { | |
Action = 11; | |
Text = 0x01; | |
}; | |
"0x61-0x100000" = { | |
Action = 11; | |
Text = "0x8 0x1"; | |
}; | |
"0x61-0x40000" = { | |
Action = 11; | |
Text = "0x04 0x01"; | |
}; | |
"0x61-0x80000" = { | |
Action = 11; | |
Text = "0x2 0x1"; | |
}; | |
"0x62-0x0" = { | |
Action = 11; | |
Text = 0x2; | |
}; | |
"0x7f-0x0" = { | |
Action = 12; | |
Text = backspace; | |
}; | |
"0x9-0x0" = { | |
Action = 12; | |
Text = tab; | |
}; | |
"0xf700-0x200000" = { | |
Action = 12; | |
Text = uparrow; | |
}; | |
"0xf701-0x200000" = { | |
Action = 12; | |
Text = downarrow; | |
}; | |
"0xf702-0x200000" = { | |
Action = 12; | |
Text = leftarrow; | |
}; | |
"0xf703-0x200000" = { | |
Action = 12; | |
Text = rightarrow; | |
}; | |
"0xf704-0x0" = { | |
Action = 12; | |
Text = f1; | |
}; | |
"0xf705-0x0" = { | |
Action = 12; | |
Text = f2; | |
}; | |
"0xf706-0x0" = { | |
Action = 12; | |
Text = f3; | |
}; | |
"0xf707-0x0" = { | |
Action = 12; | |
Text = f4; | |
}; | |
"0xf708-0x0" = { | |
Action = 12; | |
Text = f5; | |
}; | |
"0xf709-0x0" = { | |
Action = 12; | |
Text = f6; | |
}; | |
"0xf72c-0x0" = { | |
Action = 12; | |
Text = pgup; | |
}; | |
"0xf72d-0x0" = { | |
Action = 12; | |
Text = pgdown; | |
}; | |
}; | |
"0x1b-0x0" = { | |
Action = 12; | |
Text = escape; | |
}; | |
"0x41-0x20000" = { | |
Action = 12; | |
Text = "shift a"; | |
}; | |
"0x41-0x60000" = { | |
Action = 12; | |
Text = "ctrl-shift-a"; | |
}; | |
"0x41-0xa0000" = { | |
Action = 12; | |
Text = "meta-shift-a"; | |
}; | |
"0x41-0xe0000" = { | |
Action = 12; | |
Text = "ctrl-meta-shift-a"; | |
}; | |
"0x61-0x0" = { | |
Action = 12; | |
Text = a; | |
}; | |
"0x61-0xc0000" = { | |
Action = 12; | |
Text = "ctrl-meta-a"; | |
}; | |
"0x7f-0x0" = { | |
Action = 12; | |
Text = backspace; | |
}; | |
"0x9-0x0" = { | |
Action = 12; | |
Text = tab; | |
}; | |
"0xf700-0x200000" = { | |
Action = 12; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment