Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ddai-dev/e2a9ab0b233bb60e93147583fea9e891 to your computer and use it in GitHub Desktop.
Save ddai-dev/e2a9ab0b233bb60e93147583fea9e891 to your computer and use it in GitHub Desktop.

Revisions

  1. @cstrahan cstrahan renamed this gist Mar 7, 2014. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. @cstrahan cstrahan created this gist Mar 7, 2014.
    797 changes: 797 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,797 @@
    #!/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;