1#!/usr/bin/env perl 2# $XTermId: modify-keys.pl,v 1.92 2022/11/24 12:43:26 tom Exp $ 3# ----------------------------------------------------------------------------- 4# this file is part of xterm 5# 6# Copyright 2019-2020,2022 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_Escape" 616 or $sym eq "XK_Return" 617 or $sym eq "XK_Tab" ) 618 { 619 620 $result = ( $modify_parm != 0 ); 621 } 622 else { 623 if ( &IsControlInput($code) ) { 624 $result = 1; 625 } 626 elsif ( $state == $ShiftMask and $sym eq "XK_space" ) { 627 $result = 1; 628 } 629 elsif ( &computeMaskedModifier( $state, $ShiftMask ) ) { 630 $result = 1; 631 } 632 } 633 } 634 } 635 } 636 return $result; 637} 638 639# See IsControlAlias. This handles some of the special cases where the keycode 640# seen or used by xterm is not the same as the actual keycode. 641sub AliasedKey($) { 642 my $code = &toCode( $_[0] ); 643 my $result = &codeOf($code); 644 my $sym = $keyCodes{$code}; 645 if ($sym) { 646 $result = 8 if ( $sym eq "XK_BackSpace" ); 647 $result = 9 if ( $sym eq "XK_Tab" ); 648 $result = 13 if ( $sym eq "XK_Return" ); 649 $result = 27 if ( $sym eq "XK_Escape" ); 650 } 651 return $result; 652} 653 654# Returns a short display for shift/control/alt modifiers applied to the 655# keycode to show which are affected by "modifyOtherKeys" at the given level in 656# $other_key 657sub CheckOtherKey($$) { 658 my $code = shift; 659 my $other_key = shift; 660 my $modified = 0; 661 my $result = ""; 662 for my $modify_parm ( 1 .. $MAXMODS ) { 663 my $state = &ParamToState($modify_parm); 664 if ( &ModifyOtherKeys( $code, $other_key, $modify_parm, $state ) ) { 665 $modified++; 666 $result .= "*"; 667 } 668 else { 669 $result .= "-"; 670 } 671 } 672 return $modified ? $result : "-(skip)-"; 673} 674 675# Use the return-string from CheckOtherKeys as a template for deciding which 676# keys to render as escape-sequences. 677sub ShowOtherKeys($$$) { 678 my $code = &AliasedKey( $_[0] ); 679 my $mode = $_[1]; # modifyOtherKeys: 0, 1 or 2 680 my $show = $_[2]; 681 my $type = &TypeOf( $_[0] ); 682 my @result; 683 684 # index for $show[] can be tested with a bit-mask: 685 # 1 = shift 686 # 2 = alt 687 # 4 = ctrl 688 # 8 = meta 689 for my $c ( 0 .. length($show) - 1 ) { 690 my $rc = substr( $show, $c, 1 ); 691 if ( $rc eq "*" ) { 692 $result[$c] = &KeyToS( &toCode($code), $c + 1 ); 693 } 694 elsif ( $type eq "other" or ( $type eq "special" and $code < 256 ) ) { 695 my $map = $code; 696 my $tmp = &toCode($code); 697 my $chr = chr hex $tmp; 698 my $shift = ( $c & 1 ); 699 my $cntrl = ( $c & 4 ); 700 701 # TODO - can this be simplified using xkb groups? 702 if ( $chr =~ /^[`345678]$/ and ( $c & 4 ) != 0 ) { 703 if ($shift) { 704 $map = 30 if ( $chr eq "`" ); 705 $map = ord "#" if ( $chr eq "3" ); 706 $map = ord '$' if ( $chr eq "4" ); 707 $map = ord "%" if ( $chr eq "5" ); 708 $map = 30 if ( $chr eq "6" ); 709 $map = ord "&" if ( $chr eq "7" ); 710 $map = ord "*" if ( $chr eq "8" ); 711 } 712 else { 713 $map = 0 if ( $chr eq "`" ); 714 $map = 27 if ( $chr eq "3" ); 715 $map = 28 if ( $chr eq "4" ); 716 $map = 29 if ( $chr eq "5" ); 717 $map = 30 if ( $chr eq "6" ); 718 $map = 31 if ( $chr eq "7" ); 719 $map = 127 if ( $chr eq "8" ); 720 } 721 } 722 else { 723 $map = &codeOf( $Shifted{$tmp} ) 724 if ( defined( $Shifted{$tmp} ) and $shift ); 725 if ($cntrl) { 726 if ( $chr =~ /^[190:<=>.,+*()'&%\$#"!]$/ ) { 727 728 # ignore 729 } 730 elsif ( $chr =~ /^[2]$/ ) { 731 $map = 0; 732 } 733 elsif ( $chr =~ /^[:;]$/ ) { 734 $map = 27 if ( $mode > 0 ); 735 } 736 elsif ( $chr eq '-' ) { 737 $map = 31 if ($shift); 738 } 739 elsif ( $chr eq '/' ) { 740 $map = $shift ? 127 : 31 if ( $mode == 0 ); 741 $map = 31 if ( not $shift and $mode == 1 ); 742 } 743 elsif ( $chr eq '?' ) { 744 $map = 127; 745 } 746 else { 747 $map = ( $code & 0x1f ) if ( $code < 128 ); 748 } 749 } 750 } 751 $result[$c] = &VisibleChar( chr $map ); 752 } 753 elsif ( $type eq "special" ) { 754 $result[$c] = "-ignore-"; 755 } 756 else { 757 $result[$c] = sprintf( "%d:%s", $c + 1, $type ); 758 } 759 } 760 return @result; 761} 762 763sub readfile($) { 764 my $data = shift; 765 my @data; 766 if ( open my $fp, $data ) { 767 @data = <$fp>; 768 close $fp; 769 chomp @data; 770 } 771 return @data; 772} 773 774sub readpipe($) { 775 my $cmd = shift; 776 return &readfile("$cmd 2>/dev/null |"); 777} 778 779sub trim($) { 780 my $text = shift; 781 $text =~ s/^\s+//; 782 $text =~ s/\s+$//; 783 $text =~ s/\s+/ /g; 784 return $text; 785} 786 787sub html_ref($) { 788 my $header = shift; 789 my $anchor = lc &trim($header); 790 $anchor =~ s/\s/_/g; 791 return $anchor; 792} 793 794sub rightarrow() { 795 return $opt_h ? "→" : "->"; 796} 797 798sub safe_html($) { 799 my $text = shift; 800 if ($opt_h) { 801 $text =~ s/\&/\&/g; 802 $text =~ s/\</\</g; 803 $text =~ s/\</\>/g; 804 if ( length($text) == 1 ) { 805 my $s = ""; 806 for my $n ( 0 .. length($text) - 1 ) { 807 my $ch = substr( $text, $n, 1 ); 808 my $ord = ord($ch); 809 $s .= sprintf( "&#%d;", $ord ) if ( $ord >= 128 ); 810 $s .= $ch if ( $ord < 128 ); 811 } 812 $text = $s; 813 } 814 } 815 return $text; 816} 817 818sub begin_report() { 819 if ($opt_o) { 820 open( $REPORT, '>', $opt_o ) or &failed("cannot open $opt_o"); 821 select $REPORT; 822 } 823 if ($opt_h) { 824 printf <<EOF 825<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"> 826 827<html> 828<head> 829 <meta name="generator" content="$0"> 830 831 <title>XTERM - Modified "Other" Keys ($xkb_layout-$xkb_model)</title> 832 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 833 <meta name="keywords" content="xterm, special keys"> 834 <meta name="description" content="This is an example of xterm's modifyOtherKeys feature"> 835</head> 836 837<body> 838EOF 839 ; 840 } 841} 842 843sub end_report() { 844 if ($opt_h) { 845 my $output = "output.html"; 846 $output = $opt_o if ($opt_o); 847 printf <<EOF 848<div class="nav"> 849 <ul> 850 <li class="nav-top"><a href="$output">(top)</a></li> 851EOF 852 ; 853 for my $h ( 0 .. $#headers ) { 854 printf "<li><a href=\"#%s\">%s</a></li>\n", 855 &html_ref( $headers[$h] ), $headers[$h]; 856 } 857 printf <<EOF 858 </ul> 859</div> 860EOF 861 ; 862 } 863 if ($opt_o) { 864 select STDOUT; 865 close $REPORT; 866 } 867} 868 869sub begin_section($) { 870 my $header = shift; 871 $headers[ $#headers + 1 ] = $header; 872 if ($opt_h) { 873 printf "<h2><a name=\"%s\">%s</a></h2>\n", &html_ref($header), $header; 874 } 875 else { 876 printf "\n"; 877 printf "%s:\n", $header; 878 } 879 printf STDERR "** %s\n", $header if ($opt_o); 880} 881 882sub begin_table() { 883 my $title = shift; 884 &begin_section($title); 885 if ($opt_h) { 886 printf "<table border=\"1\" summary=\"$title\">\n"; 887 } 888} 889 890sub end_table() { 891 if ($opt_h) { 892 printf "</table>\n"; 893 } 894} 895 896sub tt_cell($) { 897 my $text = shift; 898 return sprintf "<tt>%s</tt>", $text; 899} 900 901sub td_any($) { 902 my $text = shift; 903 return sprintf "<td>%s</td>", &tt_cell($text); 904} 905 906sub td_left($) { 907 my $text = shift; 908 return sprintf "<td align=\"left\">%s</td>", &tt_cell($text); 909} 910 911sub td_right($) { 912 my $text = shift; 913 return sprintf "<td align=\"right\">%s</td>", &tt_cell($text); 914} 915 916sub padded($$) { 917 my $size = shift; 918 my $text = shift; 919 $text = sprintf( "%*s", $size, $text ) if ( $size > 0 ); 920 $text = sprintf( "%-*s", $size, $text ) if ( $size < 0 ); 921 $text =~ s/ / /g if ($opt_h); 922 return $text; 923} 924 925sub print_head() { 926 my $argc = $#_; 927 if ($opt_h) { 928 printf "<tr>"; 929 for ( my $n = 0 ; $n <= $argc ; $n += 2 ) { 930 my $size = $_[$n]; 931 my $text = &padded( $size, $_[ $n + 1 ] ); 932 printf "<th>%s</th>", $text; 933 } 934 printf "</tr>\n"; 935 } 936 else { 937 for ( my $n = 0 ; $n <= $argc ; $n += 2 ) { 938 my $size = $_[$n]; 939 my $text = &padded( $size, $_[ $n + 1 ] ); 940 printf "%s", $text; 941 printf " " if ( $n < $argc ); 942 } 943 printf "\n"; 944 } 945} 946 947sub link_data($$) { 948 my $thisis = shift; 949 my $thatis = shift; 950 my $column = shift; 951 my $symbol = shift; 952 my %result; 953 $result{THISIS} = $thisis; # current table name 954 $result{THATIS} = $thatis; # name of target table for link 955 $result{COLUMN} = $column; # column counting from 0 956 $result{SYMBOL} = $symbol; 957 return \%result; 958} 959 960sub unique_link($$) { 961 my $thisis = shift; 962 my $symbol = shift; 963 my $unique = 0; 964 for my $n ( 0 .. length($symbol) - 1 ) { 965 $unique += ord substr( $symbol, $n, 1 ); 966 } 967 return sprintf( "%s:%s.%x", $thisis, $symbol, $unique ); 968} 969 970# print a row in the table, using pairs of lengths and strings: 971# + Right-align lengths greater than zero and pad; 972# + Left-align lengths less than zero, pad. 973# + For the special case of zero, just left align without padding. 974sub print_data() { 975 my $argc = $#_; 976 if ($opt_h) { 977 my @links = @{ $_[0] }; 978 printf "<tr>"; 979 my $col = 0; 980 for ( my $n = 1 ; $n <= $argc ; $n += 2 ) { 981 my $size = $_[$n]; 982 my $text = &padded( $size, $_[ $n + 1 ] ); 983 if ( $#links >= 0 ) { 984 for my $l ( 0 .. $#links ) { 985 my %obj = %{ $links[$l] }; # link_data 986 if ( $obj{COLUMN} == $col ) { 987 my $props = ""; 988 my $value = &unique_link( $obj{THISIS}, $obj{SYMBOL} ); 989 990 # The symbol-map from xkbcomp has duplicates because 991 # different modifier combinations can produce the same 992 # keysym. Since it appears that the slots that the 993 # user would expect are filled in first, just ignoring 994 # the duplicate works well enough. 995 if ( not $linkUsed{$value} ) { 996 $props .= " name=\"$value\""; 997 $linkUsed{$value} = 1; 998 } 999 $value = &unique_link( $obj{THATIS}, $obj{SYMBOL} ); 1000 $props .= " href=\"#$value\""; 1001 my $tail = $text; 1002 $text =~ s/(\ )+$//; 1003 $tail = substr( $tail, length($text) ); 1004 $text = 1005 sprintf( "<a %s>%s</a>%s", $props, $text, $tail ); 1006 last; 1007 } 1008 } 1009 } 1010 printf "%s", 1011 ( $size > 0 ) ? &td_right($text) 1012 : ( $size == 0 ) ? &td_any($text) 1013 : &td_left($text); 1014 ++$col; 1015 } 1016 printf "</tr>\n"; 1017 } 1018 else { 1019 for ( my $n = 1 ; $n <= $argc ; $n += 2 ) { 1020 my $size = $_[$n]; 1021 my $text = &padded( $size, $_[ $n + 1 ] ); 1022 printf "%s", $text; 1023 printf " " if ( $n < $argc ); 1024 } 1025 printf "\n"; 1026 } 1027} 1028 1029sub begin_preformatted($) { 1030 my $title = shift; 1031 &begin_section($title); 1032 printf "<pre>\n" if ($opt_h); 1033} 1034 1035sub end_preformatted() { 1036 printf "</pre>\n" if ($opt_h); 1037} 1038 1039sub do_localectl($) { 1040 my $report = shift; 1041 my $cmd = "localectl status"; 1042 my @data = &readpipe($cmd); 1043 &begin_table("Output of $cmd") if ($report); 1044 for my $n ( 0 .. $#data ) { 1045 1046 # let command-line parameters override localectl output, for reports 1047 $data[$n] =~ s/^(\s+X11 Layout:\s+).*$/$1$opt_l/ if ($opt_l); 1048 $data[$n] =~ s/^(\s+X11 Model:\s+).*$/$1$opt_m/ if ($opt_m); 1049 my @fields = split /:\s*/, $data[$n]; 1050 next unless ( $#fields == 1 ); 1051 if ($report) { 1052 if ($opt_h) { 1053 printf "<tr>%s%s</tr>\n", 1054 &td_right( $fields[0] ), 1055 &td_left( $fields[1] ); 1056 } 1057 else { 1058 printf "%s\n", $data[$n]; 1059 } 1060 } 1061 $xkb_layout = $fields[1] if ( $fields[0] =~ /x11 layout/i ); 1062 $xkb_model = $fields[1] if ( $fields[0] =~ /x11 model/i ); 1063 } 1064 if ($report) { 1065 &end_table; 1066 } 1067} 1068 1069sub do_keysymdef() { 1070 my @data = &readfile($keyfile); 1071 my $lenSyms = 0; 1072 for my $n ( 0 .. $#data ) { 1073 my $value = &trim( $data[$n] ); 1074 next unless ( $value =~ /^#define\s+XK_/ ); 1075 my $name = $value; 1076 $name =~ s/^#define\s+//; 1077 $value = $name; 1078 $name =~ s/\s.*//; 1079 $value =~ s/^[^\s]+\s+//; 1080 my $note = $value; 1081 $value =~ s/\s.*//; 1082 1083 $note =~ s/^[^\s]+\s*//; 1084 if ( $note !~ /\b(alias|deprecated)\b/ ) { 1085 1086 if ( $note =~ /\/*.*\bU\+[[:xdigit:]]{4,8}.*\*\// ) { 1087 next if ( $note =~ /\(U\+/ ); 1088 my $code = $note; 1089 $code =~ s/^.*\bU\+([[:xdigit:]]+).*/$1/; 1090 $note =~ s/^\/\*[([:space:]]*//; 1091 $note =~ s/[)[:space:]]*\*\/$//; 1092 $uniNames{$value} = $note; 1093 $uniCodes{$value} = hex $code; 1094 } 1095 } 1096 $lenSyms = length($name) if ( length($name) > $lenSyms ); 1097 $value = lc $value; 1098 $keySyms{$name} = $value; 1099 $keyCodes{$value} = $name unless ( $keyCodes{$value} ); 1100 printf "keySyms{$name} = '$value', keyCodes{$value} = $name\n" 1101 if ($opt_d); 1102 } 1103 my $tmpfile = $keyfile; 1104 $tmpfile =~ s/^.*\///; 1105 &begin_table("Symbols from $tmpfile"); 1106 my @keys = keys %keySyms; 1107 &print_data( \@nolinks, 5, sprintf( "%d", $#keys ), 1108 0, sprintf( "keysyms are defined (longest %d)", $lenSyms ) ); 1109 @keys = keys %keyCodes; 1110 &print_data( \@nolinks, 5, sprintf( "%d", $#keys ), 1111 0, "keycodes are defined" ); 1112 @keys = keys %uniCodes; 1113 &print_data( \@nolinks, 5, sprintf( "%d", $#keys ), 1114 0, "keycodes are equated to Unicode" ); 1115 &end_table; 1116} 1117 1118# For what it's worth, there is a C library (xkbfile) which could be used, 1119# but there is no documentation and would not actually solve the problem at 1120# hand. 1121# 1122# setxkbmap -model pc105 -layout us -print | xkbcomp - -C -o - 1123sub do_xkbcomp() { 1124 my @data = 1125 &readpipe( "setxkbmap " 1126 . "-model $xkb_model " 1127 . "-layout $xkb_layout -print " 1128 . "| xkbcomp - -C -o -" ); 1129 my $state = -1; 1130 my $type = {}; 1131 for my $n ( 0 .. $#data ) { 1132 if ( $data[$n] =~ /static.*\bkeyNames\[.*{/ ) { 1133 $state = 0; 1134 next; 1135 } 1136 if ( $data[$n] =~ /static.*\bsymCache\[.*{/ ) { 1137 $state = 1; 1138 next; 1139 } 1140 if ( $data[$n] =~ /static.*\bsymMap\[.*{/ ) { 1141 $state = 2; 1142 next; 1143 } 1144 if ( $data[$n] =~ /static.*\bdflt_types\[.*{/ ) { 1145 $state = 3; 1146 next; 1147 } 1148 if ( $state >= 0 ) { 1149 if ( $data[$n] =~ /^\s*};/ ) { 1150 printf "# %s\n", $data[$n] if ($opt_d); 1151 $state = -1; 1152 next; 1153 } 1154 printf "* %s\n", $data[$n] if ($opt_d); 1155 } 1156 1157 # parse data in "keyNames[NUM_KEYS]" 1158 if ( $state == 0 ) { 1159 my $text = $data[$n]; 1160 my $name; 1161 while ( $text =~ /^.*".*".*$/ ) { 1162 $text =~ s/^[^"]*//; 1163 $name = $text; 1164 $name =~ s/"\s+}.*//; 1165 $name =~ s/"//g; 1166 $keyNames[ $#keyNames + 1 ] = $name; 1167 printf "keyNames[%d] = '%s'\n", $#keyNames, 1168 $keyNames[$#keyNames] 1169 if ($opt_v); 1170 $text =~ s/^"[^"]*"//; 1171 } 1172 } 1173 1174 # parse data in "symCache[NUM_SYMBOLS]" 1175 elsif ( $state == 1 ) { 1176 my $text = $data[$n]; 1177 my $name; 1178 while ( $text =~ /[[:alnum:]_]/ ) { 1179 $text =~ s/^[^[[:alnum:]_]*//; 1180 $name = $text; 1181 $name =~ s/[^[[:alnum:]_].*//; 1182 $symCache[ $#symCache + 1 ] = $name; 1183 printf "symCache[%d] = %s\n", $#symCache, $symCache[$#symCache] 1184 if ($opt_v); 1185 $text =~ s/^[[:alnum:]_]+//; 1186 } 1187 } 1188 1189 # parse data in "symMap[NUM_KEYS]" 1190 elsif ( $state == 2 ) { 1191 my $text = $data[$n]; 1192 my $code; 1193 while ( $text =~ /[{].*[}]/ ) { 1194 my %obj; 1195 $text =~ s/^[^{]*[{]\s*//; 1196 $code = $text; 1197 $code =~ s/[^[[:alnum:]].*//; 1198 $text =~ s/[[:alnum:]]+\s*,\s*//; 1199 $obj{TYPE} = $code; # KeyType 1200 my %tmp = %{ $keyTypes[$code] }; 1201 $tmp{USED} += 1; 1202 $keyTypes[$code] = \%tmp; 1203 $code = $text; 1204 $code =~ s/[^[[:alnum:]].*//; 1205 $text =~ s/[[:alnum:]]+\s*,\s*//; 1206 $obj{USED} = hex $code; # 0/1 for used/unused 1207 $code = $text; 1208 $code =~ s/[^[[:alnum:]].*//; 1209 $obj{CODE} = $code; # index in symCache[] 1210 $text =~ s/[[:alnum:]]+\s*//; 1211 $symMap[ $#symMap + 1 ] = \%obj; 1212 printf "symMap[%d] = {%d,%d,%d}\n", $#symMap, $obj{TYPE}, 1213 $obj{USED}, $obj{CODE} 1214 if ($opt_v); 1215 } 1216 } 1217 1218 # parse data in "dflt_types[]" 1219 elsif ( $state == 3 ) { 1220 my $text = &trim( $data[$n] ); 1221 if ( $text =~ /^\s*[}](,)?$/ ) { 1222 $type->{USED} = 0; 1223 $keyTypes[ $#keyTypes + 1 ] = $type; 1224 $type = {}; 1225 } 1226 elsif ( $text =~ /^\d+,$/ ) { 1227 $text =~ s/,//; 1228 $type->{SIZE} = $text; 1229 } 1230 elsif ( $text =~ /^None,\s+lnames_[[:alnum:]_]+$/ ) { 1231 $text =~ s/^None,\s+lnames_//; 1232 $type->{NAME} = $text; 1233 } 1234 elsif ( $text =~ /^\s*[{].*[}],\s*$/ ) { 1235 $text =~ s/^\s*[{]\s*([^,]+),.*/$1/; 1236 $type->{MODS} = $text; 1237 } 1238 } 1239 } 1240 &begin_table("Summary from xkbcomp"); 1241 &print_data( \@nolinks, 5, sprintf( "%d", $#keyNames + 1 ), 0, "keyNames" ); 1242 &print_data( \@nolinks, 5, sprintf( "%d", $#keyTypes + 1 ), 0, "keyTypes" ); 1243 &print_data( \@nolinks, 5, sprintf( "%d", $#symCache + 1 ), 0, "symCache" ); 1244 &print_data( \@nolinks, 5, sprintf( "%d", $#symMap + 1 ), 0, "symMap" ); 1245 &end_table; 1246} 1247 1248# Report keysymdef.h without the deprecated stuff, and sorted by keycode. 1249sub report_keysymdef() { 1250 &begin_table("Key symbols"); 1251 &print_head( 0, "Code", 0, "Category", 0, "Symbol" ); 1252 1253 # sort by numeric keycode rather than string 1254 my @keyCodes = keys %keyCodes; 1255 my @sortCodes; 1256 for my $c ( 0 .. $#keyCodes ) { 1257 $sortCodes[$c] = sprintf "%08X", hex $keyCodes[$c]; 1258 } 1259 @sortCodes = sort @sortCodes; 1260 for my $c ( 0 .. $#sortCodes ) { 1261 my $code = sprintf( "0x%04x", hex $sortCodes[$c] ); 1262 my $sym = $keyCodes{$code}; 1263 &print_data( \@nolinks, 9, $code, -8, &TypeOf($code), 0, $sym ); 1264 } 1265 &end_table; 1266} 1267 1268sub report_key_types() { 1269 &begin_table("Key types"); 1270 &print_head( 5, "Type", 5, "Used", 5, "Levels", 0, "Name" ); 1271 for my $t ( 0 .. $#keyTypes ) { 1272 my %type = %{ $keyTypes[$t] }; 1273 next if ( $type{USED} == 0 and not $opt_v ); 1274 &print_data( 1275 \@nolinks, 5, sprintf( "%d", $t ), 5, 1276 sprintf( "%d", $type{USED} ), 5, sprintf( "%d", $type{SIZE} ), 0, 1277 $type{NAME} 1278 ); 1279 } 1280 &end_table; 1281} 1282 1283sub report_modified_keys() { 1284 my @codes = sort keys %keysUsed; 1285 my $width = 14; 1286 &begin_table("Other modifiable keycodes"); 1287 &print_head( 1288 0, "Code", 0, "Symbol", 0, "Actual", 1289 -$width, "Mode 0", -$width, "Mode 1", -$width, "Mode 2" 1290 ); 1291 $width = 0 if ($opt_h); 1292 for my $c ( 0 .. $#codes ) { 1293 next unless ( $codes[$c] ne "" ); 1294 my @links; 1295 my $sym = $keysUsed{ $codes[$c] }; 1296 $links[0] = &link_data( "summary", "detailed", 1, $sym ); 1297 &print_data( 1298 \@links, 1299 6, $codes[$c], # 1300 -20, $keysUsed{ $codes[$c] }, # 1301 -6, sprintf( "%d", hex $codes[$c] ), # 1302 -$width, &CheckOtherKey( $codes[$c], 0 ), # 1303 -$width, &CheckOtherKey( $codes[$c], 1 ), # 1304 -$width, &CheckOtherKey( $codes[$c], 2 ) 1305 ); 1306 } 1307 &end_table; 1308 &begin_preformatted("Modify-param to/from state"); 1309 for my $param ( 0 .. $MAXMODS ) { 1310 my $state = &ParamToState($param); 1311 my $check = &xtermStateToParam($state); 1312 printf " PARAM %d %s %d %s %d (%s)\n", $param, &rightarrow, # 1313 $state, &rightarrow, # 1314 $check, &ParamToS($param); 1315 } 1316 &end_preformatted; 1317 &begin_preformatted("State to/from modify-param"); 1318 for my $state ( 0 .. 15 ) { 1319 my $param = &xtermStateToParam($state); 1320 my $check = &ParamToState($param); 1321 printf " STATE %d %s %d %s %d (%s)\n", # 1322 $state, &rightarrow, # 1323 $param, &rightarrow, # 1324 $check, &StateToS($state); 1325 } 1326 &end_preformatted; 1327} 1328 1329# Make a report showing user- and program-modes. 1330sub report_otherkey_escapes() { 1331 my @codes = sort keys %keysUsed; 1332 my $width = 14; 1333 &begin_table("Other modified-key escapes"); 1334 &print_head( 1335 0, "Code", 0, "Symbol", 0, "Actual", 1336 -$width, "Mode 0", -$width, "Mode 1", -$width, "Mode 2" 1337 ); 1338 $width = 0 if ($opt_h); 1339 for my $c ( 0 .. $#codes ) { 1340 next unless ( $codes[$c] ne "" ); 1341 my $level0 = &CheckOtherKey( $codes[$c], 0 ); 1342 my $level1 = &CheckOtherKey( $codes[$c], 1 ); 1343 my $level2 = &CheckOtherKey( $codes[$c], 2 ); 1344 my @level0 = &ShowOtherKeys( $codes[$c], 0, $level0 ); 1345 my @level1 = &ShowOtherKeys( $codes[$c], 1, $level1 ); 1346 my @level2 = &ShowOtherKeys( $codes[$c], 2, $level2 ); 1347 my @links; 1348 my $sym = $keysUsed{ $codes[$c] }; 1349 $links[0] = &link_data( "detailed", "symmap", 1, $sym ); 1350 &print_data( 1351 \@links, # 1352 -6, $codes[$c], # 1353 -20, $keysUsed{ $codes[$c] }, # 1354 -6, sprintf( "%d", hex $codes[$c] ), # 1355 -$width, $level0, # 1356 -$width, $level1, # 1357 -$width, $level2 1358 ); 1359 1360 for my $r ( 0 .. $#level0 ) { 1361 &print_data( 1362 \@nolinks, # 1363 -6, &ParamToQ( $r + 1 ), # 1364 -20, "", # 1365 -6, "", # 1366 -$width, &safe_html( $level0[$r] ), # 1367 -$width, &safe_html( $level1[$r] ), # 1368 -$width, &safe_html( $level2[$r] ) 1369 ); 1370 } 1371 } 1372 &end_table; 1373} 1374 1375sub report_keys_used() { 1376 &begin_table("Key map"); 1377 &print_head( 1378 5, "Type", # 1379 0, "Level", # 1380 0, "Name", # 1381 6, "Code", # 1382 0, 1383 "Symbol" 1384 ); 1385 for my $m ( 0 .. $#symMap ) { 1386 my %obj = %{ $symMap[$m] }; 1387 next unless ( $obj{USED} ); 1388 my $sym = $symCache[ $obj{CODE} ]; 1389 next if ( $sym eq "NoSymbol" ); 1390 my $code = ""; 1391 $code = $keySyms{$sym} if ( $keySyms{$sym} ); 1392 next if ( $code eq "" ); 1393 $keysUsed{$code} = $sym; 1394 my %type = %{ $keyTypes[ $obj{TYPE} ] }; 1395 my @links; 1396 $links[0] = &link_data( "symmap", "summary", 4, $sym ); 1397 &print_data( 1398 \@links, 1399 5, sprintf( "%d", $obj{TYPE} ), # 1400 5, sprintf( "1/%d", $type{SIZE} ), # 1401 -4, $keyNames[$m], # 1402 6, $code, # 1403 0, $sym 1404 ); 1405 1406 my $base = $code; 1407 $Shifted{$code} = $code unless ( $Shifted{$code} ); 1408 1409 for my $t ( 1 .. $type{SIZE} - 1 ) { 1410 $sym = $symCache[ $obj{CODE} + $t ]; 1411 if ( $keySyms{$sym} ) { 1412 $code = $keySyms{$sym}; 1413 $keysUsed{$code} = $sym; 1414 $links[0] = &link_data( "symmap", "summary", 4, $sym ); 1415 } 1416 else { 1417 $code = ""; 1418 @links = (); 1419 } 1420 &print_data( 1421 \@links, 1422 5, "", # 1423 5, sprintf( "%d/%d", $t + 1, $type{SIZE} ), # 1424 -4, "", # 1425 6, $code, # 1426 0, $sym 1427 ); 1428 @links = (); 1429 1430 # The shift-modifier could be used in custom groups, but the only 1431 # built-in ones that appear relevant are TWO_LEVEL and ALPHABETIC, 1432 # which have two levels. This records the shifted code for a given 1433 # base. 1434 if ( $type{SIZE} == 2 1435 and $type{MODS} 1436 and index( $type{MODS}, "ShiftMask" ) >= 0 ) 1437 { 1438 if ( $t == 1 ) { 1439 $Shifted{$base} = $code; 1440 } 1441 elsif ( not $Shifted{$code} ) { 1442 $Shifted{$code} = $code; 1443 } 1444 } 1445 } 1446 } 1447 &end_table; 1448} 1449 1450sub KeyClasses($) { 1451 my $hex = shift; 1452 my $alias = &IsControlAlias( $hex, $ControlMask ) ? "alias" : ""; 1453 my $cntrl = &IS_CTRL($hex) ? "cntrl" : ""; 1454 my $ctl_i = &IsControlInput($hex) ? "ctl_i" : ""; 1455 my $ctl_o = &IsControlOutput($hex) ? "ctl_o" : ""; 1456 my $this = sprintf( "%-5s %-5s %-5s %-5s %-8s", 1457 $alias, $cntrl, $ctl_i, $ctl_o, &TypeOf($hex) ); 1458} 1459 1460sub report_key_classes() { 1461 &begin_table("Keycode-classes"); 1462 my $base = -1; 1463 my $last = ""; 1464 my $next = 65535; 1465 my $form = " [%8s .. %-8s] %s\n"; 1466 &print_head( 0, "First", 0, "Last", 0, "Classes" ) if ($opt_h); 1467 for my $code ( 0 .. $next ) { 1468 my $hex = &toCode($code); 1469 my $this = &KeyClasses($hex); 1470 if ( $base < 0 ) { 1471 $base = 0; 1472 $last = $this; 1473 } 1474 elsif ( $this ne $last ) { 1475 printf $form, &toCode($base), &toCode( $code - 1 ), $last 1476 unless ($opt_h); 1477 &print_data( \@nolinks, 0, &toCode($base), 0, &toCode( $code - 1 ), 1478 0, $last ) 1479 if ($opt_h); 1480 $base = $code; 1481 $last = $this; 1482 } 1483 } 1484 printf $form, &toCode($base), &toCode($next), $last unless ($opt_h); 1485 &print_data( \@nolinks, 0, &toCode($base), 0, &toCode($next), 0, $last ) 1486 if ($opt_h); 1487 &end_table; 1488} 1489 1490sub main::HELP_MESSAGE() { 1491 printf STDERR <<EOF 1492Usage: $0 [options] 1493 1494Options: 1495 -d debug 1496 -h write report with html-markup 1497 -k dump keysyms/keycodes from $keyfile 1498 -K dump keycode-classes 1499 -l XXX use XXX for Xkb layout (default $xkb_layout) 1500 -m XXX use XXX for Xkb model (default $xkb_model) 1501 -o XXX write report to the file XXX. 1502 -u use CSI u format for escapes 1503 -v verbose 1504 1505EOF 1506 ; 1507 exit 1; 1508} 1509 1510binmode( STDOUT, ":utf8" ); 1511 1512&do_localectl(0); 1513 1514$Getopt::Std::STANDARD_HELP_VERSION = 1; 1515&getopts('dhKkl:m:o:uv') || &main::HELP_MESSAGE; 1516$opt_v = 1 if ($opt_d); 1517 1518&begin_report; 1519 1520&do_localectl(1); 1521 1522$xkb_layout = $opt_l if ($opt_l); 1523$xkb_model = $opt_m if ($opt_m); 1524 1525&do_keysymdef; 1526&report_keysymdef if ($opt_k); 1527 1528&do_xkbcomp; 1529 1530&report_key_classes if ($opt_K); 1531 1532&report_key_types; 1533&report_keys_used; 1534&report_modified_keys; 1535&report_otherkey_escapes; 1536 1537&end_report; 1538 15391; 1540