first commit

This commit is contained in:
xenia 2023-11-09 14:16:16 +01:00
commit 90e336b668
8 changed files with 2807 additions and 0 deletions

View File

@ -0,0 +1,247 @@
from typing import List, Tuple, Optional
import random
import argparse
import sys
import os
from mackb import StateName, ActionID, MapIndex, Modifier, ModMap, \
State, KeyMap, OnPress, ActionReference, Output, EnterState, Action, \
MacKeyboardLayout
import agda_list
import mac_parser
import mackb
import kc
def resolve_onpress(kb: mac_parser.MacKeyboardLayout, p: mackb.OnPress) -> Optional[mackb.Output]:
if isinstance(p, mackb.Output):
return p
elif isinstance(p, mackb.ActionReference):
action = kb.actions[p.ref]
if not mackb.NO_STATE in action.state_actions:
return None
return resolve_onpress(kb, kb.actions[p.ref].state_actions[mackb.NO_STATE])
return None
def where_is(kb: mac_parser.MacKeyboardLayout, ch: str) -> Optional[Tuple[mackb.MapIndex, kc.KeyCode]]:
for map_index in kb.modmap.selects.keys():
for key_code, action in kb.keymaps[map_index].keys.items():
res = resolve_onpress(kb, action)
if isinstance(res, mackb.Output) and res.output == ch:
return map_index, key_code
return None
def encode_char(ch: str) -> str:
if 'a' <= ch <= 'z' or 'A' <= ch <= 'Z' or '0' <= ch <= '9':
return ch
return "_" + hex(ord(ch))[2:] + "_"
def prefix_state_name(prefix: str) -> mackb.StateName:
return mackb.StateName("agda-" + mackb.StateName("".join(map(encode_char, prefix))))
ERROR = -1
INFO = 0
DEBUG = 1
class Agdifier:
def __init__(
self,
entry_keycode: kc.KeyCode,
terminator: str,
unknown_replacement: str,
):
self.entry_keycode = entry_keycode
self.terminator = terminator
self.unknown_replacement = unknown_replacement
self._last_state = 0
def log(self, level: int, *text):
if level == ERROR:
print("[agdifier] (ERROR)", *text, file=sys.stderr)
if level == INFO:
print("[agdifier] (INFO)", *text, file=sys.stderr)
#if level == DEBUG:
# print("[agdifier] (DEBUG)", *text, file=sys.stderr)
def make_new_id(self, prefix: str) -> str:
self._last_state += 1
return f"{prefix}-{self._last_state}"
def new_action(self, kb: mac_parser.MacKeyboardLayout) -> mackb.ActionID:
action_id = mackb.ActionID(self.make_new_id("a"))
kb.actions[action_id] = mackb.Action({})
return action_id
def add_state_if_needed(
self,
kb: mac_parser.MacKeyboardLayout,
state_name: mackb.StateName,
terminator: str,
):
if state_name in kb.states:
return
self.log(DEBUG, f"Adding state {state_name!r}")
kb.states[state_name] = mackb.State(terminator=terminator)
# Ensures that all chars in the sequence has a key that makes it, and that key has an ActionReference
# Returns false if keybind cannot be added
def ensure_keybind(
self,
kb: mac_parser.MacKeyboardLayout,
keybind: str,
) -> bool:
self.log(DEBUG, f"Verifying {keybind!r}")
for ch in keybind:
at = where_is(kb, ch)
if at is None:
self.log(ERROR, f"Keybind {keybind!r} cannot be added: missing char on base layer: {ch!r}")
return False
modmapidx, keycode = at
key_action = kb.keymaps[modmapidx].keys[keycode]
if isinstance(key_action, mackb.EnterState):
self.log(ERROR, f"Keybind {keybind!r} cannot be added: {ch!r} performs an unconditional EnterState")
return False
if isinstance(key_action, mackb.Output):
self.log(DEBUG, f"Key {kb.modmap.selects[modmapidx]}-{keycode} does not enter action (gives {key_action}). Adding new action.")
action_id = self.new_action(kb)
kb.actions[action_id].state_actions[mackb.NO_STATE] = key_action
for state_name in kb.states.keys():
kb.actions[action_id].state_actions[state_name] = key_action
kb.keymaps[modmapidx].keys[keycode] = mackb.ActionReference(action_id)
return True
def add_word(
self,
kb: mac_parser.MacKeyboardLayout,
initial_agda_state: mackb.StateName,
keybind: str,
is_prefix: bool,
target: str
):
self.log(INFO, f"Adding keybind {keybind!r} -> {target!r} (is_prefix = {is_prefix})")
# Create the states
for i in range(len(keybind) + is_prefix):
prefix = keybind[:i]
state_name = prefix_state_name(prefix)
if is_prefix and i == len(keybind):
self.add_state_if_needed(kb, state_name, target)
else:
self.add_state_if_needed(kb, state_name, prefix)
for i in range(len(keybind)):
prefix = keybind[:i]
next_ch = keybind[i]
current_state = prefix_state_name(prefix) if i > 0 else initial_agda_state
modmapidx, keycode = where_is(kb, next_ch) # type: ignore # We know this is not None
key_action = kb.keymaps[modmapidx].keys[keycode]
assert isinstance(key_action, mackb.ActionReference)
action = kb.actions[key_action.ref]
if i == len(keybind) - 1 and not is_prefix:
# last key, output the target
action.state_actions[current_state] = mackb.Output(target)
else:
next_state = prefix_state_name(prefix + next_ch)
action.state_actions[current_state] = mackb.EnterState(next_state)
def agdify(self, kb: mac_parser.MacKeyboardLayout) -> mac_parser.MacKeyboardLayout:
initial_agda_state = mackb.StateName("agda")
kb.states[initial_agda_state] = mackb.State(terminator=self.terminator)
initial_enter = self.new_action(kb)
kb.actions[initial_enter].state_actions[mackb.NO_STATE] = mackb.EnterState(initial_agda_state)
kb.keymaps[mackb.MapIndex(0)].keys[self.entry_keycode] = mackb.ActionReference(initial_enter)
working_words: List[Tuple[str, str]] = []
for keybind, target in agda_list.agda_words:
if self.ensure_keybind(kb, keybind):
working_words.append((keybind, target))
# Start with short words
working_words.sort(key=lambda x: x[0])
working_words.sort(key=lambda x: len(x[0]))
for i, (keybind, target) in enumerate(working_words):
is_prefix = any(x.startswith(keybind) for x, _ in working_words[i+1:])
self.add_word(kb, initial_agda_state, keybind, is_prefix, target)
return kb
if __name__ == "__main__":
parser = argparse.ArgumentParser(
prog="agda_input_method.py",
description="Add an Agda layer to a layout",
)
parser.add_argument(
"input_path",
)
parser.add_argument(
"output_path",
)
parser.add_argument(
"--entry-keycode",
type=int,
default=39, # svorak !
)
parser.add_argument(
"--terminator",
type=str,
default="🐔",
)
parser.add_argument(
"--unknown",
type=str,
default="🐈",
)
parser.add_argument(
"--name",
help="What do name the output keyboard. Default is to add a \" — Agda\" to the name of the input keyboard",
type=str,
required=False
)
opts = parser.parse_args()
def eprint(*text):
print(*text, file=sys.stderr)
if not os.path.isfile(opts.input_path):
eprint(f"Input file does not exist: {opts.input_path}")
exit(1)
if not os.path.isdir(os.path.dirname(opts.output_path)):
eprint(f"Output folder does not exist: {os.path.dirname(opts.output_path)}")
exit(1)
agdifier = Agdifier(
entry_keycode=kc.KeyCode(opts.entry_keycode),
terminator=opts.terminator,
unknown_replacement=opts.unknown
)
eprint("Loading keyboard layout")
kb = mac_parser.parse_keyboard_layout(opts.input_path)
eprint("Agdifying")
agdified = agdifier.agdify(kb)
if opts.name:
agdified.name = opts.name
else:
agdified.name = agdified.name + " — Agda"
eprint("Saving")
mac_parser.save_file(opts.output_path, mac_parser.unparse(kb))
eprint("done")

286
kbtrans/agda_list.py Normal file
View File

