parent
571cdb5f2d
commit
72c9dfc50c
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue