237 lines
11 KiB
Python
237 lines
11 KiB
Python
## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/
|
|
##
|
|
## This program is free software; you can redistribute it and/or modify
|
|
## it under the terms of the GNU General Public License as published by
|
|
## the Free Software Foundation; either version 2 of the License, or
|
|
## (at your option) any later version.
|
|
##
|
|
## This program is distributed in the hope that it will be useful,
|
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
## GNU General Public License for more details.
|
|
##
|
|
## You should have received a copy of the GNU General Public License along
|
|
## with this program; if not, write to the Free Software Foundation, Inc.,
|
|
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
"""Shared building blocks for regional keyboard layouts.
|
|
|
|
Each region (ANSI, ISO_QWERTY, ISO_QWERTZ, ISO_AZERTY, JIS) shares the function
|
|
row, nav-cluster, and numpad blocks; only the main alpha block differs (ANSI
|
|
includes the row 2 col 13 backslash, ISO doesn't). Regional label overrides on
|
|
top of either main block produce the final layout.
|
|
|
|
Cell positions and groupings adapted from OpenRGB's KeyboardLayoutManager.
|
|
Zone IDs are firmware values reported by Logitech HID++ feature 0x8081
|
|
(PER_KEY_LIGHTING_V2).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from ..layout import Cell
|
|
from ..layout import Layout
|
|
|
|
# --- Function row: ESC + F1..F12 (shared across all regions).
|
|
FN_ROW: tuple[Cell, ...] = (
|
|
Cell(zone_id=38, row=0, col=0, group="fn_row", label="Esc"),
|
|
Cell(zone_id=55, row=0, col=2, group="fn_row", label="F1"),
|
|
Cell(zone_id=56, row=0, col=3, group="fn_row", label="F2"),
|
|
Cell(zone_id=57, row=0, col=4, group="fn_row", label="F3"),
|
|
Cell(zone_id=58, row=0, col=5, group="fn_row", label="F4"),
|
|
Cell(zone_id=59, row=0, col=6, group="fn_row", label="F5"),
|
|
Cell(zone_id=60, row=0, col=7, group="fn_row", label="F6"),
|
|
Cell(zone_id=61, row=0, col=8, group="fn_row", label="F7"),
|
|
Cell(zone_id=62, row=0, col=9, group="fn_row", label="F8"),
|
|
Cell(zone_id=63, row=0, col=10, group="fn_row", label="F9"),
|
|
Cell(zone_id=64, row=0, col=11, group="fn_row", label="F10"),
|
|
Cell(zone_id=65, row=0, col=12, group="fn_row", label="F11"),
|
|
Cell(zone_id=66, row=0, col=13, group="fn_row", label="F12"),
|
|
)
|
|
|
|
# --- Nav cluster + arrows (shared).
|
|
EXTRAS: tuple[Cell, ...] = (
|
|
Cell(zone_id=67, row=0, col=14, group="extras", label="PrtSc"),
|
|
Cell(zone_id=68, row=0, col=15, group="extras", label="ScrLk"),
|
|
Cell(zone_id=69, row=0, col=16, group="extras", label="Pause"),
|
|
Cell(zone_id=70, row=1, col=14, group="extras", label="Ins"),
|
|
Cell(zone_id=71, row=1, col=15, group="extras", label="Home"),
|
|
Cell(zone_id=72, row=1, col=16, group="extras", label="PgUp"),
|
|
Cell(zone_id=73, row=2, col=14, group="extras", label="Del"),
|
|
Cell(zone_id=74, row=2, col=15, group="extras", label="End"),
|
|
Cell(zone_id=75, row=2, col=16, group="extras", label="PgDn"),
|
|
Cell(zone_id=79, row=4, col=15, group="extras", label="↑"),
|
|
Cell(zone_id=77, row=5, col=14, group="extras", label="←"),
|
|
Cell(zone_id=78, row=5, col=15, group="extras", label="↓"),
|
|
Cell(zone_id=76, row=5, col=16, group="extras", label="→"),
|
|
)
|
|
|
|
# --- Numpad block (only on full-size keyboards).
|
|
NUMPAD: tuple[Cell, ...] = (
|
|
Cell(zone_id=80, row=1, col=17, group="numpad", label="Num"),
|
|
Cell(zone_id=81, row=1, col=18, group="numpad", label="/"),
|
|
Cell(zone_id=82, row=1, col=19, group="numpad", label="*"),
|
|
Cell(zone_id=83, row=1, col=20, group="numpad", label="-"),
|
|
Cell(zone_id=92, row=2, col=17, group="numpad", label="7"),
|
|
Cell(zone_id=93, row=2, col=18, group="numpad", label="8"),
|
|
Cell(zone_id=94, row=2, col=19, group="numpad", label="9"),
|
|
Cell(zone_id=84, row=2, col=20, height=2.0, group="numpad", label="+"),
|
|
Cell(zone_id=89, row=3, col=17, group="numpad", label="4"),
|
|
Cell(zone_id=90, row=3, col=18, group="numpad", label="5"),
|
|
Cell(zone_id=91, row=3, col=19, group="numpad", label="6"),
|
|
Cell(zone_id=86, row=4, col=17, group="numpad", label="1"),
|
|
Cell(zone_id=87, row=4, col=18, group="numpad", label="2"),
|
|
Cell(zone_id=88, row=4, col=19, group="numpad", label="3"),
|
|
Cell(zone_id=85, row=4, col=20, height=2.0, group="numpad", label="Enter"),
|
|
Cell(zone_id=95, row=5, col=17, width=2.0, group="numpad", label="0"),
|
|
Cell(zone_id=96, row=5, col=19, group="numpad", label="."),
|
|
)
|
|
|
|
# --- Main alpha block, ANSI (104-key). Includes row 2 col 13 backslash and
|
|
# omits POUND (row 3 col 12) + ISO_BACKSLASH (row 4 col 1).
|
|
MAIN_ANSI: tuple[Cell, ...] = (
|
|
# Row 1: backtick + numbers + minus/equals + backspace
|
|
Cell(zone_id=50, row=1, col=0, group="main", label="`"),
|
|
Cell(zone_id=27, row=1, col=1, group="main", label="1"),
|
|
Cell(zone_id=28, row=1, col=2, group="main", label="2"),
|
|
Cell(zone_id=29, row=1, col=3, group="main", label="3"),
|
|
Cell(zone_id=30, row=1, col=4, group="main", label="4"),
|
|
Cell(zone_id=31, row=1, col=5, group="main", label="5"),
|
|
Cell(zone_id=32, row=1, col=6, group="main", label="6"),
|
|
Cell(zone_id=33, row=1, col=7, group="main", label="7"),
|
|
Cell(zone_id=34, row=1, col=8, group="main", label="8"),
|
|
Cell(zone_id=35, row=1, col=9, group="main", label="9"),
|
|
Cell(zone_id=36, row=1, col=10, group="main", label="0"),
|
|
Cell(zone_id=42, row=1, col=11, group="main", label="-"),
|
|
Cell(zone_id=43, row=1, col=12, group="main", label="="),
|
|
Cell(zone_id=39, row=1, col=13, group="main", label="Bksp"),
|
|
# Row 2: tab + qwerty + brackets + backslash
|
|
Cell(zone_id=40, row=2, col=0, group="main", label="Tab"),
|
|
Cell(zone_id=17, row=2, col=1, group="main", label="Q"),
|
|
Cell(zone_id=23, row=2, col=2, group="main", label="W"),
|
|
Cell(zone_id=5, row=2, col=3, group="main", label="E"),
|
|
Cell(zone_id=18, row=2, col=4, group="main", label="R"),
|
|
Cell(zone_id=20, row=2, col=5, group="main", label="T"),
|
|
Cell(zone_id=25, row=2, col=6, group="main", label="Y"),
|
|
Cell(zone_id=21, row=2, col=7, group="main", label="U"),
|
|
Cell(zone_id=9, row=2, col=8, group="main", label="I"),
|
|
Cell(zone_id=15, row=2, col=9, group="main", label="O"),
|
|
Cell(zone_id=16, row=2, col=10, group="main", label="P"),
|
|
Cell(zone_id=44, row=2, col=11, group="main", label="["),
|
|
Cell(zone_id=45, row=2, col=12, group="main", label="]"),
|
|
Cell(zone_id=46, row=2, col=13, group="main", label="\\"),
|
|
# Row 3: caps + asdf-row + semi/quote + enter
|
|
Cell(zone_id=54, row=3, col=0, group="main", label="Caps"),
|
|
Cell(zone_id=1, row=3, col=1, group="main", label="A"),
|
|
Cell(zone_id=19, row=3, col=2, group="main", label="S"),
|
|
Cell(zone_id=4, row=3, col=3, group="main", label="D"),
|
|
Cell(zone_id=6, row=3, col=4, group="main", label="F"),
|
|
Cell(zone_id=7, row=3, col=5, group="main", label="G"),
|
|
Cell(zone_id=8, row=3, col=6, group="main", label="H"),
|
|
Cell(zone_id=10, row=3, col=7, group="main", label="J"),
|
|
Cell(zone_id=11, row=3, col=8, group="main", label="K"),
|
|
Cell(zone_id=12, row=3, col=9, group="main", label="L"),
|
|
Cell(zone_id=48, row=3, col=10, group="main", label=";"),
|
|
Cell(zone_id=49, row=3, col=11, group="main", label="'"),
|
|
Cell(zone_id=37, row=3, col=13, group="main", label="Enter"),
|
|
# Row 4: shift + zxcv-row + comma/period/slash + rshift
|
|
Cell(zone_id=105, row=4, col=0, group="main", label="Shift"),
|
|
Cell(zone_id=26, row=4, col=2, group="main", label="Z"),
|
|
Cell(zone_id=24, row=4, col=3, group="main", label="X"),
|
|
Cell(zone_id=3, row=4, col=4, group="main", label="C"),
|
|
Cell(zone_id=22, row=4, col=5, group="main", label="V"),
|
|
Cell(zone_id=2, row=4, col=6, group="main", label="B"),
|
|
Cell(zone_id=14, row=4, col=7, group="main", label="N"),
|
|
Cell(zone_id=13, row=4, col=8, group="main", label="M"),
|
|
Cell(zone_id=51, row=4, col=9, group="main", label=","),
|
|
Cell(zone_id=52, row=4, col=10, group="main", label="."),
|
|
Cell(zone_id=53, row=4, col=11, group="main", label="/"),
|
|
Cell(zone_id=109, row=4, col=13, group="main", label="Shift"),
|
|
# Row 5: bottom row. Space spans cols 3..9 visually.
|
|
Cell(zone_id=104, row=5, col=0, group="main", label="Ctrl"),
|
|
Cell(zone_id=107, row=5, col=1, group="main", label="Win"),
|
|
Cell(zone_id=106, row=5, col=2, group="main", label="Alt"),
|
|
Cell(zone_id=41, row=5, col=3, width=7.0, group="main", label="Space"),
|
|
Cell(zone_id=110, row=5, col=10, group="main", label="AltGr"),
|
|
Cell(zone_id=111, row=5, col=11, group="main", label="Win"),
|
|
Cell(zone_id=98, row=5, col=12, group="main", label="Menu"),
|
|
Cell(zone_id=108, row=5, col=13, group="main", label="Ctrl"),
|
|
)
|
|
|
|
# --- Main alpha block, ISO. Drops the row 2 col 13 backslash (zone 46 is the
|
|
# upper half of the L-shape Enter on ISO, addressed by zone 37) and adds
|
|
# the two ISO-only keys: POUND (zone 47) at row 3 col 12 between ' and
|
|
# Enter, and ISO_BACKSLASH (zone 97) at row 4 col 1 between Shift and Z.
|
|
# Regional layouts override the labels to match local keycaps (# / < on
|
|
# QWERTZ, # / \ on UK QWERTY, * / < on AZERTY).
|
|
_ISO_EXTRA_KEYS: tuple[Cell, ...] = (
|
|
Cell(zone_id=47, row=3, col=12, group="main", label="#"),
|
|
Cell(zone_id=97, row=4, col=1, group="main", label="\\"),
|
|
)
|
|
MAIN_ISO: tuple[Cell, ...] = tuple(c for c in MAIN_ANSI if not (c.row == 2 and c.col == 13)) + _ISO_EXTRA_KEYS
|
|
|
|
# --- Curated allowlist for unmapped device zones surfaced in the bottom strip.
|
|
# G-keys, logo, media, brightness — the canonical "extras" Logitech firmware
|
|
# actually addresses. Phantom zones (e.g. G515's 47, 97, 99-103, 254) drop.
|
|
EXTRAS_ALLOWLIST: frozenset[int] = frozenset(
|
|
{
|
|
153, # Brightness
|
|
155, # Play/Pause
|
|
156, # Mute
|
|
157, # Next
|
|
158, # Previous
|
|
180, # G1
|
|
181, # G2
|
|
182, # G3
|
|
183, # G4
|
|
184, # G5
|
|
210, # Logo
|
|
}
|
|
)
|
|
|
|
|
|
def _relabel(cells: tuple[Cell, ...], overrides: dict[int, str]) -> tuple[Cell, ...]:
|
|
"""Return a new tuple where any cell whose zone_id is in `overrides` has
|
|
its label replaced. Unaffected cells pass through unchanged.
|
|
"""
|
|
if not overrides:
|
|
return cells
|
|
return tuple(
|
|
Cell(
|
|
zone_id=c.zone_id,
|
|
row=c.row,
|
|
col=c.col,
|
|
width=c.width,
|
|
height=c.height,
|
|
group=c.group,
|
|
label=overrides[c.zone_id] if c.zone_id in overrides else c.label,
|
|
x=c.x,
|
|
y=c.y,
|
|
)
|
|
for c in cells
|
|
)
|
|
|
|
|
|
def build_layout(
|
|
main_cells: tuple[Cell, ...],
|
|
*,
|
|
include_numpad: bool,
|
|
label_overrides: dict[int, str] | None = None,
|
|
description: str = "",
|
|
) -> Layout:
|
|
"""Assemble a regional keyboard layout from a chosen main block + the
|
|
shared fn-row / extras / (optionally) numpad blocks. Apply per-zone
|
|
label overrides to every cell whose zone matches.
|
|
"""
|
|
cells = FN_ROW + main_cells + EXTRAS
|
|
if include_numpad:
|
|
cells = cells + NUMPAD
|
|
cells = _relabel(cells, label_overrides or {})
|
|
cols = 21 if include_numpad else 17
|
|
return Layout(
|
|
cells=cells,
|
|
rows=6,
|
|
cols=cols,
|
|
extra_zones=EXTRAS_ALLOWLIST,
|
|
description=description,
|
|
)
|