From 14c1eac79c39be76fbf1543e24323b65c96eaad3 Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Sun, 26 Sep 2021 22:16:21 -0400 Subject: [PATCH] settings: add setting for DPI CHANGE button to switch Sensitivity --- lib/logitech_receiver/settings.py | 67 +++++++++++++++++++++ lib/logitech_receiver/settings_templates.py | 37 ++++++++++++ 2 files changed, 104 insertions(+) diff --git a/lib/logitech_receiver/settings.py b/lib/logitech_receiver/settings.py index 61e3997c..03e4d503 100644 --- a/lib/logitech_receiver/settings.py +++ b/lib/logitech_receiver/settings.py @@ -23,6 +23,7 @@ import math from copy import copy as _copy from logging import DEBUG as _DEBUG +from logging import WARNING as _WARNING from logging import getLogger ## use regular time instead of time_ns so as not to require Python 3.7 # from time import time_ns as _time_ns @@ -1048,6 +1049,72 @@ class MultipleRangeValidator: return w + b'\xFF' +class ActionSettingRW(object): + """Special RW class for settings that turn on and off special processing when a key or button is depressed""" + def __init__(self, name, divert_setting_name): + self.name = name + self.divert_setting_name = divert_setting_name + self.kind = FeatureRW.kind # pretend to be FeatureRW as required for HID++ 2.0 devices + self.device = None + self.key = None + self.active = False + self.pressed = False + + def press_action(self): # action to take when key is pressed + pass + + def release_action(self): # action to take when key is released + pass + + def move_action(self, dx, dy): # action to take when mouse is moved while key is down + pass + + def read(self, device): # need to return bytes, as if read from device + return _int2bytes(self.key, 2) if self.active else b'\x00\x00' + + def write(self, device, data_bytes): + def handler(device, n): # Called on notification events from the device + if n.sub_id < 0x40 and device.features[n.sub_id] == _hidpp20.FEATURE.REPROG_CONTROLS_V4: + if n.address == 0x00: + cids = _unpack('!HHHH', n.data[:8]) + if not self.pressed and int(self.key.key) in cids: + self.pressed = True + self.press_action() + elif self.pressed and int(self.key.key) not in cids: + self.pressed = False + self.release_action() + elif n.address == 0x10: + if self.pressed: + dx, dy = _unpack('!hh', n.data[:4]) + self.move_action(dx, dy) + + divertSetting = next(filter(lambda s: s.name == self.divert_setting_name, device.settings), None) + self.device = device + key = _bytes2int(data_bytes) + if key: # Enable + self.key = next((k for k in device.keys if k.key == key), None) + if self.key: + self.active = True + divertSetting.write_key_value(int(self.key.key), 1) + device.add_notification_handler(self.name, handler) + from solaar.ui import status_changed as _status_changed + _status_changed(device, refresh=True) # update main window + else: + _log.error('cannot enable %s on %s for key %s', self.name, device, key) + else: # Disable + if self.active: + self.active = False + 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(self.name) + except Exception: + if _log.isEnabledFor(_WARNING): + _log.warn('cannot disable %s on %s', self.name, device) + return True + + # Turn diverted mouse movement events into a mouse gesture # # Uses the following FSM. diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index 0e0c7a39..1dde7fd3 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -35,6 +35,7 @@ from .common import bytes2int as _bytes2int from .common import int2bytes as _int2bytes from .common import unpack as _unpack from .i18n import _ +from .settings import ActionSettingRW as _ActionSettingRW from .settings import BitFieldSetting as _BitFieldSetting from .settings import BitFieldValidator as _BitFieldV from .settings import BitFieldWithOffsetAndMaskSetting as _BitFieldOMSetting @@ -118,6 +119,9 @@ _DIVERT_CROWN = ('divert-crown', _('Divert crown events'), _('Make crown send CROWN HID++ notifications (which trigger Solaar rules but are otherwise ignored).')) _DIVERT_GKEYS = ('divert-gkeys', _('Divert G Keys'), _('Make G keys send GKEY HID++ notifications (which trigger Solaar rules but are otherwise ignored).')) +_SPEED_CHANGE = ('speed-change', _('Sensitivity Switching'), + _('Switch the current sensitivity and the remembered sensitivity when the key or button is pressed.\n' + 'If there is no remembered sensitivity, just remember the current sensitivity')) _GESTURE2_GESTURES_LABELS = { _GG['Tap1Finger']: (_('Single tap'), _('Performs a left click.')), @@ -524,6 +528,38 @@ def _feature_dpi_sliding(): return _Setting(_DPI_SLIDING, _DpiSlidingRW(), callback=_feature_dpi_sliding_callback, device_kind=(_DK.mouse, )) +def _feature_speed_change(): + """Implements the ability to switch Sensitivity by clicking on the DPI_Change button.""" + class _SpeedChangeRW(_ActionSettingRW): + def press_action(self): # switch sensitivity + currentSpeed = self.device.persister.get('pointer_speed', None) if self.device.persister else None + newSpeed = self.device.persister.get('_speed-change', None) if self.device.persister else None + speed_setting = next(filter(lambda s: s.name == _POINTER_SPEED[0], self.device.settings), None) + if newSpeed is not None: + if speed_setting: + speed_setting.write(newSpeed) + else: + _log.error('cannot save sensitivity setting on %s', self.device) + from solaar.ui import status_changed as _status_changed + _status_changed(self.device, refresh=True) # update main window + if self.device.persister: + self.device.persister['_speed-change'] = currentSpeed + + def callback(device): + key_index = device.keys.index(_special_keys.CONTROL.DPI_Change) + key = device.keys[key_index] if key_index is not None else None + if key is not None and 'divertable' in key.flags: + keys = [_NamedInt(0, _('Off')), key.key] + return _ChoicesV(_NamedInts.list(keys), byte_count=2) + + return _Setting( + _SPEED_CHANGE, + _SpeedChangeRW('speed change', _DIVERT_KEYS[0]), + callback=callback, + device_kind=(_DK.mouse, _DK.trackball) + ) + + def _feature_adjustable_dpi_callback(device): # [1] getSensorDpiList(sensorIdx) reply = device.feature_request(_F.ADJUSTABLE_DPI, 0x10) @@ -852,6 +888,7 @@ _SETTINGS_TABLE = [ _S(_DPI_SLIDING, _F.REPROG_CONTROLS_V4, _feature_dpi_sliding), _S(_MOUSE_GESTURES, _F.REPROG_CONTROLS_V4, _feature_mouse_gesture), _S(_POINTER_SPEED, _F.POINTER_SPEED, _feature_pointer_speed), + _S(_SPEED_CHANGE, _F.POINTER_SPEED, _feature_speed_change), _S(_BACKLIGHT, _F.BACKLIGHT2, _feature_backlight2), _S(_FN_SWAP, _F.FN_INVERSION, _feature_fn_swap, registerFn=_register_fn_swap), _S(_FN_SWAP, _F.NEW_FN_INVERSION, _feature_new_fn_swap, identifier='new_fn_swap'),