Skip to content

Domain Layer

Core domain models and configuration classes.

Domain Types

domain_types

Domain types and value objects for skim keymap processing.

This module defines the core domain types used throughout the skim application, including enumerations for alignment, keyboard sides, and key directions, as well as data classes for representing processed key data.

These types form the foundation of the domain model and are used across multiple layers of the application.

Attributes:

Name Type Description
SEPARATOR_CHAR

Unicode box-drawing character (│) used to separate tap and hold labels on dual-function keys.

NBSP_CHAR

Non-breaking space character used to allow the same behavior as hold-tap symbols, but for any two sides of a lable.

Example
>>> from skim.domain.domain_types import KeyboardSide, KeyDirection
>>> side = KeyboardSide.LEFT
>>> side.opposite
<KeyboardSide.RIGHT: 'right'>
>>> direction = KeyDirection.NORTH
>>> direction.opposite
<KeyDirection.SOUTH: 'south'>

SEPARATOR_CHAR module-attribute

SEPARATOR_CHAR = '│'

Unicode box-drawing character used to separate tap and hold labels.

This character (U+2502, BOX DRAWINGS LIGHT VERTICAL) is used to visually separate the "tap" and "hold" portions of dual-function key labels, such as layer-tap keys (LT) and mod-tap keys (MT).

Example

A layer-tap key that types 'A' on tap and activates layer 1 on hold would be displayed as: "A│L1"

NBSP_CHAR module-attribute

NBSP_CHAR = '\xa0'

Non-breaking space character for key labels.

Used in key labels that want to display symbol and text and align the symbol the same way the skim controls hold-tap symbols alignment. Anything on the left of the non-breaking space behaves just like the hold symbol in a hold-tap key.

Example

A label like "⌘ Cmd" could use NBSP_CHAR to make the symbol show outward the key cluster: "⌘ Cmd".

This would display "⌘ Cmd" on keys at the left side of the cluster and "Cmd ⌘" on keys at the right of the cluster.

T module-attribute

T = TypeVar('T')

Alignment

Bases: Enum

Alignment options for positioning elements.

Used for specifying horizontal or vertical alignment of graphical elements within their containers, such as keys within clusters or labels within keys.

Attributes:

Name Type Description
START

Align to the start (left for horizontal, top for vertical).

CENTER

Center alignment.

END

Align to the end (right for horizontal, bottom for vertical).

Example
>>> align = Alignment.START
>>> align.opposite
<Alignment.END: 'end'>
>>> Alignment.CENTER.opposite
<Alignment.CENTER: 'center'>

__slots__ class-attribute instance-attribute

__slots__ = ()

START class-attribute instance-attribute

START = 'start'

CENTER class-attribute instance-attribute

CENTER = 'center'

END class-attribute instance-attribute

END = 'end'

opposite property

opposite: Alignment

Get the opposite alignment.

Returns:

Type Description
Alignment

The opposite alignment: START becomes END and vice versa.

Alignment

CENTER returns itself as it has no opposite.

Example
>>> Alignment.START.opposite
<Alignment.END: 'end'>

KeyboardSide

Bases: Enum

Enumeration of keyboard sides (left or right hand).

Used to distinguish between the two halves of the split Svalboard keyboard, which affects rendering orientation and key positioning.

Attributes:

Name Type Description
LEFT

The left-hand side of the keyboard.

RIGHT

The right-hand side of the keyboard.

Example
>>> side = KeyboardSide.LEFT
>>> side.opposite
<KeyboardSide.RIGHT: 'right'>

__slots__ class-attribute instance-attribute

__slots__ = ()

LEFT class-attribute instance-attribute

LEFT = 'left'

RIGHT class-attribute instance-attribute

RIGHT = 'right'

opposite property

opposite: KeyboardSide

Get the opposite keyboard side.

Returns:

Type Description
KeyboardSide

LEFT if the current side is RIGHT, and vice versa.

Example
>>> KeyboardSide.RIGHT.opposite
<KeyboardSide.LEFT: 'left'>

KeyDirection

Bases: Enum

Cardinal directions for directional keys in finger clusters.

Each finger cluster on the Svalboard has directional keys arranged around a center key. This enum represents the four cardinal directions used for these surrounding keys.