@ -0,0 +1,286 @@
import re
from typing import List, Tuple
extras: List[Tuple[str, str]] = [
("cat", "🐱"),
("cat2", "🐈"),
("pl", "🥺"),
("plea", "🥺"),
("pleading", "🥺"),
("fl", "😳"),
("flushed", "😳"),
("crab", "🦀"),
("crazy hamburger", "🍔"),
("oil from iraq", "🛢️"),
("bread from turkie", "🍞"),
("cake", "🍰"),
("gec", "🦎"),
("amongus", ""),
("enby", ""),
("enby1", ""),
("enby2", ""),
("enby3", ""),
("dot", "·"),
("bullet", ""),
("multiocularo", ""),
("pleadpoint", "🥺👉👈"),
("point", "👉👈"),
("inv", "⁻¹"),
]
def parse_source(x: str) -> List[Tuple[str, str]]:
out = []
singular = re.compile(r'!(\S+)\s+(\S)\s+')
for k, v in singular.findall(x):
if '\\' in k or ("{" in k and "}" in k):
continue
if ord(v) < 0xffff:
out.append((k, v))
return out
source = r"""
!   !FB !bR !BGh 𝛈 !MiA 𝐴 !"{I} Ï !c{h} ḩ !amalg ∐ !lambda λ !^\script g ᶢ
!% !FC !bS 𝕊 !BGi 𝛊 !MiB 𝐵 !"{O} Ö !c{k} ķ !aoint ∳ !langle ⟨ !^\turned a ᵄ
!+ !FD !bT 𝕋 !BGk 𝛋 !MiC 𝐶 !"{U} Ü !c{l} ļ !asymp ≍ !lfloor ⌊ !^\turned h ᶣ
!- ­ !FE !bU 𝕌 !BGl 𝛌 !MiD 𝐷 !"{W} Ẅ !c{n} ņ !begin 〖 !ltimes ⋉ !^\turned i ᵎ
!0 !FF !bV 𝕍 !BGm 𝛍 !MiE 𝐸 !"{X} Ẍ !c{r} ŗ !below ┬ !mapsto ↦ !^\turned m ᵚ
!@ !FG !bW 𝕎 !BGn 𝛎 !MiF 𝐹 !"{Y} Ÿ !c{s} ş !cdots ⋯ !models ⊧ !^\turned r ʴ
!C !FH !bX 𝕏 !BGo 𝛚 !MiG 𝐺 !"{a} ä !c{t} ţ !close ┤ !nVDash ⊯ !^\turned v ᶺ
!H ̋ !FI !bY 𝕐 !BGp 𝛙 !MiH 𝐻 !"{e} ë !d-u- ⇵ !coint ∲ !nVdash ⊮ !^\turned w ꭩ
!I !FJ !bZ !BGr 𝛒 !MiI 𝐼 !"{h} ḧ !d\.S Ṩ !colon ₡ !nequiv ≢ !^\turned y 𐞠
!L Ł !FK !ba 𝕒 !BGs 𝛔 !MiJ 𝐽 !"{i} ï !d\.s ṩ !dashv ⊣ !nsimeq ≄ !circledast ⊛
!O Ø !FL !bb 𝕓 !BGt 𝛕 !MiK 𝐾 !"{o} ö !d\=L Ḹ !dddot ⃛ !numero № !complement ∁
!P !FM !bc 𝕔 !BGu 𝛖 !MiL 𝐿 !"{t} ẗ !d\=R Ṝ !ddots ⋱ !nvDash ⊭ !curlywedge ⋏
!S § !FN !bd 𝕕 !BGx 𝛏 !MiM 𝑀 !"{u} ü !d\=l ḹ !delta δ !nvdash ⊬ !eqslantgtr ⋝
!^ ̂ !FO !be 𝕖 !BGz 𝛇 !MiN 𝑁 !"{w} ẅ !d\=r ṝ !doteq ≐ !oiiint ∰ !gtreqqless ⋛
!k ̨ !FP !bf 𝕗 !BPi 𝚷 !MiO 𝑂 !"{x} ẍ !ddag ‡ !equiv ≡ !ominus ⊖ !lessapprox ≲
!v ̌ !FQ !bg 𝕘 !Box !MiP 𝑃 !"{y} ÿ !def= ≝ !frac1 ⅟ !oslash ⊘ !lesseqqgtr ⋚
!x × !FR !bh 𝕙 !Bpi 𝛑 !MiQ 𝑄 !'\.S Ṥ !defs ≙ !frown ⌢ !otimes ⊗ !longmapsto ⟼
!| !FS !bi 𝕚 !Cap !MiR 𝑅 !'\.s ṥ !dong ₫ !gamma γ !permil ‰ !mathscr{I}
!!! !FT !bj 𝕛 !Chi Χ !MiS 𝑆 !'\AE Ǽ !d{A} Ạ !gimel ℷ !peseta ₧ !nLeftarrow ⇍
!!? !FU !bk 𝕜 !Cup !MiT 𝑇 !'\ae ǽ !d{B} Ḅ !gneqq ≩ !pounds £ !nleftarrow ↚
!"' “ !FV !bl 𝕝 !Dei Ϯ !MiU 𝑈 !'{A} Á !d{D} Ḍ !gnsim ⋧ !pprime ″ !nsubseteqq ⊈
!"< « !FW !bm 𝕞 !Eta Η !MiV 𝑉 !'{C} Ć !d{E} Ẹ !iiint ∭ !preceq ≼ !nsupseteqq ⊉
!"> » !FX !bn 𝕟 !Fei Ϥ !MiW 𝑊 !'{E} É !d{H} Ḥ !imath ı !propto ∝ !precapprox ≾
!"A Ä !FY !bo 𝕠 !GTH Θ !MiX 𝑋 !'{G} Ǵ !d{I} Ị !infty ∞ !rangle ⟩ !prohibited 🛇
!"E Ë !FZ !bp 𝕡 !Gl- ƛ !MiY 𝑌 !'{I} Í !d{K} Ḳ !jmath ȷ !rddots ⋰ !registered ®
!"H Ḧ !Fa !bq 𝕢 !Glb ⨅ !MiZ 𝑍 !'{K} Ḱ !d{L} Ḷ !kappa κ !rfloor ⌋ !rightarrow →
!"I Ï !Fb !br 𝕣 !Gth θ !Mia 𝑎 !'{L} Ĺ !d{M} Ṃ !koppa ϟ !rtimes ⋊ !smallamalg ∐
!"O Ö !Fc !bs 𝕤 !H{} ˝ !Mib 𝑏 !'{M} Ḿ !d{N} Ṇ !lamda λ !square □ !smallsmile ⌣
!"U Ü !Fd !bt 𝕥 !Lsh ↰ !Mic 𝑐 !'{N} Ń !d{O} Ọ !lceil ⌈ !squb=n ⋢ !sqsubseteq ⊑
!"W Ẅ !Fe !bv 𝕧 !Lub ⨆ !Mid 𝑑 !'{O} Ó !d{R} Ṛ !ldata 《 !squp=n ⋣ !sqsupseteq ⊒
!"X Ẍ !Ff !bw 𝕨 !MCA 𝓐 !Mie 𝑒 !'{P} Ṕ !d{S} Ṣ !ldots … !stigma ϛ !subsetneqq ⊊
!"Y Ÿ !Fg !by 𝕪 !MCB 𝓑 !Mif 𝑓 !'{R} Ŕ !d{T} Ṭ !lneqq ≨ !subset ⊂ !succapprox ≿
!"` „ !Fh !bz 𝕫 !MCC 𝓒 !Mig 𝑔 !'{S} Ś !d{U} Ụ !lnsim ⋦ !succeq ≽ !supsetneqq ⊋
!"a ä !Fi !cC Ç !MCD 𝓓 !Mih !'{U} Ú !d{V} Ṿ !manat ₼ !supset ⊃ !textlquill ⁅
!"e ë !Fj !cD Ḑ !MCE 𝓔 !Mii 𝑖 !'{W} Ẃ !d{W} Ẉ !micro µ !textmu µ !textnumero №
!"h ḧ !Fk !cE Ȩ !MCF 𝓕 !Mij 𝑗 !'{Y} Ý !d{Y} Ỵ !minus !tugrik ₮ !textrecipe ℞
!"i ï !Fl !cG Ģ !MCG 𝓖 !Mik 𝑘 !'{Z} Ź !d{Z} Ẓ !nabla ∇ !u\'{A} Ắ !textrquill ⁆
!"o ö !Fm !cH Ḩ !MCH 𝓗 !Mil 𝑙 !'{a} á !d{a} ạ !naira ₦ !u\'{a} ắ !underbrace ⏟
!"t ẗ !Fn !cK Ķ !MCI 𝓘 !Mim 𝑚 !'{c} ć !d{b} ḅ !ncong ≇ !u\`{A} Ằ !underparen ⏝
!"u ü !Fo !cL Ļ !MCJ 𝓙 !Min 𝑛 !'{e} é !d{d} ḍ !ngeqq ≱ !u\`{a} ằ !upuparrows ⇈
!"w ẅ !Fp !cN Ņ !MCK 𝓚 !Mio 𝑜 !'{g} ǵ !d{e} ẹ !nleqq ≰ !u\d{A} Ặ !varepsilon ε
!"x ẍ !Fq !cR Ŗ !MCL 𝓛 !Mip 𝑝 !'{i} í !d{h} ḥ !nless ≮ !u\d{a} ặ !Rrightarrow ⇛
!"y ÿ !Fr !cS Ş !MCM 𝓜 !Miq 𝑞 !'{k} ḱ !d{i} ị !notin ∉ !u\~{A} Ẵ !Updownarrow ⇕
!'A Á !Fs !cT Ţ !MCN 𝓝 !Mir 𝑟 !'{l} ĺ !d{k} !nprec !u\~{a} !^\capital b 𐞄
!'C Ć !Ft !cc ç !MCO 𝓞 !Mis 𝑠 !'{m} ḿ !d{l} !nsucc !v\.{S} !^\capital g 𐞒
!'E É !Fu !cd ḑ !MCP 𝓟 !Mit 𝑡 !'{n} ń !d{m} !oiint !v\.{s} !^\capital h 𐞖
!'G Ǵ !Fv !ce ȩ !MCQ 𝓠 !Miu 𝑢 !'{o} ó !d{n} !omega ω !varkai ϗ !^\capital i
!'I Í !Fw !cg ģ !MCR 𝓡 !Miv 𝑣 !'{p} !d{o} !oplus !varphi φ !^\capital l
!'K Ḱ !Fx !ch ḩ !MCS 𝓢 !Miw 𝑤 !'{r} ŕ !d{r} !ounce !varrho ϱ !^\capital n
!'L Ĺ !Fy !ck ķ !MCT 𝓣 !Mix 𝑥 !'{s} ś !d{s} !pound £ !veebar !^\capital r 𐞪
!'M Ḿ !Fz !cn ņ !MCU 𝓤 !Miy 𝑦 !'{u} ú !d{t} !prcue !~\"{O} Ṏ !^\capital u ᶸ
!'N Ń !GA Α !cr ŗ !MCV 𝓥 !Miz 𝑧 !'{w} !d{u} !prime !~\"{o} ṏ !^\capital y 𐞲
!'O Ó !GB Β !cs ş !MCW 𝓦 !Psi Ψ !'{y} ý !d{v} ṿ !ratio !~\'{O} Ṍ !^\greek phi ᵠ
!'P Ṕ !GC Χ !ct ţ !MCX 𝓧 !Rho Ρ !'{z} ź !d{w} !rceil !~\'{U} Ṹ !^\rams horn 𐞑
!'R Ŕ !GD Δ !d- ↓ !MCY 𝓨 !Rsh ↱ !.\=A Ǡ !d{y} ỵ !rdata 》 !~\'{o} ṍ !^\turned ae ᵆ
!'S Ś !GE Ε !d= ⇓ !MCZ 𝓩 !San Ϻ !.\=O Ȱ !d{z} ẓ !rrect ▢ !~\'{u} ṹ !backepsilon ∍
!'U Ú !GF Φ !dd !MCa 𝓪 !Sho Ϸ !.\=a ǡ !euro € !ruble ₽ !~\={O} Ȭ !blacksmiley ☻
!'W Ẃ !GG Γ !dh ð !MCb 𝓫 !Tau Τ !.\=o ȱ !flat ♭ !rupee ₨ !~\={o} ȭ !blacksquare ▪
!'Y Ý !GH Η !dz ↯ !MCc 𝓬 !Yot Ϳ !.{A} Ȧ !flqq « !sampi ϡ !Diamond ◇ !circledcirc ⊚
!'Z Ź !GI Ι !ee !MCd 𝓭 !^Gb ᵝ !.{B} Ḃ !frqq » !sharp ♯ !Digamma Ϝ !circleddash ⊝
!'a á !GK Κ !em — !MCe 𝓮 !^Gc ᵡ !.{C} Ċ !geqq ≧ !shima ϭ !Epsilon Ε !curlyeqprec ⋞
!'c ć !GL Λ !en !MCf 𝓯 !^Gd ᵟ !.{D} Ḋ !gets ← !sigma σ !Omicron Ο !curlyeqsucc ⋟
!'e é !GM Μ !ex ∃ !MCg 𝓰 !^Ge ᵋ !.{E} Ė !glqq „ !simeq ≃ !Uparrow ⇑ !curlypreceq ≼
!'g ǵ !GN Ν !ge ≥ !MCh 𝓱 !^Gf ᵠ !.{F} Ḟ !gneq ≩ !smash ⬍ !Upsilon Υ !diamondsuit ♢
!'i í !GO Ω !gg ≫ !MCi 𝓲 !^Gg ᵞ !.{G} Ġ !grqq “ !smile ⌣ !^\alpha ᵅ !eqslantless ⋜
!'k ḱ !GP Ψ !ii !MCj 𝓳 !^a_ ª !.{H} Ḣ !hbar ℏ !sqcap ⊓ !^\delta ᵟ !nRightarrow ⇏
!'l ĺ !GR Ρ !in ∈ !MCk 𝓴 !^l- ⃖ !.{I} İ !heta ͱ !sqcup ⊔ !^\gamma ˠ !nrightarrow ↛
!'m ḿ !GS Σ !jj !MCl 𝓵 !^lr ⃡ !.{M} Ṁ !hori ϩ !squb= ⊑ !^\schwa ᵊ !ordfeminine ª
!'n ń !GT Τ !kA Ą !MCm 𝓶 !^o_ º !.{N} Ṅ !iint ∬ !squp= ⊒ !^\theta ᶿ !partnership ㉐
!'o ó !GU Υ !kE Ę !MCn 𝓷 !^r- ⃗ !.{O} Ȯ !iota ι !sub=n ⊈ !^\u bar ᶶ !precnapprox ⋨
!'p ṕ !GX Ξ !kI Į !MCo 𝓸 !_Gb ᵦ !.{P} Ṗ !k\=O Ǭ !sup=n ⊉ !afghani ؋ !radioactive ☢
!'r ŕ !GZ Ζ !kO Ǫ !MCp 𝓹 !_Gc ᵪ !.{R} Ṙ !k\=o ǭ !surd3 ∛ !austral ₳ !straightphi φ
!'s ś !Ga α !kU Ų !MCq 𝓺 !_Gf ᵩ !.{S} Ṡ !khei ϧ !surd4 ∜ !backsim ∽ !succcurlyeq ≽
!'u ú !Gb β !ka ą !MCr 𝓻 !_Gg ᵧ !.{T} Ṫ !k{A} Ą !tenge ₸ !because ∵ !succnapprox ⋩
!'w ẃ !Gc χ !ke ę !MCs 𝓼 !_Gr ᵨ !.{W} Ẇ !k{E} Ę !theta θ !between ≬ !thickapprox ≈
!'y ý !Gd δ !ki į !MCt 𝓽 !_lr ͍ !.{X} Ẋ !k{I} Į !times × !bigcirc ◯ !uncertainty ⯑
!'z ź !Ge ε !ko ǫ !MCu 𝓾 !all ∀ !.{Y} Ẏ !k{O} Ǫ !uplus ⊎ !bigodot ⨀ !updownarrow ↕
!(= !Gf φ !ku ų !MCv 𝓿 !and !.{Z} Ż !k{U} Ų !u{\i} ĭ !bigstar !^\Reversed E
!(b !Gg γ !l- !MCw 𝔀 !ast !.{a} ȧ !k{a} ą !vDash !bitcoin !^\Reversed N
!(| !Gh η !l= !MCx 𝔁 !bGG !.{b} !k{e} ę !varpi ϖ !boxplus !^\capital aa 𐞀
!)b !Gi ι !le !MCy 𝔂 !bGP !.{c} ċ !k{i} į !vdash !caution !^\capital oe 𐞣
!*= !Gk κ !ll !MCz 𝔃 !bGS !.{d} !k{o} ǫ !vdots !celsius !^\dz digraph 𐞇
!+ !Gl λ !lq !MIA 𝑨 !bGg !.{e} ė !k{u} ų !v{\i} ǐ !coloneq !^\ls digraph 𐞙
!-: !Gm μ !m= !MIB 𝑩 !bGp !.{f} !l-r- !v{\j} ǰ !ddagger !^\lz digraph 𐞚
!-> !Gn ν !mp !MIC 𝑪 !bot !.{g} ġ !lari !wedge !diamond !^\reversed e 𐞎
!-o !Go ω !mu μ !MID 𝑫 !bra !.{h} !lbag !~{\i} ĩ !digamma ϝ !^\sideways u
!-| !Gp ψ !ne !MIE 𝑬 !bub !.{m} !ldiv !"\'{I} Ḯ !dotplus ∔ !^\top half o ᵔ
!-~ !Gr ρ !ni !MIF 𝑭 !but !.{n} !leqq !"\'{U} Ǘ !drachma ₯ !^\ts digraph 𐞬
!.+ !Gs σ !nu ν !MIG 𝑮 !buw !.{o} ȯ !lira !"\'{i} ḯ !epsilon ϵ !blacklozenge ✦
!.- !Gt τ !o* !MIH 𝑯 !cap !.{p} !lneq !"\'{u} ǘ !eqarray █ !construction 🚧
!.= !Gu υ !o+ !MII 𝑰 !chi χ !.{r} !lnot ¬ !"\={A} Ǟ !eqcolon ≕ !ordmasculine º
!.A Ȧ !Gx ξ !o- !MIJ 𝑱 !ci. !.{s} !lr-- !"\={O} Ȫ !gtrless ≷ !risingdotseq ≓
!.B !Gz ζ !o. !MIK 𝑲 !ciO !.{t} !lr-n !"\={U} Ǖ !guarani ₲ !textcircledP ℗
!.C Ċ !HO Ő !o/ !MIL 𝑳 !cib !.{w} !lr=n !"\={a} ǟ !hryvnia ₴ !textdiscount ⁒
!.D !HU Ű !o= !MIM 𝑴 !ciw !.{x} !male !"\={o} ȫ !leadsto ↝ !triangledown ▿
!.E Ė !Ho ő !oe œ !MIN 𝑵 !clL !.{y} !mill !"\={u} ǖ !lessdot ⋖ !triangleleft ◃
!.F !Hu ű !oo !MIO 𝑶 !clR !.{z} ż !ngeq !"\`{U} Ǜ !lessgtr ≶ !Longleftarrow ⇐
!.G Ġ !Im !or !MIP 𝑷 !cll !<--> !ngtr !"\`{u} ǜ !lesssim ≲ !^\b with hook 𐞅
!.H !Ll !ox !MIQ 𝑸 !clr !<->n !nleq !"\v{U} Ǚ !lozenge ✧ !^\c with curl ᶝ
!.I İ !Mu Μ !pi π !MIR 𝑹 !cuL !<=>n !nmid !"\v{u} ǚ !maltese ✠ !^\d with hook 𐞌
!.M !Nu Ν !pm ± !MIS 𝑺 !cuR !<~nn !nsim !'\.{S} Ṥ !napprox ≉ !^\d with tail 𐞋
!.N !O* !r- !MIT 𝑻 !cul !=\"U Ṻ !odot ⊙ !'\.{s} ṥ !natural ♮ !^\g with hook 𐞓
!.O Ȯ !O+ !r= !MIU 𝑼 !cup !=\"u ṻ !oint ∮ !'{\AE} Ǽ !nearrow ↗ !^\greek gamma ᵞ
!.P !O. !rq !MIV 𝑽 !cur !=\'E Ḗ !perp ⊥ !'{\ae} ǽ !newline !^\h with hook ʱ
!.R !OE Œ !ss ß !MIW 𝑾 !c{} ¸ !=\'O Ṓ !peso ₱ !.\={A} Ǡ !nexists ∄ !^\l with belt 𐞛
!.S !Or !t= !MIX 𝑿 !d-2 !=\'e ḗ !prec ≺ !.\={O} Ȱ !nomisma 𐆎 !^\ligature oe ꟹ
!.T !Ox !th þ !MIY 𝒀 !d-| !=\'o ṓ !prod ∏ !.\={a} ǡ !npreceq ⋠ !^\m with hook ᶬ
!.W !Pi Π !to !MIZ 𝒁 !d== !=\AE Ǣ !qdrt !.\={o} ȱ !nsubset !^\r with tail 𐞨
!.X !Re !u+ !MIa 𝒂 !dag !=\`E !quad !=\"{U} Ṻ !nsucceq ⋡ !^\s with curl 𐞺
!.Y !TH Þ !u- !MIb 𝒃 !dd- !=\`O !r-l- !=\"{u} ṻ !nsupset ⊅ !^\s with hook ᶳ
!.Z Ż !U+ !u. !MIc 𝒄 !dei ϯ !=\`e !rbag !=\'{E} Ḗ !nwarrow ↖ !^\v with hook ᶹ
!.a ȧ !U. !u= !MId 𝒅 !di. !=\`o !rect !=\'{O} Ṓ !omicron ο !^\z with curl ᶽ
!.b !Un !uA Ă !MIe 𝒆 !dib !=\ae ǣ !rial !=\'{e} ḗ !partial ∂ !bigtriangleup △
!.c ċ !Uo ő !uE Ĕ !MIf 𝒇 !div ÷ !={A} Ā !sdiv !=\'{o} ṓ !phantom ⟡ !blacktriangle ▴
!.d !Xi Ξ !uG Ğ !MIg 𝒈 !diw !={E} Ē !shei ϣ !=\`{E} !pilcrow !divideontimes
!.e ė ![[ !uI Ĭ !MIh 𝒉 !dl- !={G} !sqrt !=\`{O} !ppprime !fallingdotseq
!.f !]] !uO Ŏ !MIi 𝒊 !dl= !={I} Ī !squb !=\`{e} !precsim !hookleftarrow
!.g ġ !^( !uU Ŭ !MIj 𝒋 !dr- !={O} Ō !squp !=\`{o} !searrow !leftarrowtail
!.h !^) !ua ă !MIk 𝒌 !dr= !={U} Ū !st12 !={\AE} Ǣ !section § !leftharpoonup
!.m !^+ !ue ĕ !MIl 𝒍 !ell !={Y} Ȳ !star !={\ae} ǣ !sqrt[3] !longleftarrow
!.n !^- !ug ğ !MIm 𝒎 !end !={a} ā !sub= !Bbb{1} 𝟙 !sqrt[4] !looparrowleft
!.o ȯ !^0 !ui ĭ !MIn 𝒏 !eta η !={e} ē !subn !Bbb{2} 𝟚 !succsim !measuredangle
!.p !^1 ¹ !un !MIo 𝒐 !exn !={g} !succ !Bbb{A} 𝔸 !swarrow !ntriangleleft
!.r !^2 ² !uo ŏ !MIp 𝒑 !fei ϥ !={i} ī !sup= !Bbb{B} 𝔹 !textwon !shortparallel
!.s !^3 ³ !uu ŭ !MIq 𝒒 !flq !={o} ō !supn !Bbb{C} !uparrow !smallsetminus
!.t !^4 !vA Ǎ !MIr 𝒓 !frq !={u} ū !surd !Bbb{D} 𝔻 !upsilon υ !textbigcircle
!.w !^5 !vC Č !MIs 𝒔 !gen !={y} ȳ !u-d- !Bbb{E} 𝔼 !varbeta ϐ !textestimated
!.x !^6 !vD Ď !MIt 𝒕 !ggg !>~nn !u\'A Ắ !Bbb{F} 𝔽 !warning ⚠ !triangleright ▹
!.y !^7 !vE Ě !MIu 𝒖 !glb !BGTH 𝚯 !u\'a ắ !Bbb{G} 𝔾 !BOmicron 𝚶 !upleftharpoon ↿
!.z ż !^8 !vG Ǧ !MIv 𝒗 !glq !BGth 𝛉 !u\`A !Bbb{H} !Bomicron 𝛐 !Leftrightarrow
!:: !^9 !vH Ȟ !MIw 𝒘 !grq !Beta Β !u\`a !Bbb{I} 𝕀 !^\h hook ʱ !Longrightarrow
!:= !^= !vI Ǐ !MIx 𝒙 !iff !Heta Ͱ !u\~A !Bbb{J} 𝕁 !^\open e !^\closed omega 𐞤
!:~ !^B !vK Ǩ !MIy 𝒚 !inf !Hori Ϩ !u\~a !Bbb{K} 𝕂 !^\open o !^\dezh digraph 𐞊
!<- !^D !vL Ľ !MIz 𝒛 !inn !H{O} Ő !ud-| !Bbb{L} 𝕃 !angstrom !^\feng digraph 𐞐
!<< !^F !vN Ň !McA 𝒜 !int !H{U} Ű !u{A} Ă !Bbb{M} 𝕄 !approxeq !^\tesh digraph 𐞮
!<n !^K !vO Ǒ !McB !kip !H{o} ő !u{E} Ĕ !Bbb{N} !backcong !^\turned alpha
!<| !^L !vR Ř !McC 𝒞 !k{} ˛ !H{u} ű !u{G} Ğ !Bbb{O} 𝕆 !barwedge !curvearrowleft
!<~ !^M !vS Š !McD 𝒟 !l-- !Iota Ι !u{I} Ĭ !Bbb{P} !bigoplus !downdownarrows
!=: !^N !vT Ť !McE !l-2 !Join !u{O} Ŏ !Bbb{Q} !bigsqcup !hookrightarrow
!== !^P !vU Ǔ !McF !l-> !Khei Ϧ !u{U} Ŭ !Bbb{R} !biguplus !leftleftarrows
!=> !^Q !vZ Ž !McG 𝒢 !l-n !Shei Ϣ !u{a} ă !Bbb{S} 𝕊 !bigwedge !leftrightarrow
!=A Ā !^R ᴿ !va ǎ !McH !l-| !U{o} ő !u{e} ĕ !Bbb{T} 𝕋 !boxminus !leftthreetimes
!=E Ē !^S Ŝ !vc č !McI !l== !Vert !u{g} ğ !Bbb{U} 𝕌 !boxtimes !longrightarrow
!=G !^T !vd ď !McJ 𝒥 !l=n !Zeta Ζ !u{i} ĭ !Bbb{V} 𝕍 !circledR ® !looparrowright
!=I Ī !^V !ve ě !McK 𝒦 !ldq !^Gth ᶿ !u{o} ŏ !Bbb{W} 𝕎 !circledS !multiplication ×
!=O Ō !^Y Ŷ !vg ǧ !McL !len !^\'A Ấ !u{u} ŭ !Bbb{X} 𝕏 !clubsuit ♣ !nshortparallel ∦
!=U Ū !^Z !vh ȟ !McM !lhd !^\'E Ế !v\.S Ṧ !Bbb{Y} 𝕐 !cruzeiro ₢ !ntriangleright ⋫
!=Y Ȳ !^b !vi ǐ !McN 𝒩 !ll- !^\'O Ố !v\.s ṧ !Bbb{Z} !curlyvee ⋎ !rightarrowtail ↣
!=a ā !^d !vj ǰ !McO 𝒪 !lr- !^\'a ấ !vbar │ !Bumpeq ≎ !currency ¤ !rightharpoonup ⇀
!=e ē !^f !vk ǩ !McP 𝒫 !lr= !^\'e ế !v{A} Ǎ !Frowny ☹ !diameter ⌀ !sphericalangle ∢
!=g !^k !vl ľ !McQ 𝒬 !lr~ !^\'o ố !v{C} Č !Gangia Ϫ !division ÷ !textopenbullet ◦
!=i ī !^m !vn ň !McR !lub !^\Ae !v{D} Ď !Lambda Λ !doteqdot !trianglelefteq
!=n !^n !vo ǒ !McS 𝒮 !mho !^\Ou !v{E} Ě !Letter !emptyset !uprightharpoon
!=u ū !^p !vr ř !McT 𝒯 !mid !^\`A !v{G} Ǧ !Smiley !geqslant !^\H With Stroke
!=y ȳ !^q 𐞥 !vs š !McU 𝒰 !neg ¬ !^\`E !v{H} Ȟ !Stigma Ϛ !gnapprox !^\bottom half o
!>= !^t !vt ť !McV 𝒱 !neq !^\`O !v{I} Ǐ !Subset !hphantom !^\h with stroke 𐞕
!>> !^x ˣ !vu ǔ !McW 𝒲 !nin !^\`a !v{K} Ǩ !Supset !intercal !^\i with stroke
!>n !_( !vz ž !McX 𝒳 !not ̸ !^\`e !v{L} Ľ !Vvdash !leqslant !^\o with stroke 𐞢
!>~ !_) !wp !McY 𝒴 !o-- !^\`o !v{N} Ň !^\'{A} Ấ !llcorner ⌞ !^\turned open e ᵌ
!?= !_+ !wr !McZ 𝒵 !ohm !^\ae 𐞃 !v{O} Ǒ !^\'{E} Ế !lnapprox ⋦ !^\turned r hook ʵ
!?? !_- !xi ξ !Mca 𝒶 !or= !^\~A !v{R} Ř !^\'{O} Ố !lrcorner ⌟ !bigtriangledown ▽
!AA Å !_0 !z: !Mcb 𝒷 !pab !^\~E !v{S} Š !^\'{a} ấ !multimap ⊸ !circlearrowleft ↺
!AE Æ !_1 !{{ !Mcc 𝒸 !paw !^\~O !v{T} Ť !^\'{e} ế !parallel ∥ !curvearrowright ↷
!B0 𝟎 !_2 !|) !Mcd 𝒹 !per !^\~a !v{U} Ǔ !^\'{o} ố !pppprime ⁗ !downleftharpoon ⇃
!B1 𝟏 !_3 !|- !Mce !phi ϕ !^\~e !v{Z} Ž !^\`{A} !precnsim !leftharpoondown
!B2 𝟐 !_4 !|= !Mcf 𝒻 !psi ψ !^\~o !v{a} ǎ !^\`{E} !setminus !leftrightarrows
!B3 𝟑 !_5 !|> !Mcg !qed !^{A} Â !v{c} č !^\`{O} !shortmid !nLeftrightarrow
!B4 𝟒 !_6 !|n !Mch 𝒽 !r-- !^{C} Ĉ !v{d} ď !^\`{a} !spesmilo !nleftrightarrow
!B5 𝟓 !_7 !|| !Mci 𝒾 !r-2 !^{E} Ê !v{e} ě !^\`{e} !sqsubset !ntrianglelefteq
!B6 𝟔 !_8 !}} !Mcj 𝒿 !r-3 !^{G} Ĝ !v{g} ǧ !^\`{o} !sqsupset !rightleftarrows
!B7 𝟕 !_9 !~- !Mck 𝓀 !r-> !^{H} Ĥ !v{h} ȟ !^\beta !subseteq !rightthreetimes
!B8 𝟖 !_= !~= !Mcl 𝓁 !r-n !^{I} Î !v{i} ǐ !^\d{A} !succnsim !squigarrowright
!B9 𝟗 !__ _ !~A Ã !Mcm 𝓂 !r-o !^{J} Ĵ !v{j} ǰ !^\d{E} !supseteq !textinterrobang
!BA 𝐀 !_a !~E !Mcn 𝓃 !r-| !^{O} Ô !v{k} ǩ !^\d{O} !textbaht ฿ !textmusicalnote
!BB 𝐁 !_e !~I Ĩ !Mco !r== !^{S} Ŝ !v{l} ľ !^\d{a} !textdied !trianglerighteq
!BC 𝐂 !_h !~N Ñ !Mcp 𝓅 !r=n !^{U} Û !v{n} ň !^\d{e} !textlira !vartriangleleft
!BD 𝐃 !_i !~O Õ !Mcq 𝓆 !rdq !^{W} Ŵ !v{o} ǒ !^\d{o} !textpeso !^\heng with hook 𐞗
!BE 𝐄 !_j !~U Ũ !Mcr 𝓇 !rhd !^{Y} Ŷ !v{r} ř !^\heng !thicksim !circlearrowright
!BF 𝐅 !_k !~V !Mcs 𝓈 !rho ρ !^{Z} !v{s} š !^\iota !triangle !downrightharpoon
!BG 𝐆 !_l !~Y !Mct 𝓉 !rr- !^{a} â !v{t} ť !^\lezh 𐞞 !ulcorner !ntrianglerighteq
!BH 𝐇 !_m !~a ã !Mcu 𝓊 !san ϻ !^{c} ĉ !v{u} ǔ !^\~{A} !underbar !rightharpoondown
!BI 𝐈 !_n !~e !Mcv 𝓋 !sbs !^{e} ê !v{z} ž !^\~{E} !undertie !rightrightarrows
!BJ 𝐉 !_o !~i ĩ !Mcw 𝓌 !sho ϸ !^{g} ĝ !zeta ζ !^\~{O} !urcorner !twoheadleftarrow
!BK 𝐊 !_p !~o õ !Mcx 𝓍 !sim !^{h} ĥ !||-n !^\~{a} !varkappa ϰ !vartriangleright
!BL 𝐋 !_r !~u ũ !Mcy 𝓎 !som !^{i} î !||=n !^\~{e} !varprime !^\r with fishhook 𐞩
!BM 𝐌 !_s !~v !Mcz 𝓏 !sq. !^{j} ĵ !|||- !^\~{o} !varsigma ς !^\reversed open e
!BN 𝐍 !_t !~y !MfA 𝔄 !sqo !^{o} ô !~\"O Ṏ !^{TEL} ℡ !vartheta ϑ !blacktriangledown ▾
!BO 𝐎 !_u !~~ !MfB 𝔅 !st6 !^{s} ŝ !~\"o ṏ !approx ≈ !vphantom ⇳ !blacktriangleleft ◂
!BP 𝐏 !_x !"{} ¨ !MfC !st8 ✴ !^{u} û !~\'O Ṍ !asmash ⬆ !Downarrow ⇓ !leftrightharpoons ⇋
!BQ 𝐐 !_~ ̰ !'\O Ǿ !MfD 𝔇 !sub ⊂ !^{w} ŵ !~\'U Ṹ !bigcap ⋂ !Leftarrow ⇐ !rightleftharpoons ⇌
!BR 𝐑 !`A À !'\o ǿ !MfE 𝔈 !sum ∑ !^{y} ŷ !~\'o ṍ !bigcup !^\upsilon ᶷ !textcolonmonetary ₡
!BS 𝐒 !`E È !'{} ´ !MfF 𝔉 !sup ⊃ !^{z} ẑ !~\'u ṹ !bigvee !backprime !textreferencemark ※
!BT 𝐓 !`I Ì !--> !MfG 𝔊 !tau τ !`{A} À !~\=O Ȭ !bowtie !backsimeq !twoheadrightarrow
!BU 𝐔 !`N Ǹ !->n !MfH !tie !`{E} È !~\=o ȭ !bumpeq !bigotimes !Longleftrightarrow
!BV 𝐕 !`O Ò !.=. !MfI !top !`{I} Ì !~{A} Ã !c\'{C} Ḉ !biohazard ☣ !^\n with left hook ᶮ
!BW 𝐖 !`U Ù !.{} ˙ !MfJ 𝔍 !u-2 !`{N} Ǹ !~{E} !c\'{c} ḉ !brokenbar ¦ !^\u with left hook ꭟ
!BX 𝐗 !`W !::- !MfK 𝔎 !u-| !`{O} Ò !~{I} Ĩ !c\u{E} !centerdot · !blacktriangleright
!BY 𝐘 !`Y !<-- !MfL 𝔏 !u== !`{U} Ù !~{N} Ñ !c\u{e} !checkmark !longleftrightarrow
!BZ 𝐙 !`a à !<-> !MfM 𝔐 !ud- !`{W} !~{O} Õ !circeq !copyright © !textpertenthousand
!Ba 𝐚 !`e è !<-n !MfN 𝔑 !ud= !`{Y} !~{U} Ũ !coprod !dotsquare !^\v with right hook 𐞰
!Bb 𝐛 !`i ì !<=> !MfO 𝔒 !ul- !`{a} à !~{V} !d\.{S} !downarrow !leftrightsquigarrow
!Bc 𝐜 !`n ǹ !<=n !MfP 𝔓 !ul= !`{e} è !~{Y} !d\.{s} !facsimile !textfractionsolidus
!Bd 𝐝 !`o ò !<~n !MfQ 𝔔 !ur- !`{i} ì !~{a} ã !d\={L} !gtrapprox !^\capital inverted r ʶ
!Be 𝐞 !`u ù !=== !MfR !ur= !`{n} ǹ !~{e} !d\={R} !gtreqless !^\turned r with hook ʵ
!Bf 𝐟 !`w !==n !MfS 𝔖 !uu- !`{o} ò !~{i} ĩ !d\={l} !gvertneqq !^\turned y with belt 𐞡
!Bg 𝐠 !`y !=>n !MfT 𝔗 !u{} ˘ !`{u} ù !~{n} ñ !d\={r} !heartsuit !^\capital g with hook 𐞔
!Bh 𝐡 !aa å !={} ¯ !MfU 𝔘 !vee !`{w} !~{o} õ !dagger !increment !^\capital l with belt 𐞜
!Bi 𝐢 !ae æ !>=n !MfV 𝔙 !v{} ˇ !`{y} !~{u} ũ !daleth !lambdabar ƛ !^\j with crossed-tail
!Bj 𝐣 !b+ !>~n !MfW 𝔚 !won !and= !~{v} !ddddot !leftarrow !^\l with middle tilde
!Bk 𝐤 !b- !And !MfX 𝔛 !yen ¥ !atop ¦ !~{y} !degree ° !lesseqgtr !^\l with palatal hook
!Bl 𝐥 !b. !BGA 𝚨 !MfY 𝔜 !|-n !beta β !'{\O} Ǿ !dsmash ⬇ !llbracket 〚 !^\t with palatal hook ᶵ
!Bm 𝐦 !b0 𝟘 !BGB 𝚩 !MfZ !|=n !beth !'{\o} ǿ !eqcirc ≖ !lvertneqq ≨ !^\d with hook and tail 𐞍
!Bn 𝐧 !b1 𝟙 !BGC 𝚾 !Mfa 𝔞 !||- !c\'C Ḉ !={\i} ī !exists ∃ !ngeqslant ≱ !^\dz digraph with curl 𐞉
!Bo 𝐨 !b2 𝟚 !BGD 𝚫 !Mfb 𝔟 !||= !c\'c ḉ !Alpha Α !female ♀ !nleqslant ≰ !^\tc digraph with curl 𐞫
!Bp 𝐩 !b3 𝟛 !BGE 𝚬 !Mfc 𝔠 !||n !c\uE !Delta Δ !forall !nparallel !^\capital i with stroke
!Bq 𝐪 !b4 𝟜 !BGF 𝚽 !Mfd 𝔡 !~-n !c\ue !Gamma Γ !frac12 ½ !nshortmid !^\dotless j with stroke
!Br 𝐫 !b5 𝟝 !BGG 𝚪 !Mfe 𝔢 !~=n !cdot · !Kappa Κ !frac13 !nsubseteq !^\l with retroflex hook
!Bs 𝐬 !b6 𝟞 !BGH 𝚮 !Mff 𝔣 !~{} ˜ !cedi !Koppa Ϟ !frac14 ¼ !nsupseteq !^\n with retroflex hook
!Bt 𝐭 !b7 𝟟 !BGI 𝚰 !Mfg 𝔤 !~~- !cent ¢ !Lamda Λ !frac15 !octagonal 🛑 !^\reversed glottal stop ˤ
!Bu 𝐮 !b8 𝟠 !BGK 𝚱 !Mfh 𝔥 !~~n !ci.. !Omega Ω !frac16 !overbrace !^\t with retroflex hook 𐞯
!Bv 𝐯 !b9 𝟡 !BGL 𝚲 !Mfi 𝔦 !~~~ !circ !Sampi Ϡ !frac18 !overparen !^\z with retroflex hook
!Bw 𝐰 !bA 𝔸 !BGM 𝚳 !Mfj 𝔧 !"\'I Ḯ !comp ∘ !Shima Ϭ !frac23 ⅔ !paragraph ¶ !^\closed reversed open e 𐞏
!Bx 𝐱 !bB 𝔹 !BGN 𝚴 !Mfk 𝔨 !"\'U Ǘ !cong ≅ !Sigma Σ !frac25 ⅖ !pitchfork ⋔ !^\l with inverted lazy s ꭝ
!By 𝐲 !bC !BGO 𝛀 !Mfl 𝔩 !"\'i ḯ !c{C} Ç !Theta Θ !frac34 ¾ !rrbracket 〛 !^\turned m with long leg ᶭ
!Bz 𝐳 !bD 𝔻 !BGP 𝚿 !Mfm 𝔪 !"\'u ǘ !c{D} Ḑ !Vdash ⊩ !frac35 ⅗ !spadesuit ♠ !^\turned r with long leg 𐞦
!DH Ð !bE 𝔼 !BGR 𝚸 !Mfn 𝔫 !"\=A Ǟ !c{E} Ȩ !^\ain ᵜ !frac38 ⅜ !subseteqq ⊆ !^\lezh with retroflex hook 𐞟
!Dd !bF 𝔽 !BGS 𝚺 !Mfo 𝔬 !"\=O Ȫ !c{G} Ģ !^\chi ᵡ !frac45 ⅘ !subsetneq ⊊ !^\dotless j with stroke and hook 𐞘
!F0 !bG 𝔾 !BGT 𝚻 !Mfp 𝔭 !"\=U Ǖ !c{H} Ḩ !^\eng ᵑ !frac56 ⅚ !supseteqq ⊇ !^\dz digraph with retroflex hook 𐞈
!F1 !bH !BGU 𝚼 !Mfq 𝔮 !"\=a ǟ !c{K} Ķ !^\esh ᶴ !frac58 ⅝ !supsetneq ⊋ !^\l with retroflex hook and belt 𐞝
!F2 !bI 𝕀 !BGX 𝚵 !Mfr 𝔯 !"\=o ȫ !c{L} Ļ !^\eth ᶞ !frac78 ⅞ !telephone ℡ !^\ts digraph with retroflex hook 𐞭
!F3 !bJ 𝕁 !BGZ 𝚭 !Mfs 𝔰 !"\=u ǖ !c{N} Ņ !^\ezh ᶾ !gangia ϫ !textnaira ₦ !^\turned r with long leg and retroflex hook 𐞧
!F4 !bK 𝕂 !BGa 𝛂 !Mft 𝔱 !"\`U Ǜ !c{R} Ŗ !^\phi ᶲ !gtrdot ⋗ !therefore ∴
!F5 !bL 𝕃 !BGb 𝛃 !Mfu 𝔲 !"\`u ǜ !c{S} Ş !^{SM} ℠ !gtrsim ≳ !triangleq ≜
!F6 !bM 𝕄 !BGc 𝛘 !Mfv 𝔳 !"\vU Ǚ !c{T} Ţ !^{TM} ™ !hsmash ⬌ !varpropto ∝
!F7 !bN !BGd 𝛅 !Mfw 𝔴 !"\vu ǚ !c{c} ç !^{\j} ĵ !iiiint ⨌ !Lleftarrow ⇚
!F8 !bO 𝕆 !BGe 𝛆 !Mfx 𝔵 !"{A} Ä !c{d} ḑ !above ┴ !k\={O} Ǭ !Rightarrow ⇒
!F9 !bP !BGf 𝛗 !Mfy 𝔶 !"{E} Ë !c{e} ȩ !aleph ℵ !k\={o} ǭ !^\Barred B ᴯ
!FA !bQ !BGg 𝛄 !Mfz 𝔷 !"{H} Ḧ !c{g} ģ !alpha α !kelvin !^\barred o ᶱ
"""
agda_words = extras + parse_source(source)
if __name__ == "__main__":
print(f"{len(agda_words)} keybinds")

