Compare commits

..

5 Commits

Author SHA1 Message Date
Peter F. Patel-Schneider 3d1e502096 ui: don't use old settings when reusing sboxes 2026-06-09 19:35:00 -04:00
Peter F. Patel-Schneider 74181125fe debug: add warning statements to determine reason for feature request failure 2026-06-09 19:35:00 -04:00
Ken Sanislo 189a50e926 perkey layouts: add POUND and ISO_BACKSLASH cells to MAIN_ISO
ISO keyboards have two physical keys that ANSI does not — POUND (#) at
row 3 col 12 between the right-of-quote position and Enter, and
ISO_BACKSLASH (<) at row 4 col 1 between LShift and Z. The firmware
reports them as zones 47 and 97 on G915 ISO models, but MAIN_ISO only
*subtracted* the ANSI backslash at row 2 col 13 without ever adding
those two cells back. They fell through to the unmapped pool and got
dropped by the EXTRAS_ALLOWLIST phantom-zone filter, so PerKey lighting
silently left them undrawable (issue #3239 — German G915).

Add both cells to MAIN_ISO with the UK QWERTY labels (# and \\) as the
default, and override them in the regional layouts: # / < on QWERTZ DE,
* / < on AZERTY FR. UK QWERTY inherits the defaults.

ANSI is unaffected — MAIN_ANSI still omits 47/97 so they keep getting
filtered as phantoms on ANSI keyboards like the G515.
2026-06-01 19:09:11 -04:00
Juan Jose del Rio Holgado f68230b83d cli/config: wrap argv in list for Gio.Application.run (PyGObject 3.56)
The remote-config path passes a yaml.dump(...) string to
Gio.Application.run(), whose argv parameter is Optional[list[str]].
Pre-3.56 PyGObject tolerated a bare str; the marshaller refactor in
the 3.55 dev series (MR !487) tightened this, and 3.56 now raises
TypeError: Unable to marshal str as an array.

Wrap the YAML string in a 1-element list. The receiver in
solaar.ui._command_line already does yaml.safe_load("".join(args)) on
the argv, so a 1-element argv reconstructs the original YAML cleanly
under both old and new PyGObject.
2026-05-28 08:19:06 -04:00
Matthaiks 7647cea478
po: Update Polish translation (#3236) 2026-05-24 17:48:53 -04:00
10 changed files with 861 additions and 410 deletions

View File

@ -706,6 +706,14 @@ class Device:
long_message=long, long_message=long,
protocol=self.protocol, protocol=self.protocol,
) )
if logger.isEnabledFor(logging.WARN):
logger.warning(
"%s: request failure for device %s %s %s",
self,
self.handle,
self.receiver,
self.receiver._devices if self.receiver else None,
)
def feature_request(self, feature, function=0x00, *params, no_reply=False): def feature_request(self, feature, function=0x00, *params, no_reply=False):
if self.protocol >= 2.0: if self.protocol >= 2.0:
@ -730,6 +738,8 @@ class Device:
if sub_idx is not None: if sub_idx is not None:
return self.centurion_bridge_request(sub_idx, function, *params, no_reply=no_reply) return self.centurion_bridge_request(sub_idx, function, *params, no_reply=no_reply)
return hidpp20.feature_request(self, feature, function, *params, no_reply=no_reply) return hidpp20.feature_request(self, feature, function, *params, no_reply=no_reply)
if logger.isEnabledFor(logging.WARN):
logger.warning("%s: feature request failure for device with protocol %s", self, self.protocol)
# Max sub-message bytes in the first bridge fragment (for 0x51): # Max sub-message bytes in the first bridge fragment (for 0x51):
# 64 - 1 (report ID) - 1 (cpl_len) - 1 (flags) - 2 (bridge prefix) - 2 (bridge hdr) = 57; # 64 - 1 (report ID) - 1 (cpl_len) - 1 (flags) - 2 (bridge prefix) - 2 (bridge hdr) = 57;

View File

@ -1756,6 +1756,10 @@ def feature_request(device, feature, function=0x00, *params, no_reply=False):
if feature in device.features: if feature in device.features:
feature_index = device.features[feature] feature_index = device.features[feature]
return device.request((feature_index << 8) + (function & 0xFF), *params, no_reply=no_reply) return device.request((feature_index << 8) + (function & 0xFF), *params, no_reply=no_reply)
if logger.isEnabledFor(logging.WARN):
logger.warning(
"%s: feature request failure for device online %s and features %s", device, device.online, device.features
)
class Hidpp20: class Hidpp20:

View File

@ -96,6 +96,8 @@ class _ThreadedHandle:
def __str__(self): def __str__(self):
if self._local: if self._local:
return str(int(self)) return str(int(self))
else:
return "None"
def __repr__(self): def __repr__(self):
return f"<_ThreadedHandle({self.path})>" return f"<_ThreadedHandle({self.path})>"

View File

@ -171,6 +171,8 @@ class Receiver:
self.pairing = Pairing() self.pairing = Pairing()
self.initialize(product_info) self.initialize(product_info)
hidpp10.set_configuration_pending_flags(self, 0xFF) hidpp10.set_configuration_pending_flags(self, 0xFF)
if logger.isEnabledFor(logging.INFO):
logger.info("%s: init receiver: handle %s, path %s, serial %s", self, self.handle, self.path, self.serial)
def initialize(self, product_info: dict): def initialize(self, product_info: dict):
# read the receiver information subregister, so we can find out max_devices # read the receiver information subregister, so we can find out max_devices
@ -185,6 +187,8 @@ class Receiver:
self.max_devices = product_info.get("max_devices", 1) self.max_devices = product_info.get("max_devices", 1)
def close(self): def close(self):
if logger.isEnabledFor(logging.INFO):
logger.info("%s: closing - handle %s %s", self, type(self.handle), self.handle)
handle, self.handle = self.handle, None handle, self.handle = self.handle, None
for _n, d in self._devices.items(): for _n, d in self._devices.items():
if d: if d:
@ -599,9 +603,10 @@ receiver_class_mapping = {
def create_receiver(low_level: LowLevelInterface, device_info, setting_callback=None) -> Optional[Receiver]: def create_receiver(low_level: LowLevelInterface, device_info, setting_callback=None) -> Optional[Receiver]:
"""Opens a Logitech Receiver found attached to the machine, by Linux device path.""" """Opens a Logitech Receiver found attached to the machine, by Linux device path."""
try: try:
handle = low_level.open_path(device_info.path) handle = low_level.open_path(device_info.path)
if logger.isEnabledFor(logging.INFO):
logger.info("create receiver %s %s", handle, device_info)
if handle: if handle:
usb_id = device_info.product_id usb_id = device_info.product_id
if isinstance(usb_id, str): if isinstance(usb_id, str):

View File

@ -231,7 +231,7 @@ def run(receivers, args, _find_receiver, find_device):
argl = ["config", dev.serial or dev.unitId, setting.name] argl = ["config", dev.serial or dev.unitId, setting.name]
argl.extend([a for a in [args.value_key, args.extra_subkey, args.extra2] if a is not None]) argl.extend([a for a in [args.value_key, args.extra_subkey, args.extra2] if a is not None])
args = yaml.dump(argl) args = yaml.dump(argl)
application.run(args) application.run([args])
else: else:
if dev.persister and setting.persist: if dev.persister and setting.persist:
dev.persister[setting.name] = setting._value dev.persister[setting.name] = setting._value

View File

@ -1153,6 +1153,7 @@ def update(device, is_online=None):
k = (device_id[0], device_id[1], s.name) k = (device_id[0], device_id[1], s.name)
if k in _items: if k in _items:
sbox = _items[k] sbox = _items[k]
sbox.setting = s # use setting from current version of device
else: else:
sbox = _create_sbox(s, device) sbox = _create_sbox(s, device)
if sbox is None: if sbox is None:

View File

@ -157,11 +157,17 @@ MAIN_ANSI: tuple[Cell, ...] = (
Cell(zone_id=108, row=5, col=13, group="main", label="Ctrl"), Cell(zone_id=108, row=5, col=13, group="main", label="Ctrl"),
) )
# --- Main alpha block, ISO. Same as ANSI minus the row 2 col 13 backslash; # --- Main alpha block, ISO. Drops the row 2 col 13 backslash (zone 46 is the
# on ISO that position is the top half of the L-shape Enter, addressed # upper half of the L-shape Enter on ISO, addressed by zone 37) and adds
# by zone 37 (the main Enter cell at row 3 col 13). Zone 46 is silently # the two ISO-only keys: POUND (zone 47) at row 3 col 12 between ' and
# unaddressable on ISO layouts — same limitation as OpenRGB's UI. # Enter, and ISO_BACKSLASH (zone 97) at row 4 col 1 between Shift and Z.
MAIN_ISO: tuple[Cell, ...] = tuple(c for c in MAIN_ANSI if not (c.row == 2 and c.col == 13)) # 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. # --- Curated allowlist for unmapped device zones surfaced in the bottom strip.
# G-keys, logo, media, brightness — the canonical "extras" Logitech firmware # G-keys, logo, media, brightness — the canonical "extras" Logitech firmware

View File

@ -56,6 +56,8 @@ _OVERRIDES: dict[int, str] = {
51: ";", # ,-position → semicolon 51: ";", # ,-position → semicolon
52: ":", # .-position → colon 52: ":", # .-position → colon
53: "!", # /-position → exclamation 53: "!", # /-position → exclamation
47: "*", # POUND key (row 3 col 12) — French * / µ
97: "<", # ISO_BACKSLASH (row 4 col 1), between Shift and W
} }

View File

@ -38,6 +38,8 @@ _OVERRIDES: dict[int, str] = {
49: "Ä", # row 3 col 11 49: "Ä", # row 3 col 11
26: "Y", # row 4 col 2 — Y/Z swap 26: "Y", # row 4 col 2 — Y/Z swap
53: "-", # row 4 col 11 53: "-", # row 4 col 11
47: "#", # POUND key (row 3 col 12), between Ä and Enter
97: "<", # ISO_BACKSLASH (row 4 col 1), between Shift and Y
} }

1225
po/pl.po

File diff suppressed because it is too large Load Diff