| #!/usr/bin/perl -w |
| # |
| # Copyright (C) 2011 Michael Brown <mbrown@fensystems.co.uk>. |
| # |
| # This program is free software; you can redistribute it and/or |
| # modify it under the terms of the GNU General Public License as |
| # published by the Free Software Foundation; either version 2 of the |
| # License, or any later version. |
| # |
| # This program is distributed in the hope that it will be useful, but |
| # WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| # General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program; if not, write to the Free Software |
| # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| # 02110-1301, USA. |
| |
| =head1 NAME |
| |
| genkeymap.pl |
| |
| =head1 SYNOPSIS |
| |
| genkeymap.pl [options] <keymap name> |
| |
| Options: |
| |
| -f,--from=<name> Set BIOS keymap name (default "us") |
| -h,--help Display brief help message |
| -v,--verbose Increase verbosity |
| -q,--quiet Decrease verbosity |
| |
| =cut |
| |
| # With reference to: |
| # |
| # http://gunnarwrobel.de/wiki/Linux-and-the-keyboard.html |
| |
| use Getopt::Long; |
| use Pod::Usage; |
| use strict; |
| use warnings; |
| |
| use constant BIOS_KEYMAP => "us"; |
| use constant BKEYMAP_MAGIC => "bkeymap"; |
| use constant MAX_NR_KEYMAPS => 256; |
| use constant NR_KEYS => 128; |
| use constant KG_SHIFT => 0; |
| use constant KG_ALTGR => 1; |
| use constant KG_CTRL => 2; |
| use constant KG_ALT => 3; |
| use constant KG_SHIFTL => 4; |
| use constant KG_KANASHIFT => 4; |
| use constant KG_SHIFTR => 5; |
| use constant KG_CTRLL => 6; |
| use constant KG_CTRLR => 7; |
| use constant KG_CAPSSHIFT => 8; |
| use constant KT_LATIN => 0; |
| use constant KT_FN => 1; |
| use constant KT_SPEC => 2; |
| use constant KT_PAD => 3; |
| use constant KT_DEAD => 4; |
| use constant KT_CONS => 5; |
| use constant KT_CUR => 6; |
| use constant KT_SHIFT => 7; |
| use constant KT_META => 8; |
| use constant KT_ASCII => 9; |
| use constant KT_LOCK => 10; |
| use constant KT_LETTER => 11; |
| use constant KT_SLOCK => 12; |
| use constant KT_SPKUP => 14; |
| |
| my $verbosity = 1; |
| my $from_name = BIOS_KEYMAP; |
| |
| # Read named keymaps using "loadkeys -b" |
| # |
| sub read_keymaps { |
| my $name = shift; |
| my $keymaps = []; |
| |
| # Generate binary keymap |
| open my $pipe, "-|", "loadkeys", "-b", $name |
| or die "Could not load keymap \"".$name."\": $!\n"; |
| |
| # Check magic |
| read $pipe, my $magic, length BKEYMAP_MAGIC |
| or die "Could not read from \"".$name."\": $!\n"; |
| die "Bad magic value from \"".$name."\"\n" |
| unless $magic eq BKEYMAP_MAGIC; |
| |
| # Read list of included keymaps |
| read $pipe, my $included, MAX_NR_KEYMAPS |
| or die "Could not read from \"".$name."\": $!\n"; |
| my @included = unpack ( "C*", $included ); |
| die "Missing or truncated keymap list from \"".$name."\"\n" |
| unless @included == MAX_NR_KEYMAPS; |
| |
| # Read each keymap in turn |
| for ( my $keymap = 0 ; $keymap < MAX_NR_KEYMAPS ; $keymap++ ) { |
| if ( $included[$keymap] ) { |
| read $pipe, my $keysyms, ( NR_KEYS * 2 ) |
| or die "Could not read from \"".$name."\": $!\n"; |
| my @keysyms = unpack ( "S*", $keysyms ); |
| die "Missing or truncated keymap ".$keymap." from \"".$name."\"\n" |
| unless @keysyms == NR_KEYS; |
| push @$keymaps, \@keysyms; |
| } else { |
| push @$keymaps, undef; |
| } |
| } |
| |
| close $pipe; |
| return $keymaps; |
| } |
| |
| # Translate keysym value to ASCII |
| # |
| sub keysym_to_ascii { |
| my $keysym = shift; |
| |
| # Non-existent keysyms have no ASCII equivalent |
| return unless $keysym; |
| |
| # Sanity check |
| if ( $keysym & 0xf000 ) { |
| warn "Unexpected keysym ".sprintf ( "0x%04x", $keysym )."\n"; |
| return; |
| } |
| |
| # Extract type and value |
| my $type = ( $keysym >> 8 ); |
| my $value = ( $keysym & 0xff ); |
| |
| # Non-simple types have no ASCII equivalent |
| return unless ( ( $type == KT_LATIN ) || ( $type == KT_ASCII ) || |
| ( $type == KT_LETTER ) ); |
| |
| # High-bit-set characters cannot be generated on a US keyboard |
| return if $value & 0x80; |
| |
| return $value; |
| } |
| |
| # Translate ASCII to descriptive name |
| # |
| sub ascii_to_name { |
| my $ascii = shift; |
| |
| if ( $ascii == 0x5c ) { |
| return "'\\\\'"; |
| } elsif ( $ascii == 0x27 ) { |
| return "'\\\''"; |
| } elsif ( ( $ascii >= 0x20 ) && ( $ascii <= 0x7e ) ) { |
| return sprintf ( "'%c'", $ascii ); |
| } elsif ( $ascii <= 0x1a ) { |
| return sprintf ( "Ctrl-%c", ( 0x40 + $ascii ) ); |
| } else { |
| return sprintf ( "0x%02x", $ascii ); |
| } |
| } |
| |
| # Produce translation table between two keymaps |
| # |
| sub translate_keymaps { |
| my $from = shift; |
| my $to = shift; |
| my $map = {}; |
| |
| foreach my $keymap ( 0, 1 << KG_SHIFT, 1 << KG_CTRL ) { |
| for ( my $keycode = 0 ; $keycode < NR_KEYS ; $keycode++ ) { |
| my $from_ascii = keysym_to_ascii ( $from->[$keymap]->[$keycode] ) |
| or next; |
| my $to_ascii = keysym_to_ascii ( $to->[$keymap]->[$keycode] ) |
| or next; |
| my $new_map = ( ! exists $map->{$from_ascii} ); |
| my $update_map = |
| ( $new_map || ( $keycode < $map->{$from_ascii}->{keycode} ) ); |
| if ( ( $verbosity > 1 ) && |
| ( ( $from_ascii != $to_ascii ) || |
| ( $update_map && ! $new_map ) ) ) { |
| printf STDERR "In keymap %d: %s => %s%s\n", $keymap, |
| ascii_to_name ( $from_ascii ), ascii_to_name ( $to_ascii ), |
| ( $update_map ? ( $new_map ? "" : " (override)" ) |
| : " (ignored)" ); |
| } |
| if ( $update_map ) { |
| $map->{$from_ascii} = { |
| to_ascii => $to_ascii, |
| keycode => $keycode, |
| }; |
| } |
| } |
| } |
| return { map { $_ => $map->{$_}->{to_ascii} } keys %$map }; |
| } |
| |
| # Parse command-line options |
| Getopt::Long::Configure ( 'bundling', 'auto_abbrev' ); |
| GetOptions ( |
| 'verbose|v+' => sub { $verbosity++; }, |
| 'quiet|q+' => sub { $verbosity--; }, |
| 'from|f=s' => sub { shift; $from_name = shift; }, |
| 'help|h' => sub { pod2usage ( 1 ); }, |
| ) or die "Could not parse command-line options\n"; |
| pod2usage ( 1 ) unless @ARGV == 1; |
| my $to_name = shift; |
| |
| # Read and translate keymaps |
| my $from = read_keymaps ( $from_name ); |
| my $to = read_keymaps ( $to_name ); |
| my $map = translate_keymaps ( $from, $to ); |
| |
| # Generate output |
| ( my $to_name_c = $to_name ) =~ s/\W/_/g; |
| printf "/** \@file\n"; |
| printf " *\n"; |
| printf " * \"".$to_name."\" keyboard mapping\n"; |
| printf " *\n"; |
| printf " * This file is automatically generated; do not edit\n"; |
| printf " *\n"; |
| printf " */\n"; |
| printf "\n"; |
| printf "FILE_LICENCE ( PUBLIC_DOMAIN );\n"; |
| printf "\n"; |
| printf "#include <ipxe/keymap.h>\n"; |
| printf "\n"; |
| printf "/** \"".$to_name."\" keyboard mapping */\n"; |
| printf "struct key_mapping ".$to_name_c."_mapping[] __keymap = {\n"; |
| foreach my $from_sym ( sort { $a <=> $b } keys %$map ) { |
| my $to_sym = $map->{$from_sym}; |
| next if $from_sym == $to_sym; |
| printf "\t{ 0x%02x, 0x%02x },\t/* %s => %s */\n", $from_sym, $to_sym, |
| ascii_to_name ( $from_sym ), ascii_to_name ( $to_sym ); |
| } |
| printf "};\n"; |