1package X11::Xlib::Keymap; 2use strict; 3use warnings; 4use Carp; 5use X11::Xlib; 6use Scalar::Util 'weaken'; 7 8# All modules in dist share a version 9BEGIN { our $VERSION= $X11::Xlib::VERSION; } 10 11=head1 NAME 12 13X11::Xlib::Keymap - Object Oriented access to the X11 keymap 14 15=head1 DESCRIPTION 16 17For better or for worse, (hah, who am I kidding; worse) the X11 protocol gives 18applications the direct keyboard scan codes from the input device, and 19provides two tables to let applications do their own interpretation of the 20codes. The first table ("keymap") maps the scan codes (single byte) to one or 21more symbolic constants describing the glyph on the key. Choosing which of 22the several symbols to use depends on which "modifiers" are in effect. 23The second table is the "modifier map", which lists keys (scan codes, again) 24that are part of each of the eight modifier groups. Two modifier groups 25(Shift and Control) have constant meaning, but the rest require some creative 26logic to interpret. 27 28The keymap can't be used without the modifier map, but the modifier map can't 29be interpreted without the keymap, so both tables are rolled together into 30this object. 31 32While there are always less than 255 hardware scan codes, the set of device- 33independent KeySym codes is huge (including Unicode as a subset). 34Since the KeySym constants can't be practically exported by a Perl module, 35this API mostly tries to let you use the symbolic names of keys, or unicode 36characters. Translating KeySym names and characters to/from KeySym values is 37a client-side operation. 38 39=head1 ATTRIBUTES 40 41=head2 display 42 43Holds a weak-ref to the Display, used for the loading and saving operations. 44 45=cut 46 47sub display { 48 my $self= shift; 49 weaken( $self->{display}= shift ) if @_; 50 $self->{display}; 51} 52 53=head2 keymap 54 55Arrayref that maps from a key code (byte) to an arrayref of KeySyms. 56 57 [ 58 ... 59 [ $normal_key, $key_with_shift, $mode2_normal_key, $mode2_key_with_shift, ... ] 60 ... 61 ] 62 63Each KeyCode (up to 255 of them) is used as an index into the outer array, 64and the inner array's elements correspond to different shift/mode states, 65where "mode2" indicates a dynamic switch of key layout of some sort. 66Each key's array can contain additonal vendor-specific elements. 67 68This table is stored exactly as loaded from the X11 server. 69 70=head2 rkeymap 71 72A hashref mapping from the symbolic name of a key to its scan code. 73 74=cut 75 76sub keymap { 77 my $self= shift; 78 if (@_) { $self->{keymap}= shift; delete $self->{rkeymap}; } 79 $self->{keymap} ||= defined wantarray? $self->display->load_keymap : undef; 80} 81 82sub rkeymap { 83 my $self= shift; 84 $self->{rkeymap} ||= do { 85 my %rkmap; 86 my $kmap= $self->keymap; 87 for (my $i= $#$kmap; $i >= 0; $i--) { 88 next unless ref $kmap->[$i] eq 'ARRAY'; 89 defined $_ and $rkmap{$_}= $i for @{$kmap->[$i]}; 90 } 91 \%rkmap; 92 }; 93} 94 95=head2 modmap 96 97An arrayref of eight modifier groups, each element being the list 98of key codes that are part of that modifier. 99 100=head2 modmap_ident 101 102A hashref of logical modifier group names to array index within the modmap. 103On a modern US-English Linux desktop you will likely find: 104 105 shift => 0, 106 lock => 1, capslock => 1, 107 control => 2, 108 alt => 3, meta => 3, 109 numlock => 4, 110 win => 6, super => 6 111 mode => 7, 112 113but the numbers 3..7 can be re-purposed by your particular key layout. 114Note that X11 has a concept of "mode switching" where a modifier completely 115changes the meaning of every key. I think this is used by multi-lingual 116setups, but I've not tested/confirmed this. 117 118=cut 119 120sub modmap { 121 my $self= shift; 122 if (@_) { $self->{modmap}= shift; delete $self->{modmap_ident}; } 123 $self->{modmap} ||= defined wantarray? $self->display->XGetModifierMapping : undef; 124} 125 126sub modmap_ident { 127 my $self= shift; 128 $self->{modmap_ident} ||= do { 129 my $km= $self->keymap; 130 my $mm= $self->modmap; 131 my %ident= ( shift => 0, lock => 1, control => 2, mod1 => 3, mod2 => 4, mod3 => 5, mod4 => 6, mod5 => 7 ); 132 # "lock" is either 'capslock' or 'shiftlock' depending on keymap. 133 # for each member of lock, see if its member keys include XK_Caps_Lock 134 if (grep { $_ && $_ eq 'Caps_Lock' } map { ($_ && defined $km->[$_])? @{ $km->[$_] } : () } @{ $mm->[1] }) { 135 $ident{capslock}= 1; 136 # Else check for the XK_Shift_Lock 137 } elsif (grep { $_ && $_ eq 'Shift_Lock' } map { ($_ && defined $km->[$_])? @{ $km->[$_] } : () } @{ $mm->[1] }) { 138 $ident{shiftlock}= 1; 139 } 140 # Identify the group based on what keys belong to it 141 for (3..7) { 142 my @syms= grep { $_ } map { ($_ && defined $km->[$_])? @{ $km->[$_] } : () } @{ $mm->[$_] }; 143 $ident{alt}= $_ if grep { /^Alt/ } @syms; 144 $ident{meta}= $_ if grep { /^Meta/ } @syms; 145 $ident{hyper}= $_ if grep { /^Hyper/ } @syms; 146 $ident{numlock}= $_ if grep { $_ eq 'Num_Lock' } @syms; 147 $ident{mode}= $_ if grep { $_ eq 'Mode_switch' } @syms; 148 if (grep { /^Super/ } @syms) { 149 $ident{super}= $_; 150 $ident{win}= $_; 151 } 152 } 153 \%ident; 154 }; 155} 156 157=head1 METHODS 158 159=head2 new 160 161 my $keymap= X11::Xlib::Keymap->new(display => $dpy, %attrs); 162 163Initialize a keymap with the list of parameters. L</display> is required 164for any load/save operations. You can use most of the class with just the 165L</keymap> and L</modmap> attributes. 166 167=cut 168 169sub new { 170 my $class= shift; 171 my %args= (@_ == 1 and ref($_[0]) eq 'HASH')? %{ $_[0] } 172 : ((@_ & 1) == 0)? @_ 173 : croak "Expected hashref or even-length list"; 174 weaken( $args{display} ) if defined $args{display}; 175 bless \%args, $class; 176} 177 178=head2 find_keycode 179 180 my $keycode= $display->find_keycode( $key_sym_or_char ); 181 182Return a keycode for the parameter, which is either a KeySym name 183(L<XStringToKeysym|X11::Xlib/XStringToKeysym>) or a string holding a unicode character 184(L<char_to_keysym|X11::Xlib/char_to_keysym>). If more than one key code can map to 185the KeySym, this returns an arbitrary one of them. Returns undef if 186no matches were found. 187 188=head2 find_keysym 189 190 my $sym_name= $display->find_keysym( $key_code, $modifier_bits ); 191 my $sym_name= $display->find_keysym( $XKeyEvent ); 192 193Returns the symbolic name of a key, given its scan code and current modifier bits. 194 195For convenience, you can pass an L<XKeyEvent|X11::Xlib::XEvent/XKeyEvent> object. 196 197If you don't have modifier bits, pass 0. 198 199=cut 200 201sub find_keycode { 202 my ($self, $sym)= @_; 203 my $code= $self->rkeymap->{$sym}; 204 return $code if defined $code; 205 # If length==1, assume it is a character and then try the name and symbol value 206 if (length $sym == 1) { 207 my $sym_val= X11::Xlib::char_to_keysym($sym); 208 my $sym_name= X11::Xlib::XKeysymToString($sym_val); 209 $code= $self->rkeymap->{$sym_name} if $sym_val && defined $sym_name; 210 $code= $self->rkeymap->{$sym_val} if $sym_val && !defined $code; 211 } 212 # Else assume it is a symbol name and try to find the symbol character 213 else { 214 my $sym_val= X11::Xlib::XStringToKeysym($sym); 215 my $sym_char= X11::Xlib::keysym_to_char($sym_val); 216 $code= $self->rkeymap->{$sym_char} if $sym_val && defined $sym_char; 217 $code= $self->rkeymap->{$sym_val} if $sym_val && !defined $code; 218 } 219 return $code; 220} 221 222sub find_keysym { 223 my $self= shift; 224 my ($keycode, $modifiers)= 225 @_ == 1 && ref($_[0]) && ref($_[0])->can('pack')? ( $_[0]->keycode, $_[0]->state ) 226 : @_ == 2? @_ 227 : croak "Expected XKeyEvent or (code,modifiers)"; 228 my $km= $self->keymap->[$keycode] 229 or return undef; 230 # Shortcut 231 return $km->[0] unless $modifiers; 232 233 my $mod_id= $self->modmap_ident; 234 my $shift= $modifiers & 1; 235 my $capslock= $mod_id->{capslock} && ($modifiers & (1 << $mod_id->{capslock})); 236 my $shiftlock= $mod_id->{shiftlock} && ($modifiers & (1 << $mod_id->{shiftlock})); 237 my $numlock= $mod_id->{numlock} && ($modifiers & (1 << $mod_id->{numlock})); 238 my $mode= ($mod_id->{mode} && ($modifiers & (1 << $mod_id->{mode})))? 2 : 0; 239 # If numlock and Num keypad KeySym... 240 if ($numlock && ($km->[1] =~ /^KP_/)) { 241 return (($shift || $shiftlock)? $km->[$mode+0] : $km->[$mode+1]); 242 } elsif (!$shift && !$capslock && !$shiftlock) { 243 return $km->[$mode]; 244 } elsif (!$shift && $capslock) { 245 return uc($km->[$mode]); 246 } elsif ($shift && $capslock) { 247 return uc($km->[$mode+1]); 248 } else { # if ($shift || $shiftlock) 249 return $km->[$mode+1]; 250 } 251} 252 253=head2 keymap_reload 254 255 $keymap->keymap_reload(); # reload all keys 256 $keymap->keymap_reload(@codes); # reload range from min to max 257 258Reload all or a portion of the keymap. 259If C<@codes> are given, then only load from C<min(@codes)> to C<max(@codes)>. 260(The cost of loading the extra codes not in the list is assumed to be 261 less than the cost of multiple round trips to the server to pick only 262 the specific codes) 263 264=head2 keymap_save 265 266 $keymap->keymap_save(@codes); # Save changes to keymap (not modmap) 267 268Save any changes to L</keymap> back to the server. 269If C<@codes> are given, then only save from C<min(@codes)> to C<max(@codes)>. 270 271See L</save> to save both the L</keymap> and L</modmap>. 272 273=cut 274 275sub keymap_reload { 276 my ($self, @codes)= @_; 277 my ($min, $max)= @codes? ($codes[0], $codes[0]) : (0,255); 278 for (@codes) { $min= $_ if $_ < $min; $max= $_ if $_ > $max; } 279 my $km= $self->display->load_keymap(2, $min, $max); 280 splice(@{$self->keymap}, $min, $max-$min+1, @$km); 281 $self->keymap; 282} 283 284sub keymap_save { 285 my ($self, @codes)= @_; 286 my $km= $self->keymap; 287 my ($min, $max)= @codes? ($codes[0], $codes[0]) : (0, $#$km); 288 for (@codes) { $min= $_ if $_ < $min; $max= $_ if $_ > $max; } 289 $self->display->save_keymap($km, $min, $max); 290} 291 292=head2 modmap_sym_list 293 294 my @keysym_names= $display->modmap_sym_list( $modifier ); 295 296Get the default keysym names for all the keys bound to the C<$modifier>. 297Modifier is one of 'shift','lock','control','mod1','mod2','mod3','mod4','mod5', 298 'alt','meta','capslock','shiftlock','win','super','numlock','hyper'. 299 300Any modifier after mod5 in that list might not be defined for your keymap 301(and return an empty list, rather than an error). 302 303=cut 304 305sub modmap_sym_list { 306 my ($self, $modifier)= @_; 307 my $km= $self->keymap; 308 my $mod_id= $self->modmap_ident->{$modifier}; 309 return unless defined $mod_id; 310 return map { $km->[$_][0]? ( $km->[$_][0] ) : () } @{ $self->modmap->[$mod_id] }; 311} 312 313=head2 modmap_add_codes 314 315 my $n_added= $keymap->modmap_add_codes( $modifier, @key_codes ); 316 317Adds key codes (and remove duplicates) to one of the eight modifier groups. 318C<$modifier> is one of the values listed above. 319 320Throws an exception if C<$modifier> doesn't exist. 321Returns the number of key codes added. 322 323=head2 modmap_add_syms 324 325 my $n_added= $keymap->modmap_add_syms( $modifier, @keysym_names ); 326 327Convert keysym names to key codes and then call L</modmap_add_codes>. 328 329Warns if any keysym is not part of the current keyboard layout. 330Returns the number of key codes added. 331 332=cut 333 334sub modmap_add_codes { 335 my ($self, $modifier, @codes)= @_; 336 my $mod_id= $self->modmap_ident->{$modifier}; 337 croak "Modifier '$modifier' does not exist in this keymap" 338 unless defined $mod_id; 339 340 my $modcodes= $self->modmap->[$mod_id]; 341 my %seen= ( 0 => 1 ); # prevent duplicates, and remove nulls 342 @$modcodes= grep { !$seen{$_}++ } @$modcodes; 343 my $n= @$modcodes; 344 push @$modcodes, grep { !$seen{$_}++ } @codes; 345 return @$modcodes - $n; 346} 347 348sub modmap_add_syms { 349 my ($self, $modifier, @names)= @_; 350 my $rkeymap= $self->rkeymap; 351 my (@codes, @notfound); 352 for (@names) { 353 my $c= $rkeymap->{$_}; 354 defined $c? push(@codes, $c) : push(@notfound, $_); 355 } 356 croak "Key codes not found: ".join(' ', @notfound) 357 if @notfound; 358 $self->modmap_add_codes($modifier, @codes); 359} 360 361=head2 modmap_del_codes 362 363 my $n_removed= $keymap->modmap_del_syms( $modifier, @key_codes ); 364 365Removes the listed key codes from the named modifier, or from all modifiers 366if C<$modifier> is undef. 367 368Warns if C<$modifier> doesn't exist. 369Silently ignores key codes that don't exist in the modifiers. 370Returns number of key codes removed. 371 372=head2 modmap_del_syms 373 374 my $n_removed= $display->modmap_del_syms( $modifier, @keysym_names ); 375 376Convert keysym names to key codes and then call L</modmap_del_codes>. 377 378Warns if any keysym is not part of the current keyboard layout. 379Returns number of key codes removed. 380 381=cut 382 383sub modmap_del_codes { 384 my ($self, $modifier, @codes)= @_; 385 my $count= 0; 386 my %del= map { $_ => 1 } @codes; 387 if (defined $modifier) { 388 my $mod_id= $self->modmap_ident->{$modifier}; 389 croak "Modifier '$modifier' does not exist in this keymap" 390 unless defined $mod_id; 391 my $cur_codes= $self->modmap->[$mod_id]; 392 my $n= @$cur_codes; 393 @$cur_codes= grep { !$del{$_} } @$cur_codes; 394 $count= $n - @$cur_codes; 395 } 396 else { 397 for (@{ $self->modmap }) { 398 my $n= @$_; 399 @$_ = grep { !$del{$_} } @$_; 400 $count += $n - @$_; 401 } 402 } 403 return $count; 404} 405 406sub modmap_del_syms { 407 my ($self, $modifier, @names)= @_; 408 my $rkeymap= $self->rkeymap; 409 my (@codes, @notfound); 410 for (@names) { 411 my $c= $rkeymap->{$_}; 412 defined $c? push(@codes, $c) : push(@notfound, $_); 413 } 414 carp "Key codes not found: ".join(' ', @notfound) 415 if @notfound; 416 $self->modmap_del_codes($modifier, @codes); 417} 418 419=head2 modmap_save 420 421 $keymap->modmap_save; 422 423Call L<X11::Xlib/XSetModifierMapping> for the current L</modmap>. 424 425=head2 save 426 427 $keymap->save 428 429Save the full L</keymap> and L</modmap>. 430 431=cut 432 433sub modmap_save { 434 my ($self, $new_modmap)= @_; 435 $self->{modmap}= $new_modmap if defined $new_modmap; 436 $self->display->XSetModifierMapping($self->modmap); 437} 438 439sub save { 440 my $self= shift; 441 $self->keymap_save; 442 $self->modmap_save; 443} 444 4451; 446 447__END__ 448 449=head1 EXAMPLES 450 451=head2 Press a Key 452 453Suppose you've got an old DOS game that you're playing in Dosbox, and you 454find a neat trick to level up your character by pressing 'R' repeatedly. 455You might bang out a quick perl one-liner like this: 456 457 perl -e 'use X11::Xlib; $d= X11::Xlib->new; 458 $r= $d->keymap->find_keycode("R") or die "No R key?"; 459 while (1) { $d->fake_key($r, 1); $d->fake_key($r, 0); 460 $d->flush; sleep 1; }' 461 462=head1 AUTHOR 463 464Olivier Thauvin, E<lt>nanardon@nanardon.zarb.orgE<gt> 465 466Michael Conrad, E<lt>mike@nrdvana.netE<gt> 467 468=head1 COPYRIGHT AND LICENSE 469 470Copyright (C) 2009-2010 by Olivier Thauvin 471 472Copyright (C) 2017 by Michael Conrad 473 474This library is free software; you can redistribute it and/or modify 475it under the same terms as Perl itself, either Perl version 5.10.0 or, 476at your option, any later version of Perl 5 you may have available. 477 478=cut 479