diff --git a/docs/devices.md b/docs/devices.md index 239f7c54..f5f7cc8d 100644 --- a/docs/devices.md +++ b/docs/devices.md @@ -107,7 +107,8 @@ Mice (Nano): | V550 Nano | 1.0 | yes | - | smooth scrolling | | VX Nano | 1.0 | yes | - | smooth scrolling | | M175 | | yes | | | -| M185 | | yes | | | +| M185 [old] | 4.5 | yes | R/W | smooth scrolling[note] | +| M185 [new] | 4.5 | no | R/W | smooth scrolling[note] | | M187 | 2.0 | yes | | | | M215 | 1.0 | yes | | | | M235 | | yes | | | @@ -116,6 +117,13 @@ Mice (Nano): | M315 | | yes | | | | MX 1100 | 1.0 | yes | - | smooth scrolling, side scrolling| +[old]: M185 with P/N: 810-003496 + +[new]: M185 with P/N: 810-005238 + +[note]: Currently, smooth scrolling events does not processed in xfce and this +setting useful only for disable smooth scrolling + Mice (Mini): diff --git a/docs/devices/m185_new.txt b/docs/devices/m185_new.txt new file mode 100644 index 00000000..f90d71a9 --- /dev/null +++ b/docs/devices/m185_new.txt @@ -0,0 +1,49 @@ +P/N: 810-005238 + +Unifying Receiver +Device path : /dev/hidraw3 + USB id : 046d:c534 + Serial : 0 + Firmware : 29.01.B0016 + Has 1 paired device(s) out of a maximum of 6. + Notifications: (none) + + 2: Wireless Mouse M185 + Codename : M185 + Kind : mouse + Wireless PID : 4054 + Protocol : HID++ 4.5 + Polling rate : 8 ms (125Hz) + Serial number: 1377ED51 + Firmware: RQM 64.00.B0008 + The power switch is located on the base. + Supports 20 HID++ 2.0 features: + 0: ROOT {0000} + 1: FEATURE SET {0001} + 2: DEVICE FW VERSION {0003} + 3: DEVICE NAME {0005} + 4: RESET {0020} + 5: REPROG CONTROLS V4 {1B04} + 6: WIRELESS DEVICE STATUS {1D4B} + 7: LOWRES WHEEL {2130} + 8: POINTER SPEED {2205} + 9: unknown:1802 {1802} internal, hidden + 10: unknown:1810 {1810} internal, hidden + 11: unknown:1830 {1830} internal, hidden + 12: unknown:1850 {1850} internal, hidden + 13: unknown:1869 {1869} internal, hidden + 14: unknown:1890 {1890} internal, hidden + 15: unknown:18B1 {18B1} internal, hidden + 16: unknown:1DF3 {1DF3} internal, hidden + 17: unknown:1E00 {1E00} hidden + 18: unknown:1F03 {1F03} internal, hidden + 19: unknown:1E80 {1E80} internal, hidden + Has 3 reprogrammable keys: + 0: LEFT CLICK , default: LeftClick => LEFT CLICK + mse, reprogrammable, pos:0, group:1, gmask:1 + 1: RIGHT CLICK , default: RightClick => RIGHT CLICK + mse, reprogrammable, pos:0, group:1, gmask:1 + 2: MIDDLE BUTTON , default: MiddleMouseButton => MIDDLE BUTTON + divertable, mse, reprogrammable, pos:0, group:2, gmask:3 + Battery status unavailable. + diff --git a/docs/devices/m185_old.txt b/docs/devices/m185_old.txt new file mode 100644 index 00000000..59470cd7 --- /dev/null +++ b/docs/devices/m185_old.txt @@ -0,0 +1,51 @@ +P/N: 810-003496 + +Unifying Receiver + Device path : /dev/hidraw3 + USB id : 046d:c52f + Serial : 6D0342C5 + Firmware : 30.00.B0009 + Has 1 paired device(s) out of a maximum of 1. + Notifications: (none) + + 1: Wireless Mouse + Codename : Wireless Mouse + Kind : mouse + Wireless PID : 4055 + Protocol : HID++ 4.5 + Polling rate : 8 ms (125Hz) + Serial number: 6D0342C5 + Firmware: RQM 65.00.B0003 + The power switch is located on the base. + Supports 22 HID++ 2.0 features: + 0: ROOT {0000} + 1: FEATURE SET {0001} + 2: DEVICE FW VERSION {0003} + 3: DEVICE NAME {0005} + 4: RESET {0020} + 5: BATTERY STATUS {1000} + 6: unknown:1810 {1810} internal, hidden + 7: unknown:1830 {1830} internal, hidden + 8: unknown:1802 {1802} internal, hidden + 9: unknown:1862 {1862} internal, hidden + 10: unknown:1890 {1890} internal, hidden + 11: unknown:18A0 {18A0} internal, hidden + 12: unknown:18B1 {18B1} internal, hidden + 13: REPROG CONTROLS V4 {1B04} + 14: WIRELESS DEVICE STATUS {1D4B} + 15: unknown:1DF0 {1DF0} hidden + 16: unknown:1DF3 {1DF3} internal, hidden + 17: unknown:1E00 {1E00} hidden + 18: unknown:1EB0 {1EB0} internal, hidden + 19: unknown:1F03 {1F03} internal, hidden + 20: LOWRES WHEEL {2130} + 21: POINTER SPEED {2205} + Has 3 reprogrammable keys: + 0: LEFT CLICK , default: LeftClick => LEFT CLICK + mse, reprogrammable, pos:0, group:1, gmask:1 + 1: RIGHT CLICK , default: RightClick => RIGHT CLICK + mse, reprogrammable, pos:0, group:1, gmask:1 + 2: MIDDLE BUTTON , default: MiddleMouseButton => MIDDLE BUTTON + divertable, mse, reprogrammable, pos:0, group:2, gmask:3 + Battery: 5%, discharging. + diff --git a/lib/logitech_receiver/base_usb.py b/lib/logitech_receiver/base_usb.py index 2a4c6345..c0dc84b6 100644 --- a/lib/logitech_receiver/base_usb.py +++ b/lib/logitech_receiver/base_usb.py @@ -49,7 +49,7 @@ NANO_RECEIVER_C525 = _nano_receiver(0xc525) NANO_RECEIVER_C526 = _nano_receiver(0xc526) NANO_RECEIVER_C52e = _nano_receiver(0xc52e) NANO_RECEIVER_C531 = _nano_receiver(0xc531) - +NANO_RECEIVER_C534 = _nano_receiver(0xc534) del _unifying_receiver, _nano_receiver @@ -67,4 +67,5 @@ ALL = ( NANO_RECEIVER_C526, NANO_RECEIVER_C52e, NANO_RECEIVER_C531, + NANO_RECEIVER_C534, ) diff --git a/lib/logitech_receiver/common.py b/lib/logitech_receiver/common.py index 8e3124ec..b6cfb67d 100644 --- a/lib/logitech_receiver/common.py +++ b/lib/logitech_receiver/common.py @@ -279,4 +279,14 @@ ReprogrammableKeyInfo = namedtuple('ReprogrammableKeyInfo', [ 'task', 'flags']) +ReprogrammableKeyInfoV4 = namedtuple('ReprogrammableKeyInfoV4', [ + 'index', + 'key', + 'task', + 'flags', + 'pos', + 'group', + 'group_mask', + 'remapped']) + del namedtuple diff --git a/lib/logitech_receiver/descriptors.py b/lib/logitech_receiver/descriptors.py index e8165c9a..3e2bbd1f 100644 --- a/lib/logitech_receiver/descriptors.py +++ b/lib/logitech_receiver/descriptors.py @@ -215,7 +215,16 @@ _D('Illuminated Living-Room Keyboard K830', protocol=2.0, wpid='4032', _D('Wireless Mouse M150', protocol=2.0, wpid='4022') _D('Wireless Mouse M175', protocol=2.0, wpid='4008') -_D('Wireless Mouse M185') +_D('Wireless Mouse M185 new', codename='M185n', protocol=4.5, wpid='4054', + settings=[ + _FS.lowres_smooth_scroll(), + _FS.pointer_speed(), + ]) +_D('Wireless Mouse M185 old', codename='M185o', protocol=4.5, wpid='4055', + settings=[ + _FS.lowres_smooth_scroll(), + _FS.pointer_speed(), + ]) _D('Wireless Mouse M187', protocol=2.0, wpid='4019') _D('Wireless Mouse M215', protocol=1.0, wpid='1020') _D('Wireless Mouse M235') diff --git a/lib/logitech_receiver/hidpp20.py b/lib/logitech_receiver/hidpp20.py index 4b8d9712..7faff999 100644 --- a/lib/logitech_receiver/hidpp20.py +++ b/lib/logitech_receiver/hidpp20.py @@ -28,6 +28,7 @@ del getLogger from .common import (FirmwareInfo as _FirmwareInfo, ReprogrammableKeyInfo as _ReprogrammableKeyInfo, + ReprogrammableKeyInfoV4 as _ReprogrammableKeyInfoV4, KwException as _KwException, NamedInts as _NamedInts, pack as _pack, @@ -300,11 +301,12 @@ class FeaturesArray(object): class KeysArray(object): """A sequence of key mappings supported by a HID++ 2.0 device.""" - __slots__ = ('device', 'keys') + __slots__ = ('device', 'keys', 'keyversion') def __init__(self, device, count): assert device is not None self.device = device + self.keyversion = 0 self.keys = [None] * count def __getitem__(self, index): @@ -312,13 +314,28 @@ class KeysArray(object): if index < 0 or index >= len(self.keys): raise IndexError(index) + # TODO: add here additional variants for other REPROG_CONTROLS if self.keys[index] is None: keydata = feature_request(self.device, FEATURE.REPROG_CONTROLS, 0x10, index) + self.keyversion=1 + if keydata is None: + keydata = feature_request(self.device, FEATURE.REPROG_CONTROLS_V4, 0x10, index) + self.keyversion=4 if keydata: - key, key_task, flags = _unpack('!HHB', keydata[:5]) + key, key_task, flags, pos, group, gmask = _unpack('!HHBBBB', keydata[:8]) ctrl_id_text = special_keys.CONTROL[key] ctrl_task_text = special_keys.TASK[key_task] - self.keys[index] = _ReprogrammableKeyInfo(index, ctrl_id_text, ctrl_task_text, flags) + if self.keyversion == 1: + self.keys[index] = _ReprogrammableKeyInfo(index, ctrl_id_text, ctrl_task_text, flags) + if self.keyversion == 4: + mapped_data = feature_request(self.device, FEATURE.REPROG_CONTROLS_V4, 0x20, key&0xff00, key&0xff) + if mapped_data: + remap_key, remap_flag, remapped = _unpack('!HBH', mapped_data[:5]) + # if key not mapped map it to itself for display + if remapped == 0: + remapped = key + remapped_text = special_keys.CONTROL[remapped] + self.keys[index] = _ReprogrammableKeyInfoV4(index, ctrl_id_text, ctrl_task_text, flags, pos, group, gmask, remapped_text) return self.keys[index] @@ -439,7 +456,10 @@ def get_battery(device): def get_keys(device): + # TODO: add here additional variants for other REPROG_CONTROLS count = feature_request(device, FEATURE.REPROG_CONTROLS) + if count is None: + count = feature_request(device, FEATURE.REPROG_CONTROLS_V4) if count: return KeysArray(device, ord(count[:1])) diff --git a/lib/logitech_receiver/notifications.py b/lib/logitech_receiver/notifications.py index 4e4aa4db..3367d4d9 100644 --- a/lib/logitech_receiver/notifications.py +++ b/lib/logitech_receiver/notifications.py @@ -156,6 +156,7 @@ def _process_hidpp10_notification(device, status, n): if n.sub_id == 0x41: protocol_name = ('unifying (eQuad DJ)' if n.address == 0x04 else 'eQuad' if n.address == 0x03 + else 'M185' if n.address == 0x0A else None) if protocol_name: if _log.isEnabledFor(_DEBUG): diff --git a/lib/logitech_receiver/receiver.py b/lib/logitech_receiver/receiver.py index 1a01ba33..3ba02e02 100644 --- a/lib/logitech_receiver/receiver.py +++ b/lib/logitech_receiver/receiver.py @@ -336,10 +336,14 @@ class Receiver(object): # read the serial immediately, so we can find out max_devices # this will tell us if it's a Unifying or Nano receiver - serial_reply = self.read_register(_R.receiver_info, 0x03) - assert serial_reply - self.serial = _strhex(serial_reply[1:5]) - self.max_devices = ord(serial_reply[6:7]) + if self.product_id != 'c534': + serial_reply = self.read_register(_R.receiver_info, 0x03) + assert serial_reply + self.serial = _strhex(serial_reply[1:5]) + self.max_devices = ord(serial_reply[6:7]) + else: + self.serial = 0 + self.max_devices = 6 if self.max_devices == 6: self.name = 'Unifying Receiver' diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index f7fbbd21..6604a147 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -122,6 +122,7 @@ _SIDE_SCROLL = ('side-scroll', _("Side Scrolling"), _("When disabled, pushing the wheel sideways sends custom button events\n" "instead of the standard side-scrolling events.")) _DPI = ('dpi', _("Sensitivity (DPI)"), None) +_POINTER_SPEED = ('pointer_speed', _("Sensitivity (Pointer Speed)"), None) _FN_SWAP = ('fn-swap', _("Swap Fx function"), _("When set, the F1..F12 keys will activate their special function,\n" "and you must hold the FN key to activate their standard function.") @@ -179,6 +180,11 @@ def _feature_smooth_scroll(): label=_SMOOTH_SCROLL[1], description=_SMOOTH_SCROLL[2], device_kind=_DK.mouse) +def _feature_lowres_smooth_scroll(): + return feature_toggle(_SMOOTH_SCROLL[0], _F.LOWRES_WHEEL, + label=_SMOOTH_SCROLL[1], description=_SMOOTH_SCROLL[2], + device_kind=_DK.mouse) + def _feature_smart_shift(): _MIN_SMART_SHIFT_VALUE = 0 _MAX_SMART_SHIFT_VALUE = 50 @@ -250,6 +256,15 @@ def _feature_adjustable_dpi(): label=_DPI[1], description=_DPI[2], device_kind=_DK.mouse) +def _feature_pointer_speed(): + """Pointer Speed feature""" + # min and max values taken from usb traces of Win software + return feature_range(_POINTER_SPEED[0], _F.POINTER_SPEED, 0x002e, 0x01ff, + read_function_id=0x0, + write_function_id=0x10, + bytes_count=2, + label=_POINTER_SPEED[1], description=_POINTER_SPEED[2], + device_kind=_DK.mouse) # # # @@ -259,8 +274,10 @@ _SETTINGS_LIST = namedtuple('_SETTINGS_LIST', [ 'fn_swap', 'new_fn_swap', 'smooth_scroll', + 'lowres_smooth_scroll', 'side_scroll', 'dpi', + 'pointer_speed', 'hand_detection', 'typing_illumination', 'smart_shift', @@ -271,8 +288,10 @@ RegisterSettings = _SETTINGS_LIST( fn_swap=_register_fn_swap, new_fn_swap=None, smooth_scroll=_register_smooth_scroll, + lowres_smooth_scroll=None, side_scroll=_register_side_scroll, dpi=_register_dpi, + pointer_speed=None, hand_detection=_register_hand_detection, typing_illumination=None, smart_shift=None, @@ -281,8 +300,10 @@ FeatureSettings = _SETTINGS_LIST( fn_swap=_feature_fn_swap, new_fn_swap=_feature_new_fn_swap, smooth_scroll=_feature_smooth_scroll, + lowres_smooth_scroll=_feature_lowres_smooth_scroll, side_scroll=None, dpi=_feature_adjustable_dpi, + pointer_speed=_feature_pointer_speed, hand_detection=None, typing_illumination=None, smart_shift=_feature_smart_shift, @@ -320,7 +341,9 @@ def check_feature_settings(device, already_known): already_known.append(feature(device)) check_feature(_SMOOTH_SCROLL[0], _F.HI_RES_SCROLLING) + check_feature(_SMOOTH_SCROLL[0], _F.LOWRES_WHEEL) check_feature(_FN_SWAP[0], _F.FN_INVERSION) check_feature(_FN_SWAP[0], _F.NEW_FN_INVERSION, 'new_fn_swap') check_feature(_DPI[0], _F.ADJUSTABLE_DPI) + check_feature(_POINTER_SPEED[0], _F.POINTER_SPEED) check_feature(_SMART_SHIFT[0], _F.SMART_SHIFT) diff --git a/lib/logitech_receiver/special_keys.py b/lib/logitech_receiver/special_keys.py index 5669e7ec..bd827f22 100644 --- a/lib/logitech_receiver/special_keys.py +++ b/lib/logitech_receiver/special_keys.py @@ -335,8 +335,11 @@ TASK = _NamedInts( ShowUI=0x0092, ) TASK._fallback = lambda x: 'unknown:%04X' % x - +# hidpp 4.5 info from https://lekensteyn.nl/files/logitech/x1b04_specialkeysmsebuttons.html KEY_FLAG = _NamedInts( + virtual=0x80, + persistently_divertable=0x40, + divertable=0x20, reprogrammable=0x10, FN_sensitive=0x08, nonstandard=0x04, diff --git a/lib/solaar/cli/show.py b/lib/solaar/cli/show.py index 29f7ca62..e69c2a01 100644 --- a/lib/solaar/cli/show.py +++ b/lib/solaar/cli/show.py @@ -98,8 +98,12 @@ def _print_device(dev): print (' Has %d reprogrammable keys:' % len(dev.keys)) for k in dev.keys: flags = _special_keys.KEY_FLAG.flag_names(k.flags) - print (' %2d: %-26s => %-27s %s' % (k.index, k.key, k.task, ', '.join(flags))) - + # TODO: add here additional variants for other REPROG_CONTROLS + if dev.keys.keyversion == 1: + print (' %2d: %-26s => %-27s %s' % (k.index, k.key, k.task, ', '.join(flags))) + if dev.keys.keyversion == 4: + print (' %2d: %-26s, default: %-27s => %-26s' % (k.index, k.key, k.task, k.remapped)) + print (' %s, pos:%d, group:%1d, gmask:%d' % ( ', '.join(flags), k.pos, k.group, k.group_mask)) if dev.online: battery = _hidpp20.get_battery(dev) if battery is None: diff --git a/packaging/debian/solaar.udev b/packaging/debian/solaar.udev index e9dde2c4..2a891650 100644 --- a/packaging/debian/solaar.udev +++ b/packaging/debian/solaar.udev @@ -32,6 +32,7 @@ ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c518", GOTO="solaar_apply" ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c51a", GOTO="solaar_apply" ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c521", GOTO="solaar_apply" ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c525", GOTO="solaar_apply" +ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c534", GOTO="solaar_apply" GOTO="solaar_end" diff --git a/rules.d/42-logitech-unify-permissions.rules b/rules.d/42-logitech-unify-permissions.rules index 0b44c447..1fbc7329 100644 --- a/rules.d/42-logitech-unify-permissions.rules +++ b/rules.d/42-logitech-unify-permissions.rules @@ -32,6 +32,7 @@ ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c518", GOTO="solaar_apply" ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c51a", GOTO="solaar_apply" ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c521", GOTO="solaar_apply" ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c525", GOTO="solaar_apply" +ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c534", GOTO="solaar_apply" GOTO="solaar_end"