Dvorak Latin-1 extended keyboard for Linux-Emacs

Last modified: Sun May 24 21:32:16 EDT 2020

Base US Dvorak layout from Wikimedia Commons.

Preface

Although the keyboard layout described below should in principle be implementable on any Linux distribution, the implementation details provided are specific to good old distributions like Slackware. Newfangled distributions ship with a heavyweight desktop environment that grabs the modifier keys for its own voluminous list of shortcuts. Figuring out how to make them cooperate is somebody else's job.

Design

Layouts that introduce dead keys or other magical combining behaviors for plain `'"~^ keys are not to my taste. Taking those options off the table, a character such as can still be entered in several other ways:

  1. By X compose sequence: Compose ' a, assuming that a Compose key exists
  2. By code:
  3. By name, in recent Emacs: C-x 8 RET latin small letter a acute RET
  4. By X copypasta:

But Chrome OS introduced me to extended layouts where the Alt key gets you accents, and I'd like to expand on that.

The use case is editing Latin-1 text in Emacs. Both the X keyboard extension (XKB) and Emacs itself get involved in the interpretation of modifier keys; a good solution requires cooperation between the two.

Control and Meta belong to Emacs. Alt is free except for a few combinations that are captured by the X server or window manager.

Muscle memory cannot be argued with. In Emacs, I use the left Control key for all C- combinations and I use Escape for all M- combinations. I literally never touch the right Control key or use Alt as a substitute for the nonexistent Meta key.

XKB already has the layouts us(dvorak-intl) "English (Dvorak, international with dead keys)" and us(dvorak-alt-intl) "English (Dvorak alternative international no dead keys)." Neither one is adequate.

Emacs already has the input methods latin-prefix, latin-1-prefix, latin-1-postfix, and latin-1-alt-postfix. All of them introduce magical behaviors for `'"~^.

New mappings by key

Bottom row modifiers

CSAASHM
Ctrl Super Alt Space Alt Super Hyper Compose
Meta

The right Control key is used for both Meta (unshifted) and Compose (shifted).

Some cheap keyboards omit the right Win (Super) key. No umlauts for you!

Left hand keys

KeyASHASAHSHASH
1!
2@
3#
'"
,<
.>
pP
yY
aA
oO
eE
uU
iI
xX

Right hand keys

KeyASAS
cC
rR
lL
/?
=+
\|
dD
tT
nN
sS
-_SHY
mM
SPNBSP

New mappings by code

See /usr/share/X11/locale/iso8859-1/Compose for alternative, equivalent compose sequences.

