diff --git a/lib/logitech/devices/__init__.py b/lib/logitech/devices/__init__.py index 2300861a..faa7ee86 100644 --- a/lib/logitech/devices/__init__.py +++ b/lib/logitech/devices/__init__.py @@ -49,11 +49,13 @@ _REQUEST_STATUS_FUNCTIONS = _FDict() _REQUEST_STATUS_FUNCTIONS[k750.NAME] = k750.request_status def request_status(devinfo, listener): - return _REQUEST_STATUS_FUNCTIONS[devinfo.name](devinfo, listener) or default_request_status(devinfo, listener) or ping(devinfo, listener) + if listener: + return _REQUEST_STATUS_FUNCTIONS[devinfo.name](devinfo, listener) or default_request_status(devinfo, listener) or ping(devinfo, listener) _PROCESS_EVENT_FUNCTIONS = _FDict() _PROCESS_EVENT_FUNCTIONS[k750.NAME] = k750.process_event def process_event(devinfo, listener, data): - return default_process_event(devinfo, listener, data) or _PROCESS_EVENT_FUNCTIONS[devinfo.name](devinfo, listener, data) + if listener: + return default_process_event(devinfo, listener, data) or _PROCESS_EVENT_FUNCTIONS[devinfo.name](devinfo, listener, data) diff --git a/lib/logitech/devices/constants.py b/lib/logitech/devices/constants.py index 2d9da9dc..de48ad77 100644 --- a/lib/logitech/devices/constants.py +++ b/lib/logitech/devices/constants.py @@ -10,7 +10,7 @@ STATUS = type('STATUS', (), )) STATUS_NAME = { - STATUS.UNAVAILABLE: 'disconnected?', + STATUS.UNAVAILABLE: 'inactive', STATUS.CONNECTED: 'connected', } diff --git a/lib/logitech/devices/k750.py b/lib/logitech/devices/k750.py index f3eeec3f..0cb4ece2 100644 --- a/lib/logitech/devices/k750.py +++ b/lib/logitech/devices/k750.py @@ -48,9 +48,10 @@ def _charge_status(data, hasLux=False): def request_status(devinfo, listener): - reply = listener.request(_trigger_solar_charge_events, devinfo) - if reply is None: - return C.STATUS.UNAVAILABLE + if listener: + reply = listener.request(_trigger_solar_charge_events, devinfo) + if reply is None: + return C.STATUS.UNAVAILABLE def process_event(devinfo, listener, data): @@ -64,7 +65,7 @@ def process_event(devinfo, listener, data): if data[:2] == b'\x09\x20' and data[7:11] == b'GOOD': logging.debug("Solar key pressed") - if _trigger_solar_charge_events(listener.receiver, devinfo) is None: + if listener and _trigger_solar_charge_events(listener.receiver, devinfo) is None: return C.STATUS.UNAVAILABLE return _charge_status(data) diff --git a/lib/logitech/unifying_receiver/api.py b/lib/logitech/unifying_receiver/api.py index 408c0df1..ce1d44b2 100644 --- a/lib/logitech/unifying_receiver/api.py +++ b/lib/logitech/unifying_receiver/api.py @@ -17,7 +17,7 @@ from . import base as _base _LOG_LEVEL = 5 -_l = logging.getLogger('logitech.unifying_receiver.api') +_l = logging.getLogger('lur.api') # # @@ -86,47 +86,8 @@ def ping(handle, devnumber): not attached, None if no conclusive reply is received. """ - ping_marker = b'\xAA' - - def _status(reply): - if not reply: - return None - - reply_code, reply_devnumber, reply_data = reply - - if reply_devnumber != devnumber: - # oops - _l.log(_LOG_LEVEL, "(%d,%d) ping: reply for another device %d: %s", handle, devnumber, reply_devnumber, _hexlify(reply_data)) - _unhandled._publish(reply_code, reply_devnumber, reply_data) - return _status(_base.read(handle)) - - if (reply_code == 0x11 and reply_data[:2] == b'\x00\x10' and reply_data[4:5] == ping_marker): - # ping ok - _l.log(_LOG_LEVEL, "(%d,%d) ping: ok [%s]", handle, devnumber, _hexlify(reply_data)) - return True - - if (reply_code == 0x10 and reply_data[:2] == b'\x8F\x00'): - # ping failed - _l.log(_LOG_LEVEL, "(%d,%d) ping: device not present", handle, devnumber) - return False - - if (reply_code == 0x11 and reply_data[:2] == b'\x09\x00' and len(reply_data) == 18 and reply_data[7:11] == b'GOOD'): - # some devices may reply with a SOLAR_CHARGE event before the - # ping_ok reply, especially right after the device connected to the - # receiver - _l.log(_LOG_LEVEL, "(%d,%d) ping: solar status [%s]", handle, devnumber, _hexlify(reply_data)) - _unhandled._publish(reply_code, reply_devnumber, reply_data) - return _status(_base.read(handle)) - - # ugh - _l.log(_LOG_LEVEL, "(%d,%d) ping: unknown reply for this device: %d=[%s]", handle, devnumber, reply_code, _hexlify(reply_data)) - _unhandled._publish(reply_code, reply_devnumber, reply_data) - return None - - _l.log(_LOG_LEVEL, "(%d,%d) pinging", handle, devnumber) - _base.write(handle, devnumber, b'\x00\x10\x00\x00' + ping_marker) - # pings may take a while to reply success - return _status(_base.read(handle, _base.DEFAULT_TIMEOUT * 3)) + reply = _base.request(handle, devnumber, b'\x00\x10', b'\x00\x00\xAA') + return reply is not None and reply[2:3] == b'\xAA' def find_device_by_name(handle, device_name): @@ -195,20 +156,21 @@ def get_feature_index(handle, devnumber, feature): feature_index = ord(reply[0:1]) if feature_index: feature_flags = ord(reply[1:2]) & 0xE0 - _l.log(_LOG_LEVEL, "(%d,%d) feature <%s:%s> has index %d flags %02x", handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature], feature_index, feature_flags) - if feature_flags == 0: - return feature_index + if _l.isEnabledFor(_LOG_LEVEL): + if feature_flags: + _l.log(_LOG_LEVEL, "(%d,%d) feature <%s:%s> has index %d: %s", + handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature], feature_index, + ','.join([C.FEATURE_FLAGS[k] for k in C.FEATURE_FLAGS if feature_flags & k])) + else: + _l.log(_LOG_LEVEL, "(%d,%d) feature <%s:%s> has index %d", handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature], feature_index) - if feature_flags & 0x80: - _l.warn("(%d,%d) feature <%s:%s> not supported: obsolete", handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature]) - if feature_flags & 0x40: - _l.warn("(%d,%d) feature <%s:%s> not supported: hidden", handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature]) - if feature_flags & 0x20: - _l.warn("(%d,%d) feature <%s:%s> not supported: Logitech internal", handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature]) - raise E.FeatureNotSupported(devnumber, feature) - else: - _l.warn("(%d,%d) feature <%s:%s> not supported by the device", handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature]) - raise E.FeatureNotSupported(devnumber, feature) + # if feature_flags: + # raise E.FeatureNotSupported(devnumber, feature) + + return feature_index + + _l.warn("(%d,%d) feature <%s:%s> not supported by the device", handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature]) + raise E.FeatureNotSupported(devnumber, feature) def get_device_features(handle, devnumber): @@ -246,9 +208,17 @@ def get_device_features(handle, devnumber): # for each index, get the feature residing at that index feature = _base.request(handle, devnumber, fs_index + b'\x10', _pack('!B', index)) if feature: + feature_flags = ord(feature[2:3]) & 0xE0 feature = feature[0:2].upper() features[index] = feature - _l.log(_LOG_LEVEL, "(%d,%d) feature <%s:%s> at index %d", handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature], index) + + if _l.isEnabledFor(_LOG_LEVEL): + if feature_flags: + _l.log(_LOG_LEVEL, "(%d,%d) feature <%s:%s> at index %d: %s", + handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature], index, + ','.join([C.FEATURE_FLAGS[k] for k in C.FEATURE_FLAGS if feature_flags & k])) + else: + _l.log(_LOG_LEVEL, "(%d,%d) feature <%s:%s> at index %d", handle, devnumber, _hexlify(feature), C.FEATURE_NAME[feature], index) features[0] = C.FEATURE.ROOT while features[-1] is None: @@ -278,11 +248,8 @@ def get_device_firmware(handle, devnumber, features_array=None): fw_type = C.FIRMWARE_TYPE[fw_level] name, = _unpack('!3s', fw_info[1:4]) name = name.decode('ascii') - version = ( chr(0x30 + (ord(fw_info[4:5]) >> 4)) + - chr(0x30 + (ord(fw_info[4:5]) & 0x0F)) + - '.' + - chr(0x30 + (ord(fw_info[5:6]) >> 4)) + - chr(0x30 + (ord(fw_info[5:6]) & 0x0F))) + version = _hexlify(fw_info[4:6]) + version = '%s.%s' % (version[0:2], version[2:4]) build, = _unpack('!H', fw_info[6:8]) extras = fw_info[9:].rstrip(b'\x00') if extras: @@ -327,8 +294,11 @@ def get_device_name(handle, devnumber, features_array=None): while len(d_name) < name_length: name_index = _pack('!B', len(d_name)) name_fragment = request(handle, devnumber, C.FEATURE.NAME, function=b'\x10', params=name_index, features_array=features_array) - name_fragment = name_fragment[:name_length - len(d_name)] - d_name += name_fragment + if name_fragment: + name_fragment = name_fragment[:name_length - len(d_name)] + d_name += name_fragment + else: + break d_name = d_name.decode('ascii') _l.log(_LOG_LEVEL, "(%d,%d) device name %s", handle, devnumber, d_name) diff --git a/lib/logitech/unifying_receiver/constants.py b/lib/logitech/unifying_receiver/constants.py index 7429a1cb..f7c6c9dd 100644 --- a/lib/logitech/unifying_receiver/constants.py +++ b/lib/logitech/unifying_receiver/constants.py @@ -45,6 +45,9 @@ FEATURE_NAME[FEATURE.WIRELESS] = 'WIRELESS' FEATURE_NAME[FEATURE.SOLAR_CHARGE] = 'SOLAR_CHARGE' +FEATURE_FLAGS = { 0x20: 'internal', 0x40: 'hidden', 0x80: 'obsolete' } + + _DEVICE_TYPES = ('Keyboard', 'Remote Control', 'NUMPAD', 'Mouse', 'Touchpad', 'Trackball', 'Presenter', 'Receiver') diff --git a/lib/logitech/unifying_receiver/listener.py b/lib/logitech/unifying_receiver/listener.py index 6e066258..d6450878 100644 --- a/lib/logitech/unifying_receiver/listener.py +++ b/lib/logitech/unifying_receiver/listener.py @@ -5,7 +5,7 @@ import logging import threading from time import sleep as _sleep -# from binascii import hexlify as _hexlify +from binascii import hexlify as _hexlify from . import base as _base from . import exceptions as E @@ -13,11 +13,11 @@ from . import exceptions as E _LOG_LEVEL = 6 -_l = logging.getLogger('logitech.unifying_receiver.listener') +_l = logging.getLogger('lur.listener') -_READ_EVENT_TIMEOUT = 90 # ms -_IDLE_SLEEP = 950 # ms +_READ_EVENT_TIMEOUT = int(_base.DEFAULT_TIMEOUT * 0.1) # ms +_IDLE_SLEEP = int(_base.DEFAULT_TIMEOUT * 0.9) # ms class EventsListener(threading.Thread): @@ -64,7 +64,8 @@ class EventsListener(threading.Thread): if self.active: if event: - _l.log(_LOG_LEVEL, "(%d) got event %s", self.receiver, event) + if _l.isEnabledFor(_LOG_LEVEL): + _l.log(_LOG_LEVEL, "(%d) got event (%02x %02x [%s])", self.receiver, event[0], event[1], _hexlify(event[2])) self.callback.__call__(*event) elif self.task is None: # _l.log(_LOG_LEVEL, "(%d) idle sleep", self.receiver) @@ -86,7 +87,8 @@ class EventsListener(threading.Thread): The api_function will get the receiver handle as a first agument, all other args and kwargs will follow. """ - # _l.log(_LOG_LEVEL, "(%d) request '%s.%s' with %s, %s", self.receiver, api_function.__module__, api_function.__name__, args, kwargs) + # if _l.isEnabledFor(_LOG_LEVEL): + # _l.log(_LOG_LEVEL, "(%d) request '%s.%s' with %s, %s", self.receiver, api_function.__module__, api_function.__name__, args, kwargs) self.task_processing.acquire() self.task_done.clear() self.task = (api_function, args, kwargs) @@ -96,13 +98,15 @@ class EventsListener(threading.Thread): self.task = self.task_reply = None self.task_processing.release() - # _l.log(_LOG_LEVEL, "(%d) request '%s.%s' => [%s]", self.receiver, api_function.__module__, api_function.__name__, _hexlify(reply)) + # if _l.isEnabledFor(_LOG_LEVEL): + # _l.log(_LOG_LEVEL, "(%d) request '%s.%s' => [%s]", self.receiver, api_function.__module__, api_function.__name__, _hexlify(reply)) if isinstance(reply, Exception): raise reply return reply def _make_request(self, api_function, args, kwargs): - _l.log(_LOG_LEVEL, "(%d) calling '%s.%s' with %s, %s", self.receiver, api_function.__module__, api_function.__name__, args, kwargs) + if _l.isEnabledFor(_LOG_LEVEL): + _l.log(_LOG_LEVEL, "(%d) calling '%s.%s' with %s, %s", self.receiver, api_function.__module__, api_function.__name__, args, kwargs) try: return api_function.__call__(self.receiver, *args, **kwargs) except E.NoReceiver as nr: diff --git a/lib/logitech/unifying_receiver/unhandled.py b/lib/logitech/unifying_receiver/unhandled.py index 2facbbad..7393387d 100644 --- a/lib/logitech/unifying_receiver/unhandled.py +++ b/lib/logitech/unifying_receiver/unhandled.py @@ -9,7 +9,7 @@ from binascii import hexlify as _hexlify def _logdebug_hook(reply_code, devnumber, data): """Default unhandled hook, logs the reply as DEBUG.""" - _l = logging.getLogger('logitech.unifying_receiver.unhandled') + _l = logging.getLogger('lur.unhandled') _l.debug("UNHANDLED (,%d) code 0x%02x data [%s]", devnumber, reply_code, _hexlify(data)) diff --git a/solaar.py b/solaar.py index 566c7a8a..49736058 100644 --- a/solaar.py +++ b/solaar.py @@ -16,7 +16,7 @@ if __name__ == '__main__': arg_parser.add_argument('-H', '--start-hidden', action='store_true', dest='start_hidden', help='hide the application window on start') arg_parser.add_argument('-t', '--close-to-tray', action='store_true', - help='closing the application window hides it') + help='closing the application window hides it instead of terminating the application') arg_parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__) args = arg_parser.parse_args()