device: remove status from Device and Receiver

This commit is contained in:
Peter F. Patel-Schneider 2024-03-10 09:17:26 -04:00
parent a1418cd834
commit 9c5ba6445e
11 changed files with 91 additions and 172 deletions

View File

@ -77,7 +77,8 @@ class Device:
self.hidpp_short = device_info.hidpp_short if device_info else None self.hidpp_short = device_info.hidpp_short if device_info else None
self.hidpp_long = device_info.hidpp_long if device_info else None self.hidpp_long = device_info.hidpp_long if device_info else None
self.bluetooth = device_info.bus_id == 0x0005 if device_info else False # Bluetooth needs long messages self.bluetooth = device_info.bus_id == 0x0005 if device_info else False # Bluetooth needs long messages
self.setting_callback = setting_callback self.setting_callback = setting_callback # for changes to settings
self.status_callback = None # for changes to other potentially visible aspects
self.wpid = pairing_info["wpid"] if pairing_info else None # the Wireless PID is unique per device model self.wpid = pairing_info["wpid"] if pairing_info else None # the Wireless PID is unique per device model
self._kind = pairing_info["kind"] if pairing_info else None # mouse, keyboard, etc (see hidpp10.DEVICE_KIND) self._kind = pairing_info["kind"] if pairing_info else None # mouse, keyboard, etc (see hidpp10.DEVICE_KIND)
self._serial = pairing_info["serial"] if pairing_info else None # serial number (an 8-char hex string) self._serial = pairing_info["serial"] if pairing_info else None # serial number (an 8-char hex string)
@ -412,7 +413,6 @@ class Device:
def changed(self, active=None, alert=ALERT.NONE, reason=None, push=False): def changed(self, active=None, alert=ALERT.NONE, reason=None, push=False):
"""The status of the device had changed, so invoke the status callback. """The status of the device had changed, so invoke the status callback.
Also push notifications and settings to the device when necessary.""" Also push notifications and settings to the device when necessary."""
changed_callback = self.status._changed_callback
if active is not None: if active is not None:
self.online = active self.online = active
was_active, self._active = self._active, active was_active, self._active = self._active, active
@ -434,7 +434,8 @@ class Device:
settings.apply_all_settings(self) settings.apply_all_settings(self)
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("device %d changed: active=%s %s", self.number, self._active, self.battery_info) logger.debug("device %d changed: active=%s %s", self.number, self._active, self.battery_info)
changed_callback(self, alert, reason) if self.status_callback is not None:
self.status_callback(self, alert, reason)
def add_notification_handler(self, id: str, fn): def add_notification_handler(self, id: str, fn):
"""Adds the notification handling callback `fn` to this device under name `id`. """Adds the notification handling callback `fn` to this device under name `id`.

View File