144
kbtrans/generate_xkb.py Normal file
View File

@ -0,0 +1,144 @@
from typing import Set
import sys
import kb
import kc
import mac_parser
IN_PATH = sys.argv[1]
OUT_PATH = sys.argv[2]
mac_kb = mac_parser.parse_keyboard_layout(IN_PATH)
keyboard = kb.from_mac(mac_kb)
with open(OUT_PATH, "w") as out_file:
out_file.write("xkb_keymap {\n")
out_file.write(' xkb_keycodes "mjaucodes"{\n')
for knr, name in kc.MAC_KEY_NAMES.items():
key = kc.KeyCode(knr)
for lin_n, lin_name in key.linux_keycodes():
out_file.write(f' {lin_name} = {lin_n}; // {key}\n')
out_file.write(' <BKSP> = 22; // Backspace\n')
out_file.write(' <LSHF> = 50; // Left shift\n')
out_file.write(' <RSHF> = 62; // Right alt\n')
out_file.write(' <LALT> = 64; // Left alt\n')
out_file.write(' <RALT> = 108; // Right alt\n')
out_file.write(' <ESC> = 9; // Escape\n')
out_file.write(' <TAB> = 23; // Tab\n')
out_file.write(' <RET> = 36; // Return\n')
out_file.write(' <LCTR> = 37; // Left Control\n')
out_file.write(' <RCTR> = 105; // Right Control\n')
out_file.write(' <UP> = 111; // Arrow up\n')
out_file.write(' <DOWN> = 116; // Arrow down\n')
out_file.write(' <LEFT> = 113; // Arrow left\n')
out_file.write(' <RGHT> = 114; // Arrow right\n')
out_file.write(' <CAPS> = 66; // Caps lock\n')
out_file.write(' };\n')
out_file.write('''\
xkb_types "mjautypes" {
virtual_modifiers NumLock,Alt,LevelThree,LAlt,RAlt,RControl,LControl,ScrollLock,LevelFive,AltGr,Meta,Super,Hyper;
type "ONE_LEVEL" {
modifiers= none;
level_name[Level1]= "Any";
};
type "ALPHABETIC" {
modifiers= Shift+Lock;
map[Shift]= Level2;
map[Lock]= Level2;
level_name[Level1]= "Base";
level_name[Level2]= "Caps";
};
type "ALTABLE" {
modifiers= Shift+Mod2;
map[Shift]= Level2;
map[Mod2]= Level3;
map[Shift+Mod2]= Level4;
level_name[Level1]= "Base";
level_name[Level2]= "Shift";
level_name[Level3]= "Alt Base";
level_name[Level4]= "Shift Alt";
};
};
xkb_compatibility "mjaucomp" {
virtual_modifiers NumLock,Alt,LevelThree,LAlt,RAlt,RControl,LControl,ScrollLock,LevelFive,AltGr,Meta,Super,Hyper;
interpret.useModMapMods= AnyLevel;
interpret.repeat= False;
interpret.locking= False;
interpret LSHF+AnyOfOrNone(all) {
action= SetMods(modifiers=Shift,clearLocks);
};
interpret LSHF+AnyOfOrNone(all) {
action= SetMods(modifiers=Shift,clearLocks);
};
interpret LALT+AnyOf(all) {
action= SetMods(modifiers=Mod2,clearLocks);
};
interpret RALT+AnyOf(all) {
action= SetMods(modifiers=modMapMode,clearLocks);
};
};
''')
out_file.write('''\
xkb_symbols "mjau"{
key <LALT> { [ Alt_L ] };
key <RALT> { [ Alt_R ] };
key <LSHF> { [ Shift_L ] };
key <RSHF> { [ Shift_R ] };
key <LCTR> { [ Control_L ] };
key <RCTR> { [ Control_R ] };
key <BKSP> { [ BackSpace ] };
key <TAB> { [ Tab ] };
key <ESC> { [ Escape ] };
key <CAPS> { [ Escape ] };
key <RET> { [ Return ] };
key <UP> { [ Up ] };
key <DOWN> { [ Down ] };
key <LEFT> { [ Left ] };
key <RGHT> { [ Right ] };
modifier_map Control { <LCTR> };
modifier_map Control { <RCTR> };
modifier_map Mod1 { <RALT> };
modifier_map Mod2 { <LALT> };
''')
def get_output(layer: kb.Layer, kc: kb.KeyCode, mods: Set[kb.Modifier]) -> str:
key = kb.Key(kc, mods)
if key not in layer.buttons:
return "none"
press = layer.buttons[key]
if not isinstance(press, kb.Output):
return "none"
if len(press.output) != 1:
print(f"Ignoring multi-output key {kc}+{mods}")
return "none"
return "U" + hex(ord(press.output[0]))[2:].rjust(4, "0")
layer = keyboard.layers[keyboard.default_layer]
for keycode in kc.KEYCODES:
none = get_output(layer, keycode, set())
shifted = get_output(layer, keycode, set([kb.Modifier.SHIFT]))
alted = get_output(layer, keycode, set([kb.Modifier.OPTION]))
shalted = get_output(layer, keycode, set([kb.Modifier.SHIFT, kb.Modifier.OPTION]))
if none == shifted == alted == shalted == "none":
continue
for _, lin_name in keycode.linux_keycodes():
out_file.write(f' key {lin_name} {{ type = "ALTABLE", symbols[Group1]= [ {none}, {shifted}, {alted}, {shalted} ] }}; // {keycode}\n')
out_file.write(' };\n')
out_file.write(' xkb_geometry "mjauometry" {};\n')
out_file.write("};\n")
print("Wrote to", OUT_PATH)

143
kbtrans/kb.py Normal file
View File

@ -0,0 +1,143 @@
# "native" representation of a keyboard layout
from __future__ import annotations
from typing import Dict, List, NewType, Set, Any, Optional, TYPE_CHECKING
from abc import ABC
from enum import Enum
if TYPE_CHECKING:
import mackb
from kc import KeyCode
LayerName = NewType("LayerName", str)
class Modifier(Enum):
SHIFT = "anyShift"
OPTION = "anyOption"
@staticmethod
def from_name(name: str) -> Modifier:
for mod in Modifier:
if mod.value == name:
return mod
raise ValueError(f"No such modifier {name}")
def shorthand(self) -> str:
if self == Modifier.SHIFT:
return "S"
elif self == Modifier.OPTION:
return "A"
raise ValueError(self)
def __lt__(self, other: Modifier) -> bool:
return self.value < other.value
class Key:
def __init__(self, keycode: KeyCode, modifiers: Set[Modifier]):
self.keycode = keycode
self.modifiers = modifiers
def __str__(self) -> str:
return "-".join([*[m.shorthand() for m in self.modifiers], f"{self.keycode}"])
def __eq__(self, other: Any) -> bool:
if isinstance(other, Key):
return self.keycode == other.keycode and self.modifiers == other.modifiers
return False
def __hash__(self) -> int:
return hash((self.keycode, tuple(sorted(list(self.modifiers))))) ^ 0xE621
class ButtonPress(ABC):
pass
class Output(ButtonPress):
def __init__(self, output: str):
self.output = output
def __repr__(self) -> str:
return f"Output({self.output!r})"
class ChangeLayer(ButtonPress):
def __init__(self, layer: LayerName):
self.layer = layer
def __repr__(self) -> str:
return f"ChangeLayer({self.layer!r})"
class Layer:
def __init__(
self,
terminator: Optional[str],
buttons: Dict[Key, ButtonPress],
):
self.terminator = terminator
self.buttons = buttons
class KeyboardLayout:
def __init__(
self,
name: str,
default_layer: LayerName,
layers: Dict[LayerName, Layer],
):
self.name = name
self.default_layer = default_layer
self.layers = layers
def from_mac(mac: mackb.MacKeyboardLayout) -> KeyboardLayout:
import mackb
layers = {}
for (name, state) in mac.states.items():
layers[LayerName(name)] = Layer(terminator=state.terminator, buttons={})
layers[LayerName("none")] = Layer(terminator=None, buttons={})
def resolve_action(o: mackb.OnPress, current_state: mackb.StateName) -> Optional[ButtonPress]:
visited: List[mackb.ActionID] = []
while True:
if isinstance(o, mackb.ActionReference):
if o.ref in visited:
print("Cycle detected!", visited)
exit(1)
visited.append(o.ref)
state_actions = mac.actions[o.ref].state_actions
if current_state in state_actions:
o = state_actions[current_state]
else:
return None
elif isinstance(o, mackb.Output):
return Output(o.output)
elif isinstance(o, mackb.EnterState):
return ChangeLayer(LayerName(o.state_name))
else:
raise ValueError(o)
for state_name in list(mac.states.keys()) + [mackb.StateName("none")]:
for idx, modifiers in mac.modmap.selects.items():
for code, on_press in mac.keymaps[idx].keys.items():
action = resolve_action(on_press, state_name)
if action is None:
continue
if isinstance(action, Output) and action.output == "":
continue # ignore empty keys
key = Key(code, modifiers)
layers[LayerName(state_name)].buttons[key] = action
return KeyboardLayout(mac.name, LayerName("none"), layers)
if __name__ == "__main__":
import mac_parser
mac_kb = mac_parser.parse_keyboard_layout("../svorak.new.keylayout")
kb = from_mac(mac_kb)
print(kb)
for name, layer in kb.layers.items():
print(f"Layer {name} (terminator={layer.terminator!r}):")
for key, press in layer.buttons.items():
print(f" {str(key):>20} -> {press}")

