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