first commit
This commit is contained in:
commit
90e336b668
247
kbtrans/agda_input_method.py
Normal file
247
kbtrans/agda_input_method.py
Normal 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
286
kbtrans/agda_list.py
Normal 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 B !bR ℝ !BGh 𝛈 !MiA 𝐴 !"{I} Ï !c{h} ḩ !amalg ∐ !lambda λ !^\script g ᶢ
|
||||
!% % !FC C !bS 𝕊 !BGi 𝛊 !MiB 𝐵 !"{O} Ö !c{k} ķ !aoint ∳ !langle ⟨ !^\turned a ᵄ
|
||||
!+ + !FD D !bT 𝕋 !BGk 𝛋 !MiC 𝐶 !"{U} Ü !c{l} ļ !asymp ≍ !lfloor ⌊ !^\turned h ᶣ
|
||||
!- !FE E !bU 𝕌 !BGl 𝛌 !MiD 𝐷 !"{W} Ẅ !c{n} ņ !begin 〖 !ltimes ⋉ !^\turned i ᵎ
|
||||
!0 ∅ !FF F !bV 𝕍 !BGm 𝛍 !MiE 𝐸 !"{X} Ẍ !c{r} ŗ !below ┬ !mapsto ↦ !^\turned m ᵚ
|
||||
!@ @ !FG G !bW 𝕎 !BGn 𝛎 !MiF 𝐹 !"{Y} Ÿ !c{s} ş !cdots ⋯ !models ⊧ !^\turned r ʴ
|
||||
!C ∁ !FH H !bX 𝕏 !BGo 𝛚 !MiG 𝐺 !"{a} ä !c{t} ţ !close ┤ !nVDash ⊯ !^\turned v ᶺ
|
||||
!H ̋ !FI I !bY 𝕐 !BGp 𝛙 !MiH 𝐻 !"{e} ë !d-u- ⇵ !coint ∲ !nVdash ⊮ !^\turned w ꭩ
|
||||
!I ⋂ !FJ J !bZ ℤ !BGr 𝛒 !MiI 𝐼 !"{h} ḧ !d\.S Ṩ !colon ₡ !nequiv ≢ !^\turned y 𐞠
|
||||
!L Ł !FK K !ba 𝕒 !BGs 𝛔 !MiJ 𝐽 !"{i} ï !d\.s ṩ !dashv ⊣ !nsimeq ≄ !circledast ⊛
|
||||
!O Ø !FL L !bb 𝕓 !BGt 𝛕 !MiK 𝐾 !"{o} ö !d\=L Ḹ !dddot ⃛ !numero № !complement ∁
|
||||
!P ¶ !FM M !bc 𝕔 !BGu 𝛖 !MiL 𝐿 !"{t} ẗ !d\=R Ṝ !ddots ⋱ !nvDash ⊭ !curlywedge ⋏
|
||||
!S § !FN N !bd 𝕕 !BGx 𝛏 !MiM 𝑀 !"{u} ü !d\=l ḹ !delta δ !nvdash ⊬ !eqslantgtr ⋝
|
||||
!^ ̂ !FO O !be 𝕖 !BGz 𝛇 !MiN 𝑁 !"{w} ẅ !d\=r ṝ !doteq ≐ !oiiint ∰ !gtreqqless ⋛
|
||||
!k ̨ !FP P !bf 𝕗 !BPi 𝚷 !MiO 𝑂 !"{x} ẍ !ddag ‡ !equiv ≡ !ominus ⊖ !lessapprox ≲
|
||||
!v ̌ !FQ Q !bg 𝕘 !Box □ !MiP 𝑃 !"{y} ÿ !def= ≝ !frac1 ⅟ !oslash ⊘ !lesseqqgtr ⋚
|
||||
!x × !FR R !bh 𝕙 !Bpi 𝛑 !MiQ 𝑄 !'\.S Ṥ !defs ≙ !frown ⌢ !otimes ⊗ !longmapsto ⟼
|
||||
!| ∣ !FS S !bi 𝕚 !Cap ⋒ !MiR 𝑅 !'\.s ṥ !dong ₫ !gamma γ !permil ‰ !mathscr{I} ℐ
|
||||
!!! ‼ !FT T !bj 𝕛 !Chi Χ !MiS 𝑆 !'\AE Ǽ !d{A} Ạ !gimel ℷ !peseta ₧ !nLeftarrow ⇍
|
||||
!!? ⁉ !FU U !bk 𝕜 !Cup ⋓ !MiT 𝑇 !'\ae ǽ !d{B} Ḅ !gneqq ≩ !pounds £ !nleftarrow ↚
|
||||
!"' “ !FV V !bl 𝕝 !Dei Ϯ !MiU 𝑈 !'{A} Á !d{D} Ḍ !gnsim ⋧ !pprime ″ !nsubseteqq ⊈
|
||||
!"< « !FW W !bm 𝕞 !Eta Η !MiV 𝑉 !'{C} Ć !d{E} Ẹ !iiint ∭ !preceq ≼ !nsupseteqq ⊉
|
||||
!"> » !FX X !bn 𝕟 !Fei Ϥ !MiW 𝑊 !'{E} É !d{H} Ḥ !imath ı !propto ∝ !precapprox ≾
|
||||
!"A Ä !FY Y !bo 𝕠 !GTH Θ !MiX 𝑋 !'{G} Ǵ !d{I} Ị !infty ∞ !rangle ⟩ !prohibited 🛇
|
||||
!"E Ë !FZ Z !bp 𝕡 !Gl- ƛ !MiY 𝑌 !'{I} Í !d{K} Ḳ !jmath ȷ !rddots ⋰ !registered ®
|
||||
!"H Ḧ !Fa a !bq 𝕢 !Glb ⨅ !MiZ 𝑍 !'{K} Ḱ !d{L} Ḷ !kappa κ !rfloor ⌋ !rightarrow →
|
||||
!"I Ï !Fb b !br 𝕣 !Gth θ !Mia 𝑎 !'{L} Ĺ !d{M} Ṃ !koppa ϟ !rtimes ⋊ !smallamalg ∐
|
||||
!"O Ö !Fc c !bs 𝕤 !H{} ˝ !Mib 𝑏 !'{M} Ḿ !d{N} Ṇ !lamda λ !square □ !smallsmile ⌣
|
||||
!"U Ü !Fd d !bt 𝕥 !Lsh ↰ !Mic 𝑐 !'{N} Ń !d{O} Ọ !lceil ⌈ !squb=n ⋢ !sqsubseteq ⊑
|
||||
!"W Ẅ !Fe e !bv 𝕧 !Lub ⨆ !Mid 𝑑 !'{O} Ó !d{R} Ṛ !ldata 《 !squp=n ⋣ !sqsupseteq ⊒
|
||||
!"X Ẍ !Ff f !bw 𝕨 !MCA 𝓐 !Mie 𝑒 !'{P} Ṕ !d{S} Ṣ !ldots … !stigma ϛ !subsetneqq ⊊
|
||||
!"Y Ÿ !Fg g !by 𝕪 !MCB 𝓑 !Mif 𝑓 !'{R} Ŕ !d{T} Ṭ !lneqq ≨ !subset ⊂ !succapprox ≿
|
||||
!"` „ !Fh h !bz 𝕫 !MCC 𝓒 !Mig 𝑔 !'{S} Ś !d{U} Ụ !lnsim ⋦ !succeq ≽ !supsetneqq ⊋
|
||||
!"a ä !Fi i !cC Ç !MCD 𝓓 !Mih ℎ !'{U} Ú !d{V} Ṿ !manat ₼ !supset ⊃ !textlquill ⁅
|
||||
!"e ë !Fj j !cD Ḑ !MCE 𝓔 !Mii 𝑖 !'{W} Ẃ !d{W} Ẉ !micro µ !textmu µ !textnumero №
|
||||
!"h ḧ !Fk k !cE Ȩ !MCF 𝓕 !Mij 𝑗 !'{Y} Ý !d{Y} Ỵ !minus − !tugrik ₮ !textrecipe ℞
|
||||
!"i ï !Fl l !cG Ģ !MCG 𝓖 !Mik 𝑘 !'{Z} Ź !d{Z} Ẓ !nabla ∇ !u\'{A} Ắ !textrquill ⁆
|
||||
!"o ö !Fm m !cH Ḩ !MCH 𝓗 !Mil 𝑙 !'{a} á !d{a} ạ !naira ₦ !u\'{a} ắ !underbrace ⏟
|
||||
!"t ẗ !Fn n !cK Ķ !MCI 𝓘 !Mim 𝑚 !'{c} ć !d{b} ḅ !ncong ≇ !u\`{A} Ằ !underparen ⏝
|
||||
!"u ü !Fo o !cL Ļ !MCJ 𝓙 !Min 𝑛 !'{e} é !d{d} ḍ !ngeqq ≱ !u\`{a} ằ !upuparrows ⇈
|
||||
!"w ẅ !Fp p !cN Ņ !MCK 𝓚 !Mio 𝑜 !'{g} ǵ !d{e} ẹ !nleqq ≰ !u\d{A} Ặ !varepsilon ε
|
||||
!"x ẍ !Fq q !cR Ŗ !MCL 𝓛 !Mip 𝑝 !'{i} í !d{h} ḥ !nless ≮ !u\d{a} ặ !Rrightarrow ⇛
|
||||
!"y ÿ !Fr r !cS Ş !MCM 𝓜 !Miq 𝑞 !'{k} ḱ !d{i} ị !notin ∉ !u\~{A} Ẵ !Updownarrow ⇕
|
||||
!'A Á !Fs s !cT Ţ !MCN 𝓝 !Mir 𝑟 !'{l} ĺ !d{k} ḳ !nprec ⊀ !u\~{a} ẵ !^\capital b 𐞄
|
||||
!'C Ć !Ft t !cc ç !MCO 𝓞 !Mis 𝑠 !'{m} ḿ !d{l} ḷ !nsucc ⊁ !v\.{S} Ṧ !^\capital g 𐞒
|
||||
!'E É !Fu u !cd ḑ !MCP 𝓟 !Mit 𝑡 !'{n} ń !d{m} ṃ !oiint ∯ !v\.{s} ṧ !^\capital h 𐞖
|
||||
!'G Ǵ !Fv v !ce ȩ !MCQ 𝓠 !Miu 𝑢 !'{o} ó !d{n} ṇ !omega ω !varkai ϗ !^\capital i ᶦ
|
||||
!'I Í !Fw w !cg ģ !MCR 𝓡 !Miv 𝑣 !'{p} ṕ !d{o} ọ !oplus ⊕ !varphi φ !^\capital l ᶫ
|
||||
!'K Ḱ !Fx x !ch ḩ !MCS 𝓢 !Miw 𝑤 !'{r} ŕ !d{r} ṛ !ounce ℥ !varrho ϱ !^\capital n ᶰ
|
||||
!'L Ĺ !Fy y !ck ķ !MCT 𝓣 !Mix 𝑥 !'{s} ś !d{s} ṣ !pound £ !veebar ⊻ !^\capital r 𐞪
|
||||
!'M Ḿ !Fz z !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 0 !bG 𝔾 !BGT 𝚻 !Mfp 𝔭 !"\=U Ǖ !c{H} Ḩ !^\eng ᵑ !frac56 ⅚ !supseteqq ⊇ !^\dz digraph with retroflex hook 𐞈
|
||||
!F1 1 !bH ℍ !BGU 𝚼 !Mfq 𝔮 !"\=a ǟ !c{K} Ķ !^\esh ᶴ !frac58 ⅝ !supsetneq ⊋ !^\l with retroflex hook and belt 𐞝
|
||||
!F2 2 !bI 𝕀 !BGX 𝚵 !Mfr 𝔯 !"\=o ȫ !c{L} Ļ !^\eth ᶞ !frac78 ⅞ !telephone ℡ !^\ts digraph with retroflex hook 𐞭
|
||||
!F3 3 !bJ 𝕁 !BGZ 𝚭 !Mfs 𝔰 !"\=u ǖ !c{N} Ņ !^\ezh ᶾ !gangia ϫ !textnaira ₦ !^\turned r with long leg and retroflex hook 𐞧
|
||||
!F4 4 !bK 𝕂 !BGa 𝛂 !Mft 𝔱 !"\`U Ǜ !c{R} Ŗ !^\phi ᶲ !gtrdot ⋗ !therefore ∴
|
||||
!F5 5 !bL 𝕃 !BGb 𝛃 !Mfu 𝔲 !"\`u ǜ !c{S} Ş !^{SM} ℠ !gtrsim ≳ !triangleq ≜
|
||||
!F6 6 !bM 𝕄 !BGc 𝛘 !Mfv 𝔳 !"\vU Ǚ !c{T} Ţ !^{TM} ™ !hsmash ⬌ !varpropto ∝
|
||||
!F7 7 !bN ℕ !BGd 𝛅 !Mfw 𝔴 !"\vu ǚ !c{c} ç !^{\j} ĵ !iiiint ⨌ !Lleftarrow ⇚
|
||||
!F8 8 !bO 𝕆 !BGe 𝛆 !Mfx 𝔵 !"{A} Ä !c{d} ḑ !above ┴ !k\={O} Ǭ !Rightarrow ⇒
|
||||
!F9 9 !bP ℙ !BGf 𝛗 !Mfy 𝔶 !"{E} Ë !c{e} ȩ !aleph ℵ !k\={o} ǭ !^\Barred B ᴯ
|
||||
!FA A !bQ ℚ !BGg 𝛄 !Mfz 𝔷 !"{H} Ḧ !c{g} ģ !alpha α !kelvin K !^\barred o ᶱ
|
||||
"""
|
||||
|
||||
agda_words = extras + parse_source(source)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"{len(agda_words)} keybinds")
|
144
kbtrans/generate_xkb.py
Normal file
144
kbtrans/generate_xkb.py
Normal 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
143
kbtrans/kb.py
Normal 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
268
kbtrans/kc.py
Normal 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
330
kbtrans/mac_parser.py
Normal 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 ꯍ 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
93
kbtrans/mackb.py
Normal 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
1296
svorak.keylayout
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user