144 lines
4.3 KiB
Python
144 lines
4.3 KiB
Python
# "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}")
|