| | 14 | |
|---|
| | 15 | (defpackage :symbolics-keyboard |
|---|
| | 16 | (:nicknames "SKBD") |
|---|
| | 17 | (:use :cl :alexandria)) |
|---|
| | 18 | |
|---|
| | 19 | (in-package :symbolics-keyboard) |
|---|
| | 20 | |
|---|
| | 21 | (defparameter *key-map* |
|---|
| | 22 | '(("Select" "F1") |
|---|
| | 23 | ("Network" "F2") |
|---|
| | 24 | ("Function" "F3") |
|---|
| | 25 | ("Suspend" "F4") |
|---|
| | 26 | ("Resume" "F5") |
|---|
| | 27 | ("Abort" "F6") |
|---|
| | 28 | ("SuperL" "F7") |
|---|
| | 29 | ("HyperL" "F8") |
|---|
| | 30 | ("Scroll" "F9" "PgDn") |
|---|
| | 31 | ("ClearInput" "F10") |
|---|
| | 32 | ("SymbolR" "KP5" "Up") |
|---|
| | 33 | ("Scroll" "KP3" "Scroll") |
|---|
| | 34 | ("RubOut" "Del") |
|---|
| | 35 | ("Complete" "F11" "Home") |
|---|
| | 36 | ("Help" "F12") |
|---|
| | 37 | ("End" "KP1" "End") |
|---|
| | 38 | ("MetaL" "AltL") |
|---|
| | 39 | ("MetaR" "AltGr" "Left") |
|---|
| | 40 | ("SuperR" "KP." "Down") |
|---|
| | 41 | ("HyperR" "KP+" "Right") |
|---|
| | 42 | ("Repeat" "KP/" "PgUp") |
|---|
| | 43 | ("ControlL" "CtrlL") |
|---|
| | 44 | ("ControlR" "CtrlR") |
|---|
| | 45 | ("(" "[") |
|---|
| | 46 | (")" "]") |
|---|
| | 47 | ("Triangle" "KP2") |
|---|
| | 48 | ("Circle" "KP3") |
|---|
| | 49 | ("Square" "KP4") |
|---|
| | 50 | ("Refresh" "KP0") |
|---|
| | 51 | ("Page" "KP6") |
|---|
| | 52 | ("Line" "KP7") |
|---|
| | 53 | ("SymbolL" "KP8") |
|---|
| | 54 | ("|" "KP-") |
|---|
| | 55 | (":" "KP*") |
|---|
| | 56 | ("CapsLock" "Caps")) |
|---|
| | 57 | "Mappings for keys. By default, Symbolics keys which have the same |
|---|
| | 58 | name (keycap label) as a PS/2 key are mapped to that corresponding |
|---|
| | 59 | key. Before the default mapping is considered, this list of lists |
|---|
| | 60 | is checked for an explicit definition. Each list consists |
|---|
| | 61 | of (KEYNAME PS2-KEYNAME [ F-MODE-PS2-KEYNAME ] ) with KEYNAME being |
|---|
| | 62 | the name of the Symbolics key, PS2-KEYNAME the name of the PS/2 key |
|---|
| | 63 | name whose PS/2 scancode should be sent and F-MODE-PS2-KEYNAME being |
|---|
| | 64 | the name of the PS/2 key whose PS/2 scancode should be sent in |
|---|
| | 65 | F-mode. PS2-KEYNAME may be NIL to indicate that no PS/2 scancode is |
|---|
| | 66 | associated to the key when not in F-Mode. F-MODE-PS2-KEYNAME |
|---|
| | 67 | defaults to PS2-KEYNAME if not specified.") |
|---|
| 112 | | ("Scroll" #x07 9 0 1.5) |
|---|
| 113 | | ("ModeLock" #x08 16 1 1.5) |
|---|
| 114 | | ("Select" #x0c 0 2 2) |
|---|
| 115 | | ("SymbolL" #x0d 1 1 1.5) |
|---|
| 116 | | ("SuperL" #x0e 1 0) |
|---|
| 117 | | ("ControlL" #x0f 3 0 2) |
|---|
| 118 | | ("Space" #x10 4 0 9) |
|---|
| 119 | | ("MetaR" #x11 6 0) |
|---|
| 120 | | ("HyperR" #x12 7 0) |
|---|
| 121 | | ("End" #x13 15 2 0) |
|---|
| 122 | | ("Z" #x17 3 1) |
|---|
| 123 | | ("C" #x18 5 1) |
|---|
| 124 | | ("B" #x19 7 1) |
|---|
| 125 | | ("M" #x1a 9 1) |
|---|
| 126 | | ("." #x1b 11 1) |
|---|
| 127 | | ("ShiftR" #x1c 13 1 2) |
|---|
| 128 | | ("Repeat" #x1d 15 1 1.5) |
|---|
| 129 | | ("Abort" #x1e 9 5) |
|---|
| 130 | | ("ShiftL" #x22 2 1 2) |
|---|
| 131 | | ("X" #x23 4 1) |
|---|
| 132 | | ("V" #x24 6 1) |
|---|
| 133 | | ("N" #x25 8 1) |
|---|
| 134 | | ("," #x26 10 1) |
|---|
| 135 | | ("/" #x27 12 1) |
|---|
| 136 | | ("SymbolR" #x28 14 1 1.5) |
|---|
| 137 | | ("Help" #x29 17 4 2) |
|---|
| 138 | | ("RubOut" #x2d 1 2 1.75) |
|---|
| 139 | | ("S" #x2e 3 2) |
|---|
| 140 | | ("F" #x2f 5 2) |
|---|
| 141 | | ("H" #x30 7 2) |
|---|
| 142 | | ("K" #x31 9 2) |
|---|
| 143 | | (";" #x32 11 2) |
|---|
| 144 | | ("Return" #x33 13 2) |
|---|
| 145 | | ("Complete" #x34 16 3 2) |
|---|
| 146 | | ("Network" #x38 0 4 2) |
|---|
| 147 | | ("A" #x39 2 2) |
|---|
| 148 | | ("D" #x3a 4 2) |
|---|
| 149 | | ("G" #x3b 6 2) |
|---|
| 150 | | ("J" #x3c 8 2) |
|---|
| 151 | | ("L" #x3d 10 2) |
|---|
| 152 | | ("'" #x3e) |
|---|
| 153 | | ("Line" #x3f) |
|---|
| 154 | | ("Function" #x43) |
|---|
| 155 | | ("W" #x44) |
|---|
| 156 | | ("R" #x45) |
|---|
| 157 | | ("Y" #x46) |
|---|
| 158 | | ("I" #x47) |
|---|
| 159 | | ("P" #x48) |
|---|
| 160 | | (")" #x49) |
|---|
| 161 | | ("Page" #x4a) |
|---|
| 162 | | ("Tab" #x4e) |
|---|
| 163 | | ("Q" #x4f) |
|---|
| 164 | | ("E" #x50) |
|---|
| 165 | | ("T" #x51) |
|---|
| 166 | | ("U" #x52) |
|---|
| 167 | | ("O" #x53) |
|---|
| 168 | | ("(" #x54) |
|---|
| 169 | | ("BackSpace" #x55) |
|---|
| 170 | | (":" #x59) |
|---|
| 171 | | ("2" #x5a) |
|---|
| 172 | | ("4" #x5b) |
|---|
| 173 | | ("6" #x5c) |
|---|
| 174 | | ("8" #x5d) |
|---|
| 175 | | ("0" #x5e) |
|---|
| 176 | | ("=" #x5f) |
|---|
| 177 | | ("\\" #x60) |
|---|
| 178 | | ("1" #x64) |
|---|
| 179 | | ("3" #x65) |
|---|
| 180 | | ("5" #x66) |
|---|
| 181 | | ("7" #x67) |
|---|
| 182 | | ("9" #x68) |
|---|
| 183 | | ("-" #x69) |
|---|
| 184 | | ("`" #x6a) |
|---|
| 185 | | ("|" #x6b) |
|---|
| 186 | | ("Escape" #x6f) |
|---|
| 187 | | ("Refresh" #x70) |
|---|
| 188 | | ("Square" #x71) |
|---|
| 189 | | ("Circle" #x72) |
|---|
| 190 | | ("Triangle" #x73) |
|---|
| 191 | | ("ClearInput" #x74) |
|---|
| 192 | | ("Suspend" #x75) |
|---|
| 193 | | ("Resume" #x76))) |
|---|
| 194 | | |
|---|
| 195 | | (defparameter *special-key-map* |
|---|
| 196 | | '(("Select" "F1") |
|---|
| 197 | | ("Network" "F2") |
|---|
| 198 | | ("Function" "F3") |
|---|
| 199 | | ("Suspend" "F4") |
|---|
| 200 | | ("Resume" "F5") |
|---|
| 201 | | ("Abort" "F6") |
|---|
| 202 | | ("SuperL" "F7") |
|---|
| 203 | | ("HyperL" "F8") |
|---|
| 204 | | ("Scroll" "F9" "PgDn") |
|---|
| 205 | | ("ClearInput" "F10") |
|---|
| 206 | | ("SymbolR" "KP5" "Up") |
|---|
| 207 | | ("Scroll" "KP3" "Scroll") |
|---|
| 208 | | ("RubOut" "Del") |
|---|
| 209 | | ("Complete" "F11" "Home") |
|---|
| 210 | | ("Help" "F12") |
|---|
| 211 | | ("End" "KP1" "End") |
|---|
| 212 | | ("MetaL" "AltL") |
|---|
| 213 | | ("MetaR" "AltGr" "Left") |
|---|
| 214 | | ("SuperR" nil "Down") |
|---|
| 215 | | ("HyperR" nil "Right") |
|---|
| 216 | | ("Repeat" nil "PgUp") |
|---|
| 217 | | ("ControlL" "CtrlL") |
|---|
| 218 | | ("ControlR" "CtrlR") |
|---|
| 219 | | ("(" "[") |
|---|
| 220 | | (")" "]") |
|---|
| 221 | | ("Triangle" "KP2") |
|---|
| 222 | | ("Circle" "KP3") |
|---|
| 223 | | ("Square" "KP4") |
|---|
| 224 | | ("Refresh" "KP0") |
|---|
| 225 | | ("Page" "KP6") |
|---|
| 226 | | ("Line" "KP7") |
|---|
| 227 | | ("SymbolL" "KP8") |
|---|
| 228 | | ("|" "KP-") |
|---|
| 229 | | (":" "KP*"))) |
|---|
| | 258 | ("HyperR" #x12 8 0) |
|---|
| | 259 | ("Scroll" #x07 9 0 1.5)) |
|---|
| | 260 | "Definition of Symbolics keyboard scan codes. One list (NAME |
|---|
| | 261 | SCANCODE XPOS YPOS [ SIZE ] ) for each key. NAME is the name of the |
|---|
| | 262 | key, as printed on the key cap. SCANCODE is the scan code. XPOS is |
|---|
| | 263 | the relative X position of the key, counted from the left. YPOS is |
|---|
| | 264 | the relative Y position of the key, counted from the bottom row. |
|---|
| | 265 | SIZE is the size of the key relative to a letter key, which has size |
|---|
| | 266 | 1 and is the default.") |
|---|
| | 267 | |
|---|
| | 268 | (defun key-labels (entry) |
|---|
| | 269 | (if (listp (first entry)) |
|---|
| | 270 | (first entry) |
|---|
| | 271 | (list (first entry)))) |
|---|
| | 272 | |
|---|
| | 273 | (defun key-name (entry) |
|---|
| | 274 | (if (listp (first entry)) |
|---|
| | 275 | (first (first entry)) |
|---|
| | 276 | (first entry))) |
|---|
| | 277 | |
|---|
| | 278 | (defun key-scancode (entry) |
|---|
| | 279 | (second entry)) |
|---|
| | 280 | |
|---|
| | 281 | (defun key-x (entry) |
|---|
| | 282 | (third entry)) |
|---|
| | 283 | |
|---|
| | 284 | (defun key-y (entry) |
|---|
| | 285 | (fourth entry)) |
|---|
| | 286 | |
|---|
| | 287 | (defun key-width (entry) |
|---|
| | 288 | (or (fifth entry) 1)) |
|---|
| | 289 | |
|---|
| | 290 | (defun group-on (list &key (test #'eql) (key #'identity) (include-key t)) |
|---|
| | 291 | (let ((hash (make-hash-table :test test)) |
|---|
| | 292 | keys) |
|---|
| | 293 | (dolist (el list) |
|---|
| | 294 | (let ((key (funcall key el))) |
|---|
| | 295 | (unless (nth-value 1 (gethash key hash)) |
|---|
| | 296 | (push key keys)) |
|---|
| | 297 | (push el (gethash key hash)))) |
|---|
| | 298 | (mapcar (lambda (key) (let ((keys (nreverse (gethash key hash)))) |
|---|
| | 299 | (if include-key |
|---|
| | 300 | (cons key keys) |
|---|
| | 301 | keys))) |
|---|
| | 302 | (nreverse keys)))) |
|---|
| | 401 | |
|---|
| | 402 | (defun draw-keyboard (&optional (label-function #'key-name)) |
|---|
| | 403 | (pdf:with-saved-state |
|---|
| | 404 | (pdf:translate 70 0) |
|---|
| | 405 | (pdf:scale 25 25) |
|---|
| | 406 | (pdf:set-line-width .05) |
|---|
| | 407 | (pdf:set-rgb-stroke 0.2 0.2 0.2) |
|---|
| | 408 | (let ((keys (sort (group-on *symbolics-map* |
|---|
| | 409 | :test #'eql |
|---|
| | 410 | :key #'key-y |
|---|
| | 411 | :include-key nil) |
|---|
| | 412 | #'> :key (compose #'key-y #'car))) |
|---|
| | 413 | (helvetica (pdf:get-font "Helvetica"))) |
|---|
| | 414 | (do* ((y 0 (incf y))) |
|---|
| | 415 | ((> y 5)) |
|---|
| | 416 | (let ((row (nth (- 5 y) keys))) |
|---|
| | 417 | (pdf:move-to 0 y) |
|---|
| | 418 | (pdf:line-to 20 y) |
|---|
| | 419 | (do* ((i 0 (incf i)) |
|---|
| | 420 | (entry (nth i row) (nth i row)) |
|---|
| | 421 | (x 0)) |
|---|
| | 422 | ((null (nth i row))) |
|---|
| | 423 | (pdf:move-to x y) |
|---|
| | 424 | (pdf:line-to x (+ y 1)) |
|---|
| | 425 | (pdf:stroke) |
|---|
| | 426 | (pdf:in-text-mode |
|---|
| | 427 | (pdf:set-font helvetica 0.3) |
|---|
| | 428 | (pdf:move-text (+ x 0.1) (+ y 0.6)) |
|---|
| | 429 | (let ((label-text (funcall label-function entry))) |
|---|
| | 430 | (cond |
|---|
| | 431 | ((null label-text)) |
|---|
| | 432 | ((= 1 (length label-text)) |
|---|
| | 433 | (pdf:move-text 0 -0.4) |
|---|
| | 434 | (pdf:draw-text (first label-text))) |
|---|
| | 435 | ((= 2 (length label-text)) |
|---|
| | 436 | (pdf:draw-text (first label-text)) |
|---|
| | 437 | (pdf:move-text 0 -0.4) |
|---|
| | 438 | (pdf:draw-text (second label-text))) |
|---|
| | 439 | (t |
|---|
| | 440 | (error "unexpected number of elements in labels list ~S returned by label function for entry ~S" |
|---|
| | 441 | label-text entry))))) |
|---|
| | 442 | (incf x (key-width entry))))) |
|---|
| | 443 | (pdf:move-to 0 6) |
|---|
| | 444 | (pdf:line-to 20 6) |
|---|
| | 445 | (pdf:stroke) |
|---|
| | 446 | (pdf:move-to 20 6) |
|---|
| | 447 | (pdf:line-to 20 0) |
|---|
| | 448 | (pdf:stroke)))) |
|---|
| | 449 | |
|---|
| | 450 | (defun split-label (string) |
|---|
| | 451 | "Split camel case label into multiple words." |
|---|
| | 452 | (cl-ppcre:split "(?<=[a-z])(?=[A-Z])" string)) |
|---|
| | 453 | |
|---|
| | 454 | (defun format-key-name (entry) |
|---|
| | 455 | "Given a Symbolics key definition entry, return one or two strings |
|---|
| | 456 | to be used as label for the key." |
|---|
| | 457 | (if (= 1 (length (key-labels entry))) |
|---|
| | 458 | (split-label (key-name entry)) |
|---|
| | 459 | (reverse (key-labels entry)))) |
|---|
| | 460 | |
|---|
| | 461 | (defun format-key-scancodes (entry) |
|---|
| | 462 | "Given a Symbolics key definition entry, return one or two strings |
|---|
| | 463 | representing the PS/2 scan code of the key." |
|---|
| | 464 | (list (format nil "~{~2,'0X~^ ~}" (map-symbolics-key (key-name entry))) |
|---|
| | 465 | (format nil "~{~2,'0X~^ ~}" (map-symbolics-key (key-name entry) t)))) |
|---|
| | 466 | |
|---|
| | 467 | (defun format-key-ps2-name (entry &optional f-mode) |
|---|
| | 468 | (cond |
|---|
| | 469 | ((equal "ModeLock" (car entry)) |
|---|
| | 470 | '("F-Mode" "Lock")) |
|---|
| | 471 | ((equal "Local" (car entry)) |
|---|
| | 472 | '("F-Mode")) |
|---|
| | 473 | (t |
|---|
| | 474 | (let ((scan-codes (map-symbolics-key (key-name entry) f-mode))) |
|---|
| | 475 | (split-label (or (car (find scan-codes *ps2-map* :key #'cdr :test #'equal)) |
|---|
| | 476 | "")))))) |
|---|
| | 477 | |
|---|
| | 478 | (defun draw-label (x y text &key (size 12) (font-name "Helvetica-Bold")) |
|---|
| | 479 | (let ((helvetica (pdf:get-font font-name))) |
|---|
| | 480 | (pdf:in-text-mode |
|---|
| | 481 | (pdf:set-font helvetica size) |
|---|
| | 482 | (pdf:move-text x y) |
|---|
| | 483 | (pdf:draw-text text)))) |
|---|
| | 484 | |
|---|
| | 485 | (defun draw-layout (&optional (pathname #P"layout.pdf")) |
|---|
| | 486 | (pdf:with-document () |
|---|
| | 487 | (pdf:with-page () |
|---|
| | 488 | (pdf:with-outline-level ("Symbolics keyboard layout" (pdf:register-page-reference)) |
|---|
| | 489 | (draw-label 70 800 "kbdbabel-symbolics Symbolics to PS/2 Adapter key code mapping" :size 14) |
|---|
| | 490 | (draw-label 240 10 "kbdbabel for Symbolics keyboard - by Alexander Kurz and Hans Hübner - http://kbdbabel.net/" |
|---|
| | 491 | :size 8 :font-name "Helvetica") |
|---|
| | 492 | (draw-label 70 35 "PS/2 scan codes (top: standard, bottom: F-mode)") |
|---|
| | 493 | (pdf:translate 0 50) |
|---|
| | 494 | (draw-keyboard #'format-key-scancodes) |
|---|
| | 495 | (draw-label 70 175 "F-Mode mapping") |
|---|
| | 496 | (pdf:translate 0 190) |
|---|
| | 497 | (draw-keyboard (rcurry #'format-key-ps2-name t)) |
|---|
| | 498 | (draw-label 70 175 "Standard mapping") |
|---|
| | 499 | (pdf:translate 0 190) |
|---|
| | 500 | (draw-keyboard #'format-key-ps2-name) |
|---|
| | 501 | (draw-label 70 175 "Key caps") |
|---|
| | 502 | (pdf:translate 0 190) |
|---|
| | 503 | (draw-keyboard #'format-key-name))) |
|---|
| | 504 | (pdf:write-document pathname))) |
|---|