From 3969472dd3ec18d1d72454431803aadcee3ceb97 Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Sat, 17 Apr 2021 08:46:46 -0400 Subject: [PATCH] settings: dpi sliding looks for suitable key --- lib/logitech_receiver/settings_templates.py | 91 ++++++++++++--------- lib/solaar/configuration.py | 11 +-- 2 files changed, 59 insertions(+), 43 deletions(-) diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index f5131a5d..0cdb13e8 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -364,26 +364,19 @@ def _feature_smart_shift_enhanced(): def _feature_dpi_sliding(): """Implements the ability to smoothly modify the DPI by sliding a mouse horizontally while holding the DPI button.""" - def _feature_dpi_sliding_callback(device): - # need _F.REPROG_CONTROLS_V4 feature and a DPI Switch that can send raw XY - # and _F.ADJUSTABLE_DPI so that the DPI can be adjusted - if _F.ADJUSTABLE_DPI in device.features: - key_index = device.keys.index(_special_keys.CONTROL.DPI_Switch) - if key_index is not None and 'raw XY' in device.keys[key_index].flags: - return _BooleanV() - class _DpiSlidingRW(object): def __init__(self): self.kind = _FeatureRW.kind # pretend to be FeatureRW as required for HID++ 2.0 devices - - def read(self, device): # need to return bytes, not a boolean - return b'0x01' if '_slidingDpiState' in device.__dict__ else b'0x00' + self.key = None class MxVerticalDpiState(object): - __slots__ = ('device', 'pressedCids', 'dpiSetting', 'dpiChoices', 'fsmState', 'otherDpiIdx', 'dx', 'movingDpiIdx') + __slots__ = ( + 'key', 'device', 'pressedCids', 'dpiSetting', 'dpiChoices', 'fsmState', 'otherDpiIdx', 'dx', 'movingDpiIdx' + ) - def __init__(self, device, dpiSetting): + def __init__(self, device, dpiSetting, key): self.device = device + self.key = key self.dpiSetting = dpiSetting self.dpiChoices = list(self.dpiSetting.choices) # Currently pressed/held control IDs @@ -407,17 +400,6 @@ def _feature_dpi_sliding(): we go into the `moved` state. When the button is released in this state the DPI is set according to the total displacement. - - release - +---------------------------------------------+ - |set DPI in current slot | - v moved | - +------+ press +---------+ enough +-------+ | - | idle |------>| pressed |------->| moved |------+ - +------+ +---------+ +-------+ - ^ release | ^ ^ - +----------------+ accumulate - switch DPI slots displacement ''' def setNewDpi(self, newDpiIdx): @@ -436,11 +418,11 @@ def _feature_dpi_sliding(): def handle_keys_event(self, cids): if self.fsmState == 'idle': - if _special_keys.CONTROL.DPI_Switch in cids: + if self.key in cids: self.fsmState = 'pressed' self.dx = 0. elif self.fsmState == 'pressed': - if _special_keys.CONTROL.DPI_Switch not in cids: + if self.key not in cids: # Swap with other DPI thisIdx = self.dpiChoices.index(self.dpiSetting.read()) newDpiIdx, self.otherDpiIdx = self.otherDpiIdx, thisIdx @@ -450,7 +432,7 @@ def _feature_dpi_sliding(): self.fsmState = 'idle' self.displayNewDpi(newDpiIdx) elif self.fsmState == 'moved': - if _special_keys.CONTROL.DPI_Switch not in cids: + if self.key not in cids: self.setNewDpi(self.movingDpiIdx) self.fsmState = 'idle' @@ -471,6 +453,9 @@ def _feature_dpi_sliding(): self.movingDpiIdx = newMovingDpiIdx self.displayNewDpi(newMovingDpiIdx) + def read(self, device): # need to return bytes, as if read from device + return _int2bytes(device._slidingDpiState.key, 2) if '_slidingDpiState' in device.__dict__ else b'\x00\x00' + def write(self, device, data_bytes): def handler(device, n): """Called on notification events from the mouse.""" @@ -483,29 +468,59 @@ def _feature_dpi_sliding(): dx, dy = _unpack('!hh', n.data[:4]) state.handle_move_event(dx, dy) - if bool(data_bytes): # Enable DPI sliding + key = _bytes2int(data_bytes) + if key: # Enable DPI sliding # Enable HID++ events on sliding the mouse while DPI button held - dpiSetting = next(filter(lambda s: s.name == _DPI[0], device.settings), None) - DPI_key_index = device.keys.index(_special_keys.CONTROL.DPI_Switch) - DPI_key = device.keys[DPI_key_index] if DPI_key_index is not None else None - if dpiSetting and DPI_key and 'raw XY' in DPI_key.flags: - DPI_key.set_rawXY_reporting(True) + self.key = next((k for k in device.keys if k.key == key), None) + if self.key: + self.key.set_rawXY_reporting(True) + divertSetting = next(filter(lambda s: s.name == _DIVERT_KEYS[0], device.settings), None) + divertSetting.write_key_value(int(self.key.key), 1) + from solaar.ui import status_changed as _status_changed + _status_changed(device, refresh=True) # update main window # Store our variables in the device object - device._slidingDpiState = self.MxVerticalDpiState(device, dpiSetting) + dpiSetting = next(filter(lambda s: s.name == _DPI[0], device.settings), None) + device._slidingDpiState = self.MxVerticalDpiState(device, dpiSetting, self.key.key) device.add_notification_handler('mx-vertical-dpi-handler', handler) + return True else: - _log.error('cannot enable DPI sliding on %s', device) + _log.error('cannot enable DPI sliding on %s for key %s', device, key) else: # Disable DPI sliding + if self.key: + self.key.set_rawXY_reporting(False) + divertSetting = next(filter(lambda s: s.name == _DIVERT_KEYS[0], device.settings), None) + divertSetting.write_key_value(int(self.key.key), 0) + from solaar.ui import status_changed as _status_changed + _status_changed(device, refresh=True) # update main window try: device.remove_notification_handler('mx-vertical-dpi-handler') - del device._slidingDpiState - DPI_key = device.keys[device.keys.index(_special_keys.CONTROL.DPI_Switch)] - DPI_key.set_rawXY_reporting(False) except Exception: if _log.isEnabledFor(_WARNING): _log.warn('cannot disable DPI sliding on %s', device) + if hasattr(device, '_slidingDpiState'): + del device._slidingDpiState return True + DPISlidingKeys = [_special_keys.CONTROL.DPI_Switch] + + def _feature_dpi_sliding_callback(device): + # need _F.REPROG_CONTROLS_V4 feature and a DPI Switch that can send raw XY + # and _F.ADJUSTABLE_DPI so that the DPI can be adjusted + if device.kind == _DK.mouse and _F.ADJUSTABLE_DPI in device.features: + keys = [] + for key in DPISlidingKeys: + key_index = device.keys.index(key) + dkey = device.keys[key_index] if key_index is not None else None + if dkey is not None and 'raw XY' in dkey.flags and 'divertable' in dkey.flags: + keys.append(dkey.key) + if not keys: # none of the keys designed for this, so look for any key with correct flags + for key in device.keys: + if 'raw XY' in key.flags and 'divertable' in key.flags and 'virtual' not in key.flags: + keys.append(key.key) + if keys: + keys.insert(0, _NamedInt(0, 'Off')) + return _ChoicesV(_NamedInts.list(keys), byte_count=2) + return _Setting(_DPI_SLIDING, _DpiSlidingRW(), callback=_feature_dpi_sliding_callback, device_kind=(_DK.mouse, )) diff --git a/lib/solaar/configuration.py b/lib/solaar/configuration.py index 1fd671be..dc8cfe70 100644 --- a/lib/solaar/configuration.py +++ b/lib/solaar/configuration.py @@ -101,12 +101,13 @@ def _cleanup(d): def _cleanup_load(d): - # remove boolean values for mouse-gestures - for device in d: + # remove boolean values for mouse-gesture and dpi-sliding + for device in d.values(): if isinstance(device, dict): - mg = device.get('mouse-gestures', None) - if mg is True or mg is False: - del device['mouse-gestures'] + for setting in ['mouse-gestures', 'dpi-sliding']: + mg = device.get(setting, None) + if mg is True or mg is False: + del device[setting] class _DeviceEntry(dict):