Data Layer¶
Data structures and configuration models.
Configuration Models¶
config ¶
Configuration models for Svalboard Keymap Image Maker tool.
This module defines the Pydantic configuration models used to customize
the appearance and behavior of generated keymap images. The configuration
is hierarchical, with the root :class:SkimConfig containing nested models
for keyboard settings, keycode mappings, and output styling.
Configuration can be loaded from YAML files or constructed programmatically. All models use Pydantic's BaseModel for validation and serialization.
Example
Loading configuration from a YAML file:
import yaml
from skim.data.config import SkimConfig
with open("skim-config.yaml") as f:
data = yaml.safe_load(f)
config = SkimConfig(**data)
Creating configuration programmatically:
from skim.data.config import SkimConfig, LayerColor, Palette
config = SkimConfig()
new_layers = config.output.style.palette.layers + (LayerColor(base_color="#FF0000"),)
new_palette = config.output.style.palette.model_copy(update={"layers": new_layers})
new_style = config.output.style.model_copy(update={"palette": new_palette})
new_output = config.output.model_copy(update={"style": new_style})
config = config.model_copy(update={"output": new_output})
Attributes:
| Name | Type | Description |
|---|---|---|
SplitSidePositionStr |
Annotated type alias for SplitSidePosition that accepts string values and converts them to enum members. |
SpacingValue
module-attribute
¶
A spacing field accepting None, float, int, or a 'N%' string.
After validation the value is always float | None; the renderer
interprets the float's magnitude (< 1.0 proportion, >= 1.0
absolute units).
SplitSidePositionStr
module-attribute
¶
SplitSidePositionStr = Annotated[
SplitSidePosition,
BeforeValidator(lambda v: SplitSidePosition(v)),
]
Annotated type for SplitSidePosition that accepts string inputs.
This type alias allows configuration files to specify hold symbol positions as plain strings (e.g., "inward", "outward") which are automatically converted to SplitSidePosition enum members during validation.
SymbolLegendFlowStr
module-attribute
¶
SymbolLegendFlowStr = Annotated[
SymbolLegendFlow,
BeforeValidator(lambda v: SymbolLegendFlow(v)),
]
LayerOrderStr
module-attribute
¶
LayerOrderStr = Annotated[
LayerOrder, BeforeValidator(lambda v: LayerOrder(v))
]
Annotated type for LayerOrder that accepts string inputs at YAML load time.
ThumbPositionStr
module-attribute
¶
ThumbPositionStr = Annotated[
ThumbPosition,
BeforeValidator(lambda v: ThumbPosition(v)),
]
Annotated type for ThumbPosition that accepts string inputs at YAML load time.
KeyboardLayer ¶
Bases: BaseModel
Configuration for a single keyboard layer.
Defines the metadata associated with a layer in the keymap, including its internal name. This is used to customize how layers are named in generated images.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str | None
|
Optional unique identifier for the layer. Used for internal
reference when processing a keymap from |
name |
str
|
Full descriptive name of the layer (e.g., "Base Layer", "Symbols", "Navigation"). Used as the "image title" on the generated images. |
variant |
str | None
|
Optional secondary label shown below the layer name (e.g., "COLEMAK"). Used to display additional layer metadata in the overview image. Defaults to None if not specified. |
Example
KeyboardFeatures ¶
Bases: BaseModel
Configuration for optional keyboard hardware features.
Controls which optional hardware features are enabled when generating keymap images. These settings affect which keys are rendered.
Attributes:
| Name | Type | Description |
|---|---|---|
double_south |
bool
|
Whether to render the DS (double-south) keys on finger clusters. When False, these positions are hidden. Defaults to False as not all Svalboard configurations have these keys. |
Note
Currently the Svalboard keyboard only have a single feature modifier that can impact a keymap image, but this configuration object is here to future-proof this tool on eventual changes.
Keyboard ¶
Bases: BaseModel
Keyboard-specific configuration settings.
Contains settings that describe the physical keyboard configuration and layer definitions. This is used to customize the rendering based on the specific keyboard setup.
Attributes:
| Name | Type | Description |
|---|---|---|
features |
KeyboardFeatures
|
Hardware feature flags controlling the keymap rendering. Defaults to a KeyboardFeatures instance with all features disabled. |
layers |
Annotated[tuple[KeyboardLayer, ...], BeforeValidator(_coerce_to_tuple)]
|
Tuple of layer configurations defining the metadata for each layer in the keymap. The order corresponds to layer indices (0, 1, 2, etc.). |
Example
features
class-attribute
instance-attribute
¶
features: KeyboardFeatures = Field(
default_factory=KeyboardFeatures
)
layers
class-attribute
instance-attribute
¶
layers: Annotated[
tuple[KeyboardLayer, ...],
BeforeValidator(_coerce_to_tuple),
] = ()
model_post_init ¶
Initialize the layer ID lookup map after model construction.
Builds an internal mapping from layer identifiers to their QMK
firmware indices for efficient layer lookup. If a layer has an
explicit id, that ID is used as the key. Otherwise, the string
representation of the layer's position index is used.
This method is called automatically by Pydantic after the model is constructed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
object
|
Pydantic validation context (unused but required by the Pydantic post-init signature). |
required |
Example
Source code in src/skim/data/config.py
layer_index ¶
Look up a layer's QMK firmware index by its identifier.
Returns the QMK firmware index of the layer matching the given key.
The key can be either a layer's explicit id, the string
representation of its position index (for layers without an id),
or an integer index which is converted to string for lookup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str | None
|
The layer identifier to look up. This can be a layer's
|
required |
Returns:
| Type | Description |
|---|---|
int | None
|
The QMK firmware index of the matching layer, or |
int | None
|
layer matches the given key or if key is |
Example
Source code in src/skim/data/config.py
qmk_index_to_position ¶
Look up a layer's position by its QMK firmware index.
Returns the zero-based position of the layer in the layers tuple that has the given QMK firmware index. This is useful when layer indices in the firmware are non-sequential (e.g., 0, 1, 2, 15).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
qmk_idx
|
int
|
The QMK firmware layer index to look up. |
required |
Returns:
| Type | Description |
|---|---|
int | None
|
The position of the matching layer in the layers tuple, or |
int | None
|
|
Example
Source code in src/skim/data/config.py
layer_qmk_index ¶
Get the QMK firmware index for a layer at a given position.
Returns the QMK firmware layer index for the layer at the given position in the layers tuple.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position
|
int
|
The zero-based position of the layer in the layers tuple. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The QMK firmware layer index. |
Example
Source code in src/skim/data/config.py
Keycode ¶
Bases: BaseModel
A keycode-to-label mapping override.
Defines a custom mapping from a QMK keycode string to a display label. This is used to customize how specific keycodes are rendered, either by preprocessing them before the standard mapping or by overriding the default label entirely.
Attributes:
| Name | Type | Description |
|---|---|---|
keycode |
str
|
The QMK keycode string to match (e.g., "KC_A", "KC_ESC"). Must match exactly, including case. |
target |
str
|
The replacement label or transformed keycode. For pre-processing, this is typically another keycode. For overrides, this is the display label. |
Example
Macro ¶
Bases: BaseModel
A macro reference with an optional human-readable name and preview.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str
|
String id matching how the keycode references this macro
( |
name |
str | None
|
Optional human-readable name surfaced by the renderer. |
preview |
str
|
Single-line display summary, generated at bootstrap time
or set to |
TapDance ¶
Bases: BaseModel
A tap-dance reference with an optional human-readable name and preview.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str
|
String id matching how the keycode references this tap dance
( |
name |
str | None
|
Optional human-readable name surfaced by the renderer. |
preview |
str
|
Single-line display summary, generated at bootstrap time
or set to |
Keycodes ¶
Bases: BaseModel
Configuration for keycode transformation and display.
Contains tuples of keycode mappings that customize how QMK keycodes are transformed and displayed. The pre-processing transformation happens without any processing by the tool. This mapping should not use the alias and other replacements features from key resolution. Then, the standard label mapping happens with the overrides defined in this configuration having higher priority than the default transformations.
Attributes:
| Name | Type | Description |
|---|---|---|
pre_process |
Annotated[tuple[Keycode, ...], BeforeValidator(_coerce_to_tuple)]
|
Keycode transformations applied before standard mapping. Useful for normalizing keycodes or representing custom keycodes that act like othes QMK keycodes including functions. Defaults to an empty tuple. |
overrides |
Annotated[tuple[Keycode, ...], BeforeValidator(_coerce_to_tuple)]
|
Keycode-to-label mappings that override the standard mapping results. Applied after all other transformations. Defaults to an empty tuple. |
macros |
Annotated[tuple[Macro, ...], BeforeValidator(_coerce_to_tuple)]
|
Macro references with optional names and previews. Defaults to an empty tuple. |
tap_dances |
Annotated[tuple[TapDance, ...], BeforeValidator(_coerce_to_tuple)]
|
Tap-dance references with optional names and previews. Defaults to an empty tuple. |
symbol_descriptions |
dict[str, dict[str, str]]
|
User overrides for the bundled symbol
description table, structured as Example: |
function_descriptions |
dict[str, dict[str, str]]
|
|
symbol_legend_aliases |
dict[str, str]
|
Example
pre_process
class-attribute
instance-attribute
¶
pre_process: Annotated[
tuple[Keycode, ...], BeforeValidator(_coerce_to_tuple)
] = ()
overrides
class-attribute
instance-attribute
¶
overrides: Annotated[
tuple[Keycode, ...], BeforeValidator(_coerce_to_tuple)
] = ()
macros
class-attribute
instance-attribute
¶
macros: Annotated[
tuple[Macro, ...], BeforeValidator(_coerce_to_tuple)
] = ()
tap_dances
class-attribute
instance-attribute
¶
tap_dances: Annotated[
tuple[TapDance, ...], BeforeValidator(_coerce_to_tuple)
] = ()
symbol_descriptions
class-attribute
instance-attribute
¶
function_descriptions
class-attribute
instance-attribute
¶
symbol_legend_aliases
class-attribute
instance-attribute
¶
__hash__ ¶
Source code in src/skim/data/config.py
Spacing ¶
Bases: BaseModel
Configurable spacing values for the document layout.
Every gap, padding, and inset the renderer applies has a built-in proportional default. Override any of them here. Each field accepts:
- Float < 1.0 — proportion of the field's base.
- Float ≥ 1.0 — absolute SVG units (independent of doc width).
- String
"N%"— shorthand forN / 100.0(proportion form). None(default) — the field's built-in default proportion.
Each field documents its base. Most scale to the document width
(output.layout.width); the two cluster geometry fields scale to
the cluster's own width so they stay proportional regardless of how
the keyboard is positioned in the canvas.
Example
margin
class-attribute
instance-attribute
¶
margin: SpacingValue = Field(default=None)
Canvas edge → outer border line. Default: 0 (flush).
inset
class-attribute
instance-attribute
¶
inset: SpacingValue = Field(default=None)
Border line → content. Also the inter-element gap inside the
document Column. Default: 40/1600 (2.5%) of doc width.
column_gap
class-attribute
instance-attribute
¶
column_gap: SpacingValue = Field(default=None)
Horizontal gap between half-keyboards (and between side-by-side
sections). Default: 40/1600 (2.5%) of doc width.
section_spacing
class-attribute
instance-attribute
¶
section_spacing: SpacingValue = Field(default=None)
Section title stripe → section body. Default:
24/1600 (1.5%) of doc width.
section_title_rule_gap
class-attribute
instance-attribute
¶
section_title_rule_gap: SpacingValue = Field(default=None)
Section title bottom → rule line below it. Default:
9/1600 (~0.56%) of doc width.
table_header_spacing
class-attribute
instance-attribute
¶
table_header_spacing: SpacingValue = Field(default=None)
Table header → first row (also: chip → cells, named-macro
header → pill row). Default: 12/1600 (0.75%) of doc width.
table_col_spacing
class-attribute
instance-attribute
¶
table_col_spacing: SpacingValue = Field(default=None)
Between adjacent table columns (pills, cells). Default:
6/1600 (0.375%) of doc width.
table_row_spacing
class-attribute
instance-attribute
¶
table_row_spacing: SpacingValue = Field(default=None)
Between adjacent table rows. Default: 9/1600 (~0.56%) of
doc width.
finger_key_gap
class-attribute
instance-attribute
¶
finger_key_gap: SpacingValue = Field(default=None)
Center key → outer keys inside a finger cluster. Base: finger
cluster width. Default: 1.8%.
thumb_key_gap
class-attribute
instance-attribute
¶
thumb_key_gap: SpacingValue = Field(default=None)
Vertical gap above each outer thumb key (pad / nail / up /
knuckle). Base: thumb cluster width. Default: 3.8%.
layer_indicator_spacing
class-attribute
instance-attribute
¶
layer_indicator_spacing: SpacingValue = Field(default=None)
Outer key → its layer indicator circle. Default chosen so finger and thumb indicators share the same gap regardless of cluster size.
chip_padding
class-attribute
instance-attribute
¶
chip_padding: SpacingValue = Field(default=None)
Symmetric horizontal inset inside any chip. Vertical inset is
derived as chip_padding * 0.25. Default:
20/1600 (1.25%) of doc width.
tap_dance_pill_padding
class-attribute
instance-attribute
¶
tap_dance_pill_padding: SpacingValue = Field(default=None)
Symmetric horizontal inset inside a tap-dance pill (cell).
Vertical inset is derived as tap_dance_pill_padding * 0.25.
Default: 20/1600 (1.25%) of doc width.
macro_action_inset
class-attribute
instance-attribute
¶
macro_action_inset: SpacingValue = Field(default=None)
Uniform inset for all three positions inside a macro pill —
pill edge → icon centre, icon centre → text start, text end →
pill edge. Default: 10/1600 (0.625%) of doc width.
layer_badge_inset
class-attribute
instance-attribute
¶
layer_badge_inset: SpacingValue = Field(default=None)
Leading horizontal inset inside a layer badge (badge edge →
label text). Trailing inset is derived as
layer_badge_inset * 2. Default: 15/1600 (~0.94%) of doc
width.
Layout ¶
Bases: BaseModel
Layout dimensions and spacing configuration.
Controls the overall dimensions and spacing of generated keymap images. The height is calculated automatically based on the width to maintain the correct aspect ratio for the Svalboard layout.
Attributes:
| Name | Type | Description |
|---|---|---|
width |
float
|
Total width of the generated image in SVG units (typically pixels at default scale). Defaults to 1600. |
spacing |
Spacing
|
Spacing configuration for margins and padding. Defaults to a Spacing instance with default values. |
Border ¶
Bases: BaseModel
Border styling configuration.
Controls the appearance of borders drawn around the keyboard layout and optionally around individual key groups.
Attributes:
| Name | Type | Description |
|---|---|---|
width |
float
|
Line width of the border in SVG units. Defaults to 2. |
radius |
float
|
Corner radius for rounded borders in SVG units. Set to 0 for square corners. Defaults to 10. |
LayerConnector ¶
Bases: BaseModel
Styling for the dotted connector paths in the keymap overview.
The overview links each layer indicator circle to its key on the
miniature keymap with a dotted path. This block configures
visibility, stroke, and dotted cadence. The two stroke / spacing
fields follow the :data:SpacingValue magnitude rule (< 1.0
proportion of doc width, >= 1.0 absolute SVG units, "N%"
string shorthand).
Attributes:
| Name | Type | Description |
|---|---|---|
show |
bool
|
Whether to draw connector paths in the overview at all.
Defaults to |
width |
SpacingValue
|
Stroke width of the connector path. Default:
|
dot_spacing |
SpacingValue
|
Gap between adjacent dots along the path —
controls the visible cadence of the dotted line. Default:
|
Example
LayerIndicator ¶
Bases: BaseModel
Styling for the layer-indicator badges painted next to
layer-switch keys in each cluster (and the matching badges in
the overview's LAYERS column).
Mirrors :class:LayerConnector — visibility flag plus a stroke
width that follows the :data:SpacingValue magnitude rule
(< 1.0 proportion of doc width, >= 1.0 absolute SVG
units, "N%" string shorthand).
The gap between an outer key's edge and its indicator circle
lives elsewhere, on
output.layout.spacing.layer_indicator_spacing, since it's a
spacing value that applies between two elements rather than a
property of the indicator itself.
Attributes:
| Name | Type | Description |
|---|---|---|
show |
bool
|
Whether to draw layer-indicator badges. Defaults to
|
width |
SpacingValue
|
Stroke width of the indicator circle. Default:
|
Example
Strokes ¶
Bases: BaseModel
Configurable stroke widths for the rendered chrome.
Two stroke widths live here; both follow the :data:SpacingValue
magnitude rule (< 1.0 proportion of doc width, >= 1.0
absolute SVG units, "N%" string shorthand).
Three stroke values live elsewhere because they're tied to a visibility flag or other styling on the same conceptual element:
output.style.border.width— paired withBorder.radius.output.style.overview.layer_connector.width— paired withlayer_connector.showanddot_spacing.output.style.layer_indicator.width— paired withlayer_indicator.show.
Attributes:
| Name | Type | Description |
|---|---|---|
chip_outline |
SpacingValue
|
Stroke around macro and tap-dance chips.
Default: |
header_rule |
SpacingValue
|
Stroke of every header rule — the section title
stripe rule and the named-macro hairline below the chip.
Default: |
Example
LayerColor ¶
Bases: BaseModel
Color configuration for a keyboard layer.
Defines the colour scheme used for keys on a specific layer. Each cluster position (centre, north, east, south, west, double-south) pulls its fill from a 6-stop gradient — adjacent keys land on adjacent stops, so a cluster reads with visual depth.
Two ways to populate the gradient:
- Provide
base_color(and optionallycolor_index) and leavegradientatNone. :func:skim.application.keymap_generator.draw_keymapauto-derives a 6-stop gradient via :func:skim.application.render.styling.make_gradient, anchoringbase_coloratcolor_indexand stepping darker / lighter to fill the surrounding stops. - Set
gradientexplicitly to a 6-tuple of CSS colour strings. In that casedraw_keymapkeeps the user-supplied tuple verbatim.color_indexstill matters: it picks which stop the rest of the render path treats as the layer's "primary" colour (indicator circles, layer badges, the layer-trigger highlight on a source key).
Attributes:
| Name | Type | Description |
|---|---|---|
base_color |
str
|
The primary CSS colour for the layer
(e.g. |
color_index |
int
|
Position (0–5) where |
gradient |
Annotated[tuple[str, str, str, str, str, str], BeforeValidator(_coerce_to_tuple)] | None
|
Optional 6-tuple of CSS colour strings — one per
cluster position. When |
Example
>>> # Auto-derived gradient — base_color anchors index 2.
>>> layer = LayerColor(base_color="#FF0000")
>>> layer.gradient is None
True
>>> # Explicit 6-stop gradient.
>>> layer = LayerColor(
... base_color="#FF0000",
... gradient=("#FF0000", "#CC0000", "#990000", "#660000", "#330000", "#000000"),
... )
>>> layer[1]
'#CC0000'
gradient
class-attribute
instance-attribute
¶
gradient: (
Annotated[
tuple[str, str, str, str, str, str],
BeforeValidator(_coerce_to_tuple),
]
| None
) = None
dark_accent_color
property
¶
Get the darker accent color for this layer.
Returns the second color in the gradient (index 1) if a gradient is defined, otherwise returns the base_color. This is typically used for key borders, shadows, or accent elements.
Returns:
| Type | Description |
|---|---|
str
|
A CSS color string for the accent color. |
__getitem__ ¶
Get the color for a specific cluster position.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
Position index from 0-5 corresponding to cluster key positions. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The CSS colour string for the gradient stop at |
str
|
When |
str
|
"let Skim derive it" case), falls back to |
str
|
for every position so the lookup never fails. The |
str
|
keymap-generation pipeline replaces |
str
|
with auto-derived 6-stop tuples before rendering, so the |
str
|
fallback only kicks in for callers that bypass that |
str
|
pipeline (tests, introspection, the TUI's pre-fill path). |
Raises:
| Type | Description |
|---|---|
IndexError
|
If index is outside the valid range (0-5). |
Source code in src/skim/data/config.py
__str__ ¶
Return a string representation of the color configuration.
Returns:
| Type | Description |
|---|---|
str
|
A JSON-like array string of the colors. For single-color |
str
|
mode, returns a single-element array. For gradient mode, |
str
|
returns all 6 colors. |
Source code in src/skim/data/config.py
Palette ¶
Bases: BaseModel
Color palette configuration for the entire keyboard.
Defines the color scheme used throughout the generated keymap images, including background colors, text colors, and per-layer key colors.
Attributes:
| Name | Type | Description |
|---|---|---|
neutral_color |
str
|
Color for keys that don't have layer-specific coloring (e.g., some thumb cluster keys). Defaults to "#6F768B" (gray). |
text_color |
str
|
Default text color for non key labels. Defaults to "black". |
key_label_color |
str
|
Text color for key labels. Defaults to "white" for contrast against typically dark key backgrounds. |
background_color |
str
|
Background color for the entire image. Defaults to "white". |
border_color |
str
|
Color for keyboard and cluster borders. Defaults to "black". |
macro_color |
str
|
Background color for macro badges and macro-table titles in the rendered keymap. Defaults to "#89511C". |
tap_dance_color |
str
|
Background color for tap-dance badges and tap-dance-table titles in the rendered keymap. Defaults to "#41687F". |
layers |
Annotated[tuple[LayerColor, ...], BeforeValidator(_coerce_to_tuple)]
|
Tuple of LayerColor configurations, one per layer. Layer indices correspond to positions in this tuple. Defaults to an empty tuple. |
Example
layers
class-attribute
instance-attribute
¶
layers: Annotated[
tuple[LayerColor, ...],
BeforeValidator(_coerce_to_tuple),
] = ()
SplitSidePosition ¶
Bases: str, Enum
Position options for hold-tap key symbol placement.
Controls where the "hold" portion of a hold-tap key is displayed relative to the "tap" portion. This affects keys like LT(1, KC_A) where tapping produces "A" but holding activates layer 1.
Attributes:
| Name | Type | Description |
|---|---|---|
QMK_DEFINED |
Use the position defined in QMK firmware settings. This respects the argument order of the macro-functions defined by QMK, which is always the hold part as the first argument, and the tap part as the second. |
|
INWARD |
Place the hold symbol toward the center of the keyboard cluster (right on left side, left on right side). |
|
OUTWARD |
Place the hold symbol toward the outside of the keyboard cluster (left on left side, right on right side). This is the default. |
SymbolLegendFlow ¶
Bases: str, Enum
Flow direction for the symbol legend's multi-column layout.
Attributes:
| Name | Type | Description |
|---|---|---|
ROW_MAJOR |
Entries fill left-to-right in the top row first, then drop to the next row. |
|
COLUMN_MAJOR |
Entries fill top-to-bottom in the leftmost column first, then move to the next column. This is the default. |
MacrosLegend ¶
Bases: BaseModel
Configuration for the macros legend table.
Controls visibility (whether the macros legend renders inside per-layer / overview images) and the body-scale multiplier applied when the macros are rendered as a standalone image.
Attributes:
| Name | Type | Description |
|---|---|---|
show |
bool
|
Whether to embed the macros legend in per-layer and
overview images. Defaults to |
scale |
float
|
Body-scale multiplier for the standalone macros image
(the artifact |
TapDancesLegend ¶
Bases: BaseModel
Configuration for the tap-dances legend table.
Mirrors :class:MacrosLegend — visibility plus a body-scale
multiplier for the standalone tap-dances image.
Attributes:
| Name | Type | Description |
|---|---|---|
show |
bool
|
Whether to embed the tap-dances legend in per-layer and
overview images. Defaults to |
scale |
float
|
Body-scale multiplier for the standalone tap-dances
image. Defaults to |
SymbolsLegend ¶
Bases: BaseModel
Configuration for the symbols legend table.
Carries the same visibility / scale fields as the macros and tap-dances legends, plus two layout knobs unique to the symbol table (the multi-column layout's flow direction and column count).
Attributes:
| Name | Type | Description |
|---|---|---|
show |
bool
|
Whether to embed the symbol legend in per-layer and
overview images. Defaults to |
scale |
float
|
Body-scale multiplier for the standalone symbols image.
Defaults to |
flow |
SymbolLegendFlowStr
|
Flow direction for the multi-column layout.
|
columns |
int | None
|
When set, force the standalone symbols image to lay
out at exactly this many columns and shrink the canvas to
fit the resulting natural width. |
Example
LegendTables ¶
Bases: BaseModel
Container for the three legend tables Skim renders alongside the keymap: macros, tap-dances, and symbols.
Each sub-block carries its own visibility flag and standalone-image body-scale multiplier (plus, for symbols, a flow direction and column count).
Attributes:
| Name | Type | Description |
|---|---|---|
macros |
MacrosLegend
|
Configuration for the macros legend. |
tap_dances |
TapDancesLegend
|
Configuration for the tap-dances legend. |
symbols |
SymbolsLegend
|
Configuration for the symbol legend. |
Example
macros
class-attribute
instance-attribute
¶
macros: MacrosLegend = Field(default_factory=MacrosLegend)
tap_dances
class-attribute
instance-attribute
¶
tap_dances: TapDancesLegend = Field(
default_factory=TapDancesLegend
)
symbols
class-attribute
instance-attribute
¶
symbols: SymbolsLegend = Field(
default_factory=SymbolsLegend
)
LayerOrder ¶
ThumbPosition ¶
Overview ¶
Bases: BaseModel
Overview-specific layout configuration.
Controls how the multi-layer overview image stacks its per-layer rows, where the (single) thumb cluster row sits among them, which layer the thumb cluster is sourced from, and the dotted layer- connector paths that link each layer-indicator badge to its key on the miniature keymap.
Attributes:
| Name | Type | Description |
|---|---|---|
layer_order |
LayerOrderStr
|
|
thumb_position |
ThumbPositionStr
|
Where to place the thumb-cluster row in the
stack. |
thumb_layer |
int | None
|
QMK layer index whose thumb cluster is rendered
in the overview's THUMBS row. |
layer_connector |
LayerConnector
|
Configuration for the dotted connector paths in the keymap overview (visibility + stroke + dot cadence). |
Example
Layer 0 at the top with its thumb cluster directly below it, then layers 1, 2, … going down::
output:
style:
overview:
layer_order: ascending
thumb_position: follow
layer_order
class-attribute
instance-attribute
¶
layer_order: LayerOrderStr = Field(default=DESCENDING)
thumb_position
class-attribute
instance-attribute
¶
thumb_position: ThumbPositionStr = Field(default=FOLLOW)
layer_connector
class-attribute
instance-attribute
¶
layer_connector: LayerConnector = Field(
default_factory=LayerConnector
)
Style ¶
Bases: BaseModel
Visual styling configuration for keymap images.
Controls the overall visual appearance of generated images, including colors, borders, and key labeling options.
Attributes:
| Name | Type | Description |
|---|---|---|
use_layer_colors_on_keys |
bool
|
Whether to color keys backgrounds based on the layer it activates. When True, keys use colors from the palette's layer list. When False, all keys use their standard colors. Defaults to True. |
hold_symbol_position |
SplitSidePositionStr
|
Where to place the "hold" portion of
hold-tap keys relative to the "tap" portion. See
:class: |
use_system_fonts |
bool
|
When True, the SVG references system fonts by name instead of embedding the bundled font subsets. Smaller file size; viewers without those fonts installed will see a fallback. Defaults to False. |
show_transparent_fallthrough |
bool
|
When True (default), transparent keycodes (KC_TRNS / _) on layers above 0 render the same label as layer 0 in a faded "ghost" color. Set False to leave transparent keys blank. |
border |
Border | None
|
Document border configuration, or None to disable.
Defaults to a :class: |
overview |
Overview
|
Overview-image-specific layout configuration — layer-row ordering, thumb-row position, and the dotted layer-connector paths. |
layer_indicator |
LayerIndicator
|
Configuration for the layer-indicator badges painted next to layer-switch keys (visibility + stroke). |
legend_tables |
LegendTables
|
Container for the macros / tap-dances / symbols legend tables (visibility + scale per legend, plus the symbol-specific flow and column count). |
strokes |
Strokes
|
Stroke widths for chrome lines that don't have their own dedicated block (chip outlines, header rules). |
palette |
Palette
|
Color palette configuration for the entire keyboard. |
Example
hold_symbol_position
class-attribute
instance-attribute
¶
hold_symbol_position: SplitSidePositionStr = Field(
default=OUTWARD
)
show_transparent_fallthrough
class-attribute
instance-attribute
¶
layer_indicator
class-attribute
instance-attribute
¶
layer_indicator: LayerIndicator = Field(
default_factory=LayerIndicator
)
legend_tables
class-attribute
instance-attribute
¶
legend_tables: LegendTables = Field(
default_factory=LegendTables
)
Output ¶
Bases: BaseModel
Output configuration for generated images.
Groups together layout dimensions and visual styling settings that control the final appearance of generated keymap images.
Attributes:
| Name | Type | Description |
|---|---|---|
layout |
Layout
|
Layout dimensions and spacing configuration. Defaults to a Layout instance with default values. |
style |
Style
|
Visual styling configuration. Defaults to a Style instance with default values. |
keymap_title |
str | None
|
Optional title for the overview keymap image. When set, overrides the auto-generated title. Defaults to None. |
copyright |
str | None
|
Optional copyright notice displayed in the overview image. Defaults to None. |
Example
SkimConfig ¶
Bases: BaseModel
Root configuration model for skim keymap image generation.
This is the top-level configuration class that contains all settings for generating Svalboard keymap images. It can be loaded from YAML files or constructed programmatically.
The configuration is organized into three main sections: - keyboard: Hardware and layer settings - keycodes: Keycode transformation and display rules - output: Layout dimensions and visual styling
Attributes:
| Name | Type | Description |
|---|---|---|
keyboard |
Keyboard
|
Keyboard-specific configuration including hardware features and layer definitions. Defaults to a Keyboard instance with default values. |
keycodes |
Keycodes
|
Keycode transformation rules including pre-processing and overrides. Defaults to a Keycodes instance with empty rule tuples. |
output |
Output
|
Output configuration including layout dimensions and visual styling. Defaults to an Output instance with default values. |
Example
Creating a basic configuration:
config = SkimConfig()
new_layout = config.output.layout.model_copy(update={"width": 1200})
new_output = config.output.model_copy(update={"layout": new_layout})
config = config.model_copy(update={"output": new_output})
Loading from a dictionary (e.g., parsed YAML):
resolve_spacing ¶
Resolve a :data:SpacingValue to absolute SVG units.
Implements the magnitude rule:
None→base * default_proportion(renderer's built-in).value < 1.0→base * value(proportion of base).value >= 1.0→value(absolute SVG units).
The 1.0 boundary is conventional: proportions live in
[0, 1), and any meaningful spacing in this codebase is at
least 1 SVG unit (sub-pixel gaps are invisible anyway).
Example
Source code in src/skim/data/config.py
Keyboard Structures¶
keyboard ¶
Keyboard cluster data structures for Svalboard key layouts.
This module provides generic container classes for representing key clusters on the Svalboard keyboard. The Svalboard has a unique 3D layout with two types of clusters:
- Finger clusters: 5-directional keys (center, north, east, south, west) plus an optional double-south key. Each hand has 4 finger clusters (pinky, ring, middle, index).
- Thumb clusters: 6 keys (down, pad, up, nail, knuckle, double-down). Each hand has 1 thumb cluster.
Both cluster types are generic containers that can hold any type of value, making them suitable for storing keycodes, labels, colors, or any other per-key data.
Example
Creating clusters with a default value and overrides::
from skim.data.keyboard import FingerCluster, ThumbCluster
# All keys set to empty string, except south_key
finger = FingerCluster("", south_key="A")
# Clusters are iterable and unpackable
center, north, east, south, west, dsouth = finger
Zipping multiple clusters together::
from skim.data.keyboard import FingerCluster, zip_clusters
keycodes = FingerCluster("KC_NO", center_key="KC_A")
labels = FingerCluster("", center_key="A")
# Create a cluster where each position contains both values
combined = zip_clusters(FingerCluster, "KeyData", codes=keycodes, labels=labels)
# combined.center_key.codes == "KC_A"
# combined.center_key.labels == "A"
__all__
module-attribute
¶
__all__ = [
"ClusterT",
"FingerCluster",
"ThumbCluster",
"SplitSide",
"SvalboardLayout",
"SvalboardKeymap",
"zip_clusters",
"zip_layouts",
]
SVALBOARD_KEY_COUNT
module-attribute
¶
Constant value representing how many keys a Svalboard keyboard has.
SVALBOARD_SIDE_KEY_COUNT
module-attribute
¶
SVALBOARD_SIDE_KEY_COUNT = SVALBOARD_KEY_COUNT / 2
Constant representing how many keys a single sides of the Svalboard has.
SVALBOARD_CLUSTER_KEY_COUNT
module-attribute
¶
Constant value representing how many keys a single key cluster has.
ClusterT
module-attribute
¶
TypeVar bound to cluster types for writing generic cluster functions.
This TypeVar is constrained to FingerCluster or ThumbCluster (or any subclass of _ClusterBase), enabling type-safe generic functions that work with any cluster type.
Example
Writing a generic function that works with any cluster::
from skim.data.keyboard import ClusterT, FingerCluster, ThumbCluster
def count_non_empty(cluster: ClusterT) -> int:
return sum(1 for value in cluster if value)
finger = FingerCluster("", center_key="A")
thumb = ThumbCluster("X")
count_non_empty(finger) # Works with FingerCluster
count_non_empty(thumb) # Works with ThumbCluster
FingerCluster
dataclass
¶
FingerCluster(default: T | _Unset = _UNSET, **kwargs: Any)
Bases: _ClusterBase[T]
A container for values associated with a Svalboard finger cluster.
Each finger on the Svalboard has a 5-directional key cluster arranged in a cross pattern, plus an optional double-south key for chording. The physical layout corresponds to pushing the finger in different directions:
- center_key: The home/rest position (pressing straight down)
- north_key: Pushing the finger forward (away from palm)
- east_key: Pushing the finger toward the thumb
- south_key: Pulling the finger backward (toward palm)
- west_key: Pushing the finger away from the thumb
- double_south_key: A secondary south key on certain Svalboard boards
This class is generic and can store any type of value at each position, making it suitable for keycodes, labels, styling information, or any other per-key data.
The class supports two initialization modes: 1. Explicit: All six fields must be provided as keyword arguments 2. Default with overrides: A default value fills all positions, with optional keyword arguments to override specific positions
Attributes:
| Name | Type | Description |
|---|---|---|
center_key |
T
|
Value for the center (home) position. |
north_key |
T
|
Value for the north (forward) position. |
east_key |
T
|
Value for the east (thumb-ward) position. |
south_key |
T
|
Value for the south (backward) position. |
west_key |
T
|
Value for the west (away from thumb) position. |
double_south_key |
T
|
Value for the double-south position. |
Example
Creating with all explicit values::
cluster = FingerCluster(
center_key="A",
north_key="B",
east_key="C",
south_key="D",
west_key="E",
double_south_key="F",
)
Creating with a default and overrides::
cluster = FingerCluster("", south_key="Space")
# center="", north="", east="", south="Space", west="", dsouth=""
Unpacking values::
center, north, east, south, west, dsouth = cluster
Initialize the finger cluster.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
default
|
T | _Unset
|
Optional default value for all positions. If provided, any position not explicitly set via kwargs will use this value. If not provided, all positions must be set via kwargs. |
_UNSET
|
**kwargs
|
Any
|
Key position values. Valid keys are: center_key, north_key, east_key, south_key, west_key, double_south_key. |
{}
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If default is not provided and any required key position is missing from kwargs. |
Source code in src/skim/data/keyboard.py
from_sequence
classmethod
¶
from_sequence(values: Sequence[T]) -> FingerCluster[T]
Create a FingerCluster from a sequence of 6 values.
See :meth:_ClusterBase.from_sequence for full documentation.
Source code in src/skim/data/keyboard.py
map ¶
map(fn: Callable[[T], U]) -> FingerCluster[U]
Create a new FingerCluster by applying a function to each value.
See :meth:_ClusterBase.map for full documentation.
Source code in src/skim/data/keyboard.py
ThumbCluster
dataclass
¶
ThumbCluster(default: T | _Unset = _UNSET, **kwargs: Any)
Bases: _ClusterBase[T]
A container for values associated with a Svalboard thumb cluster.
Each thumb on the Svalboard has a cluster of keys operated by different parts of the thumb and in different directions. The physical layout corresponds to:
- down_key: Pressing the thumb straight down
- pad_key: Using the thumb pad (fleshy part)
- up_key: Pressing upward with the thumb
- nail_key: Using the thumbnail area
- knuckle_key: Using the thumb knuckle
- double_down_key: A secondary down position activated by exercing extra force to the down key
This class is generic and can store any type of value at each position, making it suitable for keycodes, labels, styling information, or any other per-key data.
The class supports two initialization modes: 1. Explicit: All six fields must be provided as keyword arguments 2. Default with overrides: A default value fills all positions, with optional keyword arguments to override specific positions
Attributes:
| Name | Type | Description |
|---|---|---|
down_key |
T
|
Value for the down (pressing) position. |
pad_key |
T
|
Value for the thumb pad position. |
up_key |
T
|
Value for the up position. |
nail_key |
T
|
Value for the thumbnail position. |
knuckle_key |
T
|
Value for the thumb knuckle position. |
double_down_key |
T
|
Value for the double-down position. |
Example
Creating with all explicit values::
cluster = ThumbCluster(
down_key="Space",
pad_key="Enter",
up_key="Tab",
nail_key="Esc",
knuckle_key="Ctrl",
double_down_key="",
)
Creating with a default and overrides::
cluster = ThumbCluster("", down_key="Space")
# down="Space", pad="", up="", nail="", knuckle="", ddown=""
Unpacking values::
down, pad, up, nail, knuckle, ddown = cluster
Initialize the thumb cluster.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
default
|
T | _Unset
|
Optional default value for all positions. If provided, any position not explicitly set via kwargs will use this value. If not provided, all positions must be set via kwargs. |
_UNSET
|
**kwargs
|
Any
|
Key position values. Valid keys are: down_key, pad_key, up_key, nail_key, knuckle_key, double_down_key. |
{}
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If default is not provided and any required key position is missing from kwargs. |
Source code in src/skim/data/keyboard.py
from_sequence
classmethod
¶
from_sequence(values: Sequence[T]) -> ThumbCluster[T]
Create a ThumbCluster from a sequence of 6 values.
See :meth:_ClusterBase.from_sequence for full documentation.
Source code in src/skim/data/keyboard.py
map ¶
map(fn: Callable[[T], U]) -> ThumbCluster[U]
Create a new ThumbCluster by applying a function to each value.
See :meth:_ClusterBase.map for full documentation.
Source code in src/skim/data/keyboard.py
SplitSide
dataclass
¶
SplitSide(
index: FingerCluster[T],
middle: FingerCluster[T],
ring: FingerCluster[T],
pinky: FingerCluster[T],
thumb: ThumbCluster[T],
)
Bases: Generic[T]
A container representing one side (left or right) of a Svalboard keyboard.
Each side of the Svalboard consists of four finger clusters (index, middle, ring, pinky) and one thumb cluster. This class provides a generic container that can hold any type of per-key data for an entire side of the keyboard.
The class is generic over type T, which represents the type of value stored at each key position, allowing it to be used for keycodes, labels, colors, or any other per-key data.
Attributes:
| Name | Type | Description |
|---|---|---|
index |
FingerCluster[T]
|
The index finger cluster (6 keys). |
middle |
FingerCluster[T]
|
The middle finger cluster (6 keys). |
ring |
FingerCluster[T]
|
The ring finger cluster (6 keys). |
pinky |
FingerCluster[T]
|
The pinky finger cluster (6 keys). |
thumb |
ThumbCluster[T]
|
The thumb cluster (6 keys). |
Example
Creating a side with string values::
from skim.data.keyboard import SplitSide, FingerCluster, ThumbCluster
side = SplitSide(
index=FingerCluster(""),
middle=FingerCluster(""),
ring=FingerCluster(""),
pinky=FingerCluster(""),
thumb=ThumbCluster(""),
)
Accessing keys by linear index::
key = side[0] # First key of index finger (center)
key = side[24] # First key of thumb (down)
Iterating over all clusters::
for cluster in side:
print(cluster)
fingers
property
¶
fingers: tuple[
FingerCluster[T],
FingerCluster[T],
FingerCluster[T],
FingerCluster[T],
]
Return all finger clusters as a tuple.
Returns:
| Type | Description |
|---|---|
FingerCluster[T]
|
A tuple of the four finger clusters in order: |
FingerCluster[T]
|
(index, middle, ring, pinky). |
__iter__ ¶
__iter__() -> Iterator[
FingerCluster[T] | ThumbCluster[T]
]
Iterate over all clusters in the side.
Yields clusters in order: index, middle, ring, pinky, thumb.
Yields:
| Type | Description |
|---|---|
FingerCluster[T] | ThumbCluster[T]
|
Each cluster on this side of the keyboard, starting with finger |
FingerCluster[T] | ThumbCluster[T]
|
clusters and ending with the thumb cluster. |
Source code in src/skim/data/keyboard.py
__getitem__ ¶
__getitem__(idx: int) -> T
Access a key value by linear index.
Provides flat indexing across all 30 keys on this side. Keys 0-23 are the finger clusters (6 keys each × 4 fingers), and keys 24-29 are the thumb cluster.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
idx
|
int
|
Linear index from 0-29. Indices 0-5 are index finger, 6-11 are middle finger, 12-17 are ring finger, 18-23 are pinky finger, and 24-29 are thumb. |
required |
Returns:
| Type | Description |
|---|---|
T
|
The value stored at the specified key position. |
Raises:
| Type | Description |
|---|---|
IndexError
|
If idx is outside the valid range (0-29). |
Example
Accessing individual keys::
side[0] # Index finger center key
side[6] # Middle finger center key
side[24] # Thumb down key
Source code in src/skim/data/keyboard.py
SvalboardLayout
dataclass
¶
Bases: Generic[T]
A container representing the complete Svalboard keyboard layout.
The Svalboard is a split ergonomic keyboard with a unique 3D key arrangement. This class provides a container for storing per-key data across the entire keyboard, with support for both hierarchical access (side → cluster → key) and flat linear indexing.
The keyboard has 60 total keys: - Left side: 24 finger keys (4 clusters × 6 keys) + 6 thumb keys = 30 keys - Right side: 24 finger keys (4 clusters × 6 keys) + 6 thumb keys = 30 keys
The class is generic over type T, which represents the type of value stored at each key position.
Attributes:
| Name | Type | Description |
|---|---|---|
left |
SplitSide[T]
|
The left side of the keyboard (30 keys). |
right |
SplitSide[T]
|
The right side of the keyboard (30 keys). |
Example
Creating a layout with string values::
from skim.data.keyboard import (
SvalboardLayout,
SplitSide,
FingerCluster,
ThumbCluster,
)
def make_side():
return SplitSide(
index=FingerCluster(""),
middle=FingerCluster(""),
ring=FingerCluster(""),
pinky=FingerCluster(""),
thumb=ThumbCluster(""),
)
layout = SvalboardLayout(left=make_side(), right=make_side())
Accessing keys by linear index::
key = layout[0] # Right index finger center
key = layout[24] # Left index finger center
key = layout[48] # Right thumb down
key = layout[54] # Left thumb down
Iterating over all keys::
for key in layout:
print(key) # Prints all 60 keys
__iter__ ¶
__iter__() -> Iterator[T]
Iterate over all key values in the layout.
Yields keys in the standard Svalboard order: 1. Right hand finger clusters (index → pinky, 24 keys) 2. Left hand finger clusters (index → pinky, 24 keys) 3. Right thumb cluster (6 keys) 4. Left thumb cluster (6 keys)
This order matches the typical QMK keymap array layout for the Svalboard.
Yields:
| Type | Description |
|---|---|
T
|
Each key value in the layout, totaling 60 values. |
Source code in src/skim/data/keyboard.py
__getitem__ ¶
__getitem__(idx: int) -> T
Access a key value by linear index.
Provides flat indexing across all 60 keys in the layout using the standard Svalboard key ordering: - 0-23: Right hand finger keys - 24-47: Left hand finger keys - 48-53: Right thumb keys - 54-59: Left thumb keys
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
idx
|
int
|
Linear index from 0-59. |
required |
Returns:
| Type | Description |
|---|---|
T
|
The value stored at the specified key position. |
Raises:
| Type | Description |
|---|---|
IndexError
|
If idx is outside the valid range (0-59). |
Example
Accessing individual keys::
layout[0] # Right index finger center
layout[24] # Left index finger center
layout[48] # Right thumb down
layout[54] # Left thumb down
Source code in src/skim/data/keyboard.py
from_sequence
classmethod
¶
from_sequence(values: Sequence[T]) -> SvalboardLayout[T]
Create a SvalboardLayout from a flat sequence of 60 values.
Constructs a complete keyboard layout from a linear sequence of values,
mapping each index to its corresponding key position using the standard
Svalboard ordering (same as __getitem__ and __iter__).
The mapping is: - 0-23: Right hand finger keys (index, middle, ring, pinky clusters) - 24-47: Left hand finger keys (index, middle, ring, pinky clusters) - 48-53: Right thumb keys - 54-59: Left thumb keys
Within each finger cluster, the 6 keys are ordered: center, north, east, south, west, double_south.
Within each thumb cluster, the 6 keys are ordered: down, pad, up, nail, knuckle, double_down.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
Sequence[T]
|
A sequence of exactly 60 values to populate the layout.
Can be a list, tuple, or any object supporting |
required |
Returns:
| Type | Description |
|---|---|
SvalboardLayout[T]
|
A new SvalboardLayout with values distributed across all key |
SvalboardLayout[T]
|
positions. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the sequence does not contain exactly 60 values. |
Example
Creating a layout from a list::
keys = ["KC_A"] * 60 # 60 identical values
layout = SvalboardLayout.from_sequence(keys)
# Or with distinct values
keys = [f"KEY_{i}" for i in range(60)]
layout = SvalboardLayout.from_sequence(keys)
layout[0] # "KEY_0" (right index center)
layout[59] # "KEY_59" (left thumb double_down)
Source code in src/skim/data/keyboard.py
from_zipped
classmethod
¶
from_zipped(
*,
bundle: str | type = "KeyValues",
**layouts: SvalboardLayout[Any],
) -> SvalboardLayout[Any]
Create a new layout by zipping values from multiple source layouts.
This class method combines multiple SvalboardLayout instances into a single layout where each key position contains a bundle of values from all source layouts. The bundle can be either a dynamically created frozen dataclass or a user-provided dataclass type.
This is useful when you need to associate multiple pieces of data with each key position, such as pairing keycodes with their display labels, or combining styling information from multiple sources.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bundle
|
str | type
|
Either a string name for a dynamically created bundle dataclass, or an existing dataclass type to use. Defaults to "KeyValues". |
'KeyValues'
|
**layouts
|
SvalboardLayout[Any]
|
Keyword arguments mapping names to source layouts. Each name becomes an attribute on the bundle objects. |
{}
|
Returns:
| Type | Description |
|---|---|
SvalboardLayout[Any]
|
A new SvalboardLayout where each key position contains a frozen |
SvalboardLayout[Any]
|
dataclass instance with attributes from each source layout. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no layouts are provided. |
TypeError
|
If a provided bundle class is missing required attributes. |
Example
Using a dynamic bundle class::
codes = SvalboardLayout.from_sequence(["KC_A"] * 60)
labels = SvalboardLayout.from_sequence(["A"] * 60)
combined = SvalboardLayout.from_zipped(
bundle="KeyData",
code=codes,
label=labels,
)
combined[0].code # "KC_A"
combined[0].label # "A"
Using a custom dataclass::
@dataclass(frozen=True)
class KeyData:
code: str
label: str
combined = SvalboardLayout.from_zipped(bundle=KeyData, code=codes, label=labels)
Source code in src/skim/data/keyboard.py
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 | |
map ¶
map(fn: Callable[[T], U]) -> SvalboardLayout[U]
Create a new layout by applying a function to each key value.
This method transforms each of the 60 key values in the layout using the provided function, returning a new SvalboardLayout with the transformed values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[T], U]
|
A callable that takes a value of type T and returns a value of type U. Applied to each key position in the layout. |
required |
Returns:
| Type | Description |
|---|---|
SvalboardLayout[U]
|
A new SvalboardLayout with transformed values. |
Example
Transforming layout values::
codes = SvalboardLayout.from_sequence(["KC_A"] * 60)
labels = codes.map(keycode_to_label)
# Chain multiple transformations
result = layout.map(fn1).map(fn2)
# Use with lambdas
upper = labels.map(str.upper)
Source code in src/skim/data/keyboard.py
SvalboardKeymap
dataclass
¶
SvalboardKeymap(
layers: dict[int, SvalboardLayout[T]],
tap_dances: tuple[SvalboardTapDance[T], ...] = (),
macros: tuple[SvalboardMacro[T], ...] = (),
)
Bases: Generic[T]
A complete Svalboard keymap containing multiple layers.
A keymap represents the full key configuration for a Svalboard keyboard, organized into multiple layers. Each layer is a complete SvalboardLayout containing all 60 keys. Users typically switch between layers to access different key bindings (e.g., base layer, symbols layer, navigation layer).
The class is generic over type T, which represents the type of value stored at each key position, allowing it to be used for keycodes, labels, colors, or any other per-key data.
Attributes:
| Name | Type | Description |
|---|---|---|
layers |
dict[int, SvalboardLayout[T]]
|
A dict mapping layer indices to SvalboardLayout objects. Keys are QMK layer indices (which may be non-sequential, e.g. 0, 1, 2, 15). Layer 0 is typically the base/default layer. |
tap_dances |
tuple[SvalboardTapDance[T], ...]
|
Tuple of SvalboardTapDance definitions referenced by keys on the layers. Defaults to an empty tuple. Source formats that don't carry tap-dance definitions (e.g. QMK c2json) yield an empty tuple. |
macros |
tuple[SvalboardMacro[T], ...]
|
Tuple of SvalboardMacro definitions referenced by keys on the layers. Defaults to an empty tuple. Source formats that don't carry macro definitions yield an empty tuple. |
Example
Creating a keymap with multiple layers::
from skim.data.keyboard import SvalboardKeymap, SvalboardLayout
# Create layouts for each layer
base_layer = SvalboardLayout.from_sequence(["KC_A"] * 60)
symbol_layer = SvalboardLayout.from_sequence(["KC_1"] * 60)
nav_layer = SvalboardLayout.from_sequence(["KC_LEFT"] * 60)
keymap = SvalboardKeymap(layers={0: base_layer, 1: symbol_layer, 2: nav_layer})
Accessing layers::
keymap.layers[0] # Base layer
keymap.layers[1][0] # First key of symbol layer
len(keymap.layers) # Number of layers
zip_clusters ¶
zip_clusters(
cluster_type: type[ClusterT],
bundle: str | type = "KeyValues",
/,
**clusters: _ClusterBase[Any],
) -> ClusterT
Zip multiple clusters into a single cluster of bundled values.
This function combines multiple clusters of the same category (all FingerCluster or all ThumbCluster) into a new cluster where each position contains a bundle object holding values from all source clusters.
This is useful when you need to associate multiple pieces of data with each key position, such as pairing keycodes with their display labels, or combining styling information from multiple sources.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cluster_type
|
type[ClusterT]
|
The type of cluster to create (FingerCluster or ThumbCluster). This also determines the expected field structure for all source clusters. |
required |
bundle
|
str | type
|
Either a string name for a dynamically created bundle dataclass, or an existing dataclass type to use. Defaults to "KeyValues". |
'KeyValues'
|
**clusters
|
_ClusterBase[Any]
|
Keyword arguments mapping names to source clusters. Each name becomes an attribute on the bundle objects. All clusters must have the same field structure as cluster_type. |
{}
|
Returns:
| Type | Description |
|---|---|
ClusterT
|
A new cluster of the specified type where each field contains a |
ClusterT
|
frozen dataclass instance with attributes from each source cluster. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no clusters are provided. |
TypeError
|
If any source cluster has fields that don't match the target cluster type, or if a provided bundle class is missing required attributes. |
Example
Using a dynamic bundle class::
from skim.data.keyboard import FingerCluster, zip_clusters
keycodes = FingerCluster("KC_NO", center_key="KC_A", south_key="KC_SPC")
labels = FingerCluster("", center_key="A", south_key="Space")
colors = FingerCluster("#888", center_key="#F00", south_key="#0F0")
combined = zip_clusters(
FingerCluster, "KeyData", code=keycodes, label=labels, color=colors
)
# Access bundled values
combined.center_key.code # "KC_A"
combined.center_key.label # "A"
combined.center_key.color # "#F00"
Using a custom dataclass::
@dataclass(frozen=True)
class KeyData:
code: str
label: str
color: str
combined = zip_clusters(
FingerCluster, KeyData, code=keycodes, label=labels, color=colors
)
Source code in src/skim/data/keyboard.py
1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 | |
zip_layouts ¶
zip_layouts(
bundle: str = "KeyValues",
/,
**layouts: SvalboardLayout[Any],
) -> SvalboardLayout[Any]
zip_layouts(
bundle: type, /, **layouts: SvalboardLayout[Any]
) -> SvalboardLayout[Any]
zip_layouts(
bundle: str | type = "KeyValues",
/,
**layouts: SvalboardLayout[Any],
) -> SvalboardLayout[Any]
Zip multiple layouts into a single layout of bundled values.
This function combines multiple SvalboardLayout instances into a new layout where each key position contains a bundle object holding values from all source layouts.
This is useful when you need to associate multiple pieces of data with each key position, such as pairing keycodes with their display labels, or combining styling information from multiple sources.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bundle
|
str | type
|
Either a string name for a dynamically created bundle dataclass, or an existing dataclass type to use. Defaults to "KeyValues". |
'KeyValues'
|
**layouts
|
SvalboardLayout[Any]
|
Keyword arguments mapping names to source layouts. Each name becomes an attribute on the bundle objects. |
{}
|
Returns:
| Type | Description |
|---|---|
SvalboardLayout[Any]
|
A new SvalboardLayout where each key position contains a frozen |
SvalboardLayout[Any]
|
dataclass instance with attributes from each source layout. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no layouts are provided. |
TypeError
|
If a provided bundle class is missing required attributes. |
Example
Using a dynamic bundle class::
from skim.data.keyboard import SvalboardLayout, zip_layouts
codes = SvalboardLayout.from_sequence(["KC_A"] * 60)
labels = SvalboardLayout.from_sequence(["A"] * 60)
colors = SvalboardLayout.from_sequence(["#F00"] * 60)
combined = zip_layouts("KeyData", code=codes, label=labels, color=colors)
# Access bundled values
combined[0].code # "KC_A"
combined[0].label # "A"
combined[0].color # "#F00"
Using a custom dataclass::
@dataclass(frozen=True)
class KeyData:
code: str
label: str
color: str
combined = zip_layouts(KeyData, code=codes, label=labels, color=colors)
Source code in src/skim/data/keyboard.py
CLI Data Transfer Objects¶
cli ¶
Data transfer objects for CLI argument handling.
This module defines frozen dataclasses used to pass parsed CLI arguments between the command-line interface and the application layer. These DTOs provide type-safe, immutable containers for input/output file specifications and layer selection options.
Example
>>> from skim.data import InputFiles, OutputFiles, KeymapGeneratorTargets
>>> from pathlib import Path
>>> inputs = InputFiles(config=Path("config.yaml"), keymap=Path("keymap.kbi"))
>>> outputs = OutputFiles(output_dir=Path("./images"), output_format="png")
>>> targets = KeymapGeneratorTargets.from_args(("1", "3-5", "overview"))
RenderEngine ¶
OutputFiles
dataclass
¶
OutputFiles(
output_dir: Path = Path(),
output_format: str = "svg",
force_overwrite: bool = False,
use_system_fonts: bool = False,
render_engine: RenderEngine | None = None,
)
Configuration for output file generation.
Specifies where and how to write generated keymap images. This is a frozen dataclass, meaning instances are immutable after creation.
Attributes:
| Name | Type | Description |
|---|---|---|
output_dir |
Path
|
Directory path where generated images will be written. The directory will be created if it doesn't exist. Defaults to the current working directory. |
output_format |
str
|
Image format for output files. Supported values are "svg", "png", "jpeg", "webp", and "avif". Defaults to "svg". |
force_overwrite |
bool
|
Whether to overwrite existing files without prompting for confirmation. Defaults to False. |
use_system_fonts |
bool
|
Whether to use system fonts instead of embedding fonts in SVG. Defaults to False. |
render_engine |
RenderEngine | None
|
Which render engine to use for non-vector formats. Options are CHROMIUM (Playwright) or CAIRO. If None, uses the first available engine. Defaults to None. |
Example
InputFiles
dataclass
¶
InputFiles(
config: Path | None = None,
keymap: Path | None = None,
force_stdin_keymap: bool = False,
)
Configuration for input file sources.
Specifies the source files for keymap data and optional configuration. This is a frozen dataclass, meaning instances are immutable after creation.
Attributes:
| Name | Type | Description |
|---|---|---|
config |
Path | None
|
Optional path to a YAML configuration file. When provided, settings from this file override the default configuration. Defaults to None (use default configuration). |
keymap |
Path | None
|
Optional path to the keymap file (.kbi, .vil, or .json). When None, keymap data is read from stdin. Defaults to None. |
force_stdin_keymap |
bool
|
Whether to read keymap data from stdin instead of a file. When True, the keymap is ignored and stdin is used. Defaults to False. |
Example
KeymapGeneratorTargets
dataclass
¶
KeymapGeneratorTargets(
all_layers: bool = False,
overview: bool = False,
macros: bool = False,
tap_dances: bool = False,
special_keys: bool = False,
symbols: bool = False,
selected_layers: list[int] = list(),
)
Specification of which layers and views to generate.
Defines the target outputs for keymap generation, including which individual layers to render and whether to generate an overview image. This is a frozen dataclass, meaning instances are immutable after creation.
Attributes:
| Name | Type | Description |
|---|---|---|
all_layers |
bool
|
Whether to generate images for all layers in the keymap. When True, selected_layers is ignored. Defaults to False. |
overview |
bool
|
Whether to generate an overview image showing all layers in a grid layout. Defaults to False. |
selected_layers |
list[int]
|
List of specific layer indices to generate. Only used when all_layers is False. Layer indices are 1-based in CLI input but stored as 0-based internally. Defaults to an empty list. |
Example
selected_layers
class-attribute
instance-attribute
¶
from_args
classmethod
¶
from_args(
layer: tuple[str, ...],
logger: Callable[[str], None] = print,
) -> KeymapGeneratorTargets
Parse CLI layer arguments into a KeymapGeneratorTargets instance.
Interprets various layer selection formats from command-line arguments and constructs the appropriate targets configuration. Supports ranges, comma-separated values, and special keywords.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer
|
tuple[str, ...]
|
Tuple of layer specification strings from the CLI. Each string can be:
|
required |
logger
|
Callable[[str], None]
|
Callable for warning messages about invalid input. Defaults to print. Called with a string message when invalid layer specifications are encountered. |
print
|
Returns:
| Type | Description |
|---|---|
KeymapGeneratorTargets
|
A KeymapGeneratorTargets instance configured according to the |
KeymapGeneratorTargets
|
parsed arguments. |
Example
>>> # No arguments = all layers + overview
>>> targets = KeymapGeneratorTargets.from_args(())
>>> targets.all_layers
True
>>> targets.overview
True
>>> # Specific layers
>>> targets = KeymapGeneratorTargets.from_args(("1", "3-5"))
>>> targets.selected_layers
[1, 3, 4, 5]
>>> # Keywords
>>> targets = KeymapGeneratorTargets.from_args(("overview",))
>>> targets.overview
True
>>> targets.all_layers
False
Source code in src/skim/data/cli.py
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | |
Trie¶
trie ¶
Trie (prefix tree) data structure for efficient prefix matching.
This module provides a Trie implementation optimized for checking whether a string starts with any of a predefined set of prefixes. It is used internally by the keycode label adapter to efficiently match QMK macro function names like "LT", "MO", "TG", etc.
Example
Trie ¶
A trie (prefix tree) for efficient prefix matching.
This data structure allows O(m) lookup time to check if a string starts with any word from a predefined set, where m is the length of the search string (or more precisely, the length of the matching prefix).
The trie is built once during initialization and then supports fast prefix queries. It is particularly useful for parsing QMK macro functions where we need to identify function names at the start of strings like "LT(1, KC_A)" or "MO(2)".
Attributes:
| Name | Type | Description |
|---|---|---|
root |
dict
|
The root node of the trie, represented as a nested dictionary. Each key is a character leading to a child node (another dict), except for None which marks the end of a word and stores the complete word string. |
Example
Initialize the trie with a collection of words.
Builds the trie structure by inserting all provided words. Each word creates a path through the trie, with the complete word stored at the terminal node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
words
|
Iterable[str]
|
An iterable of strings to index in the trie. Can be a list, tuple, set, generator, or any other iterable of strings. |
required |
Example
Source code in src/skim/data/trie.py
get_matching_prefix ¶
Find the longest indexed word that is a prefix of the search string.
Traverses the trie following characters from the search string. If a complete indexed word is found (marked by a None key), that word is returned. The search continues to find the longest possible match.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
search_string
|
str
|
The string to check for a matching prefix. |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
The matching prefix word if found, or None if the search string |
str | None
|
doesn't start with any indexed word. |