The directions correspond to the physical movement required to press the key relative to the finger's home position:

  • NORTH: Pushing the finger forward (away from palm)
  • SOUTH: Pulling the finger backward (toward palm)
  • EAST: Moving toward the thumb
  • WEST: Moving away from the thumb

Attributes:

Name Type Description
NORTH

The northward (forward) direction.

SOUTH

The southward (backward) direction.

EAST

The eastward (thumb-ward) direction.

WEST

The westward (away from thumb) direction.

Example
>>> direction = KeyDirection.NORTH
>>> direction.opposite
<KeyDirection.SOUTH: 'south'>

__slots__ class-attribute instance-attribute

__slots__ = ()

NORTH class-attribute instance-attribute

NORTH = 'north'

SOUTH class-attribute instance-attribute

SOUTH = 'south'

EAST class-attribute instance-attribute

EAST = 'east'

WEST class-attribute instance-attribute

WEST = 'west'

opposite property

opposite: KeyDirection

Get the opposite cardinal direction.

Returns:

Type Description
KeyDirection

The opposite direction: NORTH becomes SOUTH, EAST becomes WEST,

KeyDirection

and vice versa.

Example
>>> KeyDirection.EAST.opposite
<KeyDirection.WEST: 'west'>

KeymapType

Bases: Enum

Enumeration of supported keymap file formats.

Identifies the source format of a keymap file, which determines how the file content should be parsed and interpreted.

Attributes:

Name Type Description
C2JSON

QMK's c2json format, produced by qmk c2json command. Contains raw layer data in QMK's internal ordering.

VIAL

Vial firmware format (.vil files). Contains keymap data with Vial-specific key ordering.

KEYBARD

Keybard application format (.kbi files). Contains keymap data with Keybard-specific key ordering.

Example
>>> KeymapType.VIAL.value
'vial'

__slots__ class-attribute instance-attribute

__slots__ = ()

C2JSON class-attribute instance-attribute

C2JSON = 'c2json'

VIAL class-attribute instance-attribute

VIAL = 'vial'

KEYBARD class-attribute instance-attribute

KEYBARD = 'keybard'

SvalboardTargetKey dataclass

SvalboardTargetKey(
    label: str = "",
    layer_switch: int | None = None,
    is_transparent: bool = False,
    macro_id: str | None = None,
    tap_dance_id: str | None = None,
)

Processed key data ready for rendering.

Represents a single key after all keycode transformations have been applied. Contains the display label and optional metadata about layer-switching behavior.

This is a frozen (immutable) dataclass, ensuring that key data cannot be accidentally modified after creation.

Attributes:

Name Type Description
label str

The text label to display on the key. May contain special characters like the separator character (│) for dual-function keys. Defaults to an empty string.

layer_switch int | None

The target layer index if this key switches layers (e.g., MO(1), LT(2, KC_A)), or None if the key doesn't switch layers. Layer indices are 0-based. Defaults to None.

is_transparent bool

True when the source keycode belongs to QMK's transparent family (KC_TRANSPARENT, KC_TRNS, _). Downstream stages may use this to render the key as a faded fall-through of the base-layer label. Defaults to False.

macro_id str | None

Stable id of the QMK macro this key references ("0" for MACRO_0, "MY_MACRO" for MACRO_MY_MACRO), or None when the key is not a macro reference. Defaults to None.

tap_dance_id str | None

Stable id of the QMK tap-dance this key references ("0" for TD(0), "MY_TD" for TD(MY_TD)), or None when the key is not a tap-dance reference. Defaults to None.

Example
>>> # Simple key with just a label
>>> key = SvalboardTargetKey(label="A")
>>> key.label
'A'

>>> # Layer-tap key (tap for A, hold for layer 1)
>>> key = SvalboardTargetKey(label="A│L1", layer_switch=1)
>>> key.layer_switch
1

label class-attribute instance-attribute

label: str = ''

layer_switch class-attribute instance-attribute

layer_switch: int | None = None

is_transparent class-attribute instance-attribute

is_transparent: bool = False

macro_id class-attribute instance-attribute

macro_id: str | None = None

tap_dance_id class-attribute instance-attribute

tap_dance_id: str | None = None

SvalboardTapDance dataclass

SvalboardTapDance(
    id: str,
    tap: T | None = None,
    hold: T | None = None,
    double_tap: T | None = None,
    tap_then_hold: T | None = None,
    tapping_term: int = 200,
    name: str | None = None,
)

Bases: Generic[T]

A QMK Vial-style tap-dance definition.

Attributes:

Name Type Description
id str

Stable id matching how the keycode references this tap dance ("0" for TD(0), "MY_TD" for TD(MY_TD)).

tap T | None

Action sent on a single tap, or None for "no action".

hold T | None

Action sent while held, or None.

double_tap T | None

Action sent on a double tap, or None.

tap_then_hold T | None

Action sent when tapped then held, or None.

tapping_term int

Tapping term in milliseconds. Defaults to 200.

name str | None

Optional human-readable name (populated later from config).

id instance-attribute

id: str

tap class-attribute instance-attribute

tap: T | None = None

hold class-attribute instance-attribute

hold: T | None = None

double_tap class-attribute instance-attribute

double_tap: T | None = None

tap_then_hold class-attribute instance-attribute

tap_then_hold: T | None = None

tapping_term class-attribute instance-attribute

tapping_term: int = 200

name class-attribute instance-attribute

name: str | None = None

SvalboardMacroActionKind

Bases: Enum

Discriminator for the supported QMK macro action kinds.

Vial and Keybard macros support these five kinds. TAP, DOWN, and UP carry one or more keycodes; TEXT carries a string; DELAY carries a duration in milliseconds.

__slots__ class-attribute instance-attribute

__slots__ = ()

TAP class-attribute instance-attribute

TAP = 'tap'

DOWN class-attribute instance-attribute

DOWN = 'down'

UP class-attribute instance-attribute

UP = 'up'

TEXT class-attribute instance-attribute

TEXT = 'text'

DELAY class-attribute instance-attribute

DELAY = 'delay'

SvalboardMacroAction dataclass

SvalboardMacroAction(
    kind: SvalboardMacroActionKind,
    keys: tuple[T, ...] = (),
    text: str = "",
    duration_ms: int = 0,
)

Bases: Generic[T]

A single step inside a QMK macro.

Discriminated by kind. Only the field(s) relevant to that kind carry meaningful values; the others retain their defaults.

Attributes:

Name Type Description
kind SvalboardMacroActionKind

The action kind from :class:SvalboardMacroActionKind.

keys tuple[T, ...]

Tuple of one or more keycodes for TAP/DOWN/UP.

text str

Literal string for TEXT.

duration_ms int

Delay length in milliseconds for DELAY.

kind instance-attribute

keys class-attribute instance-attribute

keys: tuple[T, ...] = ()

text class-attribute instance-attribute

text: str = ''

duration_ms class-attribute instance-attribute

duration_ms: int = 0

SvalboardMacro dataclass

SvalboardMacro(
    id: str,
    actions: tuple[SvalboardMacroAction[T], ...] = (),
    name: str | None = None,
)

Bases: Generic[T]

A QMK macro made up of an ordered sequence of actions.

Attributes:

Name Type Description
id str

Stable id matching how the keycode references this macro ("0" for MACRO_0, "MY_MACRO" for MACRO_MY_MACRO).

actions tuple[SvalboardMacroAction[T], ...]

Ordered tuple of :class:SvalboardMacroAction steps.

name str | None

Optional human-readable name (populated later from config).

id instance-attribute

id: str

actions class-attribute instance-attribute

actions: tuple[SvalboardMacroAction[T], ...] = ()

name class-attribute instance-attribute

name: str | None = None

Adapters

adapters

Adapters for data transformation and external formats.

This package provides adapters for converting between different data representations, such as: - Transforming raw keycodes to display labels - Normalizing keymap JSON from various sources (Vial, Keybard, QMK) - Converting raw keymaps to renderable target keymaps

__all__ module-attribute

__all__ = [
    "KeycodeLabelAdapter",
    "KeymapJsonAdapter",
    "KeymapTargetAdapter",
]

KeycodeLabelAdapter

KeycodeLabelAdapter(
    keyboard_config: Keyboard,
    keycode_mappings: KeycodeMappings,
)

Transforms QMK keycodes into human-readable display labels.

This adapter processes raw QMK keycode strings and converts them to labels suitable for displaying on keymap visualization images. It supports basic keycodes, macro functions, modifier combinations, and cross-references between keycodes.

