def run(receivers, args, find_receiver, _ignore): assert receivers if args.receiver: receiver_name = args.receiver.lower() receiver = find_receiver(receiver_name) if not receiver: raise Exception("no receiver found matching '%s'" % receiver_name) else: receiver = receivers[0] assert receiver receiver.status = _status.ReceiverStatus(receiver, lambda *args, **kwargs: None) # check if it's necessary to set the notification flags old_notification_flags = _hidpp10.get_notification_flags(receiver) or 0 if not (old_notification_flags & _hidpp10.NOTIFICATION_FLAG.wireless): _hidpp10.set_notification_flags(receiver, old_notification_flags | _hidpp10.NOTIFICATION_FLAG.wireless) # get all current devices known_devices = [dev.number for dev in receiver] class _HandleWithNotificationHook(int): def notifications_hook(self, n): assert n if n.devnumber == 0xFF: _notifications.process(receiver, n) elif n.sub_id == 0x41 and n.address == 0x04: if n.devnumber not in known_devices: receiver.status.new_device = receiver[n.devnumber] timeout = 20 # seconds receiver.handle = _HandleWithNotificationHook(receiver.handle) receiver.set_lock(False, timeout=timeout) print ('Pairing: turn your new device on (timing out in', timeout, 'seconds).') # the lock-open notification may come slightly later, wait for it a bit pairing_start = _timestamp() patience = 5 # seconds while receiver.status.lock_open or _timestamp() - pairing_start < patience: n = _base.read(receiver.handle) if n: n = _base.make_notification(*n) if n: receiver.handle.notifications_hook(n) if not (old_notification_flags & _hidpp10.NOTIFICATION_FLAG.wireless): # only clear the flags if they weren't set before, otherwise a # concurrently running Solaar app might stop working properly _hidpp10.set_notification_flags(receiver, old_notification_flags) if receiver.status.new_device: dev = receiver.status.new_device print ('Paired device %d: %s (%s) [%s:%s]' % (dev.number, dev.name, dev.codename, dev.wpid, dev.serial)) else: error = receiver.status.get(_status.KEYS.ERROR) or 'no device detected?' raise Exception("pairing failed: %s" % error)
def ping(handle, devnumber): """Check if a device is connected to the UR. :returns: The HID protocol supported by the device, as a floating point number, if the device is active. """ if _log.isEnabledFor(_DEBUG): _log.debug("(%s) pinging device %d", handle, devnumber) # import inspect as _inspect # print ('\n '.join(str(s) for s in _inspect.stack())) # randomize the SoftwareId and mark byte to be able to identify the ping # reply, and set most significant (0x8) bit in SoftwareId so that the reply # is always distinguishable from notifications request_id = 0x0018 | _random_bits(3) request_data = _pack(b'!HBBB', request_id, 0, 0, _random_bits(8)) ihandle = int(handle) notifications_hook = getattr(handle, 'notifications_hook', None) _skip_incoming(handle, ihandle, notifications_hook) write(ihandle, devnumber, request_data) while True: now = _timestamp() reply = _read(handle, _PING_TIMEOUT) delta = _timestamp() - now if reply: report_id, reply_devnumber, reply_data = reply if reply_devnumber == devnumber: if reply_data[:2] == request_data[:2] and reply_data[4:5] == request_data[-1:]: # HID++ 2.0+ device, currently connected return ord(reply_data[2:3]) + ord(reply_data[3:4]) / 10.0 if report_id == 0x10 and reply_data[:1] == b'\x8F' and reply_data[1:3] == request_data[:2]: assert reply_data[-1:] == b'\x00' error = ord(reply_data[3:4]) if error == _hidpp10.ERROR.invalid_SubID__command: # a valid reply from a HID++ 1.0 device return 1.0 if error == _hidpp10.ERROR.resource_error: # device unreachable # raise DeviceUnreachable(number=devnumber, request=request_id) break if error == _hidpp10.ERROR.unknown_device: # no paired device with that number _log.error("(%s) device %d error on ping request: unknown device", handle, devnumber) raise NoSuchDevice(number=devnumber, request=request_id) if notifications_hook: n = make_notification(reply_devnumber, reply_data) if n: notifications_hook(n) if delta >= _PING_TIMEOUT: _log.warn("(%s) timeout on device %d ping", handle, devnumber)
def changed(self, active=None, alert=ALERT.NONE, reason=None, timestamp=None): assert self._changed_callback d = self._device # assert d # may be invalid when processing the 'unpaired' notification timestamp = timestamp or _timestamp() if active is not None: d.online = active was_active, self._active = self._active, active if active: if not was_active: # Make sure to set notification flags on the device, they # get cleared when the device is turned off (but not when the device # goes idle, and we can't tell the difference right now). if d.protocol < 2.0: self[KEYS.NOTIFICATION_FLAGS] = d.enable_notifications( ) # If we've been inactive for a long time, forget anything # about the battery. (This is probably unnecessary.) if self.updated > 0 and timestamp - self.updated > _LONG_SLEEP: self[KEYS.BATTERY_LEVEL] = None self[KEYS.BATTERY_STATUS] = None self[KEYS.BATTERY_CHARGING] = None self[KEYS.BATTERY_VOLTAGE] = None # Devices lose configuration when they are turned off, # make sure they're up-to-date. if _log.isEnabledFor(_DEBUG): _log.debug('%s pushing device settings %s', d, d.settings) for s in d.settings: s.apply() # battery information may have changed so try to read it now self.read_battery(timestamp) else: if was_active: battery = self.get(KEYS.BATTERY_LEVEL) self.clear() # If we had a known battery level before, assume it's not going # to change much while the device is offline. if battery is not None: self[KEYS.BATTERY_LEVEL] = battery if self.updated == 0 and active is True: # if the device is active on the very first status notification, # (meaning just when the program started or a new receiver was just # detected), pop-up a notification about it alert |= ALERT.NOTIFICATION self.updated = timestamp # if _log.isEnabledFor(_DEBUG): # _log.debug("device %d changed: active=%s %s", d.number, self._active, dict(self)) self._changed_callback(d, alert, reason)
def __localtime_offset_decreased_jtm(self): # workaround for strftime GMT offset dependecy njtm = self.__jtm.copy() if self.tzinfo is None: njtm.tm_gmtoff = 0 njtm.tm_sec -= jlocaltime(int(_timestamp())).tm_gmtoff njtm.tm_zone = '' normalize_jtm(njtm) return njtm
def find_paired_node(receiver_path, index, timeout): """Find the node of a device paired with a receiver""" context = _Context() receiver_phys = _Devices.from_device_file(context, receiver_path).find_parent('hid').get('HID_PHYS') if not receiver_phys: return None phys = f'{receiver_phys}:{index}' timeout += _timestamp() delta = _timestamp() while delta < timeout: for dev in context.list_devices(subsystem='hidraw'): dev_phys = dev.find_parent('hid').get('HID_PHYS') if dev_phys and dev_phys == phys: return dev.device_node delta = _timestamp() return None
def changed(self, active=None, alert=ALERT.NONE, reason=None, timestamp=None): assert self._changed_callback d = self._device # assert d # may be invalid when processing the 'unpaired' notification timestamp = timestamp or _timestamp() if active is not None: d.online = active was_active, self._active = self._active, active if active: if not was_active: # Make sure to set notification flags on the device, they # get cleared when the device is turned off (but not when the device # goes idle, and we can't tell the difference right now). if d.protocol < 2.0: self[KEYS.NOTIFICATION_FLAGS] = d.enable_notifications() # If we've been inactive for a long time, forget anything # about the battery. if self.updated > 0 and timestamp - self.updated > _LONG_SLEEP: self[KEYS.BATTERY_LEVEL] = None self[KEYS.BATTERY_STATUS] = None self[KEYS.BATTERY_CHARGING] = None # Devices lose configuration when they are turned off, # make sure they're up-to-date. # _log.debug("%s settings %s", d, d.settings) for s in d.settings: s.apply() if self.get(KEYS.BATTERY_LEVEL) is None: self.read_battery(timestamp) else: if was_active: battery = self.get(KEYS.BATTERY_LEVEL) self.clear() # If we had a known battery level before, assume it's not going # to change much while the device is offline. if battery is not None: self[KEYS.BATTERY_LEVEL] = battery if self.updated == 0 and active == True: # if the device is active on the very first status notification, # (meaning just when the program started or a new receiver was just # detected), pop-up a notification about it alert |= ALERT.NOTIFICATION self.updated = timestamp # if _log.isEnabledFor(_DEBUG): # _log.debug("device %d changed: active=%s %s", d.number, self._active, dict(self)) self._changed_callback(d, alert, reason)
def run(self): self._active = True ihandle = int(self.receiver.handle) _log.info("started with %s (%d)", self.receiver, ihandle) self.has_started() last_tick = 0 idle_reads = 0 while self._active: if self._queued_notifications.empty(): try: # _log.debug("read next notification") n = _base.read(ihandle, _EVENT_READ_TIMEOUT) except _base.NoReceiver: _log.warning("receiver disconnected") self.receiver.close() break if n: n = _base.make_notification(*n) else: # deliver any queued notifications n = self._queued_notifications.get() if n: # if _log.isEnabledFor(_DEBUG): # _log.debug("%s: processing %s", self.receiver, n) try: self._notifications_callback(n) except: _log.exception("processing %s", n) elif self.tick_period: idle_reads += 1 if idle_reads % _IDLE_READS == 0: idle_reads = 0 now = _timestamp() if now - last_tick >= self.tick_period: last_tick = now self.tick(now) del self._queued_notifications self.has_stopped()
def strftime(self, format, _decrease_gmtoff_from_secs=False): """Return a string representing the date and time, controlled by an explicit format string. .. Note :: To show correct value for some formatting specials, e.g.'%s', libjalali's :func:`jstrftime` needs timezone informations filled in :attr:`.jtm.tm_gmtoff` (`issue 4`_) which we could not depend on here, since naive datetime objects have zero knowledge about it. Storing these timezone information in a naive datetime object, make datetime implementation heavily depended on :func:`jmktime` which have several issues itself. Additionally it makes :class:`~pyjalali.datetime.datetime` less like :class:`python:datetime.datetime` since it should store more information and change method signatures. For those directives, a possible workaround is to change :attr:`.jtm` by detecting local zone and putting its effect in other field values, but this makes other directives (hour, minute, etc) incorrect. You can use this workaround by passing True as second argument. .. _issue 4: https://github.com/ashkang/jcal/issues/4 """ self.__date._compute_yday_wday_if_necessary() if self.tzinfo is not None: njtm = self.__jtm.copy() njtm.tm_gmtoff = int(self.utcoffset().total_seconds()) njtm.tm_zone = self.tzname() if _decrease_gmtoff_from_secs: njtm.tm_sec += (njtm.tm_gmtoff - jlocaltime(int(_timestamp())).tm_gmtoff) return jstrftime(format, njtm) if _decrease_gmtoff_from_secs: return jstrftime(format, self.__localtime_offset_decreased_jtm()) return jstrftime(format, self.jtm)
def _scroll(ind, _ignore, direction): if direction != ScrollDirection.UP and direction != ScrollDirection.DOWN: # ignore all other directions return if len(_devices_info) < 4: # don't bother with scrolling when there's only one receiver # with only one device (3 = [receiver, device, separator]) return # scroll events come way too fast (at least 5-6 at once) # so take a little break between them global _last_scroll now = _timestamp() if now - _last_scroll < 0.33: # seconds return _last_scroll = now # if _log.isEnabledFor(_DEBUG): # _log.debug("scroll direction %s", direction) global _picked_device candidate = None if _picked_device is None: for info in _devices_info: # pick first peripheral found if info[1] is not None: candidate = info break else: found = False for info in _devices_info: if not info[1]: # only conside peripherals continue # compare peripherals if info[0:2] == _picked_device[0:2]: if direction == ScrollDirection.UP and candidate: # select previous device break found = True else: if found: candidate = info if direction == ScrollDirection.DOWN: break # if direction is up, but no candidate found before _picked, # let it run through all candidates, will get stuck with the last one else: if direction == ScrollDirection.DOWN: # only use the first one, in case no candidates are after _picked if candidate is None: candidate = info else: candidate = info # if the last _picked_device is gone, clear it # the candidate will be either the first or last one remaining, # depending on the scroll direction if not found: _picked_device = None _picked_device = candidate or _picked_device if _log.isEnabledFor(_DEBUG): _log.debug("scroll: picked %s", _picked_device) _update_tray_icon()
def request(handle, devnumber, request_id, *params, no_reply=False, return_error=False, long_message=False): """Makes a feature call to a device and waits for a matching reply. :param handle: an open UR handle. :param devnumber: attached device number. :param request_id: a 16-bit integer. :param params: parameters for the feature call, 3 to 16 bytes. :returns: the reply data, or ``None`` if some error occurred. or no reply expected """ # import inspect as _inspect # print ('\n '.join(str(s) for s in _inspect.stack())) assert isinstance(request_id, int) if devnumber != 0xFF and request_id < 0x8000: # For HID++ 2.0 feature requests, randomize the SoftwareId to make it # easier to recognize the reply for this request. also, always set the # most significant bit (8) in SoftwareId, to make notifications easier # to distinguish from request replies. # This only applies to peripheral requests, ofc. request_id = (request_id & 0xFFF0) | 0x08 | _random_bits(3) timeout = _RECEIVER_REQUEST_TIMEOUT if devnumber == 0xFF else _DEVICE_REQUEST_TIMEOUT # be extra patient on long register read if request_id & 0xFF00 == 0x8300: timeout *= 2 if params: params = b''.join(_pack('B', p) if isinstance(p, int) else p for p in params) else: params = b'' # if _log.isEnabledFor(_DEBUG): # _log.debug("(%s) device %d request_id {%04X} params [%s]", handle, devnumber, request_id, _strhex(params)) request_data = _pack('!H', request_id) + params ihandle = int(handle) notifications_hook = getattr(handle, 'notifications_hook', None) _skip_incoming(handle, ihandle, notifications_hook) write(ihandle, devnumber, request_data, long_message) if no_reply: return None # we consider timeout from this point request_started = _timestamp() delta = 0 while delta < timeout: reply = _read(handle, timeout) if reply: report_id, reply_devnumber, reply_data = reply if reply_devnumber == devnumber: if report_id == 0x10 and reply_data[:1] == b'\x8F' and reply_data[1:3] == request_data[:2]: error = ord(reply_data[3:4]) # if error == _hidpp10.ERROR.resource_error: # device unreachable # _log.warn("(%s) device %d error on request {%04X}: unknown device", handle, devnumber, request_id) # raise DeviceUnreachable(number=devnumber, request=request_id) # if error == _hidpp10.ERROR.unknown_device: # unknown device # _log.error("(%s) device %d error on request {%04X}: unknown device", handle, devnumber, request_id) # raise NoSuchDevice(number=devnumber, request=request_id) if _log.isEnabledFor(_DEBUG): _log.debug( '(%s) device 0x%02X error on request {%04X}: %d = %s', handle, devnumber, request_id, error, _hidpp10.ERROR[error] ) return _hidpp10.ERROR[error] if return_error else None if reply_data[:1] == b'\xFF' and reply_data[1:3] == request_data[:2]: # a HID++ 2.0 feature call returned with an error error = ord(reply_data[3:4]) _log.error( '(%s) device %d error on feature request {%04X}: %d = %s', handle, devnumber, request_id, error, _hidpp20.ERROR[error] ) raise _hidpp20.FeatureCallError(number=devnumber, request=request_id, error=error, params=params) if reply_data[:2] == request_data[:2]: if request_id & 0xFE00 == 0x8200: # long registry r/w should return a long reply assert report_id == 0x11 elif request_id & 0xFE00 == 0x8000: # short registry r/w should return a short reply assert report_id == 0x10 if devnumber == 0xFF: if request_id == 0x83B5 or request_id == 0x81F1: # these replies have to match the first parameter as well if reply_data[2:3] == params[:1]: return reply_data[2:] else: # hm, not matching my request, and certainly not a notification continue else: return reply_data[2:] else: return reply_data[2:] else: # a reply was received, but did not match our request in any way # reset the timeout starting point request_started = _timestamp() if notifications_hook: n = make_notification(reply_devnumber, reply_data) if n: notifications_hook(n) # elif _log.isEnabledFor(_DEBUG): # _log.debug("(%s) ignoring reply %02X [%s]", handle, reply_devnumber, _strhex(reply_data)) # elif _log.isEnabledFor(_DEBUG): # _log.debug("(%s) ignoring reply %02X [%s]", handle, reply_devnumber, _strhex(reply_data)) delta = _timestamp() - request_started # if _log.isEnabledFor(_DEBUG): # _log.debug("(%s) still waiting for reply, delta %f", handle, delta) _log.warn( 'timeout (%0.2f/%0.2f) on device %d request {%04X} params [%s]', delta, timeout, devnumber, request_id, _strhex(params) )
def ping(handle, devnumber, long_message=False): """Check if a device is connected to the receiver. :returns: The HID protocol supported by the device, as a floating point number, if the device is active. """ if _log.isEnabledFor(_DEBUG): _log.debug('(%s) pinging device %d', handle, devnumber) # import inspect as _inspect # print ('\n '.join(str(s) for s in _inspect.stack())) assert devnumber != 0xFF assert devnumber >= 0x00 assert devnumber < 0x0F # randomize the SoftwareId and mark byte to be able to identify the ping # reply, and set most significant (0x8) bit in SoftwareId so that the reply # is always distinguishable from notifications request_id = 0x0018 | _random_bits(3) request_data = _pack('!HBBB', request_id, 0, 0, _random_bits(8)) ihandle = int(handle) notifications_hook = getattr(handle, 'notifications_hook', None) _skip_incoming(handle, ihandle, notifications_hook) write(ihandle, devnumber, request_data, long_message) # we consider timeout from this point request_started = _timestamp() delta = 0 while delta < _PING_TIMEOUT: reply = _read(handle, _PING_TIMEOUT) if reply: report_id, reply_devnumber, reply_data = reply if reply_devnumber == devnumber: if reply_data[:2] == request_data[:2] and reply_data[4:5] == request_data[-1:]: # HID++ 2.0+ device, currently connected return ord(reply_data[2:3]) + ord(reply_data[3:4]) / 10.0 if report_id == 0x10 and reply_data[:1] == b'\x8F' and reply_data[1:3] == request_data[:2]: assert reply_data[-1:] == b'\x00' error = ord(reply_data[3:4]) if error == _hidpp10.ERROR.invalid_SubID__command: # a valid reply from a HID++ 1.0 device return 1.0 if error == _hidpp10.ERROR.resource_error: # device unreachable return if error == _hidpp10.ERROR.unknown_device: # no paired device with that number _log.error('(%s) device %d error on ping request: unknown device', handle, devnumber) raise NoSuchDevice(number=devnumber, request=request_id) if notifications_hook: n = make_notification(reply_devnumber, reply_data) if n: notifications_hook(n) # elif _log.isEnabledFor(_DEBUG): # _log.debug("(%s) ignoring reply %02X [%s]", handle, reply_devnumber, _strhex(reply_data)) delta = _timestamp() - request_started _log.warn('(%s) timeout (%0.2f/%0.2f) on device %d ping', handle, delta, _PING_TIMEOUT, devnumber)
def run(receivers, args, find_receiver, _ignore): assert receivers if args.receiver: receiver_name = args.receiver.lower() receiver = find_receiver(receivers, receiver_name) if not receiver: raise Exception("no receiver found matching '%s'" % receiver_name) else: receiver = receivers[0] assert receiver receiver.status = _status.ReceiverStatus(receiver, lambda *args, **kwargs: None) # check if it's necessary to set the notification flags old_notification_flags = _hidpp10.get_notification_flags(receiver) or 0 if not (old_notification_flags & _hidpp10.NOTIFICATION_FLAG.wireless): _hidpp10.set_notification_flags( receiver, old_notification_flags | _hidpp10.NOTIFICATION_FLAG.wireless) # get all current devices known_devices = [dev.number for dev in receiver] class _HandleWithNotificationHook(int): def notifications_hook(self, n): nonlocal known_devices assert n if n.devnumber == 0xFF: _notifications.process(receiver, n) elif n.sub_id == 0x41 and len( n.data) == _base._SHORT_MESSAGE_SIZE - 4: kd, known_devices = known_devices, None # only process one connection notification if kd is not None: if n.devnumber not in kd: receiver.status.new_device = receiver.register_new_device( n.devnumber, n) elif receiver.re_pairs: del receiver[ n. devnumber] # get rid of information on device re-paired away receiver.status.new_device = receiver.register_new_device( n.devnumber, n) timeout = 30 # seconds receiver.handle = _HandleWithNotificationHook(receiver.handle) if receiver.receiver_kind == 'bolt': # Bolt receivers require authentication to pair a device receiver.discover(timeout=timeout) print( 'Bolt Pairing: long-press the pairing key or button on your device (timing out in', timeout, 'seconds).') pairing_start = _timestamp() patience = 5 # the discovering notification may come slightly later, so be patient while receiver.status.discovering or _timestamp( ) - pairing_start < patience: if receiver.status.device_address and receiver.status.device_authentication and receiver.status.device_name: break n = _base.read(receiver.handle) n = _base.make_notification(*n) if n else None if n: receiver.handle.notifications_hook(n) address = receiver.status.device_address name = receiver.status.device_name authentication = receiver.status.device_authentication kind = receiver.status.device_kind print(f'Bolt Pairing: discovered {name}') receiver.pair_device( address=address, authentication=authentication, entropy=20 if kind == _hidpp10.DEVICE_KIND.keyboard else 10) pairing_start = _timestamp() patience = 5 # the discovering notification may come slightly later, so be patient while receiver.status.lock_open or _timestamp( ) - pairing_start < patience: if receiver.status.device_passkey: break n = _base.read(receiver.handle) n = _base.make_notification(*n) if n else None if n: receiver.handle.notifications_hook(n) if authentication & 0x01: print( f'Bolt Pairing: type passkey {receiver.status.device_passkey} and then press the enter key' ) else: passkey = f'{int(receiver.status.device_passkey):010b}' passkey = ', '.join( ['right' if bit == '1' else 'left' for bit in passkey]) print(f'Bolt Pairing: press {passkey}') print('and then press left and right buttons simultaneously') while receiver.status.lock_open: n = _base.read(receiver.handle) n = _base.make_notification(*n) if n else None if n: receiver.handle.notifications_hook(n) else: receiver.set_lock(False, timeout=timeout) print('Pairing: turn your new device on (timing out in', timeout, 'seconds).') pairing_start = _timestamp() patience = 5 # the lock-open notification may come slightly later, wait for it a bit while receiver.status.lock_open or _timestamp( ) - pairing_start < patience: n = _base.read(receiver.handle) if n: n = _base.make_notification(*n) if n: receiver.handle.notifications_hook(n) if not (old_notification_flags & _hidpp10.NOTIFICATION_FLAG.wireless): # only clear the flags if they weren't set before, otherwise a # concurrently running Solaar app might stop working properly _hidpp10.set_notification_flags(receiver, old_notification_flags) if receiver.status.new_device: dev = receiver.status.new_device print('Paired device %d: %s (%s) [%s:%s]' % (dev.number, dev.name, dev.codename, dev.wpid, dev.serial)) else: error = receiver.status.get(_status.KEYS.ERROR) if error: raise Exception('pairing failed: %s' % error) else: print('Paired device') # this is better than an error
def request(handle, devnumber, request_id, *params): """Makes a feature call to a device and waits for a matching reply. This function will wait for a matching reply indefinitely. :param handle: an open UR handle. :param devnumber: attached device number. :param request_id: a 16-bit integer. :param params: parameters for the feature call, 3 to 16 bytes. :returns: the reply data, or ``None`` if some error occured. """ # import inspect as _inspect # print ('\n '.join(str(s) for s in _inspect.stack())) assert isinstance(request_id, int) if devnumber != 0xFF and request_id < 0x8000: # For HID++ 2.0 feature requests, randomize the SoftwareId to make it # easier to recognize the reply for this request. also, always set the # most significant bit (8) in SoftwareId, to make notifications easier # to distinguish from request replies. # This only applies to peripheral requests, ofc. request_id = (request_id & 0xFFF0) | 0x08 | _random_bits(3) timeout = _RECEIVER_REQUEST_TIMEOUT if devnumber == 0xFF else _DEVICE_REQUEST_TIMEOUT # be extra patient on long register read if request_id & 0xFF00 == 0x8300: timeout *= 2 if params: params = b''.join(_pack('B', p) if isinstance(p, int) else p for p in params) else: params = b'' # if _log.isEnabledFor(_DEBUG): # _log.debug("(%s) device %d request_id {%04X} params [%s]", handle, devnumber, request_id, _strhex(params)) request_data = _pack('!H', request_id) + params ihandle = int(handle) notifications_hook = getattr(handle, 'notifications_hook', None) _skip_incoming(handle, ihandle, notifications_hook) write(ihandle, devnumber, request_data) # we consider timeout from this point request_started = _timestamp() delta = 0 while delta < timeout: reply = _read(handle, timeout) if reply: report_id, reply_devnumber, reply_data = reply if reply_devnumber == devnumber: if report_id == 0x10 and reply_data[:1] == b'\x8F' and reply_data[1:3] == request_data[:2]: error = ord(reply_data[3:4]) # if error == _hidpp10.ERROR.resource_error: # device unreachable # _log.warn("(%s) device %d error on request {%04X}: unknown device", handle, devnumber, request_id) # raise DeviceUnreachable(number=devnumber, request=request_id) # if error == _hidpp10.ERROR.unknown_device: # unknown device # _log.error("(%s) device %d error on request {%04X}: unknown device", handle, devnumber, request_id) # raise NoSuchDevice(number=devnumber, request=request_id) if _log.isEnabledFor(_DEBUG): _log.debug("(%s) device 0x%02X error on request {%04X}: %d = %s", handle, devnumber, request_id, error, _hidpp10.ERROR[error]) return if reply_data[:1] == b'\xFF' and reply_data[1:3] == request_data[:2]: # a HID++ 2.0 feature call returned with an error error = ord(reply_data[3:4]) _log.error("(%s) device %d error on feature request {%04X}: %d = %s", handle, devnumber, request_id, error, _hidpp20.ERROR[error]) raise _hidpp20.FeatureCallError(number=devnumber, request=request_id, error=error, params=params) if reply_data[:2] == request_data[:2]: if request_id & 0xFE00 == 0x8200: # long registry r/w should return a long reply assert report_id == 0x11 elif request_id & 0xFE00 == 0x8000: # short registry r/w should return a short reply assert report_id == 0x10 if devnumber == 0xFF: if request_id == 0x83B5 or request_id == 0x81F1: # these replies have to match the first parameter as well if reply_data[2:3] == params[:1]: return reply_data[2:] else: # hm, not mathing my request, and certainly not a notification continue else: return reply_data[2:] else: return reply_data[2:] else: # a reply was received, but did not match our request in any way # reset the timeout starting point request_started = _timestamp() if notifications_hook: n = make_notification(reply_devnumber, reply_data) if n: notifications_hook(n) # elif _log.isEnabledFor(_DEBUG): # _log.debug("(%s) ignoring reply %02X [%s]", handle, reply_devnumber, _strhex(reply_data)) # elif _log.isEnabledFor(_DEBUG): # _log.debug("(%s) ignoring reply %02X [%s]", handle, reply_devnumber, _strhex(reply_data)) delta = _timestamp() - request_started # if _log.isEnabledFor(_DEBUG): # _log.debug("(%s) still waiting for reply, delta %f", handle, delta) _log.warn("timeout (%0.2f/%0.2f) on device %d request {%04X} params [%s]", delta, timeout, devnumber, request_id, _strhex(params))
def _scroll(tray_icon, event, direction=None): if direction is None: direction = event.direction now = event.time / 1000.0 else: now = None if direction != ScrollDirection.UP and direction != ScrollDirection.DOWN: # ignore all other directions return if sum(map(lambda i: i[1] is not None, _devices_info)) < 2: # don't bother even trying to scroll if less than two devices return # scroll events come way too fast (at least 5-6 at once) # so take a little break between them global _last_scroll now = now or _timestamp() if now - _last_scroll < 0.33: # seconds return _last_scroll = now # if _log.isEnabledFor(_DEBUG): # _log.debug("scroll direction %s", direction) global _picked_device candidate = None if _picked_device is None: for info in _devices_info: # pick first peripheral found if info[1] is not None: candidate = info break else: found = False for info in _devices_info: if not info[1]: # only conside peripherals continue # compare peripherals if info[0:2] == _picked_device[0:2]: if direction == ScrollDirection.UP and candidate: # select previous device break found = True else: if found: candidate = info if direction == ScrollDirection.DOWN: break # if direction is up, but no candidate found before _picked, # let it run through all candidates, will get stuck with the last one else: if direction == ScrollDirection.DOWN: # only use the first one, in case no candidates are after _picked if candidate is None: candidate = info else: candidate = info # if the last _picked_device is gone, clear it # the candidate will be either the first or last one remaining, # depending on the scroll direction if not found: _picked_device = None _picked_device = candidate or _picked_device if _log.isEnabledFor(_DEBUG): _log.debug('scroll: picked %s', _picked_device) _update_tray_icon()
def changed(self, active=None, alert=ALERT.NONE, reason=None, push=False, timestamp=None): assert self._changed_callback d = self._device # assert d # may be invalid when processing the 'unpaired' notification timestamp = timestamp or _timestamp() _log.info( f"tjones: changed: active = {self._active} -> {active} ... reason = [{reason}]" ) if active is not None: d.online = active was_active, self._active = self._active, active if active: # If the device went to sleep we may not have been notified. As the device will # still be marked as active we need to also check if it's just been woken up to see # if we need to re-apply settings. if not was_active or reason == _('powered on'): #if not was_active: # Make sure to set notification flags on the device, they # get cleared when the device is turned off (but not when the device # goes idle, and we can't tell the difference right now). if d.protocol < 2.0: self[ KEYS. NOTIFICATION_FLAGS] = d.enable_connection_notifications( ) # battery information may have changed so try to read it now self.read_battery(timestamp) # Push settings for new devices (was_active is None), # when devices request software reconfiguration # and when devices become active if they don't have wireless device status feature, if was_active is None or push or not was_active and ( not d.features or _hidpp20.FEATURE.WIRELESS_DEVICE_STATUS not in d.features): if _log.isEnabledFor(_INFO): _log.info('%s pushing device settings %s', d, d.settings) _settings.apply_all_settings(d) else: if was_active: battery = self.get(KEYS.BATTERY_LEVEL) self.clear() # If we had a known battery level before, assume it's not going # to change much while the device is offline. if battery is not None: self[KEYS.BATTERY_LEVEL] = battery # A device that is not active on the first status notification # but becomes active afterwards does not produce a pop-up notification # so don't produce one here. This cuts off pop-ups when Solaar starts, # which can be problematic if Solaar is autostarted. ## if self.updated == 0 and active is True: ## if the device is active on the very first status notification, ## (meaning just when the program started or a new receiver was just ## detected), pop up a notification about it ## alert |= ALERT.NOTIFICATION self.updated = timestamp # if _log.isEnabledFor(_DEBUG): # _log.debug("device %d changed: active=%s %s", d.number, self._active, dict(self)) self._changed_callback(d, alert, reason)
def utcnow(): """See :meth:`.datetime.utcnow`.""" return datetime_from_ts(_timestamp(), False)
def _scroll(tray_icon, event, direction=None): if direction is None: direction = event.direction now = event.time / 1000.0 else: now = None if direction != ScrollDirection.UP and direction != ScrollDirection.DOWN: # ignore all other directions return if len(_devices_info) < 4: # don't bother with scrolling when there's only one receiver # with only one device (3 = [receiver, device, separator]) return # scroll events come way too fast (at least 5-6 at once) # so take a little break between them global _last_scroll now = now or _timestamp() if now - _last_scroll < 0.33: # seconds return _last_scroll = now # if _log.isEnabledFor(_DEBUG): # _log.debug("scroll direction %s", direction) global _picked_device candidate = None if _picked_device is None: for info in _devices_info: # pick first peripheral found if info[1] is not None: candidate = info break else: found = False for info in _devices_info: if not info[1]: # only conside peripherals continue # compare peripherals if info[0:2] == _picked_device[0:2]: if direction == ScrollDirection.UP and candidate: # select previous device break found = True else: if found: candidate = info if direction == ScrollDirection.DOWN: break # if direction is up, but no candidate found before _picked, # let it run through all candidates, will get stuck with the last one else: if direction == ScrollDirection.DOWN: # only use the first one, in case no candidates are after _picked if candidate is None: candidate = info else: candidate = info # if the last _picked_device is gone, clear it # the candidate will be either the first or last one remaining, # depending on the scroll direction if not found: _picked_device = None _picked_device = candidate or _picked_device if _log.isEnabledFor(_DEBUG): _log.debug("scroll: picked %s", _picked_device) _update_tray_icon()
def now(timezone=None): """See :meth:`.datetime.now`.""" return datetime_from_ts(_timestamp(), True, tz=timezone)
def today(self): """Return current local date""" return date.fromtimestamp(_timestamp())