@ -495,20 +495,20 @@ class Rule(RuleComponent):
source = "(" + self.source + ")" if self.source else "" source = "(" + self.source + ")" if self.source else ""
return "Rule%s[%s]" % (source, ", ".join([c.__str__() for c in self.components])) return "Rule%s[%s]" % (source, ", ".join([c.__str__() for c in self.components]))
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate rule: %s", self) logger.debug("evaluate rule: %s", self)
result = True result = True
for component in self.components: for component in self.components:
result = component.evaluate(feature, notification, device, status, result) result = component.evaluate(feature, notification, device, result)
if not isinstance(component, Action) and result is None: if not isinstance(component, Action) and result is None:
return None return None
if isinstance(component, Condition) and not result: if isinstance(component, Condition) and not result:
return result return result
return result return result
def once(self, feature, notification, device, status, last_result): def once(self, feature, notification, device, last_result):
self.evaluate(feature, notification, device, status, last_result) self.evaluate(feature, notification, device, last_result)
return False return False
def data(self): def data(self):
@ -522,7 +522,7 @@ class Condition(RuleComponent):
def __str__(self): def __str__(self):
return "CONDITION" return "CONDITION"
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
return False return False
@ -538,10 +538,10 @@ class Not(Condition):
def __str__(self): def __str__(self):
return "Not: " + str(self.component) return "Not: " + str(self.component)
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
result = self.component.evaluate(feature, notification, device, status, last_result) result = self.component.evaluate(feature, notification, device, last_result)
return None if result is None else not result return None if result is None else not result
def data(self): def data(self):
@ -555,12 +555,12 @@ class Or(Condition):
def __str__(self): def __str__(self):
return "Or: [" + ", ".join(str(c) for c in self.components) + "]" return "Or: [" + ", ".join(str(c) for c in self.components) + "]"
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
result = False result = False
for component in self.components: for component in self.components:
result = component.evaluate(feature, notification, device, status, last_result) result = component.evaluate(feature, notification, device, last_result)
if not isinstance(component, Action) and result is None: if not isinstance(component, Action) and result is None:
return None return None
if isinstance(component, Condition) and result: if isinstance(component, Condition) and result:
@ -578,12 +578,12 @@ class And(Condition):
def __str__(self): def __str__(self):
return "And: [" + ", ".join(str(c) for c in self.components) + "]" return "And: [" + ", ".join(str(c) for c in self.components) + "]"
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
result = True result = True
for component in self.components: for component in self.components:
result = component.evaluate(feature, notification, device, status, last_result) result = component.evaluate(feature, notification, device, last_result)
if not isinstance(component, Action) and result is None: if not isinstance(component, Action) and result is None:
return None return None
if isinstance(component, Condition) and not result: if isinstance(component, Condition) and not result:
@ -657,7 +657,7 @@ class Process(Condition):
def __str__(self): def __str__(self):
return "Process: " + str(self.process) return "Process: " + str(self.process)
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
if not isinstance(self.process, str): if not isinstance(self.process, str):
@ -688,7 +688,7 @@ class MouseProcess(Condition):
def __str__(self): def __str__(self):
return "MouseProcess: " + str(self.process) return "MouseProcess: " + str(self.process)
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
if not isinstance(self.process, str): if not isinstance(self.process, str):
@ -712,7 +712,7 @@ class Feature(Condition):
def __str__(self): def __str__(self):
return "Feature: " + str(self.feature) return "Feature: " + str(self.feature)
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
return feature == self.feature return feature == self.feature
@ -733,7 +733,7 @@ class Report(Condition):
def __str__(self): def __str__(self):
return "Report: " + str(self.report) return "Report: " + str(self.report)
def evaluate(self, report, notification, device, status, last_result): def evaluate(self, report, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
return (notification.address >> 4) == self.report return (notification.address >> 4) == self.report
@ -755,7 +755,7 @@ class Setting(Condition):
def __str__(self): def __str__(self):
return "Setting: " + " ".join([str(a) for a in self.args]) return "Setting: " + " ".join([str(a) for a in self.args])
def evaluate(self, report, notification, device, status, last_result): def evaluate(self, report, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
if len(self.args) < 3: if len(self.args) < 3:
@ -806,7 +806,7 @@ class Modifiers(Condition):
def __str__(self): def __str__(self):
return "Modifiers: " + str(self.desired) return "Modifiers: " + str(self.desired)
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
if gkeymap: if gkeymap:
@ -863,7 +863,7 @@ class Key(Condition):
def __str__(self): def __str__(self):
return "Key: %s (%s)" % ((str(self.key) if self.key else "None"), self.action) return "Key: %s (%s)" % ((str(self.key) if self.key else "None"), self.action)
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
return bool(self.key and self.key == (key_down if self.action == self.DOWN else key_up)) return bool(self.key and self.key == (key_down if self.action == self.DOWN else key_up))
@ -895,7 +895,7 @@ class KeyIsDown(Condition):
def __str__(self): def __str__(self):
return "KeyIsDown: %s" % (str(self.key) if self.key else "None") return "KeyIsDown: %s" % (str(self.key) if self.key else "None")
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
return key_is_down(self.key) return key_is_down(self.key)
@ -949,7 +949,7 @@ class Test(Condition):
def __str__(self): def __str__(self):
return "Test: " + str(self.test) return "Test: " + str(self.test)
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
return self.function(feature, notification.address, notification.data, self.parameter) return self.function(feature, notification.address, notification.data, self.parameter)
@ -979,7 +979,7 @@ class TestBytes(Condition):
def __str__(self): def __str__(self):
return "TestBytes: " + str(self.test) return "TestBytes: " + str(self.test)
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
return self.function(feature, notification.address, notification.data) return self.function(feature, notification.address, notification.data)
@ -1012,7 +1012,7 @@ class MouseGesture(Condition):
def __str__(self): def __str__(self):
return "MouseGesture: " + " ".join(self.movements) return "MouseGesture: " + " ".join(self.movements)
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
if feature == _F.MOUSE_GESTURE: if feature == _F.MOUSE_GESTURE:
@ -1054,7 +1054,7 @@ class Active(Condition):
def __str__(self): def __str__(self):
return "Active: " + str(self.devID) return "Active: " + str(self.devID)
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
dev = device.find(self.devID) dev = device.find(self.devID)
@ -1075,7 +1075,7 @@ class Device(Condition):
def __str__(self): def __str__(self):
return "Device: " + str(self.devID) return "Device: " + str(self.devID)
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
return device.unitId == self.devID or device.serial == self.devID return device.unitId == self.devID or device.serial == self.devID
@ -1095,7 +1095,7 @@ class Host(Condition):
def __str__(self): def __str__(self):
return "Host: " + str(self.host) return "Host: " + str(self.host)
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluate condition: %s", self) logger.debug("evaluate condition: %s", self)
hostname = socket.getfqdn() hostname = socket.getfqdn()
@ -1109,7 +1109,7 @@ class Action(RuleComponent):
def __init__(self, *args): def __init__(self, *args):
pass pass
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
return None return None
@ -1189,7 +1189,7 @@ class KeyPress(Action):
simulate_key(keycode, _KEY_RELEASE) simulate_key(keycode, _KEY_RELEASE)
self.mods(level, modifiers, _KEY_RELEASE) self.mods(level, modifiers, _KEY_RELEASE)
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if gkeymap: if gkeymap:
current = gkeymap.get_modifier_state() current = gkeymap.get_modifier_state()
if logger.isEnabledFor(logging.INFO): if logger.isEnabledFor(logging.INFO):
@ -1209,10 +1209,10 @@ class KeyPress(Action):
# KeyDown is dangerous as the key can auto-repeat and make your system unusable # KeyDown is dangerous as the key can auto-repeat and make your system unusable
# class KeyDown(KeyPress): # class KeyDown(KeyPress):
# def evaluate(self, feature, notification, device, status, last_result): # def evaluate(self, feature, notification, device, last_result):
# super().keyDown(self.keys, current_key_modifiers) # super().keyDown(self.keys, current_key_modifiers)
# class KeyUp(KeyPress): # class KeyUp(KeyPress):
# def evaluate(self, feature, notification, device, status, last_result): # def evaluate(self, feature, notification, device, last_result):
# super().keyUp(self.keys, current_key_modifiers) # super().keyUp(self.keys, current_key_modifiers)
@ -1229,7 +1229,7 @@ class MouseScroll(Action):
def __str__(self): def __str__(self):
return "MouseScroll: " + " ".join([str(a) for a in self.amounts]) return "MouseScroll: " + " ".join([str(a) for a in self.amounts])
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
amounts = self.amounts amounts = self.amounts
if isinstance(last_result, numbers.Number): if isinstance(last_result, numbers.Number):
amounts = [math.floor(last_result * a) for a in self.amounts] amounts = [math.floor(last_result * a) for a in self.amounts]
@ -1268,7 +1268,7 @@ class MouseClick(Action):
def __str__(self): def __str__(self):
return "MouseClick: %s (%d)" % (self.button, self.count) return "MouseClick: %s (%d)" % (self.button, self.count)
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.INFO): if logger.isEnabledFor(logging.INFO):
logger.info("MouseClick action: %d %s" % (self.count, self.button)) logger.info("MouseClick action: %d %s" % (self.count, self.button))
if self.button and self.count: if self.button and self.count:
@ -1292,7 +1292,7 @@ class Set(Action):
def __str__(self): def __str__(self):
return "Set: " + " ".join([str(a) for a in self.args]) return "Set: " + " ".join([str(a) for a in self.args])
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if len(self.args) < 3: if len(self.args) < 3:
return None return None
if logger.isEnabledFor(logging.INFO): if logger.isEnabledFor(logging.INFO):
@ -1335,7 +1335,7 @@ class Execute(Action):
def __str__(self): def __str__(self):
return "Execute: " + " ".join([a for a in self.args]) return "Execute: " + " ".join([a for a in self.args])
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if logger.isEnabledFor(logging.INFO): if logger.isEnabledFor(logging.INFO):
logger.info("Execute action: %s", self.args) logger.info("Execute action: %s", self.args)
subprocess.Popen(self.args) subprocess.Popen(self.args)
@ -1366,9 +1366,9 @@ class Later(Action):
def __str__(self): def __str__(self):
return "Later: [" + str(self.delay) + ", " + ", ".join(str(c) for c in self.components) + "]" return "Later: [" + str(self.delay) + ", " + ", ".join(str(c) for c in self.components) + "]"
def evaluate(self, feature, notification, device, status, last_result): def evaluate(self, feature, notification, device, last_result):
if self.delay and self.rule: if self.delay and self.rule:
GLib.timeout_add_seconds(self.delay, Rule.once, self.rule, feature, notification, device, status, last_result) GLib.timeout_add_seconds(self.delay, Rule.once, self.rule, feature, notification, device, last_result)
return None return None
def data(self): def data(self):
@ -1435,14 +1435,14 @@ def key_is_down(key):
return key in keys_down return key in keys_down
def evaluate_rules(feature, notification, device, status): def evaluate_rules(feature, notification, device):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("evaluating rules on %s", notification) logger.debug("evaluating rules on %s", notification)
rules.evaluate(feature, notification, device, status, True) rules.evaluate(feature, notification, device, True)
# process a notification # process a notification
def process_notification(device, status, notification, feature): def process_notification(device, notification, feature):
global keys_down, g_keys_down, m_keys_down, mr_key_down, key_down, key_up, thumb_wheel_displacement global keys_down, g_keys_down, m_keys_down, mr_key_down, key_down, key_up, thumb_wheel_displacement
key_down, key_up = None, None key_down, key_up = None, None
# need to keep track of keys that are down to find a new key down # need to keep track of keys that are down to find a new key down
@ -1487,7 +1487,7 @@ def process_notification(device, status, notification, feature):
thumb_wheel_displacement = 0 thumb_wheel_displacement = 0
thumb_wheel_displacement += signed(notification.data[0:2]) thumb_wheel_displacement += signed(notification.data[0:2])
GLib.idle_add(evaluate_rules, feature, notification, device, status) GLib.idle_add(evaluate_rules, feature, notification, device)
_XDG_CONFIG_HOME = _os.environ.get("XDG_CONFIG_HOME") or _path.expanduser(_path.join("~", ".config")) _XDG_CONFIG_HOME = _os.environ.get("XDG_CONFIG_HOME") or _path.expanduser(_path.join("~", ".config"))

View File

@ -15,8 +15,7 @@
## with this program; if not, write to the Free Software Foundation, Inc., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# Handles incoming events from the receiver/devices, updating the related # Handles incoming events from the receiver/devices, updating the object as appropriate.
# status object as appropriate.
import logging import logging
import threading as _threading import threading as _threading
@ -29,10 +28,10 @@ from . import hidpp10_constants as _hidpp10_constants
from . import hidpp20_constants as _hidpp20_constants from . import hidpp20_constants as _hidpp20_constants
from . import settings_templates as _st from . import settings_templates as _st
from .base import DJ_MESSAGE_ID as _DJ_MESSAGE_ID from .base import DJ_MESSAGE_ID as _DJ_MESSAGE_ID
from .common import ALERT as _ALERT
from .common import Battery as _Battery from .common import Battery as _Battery
from .common import strhex as _strhex from .common import strhex as _strhex
from .i18n import _ from .i18n import _
from .status import ALERT as _ALERT
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -49,17 +48,12 @@ def process(device, notification):
assert device assert device
assert notification assert notification
assert hasattr(device, "status")
status = device.status
assert status is not None
if not device.isDevice: if not device.isDevice:
return _process_receiver_notification(device, status, notification) return _process_receiver_notification(device, notification)
return _process_device_notification(device, notification)
return _process_device_notification(device, status, notification)
def _process_receiver_notification(receiver, status, n): def _process_receiver_notification(receiver, n):
# supposedly only 0x4x notifications arrive for the receiver # supposedly only 0x4x notifications arrive for the receiver
assert n.sub_id & 0x40 == 0x40 assert n.sub_id & 0x40 == 0x40
@ -148,7 +142,7 @@ def _process_receiver_notification(receiver, status, n):
logger.warning("%s: unhandled notification %s", receiver, n) logger.warning("%s: unhandled notification %s", receiver, n)
def _process_device_notification(device, status, n): def _process_device_notification(device, n):
# incoming packets with SubId >= 0x80 are supposedly replies from HID++ 1.0 requests, should never get here # incoming packets with SubId >= 0x80 are supposedly replies from HID++ 1.0 requests, should never get here
assert n.sub_id & 0x80 == 0 assert n.sub_id & 0x80 == 0
@ -163,9 +157,9 @@ def _process_device_notification(device, status, n):
# 0x40 to 0x7F appear to be HID++ 1.0 or DJ notifications # 0x40 to 0x7F appear to be HID++ 1.0 or DJ notifications
if n.sub_id >= 0x40: if n.sub_id >= 0x40:
if n.report_id == _DJ_MESSAGE_ID: if n.report_id == _DJ_MESSAGE_ID:
return _process_dj_notification(device, status, n) return _process_dj_notification(device, n)
else: else:
return _process_hidpp10_notification(device, status, n) return _process_hidpp10_notification(device, n)
# These notifications are from the device itself, so it must be active # These notifications are from the device itself, so it must be active
device.online = True device.online = True
@ -174,7 +168,7 @@ def _process_device_notification(device, status, n):
# some custom battery events for HID++ 1.0 devices # some custom battery events for HID++ 1.0 devices
if device.protocol < 2.0: if device.protocol < 2.0:
return _process_hidpp10_custom_notification(device, status, n) return _process_hidpp10_custom_notification(device, n)
# assuming 0x00 to 0x3F are feature (HID++ 2.0) notifications # assuming 0x00 to 0x3F are feature (HID++ 2.0) notifications
if not device.features: if not device.features:
@ -186,10 +180,10 @@ def _process_device_notification(device, status, n):
logger.warning("%s: notification from invalid feature index %02X: %s", device, n.sub_id, n) logger.warning("%s: notification from invalid feature index %02X: %s", device, n.sub_id, n)
return False return False
return _process_feature_notification(device, status, n, feature) return _process_feature_notification(device, n, feature)
def _process_dj_notification(device, status, n): def _process_dj_notification(device, n):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("%s (%s) DJ %s", device, device.protocol, n) logger.debug("%s (%s) DJ %s", device, device.protocol, n)
@ -215,7 +209,7 @@ def _process_dj_notification(device, status, n):
logger.warning("%s: unrecognized DJ %s", device, n) logger.warning("%s: unrecognized DJ %s", device, n)
def _process_hidpp10_custom_notification(device, status, n): def _process_hidpp10_custom_notification(device, n):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("%s (%s) custom notification %s", device, device.protocol, n) logger.debug("%s (%s) custom notification %s", device, device.protocol, n)
@ -228,7 +222,7 @@ def _process_hidpp10_custom_notification(device, status, n):
logger.warning("%s: unrecognized %s", device, n) logger.warning("%s: unrecognized %s", device, n)
def _process_hidpp10_notification(device, status, n): def _process_hidpp10_notification(device, n):
if n.sub_id == 0x40: # device unpairing if n.sub_id == 0x40: # device unpairing
if n.address == 0x02: if n.address == 0x02:
# device un-paired # device un-paired
@ -236,7 +230,7 @@ def _process_hidpp10_notification(device, status, n):
if device.number in device.receiver: if device.number in device.receiver:
del device.receiver[device.number] del device.receiver[device.number]
device.changed(active=False, alert=_ALERT.ALL, reason=_("unpaired")) device.changed(active=False, alert=_ALERT.ALL, reason=_("unpaired"))
device.status = None ## device.status = None
else: else:
logger.warning("%s: disconnection with unknown type %02X: %s", device, n.address, n) logger.warning("%s: disconnection with unknown type %02X: %s", device, n.address, n)
return True return True
@ -289,7 +283,7 @@ def _process_hidpp10_notification(device, status, n):
logger.warning("%s: unrecognized %s", device, n) logger.warning("%s: unrecognized %s", device, n)
def _process_feature_notification(device, status, n, feature): def _process_feature_notification(device, n, feature):
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("%s: notification for feature %s, report %s, data %s", device, feature, n.address >> 4, _strhex(n.data)) logger.debug("%s: notification for feature %s, report %s, data %s", device, feature, n.address >> 4, _strhex(n.data))
@ -440,5 +434,5 @@ def _process_feature_notification(device, status, n, feature):
device.setting_callback(device, _st.AdjustableDpi, [profile.resolutions[resolution_index]]) device.setting_callback(device, _st.AdjustableDpi, [profile.resolutions[resolution_index]])
break break
_diversion.process_notification(device, status, n, feature) _diversion.process_notification(device, n, feature)
return True return True

View File

@ -66,7 +66,8 @@ class Receiver:
self.handle = handle self.handle = handle
self.path = path self.path = path
self.product_id = product_id self.product_id = product_id
self.setting_callback = setting_callback self.setting_callback = setting_callback # for changes to settings
self.status_callback = None # for changes to other potentially visible aspects
self.receiver_kind = receiver_kind self.receiver_kind = receiver_kind
self.serial = None self.serial = None
self.max_devices = None self.max_devices = None
@ -105,7 +106,8 @@ class Receiver:
def changed(self, alert=ALERT.NOTIFICATION, reason=None): def changed(self, alert=ALERT.NOTIFICATION, reason=None):
"""The status of the device had changed, so invoke the status callback""" """The status of the device had changed, so invoke the status callback"""
self.status._changed_callback(self, alert=alert, reason=reason) if self.status_callback is not None:
self.status_callback(self, alert=alert, reason=reason)
@property @property
def firmware(self): def firmware(self):

View File

@ -841,7 +841,7 @@ class MouseGesturesXY(_RawXYProcessing):
logger.info("mouse gesture notification %s", self.data) logger.info("mouse gesture notification %s", self.data)
payload = _pack("!" + (len(self.data) * "h"), *self.data) payload = _pack("!" + (len(self.data) * "h"), *self.data)
notification = _HIDPP_Notification(0, 0, 0, 0, payload) notification = _HIDPP_Notification(0, 0, 0, 0, payload)
_process_notification(self.device, self.device.status, notification, _F.MOUSE_GESTURE) _process_notification(self.device, notification, _F.MOUSE_GESTURE)
self.fsmState = "idle" self.fsmState = "idle"
def move_action(self, dx, dy): def move_action(self, dx, dy):

View File

@ -1,70 +0,0 @@
## Copyright (C) 2012-2013 Daniel Pavel
## Copyright (C) 2014-2024 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.
import logging
from . import hidpp10
from . import hidpp10_constants as _hidpp10_constants
from .common import NamedInts
logger = logging.getLogger(__name__)
_R = _hidpp10_constants.REGISTERS
_hidpp10 = hidpp10.Hidpp10()
ALERT = NamedInts(NONE=0x00, NOTIFICATION=0x01, SHOW_WINDOW=0x02, ATTENTION=0x04, ALL=0xFF)
def attach_to(device, changed_callback):
assert device
assert changed_callback
if not hasattr(device, "status") or device.status is None:
if not device.isDevice:
device.status = ReceiverStatus(device, changed_callback)
else:
device.status = DeviceStatus(device, changed_callback)
class ReceiverStatus:
"""The 'runtime' status of a receiver, currently vestigial."""
def __init__(self, receiver, changed_callback):
assert receiver
self._receiver = receiver
assert changed_callback
self._changed_callback = changed_callback
class DeviceStatus:
"""Holds the 'runtime' status of a peripheral
Currently _active, battery, link_encrypted, notification_flags, error
Updates mostly come from incoming notification events from the device itself.
"""
def __init__(self, device, changed_callback):
assert device
self._device = device
assert changed_callback
self._changed_callback = changed_callback
self._active = None # is the device active?
def __bool__(self):
return bool(self._active)
__nonzero__ = __bool__

View File

@ -20,7 +20,6 @@ from logitech_receiver import base as _base
from logitech_receiver import hidpp10 from logitech_receiver import hidpp10
from logitech_receiver import hidpp10_constants as _hidpp10_constants from logitech_receiver import hidpp10_constants as _hidpp10_constants
from logitech_receiver import notifications as _notifications from logitech_receiver import notifications as _notifications
from logitech_receiver import status as _status
_hidpp10 = hidpp10.Hidpp10() _hidpp10 = hidpp10.Hidpp10()
_R = _hidpp10_constants.REGISTERS _R = _hidpp10_constants.REGISTERS
@ -38,7 +37,6 @@ def run(receivers, args, find_receiver, _ignore):
receiver = receivers[0] receiver = receivers[0]
assert receiver assert receiver
receiver.status = _status.ReceiverStatus(receiver, lambda *args, **kwargs: None)
# check if it's necessary to set the notification flags # check if it's necessary to set the notification flags
old_notification_flags = _hidpp10.get_notification_flags(receiver) or 0 old_notification_flags = _hidpp10.get_notification_flags(receiver) or 0

View File

@ -31,7 +31,6 @@ from logitech_receiver import exceptions
from logitech_receiver import hidpp10_constants as _hidpp10_constants from logitech_receiver import hidpp10_constants as _hidpp10_constants
from logitech_receiver import listener as _listener from logitech_receiver import listener as _listener
from logitech_receiver import notifications as _notifications from logitech_receiver import notifications as _notifications
from logitech_receiver import status as _status
from . import configuration from . import configuration
@ -44,15 +43,13 @@ _R = _hidpp10_constants.REGISTERS
_IR = _hidpp10_constants.INFO_SUBREGISTERS _IR = _hidpp10_constants.INFO_SUBREGISTERS
_GHOST_DEVICE = namedtuple("_GHOST_DEVICE", ("receiver", "number", "name", "kind", "status", "online")) _GHOST_DEVICE = namedtuple("_GHOST_DEVICE", ("receiver", "number", "name", "kind", "online"))
_GHOST_DEVICE.__bool__ = lambda self: False _GHOST_DEVICE.__bool__ = lambda self: False
_GHOST_DEVICE.__nonzero__ = _GHOST_DEVICE.__bool__ _GHOST_DEVICE.__nonzero__ = _GHOST_DEVICE.__bool__
def _ghost(device): def _ghost(device):
return _GHOST_DEVICE( return _GHOST_DEVICE(receiver=device.receiver, number=device.number, name=device.name, kind=device.kind, online=False)
receiver=device.receiver, number=device.number, name=device.name, kind=device.kind, status=None, online=False
)
class ReceiverListener(_listener.EventsListener): class ReceiverListener(_listener.EventsListener):
@ -62,7 +59,7 @@ class ReceiverListener(_listener.EventsListener):
assert status_changed_callback assert status_changed_callback
super().__init__(receiver, self._notifications_handler) super().__init__(receiver, self._notifications_handler)
self.status_changed_callback = status_changed_callback self.status_changed_callback = status_changed_callback
_status.attach_to(receiver, self._status_changed) receiver.status_callback = self._status_changed
def has_started(self): def has_started(self):
if logger.isEnabledFor(logging.INFO): if logger.isEnabledFor(logging.INFO):
@ -95,32 +92,30 @@ class ReceiverListener(_listener.EventsListener):
logger.exception("closing receiver %s" % r.path) logger.exception("closing receiver %s" % r.path)
self.status_changed_callback(r) self.status_changed_callback(r)
def _status_changed(self, device, alert=_status.ALERT.NONE, reason=None): def _status_changed(self, device, alert=None, reason=None):
assert device is not None assert device is not None
if logger.isEnabledFor(logging.INFO): if logger.isEnabledFor(logging.INFO):
try: try:
device.ping()
if device.kind is None: if device.kind is None:
logger.info( logger.info(
"status_changed %r: %s, %s (%X) %s", "status_changed %r: %s (%X) %s",
device, device,
"present" if bool(device) else "removed", "present" if bool(device) else "removed",
device.status, alert if alert is not None else 0,
alert,
reason or "", reason or "",
) )
else: else:
device.ping()
logger.info( logger.info(
"status_changed %r: %s %s, %s (%X) %s", "status_changed %r: %s %s (%X) %s",
device, device,
"paired" if bool(device) else "unpaired", "paired" if bool(device) else "unpaired",
"online" if device.online else "offline", "online" if device.online else "offline",
device.status, alert if alert is not None else 0,
alert,
reason or "", reason or "",
) )
except Exception: except Exception as e:
logger.info("status_changed for unknown device") logger.info("status_changed for unknown device: %s", e)
if device.kind is None: if device.kind is None:
assert device == self.receiver assert device == self.receiver
@ -214,15 +209,11 @@ class ReceiverListener(_listener.EventsListener):
# If there are saved configs, bring the device's settings up-to-date. # If there are saved configs, bring the device's settings up-to-date.
# They will be applied when the device is marked as online. # They will be applied when the device is marked as online.
configuration.attach_to(dev) configuration.attach_to(dev)
_status.attach_to(dev, self._status_changed) dev.status_callback = self._status_changed
# the receiver changed status as well # the receiver changed status as well
self._status_changed(self.receiver) self._status_changed(self.receiver)
if not hasattr(dev, "status") or dev.status is None: _notifications.process(dev, n)
# notification before device status set up - don't process it
logger.warning("%s before device %s has status", n, dev)
else:
_notifications.process(dev, n)
if self.receiver.pairing.lock_open and not already_known: if self.receiver.pairing.lock_open and not already_known:
# this should be the first notification after a device was paired # this should be the first notification after a device was paired
@ -297,8 +288,8 @@ def ping_all(resuming=False):
count = listener_thread.receiver.count() count = listener_thread.receiver.count()
if count: if count:
for dev in listener_thread.receiver: for dev in listener_thread.receiver:
if resuming and hasattr(dev, "status"): if resuming:
dev.status._active = None # ensure that settings are pushed dev._active = None # ensure that settings are pushed
if dev.ping(): if dev.ping():
dev.changed(active=True, push=True) dev.changed(active=True, push=True)
listener_thread._status_changed(dev) listener_thread._status_changed(dev)

View File

@ -19,7 +19,7 @@ import logging
import gi import gi
import yaml as _yaml import yaml as _yaml
from logitech_receiver.status import ALERT from logitech_receiver.common import ALERT
from solaar.i18n import _ from solaar.i18n import _
from solaar.ui.config_panel import change_setting, record_setting from solaar.ui.config_panel import change_setting, record_setting
@ -115,6 +115,8 @@ def _status_changed(device, alert, reason, refresh=False):
assert device is not None assert device is not None
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
logger.debug("status changed: %s (%s) %s", device, alert, reason) logger.debug("status changed: %s (%s) %s", device, alert, reason)
if alert is None:
alert = ALERT.NONE
tray.update(device) tray.update(device)
if alert & ALERT.ATTENTION: if alert & ALERT.ATTENTION:

View File

@ -113,20 +113,21 @@ if available:
if reason: if reason:
message = reason message = reason
elif dev.status is None:
message = _("unpaired")
elif bool(dev.status):
message = dev.status_string() or _("connected")
else: else:
message = _("offline") message = _("unspecified reason")
# elif dev.status is None:
# message = _("unpaired")
# elif bool(dev.status):
# message = dev.status_string() or _("connected")
# else:
# message = _("offline")
# we need to use the filename here because the notifications daemon # we need to use the filename here because the notifications daemon
# is an external application that does not know about our icon sets # is an external application that does not know about our icon sets
icon_file = _icons.device_icon_file(dev.name, dev.kind) if icon is None else _icons.icon_file(icon) icon_file = _icons.device_icon_file(dev.name, dev.kind) if icon is None else _icons.icon_file(icon)
n.update(summary, message, icon_file) n.update(summary, message, icon_file)
urgency = Notify.Urgency.LOW if dev.status else Notify.Urgency.NORMAL n.set_urgency(Notify.Urgency.NORMAL)
n.set_urgency(urgency)
n.set_hint("desktop-entry", GLib.Variant("s", NAME.lower())) n.set_hint("desktop-entry", GLib.Variant("s", NAME.lower()))
if progress: if progress:
n.set_hint("value", GLib.Variant("i", progress)) n.set_hint("value", GLib.Variant("i", progress))

View File

@ -420,7 +420,7 @@ def _remove_receiver(receiver):
def _update_menu_item(index, device): def _update_menu_item(index, device):
if device is None or device.status is None: if device is None:
logger.warning("updating an inactive device %s, assuming disconnected", device) logger.warning("updating an inactive device %s, assuming disconnected", device)
return None return None