268
kbtrans/kc.py Normal file
View File

@ -0,0 +1,268 @@
from typing import Set, Tuple, Any
# From Events.h
MAC_KEY_NAMES = {
0x00: "A",
0x01: "S",
0x02: "D",
0x03: "F",
0x04: "H",
0x05: "G",
0x06: "Z",
0x07: "X",
0x08: "C",
0x09: "V",
0x0A: "International",
0x0B: "B",
0x0C: "Q",
0x0D: "W",
0x0E: "E",
0x0F: "R",
0x10: "Y",
0x11: "T",
0x12: "1",
0x13: "2",
0x14: "3",
0x15: "4",
0x16: "6",
0x17: "5",
0x18: "Equal",
0x19: "9",
0x1A: "7",
0x1B: "Minus",
0x1C: "8",
0x1D: "0",
0x1E: "RightBracket",
0x1F: "O",
0x20: "U",
0x21: "LeftBracket",
0x22: "I",
0x23: "P",
0x25: "L",
0x26: "J",
0x27: "Quote",
0x28: "K",
0x29: "Semicolon",
0x2A: "Backslash",
0x2B: "Comma",
0x2C: "Slash",
0x2D: "N",
0x2E: "M",
0x2F: "Period",
0x32: "Grave",
0x41: "KPDecimal",
0x43: "KPMultiply",
0x45: "KPPlus",
0x47: "KPClear",
0x4B: "KPDivide",
0x4C: "KPEnter",
0x4E: "KPMinus",
0x51: "KPEquals",
0x52: "KP0",
0x53: "KP1",
0x54: "KP2",
0x55: "KP3",
0x56: "KP4",
0x57: "KP5",
0x58: "KP6",
0x59: "KP7",
0x5B: "KP8",
0x5C: "KP9",
0x24: "Return",
0x30: "Tab",
0x31: "Space",
0x33: "Del",
0x35: "Escape",
0x37: "Command",
0x38: "Shift",
0x39: "CapsLock",
0x3A: "Option",
0x3B: "Control",
0x3C: "RightShift",
0x3D: "RightOption",
0x3E: "RightControl",
0x3F: "Function",
0x40: "F17",
0x48: "VolumeUp",
0x49: "VolumeDown",
0x4A: "Mute",
0x4F: "F18",
0x50: "F19",
0x5A: "F20",
0x60: "F5",
0x61: "F6",
0x62: "F7",
0x63: "F3",
0x64: "F8",
0x65: "F9",
0x67: "F11",
0x69: "F13",
0x6A: "F16",
0x6B: "F14",
0x6D: "F10",
0x6F: "F12",
0x71: "F15",
0x72: "Help",
0x73: "Home",
0x74: "PageUp",
0x75: "FwdDel",
0x76: "F4",
0x77: "End",
0x78: "F2",
0x79: "PageDown",
0x7A: "F1",
# 0x7B: "LeftArrow",
# 0x7C: "RightArrow",
# 0x7D: "DownArrow",
# 0x7E: "UpArrow",
}
# From https://gist.github.com/rickyzhang82/8581a762c9f9fc6ddb8390872552c250
# Highly modified
LINUX_KEYCODE_NAMES = {
9: "Escape", # Esc
10: "1", # 1
11: "2", # 2
12: "3", # 3
13: "4", # 4
14: "5", # 5
15: "6", # 6
16: "7", # 7
17: "8", # 8
18: "9", # 9
19: "0", # 0
20: "Minus", # -
21: "Equal", # =
22: "Backspace", # Backspace
23: "Tab", # Tab
24: "Q", # Q
25: "W", # W
26: "E", # E
27: "R", # R
28: "T", # T
29: "Y", # Y
30: "U", # U
31: "I", # I
32: "O", # O
33: "P", # P
34: "LeftBracket", # [
35: "RightBracket", # ]
36: "Return", # Return
37: "Control", # Ctrl Left
38: "A", # A
39: "S", # S
40: "D", # D
41: "F", # F
42: "G", # G
43: "H", # H
44: "J", # J
45: "K", # K
46: "L", # L
47: "Semicolon", # ;
48: "Quote", # '
49: "International", # `
50: "Shift", # Shift Left
51: "Backslash", # \
52: "Z", # Z
53: "X", # X
54: "C", # C
55: "V", # V
56: "B", # B
57: "N", # N
58: "M", # M
59: "Comma", # ,
60: "Period", # .
61: "Slash", # /
62: "RightShift", # Shift Right
63: "KPMultiply", # KP *
64: "Option", # Alt Left (-> Command)
65: "Space", # Space
66: "CapsLock", # Caps Lock
67: "F1", # F1
68: "F2", # F2
69: "F3", # F3
70: "F4", # F4
71: "F5", # F5
72: "F6", # F6
73: "F7", # F7
74: "F8", # F8
75: "F9", # F9
76: "F10", # F10
# 77: "Num Lock", # Num Lock
# 78: "Scroll Lock", # Scroll Lock
79: "KP7", # KP 7
80: "KP8", # KP 8
81: "KP9", # KP 9
82: "KPMinus", # KP -
83: "KP4", # KP 4
84: "KP5", # KP 5
85: "KP6", # KP 6
86: "KPPlus", # KP +
87: "KP1", # KP 1
88: "KP2", # KP 2
89: "KP3", # KP 3
90: "KP0", # KP 0
91: "KPDecimal", # KP .
94: "Grave", # International
95: "F11", # F11
96: "F12", # F12
97: "Home", # Home
98: "UpArrow", # Cursor Up
99: "PageUp", # Page Up
100: "LeftArrow", # Cursor Left
102: "RightArrow", # Cursor Right
103: "End", # End
104: "DownArrow", # Cursor Down
# 105: "PageDown", # Page Down
# 106: "Insert", # Insert
107: "Del", # Delete
108: "KPEnter", # KP Enter
# 109: "RightControl", # Ctrl Right
# 110: "Pause", # Pause
# 111: "PrintScrn", # PrintScrn
112: "KPDivide", # KP /
113: "RightOption", # Alt Right (-> Command)
115: "Command", # Logo Left (-> Option)
116: "Command", # Logo Right (-> Option)
# 117: "Menu (-> International)", # Menu (-> International)
}
# Represenetd by the Mac virtual keycodes
class KeyCode:
def __init__(self, kc: int):
self.kc = kc
def __eq__(self, other: Any) -> bool:
if isinstance(other, KeyCode):
return self.kc == other.kc
return False
def __hash__(self) -> int:
return hash(self.kc) ^ 1
def __str__(self) -> str:
if self.kc in MAC_KEY_NAMES:
return MAC_KEY_NAMES[self.kc]
else:
return f"mac_{self.kc}"
__repr__ = __str__
def linux_keycodes(self) -> Set[Tuple[int, str]]:
if self.kc not in MAC_KEY_NAMES:
return set()
res = set()
for n, name in LINUX_KEYCODE_NAMES.items():
if name == MAC_KEY_NAMES[self.kc]:
res.add((n, f"<K{n}>"))
return res
KEYCODES = [KeyCode(x) for x in MAC_KEY_NAMES.keys()]
if __name__ == "__main__":
for n, name in MAC_KEY_NAMES.items():
linuxs = KeyCode(n).linux_keycodes()
if len(linuxs) == 0:
print(f"{name} has no linux variant...")

