perkey: add PRO X RAPID media top row layout
The PRO X RAPID (reported as "PRO X RAPID", USB 046D:C35B) wires five dedicated media keys above the F-row with board-specific 0x8081 zone ids (150 Bright, 155 Prev, 152 Play, 154 Next, 153 Mute) that the canonical extras map (153-158) doesn't cover: 150/152/154 were dropped as phantoms and never surfaced in the painter. Ids and positions are hardware-probed (ported from the OpenRGB key-map for this model). Add keyboard_pro_x_rapid.with_media_top_row(), which pushes any regional TKL base down one row and prepends the media row as explicit bound cells. No EXTRAS_ALLOWLIST change is needed - the media keys paint directly, while the base's extra_zones keeps filtering the phantom zones this board shares with the G515 (47, 97, 99-103, 254). Registered per region ahead of the generic family matchers so country-code routing (ANSI/ISO/AZERTY/ JIS) still selects the main block; only the media top row differs. The logo has no addressable LED on this model, so no logo zone is mapped. Labels are words rather than media glyphs: the media/emoji codepoints aren't in DejaVu (the baseline Linux font) and tofu on minimal installs, unlike the nav-arrow glyphs which the base font covers.
This commit is contained in:
parent
5e4ae7261e
commit
ef209e6f0d
|
|
@ -32,6 +32,7 @@ from . import keyboard_iso_azerty
|
|||
from . import keyboard_iso_qwerty
|
||||
from . import keyboard_iso_qwertz
|
||||
from . import keyboard_jis
|
||||
from . import keyboard_pro_x_rapid
|
||||
from . import mouse_g502x
|
||||
|
||||
# (feature_id, matcher, layout). Matcher receives a `hint` dict the editor
|
||||
|
|
@ -135,6 +136,29 @@ def _keyboard_matcher(family: str, full_size: bool) -> Callable[[dict], bool]:
|
|||
return match
|
||||
|
||||
|
||||
def _pro_x_rapid_matcher(family: str) -> Callable[[dict], bool]:
|
||||
"""Match the PRO X RAPID (reported as "PRO X RAPID", TKL) for one region.
|
||||
Same country-code family routing as the generic keyboards; only the media
|
||||
top row differs."""
|
||||
named = _name_contains("PRO X RAPID")
|
||||
|
||||
def match(hint: dict) -> bool:
|
||||
if hint.get("kind") != "keyboard":
|
||||
return False
|
||||
if not named(hint):
|
||||
return False
|
||||
if _has_numpad(hint): # PRO X RAPID is TKL
|
||||
return False
|
||||
return _keyboard_family(hint) == family
|
||||
|
||||
return match
|
||||
|
||||
|
||||
# PRO X RAPID — regional generated base + a customized media top row.
|
||||
# Registered ahead of the generic family matchers so it wins for this model.
|
||||
for _family, (_full, _tkl) in _FAMILY_LAYOUTS.items():
|
||||
register_layout(0x8081, _pro_x_rapid_matcher(_family), keyboard_pro_x_rapid.with_media_top_row(_tkl))
|
||||
|
||||
# PER_KEY_LIGHTING_V2 = 0x8081
|
||||
for _family, (_full, _tkl) in _FAMILY_LAYOUTS.items():
|
||||
register_layout(0x8081, _keyboard_matcher(_family, full_size=True), _full)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
## 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.
|
||||
|
||||
"""PRO X RAPID (PRO X TKL RAPID) per-key layout.
|
||||
|
||||
Same generated regional main block as any TKL keyboard, plus a customized
|
||||
media top row. This board wires five dedicated media keys above the F-row with
|
||||
board-specific zone ids that the canonical extras map (153-158) does not cover;
|
||||
ids and positions are hardware-probed (ported from the OpenRGB key-map for this
|
||||
model). The logo has no addressable LED here.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ..layout import Cell
|
||||
from ..layout import Layout
|
||||
|
||||
# Dedicated media keys, hardware-probed. Columns align above the F-row
|
||||
# (F4=col5, F9-F12=cols10-13). Zone ids are board-specific — NOT the canonical
|
||||
# 153-158 extras — and the logo (canonical 210) is unlit on this model.
|
||||
# group="media" keeps these out of strip_groups so they render in the matrix.
|
||||
MEDIA_TOP_ROW: tuple[Cell, ...] = (
|
||||
Cell(zone_id=150, row=0, col=5, group="media", label="Bright"),
|
||||
Cell(zone_id=155, row=0, col=10, group="media", label="Prev"),
|
||||
Cell(zone_id=152, row=0, col=11, group="media", label="Play"),
|
||||
Cell(zone_id=154, row=0, col=12, group="media", label="Next"),
|
||||
Cell(zone_id=153, row=0, col=13, group="media", label="Mute"),
|
||||
)
|
||||
|
||||
|
||||
def with_media_top_row(base: Layout) -> Layout:
|
||||
"""Return `base` with every cell pushed down one row and the PRO X RAPID
|
||||
media top row placed at row 0.
|
||||
|
||||
The media keys are explicit bound cells, so they paint directly with no
|
||||
EXTRAS_ALLOWLIST entry, while `base`'s `extra_zones` still filters the
|
||||
phantom zones this board shares with the G515 (47, 97, 99-103, 254).
|
||||
"""
|
||||
shifted = tuple(
|
||||
Cell(
|
||||
zone_id=c.zone_id,
|
||||
row=c.row + 1,
|
||||
col=c.col,
|
||||
width=c.width,
|
||||
height=c.height,
|
||||
group=c.group,
|
||||
label=c.label,
|
||||
x=c.x,
|
||||
y=c.y,
|
||||
)
|
||||
for c in base.cells
|
||||
)
|
||||
return Layout(
|
||||
cells=MEDIA_TOP_ROW + shifted,
|
||||
rows=base.rows + 1,
|
||||
cols=base.cols,
|
||||
strip_groups=base.strip_groups,
|
||||
supported_tools=base.supported_tools,
|
||||
extra_zones=base.extra_zones,
|
||||
description="PRO X RAPID",
|
||||
)
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
from solaar.ui.perkey.binding import bind
|
||||
from solaar.ui.perkey.layouts import layout_for
|
||||
|
||||
# PRO X RAPID hardware-probed media zones (board-specific, not canonical 153-158).
|
||||
_RAPID_MEDIA = {150, 152, 153, 154, 155}
|
||||
# Phantom zones this board shares with the G515 (not physical keys).
|
||||
_SHARED_PHANTOMS = {47, 97}
|
||||
|
||||
|
||||
def _rapid_hint(name="PRO X RAPID", country=1):
|
||||
return {
|
||||
"kind": "keyboard",
|
||||
"wpid": "C35B",
|
||||
"codename": name,
|
||||
"name": name,
|
||||
"keyboard_layout": country, # 1 = ANSI
|
||||
"zones": [38, 55, 66, *sorted(_RAPID_MEDIA), *sorted(_SHARED_PHANTOMS)],
|
||||
"zone_count": 9,
|
||||
}
|
||||
|
||||
|
||||
def test_pro_x_rapid_gets_media_top_row():
|
||||
layout = layout_for(0x8081, _rapid_hint())
|
||||
|
||||
assert layout.description == "PRO X RAPID"
|
||||
media = {c.zone_id: (c.row, c.col, c.label) for c in layout.cells if c.group == "media"}
|
||||
assert media == {
|
||||
150: (0, 5, "Bright"),
|
||||
155: (0, 10, "Prev"),
|
||||
152: (0, 11, "Play"),
|
||||
154: (0, 12, "Next"),
|
||||
153: (0, 13, "Mute"),
|
||||
}
|
||||
# The generated base is pushed down one row to make room for the media row.
|
||||
esc = next(c for c in layout.cells if c.zone_id == 38)
|
||||
assert esc.row == 1
|
||||
|
||||
|
||||
def test_pro_x_rapid_media_keys_are_paintable_and_phantoms_dropped():
|
||||
layout = layout_for(0x8081, _rapid_hint())
|
||||
bound = bind(layout, _rapid_hint()["zones"], lambda z: f"KEY {z}")
|
||||
|
||||
media_bound = {bc.cell.zone_id: bc.bound for bc in bound.matrix if bc.cell.group == "media"}
|
||||
assert media_bound == {z: True for z in _RAPID_MEDIA}
|
||||
# Shared phantoms must not surface as paintable strip swatches.
|
||||
strip_zones = {bc.cell.zone_id for bc in bound.strip}
|
||||
assert _SHARED_PHANTOMS.isdisjoint(strip_zones)
|
||||
|
||||
|
||||
def test_pro_x_rapid_still_regional():
|
||||
# ISO country code still routes to the ISO main block, with the media row on top.
|
||||
iso = layout_for(0x8081, _rapid_hint(country=2)) # 2 = ISO QWERTY
|
||||
assert iso.description == "PRO X RAPID"
|
||||
assert any(c.group == "media" for c in iso.cells)
|
||||
|
||||
|
||||
def test_non_rapid_tkl_unaffected():
|
||||
# A G515 TKL keeps the generic layout: no media top row.
|
||||
layout = layout_for(0x8081, _rapid_hint(name="G515 LS TKL"))
|
||||
|
||||
assert layout.description != "PRO X RAPID"
|
||||
assert not any(c.group == "media" for c in layout.cells)
|
||||
Loading…
Reference in New Issue