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