330
kbtrans/mac_parser.py Normal file
View File

@ -0,0 +1,330 @@
import base64
from typing import TypeVar, List
from xml.dom import minidom
from xml.parsers.expat import ExpatError
from xml.dom.minidom import Document, Node, Element, getDOMImplementation
from kc import KeyCode
from mackb import StateName, ActionID, MapIndex, Modifier, ModMap, \
State, KeyMap, OnPress, ActionReference, Output, EnterState, Action, \
MacKeyboardLayout
# Ukelele/mac uses &#xABCD; for escaping "invalid" characters, but Python puts the char directly. We search for all strings in the input, decode them and base64 encode them so we can parse them later
def encode_document(inp: str) -> str:
in_str = False
# TODO: This should be a bytes object. The input seems to be utf-16
# However, I don't think we'll be parsing anything outside 0x0-0xffff
out = ""
string_contents = ""
i = 0
inside_proper_tag = False
while i < len(inp):
ch = inp[i]
if not in_str:
if ch == "<":
inside_proper_tag = inp[i + 1] not in "?!"
if ch == '"':
in_str = True
else:
out += ch
i += 1
else:
if ch == "&" and inp[i + 1] == "#" and inp[i + 2] == "x" and inp[i+7] == ";":
hexed = inp[i+3:i+7]
string_contents += chr(int(hexed, 16))
i += 8
elif ch == '"':
if inside_proper_tag:
out += '"' + base64.b64encode(string_contents.encode()).decode() + '"'
else:
out += '"' + string_contents + '"'
string_contents = ""
in_str = False
i += 1
else:
string_contents += ch
i += 1
return out
def percent_encode(inp: str) -> str:
out = ""
for ch in inp:
if ord(ch) < 0x20 or ch in '&"<>':
out += "&#x" + hex(ord(ch))[2:].rjust(4, '0') + ";"
else:
out += ch
return out
def decode_document(inp: str) -> str:
in_str = False
out = ""
string_contents = ""
i = 0
inside_proper_tag = False
while i < len(inp):
ch = inp[i]
if not in_str:
if ch == "<":
inside_proper_tag = inp[i + 1] not in "?!"
if ch == '"':
in_str = True
else:
out += ch
i += 1
else:
if ch == '"':
if inside_proper_tag:
out += '"' + percent_encode(base64.b64decode(string_contents.encode()).decode()) + '"'
else:
out += '"' + string_contents + '"'
string_contents = ""
in_str = False
i += 1
else:
string_contents += ch
i += 1
return out
def decode_tree(d: Document) -> Document:
def recode_in_place(n: Node):
if isinstance(n, Element):
for name, value in n.attributes.items():
n.setAttribute(name, base64.b64decode(value.encode()).decode())
for child in n.childNodes:
recode_in_place(child)
for ch in d.childNodes:
recode_in_place(ch)
return d
def encode_tree(d: Document) -> Document:
def recode_in_place(n: Node):
if isinstance(n, Element):
for name, value in n.attributes.items():
n.setAttribute(name, base64.b64encode(value.encode()).decode())
for child in n.childNodes:
recode_in_place(child)
for ch in d.childNodes:
recode_in_place(ch)
return d
def load_file(path: str) -> Document:
with open(path, "r") as f:
data = f.read()
parsed = encode_document(data)
with open("/tmp/mjau-i", "w") as o:
o.write(parsed)
return decode_tree(minidom.parseString(parsed))
def save_file(path: str, x: Document):
gen = encode_tree(x).toprettyxml(indent=" ")
with open("/tmp/mjau-o", "w") as o:
o.write(gen)
with open(path, "w") as out:
out.write(decode_document(gen))
A = TypeVar("A")
def get_only(l: List[A], name: str) -> A:
if l == []:
raise ValueError(f"No {name}")
if len(l) > 1:
raise ValueError(f"Too many {name} ({len(l)})")
return l[0]
def parse_modmap(node: Node) -> ModMap:
used_keys = []
selects = {}
for select in node.childNodes:
if select.nodeName != "keyMapSelect":
continue
map_index = MapIndex(int(select.attributes["mapIndex"].value))
modifier = get_only([ch for ch in select.childNodes if ch.nodeName == "modifier"], f"modifier of {map_index}")
key_names = modifier.attributes["keys"].value.split(" ")
modifier_keys = set()
for key_name in key_names:
try:
modifier_keys.add(Modifier.from_name(key_name))
except ValueError as e:
continue
if modifier_keys in used_keys:
print(f"modifier select {map_index} already used, skipping")
continue
used_keys.append(modifier_keys)
selects[map_index] = modifier_keys
return ModMap(selects)
def parse_onpress(node: Element) -> OnPress:
if "action" in node.attributes:
return ActionReference(node.attributes["action"].value)
if "output" in node.attributes:
return Output(node.attributes["output"].value)
if "next" in node.attributes:
return EnterState(node.attributes["next"].value)
raise ValueError(f"node {node} with attributes {dict(node.attributes)} has no on-press")
def parse_keymap(node: Node) -> KeyMap:
keys = {}
for key in node.childNodes:
if key.nodeName != "key":
continue
code = KeyCode(int(key.attributes["code"].value))
on_press = parse_onpress(key)
keys[code] = on_press
return KeyMap(keys)
def parse_keyboard_layout(path: str) -> MacKeyboardLayout:
dom: Document = load_file(path)
keyboard = get_only(dom.getElementsByTagName("keyboard"), "keyboard")
name = keyboard.attributes["name"].value
states_node = get_only(dom.getElementsByTagName("terminators"), "terminators")
modmap_node = get_only(dom.getElementsByTagName("modifierMap"), "modifierMap")
keymap_nodes = dom.getElementsByTagName("keyMapSet")
ansi_keymaps = [keymap_node for keymap_node in keymap_nodes if keymap_node.attributes["id"].value == "ANSI"]
keymapset_node = get_only(ansi_keymaps, "keymapSet (ANSI)")
actions_node = get_only(dom.getElementsByTagName("actions"), "actions")
states = {}
for state_node in states_node.childNodes:
if state_node.nodeName != "when":
continue
state_name = StateName(state_node.attributes["state"].value)
terminator = state_node.attributes["output"].value
states[state_name] = State(terminator)
modmap = parse_modmap(modmap_node)
keymaps = {}
for keymap_node in keymapset_node.childNodes:
if keymap_node.nodeName != "keyMap":
continue
map_index = MapIndex(int(keymap_node.attributes["index"].value))
keymap = parse_keymap(keymap_node)
keymaps[map_index] = keymap
actions = {}
for action in actions_node.childNodes:
if action.nodeName != "action":
continue
action_id = ActionID(action.attributes["id"].value)
state_actions = {}
for on_press_node in action.childNodes:
if on_press_node.nodeName != "when":
continue
state = StateName(on_press_node.attributes["state"].value)
on_press = parse_onpress(on_press_node)
state_actions[state] = on_press
actions[action_id] = Action(state_actions)
terminators_node = get_only(dom.getElementsByTagName("terminators"), "terminators")
for terminator in terminators_node.childNodes:
if terminator.nodeName != "when":
continue
output = terminator.attributes["output"].value
state = StateName(output)
return MacKeyboardLayout(
name=name,
states=states,
modmap=modmap,
keymaps=keymaps,
actions=actions,
)
def write_on_press(doc: Document, name: str, on_press: OnPress) -> Element:
key = doc.createElement(name)
if isinstance(on_press, ActionReference):
key.setAttribute("action", on_press.ref)
elif isinstance(on_press, Output):
key.setAttribute("output", on_press.output)
elif isinstance(on_press, EnterState):
key.setAttribute("next", on_press.state_name)
else:
raise ValueError(on_press)
return key
def unparse(kb: MacKeyboardLayout) -> Document:
impl = getDOMImplementation()
assert impl is not None
maxout = 0
for ac in kb.actions.values():
for onpress in ac.state_actions.values():
if isinstance(onpress, Output):
maxout = max(maxout, len(onpress.output.encode("utf-16-le")) // 2)
for km in kb.keymaps.values():
for onpress in km.keys.values():
if isinstance(onpress, Output):
maxout = max(maxout, len(onpress.output.encode("utf-16-le")) // 2)
doc = impl.createDocument(None, "keyboard", None)
keyboard = doc.documentElement
keyboard.setAttribute("group", str(126))
keyboard.setAttribute("id", str(123456))
keyboard.setAttribute("name", kb.name)
keyboard.setAttribute("maxout", str(maxout))
keyboard.appendChild(layouts := doc.createElement("layouts"))
layouts.appendChild(layout := doc.createElement("layout"))
layout.setAttribute("first", str(0))
layout.setAttribute("last", str(207))
layout.setAttribute("mapSet", "ANSI")
layout.setAttribute("modifiers", "Modifiers")
keyboard.appendChild(modifierMap := doc.createElement("modifierMap"))
modifierMap.setAttribute("id", "Modifiers")
modifierMap.setAttribute("defaultIndex", str(0))
for idx, map in kb.modmap.selects.items():
modifierMap.appendChild(kbsel := doc.createElement("keyMapSelect"))
kbsel.setAttribute("mapIndex", str(idx))
kbsel.appendChild(modifier := doc.createElement("modifier"))
modifier.setAttribute("keys", " ".join(m.value for m in map))
keyboard.appendChild(keymapset := doc.createElement("keyMapSet"))
keymapset.setAttribute("id", "ANSI")
for idx, km in kb.keymaps.items():
keymapset.appendChild(keymap := doc.createElement("keyMap"))
keymap.setAttribute("index", str(idx))
for kc, on_press in km.keys.items():
keymap.appendChild(doc.createComment(str(kc)))
keymap.appendChild(key := write_on_press(doc, "key", on_press))
key.setAttribute("code", str(kc.kc))
keyboard.appendChild(actions := doc.createElement("actions"))
for action_id, action in kb.actions.items():
actions.appendChild(action_elem := doc.createElement("action"))
action_elem.setAttribute("id", action_id)
for state_name, on_press in action.state_actions.items():
action_elem.appendChild(when_elem := write_on_press(doc, "when", on_press))
when_elem.setAttribute("state", state_name)
keyboard.appendChild(terminators := doc.createElement("terminators"))
for state_name, state in kb.states.items():
terminators.appendChild(terminator := doc.createElement("when"))
terminator.setAttribute("state", state_name)
terminator.setAttribute("output", state.terminator)
return doc

93
kbtrans/mackb.py Normal file
View File

@ -0,0 +1,93 @@
# Quite literal representation of the macOS .keylayout file
from __future__ import annotations
from typing import Dict, List, NewType, Set
from abc import ABC
from enum import Enum
from kb import KeyCode, Modifier
from kc import KeyCode
StateName = NewType("StateName", str)
ActionID = NewType("ActionID", str)
MapIndex = NewType("MapIndex", int)
NO_STATE = StateName("none")
class State:
def __init__(
self,
terminator: str,
):
self.terminator = terminator
class ModMap:
def __init__(
self,
selects: Dict[MapIndex, Set[Modifier]],
):
self.selects = selects
def __repr__(self) -> str:
return f"ModMap(selects={self.selects})"
class KeyMap:
def __init__(
self,
keys: Dict[KeyCode, OnPress],
):
self.keys = keys
def __repr__(self) -> str:
return f"KeyMap(keys={self.keys})"
class OnPress(ABC): # the action, output or next attribute of a <key/> or <when/> node
pass
class ActionReference(OnPress): # <... action="..." />
def __init__(self, ref: ActionID):
self.ref = ref
def __repr__(self) -> str:
return f"ActionReference({self.ref!r})"
class Output(OnPress): # <... output="..." />
def __init__(self, output: str):
self.output = output
def __repr__(self) -> str:
return f"Output({self.output!r})"
class EnterState(OnPress): # <... next="..." />
def __init__(self, state_name: StateName):
self.state_name = state_name
def __repr__(self) -> str:
return f"EnterState({self.state_name!r})"
class Action:
def __init__(
self,
state_actions: Dict[StateName, OnPress],
):
self.state_actions = state_actions
def __repr__(self) -> str:
return f"Action({self.state_actions})"
class MacKeyboardLayout:
def __init__(
self,
name: str,
states: Dict[StateName, State],
modmap: ModMap,
keymaps: Dict[MapIndex, KeyMap],
actions: Dict[ActionID, Action],
):
self.name = name
self.states = states
self.modmap = modmap
self.keymaps = keymaps
self.actions = actions

1296
svorak.keylayout Normal file

File diff suppressed because it is too large Load Diff