modify-keys.pl revision f2e35a3a
1#!/usr/bin/env perl 2# $XTermId: modify-keys.pl,v 1.91 2020/11/15 16:43:35 tom Exp $ 3# ----------------------------------------------------------------------------- 4# this file is part of xterm 5# 6# Copyright 2019,2020 by Thomas E. Dickey 7# 8# All Rights Reserved 9# 10# Permission is hereby granted, free of charge, to any person obtaining a 11# copy of this software and associated documentation files (the 12# "Software"), to deal in the Software without restriction, including 13# without limitation the rights to use, copy, modify, merge, publish, 14# distribute, sublicense, and/or sell copies of the Software, and to 15# permit persons to whom the Software is furnished to do so, subject to 16# the following conditions: 17# 18# The above copyright notice and this permission notice shall be included 19# in all copies or substantial portions of the Software. 20# 21# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 22# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24# IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY 25# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28# 29# Except as contained in this notice, the name(s) of the above copyright 30# holders shall not be used in advertising or otherwise to promote the 31# sale, use or other dealings in this Software without prior written 32# authorization. 33# ----------------------------------------------------------------------------- 34# Print a table to illustrate the modifyOtherKeys resource choices. 35# 36# Some of the key combinations are unavailable unless certain translations 37# resource settings are suppressed. This command helped to verify those: 38# xterm -xrm '*omitTranslation:fullscreen,scroll-lock,shift-fonts' 39# 40# Additionally, a test-script was written to exercise xterm when the 41# "Allow SendEvents" feature is enabled, in combination with keys sent by 42# commands like this: 43# xdotool key --window XXX shift 2>/dev/null 44# 45# A curses application running in the target xterm showed the received data 46# in the terminfo-style format used in this script. 47 48# TODO factor in the backspace/delete meta/alt/escape resource-settings 49# TODO show keycodes via "xmodmap -pk" as alternative to xkbcomp 50# TODO show different sort-order (code, sym, xkb) 51# TODO use U+xxxx codepoints in keysymdef.h for rendering plain text 52# TODO optionally show 2**N, e.g., 4 (shift+control), 8 (shift+alt+control) or 16 (+meta) modifiers 53# TODO optionally show -c (cursor) -e (edit) -f (function-keys) with modifiers 54 55use strict; 56use warnings; 57 58use Getopt::Std; 59 60$| = 1; 61 62our ( $opt_d, $opt_h, $opt_k, $opt_K, $opt_l, $opt_m, $opt_o, $opt_u, $opt_v ); 63 64our $REPORT; 65our @headers; 66our @nolinks = (); 67our ( $xkb_layout, $xkb_model ); 68our $keyfile = "/usr/include/X11/keysymdef.h"; 69 70our @keyNames; # xkb's notion of key-names (undocumented) 71our %keySyms; # all keysyms, hashed by name 72our %keyCodes; # all keysyms, hashed by keycode 73our %uniCodes; # keysym Unicode values, hashed by keycode 74our %uniNames; # keysym Unicode descriptions, hashed by keycode 75our @keyTypes; # XkbKeyTypeRec 76our @symCache; # keysyms defined in keysymdef.h which might be used 77our @symMap; # index into symCache from keyNames 78our %keysUsed; # report derived from @symMap, etc. 79our %linkUsed; # check for uniqueness of html anchor-names 80 81our $MAXMODS = 8; # maximum for modifier-param 82our %Shifted; # map keycode to shifted-keycode seen by xterm 83 84# imitate /usr/include/X11/X.h 85our $ShiftMask = 1; 86our $LockMask = 2; 87our $ControlMask = 4; 88our $AltMask = 8; # assume mod1=alt 89our $MetaMask = 16; # assume mod2=meta 90 91our %editKeys = qw( 92 XK_Delete 1 93 XK_Prior 1 94 XK_Next 1 95 XK_Insert 1 96 XK_Find 1 97 XK_Select 1 98 XK_KP_Delete 1 99 XK_KP_Insert 1 100 XK_ISO_Left_Tab 1 101); 102 103sub failed($) { 104 printf STDERR "%s\n", $_[0]; 105 exit 1; 106} 107 108# prefer hex with 4 digit for hash keys 109sub toCode($) { 110 my $value = shift; 111 $value = sprintf( "0x%04x", $value ) if ( $value =~ /^\d+$/ ); 112 return $value; 113} 114 115sub codeOf($) { 116 my $value = shift; 117 my $result = 0; 118 &failed("missing keysym") unless ( defined $value ); 119 if ( $value =~ /^\d+$/ ) { 120 $result = $value; 121 } 122 elsif ( $value =~ /^0x[[:xdigit:]]+$/i ) { 123 $result = hex $value; 124 } 125 elsif ( $value =~ /^XK_/ ) { 126 $result = hex $keySyms{$value}; 127 } 128 else { 129 &failed("not a keysym: $value"); 130 } 131 return $result; 132} 133 134# macros from <X11/Xutil.h> 135 136sub IsKeypadKey($) { 137 my $code = &codeOf( $_[0] ); 138 my $result = ( ( $code >= &codeOf("XK_KP_Space") ) 139 and ( $code <= &codeOf("XK_KP_Equal") ) ) ? 1 : 0; 140 return $result; 141} 142 143sub IsPrivateKeypadKey($) { 144 my $code = &codeOf( $_[0] ); 145 my $result = 146 ( ( $code >= 0x11000000 ) and ( $code <= 0x1100FFFF ) ) ? 1 : 0; 147 return $result; 148} 149 150sub IsCursorKey($) { 151 my $code = &codeOf( $_[0] ); 152 my $result = 153 ( ( $code >= &codeOf("XK_Home") ) and ( $code < &codeOf("XK_Select") ) ) 154 ? 1 155 : 0; 156 return $result; 157} 158 159sub IsPFKey($) { 160 my $code = &codeOf( $_[0] ); 161 my $result = 162 ( ( $code >= &codeOf("XK_KP_F1") ) and ( $code <= &codeOf("XK_KP_F4") ) ) 163 ? 1 164 : 0; 165 return $result; 166} 167 168sub IsFunctionKey($) { 169 my $code = &codeOf( $_[0] ); 170 my $result = 171 ( ( $code >= &codeOf("XK_F1") ) and ( $code <= &codeOf("XK_F35") ) ) 172 ? 1 173 : 0; 174 return $result; 175} 176 177sub IsMiscFunctionKey($) { 178 my $code = &codeOf( $_[0] ); 179 my $result = 180 ( ( $code >= &codeOf("XK_Select") ) and ( $code <= &codeOf("XK_Break") ) ) 181 ? 1 182 : 0; 183 return $result; 184} 185 186sub IsModifierKey($) { 187 my $code = &codeOf( $_[0] ); 188 my $result = ( 189 ( 190 ( $code >= &codeOf("XK_Shift_L") ) 191 and ( $code <= &codeOf("XK_Hyper_R") ) 192 ) 193 or ( ( $code >= &codeOf("XK_ISO_Lock") ) 194 and ( $code <= &codeOf("XK_ISO_Level5_Lock") ) ) 195 or ( $code == &codeOf("XK_Mode_switch") ) 196 or ( $code == &codeOf("XK_Num_Lock") ) 197 ) ? 1 : 0; 198 return $result; 199} 200 201# debugging/reporting 202 203# Xutil.h's macros do not cover the whole range of special keys, which are not 204# actually printable. 205sub IsSpecialKey($) { 206 my $code = &codeOf( $_[0] ); 207 my $result = 208 ( ( $code >= 0xff00 ) and ( $code <= 0xffff ) ) 209 ? 1 210 : 0; 211 return $result; 212} 213 214sub VisibleChar($) { 215 my $ch = shift; 216 my $ord = ord $ch; 217 my $result = $ch; 218 if ( $ord < 32 ) { 219 if ( $ord == 8 ) { 220 $result = '\b'; 221 } 222 elsif ( $ord == 9 ) { 223 $result = '\t'; 224 } 225 elsif ( $ord == 10 ) { 226 $result = '\n'; 227 } 228 elsif ( $ord == 12 ) { 229 $result = '\f'; 230 } 231 elsif ( $ord == 13 ) { 232 $result = '\r'; 233 } 234 elsif ( $ord == 27 ) { 235 $result = '\E'; 236 } 237 else { 238 $result = sprintf( "^%c", $ord + 64 ); 239 } 240 } 241 elsif ( $ord == 32 ) { 242 $result = '\s'; 243 } 244 elsif ( $ord == 94 ) { 245 $result = '\^'; 246 } 247 elsif ( $ord == 92 ) { 248 $result = '\\\\'; 249 } 250 elsif ( $ord == 127 ) { 251 $result = '^?'; 252 } 253 return $result; 254} 255 256sub IsShift($$) { 257 my $code = shift; 258 my $state = shift; # 0/1=normal, 2=shift 259 my $result = 0; 260 if ( ( ( $state - 1 ) & 1 ) != 0 ) { 261 if ( $Shifted{$code} ) { 262 return 1 if ( $Shifted{$code} ne $code ); 263 } 264 } 265 return 0; 266} 267 268sub TypeOf($) { 269 my $code = &toCode( $_[0] ); 270 my $result = "other"; 271 $result = "special" if ( &IsSpecialKey($code) ); 272 $result = "keypad" if ( &IsKeypadKey($code) ); 273 $result = "*keypad" if ( &IsPrivateKeypadKey($code) ); 274 $result = "cursor" if ( &IsCursorKey($code) ); 275 $result = "pf-key" if ( &IsPFKey($code) ); 276 $result = "func-key" if ( &IsFunctionKey($code) ); 277 $result = "misc-key" if ( &IsMiscFunctionKey($code) ); 278 $result = "edit-key" if ( &IsEditFunctionKey($code) ); 279 $result = "modifier" if ( &IsModifierKey($code) ); 280 return $result; 281} 282 283sub KeyToS($$) { 284 my $code = &codeOf( $_[0] ); 285 my $state = $_[1]; 286 my $result = ""; 287 288 $code = &codeOf( $Shifted{ $_[0] } ) if ( &IsShift( $_[0], $state ) ); 289 my $type = &TypeOf( &toCode($code) ); 290 291 if ( $type ne "other" ) { 292 $result = ( $type eq "special" ) ? "-ignore-" : "?"; 293 } 294 elsif ($opt_u) { 295 $result = sprintf( "\\E[%d;%du", $code, $state ); 296 } 297 else { 298 $result = sprintf( "\\E[27;%d;%d~", $state, $code ); 299 } 300 return $result; 301} 302 303sub ParamToQ($) { 304 my $param = shift; 305 my $result = shift; 306 $param--; 307 $result .= ( $param & 1 ) ? 's' : '-'; 308 $result .= ( $param & 2 ) ? 'a' : '-'; 309 $result .= ( $param & 4 ) ? 'c' : '-'; 310 $result .= ( $param & 8 ) ? 'm' : '-'; 311 return $result; 312} 313 314sub ParamToS($) { 315 my $param = shift; 316 my $result = ""; 317 if ( $param-- > 1 ) { 318 $result .= "+Shift" if ( $param & 1 ); 319 $result .= "+Alt" if ( $param & 2 ); 320 $result .= "+Ctrl" if ( $param & 4 ); 321 $result .= "+Meta" if ( $param & 8 ); 322 $result =~ s/^\+//; 323 } 324 return $result; 325} 326 327sub StateToS($) { 328 my $state = shift; 329 my $result = ""; 330 $result .= "+Shift" if ( $state & $ShiftMask ); 331 $result .= "+Lock" if ( $state & $LockMask ); 332 $result .= "+Ctrl" if ( $state & $ControlMask ); 333 $result .= "+Alt" if ( $state & $AltMask ); 334 $result .= "+Meta" if ( $state & $MetaMask ); 335 $result =~ s/^\+//; 336 return $result; 337} 338 339# macros/functions in xterm's input.c 340 341sub Masked($$) { 342 my $value = shift; 343 my $mask = shift; 344 my $result = ( ($value) & ( ~($mask) ) ); 345 return $result; 346} 347 348sub IsPredefinedKey($) { 349 my $code = &codeOf( $_[0] ); 350 my $result = 0; 351 if ( $keySyms{"XK_ISO_Lock"} ) { 352 $result = 353 ( $code >= &codeOf("XK_ISO_Lock") and $code <= &codeOf("XK_Delete") ) 354 ? 1 355 : 0; 356 } 357 else { 358 $result = 359 ( $code >= &codeOf("XK_BackSpace") and $code <= &codeOf("XK_Delete") ) 360 ? 1 361 : 0; 362 } 363 return $result; 364} 365 366sub IsTabKey($) { 367 my $code = &codeOf( $_[0] ); 368 my $result = 0; 369 if ( $keySyms{"XK_ISO_Left_Tab"} ) { 370 $result = 371 ( $code == &codeOf("XK_Tab") || $code == &codeOf("XK_ISO_Left_Tab") ); 372 } 373 else { 374 $result = ( $code == &codeOf("XK_Tab") ) ? 1 : 0; 375 } 376 return $result; 377} 378 379sub IsEditFunctionKey($) { 380 my $code = shift; 381 my $result = 0; 382 if ( $keyCodes{$code} ) { 383 my $name = $keyCodes{$code}; 384 $result = 1 if ( $editKeys{$name} ); 385 } 386 return $result; 387} 388 389sub IS_CTRL($) { 390 my $code = &codeOf( $_[0] ); 391 my $result = ( $code < 32 || ( $code >= 0x7f && $code <= 0x9f ) ); 392 return $result; 393} 394 395sub IsControlInput($) { 396 my $code = &codeOf( $_[0] ); 397 my $result = 0; 398 $result = 1 if ( $code >= 0x40 && $code <= 0x7f ); 399 return $result; 400} 401 402sub IsControlOutput($) { 403 my $code = shift; 404 my $result = 0; 405 $result = 1 if &IS_CTRL($code); 406 return $result; 407} 408 409sub IsControlAlias($$) { 410 my $code = shift; 411 my $state = shift; 412 my $result = 0; 413 414 $code = &toCode($code); 415 $code = &toCode( &AliasedKey($code) ); 416 if ( hex $code < 256 ) { 417 $result = &IS_CTRL($code); 418 419 # In xterm, this function does not directly test evt_state, but relies 420 # upon kd.strbuf converted by Xutf8LookupString or XmbLookupString 421 # (ultimately in _XTranslateKeysym). 422 # 423 # See https://www.mail-archive.com/xorg@lists.x.org/msg04434.html 424 # 425 # xterm does its own special cases for XK_BackSpace 426 if ( $state & $ControlMask ) { 427 my $ch = chr &codeOf($code); 428 $result = 1 if ( &IsTabKey($code) ); 429 $result = 1 if ( &IsControlInput($code) ); 430 $result = 1 if ( $ch =~ /^[\/ 2-8]$/ ); 431 } 432 } 433 return $result; 434} 435 436sub computeMaskedModifier($$) { 437 my $state = shift; 438 my $mask = shift; 439 my $result = &xtermStateToParam( &Masked( $state, $mask ) ); 440 return $result; 441} 442 443sub xtermStateToParam($) { 444 my $state = shift; 445 my $modify_parm = 1; 446 447 $modify_parm += 1 if ( $state & $ShiftMask ); 448 $modify_parm += 2 if ( $state & $AltMask ); 449 $modify_parm += 4 if ( $state & $ControlMask ); 450 $modify_parm += 8 if ( $state & $MetaMask ); 451 $modify_parm = 0 if ( $modify_parm == 1 ); 452 return $modify_parm; 453} 454 455sub ParamToState($) { 456 my $modify_parm = shift; 457 my $state = 0; 458 $modify_parm-- if ( $modify_parm > 0 ); 459 $state |= $ShiftMask if ( $modify_parm & 1 ); 460 $state |= $AltMask if ( $modify_parm & 2 ); 461 $state |= $ControlMask if ( $modify_parm & 4 ); 462 $state |= $MetaMask if ( $modify_parm & 8 ); 463 return $state; 464} 465 466sub allowedCharModifiers($$) { 467 my $other_key = shift; 468 my $state = shift; 469 my $code = shift; 470 my $result = $state & ( $ShiftMask | $AltMask | $ControlMask | $MetaMask ); 471 472 # If modifyOtherKeys is off or medium (0 or 1), moderate its effects by 473 # excluding the common cases for modifiers. 474 if ( $other_key <= 1 ) { 475 my $sym = $keyCodes{$code}; 476 if ( &IsControlInput($code) 477 and &Masked( $result, $ControlMask ) == 0 ) 478 { 479 # These keys are already associated with the control-key 480 if ( $other_key == 0 ) { 481 $result &= ~$ControlMask; 482 } 483 } 484 elsif ( $sym eq "XK_Tab" || $sym eq "XK_Return" ) { 485 # 486 } 487 elsif ( &IsControlAlias( $code, $state ) ) { 488 489 # Things like "^_" work here... 490 if ( &Masked( $result, ( $ControlMask | $ShiftMask ) ) == 0 ) { 491 if ( $sym =~ /^XK_[34578]$/ or $sym eq "XK_slash" ) { 492 $result = 0 if ( $state == $ControlMask ); 493 } 494 else { 495 $result = 0; 496 } 497 } 498 } 499 elsif ( !&IsControlOutput($code) && !&IsPredefinedKey($code) ) { 500 501 # Printable keys are already associated with the shift-key 502 if ( !( $result & $ControlMask ) ) { 503 $result &= ~$ShiftMask; 504 } 505 } 506 507 # TODO: 508 # result = filterAltMeta(result, 509 # xw->work.meta_mods, 510 # TScreenOf(xw)->meta_sends_esc, kd); 511 # if (TScreenOf(xw)->alt_is_not_meta) { 512 # result = filterAltMeta(result, 513 # xw->work.alt_mods, 514 # TScreenOf(xw)->alt_sends_esc, kd); 515 # } 516 } 517 return $result; 518} 519 520# Some details are omitted (e.g., the backspace/delete toggle), but this gives 521# the general sense of the corresponding function in xterm's input.c 522sub ModifyOtherKeys($$$$) { 523 my $code = shift; # the keycode to test 524 my $other_key = shift; # "modifyOtherKeys" resource 525 my $modify_parm = shift; # 0=unmodified, 2=shift, etc 526 my $state = shift; # mask of modifiers, e.g., ControlMask 527 my $result = 0; 528 529 $modify_parm = 0 if ( $modify_parm == 1 ); 530 531 if ( &IsModifierKey($code) ) { 532 533 # xterm filters out bare modifiers (ignore) 534 } 535 elsif (&IsFunctionKey($code) 536 or &IsEditFunctionKey($code) 537 or &IsKeypadKey($code) 538 or &IsCursorKey($code) 539 or &IsPFKey($code) 540 or &IsMiscFunctionKey($code) 541 or &IsPrivateKeypadKey($code) ) 542 { 543 # Exclude the keys already covered by a modifier. 544 } 545 elsif ( $state > 0 ) { 546 my $sym = ""; 547 $sym = $keyCodes{$code} if ( $keyCodes{$code} ); 548 549 # TODO: 550 #if (IsBackarrowToggle(keyboard, kd->keysym, state)) { 551 # kd->keysym = XK_Delete; 552 # UIntClr(state, ControlMask); 553 #} 554 if ( !&IsPredefinedKey($code) ) { 555 $state = &allowedCharModifiers( $other_key, $state, $code ); 556 } 557 if ( $state != 0 ) { 558 if ( $other_key == 1 ) { 559 if ( $sym eq "XK_BackSpace" 560 or $sym eq "XK_Delete" ) 561 { 562 } 563 elsif ( $sym eq "XK_ISO_Left_Tab" ) { 564 $result = 1 565 if ( &computeMaskedModifier( $state, $ShiftMask ) ); 566 } 567 elsif ($sym eq "XK_Return" 568 or $sym eq "XK_Tab" ) 569 { 570 $result = ( $modify_parm != 0 ); 571 } 572 else { 573 if ( &IsControlInput($code) ) { 574 if ( $state == $ControlMask or $state == $ShiftMask ) { 575 $result = 0; 576 } 577 else { 578 $result = ( $modify_parm != 0 ); 579 } 580 } 581 elsif ( &IsControlAlias( $code, $state ) ) { 582 if ( $state == $ShiftMask ) { 583 $result = 0; 584 } 585 elsif ( &computeMaskedModifier( $state, $ControlMask ) ) 586 { 587 $result = 1; 588 } 589 } 590 else { 591 $result = 1; 592 } 593 } 594 if ($result) { # second case in xterm's Input() 595 $result = 0 596 if ( &allowedCharModifiers( $other_key, $state, $code ) == 597 0 ); 598 } 599 } 600 elsif ( $other_key == 2 ) { 601 if ( $sym eq "XK_BackSpace" ) { 602 603 # strip ControlMask as per IsBackarrowToggle() 604 $result = 1 605 if ( &computeMaskedModifier( $state, $ControlMask ) ); 606 } 607 elsif ( $sym eq "XK_Delete" ) { 608 609 $result = ( &xtermStateToParam($state) != 0 ); 610 } 611 elsif ( $sym eq "XK_ISO_Left_Tab" ) { 612 $result = 1 613 if ( &computeMaskedModifier( $state, $ShiftMask ) ); 614 } 615 elsif ( $sym eq "XK_Return" or $sym eq "XK_Tab" ) { 616 617 $result = ( $modify_parm != 0 ); 618 } 619 else { 620 if ( &IsControlInput($code) ) { 621 $result = 1; 622 } 623 elsif ( $state == $ShiftMask ) { 624 $result = ( $sym eq "XK_space" or $sym eq "XK_Return" ); 625 } 626 elsif ( &computeMaskedModifier( $state, $ShiftMask ) ) { 627 $result = 1; 628 } 629 } 630 } 631 } 632 } 633 return $result; 634} 635 636# See IsControlAlias. This handles some of the special cases where the keycode 637# seen or used by xterm is not the same as the actual keycode. 638sub AliasedKey($) { 639 my $code = &toCode( $_[0] ); 640 my $result = &codeOf($code); 641 my $sym = $keyCodes{$code}; 642 if ($sym) { 643 $result = 8 if ( $sym eq "XK_BackSpace" ); 644 $result = 9 if ( $sym eq "XK_Tab" ); 645 $result = 13 if ( $sym eq "XK_Return" ); 646 $result = 27 if ( $sym eq "XK_Escape" ); 647 } 648 return $result; 649} 650 651# Returns a short display for shift/control/alt modifiers applied to the 652# keycode to show which are affected by "modifyOtherKeys" at the given level in 653# $other_key 654sub CheckOtherKey($$) { 655 my $code = shift; 656 my $other_key = shift; 657 my $modified = 0; 658 my $result = ""; 659 for my $modify_parm ( 1 .. $MAXMODS ) { 660 my $state = &ParamToState($modify_parm); 661 if ( &ModifyOtherKeys( $code, $other_key, $modify_parm, $state ) ) { 662 $modified++; 663 $result .= "*"; 664 } 665 else { 666 $result .= "-"; 667 } 668 } 669 return $modified ? $result : "-(skip)-"; 670} 671 672# Use the return-string from CheckOtherKeys as a template for deciding which 673# keys to render as escape-sequences. 674sub ShowOtherKeys($$$) { 675 my $code = &AliasedKey( $_[0] ); 676 my $mode = $_[1]; # modifyOtherKeys: 0, 1 or 2 677 my $show = $_[2]; 678 my $type = &TypeOf( $_[0] ); 679 my @result; 680 681 # index for $show[] can be tested with a bit-mask: 682 # 1 = shift 683 # 2 = alt 684 # 4 = ctrl 685 # 8 = meta 686 for my $c ( 0 .. length($show) - 1 ) { 687 my $rc = substr( $show, $c, 1 ); 688 if ( $rc eq "*" ) { 689 $result[$c] = &KeyToS( &toCode($code), $c + 1 ); 690 } 691 elsif ( $type eq "other" or ( $type eq "special" and $code < 256 ) ) { 692 my $map = $code; 693 my $tmp = &toCode($code); 694 my $chr = chr hex $tmp; 695 my $shift = ( $c & 1 ); 696 my $cntrl = ( $c & 4 ); 697 698 # TODO - can this be simplified using xkb groups? 699 if ( $chr =~ /^[`345678]$/ and ( $c & 4 ) != 0 ) { 700 if ($shift) { 701 $map = 30 if ( $chr eq "`" ); 702 $map = ord "#" if ( $chr eq "3" ); 703 $map = ord '$' if ( $chr eq "4" ); 704 $map = ord "%" if ( $chr eq "5" ); 705 $map = 30 if ( $chr eq "6" ); 706 $map = ord "&" if ( $chr eq "7" ); 707 $map = ord "*" if ( $chr eq "8" ); 708 } 709 else { 710 $map = 0 if ( $chr eq "`" ); 711 $map = 27 if ( $chr eq "3" ); 712 $map = 28 if ( $chr eq "4" ); 713 $map = 29 if ( $chr eq "5" ); 714 $map = 30 if ( $chr eq "6" ); 715 $map = 31 if ( $chr eq "7" ); 716 $map = 127 if ( $chr eq "8" ); 717 } 718 } 719 else { 720 $map = &codeOf( $Shifted{$tmp} ) 721 if ( defined( $Shifted{$tmp} ) and $shift ); 722 if ($cntrl) { 723 if ( $chr =~ /^[190:<=>.,+*()'&%\$#"!]$/ ) { 724 725 # ignore 726 } 727 elsif ( $chr =~ /^[2]$/ ) { 728 $map = 0; 729 } 730 elsif ( $chr =~ /^[:;]$/ ) { 731 $map = 27 if ( $mode > 0 ); 732 } 733 elsif ( $chr eq '-' ) { 734 $map = 31 if ($shift); 735 } 736 elsif ( $chr eq '/' ) { 737 $map = $shift ? 127 : 31 if ( $mode == 0 ); 738 $map = 31 if ( not $shift and $mode == 1 ); 739 } 740 elsif ( $chr eq '?' ) { 741 $map = 127; 742 } 743 else { 744 $map = ( $code & 0x1f ) if ( $code < 128 ); 745 } 746 } 747 } 748 $result[$c] = &VisibleChar( chr $map ); 749 } 750 elsif ( $type eq "special" ) { 751 $result[$c] = "-ignore-"; 752 } 753 else { 754 $result[$c] = sprintf( "%d:%s", $c + 1, $type ); 755 } 756 } 757 return @result; 758} 759 760sub readfile($) { 761 my $data = shift; 762 my @data; 763 if ( open my $fp, $data ) { 764 @data = <$fp>; 765 close $fp; 766 chomp @data; 767 } 768 return @data; 769} 770 771sub readpipe($) { 772 my $cmd = shift; 773 return &readfile("$cmd 2>/dev/null |"); 774} 775 776sub trim($) { 777 my $text = shift; 778 $text =~ s/^\s+//; 779 $text =~ s/\s+$//; 780 $text =~ s/\s+/ /g; 781 return $text; 782} 783 784sub html_ref($) { 785 my $header = shift; 786 my $anchor = lc &trim($header); 787 $anchor =~ s/\s/_/g; 788 return $anchor; 789} 790 791sub rightarrow() { 792 return $opt_h ? "→" : "->"; 793} 794 795sub safe_html($) { 796 my $text = shift; 797 if ($opt_h) { 798 $text =~ s/\&/\&/g; 799 $text =~ s/\</\</g; 800 $text =~ s/\</\>/g; 801 if ( length($text) == 1 ) { 802 my $s = ""; 803 for my $n ( 0 .. length($text) - 1 ) { 804 my $ch = substr( $text, $n, 1 ); 805 my $ord = ord($ch); 806 $s .= sprintf( "&#%d;", $ord ) if ( $ord >= 128 ); 807 $s .= $ch if ( $ord < 128 ); 808 } 809 $text = $s; 810 } 811 } 812 return $text; 813} 814 815sub begin_report() { 816 if ($opt_o) { 817 open( $REPORT, '>', $opt_o ) or &failed("cannot open $opt_o"); 818 select $REPORT; 819 } 820 if ($opt_h) { 821 printf <<EOF 822<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"> 823 824<html> 825<head> 826 <meta name="generator" content="$0"> 827 828 <title>XTERM - Modified "Other" Keys ($xkb_layout-$xkb_model)</title> 829 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 830 <meta name="keywords" content="xterm, special keys"> 831 <meta name="description" content="This is an example of xterm's modifyOtherKeys feature"> 832</head> 833 834<body> 835EOF 836 ; 837 } 838} 839 840sub end_report() { 841 if ($opt_h) { 842 my $output = "output.html"; 843 $output = $opt_o if ($opt_o); 844 printf <<EOF 845<div class="nav"> 846 <ul> 847 <li class="nav-top"><a href="$output">(top)</a></li> 848EOF 849 ; 850 for my $h ( 0 .. $#headers ) { 851 printf "<li><a href=\"#%s\">%s</a></li>\n", 852 &html_ref( $headers[$h] ), $headers[$h]; 853 } 854 printf <<EOF 855 </ul> 856</div> 857EOF 858 ; 859 } 860 if ($opt_o) { 861 select STDOUT; 862 close $REPORT; 863 } 864} 865 866sub begin_section($) { 867 my $header = shift; 868 $headers[ $#headers + 1 ] = $header; 869 if ($opt_h) { 870 printf "<h2><a name=\"%s\">%s</a></h2>\n", &html_ref($header), $header; 871 } 872 else { 873 printf "\n"; 874 printf "%s:\n", $header; 875 } 876 printf STDERR "** %s\n", $header if ($opt_o); 877} 878 879sub begin_table() { 880 my $title = shift; 881 &begin_section($title); 882 if ($opt_h) { 883 printf "<table border=\"1\" summary=\"$title\">\n"; 884 } 885} 886 887sub end_table() { 888 if ($opt_h) { 889 printf "</table>\n"; 890 } 891} 892 893sub tt_cell($) { 894 my $text = shift; 895 return sprintf "<tt>%s</tt>", $text; 896} 897 898sub td_any($) { 899 my $text = shift; 900 return sprintf "<td>%s</td>", &tt_cell($text); 901} 902 903sub td_left($) { 904 my $text = shift; 905 return sprintf "<td align=\"left\">%s</td>", &tt_cell($text); 906} 907 908sub td_right($) { 909 my $text = shift; 910 return sprintf "<td align=\"right\">%s</td>", &tt_cell($text); 911} 912 913sub padded($$) { 914 my $size = shift; 915 my $text = shift; 916 $text = sprintf( "%*s", $size, $text ) if ( $size > 0 ); 917 $text = sprintf( "%-*s", $size, $text ) if ( $size < 0 ); 918 $text =~ s/ / /g if ($opt_h); 919 return $text; 920} 921 922sub print_head() { 923 my $argc = $#_; 924 if ($opt_h) { 925 printf "<tr>"; 926 for ( my $n = 0 ; $n <= $argc ; $n += 2 ) { 927 my $size = $_[$n]; 928 my $text = &padded( $size, $_[ $n + 1 ] ); 929 printf "<th>%s</th>", $text; 930 } 931 printf "</tr>\n"; 932 } 933 else { 934 for ( my $n = 0 ; $n <= $argc ; $n += 2 ) { 935 my $size = $_[$n]; 936 my $text = &padded( $size, $_[ $n + 1 ] ); 937 printf "%s", $text; 938 printf " " if ( $n < $argc ); 939 } 940 printf "\n"; 941 } 942} 943 944sub link_data($$) { 945 my $thisis = shift; 946 my $thatis = shift; 947 my $column = shift; 948 my $symbol = shift; 949 my %result; 950 $result{THISIS} = $thisis; # current table name 951 $result{THATIS} = $thatis; # name of target table for link 952 $result{COLUMN} = $column; # column counting from 0 953 $result{SYMBOL} = $symbol; 954 return \%result; 955} 956 957sub unique_link($$) { 958 my $thisis = shift; 959 my $symbol = shift; 960 my $unique = 0; 961 for my $n ( 0 .. length($symbol) - 1 ) { 962 $unique += ord substr( $symbol, $n, 1 ); 963 } 964 return sprintf( "%s:%s.%x", $thisis, $symbol, $unique ); 965} 966 967# print a row in the table, using pairs of lengths and strings: 968# + Right-align lengths greater than zero and pad; 969# + Left-align lengths less than zero, pad. 970# + For the special case of zero, just left align without padding. 971sub print_data() { 972 my $argc = $#_; 973 if ($opt_h) { 974 my @links = @{ $_[0] }; 975 printf "<tr>"; 976 my $col = 0; 977 for ( my $n = 1 ; $n <= $argc ; $n += 2 ) { 978 my $size = $_[$n]; 979 my $text = &padded( $size, $_[ $n + 1 ] ); 980 if ( $#links >= 0 ) { 981 for my $l ( 0 .. $#links ) { 982 my %obj = %{ $links[$l] }; # link_data 983 if ( $obj{COLUMN} == $col ) { 984 my $props = ""; 985 my $value = &unique_link( $obj{THISIS}, $obj{SYMBOL} ); 986 987 # The symbol-map from xkbcomp has duplicates because 988 # different modifier combinations can produce the same 989 # keysym. Since it appears that the slots that the 990 # user would expect are filled in first, just ignoring 991 # the duplicate works well enough. 992 if ( not $linkUsed{$value} ) { 993 $props .= " name=\"$value\""; 994 $linkUsed{$value} = 1; 995 } 996 $value = &unique_link( $obj{THATIS}, $obj{SYMBOL} ); 997 $props .= " href=\"#$value\""; 998 my $tail = $text; 999 $text =~ s/(\ )+$//; 1000 $tail = substr( $tail, length($text) ); 1001 $text = 1002 sprintf( "<a %s>%s</a>%s", $props, $text, $tail ); 1003 last; 1004 } 1005 } 1006 } 1007 printf "%s", 1008 ( $size > 0 ) ? &td_right($text) 1009 : ( $size == 0 ) ? &td_any($text) 1010 : &td_left($text); 1011 ++$col; 1012 } 1013 printf "</tr>\n"; 1014 } 1015 else { 1016 for ( my $n = 1 ; $n <= $argc ; $n += 2 ) { 1017 my $size = $_[$n]; 1018 my $text = &padded( $size, $_[ $n + 1 ] ); 1019 printf "%s", $text; 1020 printf " " if ( $n < $argc ); 1021 } 1022 printf "\n"; 1023 } 1024} 1025 1026sub begin_preformatted($) { 1027 my $title = shift; 1028 &begin_section($title); 1029 printf "<pre>\n" if ($opt_h); 1030} 1031 1032sub end_preformatted() { 1033 printf "</pre>\n" if ($opt_h); 1034} 1035 1036sub do_localectl($) { 1037 my $report = shift; 1038 my $cmd = "localectl status"; 1039 my @data = &readpipe($cmd); 1040 &begin_table("Output of $cmd") if ($report); 1041 for my $n ( 0 .. $#data ) { 1042 1043 # let command-line parameters override localectl output, for reports 1044 $data[$n] =~ s/^(\s+X11 Layout:\s+).*$/$1$opt_l/ if ($opt_l); 1045 $data[$n] =~ s/^(\s+X11 Model:\s+).*$/$1$opt_m/ if ($opt_m); 1046 my @fields = split /:\s*/, $data[$n]; 1047 next unless ( $#fields == 1 ); 1048 if ($report) { 1049 if ($opt_h) { 1050 printf "<tr>%s%s</tr>\n", 1051 &td_right( $fields[0] ), 1052 &td_left( $fields[1] ); 1053 } 1054 else { 1055 printf "%s\n", $data[$n]; 1056 } 1057 } 1058 $xkb_layout = $fields[1] if ( $fields[0] =~ /x11 layout/i ); 1059 $xkb_model = $fields[1] if ( $fields[0] =~ /x11 model/i ); 1060 } 1061 if ($report) { 1062 &end_table; 1063 } 1064} 1065 1066sub do_keysymdef() { 1067 my @data = &readfile($keyfile); 1068 my $lenSyms = 0; 1069 for my $n ( 0 .. $#data ) { 1070 my $value = &trim( $data[$n] ); 1071 next unless ( $value =~ /^#define\s+XK_/ ); 1072 my $name = $value; 1073 $name =~ s/^#define\s+//; 1074 $value = $name; 1075 $name =~ s/\s.*//; 1076 $value =~ s/^[^\s]+\s+//; 1077 my $note = $value; 1078 $value =~ s/\s.*//; 1079 1080 $note =~ s/^[^\s]+\s*//; 1081 if ( $note !~ /\b(alias|deprecated)\b/ ) { 1082 1083 if ( $note =~ /\/*.*\bU\+[[:xdigit:]]{4,8}.*\*\// ) { 1084 next if ( $note =~ /\(U\+/ ); 1085 my $code = $note; 1086 $code =~ s/^.*\bU\+([[:xdigit:]]+).*/$1/; 1087 $note =~ s/^\/\*[([:space:]]*//; 1088 $note =~ s/[)[:space:]]*\*\/$//; 1089 $uniNames{$value} = $note; 1090 $uniCodes{$value} = hex $code; 1091 } 1092 } 1093 $lenSyms = length($name) if ( length($name) > $lenSyms ); 1094 $value = lc $value; 1095 $keySyms{$name} = $value; 1096 $keyCodes{$value} = $name unless ( $keyCodes{$value} ); 1097 printf "keySyms{$name} = '$value', keyCodes{$value} = $name\n" 1098 if ($opt_d); 1099 } 1100 my $tmpfile = $keyfile; 1101 $tmpfile =~ s/^.*\///; 1102 &begin_table("Symbols from $tmpfile"); 1103 my @keys = keys %keySyms; 1104 &print_data( \@nolinks, 5, sprintf( "%d", $#keys ), 1105 0, sprintf( "keysyms are defined (longest %d)", $lenSyms ) ); 1106 @keys = keys %keyCodes; 1107 &print_data( \@nolinks, 5, sprintf( "%d", $#keys ), 1108 0, "keycodes are defined" ); 1109 @keys = keys %uniCodes; 1110 &print_data( \@nolinks, 5, sprintf( "%d", $#keys ), 1111 0, "keycodes are equated to Unicode" ); 1112 &end_table; 1113} 1114 1115# For what it's worth, there is a C library (xkbfile) which could be used, 1116# but there is no documentation and would not actually solve the problem at 1117# hand. 1118# 1119# setxkbmap -model pc105 -layout us -print | xkbcomp - -C -o - 1120sub do_xkbcomp() { 1121 my @data = 1122 &readpipe( "setxkbmap " 1123 . "-model $xkb_model " 1124 . "-layout $xkb_layout -print " 1125 . "| xkbcomp - -C -o -" ); 1126 my $state = -1; 1127 my $type = {}; 1128 for my $n ( 0 .. $#data ) { 1129 if ( $data[$n] =~ /static.*\bkeyNames\[.*{/ ) { 1130 $state = 0; 1131 next; 1132 } 1133 if ( $data[$n] =~ /static.*\bsymCache\[.*{/ ) { 1134 $state = 1; 1135 next; 1136 } 1137 if ( $data[$n] =~ /static.*\bsymMap\[.*{/ ) { 1138 $state = 2; 1139 next; 1140 } 1141 if ( $data[$n] =~ /static.*\bdflt_types\[.*{/ ) { 1142 $state = 3; 1143 next; 1144 } 1145 if ( $state >= 0 ) { 1146 if ( $data[$n] =~ /^\s*};/ ) { 1147 printf "# %s\n", $data[$n] if ($opt_d); 1148 $state = -1; 1149 next; 1150 } 1151 printf "* %s\n", $data[$n] if ($opt_d); 1152 } 1153 1154 # parse data in "keyNames[NUM_KEYS]" 1155 if ( $state == 0 ) { 1156 my $text = $data[$n]; 1157 my $name; 1158 while ( $text =~ /^.*".*".*$/ ) { 1159 $text =~ s/^[^"]*//; 1160 $name = $text; 1161 $name =~ s/"\s+}.*//; 1162 $name =~ s/"//g; 1163 $keyNames[ $#keyNames + 1 ] = $name; 1164 printf "keyNames[%d] = '%s'\n", $#keyNames, 1165 $keyNames[$#keyNames] 1166 if ($opt_v); 1167 $text =~ s/^"[^"]*"//; 1168 } 1169 } 1170 1171 # parse data in "symCache[NUM_SYMBOLS]" 1172 elsif ( $state == 1 ) { 1173 my $text = $data[$n]; 1174 my $name; 1175 while ( $text =~ /[[:alnum:]_]/ ) { 1176 $text =~ s/^[^[[:alnum:]_]*//; 1177 $name = $text; 1178 $name =~ s/[^[[:alnum:]_].*//; 1179 $symCache[ $#symCache + 1 ] = $name; 1180 printf "symCache[%d] = %s\n", $#symCache, $symCache[$#symCache] 1181 if ($opt_v); 1182 $text =~ s/^[[:alnum:]_]+//; 1183 } 1184 } 1185 1186 # parse data in "symMap[NUM_KEYS]" 1187 elsif ( $state == 2 ) { 1188 my $text = $data[$n]; 1189 my $code; 1190 while ( $text =~ /[{].*[}]/ ) { 1191 my %obj; 1192 $text =~ s/^[^{]*[{]\s*//; 1193 $code = $text; 1194 $code =~ s/[^[[:alnum:]].*//; 1195 $text =~ s/[[:alnum:]]+\s*,\s*//; 1196 $obj{TYPE} = $code; # KeyType 1197 my %tmp = %{ $keyTypes[$code] }; 1198 $tmp{USED} += 1; 1199 $keyTypes[$code] = \%tmp; 1200 $code = $text; 1201 $code =~ s/[^[[:alnum:]].*//; 1202 $text =~ s/[[:alnum:]]+\s*,\s*//; 1203 $obj{USED} = hex $code; # 0/1 for used/unused 1204 $code = $text; 1205 $code =~ s/[^[[:alnum:]].*//; 1206 $obj{CODE} = $code; # index in symCache[] 1207 $text =~ s/[[:alnum:]]+\s*//; 1208 $symMap[ $#symMap + 1 ] = \%obj; 1209 printf "symMap[%d] = {%d,%d,%d}\n", $#symMap, $obj{TYPE}, 1210 $obj{USED}, $obj{CODE} 1211 if ($opt_v); 1212 } 1213 } 1214 1215 # parse data in "dflt_types[]" 1216 elsif ( $state == 3 ) { 1217 my $text = &trim( $data[$n] ); 1218 if ( $text =~ /^\s*[}](,)?$/ ) { 1219 $type->{USED} = 0; 1220 $keyTypes[ $#keyTypes + 1 ] = $type; 1221 $type = {}; 1222 } 1223 elsif ( $text =~ /^\d+,$/ ) { 1224 $text =~ s/,//; 1225 $type->{SIZE} = $text; 1226 } 1227 elsif ( $text =~ /^None,\s+lnames_[[:alnum:]_]+$/ ) { 1228 $text =~ s/^None,\s+lnames_//; 1229 $type->{NAME} = $text; 1230 } 1231 elsif ( $text =~ /^\s*[{].*[}],\s*$/ ) { 1232 $text =~ s/^\s*[{]\s*([^,]+),.*/$1/; 1233 $type->{MODS} = $text; 1234 } 1235 } 1236 } 1237 &begin_table("Summary from xkbcomp"); 1238 &print_data( \@nolinks, 5, sprintf( "%d", $#keyNames + 1 ), 0, "keyNames" ); 1239 &print_data( \@nolinks, 5, sprintf( "%d", $#keyTypes + 1 ), 0, "keyTypes" ); 1240 &print_data( \@nolinks, 5, sprintf( "%d", $#symCache + 1 ), 0, "symCache" ); 1241 &print_data( \@nolinks, 5, sprintf( "%d", $#symMap + 1 ), 0, "symMap" ); 1242 &end_table; 1243} 1244 1245# Report keysymdef.h without the deprecated stuff, and sorted by keycode. 1246sub report_keysymdef() { 1247 &begin_table("Key symbols"); 1248 &print_head( 0, "Code", 0, "Category", 0, "Symbol" ); 1249 1250 # sort by numeric keycode rather than string 1251 my @keyCodes = keys %keyCodes; 1252 my @sortCodes; 1253 for my $c ( 0 .. $#keyCodes ) { 1254 $sortCodes[$c] = sprintf "%08X", hex $keyCodes[$c]; 1255 } 1256 @sortCodes = sort @sortCodes; 1257 for my $c ( 0 .. $#sortCodes ) { 1258 my $code = sprintf( "0x%04x", hex $sortCodes[$c] ); 1259 my $sym = $keyCodes{$code}; 1260 &print_data( \@nolinks, 9, $code, -8, &TypeOf($code), 0, $sym ); 1261 } 1262 &end_table; 1263} 1264 1265sub report_key_types() { 1266 &begin_table("Key types"); 1267 &print_head( 5, "Type", 5, "Used", 5, "Levels", 0, "Name" ); 1268 for my $t ( 0 .. $#keyTypes ) { 1269 my %type = %{ $keyTypes[$t] }; 1270 next if ( $type{USED} == 0 and not $opt_v ); 1271 &print_data( 1272 \@nolinks, 5, sprintf( "%d", $t ), 5, 1273 sprintf( "%d", $type{USED} ), 5, sprintf( "%d", $type{SIZE} ), 0, 1274 $type{NAME} 1275 ); 1276 } 1277 &end_table; 1278} 1279 1280sub report_modified_keys() { 1281 my @codes = sort keys %keysUsed; 1282 my $width = 14; 1283 &begin_table("Other modifiable keycodes"); 1284 &print_head( 1285 0, "Code", 0, "Symbol", 0, "Actual", 1286 -$width, "Mode 0", -$width, "Mode 1", -$width, "Mode 2" 1287 ); 1288 $width = 0 if ($opt_h); 1289 for my $c ( 0 .. $#codes ) { 1290 next unless ( $codes[$c] ne "" ); 1291 my @links; 1292 my $sym = $keysUsed{ $codes[$c] }; 1293 $links[0] = &link_data( "summary", "detailed", 1, $sym ); 1294 &print_data( 1295 \@links, 1296 6, $codes[$c], # 1297 -20, $keysUsed{ $codes[$c] }, # 1298 -6, sprintf( "%d", hex $codes[$c] ), # 1299 -$width, &CheckOtherKey( $codes[$c], 0 ), # 1300 -$width, &CheckOtherKey( $codes[$c], 1 ), # 1301 -$width, &CheckOtherKey( $codes[$c], 2 ) 1302 ); 1303 } 1304 &end_table; 1305 &begin_preformatted("Modify-param to/from state"); 1306 for my $param ( 0 .. $MAXMODS ) { 1307 my $state = &ParamToState($param); 1308 my $check = &xtermStateToParam($state); 1309 printf " PARAM %d %s %d %s %d (%s)\n", $param, &rightarrow, # 1310 $state, &rightarrow, # 1311 $check, &ParamToS($param); 1312 } 1313 &end_preformatted; 1314 &begin_preformatted("State to/from modify-param"); 1315 for my $state ( 0 .. 15 ) { 1316 my $param = &xtermStateToParam($state); 1317 my $check = &ParamToState($param); 1318 printf " STATE %d %s %d %s %d (%s)\n", # 1319 $state, &rightarrow, # 1320 $param, &rightarrow, # 1321 $check, &StateToS($state); 1322 } 1323 &end_preformatted; 1324} 1325 1326# Make a report showing user- and program-modes. 1327sub report_otherkey_escapes() { 1328 my @codes = sort keys %keysUsed; 1329 my $width = 14; 1330 &begin_table("Other modified-key escapes"); 1331 &print_head( 1332 0, "Code", 0, "Symbol", 0, "Actual", 1333 -$width, "Mode 0", -$width, "Mode 1", -$width, "Mode 2" 1334 ); 1335 $width = 0 if ($opt_h); 1336 for my $c ( 0 .. $#codes ) { 1337 next unless ( $codes[$c] ne "" ); 1338 my $level0 = &CheckOtherKey( $codes[$c], 0 ); 1339 my $level1 = &CheckOtherKey( $codes[$c], 1 ); 1340 my $level2 = &CheckOtherKey( $codes[$c], 2 ); 1341 my @level0 = &ShowOtherKeys( $codes[$c], 0, $level0 ); 1342 my @level1 = &ShowOtherKeys( $codes[$c], 1, $level1 ); 1343 my @level2 = &ShowOtherKeys( $codes[$c], 2, $level2 ); 1344 my @links; 1345 my $sym = $keysUsed{ $codes[$c] }; 1346 $links[0] = &link_data( "detailed", "symmap", 1, $sym ); 1347 &print_data( 1348 \@links, # 1349 -6, $codes[$c], # 1350 -20, $keysUsed{ $codes[$c] }, # 1351 -6, sprintf( "%d", hex $codes[$c] ), # 1352 -$width, $level0, # 1353 -$width, $level1, # 1354 -$width, $level2 1355 ); 1356 1357 for my $r ( 0 .. $#level0 ) { 1358 &print_data( 1359 \@nolinks, # 1360 -6, &ParamToQ( $r + 1 ), # 1361 -20, "", # 1362 -6, "", # 1363 -$width, &safe_html( $level0[$r] ), # 1364 -$width, &safe_html( $level1[$r] ), # 1365 -$width, &safe_html( $level2[$r] ) 1366 ); 1367 } 1368 } 1369 &end_table; 1370} 1371 1372sub report_keys_used() { 1373 &begin_table("Key map"); 1374 &print_head( 1375 5, "Type", # 1376 0, "Level", # 1377 0, "Name", # 1378 6, "Code", # 1379 0, 1380 "Symbol" 1381 ); 1382 for my $m ( 0 .. $#symMap ) { 1383 my %obj = %{ $symMap[$m] }; 1384 next unless ( $obj{USED} ); 1385 my $sym = $symCache[ $obj{CODE} ]; 1386 next if ( $sym eq "NoSymbol" ); 1387 my $code = ""; 1388 $code = $keySyms{$sym} if ( $keySyms{$sym} ); 1389 next if ( $code eq "" ); 1390 $keysUsed{$code} = $sym; 1391 my %type = %{ $keyTypes[ $obj{TYPE} ] }; 1392 my @links; 1393 $links[0] = &link_data( "symmap", "summary", 4, $sym ); 1394 &print_data( 1395 \@links, 1396 5, sprintf( "%d", $obj{TYPE} ), # 1397 5, sprintf( "1/%d", $type{SIZE} ), # 1398 -4, $keyNames[$m], # 1399 6, $code, # 1400 0, $sym 1401 ); 1402 1403 my $base = $code; 1404 $Shifted{$code} = $code unless ( $Shifted{$code} ); 1405 1406 for my $t ( 1 .. $type{SIZE} - 1 ) { 1407 $sym = $symCache[ $obj{CODE} + $t ]; 1408 if ( $keySyms{$sym} ) { 1409 $code = $keySyms{$sym}; 1410 $keysUsed{$code} = $sym; 1411 $links[0] = &link_data( "symmap", "summary", 4, $sym ); 1412 } 1413 else { 1414 $code = ""; 1415 @links = (); 1416 } 1417 &print_data( 1418 \@links, 1419 5, "", # 1420 5, sprintf( "%d/%d", $t + 1, $type{SIZE} ), # 1421 -4, "", # 1422 6, $code, # 1423 0, $sym 1424 ); 1425 @links = (); 1426 1427 # The shift-modifier could be used in custom groups, but the only 1428 # built-in ones that appear relevant are TWO_LEVEL and ALPHABETIC, 1429 # which have two levels. This records the shifted code for a given 1430 # base. 1431 if ( $type{SIZE} == 2 1432 and $type{MODS} 1433 and index( $type{MODS}, "ShiftMask" ) >= 0 ) 1434 { 1435 if ( $t == 1 ) { 1436 $Shifted{$base} = $code; 1437 } 1438 elsif ( not $Shifted{$code} ) { 1439 $Shifted{$code} = $code; 1440 } 1441 } 1442 } 1443 } 1444 &end_table; 1445} 1446 1447sub KeyClasses($) { 1448 my $hex = shift; 1449 my $alias = &IsControlAlias( $hex, $ControlMask ) ? "alias" : ""; 1450 my $cntrl = &IS_CTRL($hex) ? "cntrl" : ""; 1451 my $ctl_i = &IsControlInput($hex) ? "ctl_i" : ""; 1452 my $ctl_o = &IsControlOutput($hex) ? "ctl_o" : ""; 1453 my $this = sprintf( "%-5s %-5s %-5s %-5s %-8s", 1454 $alias, $cntrl, $ctl_i, $ctl_o, &TypeOf($hex) ); 1455} 1456 1457sub report_key_classes() { 1458 &begin_table("Keycode-classes"); 1459 my $base = -1; 1460 my $last = ""; 1461 my $next = 65535; 1462 my $form = " [%8s .. %-8s] %s\n"; 1463 &print_head( 0, "First", 0, "Last", 0, "Classes" ) if ($opt_h); 1464 for my $code ( 0 .. $next ) { 1465 my $hex = &toCode($code); 1466 my $this = &KeyClasses($hex); 1467 if ( $base < 0 ) { 1468 $base = 0; 1469 $last = $this; 1470 } 1471 elsif ( $this ne $last ) { 1472 printf $form, &toCode($base), &toCode( $code - 1 ), $last 1473 unless ($opt_h); 1474 &print_data( \@nolinks, 0, &toCode($base), 0, &toCode( $code - 1 ), 1475 0, $last ) 1476 if ($opt_h); 1477 $base = $code; 1478 $last = $this; 1479 } 1480 } 1481 printf $form, &toCode($base), &toCode($next), $last unless ($opt_h); 1482 &print_data( \@nolinks, 0, &toCode($base), 0, &toCode($next), 0, $last ) 1483 if ($opt_h); 1484 &end_table; 1485} 1486 1487sub main::HELP_MESSAGE() { 1488 printf STDERR <<EOF 1489Usage: $0 [options] 1490 1491Options: 1492 -d debug 1493 -h write report with html-markup 1494 -k dump keysyms/keycodes from $keyfile 1495 -K dump keycode-classes 1496 -l XXX use XXX for Xkb layout (default $xkb_layout) 1497 -m XXX use XXX for Xkb model (default $xkb_model) 1498 -o XXX write report to the file XXX. 1499 -u use CSI u format for escapes 1500 -v verbose 1501 1502EOF 1503 ; 1504 exit 1; 1505} 1506 1507binmode( STDOUT, ":utf8" ); 1508 1509&do_localectl(0); 1510 1511$Getopt::Std::STANDARD_HELP_VERSION = 1; 1512&getopts('dhKkl:m:o:uv') || &main::HELP_MESSAGE; 1513$opt_v = 1 if ($opt_d); 1514 1515&begin_report; 1516 1517&do_localectl(1); 1518 1519$xkb_layout = $opt_l if ($opt_l); 1520$xkb_model = $opt_m if ($opt_m); 1521 1522&do_keysymdef; 1523&report_keysymdef if ($opt_k); 1524 1525&do_xkbcomp; 1526 1527&report_key_classes if ($opt_K); 1528 1529&report_key_types; 1530&report_keys_used; 1531&report_modified_keys; 1532&report_otherkey_escapes; 1533 1534&end_report; 1535 15361; 1537