Merge pull request #337 from doctor64/m185

Basic support for M185 mouse
This commit is contained in:
Peter Wu 2017-07-17 00:08:19 +02:00 committed by GitHub
commit 684afa871e
14 changed files with 198 additions and 13 deletions

View File

@ -107,7 +107,8 @@ Mice (Nano):
| V550 Nano | 1.0 | yes | - | smooth scrolling | | V550 Nano | 1.0 | yes | - | smooth scrolling |
| VX Nano | 1.0 | yes | - | smooth scrolling | | VX Nano | 1.0 | yes | - | smooth scrolling |
| M175 | | yes | | | | 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 | | | | M187 | 2.0 | yes | | |
| M215 | 1.0 | yes | | | | M215 | 1.0 | yes | | |
| M235 | | yes | | | | M235 | | yes | | |
@ -116,6 +117,13 @@ Mice (Nano):
| M315 | | yes | | | | M315 | | yes | | |
| MX 1100 | 1.0 | yes | - | smooth scrolling, side scrolling| | 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): Mice (Mini):

49
docs/devices/m185_new.txt Normal file
View File

@ -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.

51
docs/devices/m185_old.txt Normal file
View File

@ -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.

View File

@ -49,7 +49,7 @@ NANO_RECEIVER_C525 = _nano_receiver(0xc525)
NANO_RECEIVER_C526 = _nano_receiver(0xc526) NANO_RECEIVER_C526 = _nano_receiver(0xc526)
NANO_RECEIVER_C52e = _nano_receiver(0xc52e) NANO_RECEIVER_C52e = _nano_receiver(0xc52e)
NANO_RECEIVER_C531 = _nano_receiver(0xc531) NANO_RECEIVER_C531 = _nano_receiver(0xc531)
NANO_RECEIVER_C534 = _nano_receiver(0xc534)
del _unifying_receiver, _nano_receiver del _unifying_receiver, _nano_receiver
@ -67,4 +67,5 @@ ALL = (
NANO_RECEIVER_C526, NANO_RECEIVER_C526,
NANO_RECEIVER_C52e, NANO_RECEIVER_C52e,
NANO_RECEIVER_C531, NANO_RECEIVER_C531,
NANO_RECEIVER_C534,
) )

View File

@ -279,4 +279,14 @@ ReprogrammableKeyInfo = namedtuple('ReprogrammableKeyInfo', [
'task', 'task',
'flags']) 'flags'])
ReprogrammableKeyInfoV4 = namedtuple('ReprogrammableKeyInfoV4', [
'index',
'key',
'task',
'flags',
'pos',
'group',
'group_mask',
'remapped'])
del namedtuple del namedtuple

View File

@ -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 M150', protocol=2.0, wpid='4022')
_D('Wireless Mouse M175', protocol=2.0, wpid='4008') _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 M187', protocol=2.0, wpid='4019')
_D('Wireless Mouse M215', protocol=1.0, wpid='1020') _D('Wireless Mouse M215', protocol=1.0, wpid='1020')
_D('Wireless Mouse M235') _D('Wireless Mouse M235')

View File

