kblayouts/kbtrans/kb.py

144 lines
4.3 KiB
Python
Raw Normal View History

2023-11-09 14:16:16 +01:00
# "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}")