The transformation process: 1. Apply pre-processing rules (normalize/alias keycodes) 2. Check for macro function syntax and expand templates 3. Resolve keycode to label using the mapping dictionary 4. Handle alias references (@@KEYCODE; syntax)

Attributes:

Name Type Description
_keycodes dict[str, str]

Dictionary mapping keycodes to their display labels.

_pre_processing dict[str, str]

Dictionary of pre-processing transformations.

_macro_functions dict[str, str]

Dictionary of macro function templates.

_modifier_union dict[str, str]

Dictionary mapping modifier constants to labels.

_label_separator str

Character used to separate tap/hold labels.

Example
>>> from skim.application.loaders import load_keycode_mappings
>>> from skim.data.config import SkimConfig
>>> config = SkimConfig()
>>> mappings = load_keycode_mappings()
>>> adapter = KeycodeLabelAdapter(config.keyboard, mappings)
>>> adapter.transform("KC_A")
('A', None)
>>> adapter.transform("MO(2)")
('L3', 2)

Initialize the adapter with keyboard configuration and keycode mappings.

Parameters:

Name Type Description Default
keyboard_config Keyboard

Keyboard configuration containing layer definitions. Used to resolve layer references by name (e.g., "nav", "sym") to their numeric indices.

required
keycode_mappings KeycodeMappings

The keycode internal dictionary representing all the keycode mappings to labels. This dictionary must contain dictionaries for keycodes, pre_processing, macro_functions, and modifier_union.

required
Example
>>> from skim.application.loaders import load_keycode_mappings
>>> from skim.data import Keyboard, KeyboardLayer, SkimConfig
>>> keyboard = Keyboard(layers=[KeyboardLayer(id="base", name="Base")])
>>> config = SkimConfig()
>>> mappings = load_keycode_mappings(config.keycodes)
>>> adapter = KeycodeLabelAdapter(keyboard, mappings)
Source code in src/skim/domain/adapters/keycode_label_adapter.py
def __init__(self, keyboard_config: Keyboard, keycode_mappings: KeycodeMappings) -> None:
    """Initialize the adapter with keyboard configuration and keycode mappings.

    Args:
        keyboard_config: Keyboard configuration containing layer definitions.
            Used to resolve layer references by name (e.g., "nav", "sym")
            to their numeric indices.
        keycode_mappings: The keycode internal dictionary representing all
            the keycode mappings to labels. This dictionary must contain
            dictionaries for keycodes, pre_processing, macro_functions, and
            modifier_union.

    Example:
        ```pycon
        >>> from skim.application.loaders import load_keycode_mappings
        >>> from skim.data import Keyboard, KeyboardLayer, SkimConfig
        >>> keyboard = Keyboard(layers=[KeyboardLayer(id="base", name="Base")])
        >>> config = SkimConfig()
        >>> mappings = load_keycode_mappings(config.keycodes)
        >>> adapter = KeycodeLabelAdapter(keyboard, mappings)

        ```
    """
    self._keycodes = keycode_mappings.get("keycodes", {})
    self._pre_processing = keycode_mappings.get("pre_processing", {})
    self._macro_functions = keycode_mappings.get("macro_functions", {})
    self._modifier_union = keycode_mappings.get("modifier_union", {})
    self._label_separator = SEPARATOR_CHAR
    self._keyboard_config = keyboard_config

transform

transform(text: str) -> SvalboardTargetKey

Transform a QMK keycode into a SvalboardTargetKey.

Processes the keycode through pre-processing, macro expansion, and label resolution to produce a SvalboardTargetKey ready for rendering.

Parameters:

Name Type Description Default
text str

The raw QMK keycode string (e.g., "KC_A", "LT(1, KC_SPC)").

required

Returns:

Type Description
SvalboardTargetKey

A SvalboardTargetKey containing the human-readable display string

SvalboardTargetKey

in label, and the target layer index in layer_switch if this

SvalboardTargetKey

key switches layers (otherwise None).

