settings: dpi sliding looks for suitable key

This commit is contained in:
Peter F. Patel-Schneider 2021-04-17 08:46:46 -04:00
parent 90bae7db84
commit 3969472dd3
2 changed files with 59 additions and 43 deletions

View File

@ -364,26 +364,19 @@ def _feature_smart_shift_enhanced():
def _feature_dpi_sliding(): def _feature_dpi_sliding():
"""Implements the ability to smoothly modify the DPI """Implements the ability to smoothly modify the DPI
by sliding a mouse horizontally while holding the DPI button.""" 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): class _DpiSlidingRW(object):
def __init__(self): def __init__(self):
self.kind = _FeatureRW.kind # pretend to be FeatureRW as required for HID++ 2.0 devices self.kind = _FeatureRW.kind # pretend to be FeatureRW as required for HID++ 2.0 devices
self.key = None
def read(self, device): # need to return bytes, not a boolean
return b'0x01' if '_slidingDpiState' in device.__dict__ else b'0x00'
class MxVerticalDpiState(object): 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.device = device
self.key = key
self.dpiSetting = dpiSetting self.dpiSetting = dpiSetting
self.dpiChoices = list(self.dpiSetting.choices) self.dpiChoices = list(self.dpiSetting.choices)
# Currently pressed/held control IDs # Currently pressed/held control IDs
@ -407,17 +400,6 @@ def _feature_dpi_sliding():
we go into the `moved` state. we go into the `moved` state.
When the button is released in this state When the button is released in this state
the DPI is set according to the total displacement. 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): def setNewDpi(self, newDpiIdx):
@ -436,11 +418,11 @@ def _feature_dpi_sliding():
def handle_keys_event(self, cids): def handle_keys_event(self, cids):
if self.fsmState == 'idle': if self.fsmState == 'idle':
if _special_keys.CONTROL.DPI_Switch in cids: if self.key in cids:
self.fsmState = 'pressed' self.fsmState = 'pressed'
self.dx = 0. self.dx = 0.
elif self.fsmState == 'pressed': elif self.fsmState == 'pressed':
if _special_keys.CONTROL.DPI_Switch not in cids: if self.key not in cids:
# Swap with other DPI # Swap with other DPI
thisIdx = self.dpiChoices.index(self.dpiSetting.read()) thisIdx = self.dpiChoices.index(self.dpiSetting.read())
newDpiIdx, self.otherDpiIdx = self.otherDpiIdx, thisIdx newDpiIdx, self.otherDpiIdx = self.otherDpiIdx, thisIdx
@ -450,7 +432,7 @@ def _feature_dpi_sliding():
self.fsmState = 'idle' self.fsmState = 'idle'
self.displayNewDpi(newDpiIdx) self.displayNewDpi(newDpiIdx)
elif self.fsmState == 'moved': elif self.fsmState == 'moved':
if _special_keys.CONTROL.DPI_Switch not in cids: if self.key not in cids:
self.setNewDpi(self.movingDpiIdx) self.setNewDpi(self.movingDpiIdx)
self.fsmState = 'idle' self.fsmState = 'idle'
@ -471,6 +453,9 @@ def _feature_dpi_sliding():
self.movingDpiIdx = newMovingDpiIdx self.movingDpiIdx = newMovingDpiIdx
self.displayNewDpi(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 write(self, device, data_bytes):
def handler(device, n): def handler(device, n):
"""Called on notification events from the mouse.""" """Called on notification events from the mouse."""
@ -483,29 +468,59 @@ def _feature_dpi_sliding():
dx, dy = _unpack('!hh', n.data[:4]) dx, dy = _unpack('!hh', n.data[:4])
state.handle_move_event(dx, dy) 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 # Enable HID++ events on sliding the mouse while DPI button held
dpiSetting = next(filter(lambda s: s.name == _DPI[0], device.settings), None) self.key = next((k for k in device.keys if k.key == key), None)
DPI_key_index = device.keys.index(_special_keys.CONTROL.DPI_Switch) if self.key:
DPI_key = device.keys[DPI_key_index] if DPI_key_index is not None else None self.key.set_rawXY_reporting(True)
if dpiSetting and DPI_key and 'raw XY' in DPI_key.flags: divertSetting = next(filter(lambda s: s.name == _DIVERT_KEYS[0], device.settings), None)
DPI_key.set_rawXY_reporting(True) 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 # 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) device.add_notification_handler('mx-vertical-dpi-handler', handler)
return True
else: 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 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: try:
device.remove_notification_handler('mx-vertical-dpi-handler') 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: except Exception:
if _log.isEnabledFor(_WARNING): if _log.isEnabledFor(_WARNING):
_log.warn('cannot disable DPI sliding on %s', device) _log.warn('cannot disable DPI sliding on %s', device)
if hasattr(device, '_slidingDpiState'):
del device._slidingDpiState
return True 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, )) return _Setting(_DPI_SLIDING, _DpiSlidingRW(), callback=_feature_dpi_sliding_callback, device_kind=(_DK.mouse, ))

View File

@ -101,12 +101,13 @@ def _cleanup(d):
def _cleanup_load(d): def _cleanup_load(d):
# remove boolean values for mouse-gestures # remove boolean values for mouse-gesture and dpi-sliding
for device in d: for device in d.values():
if isinstance(device, dict): if isinstance(device, dict):
mg = device.get('mouse-gestures', None) for setting in ['mouse-gestures', 'dpi-sliding']:
mg = device.get(setting, None)
if mg is True or mg is False: if mg is True or mg is False:
del device['mouse-gestures'] del device[setting]
class _DeviceEntry(dict): class _DeviceEntry(dict):