Remove NamedInts: Convert NotificationFlag to flag

Related #2273
This commit is contained in:
MattHag 2024-11-16 14:46:45 +01:00 committed by Peter F. Patel-Schneider
parent 571cdb5f2d
commit 72c9dfc50c
7 changed files with 81 additions and 47 deletions

View File

@ -38,6 +38,7 @@ from . import settings
from . import settings_templates from . import settings_templates
from .common import Alert from .common import Alert
from .common import Battery from .common import Battery
from .hidpp10_constants import NotificationFlag
from .hidpp20_constants import SupportedFeature from .hidpp20_constants import SupportedFeature
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
@ -467,10 +468,8 @@ class Device:
if enable: if enable:
set_flag_bits = ( set_flag_bits = (
hidpp10_constants.NOTIFICATION_FLAG.battery_status NotificationFlag.BATTERY_STATUS | NotificationFlag.UI | NotificationFlag.CONFIGURATION_COMPLETE
| hidpp10_constants.NOTIFICATION_FLAG.ui ).value
| hidpp10_constants.NOTIFICATION_FLAG.configuration_complete
)
else: else:
set_flag_bits = 0 set_flag_bits = 0
ok = _hidpp10.set_notification_flags(self, set_flag_bits) ok = _hidpp10.set_notification_flags(self, set_flag_bits)
@ -479,8 +478,12 @@ class Device:
flag_bits = _hidpp10.get_notification_flags(self) flag_bits = _hidpp10.get_notification_flags(self)
if logger.isEnabledFor(logging.INFO): if logger.isEnabledFor(logging.INFO):
flag_names = None if flag_bits is None else tuple(hidpp10_constants.NOTIFICATION_FLAG.flag_names(flag_bits)) if flag_bits is None:
logger.info("%s: device notifications %s %s", self, "enabled" if enable else "disabled", flag_names) flag_names = None
else:
flag_names = hidpp10_constants.NotificationFlag.flag_names(flag_bits)
is_enabled = "enabled" if enable else "disabled"
logger.info(f"{self}: device notifications {is_enabled} {flag_names}")
return flag_bits if ok else None return flag_bits if ok else None
def add_notification_handler(self, id: str, fn): def add_notification_handler(self, id: str, fn):

View File

@ -26,6 +26,7 @@ from .common import Battery
from .common import BatteryLevelApproximation from .common import BatteryLevelApproximation
from .common import BatteryStatus from .common import BatteryStatus
from .common import FirmwareKind from .common import FirmwareKind
from .hidpp10_constants import NotificationFlag
from .hidpp10_constants import Registers from .hidpp10_constants import Registers
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -190,7 +191,7 @@ class Hidpp10:
def get_notification_flags(self, device: Device): def get_notification_flags(self, device: Device):
return self._get_register(device, Registers.NOTIFICATIONS) return self._get_register(device, Registers.NOTIFICATIONS)
def set_notification_flags(self, device: Device, *flag_bits): def set_notification_flags(self, device: Device, *flag_bits: NotificationFlag):
assert device is not None assert device is not None
# Avoid a call if the device is not online, # Avoid a call if the device is not online,
@ -200,7 +201,7 @@ class Hidpp10:
if device.protocol and device.protocol >= 2.0: if device.protocol and device.protocol >= 2.0:
return return
flag_bits = sum(int(b) for b in flag_bits) flag_bits = sum(int(b.value) for b in flag_bits)
assert flag_bits & 0x00FFFFFF == flag_bits assert flag_bits & 0x00FFFFFF == flag_bits
result = write_register(device, Registers.NOTIFICATIONS, common.int2bytes(flag_bits, 3)) result = write_register(device, Registers.NOTIFICATIONS, common.int2bytes(flag_bits, 3))
return result is not None return result is not None

View File