Example
>>> adapter.transform("KC_SPACE")
SvalboardTargetKey(label='Space', layer_switch=None)
>>> adapter.transform("MO(1)")
SvalboardTargetKey(label='L2', layer_switch=1)
Source code in src/skim/domain/adapters/keycode_label_adapter.py
def transform(self, text: str) -> SvalboardTargetKey:
    """Transform a QMK keycode into a SvalboardTargetKey.

    Processes the keycode through pre-processing, macro expansion, and
    label resolution to produce a SvalboardTargetKey ready for rendering.

    Args:
        text: The raw QMK keycode string (e.g., "KC_A", "LT(1, KC_SPC)").

    Returns:
        A SvalboardTargetKey containing the human-readable display string
        in ``label``, and the target layer index in ``layer_switch`` if this
        key switches layers (otherwise ``None``).

    Example:
        ```pycon
        >>> adapter.transform("KC_SPACE")
        SvalboardTargetKey(label='Space', layer_switch=None)
        >>> adapter.transform("MO(1)")
        SvalboardTargetKey(label='L2', layer_switch=1)

        ```
    """
    keycode = self._apply_pre_processing(text)

    macro_result, target_layer = self._parse_macro_function(keycode)
    if macro_result is not None:
        return SvalboardTargetKey(
            label=macro_result,
            layer_switch=target_layer,
        )

    is_transparent = keycode in _TRANSPARENT_KEYCODES
    return SvalboardTargetKey(
        label=self._resolve_keycode(keycode),
        is_transparent=is_transparent,
    )

KeymapJsonAdapter

Adapts keymap JSON data from various formats to QMK ordering.

This adapter normalizes keymap data exported from different applications (Vial, Keybard) into the key ordering expected by QMK firmware. This is necessary because each application has its own internal key ordering that differs from QMK's standard layout.

The adapter is stateless and uses only static methods, making it suitable for use as a utility class without instantiation.

Example
>>> from skim.domain.domain_types import KeymapType
>>> # Transform Keybard format to QMK ordering
>>> keybard_layers = [["KC_A"] * 60, ["KC_B"] * 60]
>>> qmk_layers = KeymapJsonAdapter.transform(keybard_layers, KeymapType.KEYBARD)

transform staticmethod

transform(
    json_data: Any, data_type: KeymapType
) -> list[list[str]]

Transform keymap data from a source format to QMK ordering.

Parameters:

Name Type Description Default
json_data Any

The raw keymap data from the source format. Structure varies by format: - VIAL: list[list[list[str]]] (layers → clusters → keys) - KEYBARD: list[list[str]] (layers → keys) - C2JSON: list[list[str]] (layers → keys, already QMK order)

required
data_type KeymapType

The source format type indicating how to interpret and transform the data.

required

Returns:

Type Description
list[list[str]]

Normalized keymap data as list[list[str]] where each inner list

list[list[str]]

contains 60 keycode strings in QMK's expected order.

Example
>>> KeymapJsonAdapter.transform([["KC_A"] * 60], KeymapType.C2JSON)
[['KC_A', 'KC_A', ...]]  # Returned unchanged
Source code in src/skim/domain/adapters/keymap_json_adapter.py
@staticmethod
def transform(json_data: Any, data_type: KeymapType) -> list[list[str]]:
    """Transform keymap data from a source format to QMK ordering.

    Args:
        json_data: The raw keymap data from the source format. Structure
            varies by format:
            - VIAL: list[list[list[str]]] (layers → clusters → keys)
            - KEYBARD: list[list[str]] (layers → keys)
            - C2JSON: list[list[str]] (layers → keys, already QMK order)
        data_type: The source format type indicating how to interpret
            and transform the data.

    Returns:
        Normalized keymap data as list[list[str]] where each inner list
        contains 60 keycode strings in QMK's expected order.

    Example:
        ```pycon
        >>> KeymapJsonAdapter.transform([["KC_A"] * 60], KeymapType.C2JSON)
        [['KC_A', 'KC_A', ...]]  # Returned unchanged

        ```
    """
    if data_type == KeymapType.VIAL:
        return KeymapJsonAdapter._from_vial(json_data)

    if data_type == KeymapType.KEYBARD:
        return KeymapJsonAdapter._from_keybard(json_data)

    return json_data

KeymapTargetAdapter

KeymapTargetAdapter(
    label_adapter: KeycodeLabelAdapter,
    fallthrough_to_layer_zero: bool = True,
)

Transforms keymaps from raw keycode strings to renderable target keys.

This adapter is the final stage of keymap processing, converting raw QMK keycode strings into SvalboardTargetKey objects that contain all information needed for rendering: display labels and layer-switching metadata.