CodeCharacterModalCompose
a0NBSPA-SPSP SP
a1A-!!!
a2A-S-c/c
a3A-l-l
a4A-S-Cox
a5H-y=y
a6A-|||
a7S-ss!
a8A-"""
a9S-coc
aaA-S-H-a_a
abA-<<<
acA-\-,
adSHYA----
aeA-rOR
afA-_^-
b0S-d^0
b1A-=+-
b2S-2^2
b3S-3^3
b4A-'''
b5A-m/u
b6A-pp!
b7A-..^
b8A-,,,
b9S-1^1
baA-S-H-o_o
bbA->>>
bcA-114
bdA-212
beA-334
bfA-???
c0H-A`A
c1A-A'A
c2A-S-A^A
c3A-H-A~A
c4S-A"A
c5S-H-AAA
c6A-H-EAE
c7A-C,C
c8H-E`E
c9A-E'E
caA-S-E^E
cbS-E"E
ccH-I`I
cdA-I'I
ceA-S-I^I
cfS-I"I
d0A-D-D
d1A-N~N
d2H-O`O
d3A-O'O
d4A-S-O^O
d5A-H-O~O
d6S-O"O
d7A-xxx
d8S-H-O/O
d9H-U`U
daA-U'U
dbA-S-U^U
dcS-U"U
ddA-Y'Y
deA-TTH
dfA-sss
e0H-a`a
e1A-a'a
e2A-S-a^a
e3A-H-a~a
e4S-a"a
e5S-H-aaa
e6A-H-eae
e7A-c,c
e8H-e`e
e9A-e'e
eaA-S-e^e
ebS-e"e
ecH-i`i
edA-i'i
eeA-S-i^i
efS-i"i
f0A-d-d
f1A-n~n
f2H-o`o
f3A-o'o
f4A-S-o^o
f5A-H-o~o
f6S-o"o
f7A-/-:
f8S-H-o/o
f9H-u`u
faA-u'u
fbA-S-u^u
fcS-u"u
fdA-y'y
feA-tth
ffS-y"y

XKB

In theory, the new mappings could be implemented entirely in XKB. In practice, I found XKB to be overcomplicated, poorly documented, and impenetrable. Therefore, I have made the smallest change that suffices to get XKB out of the way, disregarding collateral damage to XKB's intricate system of rules and templates.

XKB's config files reside in /etc/X11/xkb. Which ones are relevant is influenced by the values of XkbRules, XkbModel, XkbLayout, XkbVariant, and XkbOptions that may be set in /etc/X11/xorg.conf, but XKB's rules and defaults have the final say. The best clue about what XKB is actually doing is the output of setxkbmap -print.

bash-4.3$ fgrep -i xkb /etc/X11/xorg.conf
  Option "XkbModel"   "pc104"
  Option "XkbLayout"  "us"
  Option "XkbVariant" "dvorak"

bash-4.3$ setxkbmap -print 
xkb_keymap {
        xkb_keycodes  { include "evdev+aliases(qwerty)" };
        xkb_types     { include "complete"      };
        xkb_compat    { include "complete"      };
        xkb_symbols   { include "pc+us(dvorak)+inet(evdev)+terminate(ctrl_alt_bksp)"  };
        xkb_geometry  { include "pc(pc104)"     };
};

The link that I chose to break is the definition pc105 in the file symbols/pc. "But your keyboard model is pc104!" Yes, but pc105 is what XKB is using from that file.

Before:

key <LFSH> {        [ Shift_L               ]       };
key <LCTL> {        [ Control_L             ]       };
key <LWIN> {        [ Super_L               ]       };

key <RTSH> {        [ Shift_R               ]       };
key <RCTL> {        [ Control_R             ]       };
key <RWIN> {        [ Super_R               ]       };
key <MENU> {        [ Menu                  ]       };

// Beginning of modifier mappings.
modifier_map Shift  { Shift_L, Shift_R };
modifier_map Lock   { Caps_Lock };
modifier_map Control{ Control_L, Control_R };
modifier_map Mod2   { Num_Lock };
modifier_map Mod4   { Super_L, Super_R };

// Fake keys for virtual<->real modifiers mapping:
key <LVL3> {        [ ISO_Level3_Shift      ]       };
key <MDSW> {        [ Mode_switch           ]       };
modifier_map Mod5   { <LVL3>, <MDSW> };

key <ALT>  {        [ NoSymbol, Alt_L       ]       };
include "altwin(meta_alt)"

key <META> {        [ NoSymbol, Meta_L      ]       };
modifier_map Mod1   { <META> };

key <SUPR> {        [ NoSymbol, Super_L     ]       };
modifier_map Mod4   { <SUPR> };

key <HYPR> {        [ NoSymbol, Hyper_L     ]       };
modifier_map Mod4   { <HYPR> };
// End of modifier mappings.

"altwin(meta_alt)" maps Alt to Meta in a way that is hard to undo. For A- and M- to work as desired in Emacs, XKB has to establish Alt and Meta as distinct modifiers with appropriate keysyms assigned.

After:

key <LFSH> {        [ Shift_L               ]       };
key <LCTL> {        [ Control_L             ]       };
key <LWIN> {        [ Super_L               ]       };
key <LALT> {        [ Alt_L                 ]       };

key <RTSH> {        [ Shift_R               ]       };
key <RALT> {        [ Alt_R                 ]       };
key <RWIN> {        [ Super_R               ]       };
key <MENU> {        [ Hyper_R               ]       };
key <RCTL> {type[Group1]="TWO_LEVEL", [Meta_R, Multi_key]};

modifier_map Shift  { Shift_L, Shift_R };
modifier_map Lock   { Caps_Lock };
modifier_map Control{ Control_L };
modifier_map Mod1   { Alt_L, Alt_R };
modifier_map Mod2   { Num_Lock };
modifier_map Mod3   { Meta_R };
modifier_map Mod4   { Super_L, Super_R };
modifier_map Mod5   { Hyper_R };

Emacs

With the modifier map established by XKB, the character mapping can be done in an Emacs init file.

The code below uses literal characters instead of integers or symbolic names for the characters. The soft hyphen (SHY) and nonbreaking space (NBSP) characters are not self-evident in a web browser.

; -*- coding: iso-8859-1 -*-
(global-set-key [(alt space)]      '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?!)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt super ?c)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?l)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt super ?C)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(hyper ?y)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?|)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?s)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?")]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?c)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt super hyper ?a)] '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?<)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?\\)]        '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?-)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?r)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?_)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?d)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?=)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?2)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?3)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?')]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?m)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?p)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?.)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?,)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?1)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt super hyper ?o)] '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?>)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?1)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?2)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?3)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ??)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(hyper ?A)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?A)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt super ?A)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt hyper ?A)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?A)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super hyper ?A)] '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt hyper ?E)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?C)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(hyper ?E)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?E)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt super ?E)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?E)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(hyper ?I)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?I)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt super ?I)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?I)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?D)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?N)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(hyper ?O)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?O)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt super ?O)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt hyper ?O)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?O)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?x)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super hyper ?O)] '(lambda () (interactive) (insert-char ?)))
(global-set-key [(hyper ?U)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?U)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt super ?U)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?U)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?Y)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?T)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?s)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(hyper ?a)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?a)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt super ?a)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt hyper ?a)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?a)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super hyper ?a)] '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt hyper ?e)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?c)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(hyper ?e)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?e)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt super ?e)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?e)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(hyper ?i)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?i)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt super ?i)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?i)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?d)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?n)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(hyper ?o)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?o)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt super ?o)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt hyper ?o)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?o)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?/)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super hyper ?o)] '(lambda () (interactive) (insert-char ?)))
(global-set-key [(hyper ?u)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?u)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt super ?u)]   '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?u)]       '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?y)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(alt ?t)]         '(lambda () (interactive) (insert-char ?)))
(global-set-key [(super ?y)]       '(lambda () (interactive) (insert-char ?)))

Glitch

Emacs deliberately ignores Caps Lock when other mod keys are down, so you get SIMN BOLVAR when you expected SIMN BOLVAR. Changing this behavior requires patching the C source.

xemacs-21.5.32: file src/event-Xt.c, function x_event_to_emacs_event
"Ignore the Caps_Lock key if:" ...

emacs-26.3: file src/keyboard.c, function make_lispy_event
"Caps-lock shouldn't affect interpretation of key chords:" ...


KB
Home