@ -16,7 +16,9 @@
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from __future__ import annotations from __future__ import annotations
from enum import Flag
from enum import IntEnum from enum import IntEnum
from typing import List
from .common import NamedInts from .common import NamedInts
@ -58,37 +60,61 @@ class PowerSwitchLocation(IntEnum):
BOTTOM_EDGE = 0x0C BOTTOM_EDGE = 0x0C
# Some flags are used both by devices and receivers. The Logitech documentation class NotificationFlag(Flag):
# mentions that the first and last (third) byte are used for devices while the """Some flags are used both by devices and receivers.
# second is used for the receiver. In practise, the second byte is also used for
# some device-specific notifications (keyboard illumination level). Do not The Logitech documentation mentions that the first and last (third)
# simply set all notification bits if the software does not support it. For byte are used for devices while the second is used for the receiver.
# example, enabling keyboard_sleep_raw makes the Sleep key a no-operation unless In practise, the second byte is also used for some device-specific
# the software is updated to handle that event. notifications (keyboard illumination level). Do not simply set all
# Observations: notification bits if the software does not support it. For example,
# - wireless and software present were seen on receivers, reserved_r1b4 as well enabling keyboard_sleep_raw makes the Sleep key a no-operation
# - the rest work only on devices as far as we can tell right now unless the software is updated to handle that event.
# In the future would be useful to have separate enums for receiver and device notification flags,
# but right now we don't know enough. Observations:
# additional flags taken from https://drive.google.com/file/d/0BxbRzx7vEV7eNDBheWY0UHM5dEU/view?usp=sharing - wireless and software present seen on receivers,
NOTIFICATION_FLAG = NamedInts( reserved_r1b4 as well
numpad_numerical_keys=0x800000, - the rest work only on devices as far as we can tell right now
f_lock_status=0x400000, In the future would be useful to have separate enums for receiver
roller_H=0x200000, and device notification flags, but right now we don't know enough.
battery_status=0x100000, # send battery charge notifications (0x07 or 0x0D) Additional flags taken from https://drive.google.com/file/d/0BxbRzx7vEV7eNDBheWY0UHM5dEU/view?usp=sharing
mouse_extra_buttons=0x080000, """
roller_V=0x040000,
power_keys=0x020000, # system control keys such as Sleep @classmethod
keyboard_multimedia_raw=0x010000, # consumer controls such as Mute and Calculator def flag_names(cls, flag_bits: int) -> List[str]:
multi_touch=0x001000, # notify on multi-touch changes """Extract the names of the flags from the integer."""
software_present=0x000800, # software is controlling part of device behaviour indexed = {item.value: item.name for item in cls}
link_quality=0x000400, # notify on link quality changes
ui=0x000200, # notify on UI changes flag_names = []
wireless=0x000100, # notify when the device wireless goes on/off-line unknown_bits = flag_bits
configuration_complete=0x000004, for k in indexed:
voip_telephony=0x000002, # Ensure that the key (flag value) is a power of 2 (a single bit flag)
threed_gesture=0x000001, assert bin(k).count("1") == 1
) if k & flag_bits == k:
unknown_bits &= ~k
flag_names.append(indexed[k].replace("_", " ").lower())
# Yield any remaining unknown bits
if unknown_bits != 0:
flag_names.append(f"unknown:{unknown_bits:06X}")
return flag_names
NUMPAD_NUMERICAL_KEYS = 0x800000
F_LOCK_STATUS = 0x400000
ROLLER_H = 0x200000
BATTERY_STATUS = 0x100000 # send battery charge notifications (0x07 or 0x0D)
MOUSE_EXTRA_BUTTONS = 0x080000
ROLLER_V = 0x040000
POWER_KEYS = 0x020000 # system control keys such as Sleep
KEYBOARD_MULTIMEDIA_RAW = 0x010000 # consumer controls such as Mute and Calculator
MULTI_TOUCH = 0x001000 # notify on multi-touch changes
SOFTWARE_PRESENT = 0x000800 # software is controlling part of device behaviour
LINK_QUALITY = 0x000400 # notify on link quality changes
UI = 0x000200 # notify on UI changes
WIRELESS = 0x000100 # notify when the device wireless goes on/off-line
CONFIGURATION_COMPLETE = 0x000004
VOIP_TELEPHONY = 0x000002
THREED_GESTURE = 0x000001
def flags_to_str(flag_bits: int | None, fallback: str) -> str: def flags_to_str(flag_bits: int | None, fallback: str) -> str:
@ -97,8 +123,8 @@ def flags_to_str(flag_bits: int | None, fallback: str) -> str:
if flag_bits == 0: if flag_bits == 0:
flag_names = (fallback,) flag_names = (fallback,)
else: else:
flag_names = list(NOTIFICATION_FLAG.flag_names(flag_bits)) flag_names = NotificationFlag.flag_names(flag_bits)
return f"\n{' ':15}".join(flag_names) return f"\n{' ':15}".join(sorted(flag_names))
class ErrorCode(IntEnum): class ErrorCode(IntEnum):

View File

@ -36,6 +36,7 @@ from . import hidpp10_constants
from .common import Alert from .common import Alert
from .common import Notification from .common import Notification
from .device import Device from .device import Device
from .hidpp10_constants import NotificationFlag
from .hidpp10_constants import Registers from .hidpp10_constants import Registers
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
@ -174,7 +175,7 @@ class Receiver:
return False return False
if enable: if enable:
set_flag_bits = hidpp10_constants.NOTIFICATION_FLAG.wireless | hidpp10_constants.NOTIFICATION_FLAG.software_present set_flag_bits = NotificationFlag.WIRELESS | NotificationFlag.SOFTWARE_PRESENT
else: else:
set_flag_bits = 0 set_flag_bits = 0
ok = _hidpp10.set_notification_flags(self, set_flag_bits) ok = _hidpp10.set_notification_flags(self, set_flag_bits)
@ -183,7 +184,10 @@ class Receiver:
return None return None
flag_bits = _hidpp10.get_notification_flags(self) flag_bits = _hidpp10.get_notification_flags(self)
flag_names = None if flag_bits is None else tuple(hidpp10_constants.NOTIFICATION_FLAG.flag_names(flag_bits)) if flag_bits is None:
flag_names = None
else:
flag_names = hidpp10_constants.NotificationFlag.flag_names(flag_bits)
if logger.isEnabledFor(logging.INFO): if logger.isEnabledFor(logging.INFO):
logger.info("%s: receiver notifications %s => %s", self, "enabled" if enable else "disabled", flag_names) logger.info("%s: receiver notifications %s => %s", self, "enabled" if enable else "disabled", flag_names)
return flag_bits return flag_bits

View File

@ -81,7 +81,7 @@ class SolaarListener(listener.EventsListener):
logger.info("%s: notifications listener has started (%s)", self.receiver, self.receiver.handle) logger.info("%s: notifications listener has started (%s)", self.receiver, self.receiver.handle)
nfs = self.receiver.enable_connection_notifications() nfs = self.receiver.enable_connection_notifications()
if logger.isEnabledFor(logging.WARNING): if logger.isEnabledFor(logging.WARNING):
if not self.receiver.isDevice and not ((nfs if nfs else 0) & hidpp10_constants.NOTIFICATION_FLAG.wireless): if not self.receiver.isDevice and not ((nfs if nfs else 0) & hidpp10_constants.NotificationFlag.WIRELESS.value):
logger.warning( logger.warning(
"Receiver on %s might not support connection notifications, GUI might not show its devices", "Receiver on %s might not support connection notifications, GUI might not show its devices",
self.receiver.path, self.receiver.path,

View File

@ -551,7 +551,7 @@ def _update_details(button):
flag_bits = device.notification_flags flag_bits = device.notification_flags
if flag_bits is not None: if flag_bits is not None:
flag_names = hidpp10_constants.flags_to_names(flag_bits, fallback=f"({_('none')})") flag_names = hidpp10_constants.flags_to_str(flag_bits, fallback=f"({_('none')})")
yield _("Notifications"), flag_names yield _("Notifications"), flag_names
def _set_details(text): def _set_details(text):

View File

@ -255,7 +255,7 @@ def test_set_notification_flags(mocker):
spy_request = mocker.spy(device, "request") spy_request = mocker.spy(device, "request")
result = _hidpp10.set_notification_flags( result = _hidpp10.set_notification_flags(
device, hidpp10_constants.NOTIFICATION_FLAG.battery_status, hidpp10_constants.NOTIFICATION_FLAG.wireless device, hidpp10_constants.NotificationFlag.BATTERY_STATUS, hidpp10_constants.NotificationFlag.WIRELESS
) )
spy_request.assert_called_once_with(0x8000 | Registers.NOTIFICATIONS, b"\x10\x01\x00") spy_request.assert_called_once_with(0x8000 | Registers.NOTIFICATIONS, b"\x10\x01\x00")
@ -267,7 +267,7 @@ def test_set_notification_flags_bad(mocker):
spy_request = mocker.spy(device, "request") spy_request = mocker.spy(device, "request")
result = _hidpp10.set_notification_flags( result = _hidpp10.set_notification_flags(
device, hidpp10_constants.NOTIFICATION_FLAG.battery_status, hidpp10_constants.NOTIFICATION_FLAG.wireless device, hidpp10_constants.NotificationFlag.BATTERY_STATUS, hidpp10_constants.NotificationFlag.WIRELESS
) )
assert spy_request.call_count == 0 assert spy_request.call_count == 0