The transformation uses a KeycodeLabelAdapter to resolve each keycode to its display label and extract any layer-switching behavior.

Attributes:

Name Type Description
_label_adapter KeycodeLabelAdapter

The adapter used to transform individual keycodes.

Example
>>> from skim.application.loaders import load_keycode_mappings
>>> from skim.data import SkimConfig
>>> from skim.domain.adapters import KeycodeLabelAdapter, KeymapTargetAdapter
>>> config = SkimConfig()
>>> mappings = load_keycode_mappings(config.keycodes)
>>> label_adapter = KeycodeLabelAdapter(config.keyboard, mappings)
>>> adapter = KeymapTargetAdapter(label_adapter)

Initialize the adapter with configuration and label transformer.

Parameters:

Name Type Description Default
label_adapter KeycodeLabelAdapter

The adapter for transforming individual keycodes to labels.

required
fallthrough_to_layer_zero bool

When True (default), transparent keys on layers above 0 borrow their display label from the same key position on layer 0. Set False to leave transparent keys blank.

True
Source code in src/skim/domain/adapters/keymap_target_adapter.py
def __init__(
    self,
    label_adapter: KeycodeLabelAdapter,
    fallthrough_to_layer_zero: bool = True,
) -> None:
    """Initialize the adapter with configuration and label transformer.

    Args:
        label_adapter: The adapter for transforming individual keycodes
            to labels.
        fallthrough_to_layer_zero: When True (default), transparent keys
            on layers above 0 borrow their display label from the same
            key position on layer 0. Set False to leave transparent keys
            blank.
    """
    self._label_adapter = label_adapter
    self._fallthrough_to_layer_zero = fallthrough_to_layer_zero

transform

transform(
    keymap: SvalboardKeymap[str],
) -> SvalboardKeymap[SvalboardTargetKey]

Transform an entire keymap from strings to target keys.

Processes each layer in the keymap, converting raw keycode strings to SvalboardTargetKey objects containing display labels and layer metadata. When fallthrough is enabled and layer 0 is present, any transparent key on a higher layer is rewritten to display the layer-0 label at the same position.

Parameters:

Name Type Description Default
keymap SvalboardKeymap[str]

A keymap containing raw QMK keycode strings at each key position.

required

Returns:

Type Description
SvalboardKeymap[SvalboardTargetKey]

A new keymap containing SvalboardTargetKey objects at each

SvalboardKeymap[SvalboardTargetKey]

position, ready for rendering.

Source code in src/skim/domain/adapters/keymap_target_adapter.py
def transform(self, keymap: SvalboardKeymap[str]) -> SvalboardKeymap[SvalboardTargetKey]:
    """Transform an entire keymap from strings to target keys.

    Processes each layer in the keymap, converting raw keycode strings
    to SvalboardTargetKey objects containing display labels and layer
    metadata. When fallthrough is enabled and layer 0 is present, any
    transparent key on a higher layer is rewritten to display the
    layer-0 label at the same position.

    Args:
        keymap: A keymap containing raw QMK keycode strings at each
            key position.

    Returns:
        A new keymap containing SvalboardTargetKey objects at each
        position, ready for rendering.
    """
    layers = {
        idx: self._transform_layer(layer, keymap.macros, keymap.tap_dances)
        for idx, layer in keymap.layers.items()
    }

    base = layers.get(0)
    if self._fallthrough_to_layer_zero and base is not None:
        layers = {
            idx: self._apply_fallthrough(layer, base, idx) if idx != 0 else layer
            for idx, layer in layers.items()
        }
    elif not self._fallthrough_to_layer_zero:
        # Without fall-through, transparent keys would otherwise render
        # blank.  Stamp the Vial-style ⛛ glyph on each transparent
        # position so users can still see where the transparent keys
        # are.  The ghost colour applied at render time uses the same
        # ``is_transparent`` flag, so the glyph appears faded.
        layers = {idx: layer.map(_stamp_transparent_glyph) for idx, layer in layers.items()}

    tap_dances = tuple(self._transform_tap_dance(td) for td in keymap.tap_dances)
    macros = tuple(self._transform_macro(macro) for macro in keymap.macros)

    return SvalboardKeymap(layers=layers, tap_dances=tap_dances, macros=macros)