@ -28,6 +28,7 @@ del getLogger
from .common import (FirmwareInfo as _FirmwareInfo, from .common import (FirmwareInfo as _FirmwareInfo,
ReprogrammableKeyInfo as _ReprogrammableKeyInfo, ReprogrammableKeyInfo as _ReprogrammableKeyInfo,
ReprogrammableKeyInfoV4 as _ReprogrammableKeyInfoV4,
KwException as _KwException, KwException as _KwException,
NamedInts as _NamedInts, NamedInts as _NamedInts,
pack as _pack, pack as _pack,
@ -300,11 +301,12 @@ class FeaturesArray(object):
class KeysArray(object): class KeysArray(object):
"""A sequence of key mappings supported by a HID++ 2.0 device.""" """A sequence of key mappings supported by a HID++ 2.0 device."""
__slots__ = ('device', 'keys') __slots__ = ('device', 'keys', 'keyversion')
def __init__(self, device, count): def __init__(self, device, count):
assert device is not None assert device is not None
self.device = device self.device = device
self.keyversion = 0
self.keys = [None] * count self.keys = [None] * count
def __getitem__(self, index): def __getitem__(self, index):
@ -312,13 +314,28 @@ class KeysArray(object):
if index < 0 or index >= len(self.keys): if index < 0 or index >= len(self.keys):
raise IndexError(index) raise IndexError(index)
# TODO: add here additional variants for other REPROG_CONTROLS
if self.keys[index] is None: if self.keys[index] is None:
keydata = feature_request(self.device, FEATURE.REPROG_CONTROLS, 0x10, index) 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: 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_id_text = special_keys.CONTROL[key]
ctrl_task_text = special_keys.TASK[key_task] ctrl_task_text = special_keys.TASK[key_task]
if self.keyversion == 1:
self.keys[index] = _ReprogrammableKeyInfo(index, ctrl_id_text, ctrl_task_text, flags) 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] return self.keys[index]
@ -439,7 +456,10 @@ def get_battery(device):
def get_keys(device): def get_keys(device):
# TODO: add here additional variants for other REPROG_CONTROLS
count = feature_request(device, FEATURE.REPROG_CONTROLS) count = feature_request(device, FEATURE.REPROG_CONTROLS)
if count is None:
count = feature_request(device, FEATURE.REPROG_CONTROLS_V4)
if count: if count:
return KeysArray(device, ord(count[:1])) return KeysArray(device, ord(count[:1]))

View File

@ -156,6 +156,7 @@ def _process_hidpp10_notification(device, status, n):
if n.sub_id == 0x41: if n.sub_id == 0x41:
protocol_name = ('unifying (eQuad DJ)' if n.address == 0x04 protocol_name = ('unifying (eQuad DJ)' if n.address == 0x04
else 'eQuad' if n.address == 0x03 else 'eQuad' if n.address == 0x03
else 'M185' if n.address == 0x0A
else None) else None)
if protocol_name: if protocol_name:
if _log.isEnabledFor(_DEBUG): if _log.isEnabledFor(_DEBUG):

View File

@ -336,10 +336,14 @@ class Receiver(object):
# read the serial immediately, so we can find out max_devices # read the serial immediately, so we can find out max_devices
# this will tell us if it's a Unifying or Nano receiver # this will tell us if it's a Unifying or Nano receiver
if self.product_id != 'c534':
serial_reply = self.read_register(_R.receiver_info, 0x03) serial_reply = self.read_register(_R.receiver_info, 0x03)
assert serial_reply assert serial_reply
self.serial = _strhex(serial_reply[1:5]) self.serial = _strhex(serial_reply[1:5])
self.max_devices = ord(serial_reply[6:7]) self.max_devices = ord(serial_reply[6:7])
else:
self.serial = 0
self.max_devices = 6
if self.max_devices == 6: if self.max_devices == 6:
self.name = 'Unifying Receiver' self.name = 'Unifying Receiver'

View File

@ -122,6 +122,7 @@ _SIDE_SCROLL = ('side-scroll', _("Side Scrolling"),
_("When disabled, pushing the wheel sideways sends custom button events\n" _("When disabled, pushing the wheel sideways sends custom button events\n"
"instead of the standard side-scrolling events.")) "instead of the standard side-scrolling events."))
_DPI = ('dpi', _("Sensitivity (DPI)"), None) _DPI = ('dpi', _("Sensitivity (DPI)"), None)
_POINTER_SPEED = ('pointer_speed', _("Sensitivity (Pointer Speed)"), None)
_FN_SWAP = ('fn-swap', _("Swap Fx function"), _FN_SWAP = ('fn-swap', _("Swap Fx function"),
_("When set, the F1..F12 keys will activate their special function,\n" _("When set, the F1..F12 keys will activate their special function,\n"
"and you must hold the FN key to activate their standard function.") "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], label=_SMOOTH_SCROLL[1], description=_SMOOTH_SCROLL[2],
device_kind=_DK.mouse) 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(): def _feature_smart_shift():
_MIN_SMART_SHIFT_VALUE = 0 _MIN_SMART_SHIFT_VALUE = 0
_MAX_SMART_SHIFT_VALUE = 50 _MAX_SMART_SHIFT_VALUE = 50
@ -250,6 +256,15 @@ def _feature_adjustable_dpi():
label=_DPI[1], description=_DPI[2], label=_DPI[1], description=_DPI[2],
device_kind=_DK.mouse) 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', 'fn_swap',
'new_fn_swap', 'new_fn_swap',
'smooth_scroll', 'smooth_scroll',
'lowres_smooth_scroll',
'side_scroll', 'side_scroll',
'dpi', 'dpi',
'pointer_speed',
'hand_detection', 'hand_detection',
'typing_illumination', 'typing_illumination',
'smart_shift', 'smart_shift',
@ -271,8 +288,10 @@ RegisterSettings = _SETTINGS_LIST(
fn_swap=_register_fn_swap, fn_swap=_register_fn_swap,
new_fn_swap=None, new_fn_swap=None,
smooth_scroll=_register_smooth_scroll, smooth_scroll=_register_smooth_scroll,
lowres_smooth_scroll=None,
side_scroll=_register_side_scroll, side_scroll=_register_side_scroll,
dpi=_register_dpi, dpi=_register_dpi,
pointer_speed=None,
hand_detection=_register_hand_detection, hand_detection=_register_hand_detection,
typing_illumination=None, typing_illumination=None,
smart_shift=None, smart_shift=None,
@ -281,8 +300,10 @@ FeatureSettings = _SETTINGS_LIST(
fn_swap=_feature_fn_swap, fn_swap=_feature_fn_swap,
new_fn_swap=_feature_new_fn_swap, new_fn_swap=_feature_new_fn_swap,
smooth_scroll=_feature_smooth_scroll, smooth_scroll=_feature_smooth_scroll,
lowres_smooth_scroll=_feature_lowres_smooth_scroll,
side_scroll=None, side_scroll=None,
dpi=_feature_adjustable_dpi, dpi=_feature_adjustable_dpi,
pointer_speed=_feature_pointer_speed,
hand_detection=None, hand_detection=None,
typing_illumination=None, typing_illumination=None,
smart_shift=_feature_smart_shift, smart_shift=_feature_smart_shift,
@ -320,7 +341,9 @@ def check_feature_settings(device, already_known):
already_known.append(feature(device)) already_known.append(feature(device))
check_feature(_SMOOTH_SCROLL[0], _F.HI_RES_SCROLLING) 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.FN_INVERSION)
check_feature(_FN_SWAP[0], _F.NEW_FN_INVERSION, 'new_fn_swap') check_feature(_FN_SWAP[0], _F.NEW_FN_INVERSION, 'new_fn_swap')
check_feature(_DPI[0], _F.ADJUSTABLE_DPI) check_feature(_DPI[0], _F.ADJUSTABLE_DPI)
check_feature(_POINTER_SPEED[0], _F.POINTER_SPEED)
check_feature(_SMART_SHIFT[0], _F.SMART_SHIFT) check_feature(_SMART_SHIFT[0], _F.SMART_SHIFT)

View File

@ -335,8 +335,11 @@ TASK = _NamedInts(
ShowUI=0x0092, ShowUI=0x0092,
) )
TASK._fallback = lambda x: 'unknown:%04X' % x TASK._fallback = lambda x: 'unknown:%04X' % x
# hidpp 4.5 info from https://lekensteyn.nl/files/logitech/x1b04_specialkeysmsebuttons.html
KEY_FLAG = _NamedInts( KEY_FLAG = _NamedInts(
virtual=0x80,
persistently_divertable=0x40,
divertable=0x20,
reprogrammable=0x10, reprogrammable=0x10,
FN_sensitive=0x08, FN_sensitive=0x08,
nonstandard=0x04, nonstandard=0x04,

View File

@ -98,8 +98,12 @@ def _print_device(dev):
print (' Has %d reprogrammable keys:' % len(dev.keys)) print (' Has %d reprogrammable keys:' % len(dev.keys))
for k in dev.keys: for k in dev.keys:
flags = _special_keys.KEY_FLAG.flag_names(k.flags) flags = _special_keys.KEY_FLAG.flag_names(k.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))) 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: if dev.online:
battery = _hidpp20.get_battery(dev) battery = _hidpp20.get_battery(dev)
if battery is None: if battery is None:

View File

@ -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}=="c51a", GOTO="solaar_apply"
ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c521", 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}=="c525", GOTO="solaar_apply"
ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c534", GOTO="solaar_apply"
GOTO="solaar_end" GOTO="solaar_end"

View File

@ -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}=="c51a", GOTO="solaar_apply"
ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c521", 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}=="c525", GOTO="solaar_apply"
ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c534", GOTO="solaar_apply"
GOTO="solaar_end" GOTO="solaar_end"