Compare commits

...

450 Commits
0.9.2 ... 1.0.2

Author SHA1 Message Date
Peter F. Patel-Schneider
4a108b3ab7 release 1.0.2 2020-05-22 01:43:53 +01:00
Peter F. Patel-Schneider
fac6cd6840 po: minor fix to es translation file 2020-05-22 01:19:20 +01:00
Peter F. Patel-Schneider
edf610d235 doc: add usage document 2020-05-22 01:18:44 +01:00
Peter F. Patel-Schneider
dac9c1bd8e doc: minor improvements to index.md 2020-05-22 01:18:44 +01:00
Peter F. Patel-Schneider
352375aa9a doc: minor improvements to installation.md 2020-05-22 01:18:44 +01:00
Peter F. Patel-Schneider
44722560d2 release 1.0.2rc3 2020-05-17 18:39:31 +01:00
daviddavid
101e24c7bf Update again French translation
- by David Geiger <david.david@mageialinux-online.org>
2020-05-17 11:57:11 -04:00
Peter F. Patel-Schneider
b7e8e646b5 ui: don't complain for receivers in other seats 2020-05-17 11:55:01 -04:00
Peter F. Patel-Schneider
97fa7de28a devices: pass arguments through in feature_choices_dynamic 2020-05-17 11:51:38 -04:00
Peter F. Patel-Schneider
302720b0db ui: add --battery-icons=symbolic option to prefer symbolic icons 2020-05-17 16:29:14 +01:00
daviddavid
813c238704 Update French translation
- by David Geiger <david.david@mageialinux-online.org>
2020-05-14 09:36:32 -04:00
Peter F. Patel-Schneider
354c914bd2 docs: improve installation document 2020-05-13 11:48:06 -04:00
daviddavid
574aee0cc7 po formatting updates (for release 1.0.2rc2) 2020-05-13 15:37:57 +01:00
Peter F. Patel-Schneider
a3cdcf4cd1 git: add lib/solaar.egg-info/ to .gitignore 2020-05-13 15:35:35 +01:00
Peter F. Patel-Schneider
592ced0a53 release 1.0.2rc2 2020-05-10 23:11:37 +01:00
Peter F. Patel-Schneider
3a2593798e release: fix up RELEASE.md 2020-05-10 03:49:32 +01:00
Peter F. Patel-Schneider
67b7d46844 docs: update ChangeLog 2020-05-07 19:33:57 +01:00
Peter F. Patel-Schneider
5411e43b77 ui: use Solaar icon instead of missing battery icons 2020-05-07 19:33:57 +01:00
Peter F. Patel-Schneider
66dc0bd285 release: remove packaging directory as it is not maintained 2020-05-02 18:44:34 +01:00
Peter F. Patel-Schneider
f641554ed5 release: release candidates do not have hyphen before rc 2020-05-02 17:42:13 +01:00
Peter F. Patel-Schneider
beec4c2130 release: change version from 1.0.2-rc1 to 1.0.2rc2 2020-05-02 17:42:13 +01:00
Peter F. Patel-Schneider
f5bf488ffc docs: update ChangeLog for 1.0.2rc2 2020-05-02 17:42:13 +01:00
Steven Lilley
1deaa09142 devices: Add Pebble M350 mouse 2020-05-02 17:25:29 +01:00
Peter F. Patel-Schneider
88a0dfff6f ui: add environment variable to switch to symbolic battery icons in tray 2020-05-02 10:28:51 -04:00
Peter F. Patel-Schneider
f28da6e7c4 docs: add a short discussion of how battery icons work 2020-05-02 10:28:51 -04:00
Peter F. Patel-Schneider
5290980ac8 ui: only use standard battery icons 2020-05-02 10:28:51 -04:00
Peter F. Patel-Schneider
4d23d3abac device: heuristic inference of battery level when charging and no discharging information available 2020-05-02 09:58:24 -04:00
Peter F. Patel-Schneider
0c4e757cf9 docs: add K600 TV, M350 WPID 4080, and MX Keys to devices.md and improve wording in it 2020-05-02 09:26:01 -04:00
Peter F. Patel-Schneider
d487a0d889 ui: remove assert that updated receiver is still in window 2020-05-02 14:07:42 +01:00
Roshan Shariff
65824a4c08 ui: add 'desktop-entry' hint to indicate notification source.
GNOME requires that notifications have a 'desktop-entry' hint to be
able to filter notifications by application. See
https://wiki.gnome.org/Initiatives/GnomeGoals/NotificationSource.

Fixes pwr-Solaar/Solaar#734
2020-05-01 13:56:22 -04:00
tarxf
58a1c7ffb0 docs: dixed name typo for MX Master 2S 2020-04-25 02:38:02 +01:00
Peter F. Patel-Schneider
1f896d2b9e docs: add changing device features to long description of Solaar 2020-04-22 21:37:25 +01:00
Peter F. Patel-Schneider
377c954933 install: remove Daniels email from setup.py 2020-04-20 12:01:57 -04:00
Peter F. Patel-Schneider
4744ce47b2 install: update installation directions and remove outdated rules/install.sh 2020-04-20 12:01:57 -04:00
Peter F. Patel-Schneider
b01b74f2a8 install: install udev rule with pip 2020-04-20 12:01:57 -04:00
Peter F. Patel-Schneider
6282ae05e8 install: remove incorrect code for determining autostart directory from setup.py 2020-04-20 12:01:57 -04:00
Peter F. Patel-Schneider
b5e1f47c50 ui: add setuptools global install share directory to icon directories 2020-04-20 10:32:40 -04:00
Peter F. Patel-Schneider
8ffaeb112f devices: add four older devices 2020-04-20 15:26:37 +01:00
Peter F. Patel-Schneider
2189d4a35b receiver: add new macro for old receivers and use it to set max devices to 6 for C517 2020-04-20 15:26:37 +01:00
Jan Szenborn
706ee2990f Improve polish translations 2020-04-20 15:25:47 +01:00
Peter F. Patel-Schneider
06875fb9ab release: update remaining version 1.0.1 to 1.0.2-rc1 where needed 2020-04-20 15:22:30 +01:00
Peter F. Patel-Schneider
f6b0cc59ed ui: only use file path for tray icon if icon name is file in current directory 2020-04-20 15:21:19 +01:00
Peter F. Patel-Schneider
e3aacdbf05 receiver: don't check that device kind matches feature kind 2020-04-20 15:20:03 +01:00
Peter F. Patel-Schneider
45dc81b4c4 docs: update information on pre-built packages 2020-04-20 15:19:22 +01:00
Peter F. Patel-Schneider
0324fd9a1e ui: more flexible way to determine icon from battery level 2020-04-18 14:27:42 +01:00
Thomas Uhle
ca9f9b3447 ui: add support for Ayatana AppIndicator 2020-04-07 18:58:18 -04:00
Peter F. Patel-Schneider
624247d378 install: fix up errors when required packages not installed 2020-04-05 20:55:32 +01:00
Thomas Uhle
5f8dbdeb6d ui: fix tooltip description
The program's name is printed twice if AppIndicator is used and no receiver
is found because AppIndicator always adds the program's name as a title to
the tooltip.
2020-04-01 11:10:54 -04:00
Thomas Uhle
2bdb844557 ui: fix end of iterative calls to _blink()
Prepare _icon_before_attention for the next iteration by reinitializing it
at the end of the current iteration.
2020-04-01 11:10:54 -04:00
Thomas Uhle
422f336d70 ui: add missing parentheses to function call
Compare function's return value to AppIndicator3.IndicatorStatus.ATTENTION
instead of the function object reference.
2020-04-01 11:10:54 -04:00
Peter F. Patel-Schneider
563ef0d8ef receiver: remove extra argument in call when unpairing 2020-03-22 17:23:28 +00:00
Peter F. Patel-Schneider
3a85c4f64b UI: lookup attention icon filename directly to get around bug in libappindicator 2020-03-17 23:17:59 +00:00
Filipe Laíns
4056f5b9d3 release.sh: simplify script
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2020-03-16 21:59:41 +00:00
Filipe Laíns
442a7d18a5 release.sh: document release process
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2020-03-16 21:59:41 +00:00
Filipe Laíns
79be4b20d7 release.sh: don't throw github release creation error on dry run
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2020-03-16 21:59:41 +00:00
Filipe Laíns
b2f3b56c31 release 1.0.2-rc1
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2020-03-16 20:57:35 +00:00
Peter F. Patel-Schneider
072f932206 ui: handle devices and receivers with no firmware information 2020-03-16 20:55:18 +00:00
Filipe Laíns
d5a3a4fe7e release.sh: initial script
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2020-03-16 16:58:41 +00:00
Peter F. Patel-Schneider
a49b7938e0 receiver: Receiver C517 can have two paired devices 2020-03-16 16:04:16 +00:00
Peter F. Patel-Schneider
1eef88eb44 cli: handle receivers with no firmware information 2020-03-16 16:04:16 +00:00
Peter F. Patel-Schneider
eda1399330 cli: force unpairing attempt when using cli 2020-03-15 23:34:51 +00:00
Peter F. Patel-Schneider
8dbc4b5707 docs: Update ChangeLog 2020-03-15 23:33:47 +00:00
Peter F. Patel-Schneider
687a28d2c2 UI: lookup icon filenames directly to get around bug in libappindicator 2020-03-15 22:00:56 +00:00
Peter F. Patel-Schneider
f733390bd1 install: autostart .desktop file has --window=hide 2020-03-15 22:00:21 +00:00
Peter F. Patel-Schneider
ac6fa9643f UI: change default for --window to show 2020-03-15 22:00:21 +00:00
Peter F. Patel-Schneider
f28053a09a docs: improve documentation on required packages 2020-03-15 21:59:42 +00:00
gogogogi
49145ae272 translations: Update Croatian language translation
* Update Croatian language

* Update version
2020-03-15 16:30:00 -04:00
Peter F. Patel-Schneider
af26870e83 docs: use correct name of udev rules file in installation directions 2020-03-08 15:15:17 +00:00
Peter F. Patel-Schneider
007163a563 docs: update ChangeLog 2020-03-08 15:15:17 +00:00
Peter F. Patel-Schneider
a9acdbe47a receiver: add c537 nano receiver 2020-03-06 18:00:37 -05:00
Peter F. Patel-Schneider
955df30c7f upower: properly add signal receiver 2020-02-22 08:46:56 -05:00
Peter F. Patel-Schneider
23c0397764 listener: mark device as inactive after resume so that settings are correctly pushed 2020-02-22 08:46:56 -05:00
Peter F. Patel-Schneider
789f5f05c3 receiver: don't check features when device is not online 2020-02-22 08:46:56 -05:00
Peter F. Patel-Schneider
ea2c22c015 receiver: fix seldom-encountered bug for devices that have no features set up yet 2020-02-21 16:52:28 +00:00
Peter F. Patel-Schneider
1c09b9c45d upower: add in logind signals for suspend/resume 2020-02-21 16:52:28 +00:00
Peter F. Patel-Schneider
dd9626f7a4 docs: ask for git describe output if running a cloned version 2020-02-21 16:48:05 +00:00
Rijnhard Hessel
35909afb0d packaging: remove solaar-gnome3 package 2020-02-21 09:50:30 -05:00
Rijnhard Hessel
8e67bbbc11 receiver: add safety for non-compliant mouse features
receiver: safely handle errors with check_feature to allow device to be detected even if some features are inconsistent
2020-02-21 09:33:14 -05:00
Peter F. Patel-Schneider
a5813e4e23 packaging: remove last vestiges of python2 2020-02-20 19:49:38 +00:00
Peter F. Patel-Schneider
d439d43be6 docs: replace dummy URL for Solaar with real URL 2020-02-20 12:27:41 +00:00
Peter F. Patel-Schneider
11465f2b2a cli: add probe command to show receiver register information 2020-02-18 23:21:59 +00:00
Peter F. Patel-Schneider
079f2dacd4 docs: add information on --windows flag and rewrite other bits of index.md 2020-02-18 08:32:02 -05:00
Peter F. Patel-Schneider
7c6bd4202d receiver: use dictionary for expected message length checking; ignore messages with unknown report ids 2020-02-18 08:14:22 -05:00
Peter F. Patel-Schneider
1740a9a8c4 receiver: segregate and standardly handle DJ notifications separately - just ignore them all for now 2020-02-18 08:14:22 -05:00
Peter F. Patel-Schneider
0cadc3247e receiver: don't create notifications for movement and key reports 2020-02-18 08:14:22 -05:00
Peter F. Patel-Schneider
e23de2ee9f ui: add quit button to main window 2020-02-18 13:14:10 +00:00
Peter F. Patel-Schneider
8fb52a3f37 ui: don't show warning in pairing window for receivers with unlimited pairing 2020-02-18 12:19:45 +00:00
Peter F. Patel-Schneider
3edac85b90 ui: turn on window manager notifications again 2020-02-18 12:17:49 +00:00
Peter F. Patel-Schneider
64c76e51ef receiver,cli,ui: minor code cleanup 2020-02-17 11:53:39 +00:00
Peter F. Patel-Schneider
02cac598a0 receiver: all nano receivers re-pair but don't unpair 2020-02-17 11:53:39 +00:00
Peter F. Patel-Schneider
02a47b4003 install: adjust autostart desktop installation to use usual desktop file 2020-02-17 08:35:47 +00:00
Peter F. Patel-Schneider
4afe8c893b ui: add option to show the main window or run solaar without tray 2020-02-17 08:35:47 +00:00
Peter F. Patel-Schneider
5afa094175 ui: Add message about remaining pairings to pairing window when receiver has limited pairings 2020-02-17 08:34:53 +00:00
Peter F. Patel-Schneider
196705ba1f docs: fix information about receiver c534 2020-02-17 08:34:53 +00:00
Peter F. Patel-Schneider
4c46a999f7 gui: treat nano protocol disconnections correctly when pairing 2020-02-17 08:34:53 +00:00
Peter F. Patel-Schneider
7bd31c0d95 cli: complain when trying to unpair from a receiver that does not unpair 2020-02-17 08:34:53 +00:00
Peter F. Patel-Schneider
dc2a1adf26 ui: handle re-pairing receivers correctly when pairing 2020-02-17 08:34:53 +00:00
Peter F. Patel-Schneider
e89b50cdf2 cli: correct reporting for pairing with receivers that re-pair (i.e., c534) 2020-02-17 08:34:53 +00:00
Peter F. Patel-Schneider
d08e0a9574 cli: Show a positive response instead of an error when receiver pairs on already-used slot in CLI 2020-02-17 08:34:53 +00:00
Peter F. Patel-Schneider
22d37c4c1c ui: Better determination in GUI of when receiver may pair (still might be conservative) 2020-02-17 08:34:53 +00:00
Peter F. Patel-Schneider
546ccb7ac2 receiver: determine remaining pairings (if receiver has this) and display in solaar show and main window 2020-02-17 08:34:53 +00:00
Peter F. Patel-Schneider
1ab03d4c9f receiver: add check for bad results from getting the usb device 2020-02-16 22:16:37 +00:00
Peter F. Patel-Schneider
a6fcb75aa8 receiver: get receiver name from receiver information list 2020-02-16 22:16:37 +00:00
Peter F. Patel-Schneider
907c5ab075 receiver: change usb identification of receivers to dictionary to allow adding other fields, add receiver name to dictionary 2020-02-16 22:16:37 +00:00
Peter F. Patel-Schneider
515f994ab8 receiver: ignore disconnects of disconnected devices 2020-02-16 22:16:37 +00:00
Peter F. Patel-Schneider
25905c5d77 receiver: Show type of receiver in CLI show command 2020-02-16 22:16:37 +00:00
Peter F. Patel-Schneider
babf0f4ded receiver: Show serial number in stringify of receivers 2020-02-16 22:16:37 +00:00
Peter F. Patel-Schneider
41664b8e21 docs: update changelog to 11 February 2020 commits 2020-02-16 22:07:45 +00:00
svntjng
5c080a7831 docs: add supported feature for M330 2020-02-16 22:06:12 +00:00
Peter F. Patel-Schneider
5aa4fa0239 update version in docs/_config.yml to 1.0.1 2020-02-11 15:55:15 +00:00
Peter F. Patel-Schneider
4f4e610635 receiver: battery level 0 is unknown level 2020-02-10 18:44:28 +00:00
Peter F. Patel-Schneider
39791be440 doc: fix typos and improve some wording 2020-02-06 19:09:00 +01:00
Peter F. Patel-Schneider
2814350f9f doc: fix wording about supported Nano receivers 2020-02-06 19:09:00 +01:00
Peter F. Patel-Schneider
4a28b48275 doc: add paragraph on firmware 2020-02-06 19:09:00 +01:00
Peter F. Patel-Schneider
97474a21de doc: fix formatting, add receivers, change version 4.5 to 2.0 2020-02-06 19:09:00 +01:00
Peter F. Patel-Schneider
719106bf0f doc: solaar does keep track of device status 2020-02-06 19:09:00 +01:00
Peter F. Patel-Schneider
0a735a32ed doc: better documentation: devices and capabilities 2020-02-06 19:09:00 +01:00
Dmitriusan
bd26b9340d scripts/debian: switch to python3 2020-02-06 19:06:47 +01:00
Peter F. Patel-Schneider
f45ae5ffad solaar: use python3, not generic python 2020-02-06 17:37:10 +01:00
Peter F. Patel-Schneider
0520dde990 receiver: use None for serial number of c534 2020-02-04 22:04:43 +01:00
Peter F. Patel-Schneider
e53781ec6e install: autostart desktop file starts with tray only 2020-01-22 22:22:14 +00:00
Peter F. Patel-Schneider
1bc189e2fe ui: add --tray option to start without window visible 2020-01-22 22:22:14 +00:00
Peter F. Patel-Schneider
91bcfa28ea docs: minor changes to installation.md to eliminate python2 support 2020-01-21 21:51:35 +00:00
Filipe Laíns
64c1de32da setup.py: drop python 2 support
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2020-01-21 21:51:25 +00:00
Peter F. Patel-Schneider
56762b5494 device: move persister from Setting to _DeviceDescriptor to get around problem with settings discovered after startup 2020-01-21 03:23:25 +00:00
Peter F. Patel-Schneider
77e21a0b63 ui: fix crash when styles add padding by not setting 1-pixel columns; slightly better window layout; add style class to window 2020-01-21 03:11:35 +00:00
Peter F. Patel-Schneider
d6fa5269c5 ui: don't ignore offline devices when determining whether pairing is possible 2020-01-21 03:08:49 +00:00
Peter F. Patel-Schneider
0198d58dc7 hidpp20: fix error when showing battery
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2020-01-19 18:18:33 +00:00
Filipe Laíns
c30f0a79bd docs: features: add partly working and unsupported status
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2020-01-19 18:18:30 +00:00
Filipe Laíns
89ee83b906 docs: features: change checkmark style
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2020-01-19 18:18:11 +00:00
Filipe Laíns
0bef7b84f8 docs: features: make the table easier to maintain
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2020-01-19 18:18:08 +00:00
Filipe Laíns
c3e90bc73e docs: features: make the table readable in plain ascii
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2020-01-19 18:17:54 +00:00
Alex Cherkayev
814b7f30ba docs: features: added some missing feature names and list of implemented features
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2020-01-19 18:17:18 +00:00
Alex Cherkayev
8039c035a7 devices: Add new variant of K800 keyboard
docs: Add K800 new variant to supported
2020-01-12 16:11:03 +00:00
effective-light
430b70711b hidpp20: features: add BATTERY_VOLTAGE (0x1001) support
Signed-off-by: Filipe Laíns <lains@archlinux.org>

Co-authored-by: Filipe Laíns <lains@archlinux.org>
2020-01-12 16:05:21 +00:00
nightsky30
82915eea4b docs: update the USB IDs list 2019-12-29 14:46:59 +00:00
Peter F. Patel-Schneider
995544396c receiver: don't log messages with unknown report IDs 2019-12-26 16:10:48 +00:00
Peter F. Patel-Schneider
4678f52293 don't have window always be on top 2019-12-24 18:54:12 +00:00
Peter F. Patel-Schneider
26c5a5f9ba clarify license status; fix debian packaging copyight notice 2019-12-24 18:53:43 +00:00
Peter F. Patel-Schneider
c6ccc0638d devices: add M310 2019-12-24 12:49:00 +00:00
drupal-daffie
6ba8c6c3fc setup: use setuptools if available 2019-12-24 12:16:35 +00:00
Peter F. Patel-Schneider
9087f4187c docs: adjust documentation to new changes 2019-12-24 12:13:58 +00:00
drupal-daffie
673c81c096 docs: devices: add MX Master 2s 2019-12-16 15:33:50 +00:00
drupal-daffie
43bfa80753 docs: devices: add Mx Master 3 2019-12-16 12:03:03 +00:00
gogogogi
3884ce14d7 docs: readme: add new ppa 2019-12-14 21:22:25 +00:00
spaced
4fafd4f6be hidpp20: features: add very basic implementation of 0x1982 (Backlight 2)
Fixes #547
2019-12-12 18:56:08 +00:00
Chris Rainey
0bec0e02b1 docd: devices: add M330 2019-12-10 23:36:31 +00:00
Filipe Laíns
59c0367283 github: add issue templates 2019-12-01 21:48:57 +00:00
marcelkarger
63f523dac6 po: update German translation 2019-11-30 23:14:32 +00:00
Heimen Stoffels
4822975acb po: added Dutch translation 2019-11-30 23:13:26 +00:00
Chad Condon
c389c9c2b7 ur: repair low resolution smooth scrolling 2019-11-30 23:12:07 +00:00
KTB
76e70799aa Update devices.md for Anywhere MX 2 - R/W DPI
Simple doc update to show that Solaar supports changing DPI for the Logitech Anywhere MX 2.
2019-10-20 23:55:02 +01:00
Tom Swartz
2a94cc9233 Fix typos in docs 2019-10-20 23:53:45 +01:00
Tom Swartz
ae9e862f19 Fix typos in comments 2019-10-20 23:53:45 +01:00
Tom Swartz
12f4e187d6 Fix typos in logging, output 2019-10-20 23:53:45 +01:00
Filipe Laíns
51dd2a5d28 notifications: add missing protocol names
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2019-10-20 23:19:34 +01:00
Alex Cherkayev
6981555804 notifications: fix battery status notification parsing
Fix indicator tooltip (duplicate Solaar line, python list instead of battery status).
2019-08-20 09:40:39 +02:00
spaced
3583759d3a devices: basic support for craft keyboard
* basic support for craft keyboard
* fix issue where _(status) throws exception because namedInt with UTF8.decode
2019-08-20 09:39:11 +02:00
david_david
c92a889b68 po: formatting updates
* po formatting updates

- Update French translation by David Geiger <david.david@mageialinux-online.org>
- Update version into setup.py file

* Update version in fr.po
2019-08-20 09:37:15 +02:00
Peter Wu
b9fb005c9c docs: Deduplicate README contents, use a symlink
docs/index.md and README.md are almost identical, except for some paths
and the metadata table on top of the file. Use absolute paths to the
website to avoid breaking links, and symlink README.md.
2019-08-20 09:36:16 +02:00
Zachary Cook
a06ea6de11 data: fix icon theme for battery level
It was previously assigning levels 1-89 to caution and 90+ to full, which was unintended due to the floor division by 100
2019-08-20 09:35:31 +02:00
Andreas Schneider
a868b477e1 Fix reading and storing DPI in config settings
Fixes #328
Fixes #356
Fixes #545
2019-08-20 09:24:22 +02:00
spaced
c799e038de added to list of supported devices 2019-08-20 09:23:38 +02:00
spaced
8468be2126 add doc for silent mouse M585/M590 2019-08-20 09:23:38 +02:00
spaced
1ff9c3a40b support for silent mouse M585/M590 2019-08-20 09:23:38 +02:00
Chris Johnston
9b201f6b0d copy updated README content 2019-07-27 15:56:53 +01:00
Chris Johnston
afda6f9f66 remove all google analytics stuff from page templates 2019-07-27 15:56:53 +01:00
Chris Johnston
5cc0b8854d update config.yml for new repo path 2019-07-27 15:56:53 +01:00
Chris Johnston
f88951c56d Squash previous changes and rebase master branch
Set theme jekyll-theme-slate

update _config.yml

move some files around

add an index file

delete files that will no longer be needed

include jekyll front matter

Update the index page layout to use updated template

re-create the page layout from the updated template

add a favicon

remove manual gh pages build script

use master branch docs/ folder instead

add favicon to default layout

move layouts and includes back because of restriction with gh pages

testing: move index back into project root

move everything under docs, including config.yml

dont put the favicon in _includes

Fix building locally, fix logo

- resize the original logo document so that it doesn't have extra whitespace
- style the logo to match the original page
- fix issue when building locally, repo format was incorrect

Ignore files that are specific to documentation

add a readme that describes how to build the docs

remove downloads, since installation instructions are provided elsewhere

fix broken links in index page

fix the page header on all non-index pages

use site.baseurl for images

fix urls when on testing site with baseurl
2019-07-27 15:56:53 +01:00
Filipe Laíns
2aab7f71fd release 1.0.1
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2019-07-26 16:55:51 +01:00
Filipe Laíns
6376980213 udev: fix c52b + MX Master
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2019-07-26 17:50:13 +02:00
Filipe Laíns
1bb08d011d base-usb: add new Lightspeed receiver
used in the G305

Signed-off-by: Filipe Laíns <lains@archlinux.org>
2019-07-26 15:23:55 +01:00
Filipe Laíns
966c55e69d base-usb: fix non-unifying for linux 5.2
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2019-07-26 15:18:24 +01:00
Filipe Laíns
6b14004a6f udev: fix writting to the device
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2019-07-26 15:10:59 +01:00
Daehyeok Mun
7a4f7bbb84 docs: fix broken github.io link to new repo url
Signed-off-by: Daehyeok Mun <daehyeok@gmail.com>
2019-07-26 15:38:16 +02:00
Matthias Fulz
155e2f8c40 fix: #531 #537. Better handling of EPIPE during hid write. 2019-07-26 15:35:21 +02:00
Filipe Laíns
6a1968beff release 1.0.0
Signed-off-by: Filipe Laíns <lains@archlinux.org>
2019-07-26 14:33:16 +01:00
Filipe Laíns
f4557233f1 base-usb: add support for Lightspeed receivers
They way we are currently identifying the type of a receiver (unifying,
nano, lightspeed) in the Receiver class is pretty bad. The correct
approach would be to specify the receiver type name string in
base_usb.py.

Signed-off-by: Filipe Laíns <lains@archlinux.org>
2019-07-16 11:10:03 +02:00
muzena
a597b81f5a Update Croatian translation 2019-07-14 23:57:15 +01:00
Peter Wu
a587ae65d1 Merge pull request #477 from doctor64/pointer_info_show
Added status info print and rename some functions
2019-06-29 18:08:52 +02:00
Peter Wu
6522b5fef0 Merge pull request #514 from pdecat/fix-zh_TW
Fix zh_TW translation (nplural=1)
2019-06-29 18:05:49 +02:00
Peter Wu
7f78f0c580 Merge pull request #515 from pdecat/fix-icon-dep
Add gnome-icon-theme as an acceptable dependency for icons
2019-06-29 18:05:14 +02:00
Peter Wu
127a8053d8 Merge pull request #521 from kacpi2442/master
Added basic support for Lenovo dongle,
https://download.lenovo.com/consumer/options/lenovo_n50_wireless_optical_mouse.pdf
2019-06-29 18:04:25 +02:00
Patrick Decat
6c0acee645 Fix UnicodeDecodeError with ngettext and python2 2019-06-29 18:01:54 +02:00
nicolas
60c52eb13f Name for K780
“Multi-Device” and not “Solar”
2019-06-29 17:59:44 +02:00
Benoit Hanotte
8bac8d68f4 Add MX Master settings
Add the same settings to the logitech MX Master as the one from the MX
Master 2 (scroll wheel DPI, smart scoll sensitivity).
2019-06-29 17:57:02 +02:00
Maarti
087a5d2c42 Update of the installation instructions for Debian (#490)
Solaar is available on official Debian repository and the old repo link
`https://pwr.github.io/Solaar/packages/` is dead and causes `404 Not
Found` error and `The repository 'http://pwr.github.io/Solaar/packages
./ Release' does not have a Release file.` error when doing `apt update`
so I updated the installation instructions.
2019-06-29 17:56:07 +02:00
kacpi2442
481511b6c2 Added basic support for Lenovo dongle 2019-04-15 15:40:40 +02:00
Patrick Decat
21d98294bd Add gnome-icon-theme as an acceptable dependency for icons 2019-03-26 15:46:14 +01:00
Patrick Decat
ebbb54758b Fix zh_TW translation (nplural=1) 2019-03-26 15:12:44 +01:00
Peter Wu
c07c115ee3 Avoid collision for wpid 4055 (M185 vs M235)
Hopefully use of a slash does not cause issues. M505 already uses slash
in its codename.

Fixes #499
2019-01-30 16:58:00 +01:00
Peter Dave Hello
8c02320795 docs/devices: Add m235 device information 2019-01-30 16:55:24 +01:00
Markus Heiser
932164458a icons: add application icons from repo's $git-toplevel/share/solaar/
Add application path from the repository to the search paths.

When you Install a project in editable mode (i.e. setuptools "develop mode")
from a local project path, the application path is ``./share``, relative to
git's top level folder. Add this path at the end of search path::

  echo "$(git rev-parse --show-toplevel)/share"
2019-01-30 12:38:54 +01:00
Markus Heiser
de79bf1b5c gtk.main(): make source more readable + comment about cli & gui 2019-01-30 12:38:54 +01:00
Markus Heiser
5944103aef setup.py: fix install_requires and add comment about OS packages 2019-01-30 12:38:54 +01:00
Markus Heiser
ab4226e066 setup.py: fix ModuleNotFoundError: No module named 'solaar'
Fix the "chicken or the egg" problem: while installing solaar, setup.py tries to
import solaar. This will only work if solaar already is installed before. On
first time installation a import exception is raised.::

$ pip install git+https://github.com/pwr/Solaar.git
Collecting git+https://github.com/pwr/Solaar.git
  Cloning https://github.com/pwr/Solaar.git to /tmp/pip-req-build-xzyoskf5
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-req-build-xzyoskf5/setup.py", line 11, in <module>
        from solaar import NAME, __version__
    ModuleNotFoundError: No module named 'solaar'
2019-01-30 12:38:54 +01:00
Peter Dave Hello
a66121db0d Optimize PNG images losslessly with zopflipng (#484)
11 images recompressed as below:

jekyll/images/bg_hr.png              | Bin 943 -> 96 bytes
jekyll/images/blacktocat.png         | Bin 1428 -> 432 bytes
jekyll/images/icon_download.png      | Bin 1162 -> 230 bytes
jekyll/images/sprite_download.png    | Bin 16799 -> 14650 bytes
share/solaar/icons/light_000.png     | Bin 1750 -> 1657 bytes
share/solaar/icons/light_020.png     | Bin 1942 -> 1895 bytes
share/solaar/icons/light_040.png     | Bin 1578 -> 1461 bytes
share/solaar/icons/light_060.png     | Bin 2442 -> 2319 bytes
share/solaar/icons/light_080.png     | Bin 1910 -> 1803 bytes
share/solaar/icons/light_100.png     | Bin 2376 -> 2249 bytes
share/solaar/icons/light_unknown.png | Bin 1108 -> 1007 bytes
2019-01-30 12:36:20 +01:00
Peter Dave Hello
eacfdea37f Add zh_TW Traditional translation po file (#485) 2019-01-30 12:35:35 +01:00
Peter Dave Hello
68b1f4e413 Update Data of Wireless Mouse M235 (#486) 2019-01-30 12:35:20 +01:00
CD Athuraliya
947035a7b7 Add K400 Plus (#492) 2019-01-30 12:30:36 +01:00
Xaver Maierhofer
e54134cda9 Update arch package information (#496) 2019-01-30 12:29:58 +01:00
Beni Cherniavsky-Paskin
1609c3e98a Display "K400 Plus" rather than just "Plus" (#483) 2018-12-30 18:19:38 +01:00
Alex Cherkayev
ed9aa76547 Added status info on features MOUSE_POINTER, VERTICAL_SCROLLING, HI_RES_SCROLLING, POINTER_SPEED and LOWRES_WHEEL to solaar show.
Renamed functions for HI_RES_SCROLLING and LOWRES_WHEEL features for consistency.
2018-11-20 16:29:38 +02:00
Danny
b8529030e6 setup.py: detect "--prefix=" correctly. (#433) 2018-08-15 18:46:50 +02:00
Peter Wu
1412a07899 Merge pull request #440 from nostrad/master
Improve trackball support
2018-08-15 15:14:08 +02:00
Mattias Jernberg
f79ad67ea7 Add trackball for all settings that mice have 2018-08-13 18:44:16 +02:00
Mattias Jernberg
dd8018a4d5 Prepare for allowing multiple device kinds in settings 2018-08-13 18:44:16 +02:00
Mattias Jernberg
350784eb70 Print an error message when assertion fails 2018-08-13 18:36:52 +02:00
Peter Wu
f0fc63e5b7 Merge pull request #295 from cschreib/master
Added support for MintX icon set (for Linux Mint integration)
2018-08-13 11:33:10 +02:00
Peter Wu
51375d4510 Merge pull request #454 from lopsided98/tree-scroll-fix
Fix device tree GUI hierarchy (Closes #453).
2018-08-12 12:22:50 +02:00
Ben Wolsieffer
2c0ea34694 Fix device tree GUI hierarchy. 2018-08-11 16:24:15 -04:00
Peter Wu
867edf8516 Merge pull request #146 from tristianc/master
Added border around tree view.
2018-08-11 17:55:37 +02:00
Peter Wu
dcd1a88250 Merge pull request #390 from Toshik1978/master
Added Logitech K375s support
2018-08-11 16:36:13 +02:00
Peter Wu
8f95dd323d Merge pull request #410 from lckarssen/add-mx-ergo
Update devices.md with MX Ergo trackball
2018-08-11 16:27:29 +02:00
Peter Wu
336b778398 Merge pull request #452 from lopsided98/hidpp-4.5-fixes
Fix errors related to sleep and power on/off (especially HID++ >=2.0 devices).

Closes #414 by @SonicFrog who originally came up with a similar fix for the connection notice.
2018-08-11 16:26:25 +02:00
Peter Wu
572c7f5e59 Merge pull request #450 from lopsided98/new-m705
Add support for new version of M705 (M-R0073).
2018-08-11 16:11:37 +02:00
Peter Wu
6b12b7935f Merge pull request #428 from Lin-Buo-Ren/patch-add-mk240-nano-information
Information and comments regarding Logitech MK240 NANO Combo
2018-08-11 16:06:08 +02:00
Peter Wu
ade2fbacec Merge pull request #399 from Valantin/patch-1
Add MX Master 2S descriptor
2018-08-11 16:03:18 +02:00
Ben Wolsieffer
a59368f3e7 Fix issues with HID++ >= 2.0 devices (particularly related to sleep).
* Don't assume 0x41 messages only occur when a device is first paired
    (prevents errors when waking from sleep or turning a device on)
  * Delay reads/writes when a device is powered on, to prevent broken pipe
    errors (hacky solution).
  * Don't clear status when a device connects, preventing settings from being
    cleared when a device sleeps or is turned off.
  * Fix typos.
2018-08-10 23:15:37 -04:00
Jason Tibbitts
d021d87656 Python 3.7 compatibility fixes
This fixes the python 3.7 incompatibilities arising from 'async'
becoming a reserved word.

The file lib/solaar/async.py is renamed to asks.py.  I picked the name
because it defines the TaskRunner class and it's the best I could come
up in fifteen seconds.

The async function in solar/ui/__init__.py is renamed to ui_async, and
the various imports of that function are changed to match.

Without this patch it doesn't build at all.  I am running with this
patch applied and everything appears to work as expected.

Signed-off-by: Jason Tibbitts <tibbs@math.uh.edu>
2018-08-07 12:51:32 -04:00
Ben Wolsieffer
5d0d353c74 Add support for new version of M705 (M-R0073). 2018-08-05 00:10:59 -04:00
林博仁(Buo-ren, Lin)
e00d680fc9 Information and comments regarding Logitech MK240 NANO Combo
Signed-off-by: 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com>
2018-04-12 14:15:47 +08:00
Peter Wu
59b7285fdf Merge pull request #412 from aimylios/aimylios-fedora
Update link to Fedora package in README.md
2018-02-02 16:20:47 +01:00
aimylios
988bdc2c8d Update link to Fedora package in README.md
The "Fedora Package DB" is not updated any more, refer to the new "Fedora Packages" instead.
2018-01-14 12:15:38 +01:00
Peter Wu
1a67b33860 Merge pull request #382 from mchehab/fix_solaar_start_issues
Fix solaar start issues
2018-01-12 18:11:45 +01:00
Lennart C. Karssen
d43bffc9d9 Update devices.md with MX Ergo trackball
This commit adds the Logitech MX Ergo to the list of supported devices.
2018-01-04 16:52:23 +01:00
Roberto Valentini
f8079f4f9d Add MX Master 2S descriptor
Add descriptor for MX Master 2S to support High Resolution Wheel, correct charge level and DPI configuration
2017-11-16 14:53:27 +01:00
Anton Krivenko
47e7f997d5 Added Logitech K375s support 2017-10-14 22:08:40 +03:00
Peter Wu
8c0cf9fe9b Merge pull request #342 from jrbenito/MK270-desc
Mk270 Combo information and descritptors
2017-09-26 23:16:58 +01:00
Peter Wu
fa8bd83ba3 Merge pull request #387 from ryantig/patch-1
Minor installation.md edit; gpasswd cmd
2017-09-26 23:13:59 +01:00
ryantig
697fb999b4 Minor installation.md edit; gpasswd cmd
Add '-a' after gpasswd to add USER to group plugdev.
Per: ➜ gpasswd --help
Usage: gpasswd [option] GROUP

Options:
  -a, --add USER                add USER to GROUP
  -d, --delete USER             remove USER from GROUP
  -h, --help                    display this help message and exit
  -Q, --root CHROOT_DIR         directory to chroot into
  -r, --remove-password         remove the GROUP's password
  -R, --restrict                restrict access to GROUP to its members
  -M, --members USER,...        set the list of members of GROUP
  -A, --administrators ADMIN,...
                                set the list of administrators for GROUP
Except for the -A and -M options, the options cannot be combined.
2017-09-26 09:23:35 -07:00
Josenivaldo Benito Jr
aeb943fc9e Descriptor for K270 and M185
This K270 is the non unifying version and we know that it has, at
least, one variation (unifying) out there.

M185 mouse is also older, as per PR #337 there is at least two newer
versions of this mouse.

Signed-off-by: Josenivaldo Benito Jr <jrbenito@benito.qsl.br>
2017-09-25 14:57:11 -03:00
Josenivaldo Benito Jr
434af009cb Information regarding MK270 combo
This combo is composed by K270 keyboard (non unifying version) and M185
mouse. Both paired to a nano receiver with PID c534.

Signed-off-by: Josenivaldo Benito Jr <jrbenito@benito.qsl.br>
2017-09-25 14:55:47 -03:00
Peter Wu
cc010cb653 Merge pull request #385 from mchehab/pt-br-translation
Update pt_BR translation
2017-09-21 01:52:22 +01:00
Mauro Carvalho Chehab
e450e959a6 Update pt_BR translation
Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
2017-09-20 21:48:08 -03:00
Peter Wu
10f3421021 Merge pull request #345 from jrbenito/M510_v4.5
Add M510 (new version) to descriptors
2017-09-21 00:30:31 +01:00
Peter Wu
8eba60d617 Merge pull request #360 from lasers/remove-dead-links
Remove dead links
2017-09-21 00:25:11 +01:00
Peter Wu
e3d658ea10 Merge pull request #351 from mchehab/mx_anywhere2-v5
Mx anywhere2 v5
2017-09-21 00:16:15 +01:00
Peter Wu
ef1c051e10 Merge pull request #383 from skarmoutsosv/skarmoutsosv-patch-1
Update el.po
2017-09-21 00:11:04 +01:00
Peter Wu
bc566718b4 Merge pull request #384 from michal2229/master
Updated Polish translation
2017-09-21 00:09:05 +01:00
Michal B
22719f21fc Updated Polish translation 2017-09-19 20:00:43 +02:00
Vangelis Skarmoutsos
c51daa922c Update el.po 2017-09-11 21:02:14 +03:00
Mauro Carvalho Chehab
632d8804be listener: don't add elements if queue is full
When Solaar is loaded, if a large number of events happen,
it will lose the register events, as the queue size is too
small (16). So, check if the queue is full, in order to
avoid losing those important events.

Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
2017-09-11 10:36:12 -03:00
Mauro Carvalho Chehab
a4b7194490 listener: fix device registration logic
The "already_known" var actually doesn't track if the device was
already registered or not.

That causes race issues at Solaar, causing it to sometimes not
detect a device.

Change the logic to always call register_new_device if the
corresponding events happen, and updating already_known
to reflect it.

Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
2017-09-11 10:34:23 -03:00
Mauro Carvalho Chehab
ea80c6d639 ui: notify.py: disable python Notify extension
For whatever reason, this doesn't work on Fedora 26:

06:47:05,925    DEBUG [ReceiverListener:hidraw1] logitech_receiver.base: (13) => r[20 02 0100 0000000000000000000000]
06:47:05,925    DEBUG [ReceiverListener:hidraw1] logitech_receiver.notifications: <PairedDevice(2,2011,K520)> (1.0) custom notification Notification(2,01,00,000000000$
06:47:05,925  WARNING [ReceiverListener:hidraw1] logitech_receiver.notifications: <PairedDevice(2,2011,K520)>: unrecognized Notification(2,01,00,000000000000000000000$
06:47:08,806    ERROR [MainThread] solaar.ui.notify: showing <Notify.Notification object at 0x7f82c2484640 (NotifyNotification at 0x556fa0fc5a40)>
Traceback (most recent call last):
  File "./devel/solaar/lib/solaar/ui/notify.py", line 145, in show
    n.show()
Error: g-io-error-quark: Error calling StartServiceByName for org.freedesktop.Notifications: Timeout was reached (24)


So, disable it.

Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
2017-09-11 07:02:48 -03:00
Mauro Carvalho Chehab
a01e4b2efe hidpp20.py: handle gracefully errors at REPROG_CONTROLS_V4
At least with Anywhere Mouse MX 2, one reprogrammed key
fails to read:

    ERROR [MainThread] logitech_receiver.base: (3) device 3 error on feature request {0829}: 2 = invalid argument

That causes "solaar show" to crash.  Instead, let's handle
errors there gracefully, reporting it as:

         7: unknown:00C3              , default: unknown:009C                => unknown:00C3
             virtual, pos:0, group:4, gmask:0

Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
2017-09-10 22:53:19 -03:00
Mauro Carvalho Chehab
801bdfa224 Remove the hid++ high-res wheel notification
The best is to make the logitech-hidpp driver to switch to
hid++ notification mode when it starts. As we don't want users
to mangle with it, let's remove support from it.

PS.: I opted to keep this as a separate patch. This way, if
anyone needs to add support for it, in order to debug the
driver, it is just a matter of reverting this patch.

Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
2017-09-10 21:49:18 -03:00
Mauro Carvalho Chehab
410d100dc6 Add notification for high-resolution Wheel events
The event at address 0 is only produced while in HID++ mode.

The rachet event (address 0x10) happens on both HID and
HID++ modes.

Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
2017-09-10 21:49:18 -03:00
Mauro Carvalho Chehab
7078750a3f MX Anywhere2: Correct feature Smooth Scrool
Set wrongly to HI_RES_SCROLLING. Correct it to HIRES_WHEEL as reported
by MX Anywhere2.

relates to #283

Signed-off-by: Josenivaldo Benito Jr <jrbenito@benito.qsl.br>
2017-09-10 21:49:18 -03:00
Mauro Carvalho Chehab
360c92f6b0 Add support for CLI show to display High Res Wheel settings
Add support for the high resolution wheel found on MX Anywhere 2.

Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
2017-09-10 21:44:30 -03:00
lasers
2377fef3a8 Remove dead links 2017-07-24 03:44:00 -05:00
Peter Wu
684afa871e Merge pull request #337 from doctor64/m185
Basic support for M185 mouse
2017-07-17 00:08:19 +02:00
Peter Wu
dd2f884504 Merge pull request #354 from luzfcb/patch-1
update ubuntu solaar package informations
2017-04-16 12:20:27 +02:00
Fábio C. Barrionuevo da Luz
9dd05bb727 update ubuntu solaar package informations 2017-04-10 21:26:23 -03:00
Alex Cherkayev
e96a0fbfbb Add docs on both variants of Logitech M185 2017-03-21 18:03:07 +02:00
Alex Cherkayev
f9618dd0ab Add M185 keys reporting using REPROG CONTROLS V4 in cli
Add smooth scrolling using LOWRES_WHEEL
Add mouse speed selection using POINTER_SPEED
2017-03-21 18:02:59 +02:00
Alex Cherkayev
db0656967b Add basic 0xc534 receiver and M185 mouse support
Fix connection notification protocol for M185
Fixes from review
2017-03-21 18:00:44 +02:00
Peter Wu
53ec751cf1 Merge pull request #343 from jrbenito/K270-unifying
Wireless Keyboard K270 unifying
2017-03-21 00:07:26 +01:00
Peter Wu
45cc161cc7 Merge pull request #344 from jrbenito/MK520
MK520 Combo - Documentation
2017-03-21 00:02:52 +01:00
Peter Wu
1ccd2d1856 Merge pull request #346 from jrbenito/MK220-mouse
MK220 Combo descriptor for Mouse M150 and new docs
2017-03-20 23:58:49 +01:00
Josenivaldo Benito Jr
d9d37edc2f MK220 Combo descriptor for Mouse M150 and new docs
Thanks to @FabioBeneditto for providing information.

Depends on #337

fixes #222

Signed-off-by: Josenivaldo Benito Jr <jrbenito@benito.qsl.br>
2017-03-10 10:47:23 -03:00
Josenivaldo Benito Jr
ae405871fb Add M510 (new version) to descriptors
Depends on #337

fixes #279

Signed-off-by: Josenivaldo Benito Jr <jrbenito@benito.qsl.br>
2017-03-08 19:48:23 -03:00
Josenivaldo Benito Jr
f4700f07ce MK520 Combo
Wireless Keyboard K520
Wireless Mouse M310

Fully functional on Solaar at this point.

Signed-off-by: Josenivaldo Benito Jr <jrbenito@benito.qsl.br>
2017-03-08 18:59:36 -03:00
Josenivaldo Benito Jr
d2d19a615d Wireless Keyboard K270 unifying
Descriptor and docs for K270 unifying version.

Signed-off-by: Josenivaldo Benito Jr <jrbenito@benito.qsl.br>
2017-03-08 16:23:30 -03:00
Peter Wu
c36cc506b6 Merge pull request #327 from david-geiger/master
Update French translation
2017-01-03 12:27:22 +01:00
daviddavid
7180693f89 Update French translation
- by David Geiger <david.david@mageialinux-online.org>
2016-12-31 16:43:44 +01:00
Peter Wu
aa08356823 Merge pull request #144 from DJm00n/master
Russian translation added.
2016-12-23 15:16:06 +01:00
Dimitriy Ryazantcev
197ff7365d Add Russian translation 2016-12-21 00:18:01 +02:00
Dimitriy Ryazantcev
a0c6a6ce59 Update l10n pot template 2016-12-21 00:18:01 +02:00
Dimitriy Ryazantcev
934ecb821d Update l10n strings in code 2016-12-21 00:18:01 +02:00
Peter Wu
f0cf949336 Merge pull request #322 from jrbenito/anywhere2
Anywhere MX 2 device information and descriptor
2016-12-20 21:29:10 +01:00
Peter Wu
ecd245e8dc Merge pull request #317 from jrbenito/m560-devinfo
M560 device info contributed by @mhalano
2016-12-03 02:19:04 +01:00
Peter Wu
f65f000d38 Merge pull request #319 from jrbenito/k780
K780 add descriptor support and device information
2016-12-03 02:03:35 +01:00
Josenivaldo Benito Jr
3b60f205f7 K780 add descriptor support and device information
Device information provided by @faassen
Tests provided by @ralphdd

relates to #298

Signed-off-by: Josenivaldo Benito Jr <jrbenito@benito.qsl.br>
2016-11-30 14:38:44 -02:00
Josenivaldo Benito Jr
d6c93cfcfe Anywhere MX 2 device information and descriptor
Information from mouse Anywhere MX 2 as provided by @fropeter
Descriptor for mouse based on dump provided

Signed-off-by: Josenivaldo Benito Jr <jrbenito@benito.qsl.br>
2016-11-30 14:24:50 -02:00
Josenivaldo Benito Jr
cbd71f9d24 M510 HID++ V4.5 information
Dump from M510, newer version which uses HID++ V4.5 instead of V1.0.
Wireless PID 4051

Signed-off-by: Josenivaldo Benito Jr <jrbenito@benito.qsl.br>
2016-11-30 14:07:42 -02:00
Josenivaldo Benito Jr
45d5119a5a M560 device info contributed by @mhalano
Signed-off-by: Josenivaldo Benito Jr <jrbenito@benito.qsl.br>
2016-11-30 14:07:27 -02:00
Peter Wu
773cb7f45f Merge pull request #312 from jrbenito/rules
Add receiver to udev rules
2016-11-29 02:16:36 +01:00
Josenivaldo Benito Jr
d74e3a9b92 Add receivers to udev rules
Sync lib/logitech_receiver/base_usb.py PIDs with udev rules

Fixes #125

Signed-off-by: Josenivaldo Benito Jr <jrbenito@benito.qsl.br>
2016-11-24 09:54:24 -02:00
Peter Wu
fe1fba7ac6 Merge pull request #316 from david-geiger/master
Add French translation on desktop file and update maintainer in README.
2016-11-24 11:09:46 +01:00
daviddavid
4e20ef61c8 Add French translation on desktop file
- update README.md
2016-11-23 20:38:30 +01:00
Peter Wu
a750b32d14 Merge pull request #313 from jrbenito/doc
Update installation to point correct rules file
2016-11-23 13:46:27 +01:00
Josenivaldo Benito Jr
69006c48c6 Update installation to point correct rules file
Signed-off-by: Josenivaldo Benito Jr <jrbenito@benito.qsl.br>
2016-11-23 09:55:07 -02:00
Peter Wu
b12d03dfe3 Merge pull request #311 from jrbenito/nanoDocs
README nano receiver broken link
2016-11-22 23:46:00 +01:00
Peter Wu
ddbf93fb24 Merge pull request #309 from jrbenito/pt-br
pt-br: Improve translations
2016-11-22 23:22:23 +01:00
Josenivaldo Benito Jr
65c55b789c README nano receiver broken link
Fixes #261

Signed-off-by: Josenivaldo Benito Jr <jrbenito@benito.qsl.br>
2016-11-22 11:37:40 -02:00
Josenivaldo Benito Jr
d83530bee3 pt-br: Improve translations
Some pt-br translation where ambiguous or too missing 'not' statement.
Add original translator, Mr. Drovetto, to the documentation.

Signed-off-by: Josenivaldo Benito Jr <jrbenito@benito.qsl.br>
2016-11-21 17:25:22 -02:00
Peter Wu
e6c02f5c52 Merge pull request #303 from jrbenito/UTF8
UTF-8 file paths error on python 2
2016-11-18 22:22:11 +01:00
Josenivaldo Benito Jr
122d76d061 UTF-8 file paths error on python 2
Python 2 needs UTF-8 decode since it uses 'ascii' decode by default.
Python 3 might have problems converting back to UTF-8 in case of Unicode
surrogates

Signed-off-by: Josenivaldo Benito Jr <jrbenito@benito.qsl.br>
2016-11-18 15:00:20 -02:00
muzena
fb1442dbcb Add croatian translation 2016-11-15 16:34:20 +01:00
muzena
0b8141f092 Logitech G700 and G700s Gaming mouse support 2016-11-14 16:39:23 +01:00
Peter Wu
137c32262b Merge pull request #242 from drovetto/master
Added brazilian portuguese translation
2016-11-10 13:20:25 +01:00
Peter Wu
7c726738b1 Merge pull request #185 from zipperten/patch-4
Update sv.po
2016-11-10 13:19:35 +01:00
Peter Wu
2570f584b9 Merge pull request #228 from berte/master
add turkish translation file
2016-11-10 13:07:29 +01:00
Peter Wu
4c1f568fab Merge pull request #141 from ghost
Italian translations
2016-11-10 13:04:42 +01:00
Peter Wu
27bd71253e Merge pull request #129 from Tomin1/finnish
Finnish translations
2016-11-10 12:57:06 +01:00
Jhonny Oliveira
9a9a82016f Add support to G700 Gaming Mouse receiver 2016-11-10 12:25:54 +01:00
Jhonny Oliveira
aa03cef61f Add support to MK320 mouse and keyboard combo 2016-11-08 09:17:02 +01:00
Peter Wu
c14e30b6f5 Fix crash when AppIndicator3 is unavailable
Convert ValueError to an ImportError (hack!) to allow the fallback code
to function.

Fixes https://github.com/pwr/Solaar/pull/276
2016-11-06 19:50:32 +01:00
cschreib
b5d43cdc3c Added support for MintX icon set (for Linux Mint integration) 2016-10-22 13:49:50 +02:00
Peter Wu
1edd8a577b Merge pull request #290 from verybadsoldier/invert-online-bit
Fixes #289.
2016-10-03 01:58:51 +02:00
vbs
358e0958bb fixed wrong interpretation of bit 6 of the notification byte which indicates if the link could be established (bit cleared) or not (bit set)
https://lekensteyn.nl/files/logitech/logitech_hidpp10_specification_for_Unifying_Receivers.pdf
2016-09-17 18:05:03 +02:00
Peter Wu
fa5fba796b Merge pull request #263 from MagicFab/patch-1
Update es.po
2016-09-04 22:02:38 +02:00
Peter Wu
ce215228f3 Merge pull request #277 from wsargent/patch-1
Use HTTPS for github based repositories
2016-08-09 15:02:03 +02:00
Peter Wu
28e43395ab Merge pull request #259 from javitonino/support-mx-master
Support mx master
2016-07-24 23:09:28 +02:00
Will Sargent
e09231c05a Use HTTPS for github based repositories
Github Pages support HTTPS natively, so it's better for security to default to that.
2016-07-12 09:02:28 -07:00
Javier Torres
f29de0f3dd Update docs: mx master supports smart shift 2016-06-09 19:46:03 +02:00
Javier Torres
ba540338e2 Add smart shift feature for MX master 2016-06-09 19:46:03 +02:00
Javier Torres
2442fee341 Add support for range features in GUI 2016-06-09 19:46:03 +02:00
Javier Torres
bbadd3e755 Add support for range features in CLI 2016-06-09 19:46:03 +02:00
Javier Torres
2fdce2f938 Add range features 2016-06-09 19:46:03 +02:00
Javier Torres
f1fad2d16b Add range validator 2016-06-09 19:46:03 +02:00
Peter Wu
8a5700b44e Merge pull request #270 from cjmayo/appindicator
Fix: "AppIndicator3 was imported without specifying a version first"
2016-05-21 19:28:19 +02:00
Chris Mayo
e046455a05 Fix: "AppIndicator3 was imported without specifying a version first"
PyGIWarning: AppIndicator3 was imported without specifying a version
first. Use gi.require_version('AppIndicator3', '0.1') before import to
ensure that the right version gets loaded.
2016-05-03 20:20:46 +01:00
Peter Wu
ddbc27486c Merge pull request #136 from erik-smit/gobject_not_allow_None
Fixes a TypeError on older GObject libraries (as evident with Ubuntu 12.04 and OpenSUSE 12.3).
2016-04-17 14:11:15 +02:00
Peter Wu
2041007b38 Merge branch 'features'
Automatically detect FN swap feature and DPI adjustment on some newer
devices. DPI adjustment partially addresses support for the MX Master
(#208), Smart shift is still missing.
2016-04-17 12:43:15 +02:00
Fabian Rodriguez
7b7e17819e Fixed "Hardware" following comments 2016-04-01 07:55:13 -04:00
Fabian Rodriguez
f855cd60e1 Update es.po
MIssing spaces, small terms corrections, typos
2016-03-31 19:22:55 -04:00
Peter Wu
883ed9549d Fix DPI list assertion 2016-03-25 00:06:00 +01:00
Peter Wu
d1858f747b Assume 7 words for the DPI list response
HID++ 2.0 responses are 20 bytes, once you strip the 4 byte common
header and 1 byte sensorIdx, you have 15 bytes left. Since DPI values
are 16-bit words, only 14 bytes should be used.
2016-03-24 16:59:05 +01:00
Peter Wu
aa7d1b6410 Skip sensorIdx in getSensorDpiList response 2016-03-24 15:13:06 +01:00
Peter Wu
bc3a98ddb2 Merge pull request #258 from cjmayo/gentoo
readme Gentoo link updated with official package
2016-03-24 14:58:34 +01:00
Peter Wu
5b01f375e0 Merge pull request #254 from emerham/master
Adding Logitech mx 1100 cordless laser mouse to discriptors
2016-03-24 14:56:55 +01:00
Peter Wu
dd2755909d cli/config: fix error message
Attempt to fix:

    $ bin/solaar config master dpi higher
    solaar: error: coercing to Unicode: need string or buffer, int found

The DPI choices are integers, therefore cast it to a str.
2016-03-18 12:27:16 +01:00
Peter Wu
b052ab9ef0 Fix thinko in Adjustable DPI setting
There are three bytes forming the parameter, the sensor ID is the MSB,
not LSB.
2016-03-18 12:14:15 +01:00
Peter Wu
9c768d60a1 Add full support for adjustable DPI
Feature 0x2201 as used by the MX Master. Valid DPI values are read
directly from the device. Based on Logitech specifications.
2016-03-15 23:37:39 +01:00
Matthew Brabham
d1d0ab85ff Updated mouse device settings and deffinition. Added docs for the MX 1100 2016-03-14 13:05:05 -07:00
Peter Wu
e1eee2e078 Fix "Gtk was imported without specifying a version first"
Fixes the following warnings:

    sys:1: PyGIWarning: Gtk was imported without specifying a version first. Use gi.require_version('Gtk', '3.0') before import to ensure that the right version gets loaded.
    sys:1: PyGIWarning: Notify was imported without specifying a version first. Use gi.require_version('Notify', '0.7') before import to ensure that the right version gets loaded.
2016-03-14 11:33:05 +01:00
Peter Wu
5c38f90cd6 Merge branch 'issue/199'
Fixes #136.
2016-03-14 11:10:49 +01:00
Peter Wu
45d49b9de8 settings: fix Python 3 compat for boolean values
As observed for the fn-swap setting.
2016-03-14 01:48:56 +01:00
Peter Wu
4ccb8ab26d Merge pull request #257 from hobarrera/subtle-critical-battery-notification
Don't open a main window when battery is critical
2016-03-14 00:36:26 +01:00
Peter Wu
ab162583e4 cli: do not die on missing description
The DPI setting has no description, do not try to display it.
2016-03-13 23:59:21 +01:00
Chris Mayo
e25b47aa1c readme Gentoo link updated with official package 2016-03-05 16:27:16 +00:00
Hugo Osvaldo Barrera
63a98819a6 Don't open a main window when battery is critical
The main window suddenly opening when a battery is critical is very
disruptive. It can pop up on all sort of undesirable scenarios.

Rather than catch users of guard, merely show a notification that the
battery is critical.
2016-02-19 03:25:55 -03:00
Peter Wu
27d3d80d30 Merge pull request #256 from SlySven/add_m175DestinguishingDetails
add: wpid for M175 to distinguish between it and M185 wireless mouse (Fixes #255)
2016-02-11 18:30:02 +01:00
Stephen Lyons
478381a994 add: wpid for M 175 to distinguish between it and M185 wireless mouse
I own the former and wanted to correct this detail which caused the wrong
model to be shown in GUI.

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2016-02-10 19:09:25 +00:00
Matthew Brabham
51a15a960f Adding Logitech mx 1100 cordless laser mouse to discriptors 2016-01-26 11:04:47 -08:00
Peter Wu
92967eed23 Merge pull request #249 from mulkieran/master-pyudev
Fix some bugs in udev.get_indexed_string.
2016-01-13 15:24:51 +01:00
Peter Wu
57338303e8 Merge pull request #239 from marcbelmont/patch-1
Add setting for K400 Plus keyboard

devices.md was updated in #174
2016-01-09 12:37:35 +01:00
Peter Wu
e735ce10f0 Merge pull request #174 from IrvinPoe/patch-1
Update devices.md with K400 Touch
2016-01-09 12:36:27 +01:00
mulhern
d75b6d2f2b Fix some bugs in udev.get_indexed_string.
* from_device_number raises an exception if no device found, rather than
returning None. So, instead of checking the result, catch the exception.
* Use Attributes.get() method instead of checking containment and using the
index operator. This is really the only correct way, see rhbz#1267584.

Effect of the changes:
Previously, if no device was found this method would raise an exception.
Now it returns None instead.
Previously this method read the value of the attribute corresponding to key
twice, once via 'key in attributes' and again when accessing the key.
Now, it just reads it the one time.

Reason for noticing all these problems:
In pyudev-0.18 the Attributes class is fixed, and no longer has the []
operator or __contains__ method or other methods which require a total
mapping. This patch fixes several bugs while simultaneously avoiding these
removed operators.

Signed-off-by: mulhern <amulhern@redhat.com>
2015-12-23 09:22:48 -05:00
sadj
bd1470dad7 Added brazilian portuguese translation. 2015-10-08 18:52:14 -03:00
Marc Belmont
15f97681ef Add setting for K400 Plus keyboard
The new_fn_swap works on the K400 Plus model.
2015-08-24 19:01:21 +02:00
Behzat
7075ce910a add turkish translation file 2015-07-16 22:07:38 +03:00
Peter Wu
5ba816dd38 [WIP] Support MX Master with DPI adjustment support (#208)
It's not known whether the DPI ranges can be queried, so let's set
hard-code some values for now. Step size is 200. Does this need to
be changed?

TODO: need a capture of whether this is really a read function.
2015-05-31 15:53:26 +02:00
Peter Wu
a515cc3860 Auto-detect FN swap feature for newer devices 2015-05-31 10:56:57 +02:00
Peter Wu
73344cbf26 Simplify feature checking
Make mapping features to settings more readable. No functional changes.
2015-05-31 10:38:49 +02:00
Peter Wu
cf27328d13 Merge branch 'updates'
Documentation updates.
2015-05-27 23:43:10 +02:00
Peter Wu
869f1e4791 doc/devices.md: add MX Master
Information from (IRC) xaver. See also #208.

Product page at http://www.logitech.com/product/mx-master
2015-05-27 23:40:58 +02:00
Peter Wu
e2d65a690e hidpp20: update features list
Based on SetPoint6.65.62_32.exe and Options_2.10.73.exe (features.xml,
defaults.xml, LogiOptionsMgr.exe).

The names are based on the named next to the feature ID. For duplicates,
_2 is appended to the name.

This commands helps parsing numbers from debug prints:

    strings LogiOptionsMgr.exe |
    sed -nr 's/.*(Feature[0-9a-fA-F]{4})/\1 /p' | sort |
    awk 'tolower(a)!=tolower($1){print"";a=$1}1;' | less -S
2015-05-27 23:20:43 +02:00
Peter Wu
3141f06472 Fix crash due to non-locatable icon names (#199) 2015-05-27 23:16:39 +02:00
Peter Wu
b88cf5a07b Ignore HID++ devices on Linux 3.19+
Additionally update the docuentation to reflect new module names.
2015-05-14 23:28:04 +02:00
Peter Wu
a344ad9867 docs: document Smooth Scrolling for more devices
Based on the HI_RES_SCROLLING feature (0x2120) in docs/devices/. Tested
with a M525.
2015-05-14 23:06:14 +02:00
CzBiX
76e327d2fa update docs for M325 2015-05-08 13:27:29 +08:00
CzBiX
0ae0ef195a support switch smooth scroll feature 2015-05-08 13:20:46 +08:00
Peter Wu
b1f74a1280 Merge pull request #213 from titanofold/add-M570
Add M570 Trackball device information
2015-04-24 09:39:37 +02:00
Aaron W. Swenson
30431fbfb8 Add M570 Trackball detail 2015-04-20 14:18:07 -04:00
Peter Wu
fb4c055fbf Merge pull request #193 from dmsc/master
Add support for MK220 mouse-keyboard combo device.
2015-03-07 18:12:21 +01:00
Daniel Serpell
3ea51fe1a2 Add support for MK220 mouse-keyboard combo device.
The MK220 receiver has usbid 046d:c52e, adding this id as a generic
receiver is enough to show the mouse and keyboard charge status.
2015-03-06 08:07:22 -03:00
Peter Wu
afd36764c2 Merge pull request #202 from decibyte/master
k830 support + support for NEW FN INVERSION
2015-03-06 11:45:53 +01:00
Mikkel Munch Mortensen
b694809394 Updates to dos for k830 keyboard. 2015-03-05 19:17:00 +01:00
Mikkel Munch Mortensen
66296265ba Descriptor for k830. 2015-03-05 19:15:12 +01:00
Mikkel Munch Mortensen
952495ce04 Support for NEW_FN_INVERSION. 2015-03-05 19:14:28 +01:00
Peter Wu
29eb377267 Merge pull request #203 from HugoJH/master
Added Spanish Translation
2015-03-05 15:04:45 +01:00
Hugo Jiménez Hernández
47345e1dcb Added Spanish Translation 2015-03-05 13:05:59 +00:00
Peter Wu
36fc6de760 Merge pull request #168 from jtsagata/master
Add Greek language translation
2015-03-05 13:07:25 +01:00
Peter Wu
6c0aca8c63 Merge pull request #170 from SubOptimal/translation
add german translation
2015-03-05 13:06:52 +01:00
Peter Wu
bd93689109 Merge pull request #171 from dirtycold/master
added Chinese (simplified) translation
2015-03-05 13:06:08 +01:00
Peter Wu
4e5d4853d6 Merge pull request #173 from stickster/master
Add Fedora package information to README.md
2015-03-05 13:05:37 +01:00
zipperten
bb2361a986 Update sv.po
minor changes
2014-10-23 09:49:05 +02:00
IrvinPoe
7c8d4815f0 Update devices.md
Add Nano receiver 046d:c52b in the supported devices, and FN swap works on K400.
2014-08-17 17:47:48 +02:00
Paul W. Frields
0f05589cd6 Add Fedora package information to README.md 2014-07-30 14:39:46 -04:00
ZH
21db60a14e fix compiling Chinese simplified translation 2014-07-13 15:13:10 +08:00
ZH
091c28ca8b added Chinese (simplified) translation 2014-07-13 10:13:17 +08:00
Frank Dietrich
9af353dd32 add german translation 2014-07-02 02:12:16 +02:00
Giannis Tsagatakis
981ff00ca3 Add Greek language translation 2014-06-13 14:32:57 +03:00
Daniel Pavel
ec0197df3d Merge pull request #154 from mmehnert/patch-1
Update TK820 compatibility information
2014-04-20 22:54:27 +03:00
mmehnert
2111107ff4 Update TK820 compatibility information 2014-04-19 14:50:21 +02:00
Tristin Celestin
2b08c5e0c9 Added border around tree view. 2014-01-01 21:45:44 -05:00
Daniel Pavel
cc8a6c4ab5 Merge pull request #134 from FrederikNS/patch-1
Added Smooth Scrolling to the Performance Mouse MX
2013-12-13 09:43:16 -08:00
Michele Olivo
437e17cf37 Added Italian translation 2013-12-13 18:09:46 +01:00
Daniel Pavel
22191d7d0c Merge pull request #139 from parkerlreed/patch-1
Update devices.md
2013-12-13 07:48:21 -08:00
parkerlreed
ba890c2d2e Update devices.md
Added a little bit of info for the M325
2013-12-02 10:46:47 -05:00
erik-smit
9a6cdd9cee TypeError: Argument 3 does not allow None as a value 2013-10-31 11:16:38 +01:00
Frederik Nordahl Sabroe
32324f3e66 Added Smooth Scrolling to the Performance Mouse MX
I tested solaar with my Performance Mouse MX, and the smooth scrolling worked like a charm, I just need to find a way to make the scrolling speed slower now.
System is Linux Mint 15.
2013-10-14 17:18:49 +02:00
Tomi Leppänen
ffe297b239 Merge branch 'finnish' of github.com:Tomin1/Solaar into finnish
Conflicts:
	po/fi.po
	po/solaar.pot
2013-09-29 16:37:33 +03:00
Tomi Leppänen
7fe5983f7c Fixed a bit Finnish translation.
Some fixes to Finnish translation.
A few strings were marked as fuzzy.
Run po-compile.sh.
2013-09-29 16:34:02 +03:00
Tomi Leppänen
d5f14bb5f8 Merge branch 'master' into finnish
Conflicts:
	po/solaar.pot - run po-compile.sh
2013-09-29 16:24:06 +03:00
Daniel Pavel
f3049ed451 Merge pull request #126 from slaveriq/master
Fixing a crash when level is type None
2013-09-25 10:08:09 -07:00
David Plassmann
8485494ea7 It should check for None specificly 2013-09-25 14:12:53 +02:00
David Plassmann
7a75813a84 level is returned as None in hidpp10.py when the performanceMX mouse is
charging. Since the battery state is unknown when beeing recharged it
will now return "N/A".
Before it would crash becasue level was None and could not be inserted
using %d.
2013-09-24 16:56:50 +02:00
Daniel Pavel
35830e3d50 Merge pull request #121 from Lekensteyn/doc-updates
Documentation updates: DJ pdf and devices
2013-09-04 16:56:46 -07:00
Tomi Leppänen
46fd4dac56 Merge branch 'master' into finnish
Conflicts:
	po/solaar.pot - Run tools/po-compile.sh
2013-09-01 12:21:51 +03:00
Peter Wu
ebd8635f84 docs/devices: add K750 (from Julien Danjou)
Received from Julien Danjou on the devkit mailing list.
2013-08-29 19:06:03 +02:00
Peter Wu
efbf43b472 docs/logitech: Add DJ receiver spec
docx files is taken from hidpp20 public folder[1] and was converted to
PDF format using:

    loffice --convert-to pdf ...docx
    qpdf --linearize orig.pdf ...pdf

Mirrored on https://lekensteyn.nl/files/logitech/.

 [1]: https://drive.google.com/#folders/0BxbRzx7vEV7eWmgwazJ3NUFfQ28
2013-08-27 18:06:29 +02:00
Peter Wu
8a6d8e2ed1 docs/devices/m525: add ltunify and solaar output 2013-08-27 18:06:29 +02:00
Peter Wu
bff99799ab docs/devices/t650: add reprogrammable keys 2013-08-27 18:06:29 +02:00
Peter Wu
1434b2a50d Update features and keys list
Updated to information available from SetPoint 6.61.15.
2013-08-27 17:55:45 +02:00
Daniel Pavel
c3fdbfb643 translate battery alerts (fixes #119) 2013-08-25 23:03:25 +03:00
Daniel Pavel
a9ebac264f po formatting updates 2013-08-25 01:34:27 +03:00
Daniel Pavel
60a591ab6b Merge pull request #114 from zipperten/patch-2
Update sv.po
2013-08-24 15:32:23 -07:00
Daniel Pavel
ab3a6b50d4 Merge pull request #118 from Lekensteyn/fixes
Regression fixes: --hidraw option, python3, hidpp10
2013-08-24 14:05:11 -07:00
Peter Wu
b193b39701 Fix missing reprogrammable keys
The assumption that the Features IDs are in increasing order does not
hold. This causes the feature check for REPROG CONTROLS (1B00) to fail,
therefore remove the micro-optimisation.

While at it, rename variables and document the functions better.
2013-08-23 21:33:45 +02:00
Daniel Pavel
806d67a72f Merge pull request #117 from Lekensteyn/doc-updates
docs: added T400, T650, Anywhere MX dumps
2013-08-21 13:12:23 -07:00
Peter Wu
9c465cd998 Fix wrong register name for illumination (hidpp10)
Broken since 9a2a28e, this causes a lot of exceptions for every move.
2013-08-21 21:47:24 +02:00
Peter Wu
024a71b618 solaar: add --hidraw option again
This options allows the devices list to be restricted which got removed
in:

    commit 3b75b69970
    Author: Daniel Pavel <daniel.pavel@gmail.com>
    Date:   Fri Aug 9 12:25:47 2013 +0200

        merged solaar-cli functionality into main solaar binary

solaar-cli is still busted, but since it is deprecated, it's probably
fine.
2013-08-21 21:47:24 +02:00
Peter Wu
f144816256 solaar-cli: fix argument parsing in Python 3 again
Fix from e3a887f36c, this got removed
in:

    commit 3b75b69970
    Author: Daniel Pavel <daniel.pavel@gmail.com>
    Date:   Fri Aug 9 12:25:47 2013 +0200

        merged solaar-cli functionality into main solaar binary
2013-08-21 21:47:24 +02:00
Peter Wu
f0eeea8625 docs/devicesmd: update with T400 and Anywhere MX 2013-08-21 21:45:04 +02:00
Peter Wu
72819b6130 docs/devices: add Anywhere MX mouse
Thanks to Nestor from Logitech for providing me this device.

This mouse has two buttons on the left, currently mapped to 8 (bottom
button) and 9 (top button).

Note: setting bit 1 of register 01 does odd things in KDE. I looked in
SetPoint 6.61.15 and could not find an option for "Side Scroll",
perhaps it is not applicable here?
2013-08-21 21:45:04 +02:00
Peter Wu
23e4f32e11 docs/devices: add T650 touchpad
Thanks to Nestor from Logitech for providing me this device.

The firmware on this touchpad features out-of-the-box tap-to-click
(no host software needed). The following features work with no
additional software (tested in Linux 3.11-rc6 with KDE 4.11):

- Left-click: single tap
- Middle-click ("scroll-wheel click"): three finger tap
- Right-click ("context menu"): two finger tap or tap in right corner
- Navigate left ("Back"): three finger slide to left
- Navigate right ("Forward"): three finger swipe to the right
- Scroll (up/down/right/left): slide with two fingers
- Move pointer (any direction): slide

Click and drag is also possible (for example, for selecting text or
moving windows with Super + right/left-click in KDE), just press harder
on the surface. Right click is in the bottom-right corner, left is
anywhere else.

Sliding with three fingers up acts if you pressed Super. Sliding with
three fingers down triggers a key press of Super + D (normally the
"Show Desktop" in MSWIN).

Not working as it requires additional software:

- Pinch-to-zoom.
- Four-finger gestures, likely needs to be controlled via features as
  there are no HID messages at all.
2013-08-21 21:45:04 +02:00
Peter Wu
243d1548dc docs/devices: add Zone Touch Mouse T400
Thanks to Nestor from Logitech for providing me this device.

User experience note: the upper part (covering two-third) of the middle
button is the "METRO START SCREEN" button, the smaller bottom part
provides the "middle mouse button" (MIDLLE BUTTON). This might be OK
for a desktop user, but it's awful if you have a big hand like me and
make much use of it for opening URLs in a new tab or pasting stuff.

On the other hand, the middle button can be remapped and there is both
horizontal and vertical scrolling functionality.
2013-08-21 21:45:04 +02:00
zipperten
c3bc56f306 Update sv.po
fix for paring lock notification.
2013-08-20 22:49:08 +02:00
Daniel Pavel
288c607069 Merge pull request #113 from david-geiger/patch-1
Ouch. Added 'solaar.cli' on setup.py files (line 74 "packages=").
2013-08-13 02:20:58 -07:00
david_david
55c307d0e5 added 'solaar.cli' on setup.py files (line 74 "packages=")
missing packages 'cli' from "/lib/solaar/" and needed for building package rpm.

[david@david ~]$ solaar -dd
Traceback (most recent call last):
  File "/usr/bin/solaar", line 43, in <module>
    import solaar.gtk
  File "/usr/lib/python2.7/site-packages/solaar/gtk.py", line 26, in <module>
    import solaar.cli as _cli
ImportError: No module named cli
2013-08-13 11:02:13 +02:00
Daniel Pavel
69febfcea5 don't rely on upower to check devices after wake-up (#111)
still use upower resume events to ping devices, just in case
the old behaviour is available with a command-line option
2013-08-12 15:22:34 +02:00
Daniel Pavel
9934755566 more translation fixes (#108) 2013-08-12 00:32:19 +02:00
Tomi Leppänen
a7561eaa9b Updated Finnish translation
Merge branch 'finnish' of github.com:Tomin1/Solaar into finnish

Conflicts:
	po/fi.po
2013-08-11 17:24:06 +03:00
Tomi Leppänen
689661169a Updated Finnish translation. 2013-08-11 17:22:40 +03:00
Tomi Leppänen
eac40abc3e Updated Finnish translation. 2013-08-11 16:56:30 +03:00
Tomi Leppänen
372e4e0949 Merge branch 'master' into finnish
Conflicts:
	po/solaar.pot - run tools/po-compile.sh
2013-08-11 16:48:12 +03:00
Daniel Pavel
e42f7f1393 jekyll configuration update 2013-08-09 12:29:02 +02:00
Daniel Pavel
3860d4a56b changelog update 2013-08-09 12:28:47 +02:00
Daniel Pavel
3b75b69970 merged solaar-cli functionality into main solaar binary 2013-08-09 12:25:47 +02:00
Daniel Pavel
a4f0eab855 updated descriptors with a few more devices 2013-08-08 22:44:23 +02:00
Daniel Pavel
f54ea27d97 updated translator credits in about dialog 2013-08-08 22:43:32 +02:00
Daniel Pavel
db48fe1b40 re-formatted translation files 2013-08-08 22:41:57 +02:00
Daniel Pavel
d0a0e902a7 added translator credits to i18n documentation 2013-08-08 22:38:59 +02:00
Daniel Pavel
95a6412d5c po-update.sh without arguments re-formats all translation files 2013-08-08 22:22:39 +02:00
Daniel Pavel
5faecbf4f3 some strings were untranslated; fixes #100 2013-08-08 21:50:04 +02:00
Daniel Pavel
5af1719384 tray: always pick the lowest battery, if the user hasn't already 2013-08-08 21:49:59 +02:00
Daniel Pavel
6a66370ffe scrolling over the tray icon switches among devices; fixes #101
It now works for the standard systray as well, not just the
appindicator.
2013-08-08 21:49:54 +02:00
Daniel Pavel
c0874220d2 don't str() translated device status; fixes #108 2013-08-08 21:49:50 +02:00
Daniel Pavel
9c67b1b494 debian packaging updates 2013-08-08 21:49:35 +02:00
Daniel Pavel
22656d5b82 use Gtk.Application properly 2013-08-08 21:49:35 +02:00
Daniel Pavel
d55963caba clean-up in usb IDs 2013-08-08 21:49:35 +02:00
Daniel Pavel
413cebe40f delay some UI initialization until necessary 2013-08-08 21:49:35 +02:00
Daniel Pavel
140ffae01a Merge pull request #106 from damsweb/master
Update fr.po to fix a few typo/mistakes
2013-08-08 07:12:01 -07:00
Daniel Pavel
68251777fb Merge pull request #107 from damsweb/patch-2
Update README.md to add Mageia
2013-08-08 06:29:21 -07:00
Damien Lallement
188b20a4f1 Update README.md to add Mageia 2013-08-07 16:43:46 +02:00
Damien Lallement
5fba1db11c Update fr.po to fix a few typo/mistakes
Fixing feminine words and fix bad translations for connection.
2013-08-07 14:47:46 +02:00
Daniel Pavel
21b1a722fc Merge pull request #102 from david-geiger/patch-1
Add French translation (fr.po) (pwr/Solaar#97)
2013-08-06 07:51:32 -07:00
Daniel Pavel
715c50685b Merge pull request #103 from zipperten/master
Added Swedich translation
2013-08-06 07:50:46 -07:00
Tomi Leppänen
dd0ba17351 The first version of Finnish translation.
I don't have much experience in translating, so I'd like my translations
to be reviewed by someone with more experience.
Some translations aren't that good. Especially I'd like to know if there
is any better or newer word for pairing in Finnish. Some problems I've
already marked in po/fi.po. I'd imagine that the longest texts need the
most fixing.
2013-08-05 19:47:03 +03:00
david_david
576dd4cdd4 Update fr.po
update "Last-Translator:"
2013-08-05 08:15:44 +02:00
david_david
a2a0380401 Update fr.po
#: lib/solaar/ui/about.py:49
#: lib/solaar/ui/window.py:640
2013-08-04 23:08:47 +02:00
david_david
f1a85f45bd Update fr.po 2013-08-04 11:51:53 +02:00
david_david
200cf48df4 Update fr.po 2013-08-04 11:50:24 +02:00
david_david
14b5993dc3 Create fr.po 2013-08-03 16:48:12 +02:00
david_david
585ba93588 Create fr.po 2013-08-03 16:44:30 +02:00
zipperten
e30a692a6e Added Swedich translation 2013-07-30 01:51:35 +02:00
165 changed files with 15160 additions and 3411 deletions

34
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,34 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Information**
<!-- Do not bother opening an issue for version 0.9.2 or older, it is extremely outdated. Please update to the latest one and see if your issue persists. -->
- Solaar version (`git describe --tags` if cloned from Solaar repository):
- Distribution:
- Kernel version (ex. `uname -srmo`): `KERNEL VERSION HERE`
- Output of `solaar show`:
```
OUTPUT HERE
```
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,26 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Information**
- Output of `solaar show` for the target device (if applicable):
```
OUTPUT HERE
```
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@ __pycache__/
*.mo
/lib/Solaar.egg-info/
/lib/solaar.egg-info/
/build/
/sdist/
/dist/

View File

@@ -1,2 +1,14 @@
Copyright 2012, 2013
Daniel Pavel <daniel.pavel@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 2.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

View File

@@ -1,7 +1,89 @@
1.0.2:
* Add usage document
1.0.2rc3:
* Don't produce error dialog for inaccessible receivers with access control lists.
* Add option --battery-icons=symbolic to use symbolic icons if available.
* Update French translation
* Update installation documentation
1.0.2rc2:
* Remove packaging directory tree as it is not maintained
* Pip installs udev rule and solaar autostart when doing install without --user flag
* Use Solaar icon instead of a missing battery icon
* Use only standard icons for battery levels. Symbolic icons do not change to white in dark themes because of problems external to Solaar.
* Better reporting of battery levels when charging for some devices.
* Add information on K600 TV, M350 WIPD 4080, and MX Keys
* Remove assertion requiring receivers to still be in window when they are updated.
* Augment long description of Solaar showing up in repositories.
* Update installation directions.
* Install udev rule as well as autostart file when doing system install.
* Add support for Ayatana AppIndicator.
* Use setuptools icon directory on system installs when not using pip.
* Add receiver C517 and several older devices.
* Improved translations for polish.
* Bypass bug in appindicator when solaar is file in current directory.
* Don't check that device kind matches feature kind.
* Better determination of icons for battery levels.
* Use Ayatana AppIndicator if available.
* Improve error reporting when required system packages are not install.
* Better tooltip description
* Add release script to help when creating releases
1.0.2-rc1:
* Look up tray icon filenames to get around a bug in libappindicator.
* Make the default behavior be to show the main window at startup.
* Support c537 nano receiver
* Add logind signals for suspend/resume.
* Remove solaar-gnome3 package
* Ignore features for devices that don't follow feature specification
* Add probe command to command-line interface to dump receiver registers
* Don't terminate on malformed or unknown messages
* Create fewer internal notifications for messages from devices
* Add a button to the main window to terminate (quit) Solaar
* Set up nano receivers as receivers with no unpairing and with re-pairing
* Set up c534 as receiver with max 2 pairings, no unpairing, re-pairing
* Better support receivers that do not unpair or when pairing replace existing pairings
* Add information about receiver pairing to receiver data structure
* Better support devices that only allow a limited number of total re-pairings
* Add --window option to control main window visibility and tray usage
* Ignore receiver if USB id is not retrieved
* Fix bug with double deleting when devices are disconnected
* Determine some receiver information from data structure for USB ids
* Treat battery level of 0 as unknown
* Fix bug on devices with no serial number
* Drop support for python2, and use python3 throughout
* Fix bug in remembering features discovered from device reports
* Show icons in main window device list
* Count offline devices when determining whether pairing is possible
* Update French, Dutch, German, and Croation translations
* Better icons for battery levels
* Support DPI, Backlight 2, Battery Voltage features
* Support M585, M590, M330, MX Master 2s and 3, new M310, new K800, craft keyboard
* Documentation improvements
* Clean up directory structure and remove unused files
1.0.1:
* Updated the repo url.
* Fixed typo which was crashing the application.
* Improved the HID write routine which was causing issues on some devices.
* Fix non-unifying receivers in Linux 5.2.
* Add new Lightspeed receiver (used in the G305)
1.0.0:
* Too many to track...
0.9.3:
* Merged solaar-cli functionality into main solaar.
* Scrolling over the systray icon switches between multiple peripherals.
* Swedish translation courtesy of Daniel Zippert and Emelie Snecker
* French translation courtesy of Papoteur, David Geiger and Damien Lallement.
* Fixed some untranslated strings.
0.9.2:
* Added support for hand detection on the K800.
* Added support for V550 and V450 Nano.
* Fixed side-scrolling wit the M705 Marathon.
* Fixed side-scrolling with the M705 Marathon.
* Fixed identification of the T650 Touchpad.
* Added internationalization support and romanian translation.
* Polish translation courtesy of Adrian Piotrowicz.
@@ -11,7 +93,7 @@
* Make sure devices in the window tree are sorted by registration index.
* Added an autostart .desktop file.
* Replaced single-instance code with GtkApplication.
* Fixed indentification of the M505 mouse.
* Fixed identification of the M505 mouse.
* Fixed an occasional windowing layout bug with the C52F Nano Receiver.
0.9.0:

View File

@@ -1,88 +0,0 @@
**Solaar** is a Linux device manager for Logitech's [Unifying Receiver][unifying]
peripherals. It is able to pair/unpair devices to the receiver, and for most
devices read battery status.
It comes in two flavors, command-line and GUI. Both are able to list the
devices paired to a Unifying Receiver, show detailed info for each device, and
also pair/unpair supported devices with the receiver.
[unifying]: http://logitech.com/en-us/66/6079
## Supported Devices
**Solaar** will detect all devices paired with your Unifying Receiver, and at
the very least display some basic information about them.
For some devices, extra settings (usually not available through the standard
Linux system configuration) are supported. For a full list of supported devices
and their features, see [docs/devices.md](docs/devices.md).
## Pre-built packages
Pre-built packages are available for a few Linux distros.
* Debian 7 (Wheezy) or higher: packages in this [repository](docs/debian.md)
* Ubuntu/Kubuntu 12.04+: [ppa:daniel.pavel/solaar][ppa]
The `solaar` package uses a standard system tray implementation; to ensure
integration with *gnome-shell* or *Unity*, install `solaar-gnome3`.
* a [Gentoo overlay][gentoo], courtesy of Carlos Silva
* an [OpenSUSE rpm][opensuse], courtesy of Mathias Homann
* an [Arch package][arch], courtesy of Arnaud Taffanel
[ppa]: http://launchpad.net/~daniel.pavel/+archive/solaar
[gentoo]: http://code.r3pek.org/gentoo-overlay/src
[opensuse]: http://software.opensuse.org/package/Solaar
[arch]: http://aur.archlinux.org/packages/solaar
## Manual installation
See [docs/installation.md](docs/installation.md) for the step-by-step
procedure for manual installation.
## Known Issues
- KDE/Kubuntu: if some icons appear broken in the application, make sure you've
properly configured the Gtk theme and icon theme in KDE's control panel.
- Some devices using the [Nano Receiver][nano] (which is very similar to the
Unifying Receiver) are supported, but not all. For details, see
[docs/devices.md](docs/devices.md).
- Running the command-line application (`bin/solaar-cli`) while the GUI
application is also running *may* occasionally cause either of them to become
confused about the state of the devices. I haven't encountered this often
enough to be able to be able to diagnose it properly yet.
[nano]: http://logitech.com/mice-pointers/articles/5926
## License
This software is distributed under the terms of the
[GNU Public License, v2](COPYING).
## Thanks
This project began as a third-hand clone of [Noah K. Tilton](https://github.com/noah)'s
logitech-solar-k750 project on GitHub (no longer available). It was developed
further thanks to the diggings in Logitech's HID++ protocol done by many other
people:
- [Julien Danjou](http://julien.danjou.info/blog/2012/logitech-k750-linux-support),
who also provided some internal
[Logitech documentation](http://julien.danjou.info/blog/2012/logitech-unifying-upower)
- [Lars-Dominik Braun](http://6xq.net/git/lars/lshidpp.git)
- [Alexander Hofbauer](http://derhofbauer.at/blog/blog/2012/08/28/logitech-performance-mx)
- [Clach04](http://bitbucket.org/clach04/logitech-unifying-receiver-tools)
- [Peter Wu](https://lekensteyn.nl/logitech-unifying.html)
- [Nestor Lopez Casado](http://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28)
provided some more Logitech specifications for the HID++ protocol
Also thanks to Douglas Wagner, Julien Gascard and Peter Wu for helping with
application testing and supporting new devices.

1
README.md Symbolic link
View File

@@ -0,0 +1 @@
docs/index.md

15
RELEASE.md Normal file
View File

@@ -0,0 +1,15 @@
# Solaar releases
###### Please read before making a release.
We support two type of releases: normal releases (ex. `1.0.0`) and release
candidates (ex. `1.0.0rc1`). Release candidates must have a `rcX` suffix.
Release routine:
- Update ChangeLog, setup.py, lib/solaar/__init__.py, and docs/_config.yml to the new version
- Create a commit that starts with `release VERSION`
- Push commit to Solaar repository
- Invoke `./release.sh`
- Git tags are signed so you must have GPG set up
- You are required to have a have a github token with `public_repo` access
in `~/.github-token`

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- python-mode -*-
# -*- coding: UTF-8 -*-
@@ -26,7 +26,20 @@ def init_paths():
import sys
import os.path as _path
prefix = _path.normpath(_path.join(_path.realpath(sys.path[0]), '..'))
# Python 2 need conversion from utf-8 filenames
# Python 3 might have problems converting back to UTF-8 in case of Unicode surrogates
try:
if sys.version_info < (3,):
decoded_path = sys.path[0].decode(sys.getfilesystemencoding())
else:
decoded_path = sys.path[0]
sys.path[0].encode(sys.getfilesystemencoding())
except UnicodeError:
sys.stderr.write('ERROR: Solaar cannot recognize encoding of filesystem path, this may happen because non UTF-8 characters in the pathname.\n')
sys.exit(1)
prefix = _path.normpath(_path.join(_path.realpath(decoded_path), '..'))
src_lib = _path.join(prefix, 'lib')
share_lib = _path.join(prefix, 'share', 'solaar', 'lib')
for location in src_lib, share_lib:

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- python-mode -*-
# -*- coding: UTF-8 -*-
@@ -37,6 +37,7 @@ def init_paths():
if __name__ == '__main__':
print ('WARNING: solaar-cli is deprecated; use solaar with the usual arguments')
init_paths()
import solaar.cli
solaar.cli.main()
solaar.cli.run()

6
docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
# ignore documentation-specific files
.jekyll-metadata
Gemfile
Gemfile.lock
_site/

4
docs/README.md Normal file
View File

@@ -0,0 +1,4 @@
# Documentation Readme
This project's documentation is hosted using GitHub pages, which serves static pages using Jeykll.
[Please refer to the official documentation for instructions for how to build the site locally.](https://help.github.com/articles/setting-up-your-github-pages-site-locally-with-jekyll/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
docs/Solaar-menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

10
docs/_config.yml Normal file
View File

@@ -0,0 +1,10 @@
title: Solaar
description: Linux Device Manager for Logitech Unifying Receivers and Paired Devices.
tagline: Linux Device Manager for Logitech Unifying Receivers and Paired Devices.
owner: pwr-Solaar
owner_url: https://github.com/pwr-Solaar
repository: pwr-Solaar/Solaar
version: 1.0.2
show_downloads: false
encoding: utf-8
theme: jekyll-theme-slate

View File

@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="{{ site.lang | default: "en-US" }}">
<head>
<meta charset='utf-8'>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,maximum-scale=2">
<link rel="stylesheet" type="text/css" media="screen" href="{{ '/assets/css/style.css?v=' | append: site.github.build_revision | relative_url }}">
<link rel="icon" type="image/png" href="{{ site.baseurl }}/assets/favicon.png" />
{% seo %}
</head>
<body>
<!-- HEADER -->
<div id="header_wrap" class="outer">
<header class="inner">
{% if site.github.is_project_page %}
<a id="forkme_banner" href="{{ site.github.repository_url }}">View on GitHub</a>
{% endif %}
<h1 id="project_title">
<img src="{{ site.baseurl }}/assets/solaar.svg" style="margin-bottom: -10px; width: 48px; height: 48px; border: 0; box-shadow: none;" />
{{ site.title | default: site.github.repository_name }}</h1>
<h2 id="project_tagline">{{ site.description | default: site.github.project_tagline }}</h2>
{% if site.show_downloads %}
<section id="downloads">
<a class="zip_download_link" href="{{ site.github.zip_url }}">Download this project as a .zip file</a>
<a class="tar_download_link" href="{{ site.github.tar_url }}">Download this project as a tar.gz file</a>
</section>
{% endif %}
</header>
</div>
<!-- MAIN CONTENT -->
<div id="main_content_wrap" class="outer">
<section id="main_content" class="inner">
{{ content }}
</section>
</div>
<!-- FOOTER -->
<div id="footer_wrap" class="outer">
<footer class="inner">
{% if site.github.is_project_page %}
<p class="copyright">{{ site.title | default: site.github.repository_name }} maintained by <a href="{{ site.github.owner_url }}">{{ site.github.owner_name }}</a></p>
{% endif %}
<p>Published with <a href="https://pages.github.com">GitHub Pages</a></p>
</footer>
</div>
</body>
</html>

47
docs/_layouts/page.html Normal file
View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="{{ site.lang | default: "en-US" }}">
<head>
<meta charset='utf-8'>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,maximum-scale=2">
<link rel="stylesheet" type="text/css" media="screen" href="{{ '/assets/css/style.css?v=' | append: site.github.build_revision | relative_url }}">
<link rel="icon" type="image/png" href="{{ site.baseurl }}/assets/favicon.png" />
{% seo %}
</head>
<body>
<!-- HEADER -->
<div id="header_wrap" class="outer">
<header class="inner">
{% if site.github.is_project_page %}
<a id="forkme_banner" href="{{ site.github.repository_url }}">View on GitHub</a>
{% endif %}
<h1 id="project_title">
<a href="{{ site.baseurl }}" style="color: #fff;">
<img src="{{ site.baseurl }}/assets/solaar.svg" style="margin-bottom: -10px; width: 48px; height: 48px; border: 0; box-shadow: none;" />
{{ site.title | default: site.github.repository_name }}</h1>
</a>
</header>
</div>
<!-- MAIN CONTENT -->
<div id="main_content_wrap" class="outer">
<section id="main_content" class="inner">
{{ content }}
</section>
</div>
<!-- FOOTER -->
<div id="footer_wrap" class="outer">
<footer class="inner">
{% if site.github.is_project_page %}
<p class="copyright">{{ site.title | default: site.github.repository_name }} maintained by <a href="{{ site.github.owner_url }}">{{ site.github.owner_name }}</a></p>
{% endif %}
<p>Published with <a href="https://pages.github.com">GitHub Pages</a></p>
</footer>
</div>
</body>
</html>

BIN
docs/assets/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

100
docs/assets/solaar.svg Normal file
View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="solaar.svg"
inkscape:export-filename="/home/chris/Git/Solaar/_includes/favicon.png"
inkscape:export-xdpi="66.666672"
inkscape:export-ydpi="66.666672"
width="43.200001"
height="43.200001">
<metadata
id="metadata21">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1807"
inkscape:window-height="797"
id="namedview19"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="2.36"
inkscape:cx="1.4135593"
inkscape:cy="-4.4"
inkscape:window-x="3000"
inkscape:window-y="840"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<defs
id="defs4">
<linearGradient
id="gradient_blue">
<stop
style="stop-color:#009099;stop-opacity:1"
offset="0"
id="stop7" />
<stop
style="stop-color:#00a899;stop-opacity:0.9"
offset="1"
id="stop9" />
</linearGradient>
<linearGradient
x1="5"
y1="50"
x2="95"
y2="50"
id="gradient_rect"
xlink:href="#gradient_blue"
gradientUnits="userSpaceOnUse" />
<linearGradient
x1="37"
y1="50"
x2="63"
y2="50"
id="gradient_dot"
xlink:href="#gradient_blue"
gradientUnits="userSpaceOnUse" />
</defs>
<g
transform="matrix(0.48,0,0,0.48,-2.4,-2.4)"
id="g13">
<path
d="m 21.5,5.5 c -8.864,0 -16,7.136 -16,16 l 0,57 c 0,8.864 7.136,16 16,16 l 57,0 c 8.864,0 16,-7.136 16,-16 l 0,-57 c 0,-8.864 -7.136,-16 -16,-16 l -57,0 z m 16.1875,11.1875 9.03125,15.625 C 47.784179,32.115965 48.877705,32 50,32 c 1.122295,0 2.215821,0.115965 3.28125,0.3125 l 9.03125,-15.625 10.375,6 -9.03125,15.625 C 65.078123,39.972287 66.191785,41.898777 66.9375,44 L 85,44 85,56 66.9375,56 c -0.745715,2.101223 -1.859377,4.027713 -3.28125,5.6875 l 9.03125,15.625 -10.375,6 -9.03125,-15.625 C 52.215821,67.884035 51.122295,68 50,68 48.877705,68 47.784179,67.884035 46.71875,67.6875 l -9.03125,15.625 -10.375,-6 9.03125,-15.625 C 34.921877,60.027713 33.808215,58.101223 33.0625,56 L 15,56 15,44 33.0625,44 c 0.745715,-2.101223 1.859377,-4.027713 3.28125,-5.6875 l -9.03125,-15.625 10.375,-6 z"
style="fill:url(#gradient_rect);fill-opacity:1;fill-rule:nonzero;stroke:#16161d;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1"
id="path15"
inkscape:connector-curvature="0" />
<path
d="m 62,50 a 12,12 0 1 1 -24,0 12,12 0 1 1 24,0 z"
style="fill:url(#gradient_dot);fill-opacity:1;fill-rule:nonzero;stroke:#16161d;stroke-opacity:1"
id="path17"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

115
docs/capabilities.md Normal file
View File

@@ -0,0 +1,115 @@
---
title: Solaar Capabilities
layout: page
---
# Solaar capabilities
[**Solaar**][solaar] reports on and controls [Logitech][logitech] devices
(keyboards, mice, and trackballs) that connect to your computer via a
Logitech USB receiver (a very small piece of hardware that plugs into one of
your USB ports) and communicate with the receiver using Logitech's HID++
protocol. Solaar is designed to detect all devices paired with your
receivers, and at the very least display some basic information about them.
At this moment, all [Unifying][unifying] receivers are supported (devices
with USB ID `046d:c52b` or `046d:c532`) as are several Lightspeed Receivers
and a dozen Nano receivers.
## HID++
HID++ is a Logitech-proprietary protocol that extends the standard HID
protocol for interfacing with keyboards, mice, and so on. HID++ allows
Logitech receivers to communicate with multiple devices and modify some
features of the device on the device itself. As the HID++ protocol is
proprietary many aspects of it are unknown. Some information about HID++
has been obtained from Logitech but even that is subject to change and
extension.
There are several versions of the HID++ and many different Logitech
receivers and devices that utilize it. Different receivers and devices
implement different portions of HID++ so even if two devices appear to be
the same in both physical appearance and behavior they may working
completely differently underneath. (For example, there are versions of the
M510 mouse that use different versions of the HID++ protocol.)
Contrariwise, two different devices may appear different physically but
actually look the same to software. (For example, some M185 mice look the
same to software as some M310 mice.)
The software identity of a receiver can be determined by its USB id
(reported by Solaar and also viewable in Linux using `lsusb`). The software
identity of a device can be determined by its Wireless PID as reported by
Solaar.
Even something as fundamental as pairing works differently for different
receivers. For Unifying receivers, pairing adds a new paired device, but
only if there is an open slot on the receiver. So these receivers need to
be able to unpair devices that they have been paired with or else they will
not have any open slots for pairing. Some other receivers, like the
Nano receiver with USB ID `046d:c534`, can only pair with particular kinds of
devices and pairing a new device replaces whatever device of that kind was
previously paired to the receiver. These receivers cannot unpair. Further,
some receivers can pair an unlimited number of times but others can only
pair a limited number of times.
Only some connections between receivers and devices are possible. In should
be possible to connect any device with a Unifying logo on it to any receiver
with a Unifying logo on it. Receivers without the Unifying logo probably
can connect only to the kind of devices they were bought with and devices
without the Unifying logo can probably only connect to the kind of receiver
that they were bought with.
## Supported features
Solaar uses the HID++ protocol to pair devices to receivers and unpair
devices from receivers. Solaar also uses the HID++ protocol to display
features of receivers and devices. Solaar can modify some of the features
of devices. Solaar currently only displays some features and can modify
even fewer.
Solaar does not do anything beyond using the HID++ protocol to change the
behavior of receivers and devices. In particular, Solaar cannot change how
the operating system turns the keycodes that a keyboard produces into
characters that are sent to programs. That is the province of HID device
drivers and other software (such as X11).
Logitech receivers and devices have firmware in them. Some of the firmware
can be updated using Logitech software in Windows. For example, there are
security issues with some Logitech receivers and devices and Logitech has
firmware updates to alleviate these problems. Some Logitech firmware can
also be updated in Linux using `fwupdmgr`.
WARNING: Updating firmware can cause a piece of hardware to to become
permanently non-functional if something goes wrong with the update or the
update installs the wrong firmware.
Solaar does keep track of some of the changeable state of a device between
invocations. When Solaar starts it restores on-line devices to their
previously-known state. Also, while running Solaar restores devices to
their previously-known state when the device comes on line.
Querying a device for its current state can require quite a few HID++
interactions. These interactions can temporarily slow down the device, so
Solaar tries to internally cache information about devices. If the device
state is changed by some other means, even sometimes by another invocation
of Solaar, this cached information may become incorrect. Currently there is
no way to force an update of the cached information besides terminating
Solaar and starting it again.
## Battery Icons
For many devices, Solaar shows the approximate battery level via icons that
show up in both main Solaar window and the system tray. Solaar used to use
several heuristics to determine which icon names to use for this purpose,
but as more and more battery icon schemes have been developed this has
become impossible to do well. Solaar now uses the eleven standard
battery icon names `battery-{full,good,low,critical,empty}[-charging]` and
`battery-missing`.
Solaar will use the symbolic versions of these icons if started with the
option `--battery-icons=symbolic`. Because of bugs external to Solaar
these symbolic icons may be nearly invisible in dark themes.
[solaar]: https://github.com/pwr-Solaar/Solaar
[logitech]: https://www.logitech.com
[unifying]: https://en.wikipedia.org/wiki/Logitech_Unifying_receiver

View File

@@ -1,7 +1,8 @@
---
title: Debian Repository
layout: page
---
# Debian repository
To use this repository with your Debian machine, create a file `solaar.list` in
`/etc/apt/sources.list.d/`, with the following contents:
deb http://pwr.github.io/Solaar/packages/ ./
deb-src http://pwr.github.io/Solaar/packages/ ./
Solaar is now part of the [official debian repository](https://packages.debian.org/solaar), to install it on your debian machine, use the following command: `sudo apt install solaar`

View File

@@ -1,49 +1,18 @@
# Supported devices
---
title: Supported Devices
layout: page
---
**Solaar** will detect all devices paired with your receiver, and at the very
least display some basic information about them.
# Supported devices and receivers
At this moment, all [Unifying Receiver][unifying] are supported (devices with
USB ID `046d:c52b` or `046d:c532`), but only some newer [Nano Receiver][nano]s
(devices with USB ID `046d:c52f`). You can check your connected Logitech devices
by running `lsusb -d 046d:` in a console.
These tables list Logitech receivers and devices and to what degree their
features are supported by Solaar. The information in these tables is
incomplete, based on what devices users have been able to test Solaar with.
For some devices, extra settings (usually not available through the standard
Linux system configuration) are supported:
* The [K750 Solar Keyboard][K750] is also queried for its solar charge status.
Pressing the `Light-Check` button on the keyboard will pop-up the application
window and display the current lighting value (Lux) as reported by the
keyboard, similar to Logitech's *Solar.app* for Windows.
* The state of the `FN` key can be toggled on some keyboards ([K360][K360],
[MK700][K700], [K750][K750] and [K800][K800]). It changes the way the function
keys (`F1`..`F12`) work, i.e. whether holding `FN` while pressing the function
keys will generate the standard `Fx` keycodes or the special function (yellow
icons) keycodes.
* The DPI can be changed on the [Performance MX Mouse][P_MX].
* Smooth scrolling (higher sensitivity on vertical scrolling with the wheel) can
be toggled on the [M705 Marathon Mouse][M705] and [M510 Wireless Mouse][M510].
# Supported features
These tables list all known Logitech [Unifying][unifying] devices, and to what
degree their features are supported by Solaar. If your device is not listed here
at all, it is very unlikely Solaar would be able to support it.
The information in these tables is incomplete, based on what devices myself and
other users have been able to test Solaar with. If your device works with
Solaar, but its supported features are not specified here, I would love to hear
about it.
Devices marked with an asterisk (*) use a Nano receiver that knows the Unifying
protocol, and should be fully supported by Solaar.
The HID++ column specifies the device's HID++ version.
The HID++ column specifies the device's HID++ version. Some devices report
version 4.5, but that is the same as version 2.0 as listed here.
For devices what support HID++ 2.0 or greater, Solaar is able to discover
the features the device supports.
The Battery column specifies if Solaar is able to read the device's battery
level.
@@ -52,88 +21,197 @@ For mice, the DPI column specifies if the mouse's sensitivity is fixed (`-`),
can only be read (`R`), or can be read and changed by Solaar (`R/W`).
The reprog(rammable) keys feature is currently not fully supported by Solaar.
You are able to read this feature using solaar-cli, but it is not possible to
assign different keys.
You are able to read this feature using command-line interface of Solaar,
but it is not possible to assign different keys.
Keyboards:
| Device | HID++ | Battery | Other supported features |
|------------------|-------|---------|-----------------------------------------|
| K230 | 2.0 | yes | |
| K270 | | | |
| K340 | | | |
| K350 | | | |
| K360 | 2.0 | yes | FN swap, reprog keys |
| K400 Touch | 2.0 | yes | |
| K750 Solar | 2.0 | yes | FN swap, Lux reading, light button |
| K800 Illuminated | 1.0 | yes | FN swap, reprog keys |
| MK700 | 1.0 | yes | FN swap, reprog keys |
If your device works with Solaar, but its supported features are not
correctly specified here, please open an issue on the [Solaar github
repository][solaar] with the pleasant news.
Mice:
### Receivers:
| Device | HID++ | Battery | DPI | Other supported features |
|------------------|-------|---------|-------|---------------------------------|
| V450 Nano | 1.0 | yes | - | smooth scrolling |
| V550 Nano | 1.0 | yes | - | smooth scrolling |
| VX Nano | 1.0 | yes | - | smooth scrolling |
| M175 * | | yes | | |
| M185 * | | yes | | |
| M187 * | 2.0 | yes | | |
| M215 * | 1.0 | yes | | |
| M235 * | | yes | | |
| M305 * | 1.0 | yes | | |
| M310 * | | yes | | |
| M315 * | | yes | | |
| M317 | | | | |
| M325 | | | | |
| M345 | 2.0 | yes | - | |
| M505 | 1.0 | yes | | |
| M510 | 1.0 | yes | | smooth scrolling |
| M515 Couch | 2.0 | yes | - | |
| M525 | 2.0 | yes | - | |
| M600 Touch | 2.0 | yes | | |
| M705 Marathon | 1.0 | yes | - | smooth scrolling |
| T400 Zone Touch | | | | |
| T620 Touch | 2.0 | | | |
| Performance MX | 1.0 | yes | R/W | |
| Anywhere MX | 1.0 | yes | - | |
| Cube | 2.0 | yes | | |
| USB ID | Kind | Max Paired Devices |
------------|------------|--------------------|
| 046d:c517 | Nano | 1 |
| 046d:c518 | Nano | 1 |
| 046d:c51a | Nano | 1 |
| 046d:c51b | Nano | 1 |
| 046d:c521 | Nano | 1 |
| 046d:c525 | Nano | 1 |
| 046d:c526 | Nano | 1 |
| 046d:c52b | Unifying | 6 |
| 046d:c52e | Nano | 1 |
| 046d:c52f | Nano | 1 |
| 046d:c531 | Nano | 1 |
| 046d:c532 | Unifying | 6 |
| 064d:c534 | Nano | 2 |
| 064d:c539 | Lightspeed | 1 |
| 064d:c53a | Lightspeed | 1 |
| 064d:c53f | Lightspeed | 1 |
| 17ef:6042 | Nano | 1 |
Trackballs:
| Device | HID++ | Battery | DPI | Other supported features |
|------------------|-------|---------|-------|---------------------------------|
| M570 Trackball | | | | |
### Keyboards (Unifying):
| Device | WPID | HID++ | Battery | Other supported features |
|------------------|------|-------|---------|-----------------------------------------|
| K230 | 400D | 2.0 | yes | |
| K270 | 4003 | 2.0 | yes | |
| K340 | 2007 | 1.0 | yes | |
| K350 | 200A | 1.0 | yes | |
| K360 | 4004 | 2.0 | yes | FN swap, reprog keys |
| K375s | 4071 | | | FN swap |
| K400 Touch | 400E | 2.0 | yes | FN swap |
| K400 Touch | 4024 | 2.0 | yes | FN swap |
| K400 Plus | 404D | 2.0 | | FN swap |
| K520 | 2011 | 1.0 | yes | FN swap |
| K600 TV | 4078 | 2.0 | yes | FN swap |
| K750 Solar | 4002 | 2.0 | yes | FN swap, Lux reading, light button |
| K780 | 405B | 2.0 | yes | FN swap |
| K800 Illuminated | 2010 | 1.0 | yes | FN swap, reprog keys, LEDs |
| K800 (new ver) | 406E | 2.0 | yes | FN swap |
| K830 Illuminated | 4032 | 2.0 | yes | FN swap |
| MX Keys | 408A | 2.0 | yes | |
| N545 | 2006 | | yes | |
| TK820 | | 2.0 | yes | FN swap |
| Craft | 4066 | 2.0 | | |
* The [K750 Solar Keyboard][K750] can be queried for its solar charge status.
Pressing the `Light-Check` button on the keyboard will pop-up the application
window and display the current lighting value (Lux) as reported by the
keyboard, similar to Logitech's *Solar.app* for Windows.
* FN swap changes the way the function keys (`F1`..`F12`) work, i.e., whether holding `FN` while pressing the function keys will generate the standard `Fx` keycodes or the special function (yellow icons) keycodes.
Touchpads:
### Mice (Unifying):
| Device | HID++ | Battery | DPI | Other supported features |
|------------------|-------|---------|-------|---------------------------------|
| Wireless Touch | 2.0 | | | |
| T650 Touchpad | 2.0 | | | |
| Device | WPID | HID++ | Battery | DPI | Other supported features |
|------------------|------|-------|---------|-------|---------------------------------|
| M150 | 4022 | 2.0 | | | |
| M185 | 4055 | 2.0 | | R/W | smooth scrolling |
| M310 | 4031 | 2.0 | yes | | |
| M310 | 4055 | 2.0 | | R/W | smooth scrolling |
| M317 | | | | | |
| M325 | 400A | 2.0 | yes | 1000 | smooth scrolling |
| M330 | | 2.0 | yes | 1000 | smooth scrolling |
| M345 | 4017 | 2.0 | yes | - | smooth scrolling |
| M350 | 101C | 1.0 | yes | | |
| M350 | 4080 | 2.0 | | | |
| M505 | 101D | 1.0 | yes | | smooth scrolling, side scrolling|
| M510 | 1025 | 1.0 | yes | | smooth scrolling, side scrolling|
| M510 | 4051 | 2.0 | yes | | smooth scrolling |
| M515 Couch | 4007 | 2.0 | yes | - | smooth scrolling |
| M525 | 4013 | 2.0 | yes | - | smooth scrolling |
| M560 | | 2.0 | yes | - | smooth scrolling |
| M585 | 406B | 2.0 | yes | R/W | smooth scrolling |
| M590 | 406B | 2.0 | yes | R/W | smooth scrolling |
| M600 Touch | 401A | 2.0 | yes | | |
| M705 Marathon | 101B | 1.0 | yes | - | smooth scrolling, side scrolling|
| M705 Marathon | 406D | 2.0 | yes | R/W | smooth scrolling |
| T400 Zone Touch | | 2.0 | yes | | smooth scrolling |
| T620 Touch | | 2.0 | yes | | |
| Performance MX | 101A | 1.0 | yes | R/W | smooth scrolling, side scrolling|
| Anywhere MX | 1017 | 1.0 | yes | R/W | smooth scrolling, side scrolling|
| Anywhere MX 2 | 404A | 2.0 | yes | R/W | smooth scrolling |
| MX Master | 4041 | 2.0 | yes | R/W | smooth scrolling, smart shift |
| MX Master 2S | 4069 | 2.0 | yes | R/W | smooth scrolling, smart shift |
| Cube | | 2.0 | yes | | |
Mouse-Keyboard combos:
### Mice (Nano):
| Device | HID++ | Battery | Other supported features |
|------------------|-------|---------|-----------------------------------------|
| MK330 | | | |
| MK520 | | | |
| MK550 | | | |
| MK710 | 1.0 | yes | FN swap, reprog keys |
| Device | WPID | HID++ | Battery | DPI | Other supported features |
|------------------|------|-------|---------|-------|---------------------------------|
| G7 | 1002 | 1.0 | yes | - | |
| G700 | 1023 | 1.0 | yes | - | smooth scrolling, side scrolling|
| G700s | 102A | 1.0 | yes | - | smooth scrolling, side scrolling|
| V450 Nano | 1011 | 1.0 | yes | - | smooth scrolling |
| V550 Nano | 1013 | 1.0 | yes | - | smooth scrolling, side scrolling|
| VX Nano | 100B | 1.0 | yes | - | smooth scrolling, side scrolling|
| VX Nano | 100F | 1.0 | yes | - | smooth scrolling, side scrolling|
| M175 | 4008 | | yes | | |
| M185 (old) | 4038 | 2.0 | yes | R/W | smooth scrolling (note) |
| M185 (new) | 4054 | 2.0 | no | R/W | smooth scrolling (note) |
| M187 | 4019 | 2.0 | yes | | |
| M215 | 1020 | 1.0 | yes | | |
| M235 | 4055 | 2.0 | yes | R/W | smooth scrolling |
| M305 | 101F | 1.0 | yes | | side scrolling |
| M310 | 1024 | 1.0 | yes | | |
| M315 | | | yes | | |
| M330 | | ?.? | yes | ? | smooth scrolling |
| MX 1100 | 1014 | 1.0 | yes | - | smooth scrolling, side scrolling|
(old): M185 with P/N: 810-003496
(new): M185 with P/N: 810-005238 or 810-005232
(note): Currently, smooth scrolling events are not processed in xfce and this
setting is useful only to disable smooth scrolling.
[unifying]: http://logitech.com/en-us/66/6079
[nano]: http://logitech.com/mice-pointers/articles/5926
### Mice (Mini):
| Device | WPID | HID++ | Battery | DPI | Other supported features |
|------------------|------|-------|---------|-------|---------------------------------|
| MX610 | 1001 | 1.0 | yes | | |
| MX610 lefthanded | 1004 | 1.0 | yes | | |
| MX620 | 100A | 1.0 | yes | | |
| MX620 | 1016 | 1.0 | yes | | |
| V400 | 1003 | 1.0 | yes | | |
| V450 | 1005 | 1.0 | yes | | |
| VX Revolution | 1006 | 1.0 | yes | | |
| VX Revolution | 100D | 1.0 | yes | | |
| MX Air | 1007 | 1.0 | yes | | |
| MX Air | 100E | 1.0 | yes | | |
| MX Revolution | 1008 | 1.0 | yes | | |
| MX Revolution | 100C | 1.0 | yes | | |
### Trackballs (Unifying):
| Device | WPID | HID++ | Battery | DPI | Other supported features |
|-------------------|------|-------|---------|-------|---------------------------------|
| M570 Trackball | | 1.0 | yes | - | |
| MX Ergo Trackball | | 2.0 | yes | - | |
### Touchpads (Unifying):
| Device | WPID | HID++ | Battery | DPI | Other supported features |
|------------------|------|-------|---------|-------|---------------------------------|
| Wireless Touch | 4011 | 2.0 | yes | | |
| T650 Touchpad | 4101 | 2.0 | yes | | smooth scrolling |
### Mice and Keyboards sold as combos:
| Device | WPID | HID++ | Battery | Other supported features |
|------------------|------|-------|---------|-----------------------------------------|
| MK220 | | 2.0 | yes | |
| MK270 | 4023 | 2.0 | yes | reprog keys |
| MK320 | 200F | | | |
| MK330 | | | | |
| MK520 | | M2/K1 | yes | FN swap, reprog keys |
| MK550 | | | | |
| MK700 | 2008 | 1.0 | yes | FN swap, reprog keys |
| MK710 | | 1.0 | yes | FN swap, reprog keys |
[solaar]: https://github.com/pwr-Solaar/Solaar
[logitech]: https://www.logitech.com
[unifying]: https://en.wikipedia.org/wiki/Logitech_Unifying_receiver
[G700s]: https://gaming.logitech.com/en-us/product/g700s-rechargeable-wireless-gaming-mouse
[K360]: http://logitech.com/product/keyboard-k360
[K700]: http://logitech.com/product/wireless-desktop-mk710
[K750]: http://logitech.com/product/k750-keyboard
[K800]: http://logitech.com/product/wireless-illuminated-keyboard-k800
[K830]: http://logitech.com/product/living-room-keyboard-k830
[M510]: http://logitech.com/product/wireless-mouse-m510
[M705]: http://logitech.com/product/marathon-mouse-m705
[P_MX]: http://logitech.com/product/performance-mouse-mx
[A_MX]: http://logitech.com/product/anywhere-mouse-mx
[M325]: http://logitech.com/product/wireless-mouse-m325
[M330]: https://www.logitech.com/en-us/product/m330-silent-plus

View File

@@ -0,0 +1,48 @@
1: Wireless Mouse M510
Codename : M510v2
Kind : mouse
Wireless PID : 4051
Protocol : HID++ 4.5
Polling rate : 8 ms (125Hz)
Serial number: 8989B04C
Firmware: RQM 62.00.B0013
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:1802 {1802} internal, hidden
7: unknown:1810 {1810} internal, hidden
8: unknown:1830 {1830} 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 7 reprogrammable keys:
0: LEFT CLICK , default: LeftClick => LEFT CLICK
divertable, mse, pos:0, group:1, gmask:1
1: RIGHT CLICK , default: RightClick => RIGHT CLICK
divertable, mse, pos:0, group:1, gmask:1
2: MIDDLE BUTTON , default: MiddleMouseButton => MIDDLE BUTTON
divertable, mse, reprogrammable, pos:0, group:2, gmask:3
3: LEFT SCROLL AS AC PAN , default: HorzScrollLeftSet => LEFT SCROLL AS AC PAN
divertable, mse, reprogrammable, pos:0, group:2, gmask:3
4: RIGHT SCROLL AS AC PAN , default: HorzScrollRightSet => RIGHT SCROLL AS AC PAN
divertable, mse, reprogrammable, pos:0, group:2, gmask:3
5: BACK AS BUTTON 4 , default: BackEx => BACK AS BUTTON 4
divertable, mse, reprogrammable, pos:0, group:2, gmask:3
6: FORWARD AS BUTTON 5 , default: BrowserForwardEx => FORWARD AS BUTTON 5
divertable, mse, reprogrammable, pos:0, group:2, gmask:3
Battery: 70%, discharging.

View File

@@ -0,0 +1,51 @@
Unifying Receiver
Device path : /dev/hidraw0
USB id : 046d:c52b
Serial : A7F5923B
Firmware : 24.01.B0023
Bootloader : 01.08
Other : AA.AD
Has 1 paired device(s) out of a maximum of 6.
Notifications: wireless, software present (0x000900)
Device activity counters: 1=11
1: Wireless Mouse MX Anywhere 2
Codename : MX Anywhere 2
Kind : mouse
Wireless PID : 404A
Protocol : HID++ 4.5
Polling rate : 8 ms (125Hz)
Serial number: F3B81C5B
Bootloader: BOT 23.00.B0007
Firmware: MPM 02.00.B0007
Firmware: MPM 02.00.B0007
Other:
The power switch is located on the base.
Supports 26 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: WIRELESS DEVICE STATUS {1D4B}
5: RESET {0020}
6: BATTERY STATUS {1000}
7: CHANGE HOST {1814}
8: REPROG CONTROLS V4 {1B04}
9: ADJUSTABLE DPI {2201}
10: VERTICAL SCROLLING {2100}
11: HIRES WHEEL {2121}
12: DFUCONTROL 2 {00C1}
13: unknown:1813 {1813} internal, hidden
14: unknown:1830 {1830} internal, hidden
15: unknown:1890 {1890} internal, hidden
16: unknown:1891 {1891} internal, hidden
17: unknown:18A1 {18A1} internal, hidden
18: unknown:18C0 {18C0} internal, hidden
19: unknown:1DF3 {1DF3} internal, hidden
20: unknown:1E00 {1E00} hidden
21: unknown:1EB0 {1EB0} internal, hidden
22: unknown:1803 {1803} internal, hidden
23: unknown:1861 {1861} internal, hidden
24: unknown:9000 {9000} internal, hidden
25: unknown:1805 {1805} internal, hidden
Battery: 0%, recharging.

View File

@@ -0,0 +1,88 @@
Receiver
LZ301AR-DJ
M/N:C-U0007
(ltunify)
Serial number: D1759614
Firmware version: 012.001.00019
Bootloader version: BL.002.014
Supported notification flags: 00 09 00
- 01: Wireless Notifications
- 08: Software Present
Mouse
(ltunify)
HID++ version: 1.0
Device index 1
Mouse
Name: Anywhere MX
Wireless Product ID: 1017
Serial number: 13865F99
Firmware version: 016.001.00040
Bootloader version: BL.002.010
(solaar)
Unifying Receiver
Device path : /dev/hidraw2
USB id : 046d:c52b
Serial : D1759614
Firmware : 12.01.B0019
Bootloader : 02.14
Has 1 paired device(s) out of a maximum of 6.
Notifications: (none)
Device activity counters: 1=19
1: Anywhere Mouse MX
Codename : Anywhere MX
Kind : mouse
Wireless PID : 1017
Protocol : HID++ 1.0
Polling rate : 8 ms (125Hz)
Serial number: 13865F99
Firmware: 16.01.B0040
Bootloader: 02.10
Other: 00.06
The power switch is located on the base.
Notifications: (none).
Battery: 100%, discharging.
(scan-registers)
# Old notification flags: 000000
# 00 - Enabled Notifications, supported flags: Battery Status (10)
>> ( 0.792) [10 01 8100 100000] b'\x10\x01\x81\x00\x10\x00\x00'
# 01 - scrolling settings?
# Flags:
# 0x40 - Enable Smooth Scrolling
# 0x02 - "confuse KDE", see https://github.com/pwr/Solaar/issues/115
<< ( 0.011) [10 01 8101 000000] b'\x10\x01\x81\x01\x00\x00\x00'
>> ( 1.710) [10 01 8101 020000] b'\x10\x01\x81\x01\x02\x00\x00'
# 0D - battery level. first byte is remaining charge in percent; second is
# (guessed) maximum?; third is charge status (30=discharging)
# "10 ix 0D 64 64 30 00" is a battery notification (when enabled)
<< ( 9.789) [10 01 810D 000000] b'\x10\x01\x81\r\x00\x00\x00'
>> ( 9.816) [10 01 810D 646430] b'\x10\x01\x81\rdd0'
# 63 - DPI (range 0x80-0x8e (inclusive))
<< ( 75.521) [10 01 8163 000000] b'\x10\x01\x81c\x00\x00\x00'
>> ( 75.550) [10 01 8163 890000] b'\x10\x01\x81c\x89\x00\x00'
# D0 - ?
<< ( 163.118) [10 01 81D0 000000] b'\x10\x01\x81\xd0\x00\x00\x00'
>> ( 163.148) [10 01 81D0 000000] b'\x10\x01\x81\xd0\x00\x00\x00'
# D4 - ?
<< ( 166.034) [10 01 81D4 000000] b'\x10\x01\x81\xd4\x00\x00\x00'
>> ( 166.063) [10 01 81D4 000008] b'\x10\x01\x81\xd4\x00\x00\x08'
# F1 - firmware/bootloader version
<< ( 187.172) [10 01 81F1 000000] b'\x10\x01\x81\xf1\x00\x00\x00'
>> ( 187.199) [10 01 8F81 F10300] b'\x10\x01\x8f\x81\xf1\x03\x00'
# F3 - ?
<< ( 188.629) [10 01 81F3 000000] b'\x10\x01\x81\xf3\x00\x00\x00'
>> ( 188.661) [10 01 81F3 000000] b'\x10\x01\x81\xf3\x00\x00\x00'
# FD - ?
<< ( 195.715) [10 01 83FD 000000] b'\x10\x01\x83\xfd\x00\x00\x00'
>> ( 195.746) [11 01 83FD 00000000000000000000000000000000] b'\x11\x01\x83\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

114
docs/devices/craft.txt Normal file
View File

@@ -0,0 +1,114 @@
Unifying Receiver
Device path : /dev/hidraw0
USB id : 046d:c52b
Serial : E21FAD57
Firmware : 24.06.B0030
Bootloader : 01.08
Other : AA.AC
Has 2 paired device(s) out of a maximum of 6.
Notifications: wireless, software present (0x000900)
Device activity counters: 1=243, 2=173
1: Craft Advanced Keyboard
Codename : Craft
Kind : keyboard
Wireless PID : 4066
Protocol : HID++ 4.5
Polling rate : 8 ms (125Hz)
Serial number: 428C2F81
Bootloader: BOT 41.00.B0014
Firmware: MPK 07.01.B0015
Other:
Other:
The power switch is located on the edge of top right corner.
Supports 39 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: WIRELESS DEVICE STATUS {1D4B}
5: RESET {0020}
6: unknown:0007 {0007}
7: BATTERY STATUS {1000}
8: CHANGE HOST {1814}
9: unknown:1815 {1815}
10: unknown:1982 {1982}
11: REPROG CONTROLS V4 {1B04}
12: unknown:1C00 {1C00}
13: K375S FN INVERSION {40A3}
14: ENCRYPTION {4100}
15: LOCK KEY STATE {4220}
16: KEYBOARD DISABLE {4521}
17: unknown:4531 {4531}
18: unknown:4600 {4600}
19: unknown:00C2 {00C2}
20: unknown:1803 {1803} internal, hidden
21: unknown:1806 {1806} internal, hidden
22: unknown:1813 {1813} internal, hidden
23: unknown:1805 {1805} internal, hidden
24: unknown:1830 {1830} internal, hidden
25: unknown:1890 {1890} internal, hidden
26: unknown:1891 {1891} internal, hidden
27: unknown:1801 {1801} internal, hidden
28: unknown:18A1 {18A1} internal, hidden
29: unknown:9280 {9280} internal, hidden
30: unknown:1A20 {1A20} internal, hidden
31: unknown:1DF3 {1DF3} internal, hidden
32: unknown:1E00 {1E00} hidden
33: unknown:1EB0 {1EB0} internal, hidden
34: unknown:1861 {1861} internal, hidden
35: unknown:18B0 {18B0} internal, hidden
36: unknown:92C0 {92C0} internal, hidden
37: unknown:9203 {9203} internal, hidden
38: unknown:3615 {3615} internal, hidden
Has 24 reprogrammable keys:
0: unknown:00D1 , default: unknown:00AE => unknown:00D1
divertable, nonstandard, persistently divertable, pos:0, group:0, gmask:0
1: unknown:00D2 , default: unknown:00AF => unknown:00D2
divertable, nonstandard, persistently divertable, pos:0, group:0, gmask:0
2: unknown:00D3 , default: unknown:00B0 => unknown:00D3
divertable, nonstandard, persistently divertable, pos:0, group:0, gmask:0
3: unknown:00C7 , default: unknown:00A3 => unknown:00C7
divertable, is FN, FN sensitive, persistently divertable, reprogrammable, pos:1, group:0, gmask:0
4: unknown:00C8 , default: unknown:00A4 => unknown:00C8
divertable, is FN, FN sensitive, persistently divertable, reprogrammable, pos:2, group:0, gmask:0
5: unknown:00E0 , default: unknown:00BF => unknown:00E0
divertable, is FN, FN sensitive, persistently divertable, reprogrammable, pos:3, group:0, gmask:0
6: unknown:00E1 , default: unknown:00C0 => unknown:00E1
divertable, is FN, FN sensitive, persistently divertable, reprogrammable, pos:4, group:0, gmask:0
7: SHOW DESKTOP , default: ShowDesktop => SHOW DESKTOP
divertable, is FN, FN sensitive, persistently divertable, reprogrammable, pos:5, group:0, gmask:0
8: unknown:00E2 , default: unknown:00C1 => unknown:00E2
divertable, is FN, FN sensitive, persistently divertable, reprogrammable, pos:6, group:0, gmask:0
9: unknown:00E3 , default: unknown:00C2 => unknown:00E3
divertable, is FN, FN sensitive, persistently divertable, reprogrammable, pos:7, group:0, gmask:0
10: unknown:00E4 , default: Previous => unknown:00E4
divertable, is FN, FN sensitive, persistently divertable, reprogrammable, pos:8, group:0, gmask:0
11: unknown:00E5 , default: Play/Pause => unknown:00E5
divertable, is FN, FN sensitive, persistently divertable, reprogrammable, pos:9, group:0, gmask:0
12: unknown:00E6 , default: Next => unknown:00E6
divertable, is FN, FN sensitive, persistently divertable, reprogrammable, pos:10, group:0, gmask:0
13: unknown:00E7 , default: Mute => unknown:00E7
divertable, is FN, FN sensitive, persistently divertable, reprogrammable, pos:11, group:0, gmask:0
14: unknown:00E8 , default: Volume Down => unknown:00E8
divertable, is FN, FN sensitive, persistently divertable, reprogrammable, pos:12, group:0, gmask:0
15: unknown:00E9 , default: Volume Up => unknown:00E9
divertable, nonstandard, persistently divertable, reprogrammable, pos:0, group:0, gmask:0
16: Calculator , default: Calculator => Calculator
divertable, nonstandard, persistently divertable, reprogrammable, pos:0, group:0, gmask:0
17: unknown:00BF , default: unknown:009B => unknown:00BF
divertable, nonstandard, persistently divertable, reprogrammable, pos:0, group:0, gmask:0
18: unknown:00EA , default: unknown:00C3 => unknown:00EA
divertable, nonstandard, persistently divertable, reprogrammable, pos:0, group:0, gmask:0
19: Lock PC , default: WindowsLock => Lock PC
divertable, nonstandard, persistently divertable, reprogrammable, pos:0, group:0, gmask:0
20: unknown:00EC , default: unknown:00B8 => unknown:00EC
divertable, nonstandard, persistently divertable, pos:0, group:0, gmask:0
21: unknown:00EB , default: unknown:00B6 => unknown:00EB
divertable, nonstandard, persistently divertable, pos:0, group:0, gmask:0
22: unknown:00DE , default: Do Nothing One => unknown:00DE
is FN, pos:0, group:0, gmask:0
23: unknown:0034 , default: Do Nothing One => unknown:0034
nonstandard, pos:0, group:0, gmask:0
Battery: 0%, full.

View File

@@ -0,0 +1,43 @@
Unifying Receiver
Device path : /dev/hidraw2
USB id : 046d:c52b
Serial : C03E9E2E
Firmware : 12.01.B0019
Bootloader : 02.14
Has 2 paired device(s) out of a maximum of 6.
Notifications: wireless, software present (0x000900)
Device activity counters: 2=118
2: Wireless Keyboard K270(unifying)
Codename : K270(unifying)
Kind : keyboard
Wireless PID : 4003
Protocol : HID++ 2.0
Polling rate : 20 ms (50Hz)
Serial number: FE8C8542
Firmware: RQK 35.00.B0017
The power switch is located on the top case.
Supports 12 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: BATTERY STATUS {1000}
5: unknown:1820 {1820} hidden
6: REPROG CONTROLS {1B00}
7: WIRELESS DEVICE STATUS {1D4B}
8: unknown:1DF0 {1DF0} hidden
9: unknown:1DF3 {1DF3} hidden
10: ENCRYPTION {4100}
11: KEYBOARD LAYOUT {4520}
Has 8 reprogrammable keys:
0: Play/Pause => Play/Pause nonstandard
1: Mute => Mute nonstandard
2: Volume Down => Volume Down nonstandard
3: Volume Up => Volume Up nonstandard
4: MY HOME => HomePage nonstandard, reprogrammable
5: Mail => Email nonstandard, reprogrammable
6: SLEEP => Sleep nonstandard, reprogrammable
7: Calculator => Calculator nonstandard, reprogrammable
Battery: 90%, discharging.

39
docs/devices/k750.txt Normal file
View File

@@ -0,0 +1,39 @@
(from Julien Danjou)
(solaar)
2: Wireless Solar Keyboard K750
Codename : K750
Kind : keyboard
Wireless PID : 4002
Protocol : HID++ 2.0
Polling rate : 20 ms (50Hz)
Serial number: 5692B2EC
Firmware: RQK 33.00.B0015
Bootloader: DFU 00.02.B0003
The power switch is located on the edge of top right corner.
Supports 11 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: REPROG CONTROLS {1B00}
5: WIRELESS DEVICE STATUS {1D4B}
6: unknown:1DF3 {1DF3} hidden
7: FN INVERSION {40A0}
8: ENCRYPTION {4100}
9: SOLAR DASHBOARD {4301}
10: KEYBOARD LAYOUT {4520}
Has 12 reprogrammable keys:
0: MY HOME => HomePage FN sensitive, is FN, reprogrammable
1: Mail => Email FN sensitive, is FN, reprogrammable
2: SEARCH => Search FN sensitive, is FN, reprogrammable
3: Calculator => Calculator FN sensitive, is FN, reprogrammable
4: MEDIA PLAYER => Music FN sensitive, is FN, reprogrammable
5: Previous => Previous FN sensitive, is FN
6: Play/Pause => Play/Pause FN sensitive, is FN
7: Next => Next FN sensitive, is FN
8: Mute => Mute FN sensitive, is FN
9: Volume Down => Volume Down FN sensitive, is FN
10: Volume Up => Volume Up FN sensitive, is FN
11: SLEEP => Sleep FN sensitive, is FN, reprogrammable
Battery status unavailable.

44
docs/devices/k780.txt Normal file
View File

@@ -0,0 +1,44 @@
2: K780 Multi-Device Keyboard
Codename : K780
Kind : keyboard
Wireless PID : 405B
Protocol : HID++ 4.5
Polling rate : 20 ms (50Hz)
Serial number: 4D71FEE1
Bootloader: BOT 25.00.B0005
Firmware: MPK 01.00.B0018
Other:
The power switch is located on the edge of top right corner.
Supports 31 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: WIRELESS DEVICE STATUS {1D4B}
5: RESET {0020}
6: unknown:0007 {0007}
7: BATTERY STATUS {1000}
8: CHANGE HOST {1814}
9: unknown:1815 {1815}
10: REPROG CONTROLS V4 {1B04}
11: unknown:1C00 {1C00}
12: NEW FN INVERSION {40A2}
13: ENCRYPTION {4100}
14: KEYBOARD DISABLE {4521}
15: unknown:4531 {4531}
16: LOCK KEY STATE {4220}
17: unknown:00C2 {00C2}
18: unknown:1803 {1803} internal, hidden
19: unknown:1806 {1806} internal, hidden
20: unknown:1805 {1805} internal, hidden
21: unknown:1813 {1813} internal, hidden
22: unknown:1830 {1830} internal, hidden
23: unknown:1861 {1861} internal, hidden
24: unknown:1890 {1890} internal, hidden
25: unknown:1891 {1891} internal, hidden
26: unknown:18A1 {18A1} internal, hidden
27: unknown:1DF3 {1DF3} internal, hidden
28: unknown:1E00 {1E00} hidden
29: unknown:1EB0 {1EB0} internal, hidden
30: unknown:18B0 {18B0} internal, hidden
Battery: 90%, discharging.

557
docs/devices/k800-new.txt Normal file
View File

@@ -0,0 +1,557 @@
[kamil@kamil-pc Solaar]$ bin/solaar -dd show
16:01:46,533 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 FF 83B5 030000]
16:01:46,539 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 FF 83B5 03DC279AB20A06090000000000000000]
16:01:46,539 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 FF 80B2 000000]
16:01:46,550 DEBUG [MainThread] logitech_receiver.base: (3) => r[20 01 4101 71401E0000000400000000]
16:01:46,551 DEBUG [MainThread] logitech_receiver.base: (3) => r[20 02 4101 6E401A4000000400000000]
16:01:46,551 DEBUG [MainThread] logitech_receiver.base: (3) => r[20 00 4102 0000000000000000000000]
16:01:46,551 DEBUG [MainThread] logitech_receiver.base: (3) => r[10 FF 8F80 B20300]
16:01:46,551 DEBUG [MainThread] logitech_receiver.base: (3) device 0xFF error on request {80B2}: 3 = invalid value
16:01:46,551 DEBUG [MainThread] solaar.cli: [/dev/hidraw0] => <UnifyingReceiver(/dev/hidraw0,3)>
16:01:46,646 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 FF 8102 000000]
16:01:46,651 DEBUG [MainThread] logitech_receiver.base: (3) => r[10 FF 8102 000200]
Unifying Receiver
Device path : /dev/hidraw0
USB id : 046d:c52b
Serial : DC279AB2
16:01:46,652 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 FF 81F1 010000]
16:01:46,657 DEBUG [MainThread] logitech_receiver.base: (3) => r[10 FF 81F1 012411]
16:01:46,658 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 FF 81F1 020000]
16:01:46,664 DEBUG [MainThread] logitech_receiver.base: (3) => r[10 FF 81F1 020036]
16:01:46,664 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 FF 81F1 040000]
16:01:46,669 DEBUG [MainThread] logitech_receiver.base: (3) => r[10 FF 81F1 040209]
16:01:46,670 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 FF 81F1 030000]
16:01:46,675 DEBUG [MainThread] logitech_receiver.base: (3) => r[10 FF 81F1 03AAAC]
Firmware : 24.11.B0036
Bootloader : 02.09
Other : AA.AC
Has 2 paired device(s) out of a maximum of 6.
16:01:46,676 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 FF 8100 000000]
16:01:46,681 DEBUG [MainThread] logitech_receiver.base: (3) => r[10 FF 8100 000900]
Notifications: wireless, software present (0x000900)
16:01:46,682 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 FF 83B3 000000]
16:01:46,687 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 FF 83B3 51060000000000000000000000000000]
Device activity counters: 1=81, 2=6
16:01:46,688 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 FF 8102 000000]
16:01:46,693 DEBUG [MainThread] logitech_receiver.base: (3) => r[10 FF 8102 000200]
16:01:46,694 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 FF 83B5 200000]
16:01:46,699 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 FF 83B5 20090840710402020700000000000000]
16:01:46,700 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 FF 83B5 400000]
16:01:46,705 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 FF 83B5 40094D58204D61737465720000000000]
16:01:46,706 INFO [MainThread] logitech_receiver.receiver: <UnifyingReceiver(/dev/hidraw0,3)>: found new device 1 (4071)
16:01:46,706 DEBUG [MainThread] logitech_receiver.base: (3) pinging device 1
16:01:46,706 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 001E 000016]
16:01:47,033 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000F 00000000000000000000000000000000]
16:01:47,049 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 001E 04051600000000000000000000000000]
1: Wireless Mouse MX Master
Codename : MX Master
Kind : mouse
Wireless PID : 4071
Protocol : HID++ 4.5
Polling rate : 8 ms (125Hz)
16:01:47,049 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 FF 83B5 300000]
16:01:47,055 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 FF 83B5 30A6A400641E00000001000000000000]
Serial number: A6A40064
16:01:47,055 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0009 000100]
16:01:47,061 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000B 00000000000000000000000000000000]
16:01:47,077 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0009 01000100000000000000000000000000]
16:01:47,077 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0108 000000]
16:01:47,093 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000D 00000000000000000000000000000000]
16:01:47,101 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0108 1F000000000000000000000000000000]
16:01:47,101 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000C 000300]
16:01:47,109 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000B 00000000000000000000000000000000]
16:01:47,117 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000C 02000200000000000000000000000000]
16:01:47,117 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 020D 000000]
16:01:47,125 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000B 00000000000000000000000000000000]
16:01:47,133 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 020D 040382B4290006B01E40710000030000]
16:01:47,134 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 021E 000000]
16:01:47,141 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000B 00000000000000000000000000000000]
16:01:47,149 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 021E 01424F54561000050040717622661101]
16:01:47,150 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 021C 010000]
16:01:47,171 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 021C 004D504D1210000500B01E7622661101]
16:01:47,171 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 021E 020000]
16:01:47,191 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 021E 004D504D121000050140717622661101]
16:01:47,191 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 021F 030000]
16:01:47,213 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 021F 050000000000005A0000000000000000]
Bootloader: BOT 56.10.B0005
Firmware: MPM 12.10.B0005
Firmware: MPM 12.10.B0005
Other:
The power switch is located on the base.
Supports 32 HID++ 2.0 features:
16:01:47,214 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000B 000000]
16:01:47,311 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000B 00000100000000000000000000000000]
0: ROOT {0000}
16:01:47,312 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000C 000100]
16:01:47,333 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000C 01000100000000000000000000000000]
1: FEATURE SET {0001}
16:01:47,334 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000F 000300]
16:01:47,431 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000F 02000200000000000000000000000000]
2: DEVICE FW VERSION {0003}
16:01:47,431 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011C 030000]
16:01:47,453 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011C 00050000000000000000000000000000]
16:01:47,454 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000F 000500]
16:01:47,475 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000F 03000000000000000000000000000000]
3: DEVICE NAME {0005}
16:01:47,475 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0119 040000]
16:01:47,575 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0119 1D4B0000000000000000000000000000]
16:01:47,575 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000A 1D4B00]
16:01:47,593 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000A 04000000000000000000000000000000]
4: WIRELESS DEVICE STATUS {1D4B}
16:01:47,593 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011A 050000]
16:01:47,617 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011A 00200000000000000000000000000000]
16:01:47,617 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0009 002000]
16:01:47,715 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0009 05000000000000000000000000000000]
5: RESET {0020}
16:01:47,715 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011E 060000]
16:01:47,735 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011E 00210000000000000000000000000000]
16:01:47,735 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000B 002100]
16:01:47,755 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000B 06000000000000000000000000000000]
6: unknown:0021 {0021}
16:01:47,755 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011C 070000]
16:01:47,775 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011C 10000000000000000000000000000000]
16:01:47,775 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000C 100000]
16:01:47,795 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000C 07000000000000000000000000000000]
7: BATTERY STATUS {1000}
16:01:47,795 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011F 080000]
16:01:47,815 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011F 18066000000000000000000000000000]
16:01:47,815 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000C 180600]
16:01:47,835 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000C 08600000000000000000000000000000]
8: unknown:1806 {1806} internal, hidden
16:01:47,835 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0119 090000]
16:01:47,855 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0119 18140001000000000000000000000000]
16:01:47,855 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000A 181400]
16:01:47,875 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000A 09000100000000000000000000000000]
9: CHANGE HOST {1814}
16:01:47,875 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011A 0A0000]
16:01:47,895 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011A 1B040003000000000000000000000000]
16:01:47,895 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000A 1B0400]
16:01:47,915 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000A 0A000300000000000000000000000000]
10: REPROG CONTROLS V4 {1B04}
16:01:47,915 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011E 0B0000]
16:01:47,935 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011E 22010001000000000000000000000000]
16:01:47,935 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000E 220100]
16:01:47,957 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000E 0B000100000000000000000000000000]
11: ADJUSTABLE DPI {2201}
16:01:47,957 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011A 0C0000]
16:01:47,977 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011A 21000000000000000000000000000000]
16:01:47,977 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0009 210000]
16:01:47,997 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0009 0C000000000000000000000000000000]
12: VERTICAL SCROLLING {2100}
16:01:47,998 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0C0C 000000]
16:01:48,017 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0C0C 03180000000000000000000000000000]
Roller type: 3G
Ratchet per turn: 24
Scroll lines: 0
16:01:48,017 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011A 0D0000]
16:01:48,037 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011A 21100000000000000000000000000000]
16:01:48,037 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0008 211000]
16:01:48,057 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0008 0D000000000000000000000000000000]
13: SMART SHIFT {2110}
16:01:48,057 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011C 0E0000]
16:01:48,077 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011C 21210000000000000000000000000000]
16:01:48,077 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000C 212100]
16:01:48,097 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000C 0E000000000000000000000000000000]
14: HIRES WHEEL {2121}
16:01:48,097 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0E09 000000]
16:01:48,117 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0E09 080C0000000000000000000000000000]
16:01:48,117 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0E1D 000000]
16:01:48,137 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0E1D 02000000000000000000000000000000]
16:01:48,137 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0E38 000000]
16:01:48,159 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0E38 01000000000000000000000000000000]
Multiplier: 8
Has invert
Normal wheel motion
Has ratchet switch
Normal wheel mode
High resolution mode
HID notification
16:01:48,159 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011F 0F0000]
16:01:48,179 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011F 65010000000000000000000000000000]
16:01:48,179 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000A 650100]
16:01:48,199 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000A 0F000000000000000000000000000000]
15: GESTURE 2 {6501}
16:01:48,199 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0119 100000]
16:01:48,219 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0119 00C20000000000000000000000000000]
16:01:48,219 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0009 00C200]
16:01:48,239 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0009 10000000000000000000000000000000]
16: unknown:00C2 {00C2}
16:01:48,240 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011B 110000]
16:01:48,259 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011B 18136000000000000000000000000000]
16:01:48,259 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0009 181300]
16:01:48,279 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0009 11600000000000000000000000000000]
17: unknown:1813 {1813} internal, hidden
16:01:48,279 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011C 120000]
16:01:48,299 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011C 18306000000000000000000000000000]
16:01:48,299 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000C 183000]
16:01:48,319 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000C 12600000000000000000000000000000]
18: unknown:1830 {1830} internal, hidden
16:01:48,319 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011B 130000]
16:01:48,339 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011B 18906000000000000000000000000000]
16:01:48,339 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0008 189000]
16:01:48,359 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0008 13600000000000000000000000000000]
19: unknown:1890 {1890} internal, hidden
16:01:48,359 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011B 140000]
16:01:48,381 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011B 18916000000000000000000000000000]
16:01:48,381 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0008 189100]
16:01:48,401 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0008 14600000000000000000000000000000]
20: unknown:1891 {1891} internal, hidden
16:01:48,401 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011C 150000]
16:01:48,421 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011C 18A16000000000000000000000000000]
16:01:48,421 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000F 18A100]
16:01:48,441 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000F 15600000000000000000000000000000]
21: unknown:18A1 {18A1} internal, hidden
16:01:48,441 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011A 160000]
16:01:48,461 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011A 18C06000000000000000000000000000]
16:01:48,461 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0008 18C000]
16:01:48,481 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0008 16600000000000000000000000000000]
22: unknown:18C0 {18C0} internal, hidden
16:01:48,481 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011E 170000]
16:01:48,501 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011E 1DF36000000000000000000000000000]
16:01:48,501 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0009 1DF300]
16:01:48,521 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0009 17600000000000000000000000000000]
23: unknown:1DF3 {1DF3} internal, hidden
16:01:48,521 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011E 180000]
16:01:48,541 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011E 1E004000000000000000000000000000]
16:01:48,541 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0009 1E0000]
16:01:48,561 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0009 18400000000000000000000000000000]
24: unknown:1E00 {1E00} hidden
16:01:48,561 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0119 190000]
16:01:48,581 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0119 1EB06000000000000000000000000000]
16:01:48,581 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0009 1EB000]
16:01:48,603 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0009 19600000000000000000000000000000]
25: unknown:1EB0 {1EB0} internal, hidden
16:01:48,603 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011F 1A0000]
16:01:48,623 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011F 18036000000000000000000000000000]
16:01:48,623 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000F 180300]
16:01:48,643 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000F 1A600000000000000000000000000000]
26: unknown:1803 {1803} internal, hidden
16:01:48,643 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0118 1B0000]
16:01:48,663 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0118 18616000000000000000000000000000]
16:01:48,663 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0009 186100]
16:01:48,683 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0009 1B600000000000000000000000000000]
27: unknown:1861 {1861} internal, hidden
16:01:48,683 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0118 1C0000]
16:01:48,703 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0118 90016000000000000000000000000000]
16:01:48,703 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000F 900100]
16:01:48,723 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000F 1C600000000000000000000000000000]
28: unknown:9001 {9001} internal, hidden
16:01:48,723 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0119 1D0000]
16:01:48,743 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0119 92006000000000000000000000000000]
16:01:48,743 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000D 920000]
16:01:48,763 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000D 1D600000000000000000000000000000]
29: unknown:9200 {9200} internal, hidden
16:01:48,763 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 011F 1E0000]
16:01:48,783 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 011F 92026000000000000000000000000000]
16:01:48,783 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000C 920200]
16:01:48,805 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000C 1E600000000000000000000000000000]
30: unknown:9202 {9202} internal, hidden
16:01:48,805 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0119 1F0000]
16:01:48,825 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0119 18056000000000000000000000000000]
16:01:48,825 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 000B 180500]
16:01:48,845 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 000B 1F600000000000000000000000000000]
31: unknown:1805 {1805} internal, hidden
16:01:48,845 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A0A 000000]
16:01:48,865 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A0A 08000000000000000000000000000000]
Has 8 reprogrammable keys:
16:01:48,865 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A1C 000000]
16:01:48,885 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A1C 00500038010001010000000000000000]
16:01:48,885 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A2B 005000]
16:01:48,905 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A2B 00500000000000000000000000000000]
0: LEFT CLICK , default: LeftClick => LEFT CLICK
mse, pos:0, group:1, gmask:1
16:01:48,905 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A1D 010000]
16:01:48,925 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A1D 00510039010001010000000000000000]
16:01:48,925 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A28 005100]
16:01:48,945 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A28 00510000000000000000000000000000]
1: RIGHT CLICK , default: RightClick => RIGHT CLICK
mse, pos:0, group:1, gmask:1
16:01:48,945 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A1B 020000]
16:01:48,965 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A1B 0052003A310003070100000000000000]
16:01:48,965 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A2B 005200]
16:01:48,985 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A2B 00520000000000000000000000000000]
2: MIDDLE BUTTON , default: MiddleMouseButton => MIDDLE BUTTON
mse, reprogrammable, divertable, pos:0, group:3, gmask:7
16:01:48,985 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A1E 030000]
16:01:49,005 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A1E 0053003C310002030100000000000000]
16:01:49,006 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A28 005300]
16:01:49,027 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A28 00530000000000000000000000000000]
3: BACK AS BUTTON 4 , default: BackEx => BACK AS BUTTON 4
mse, reprogrammable, divertable, pos:0, group:2, gmask:3
16:01:49,027 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A1B 040000]
16:01:49,047 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A1B 0056003E310002030100000000000000]
16:01:49,047 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A2F 005600]
16:01:49,067 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A2F 00560000000000000000000000000000]
4: FORWARD AS BUTTON 5 , default: BrowserForwardEx => FORWARD AS BUTTON 5
mse, reprogrammable, divertable, pos:0, group:2, gmask:3
16:01:49,067 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A1C 050000]
16:01:49,087 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A1C 00C300A9310003070100000000000000]
16:01:49,087 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A2A 00C300]
16:01:49,107 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A2A 00C30000000000000000000000000000]
5: unknown:00C3 , default: unknown:00A9 => unknown:00C3
mse, reprogrammable, divertable, pos:0, group:3, gmask:7
16:01:49,107 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A1A 060000]
16:01:49,127 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A1A 00C4009D310003070100000000000000]
16:01:49,127 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A2C 00C400]
16:01:49,147 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A2C 00C40000000000000000000000000000]
6: unknown:00C4 , default: unknown:009D => unknown:00C4
mse, reprogrammable, divertable, pos:0, group:3, gmask:7
16:01:49,147 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A1C 070000]
16:01:49,167 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A1C 00D700B4A00004000300000000000000]
16:01:49,167 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 0A2C 00D700]
16:01:49,187 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 0A2C 00D70000000000000000000000000000]
7: unknown:00D7 , default: unknown:00B4 => unknown:00D7
divertable, virtual, pos:0, group:4, gmask:0
16:01:49,187 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 01 070B 000000]
16:01:49,207 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 01 070B 32140000000000000000000000000000]
16:01:49,207 DEBUG [MainThread] logitech_receiver.hidpp20: device 1 battery 50% charged, next level 20% charge, status 0 = discharging
Battery: 50%, discharging.
16:01:49,207 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 FF 83B5 210000]
16:01:49,213 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 FF 83B5 210814406E0402010700000000000000]
16:01:49,213 INFO [MainThread] logitech_receiver.receiver: <UnifyingReceiver(/dev/hidraw0,3)>: found new device 2 (406E)
16:01:49,213 DEBUG [MainThread] logitech_receiver.base: (3) pinging device 2
16:01:49,213 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 001F 0000F4]
16:01:49,245 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 001F 0405F400000000000000000000000000]
2: Wireless Illuminated Keyboard K800 new
Codename : new
Kind : keyboard
Wireless PID : 406E
Protocol : HID++ 4.5
Polling rate : 20 ms (50Hz)
16:01:49,245 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 FF 83B5 310000]
16:01:49,251 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 FF 83B5 31636E14131A40000007000000000000]
Serial number: 636E1413
16:01:49,251 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000A 000100]
16:01:49,285 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000A 01000100000000000000000000000000]
16:01:49,285 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 010B 000000]
16:01:49,325 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 010B 18000000000000000000000000000000]
16:01:49,325 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0009 000300]
16:01:49,365 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0009 02000200000000000000000000000000]
16:01:49,365 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 020C 000000]
16:01:49,405 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 020C 0254FC62F90004406E00000000010000]
16:01:49,405 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 021E 000000]
16:01:49,446 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 021E 01424F546000000200406ED4E7454501]
16:01:49,446 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 021C 010000]
16:01:49,487 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 021C 0052514B6801000901406ED4E7454501]
Bootloader: BOT 60.00.B0002
Firmware: RQK 68.01.B0009
The power switch is located on the top right corner.
Supports 25 HID++ 2.0 features:
16:01:49,487 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000F 000000]
16:01:49,527 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000F 00000100000000000000000000000000]
0: ROOT {0000}
16:01:49,527 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000F 000100]
16:01:49,567 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000F 01000100000000000000000000000000]
1: FEATURE SET {0001}
16:01:49,568 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000C 000300]
16:01:49,607 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000C 02000200000000000000000000000000]
2: DEVICE FW VERSION {0003}
16:01:49,607 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 011A 030000]
16:01:49,647 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 011A 00050000000000000000000000000000]
16:01:49,647 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000D 000500]
16:01:49,687 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000D 03000000000000000000000000000000]
3: DEVICE NAME {0005}
16:01:49,687 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 011C 040000]
16:01:49,729 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 011C 1D4B0000000000000000000000000000]
16:01:49,729 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000D 1D4B00]
16:01:49,770 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000D 04000000000000000000000000000000]
4: WIRELESS DEVICE STATUS {1D4B}
16:01:49,770 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0119 050000]
16:01:49,809 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0119 00200000000000000000000000000000]
16:01:49,809 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000D 002000]
16:01:49,849 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000D 05000000000000000000000000000000]
5: RESET {0020}
16:01:49,849 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0118 060000]
16:01:49,889 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0118 10000000000000000000000000000000]
16:01:49,889 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0009 100000]
16:01:49,929 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0009 06000000000000000000000000000000]
6: BATTERY STATUS {1000}
16:01:49,929 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 011D 070000]
16:01:49,969 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 011D 19830000000000000000000000000000]
16:01:49,969 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000B 198300]
16:01:50,009 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000B 07000000000000000000000000000000]
7: unknown:1983 {1983}
16:01:50,010 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 011F 080000]
16:01:50,051 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 011F 1B040003000000000000000000000000]
16:01:50,051 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000E 1B0400]
16:01:50,091 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000E 08000300000000000000000000000000]
8: REPROG CONTROLS V4 {1B04}
16:01:50,091 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0119 090000]
16:01:50,131 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0119 40A00000000000000000000000000000]
16:01:50,131 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0009 40A000]
16:01:50,171 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0009 09000000000000000000000000000000]
9: FN INVERSION {40A0}
16:01:50,171 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 011C 0A0000]
16:01:50,211 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 011C 41000000000000000000000000000000]
16:01:50,211 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000A 410000]
16:01:50,251 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000A 0A000000000000000000000000000000]
10: ENCRYPTION {4100}
16:01:50,251 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0118 0B0000]
16:01:50,293 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0118 45210000000000000000000000000000]
16:01:50,293 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000F 452100]
16:01:50,333 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000F 0B000000000000000000000000000000]
11: KEYBOARD DISABLE {4521}
16:01:50,334 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 011E 0C0000]
16:01:50,373 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 011E 00C20000000000000000000000000000]
16:01:50,373 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000F 00C200]
16:01:50,413 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000F 0C000000000000000000000000000000]
12: unknown:00C2 {00C2}
16:01:50,414 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0118 0D0000]
16:01:50,453 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0118 18036000000000000000000000000000]
16:01:50,454 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0008 180300]
16:01:50,493 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0008 0D600000000000000000000000000000]
13: unknown:1803 {1803} internal, hidden
16:01:50,494 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 011D 0E0000]
16:01:50,533 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 011D 18066001000000000000000000000000]
16:01:50,533 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000C 180600]
16:01:50,575 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000C 0E600100000000000000000000000000]
14: unknown:1806 {1806} internal, hidden
16:01:50,576 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 011A 0F0000]
16:01:50,615 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 011A 18116000000000000000000000000000]
16:01:50,615 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0008 181100]
16:01:50,655 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0008 0F600000000000000000000000000000]
15: unknown:1811 {1811} internal, hidden
16:01:50,656 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 011B 100000]
16:01:50,695 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 011B 18306000000000000000000000000000]
16:01:50,696 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000F 183000]
16:01:50,735 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000F 10600000000000000000000000000000]
16: unknown:1830 {1830} internal, hidden
16:01:50,736 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0119 110000]
16:01:50,775 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0119 18906000000000000000000000000000]
16:01:50,776 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000A 189000]
16:01:50,815 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000A 11600000000000000000000000000000]
17: unknown:1890 {1890} internal, hidden
16:01:50,816 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0118 120000]
16:01:50,858 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0118 18A16000000000000000000000000000]
16:01:50,858 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000F 18A100]
16:01:50,897 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000F 12600000000000000000000000000000]
18: unknown:18A1 {18A1} internal, hidden
16:01:50,898 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0118 130000]
16:01:50,937 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0118 1DF36000000000000000000000000000]
16:01:50,938 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000B 1DF300]
16:01:50,977 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000B 13600000000000000000000000000000]
19: unknown:1DF3 {1DF3} internal, hidden
16:01:50,978 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 011B 140000]
16:01:51,017 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 011B 1E004000000000000000000000000000]
16:01:51,018 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000E 1E0000]
16:01:51,057 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000E 14400000000000000000000000000000]
20: unknown:1E00 {1E00} hidden
16:01:51,058 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 011B 150000]
16:01:51,097 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 011B 1EB06000000000000000000000000000]
16:01:51,098 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000F 1EB000]
16:01:51,137 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000F 15600000000000000000000000000000]
21: unknown:1EB0 {1EB0} internal, hidden
16:01:51,138 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 011D 160000]
16:01:51,179 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 011D 18616000000000000000000000000000]
16:01:51,180 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000E 186100]
16:01:51,220 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000E 16600000000000000000000000000000]
22: unknown:1861 {1861} internal, hidden
16:01:51,220 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 011D 170000]
16:01:51,260 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 011D 1A206000000000000000000000000000]
16:01:51,260 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0008 1A2000]
16:01:51,300 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0008 17600000000000000000000000000000]
23: unknown:1A20 {1A20} internal, hidden
16:01:51,300 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0118 180000]
16:01:51,339 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0118 18B06000000000000000000000000000]
16:01:51,340 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 000E 18B000]
16:01:51,379 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 000E 18600000000000000000000000000000]
24: unknown:18B0 {18B0} internal, hidden
16:01:51,380 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 080E 000000]
16:01:51,419 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 080E 0F000000000000000000000000000000]
Has 15 reprogrammable keys:
16:01:51,420 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 081C 000000]
16:01:51,461 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 081C 0022001A3A0100000000000000000000]
16:01:51,462 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 082F 002200]
16:01:51,501 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 082F 00220000000000000000000000000000]
0: MY HOME , default: HomePage => MY HOME
is FN, FN sensitive, reprogrammable, divertable, pos:1, group:0, gmask:0
16:01:51,502 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0819 010000]
16:01:51,541 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0819 000E000E3A0200000000000000000000]
16:01:51,542 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0829 000E00]
16:01:51,581 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0829 000E0000000000000000000000000000]
1: Mail , default: Email => Mail
is FN, FN sensitive, reprogrammable, divertable, pos:2, group:0, gmask:0
16:01:51,582 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0818 020000]
16:01:51,621 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0818 003E002D3A0300000000000000000000]
16:01:51,622 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0828 003E00]
16:01:51,661 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0828 003E0000000000000000000000000000]
2: SEARCH , default: SearchForFiles => SEARCH
is FN, FN sensitive, reprogrammable, divertable, pos:3, group:0, gmask:0
16:01:51,662 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 081B 030000]
16:01:51,701 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 081B 000800083A0400000000000000000000]
16:01:51,702 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 082E 000800]
16:01:51,743 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 082E 00080000000000000000000000000000]
3: Application Switcher , default: Application Switcher => Application Switcher
is FN, FN sensitive, reprogrammable, divertable, pos:4, group:0, gmask:0
16:01:51,744 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 081D 040000]
16:01:51,784 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 081D 00E200C12A0500000000000000000000]
16:01:51,784 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 082E 00E200]
16:01:51,824 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 082E 00E20000000000000000000000000000]
4: unknown:00E2 , default: unknown:00C1 => unknown:00E2
is FN, FN sensitive, divertable, pos:5, group:0, gmask:0
16:01:51,824 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 081A 050000]
16:01:51,864 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 081A 00E300C22A0600000000000000000000]
16:01:51,864 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 082B 00E300]
16:01:51,904 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 082B 00E30000000000000000000000000000]
5: unknown:00E3 , default: unknown:00C2 => unknown:00E3
is FN, FN sensitive, divertable, pos:6, group:0, gmask:0
16:01:51,904 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0818 060000]
16:01:51,943 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0818 0040002F3A0800000000000000000000]
16:01:51,944 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 082E 004000]
16:01:51,984 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 082E 00400000000000000000000000000000]
6: SLEEP , default: Sleep => SLEEP
is FN, FN sensitive, reprogrammable, divertable, pos:8, group:0, gmask:0
16:01:51,984 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 081D 070000]
16:01:52,025 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 081D 0028001D3A0900000000000000000000]
16:01:52,026 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0828 002800]
16:01:52,065 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0828 00280000000000000000000000000000]
7: MEDIA PLAYER , default: Music => MEDIA PLAYER
is FN, FN sensitive, reprogrammable, divertable, pos:9, group:0, gmask:0
16:01:52,066 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 081F 080000]
16:01:52,106 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 081F 000600062A0A00000000000000000000]
16:01:52,106 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0828 000600]
16:01:52,145 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0828 00060000000000000000000000000000]
8: Previous , default: Previous => Previous
is FN, FN sensitive, divertable, pos:10, group:0, gmask:0
16:01:52,146 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 081F 090000]
16:01:52,186 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 081F 000400042A0B00000000000000000000]
16:01:52,186 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 082B 000400]
16:01:52,225 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 082B 00040000000000000000000000000000]
9: Play/Pause , default: Play/Pause => Play/Pause
is FN, FN sensitive, divertable, pos:11, group:0, gmask:0
16:01:52,226 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 081B 0A0000]
16:01:52,265 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 081B 000500052A0C00000000000000000000]
16:01:52,266 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0829 000500]
16:01:52,308 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0829 00050000000000000000000000000000]
10: Next , default: Next => Next
is FN, FN sensitive, divertable, pos:12, group:0, gmask:0
16:01:52,308 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0818 0B0000]
16:01:52,348 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0818 00010001240000000000000000000000]
16:01:52,348 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 082C 000100]
16:01:52,387 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 082C 00010000000000000000000000000000]
11: Volume Up , default: Volume Up => Volume Up
nonstandard, divertable, pos:0, group:0, gmask:0
16:01:52,388 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 081C 0C0000]
16:01:52,427 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 081C 00020002240000000000000000000000]
16:01:52,428 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 082C 000200]
16:01:52,468 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 082C 00020000000000000000000000000000]
12: Volume Down , default: Volume Down => Volume Down
nonstandard, divertable, pos:0, group:0, gmask:0
16:01:52,468 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 0819 0D0000]
16:01:52,507 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 0819 00030003240000000000000000000000]
16:01:52,508 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 082D 000300]
16:01:52,547 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 082D 00030000000000000000000000000000]
13: Mute , default: Mute => Mute
nonstandard, divertable, pos:0, group:0, gmask:0
16:01:52,548 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 081B 0E0000]
16:01:52,589 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 081B 000A000A340000000000000000000000]
16:01:52,590 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 082F 000A00]
16:01:52,630 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 082F 000A0000000000000000000000000000]
14: Calculator , default: Calculator => Calculator
nonstandard, reprogrammable, divertable, pos:0, group:0, gmask:0
16:01:52,630 DEBUG [MainThread] logitech_receiver.base: (3) <= w[10 02 060C 000000]
16:01:52,670 DEBUG [MainThread] logitech_receiver.base: (3) => r[11 02 060C 32140000000000000000000000000000]
16:01:52,670 DEBUG [MainThread] logitech_receiver.hidpp20: device 2 battery 50% charged, next level 20% charge, status 0 = discharging
Battery: 50%, discharging.

41
docs/devices/k830.txt Normal file
View File

@@ -0,0 +1,41 @@
# Provided by Mikkel Munch Mortensen
(solaar)
3: Illuminated Living-Room Keyboard K830
Codename : K830
Kind : keyboard
Wireless PID : 4032
Protocol : HID++ 4.1
Polling rate : 8 ms (125Hz)
Serial number: 9F7C6FD7
Firmware: RQK 56.00.B0020
The power switch is located on the edge of top right corner.
Supports 27 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: WIRELESS DEVICE STATUS {1D4B}
5: unknown:0020 {0020}
6: BATTERY STATUS {1000}
7: BACKLIGHT {1981}
8: unknown:1B04 {1B04}
9: unknown:2005 {2005}
10: NEW FN INVERSION {40A2}
11: ENCRYPTION {4100}
12: unknown:4521 {4521}
13: TOUCHPAD RAW XY {6100} hidden
14: unknown:6501 {6501}
15: unknown:00C1 {00C1}
16: unknown:1811 {1811} internal, hidden
17: unknown:1830 {1830} internal, hidden
18: unknown:1890 {1890} internal, hidden
19: unknown:18A0 {18A0} internal, hidden
20: unknown:1DF3 {1DF3} internal, hidden
21: unknown:1E00 {1E00} hidden
22: unknown:1EB0 {1EB0} internal, hidden
23: unknown:1861 {1861} internal, hidden
24: unknown:1A20 {1A20} internal, hidden
25: unknown:18B0 {18B0} internal, hidden
26: unknown:1F07 {1F07} internal, hidden
Battery: 50%, discharging.

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.

40
docs/devices/m235.txt Normal file
View File

@@ -0,0 +1,40 @@
Wireless Mouse M235
Codename : M235
Kind : mouse
Wireless PID : 4055
Protocol : HID++ 4.5
Polling rate : 8 ms (125Hz)
Serial number: 00000000
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
divertable, mse, pos:0, group:1, gmask:1
1: RIGHT CLICK , default: RightClick => RIGHT CLICK
divertable, mse, pos:0, group:1, gmask:1
2: MIDDLE BUTTON , default: MiddleMouseButton => MIDDLE BUTTON
divertable, mse, reprogrammable, pos:0, group:2, gmask:3
Battery: 70%, discharging.

29
docs/devices/m325.txt Normal file
View File

@@ -0,0 +1,29 @@
Wireless Mouse M325
Codename : M325
Kind : mouse
Wireless PID : 400A
Protocol : HID++ 2.0
Polling rate : 8 ms (125Hz)
Serial number: ABB05E01
Firmware: RQM 27.02.B0028
The power switch is located on the base.
Supports 13 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: BATTERY STATUS {1000}
5: WIRELESS DEVICE STATUS {1D4B}
6: unknown:1DF3 {1DF3} hidden
7: REPROG CONTROLS {1B00}
8: unknown:1DF0 {1DF0} hidden
9: unknown:1F03 {1F03} hidden
10: VERTICAL SCROLLING {2100}
11: HI RES SCROLLING {2120}
12: MOUSE POINTER {2200}
Has 5 reprogrammable keys:
0: LEFT CLICK => LeftClick mse, reprogrammable
1: RIGHT CLICK => RightClick mse, reprogrammable
2: MIDDLE BUTTON => MiddleMouseButton mse, reprogrammable
3: BACK AS BUTTON 4 => Back mse, reprogrammable
4: FORWARD AS BUTTON 5 => BrowserForward mse, reprogrammable

View File

@@ -1,2 +1,56 @@
No non-error messages received for GET_REG and GET_REG_LONG. Perhaps because
this is a HID++ 2.0 device?
Mouse
(ltunify)
HID++ version: 2.0
Device index 1
Mouse
Name: M525
Wireless Product ID: 4013
Serial number: DAFA335E
Device was unavailable, version information not available.
Total number of HID++ 2.0 features: 12
0: [0000] IRoot
1: [0001] IFeatureSet
2: [0003] IFirmwareInfo
3: [0005] GetDeviceNameType
4: [1000] batteryLevelStatus
5: [1D4B] WirelessDeviceStatus
6: [1DF3] H unknown
7: [1B00] SpecialKeysMSEButtons
8: [1DF0] H unknown
9: [1F03] H unknown
10: [2100] VerticalScrolling
11: [2120] HiResScrolling
12: [2200] MousePointer
(O = obsolete feature; H = SW hidden feature;
I = reserved for internal use)
(solaar)
1: Wireless Mouse M525
Codename : M525
Kind : mouse
Wireless PID : 4013
Protocol : HID++ 2.0
Polling rate : 8 ms (125Hz)
Serial number: DAFA335E
Firmware: RQM 27.02.B0028
The power switch is located on the base.
Supports 13 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: BATTERY STATUS {1000}
5: WIRELESS DEVICE STATUS {1D4B}
6: unknown:1DF3 {1DF3} hidden
7: REPROG CONTROLS {1B00}
8: unknown:1DF0 {1DF0} hidden
9: unknown:1F03 {1F03} hidden
10: VERTICAL SCROLLING {2100}
11: HI RES SCROLLING {2120}
12: MOUSE POINTER {2200}
Has 5 reprogrammable keys:
0: LEFT CLICK => LeftClick mse, reprogrammable
1: RIGHT CLICK => RightClick mse, reprogrammable
2: MIDDLE BUTTON => MiddleMouseButton mse, reprogrammable
3: BACK AS BUTTON 4 => Back mse, reprogrammable
4: FORWARD AS BUTTON 5 => BrowserForward mse, reprogrammable
Battery: 90%, discharging.

41
docs/devices/m560.txt Normal file
View File

@@ -0,0 +1,41 @@
1: Wireless Mouse M560
Codename : M560
Kind : mouse
Wireless PID : 402D
Protocol : HID++ 2.0
Polling rate : 8 ms (125Hz)
Serial number: 16E6E42A
Firmware: RQM 48.00.B0015
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: BATTERY STATUS {1000}
5: unknown:1830 {1830} internal, hidden
6: unknown:1850 {1850} internal, hidden
7: unknown:1860 {1860} internal, hidden
8: unknown:1890 {1890} internal, hidden
9: unknown:18A0 {18A0} internal, hidden
10: REPROG CONTROLS V3 {1B03}
11: WIRELESS DEVICE STATUS {1D4B}
12: unknown:1DF3 {1DF3} internal, hidden
13: REPROG CONTROLS {1B00}
14: unknown:1DF0 {1DF0} hidden
15: unknown:1E00 {1E00} hidden
16: unknown:18B0 {18B0} internal, hidden
17: unknown:1E90 {1E90} internal, hidden
18: unknown:1F03 {1F03} internal, hidden
19: VERTICAL SCROLLING {2100}
20: HI RES SCROLLING {2120}
21: MOUSE POINTER {2200}
Has 7 reprogrammable keys:
0: LEFT CLICK => LeftClick mse, reprogrammable
1: RIGHT CLICK => RightClick mse, reprogrammable
2: BACK AS BUTTON 4 => Win8BackHorzLeft mse, reprogrammable
3: FORWARD AS BUTTON 5 => Win8ForwardHorzRight mse, reprogrammable
4: unknown:00B0 => Win8MetroWin7Forward mse, reprogrammable
5: SHOW DESKTOP HPP => Win8ShowDesktopWin7Back mse, reprogrammable
6: unknown:00AF => Win8Charm Appswitch GifAnimation mse, reprogrammable, unknown:000020
Battery: 70%, discharging.

25
docs/devices/m570.txt Normal file
View File

@@ -0,0 +1,25 @@
Unifying Receiver
Device path : /dev/hidraw0
USB id : 046d:c52b
Serial : BAFF9007
Firmware : 12.03.B0025
Bootloader : 02.15
Other : AA.AA
Has 1 paired device(s) out of a maximum of 6.
Notifications: wireless, software present (0x000900)
Device activity counters: 1=221
1: Wireless Trackball M570
Codename : M570
Kind : mouse
Wireless PID : 1028
Protocol : HID++ 1.0
Polling rate : 8 ms (125Hz)
Serial number: 891DC05A
Firmware: 26.00.B0003
Bootloader: 02.06
Other: 00.01
The power switch is located on the base.
Notifications: battery status (0x100000).
Battery: 100%, discharging.

83
docs/devices/m585.txt Normal file
View File

@@ -0,0 +1,83 @@
Unifying Receiver
Device path : /dev/hidraw0
USB id : 046d:c52b
Serial : E21FAD57
Firmware : 24.06.B0030
Bootloader : 01.08
Other : AA.AC
Has 2 paired device(s) out of a maximum of 6.
Notifications: wireless, software present (0x000900)
Device activity counters: 1=190, 2=66
2: Multi Device Silent Mouse M585/M590
Codename : M585/M590
Kind : mouse
Wireless PID : 406B
Protocol : HID++ 4.5
Polling rate : 8 ms (125Hz)
Serial number: 22AEB299
Bootloader: BOT 48.01.B0002
Firmware: MPM 05.10.B0011
Other:
The power switch is located on the base.
Supports 35 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: WIRELESS DEVICE STATUS {1D4B}
5: RESET {0020}
6: unknown:0021 {0021}
7: unknown:0007 {0007}
8: BATTERY STATUS {1000}
9: CHANGE HOST {1814}
10: unknown:1815 {1815}
11: REPROG CONTROLS V4 {1B04}
12: unknown:1C00 {1C00}
13: POINTER SPEED {2205}
Pointer Speed: 1.31640625
14: VERTICAL SCROLLING {2100}
Roller type: standard
Ratchet per turn: 18
Scroll lines: 0
15: unknown:00C2 {00C2}
16: unknown:1802 {1802} internal, hidden
17: unknown:1803 {1803} internal, hidden
18: unknown:1806 {1806} internal, hidden
19: unknown:1805 {1805} internal, hidden
20: unknown:1813 {1813} internal, hidden
21: unknown:1830 {1830} internal, hidden
22: unknown:1861 {1861} internal, hidden
23: unknown:1890 {1890} internal, hidden
24: unknown:1891 {1891} internal, hidden
25: unknown:18A1 {18A1} internal, hidden
26: unknown:1DF3 {1DF3} internal, hidden
27: unknown:1E00 {1E00} hidden
28: unknown:1EB0 {1EB0} internal, hidden
29: unknown:18B1 {18B1} internal, hidden
30: unknown:1850 {1850} internal, hidden
31: unknown:1E22 {1E22}
32: unknown:1F03 {1F03} internal, hidden
33: unknown:18C0 {18C0} internal, hidden
34: LOWRES WHEEL {2130}
Wheel Reports: HID
Has 8 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, persistently divertable, reprogrammable, pos:0, group:2, gmask:3
3: BACK AS BUTTON 4 , default: BackEx => BACK AS BUTTON 4
divertable, mse, persistently divertable, reprogrammable, pos:0, group:2, gmask:3
4: FORWARD AS BUTTON 5 , default: BrowserForwardEx => FORWARD AS BUTTON 5
divertable, mse, persistently divertable, reprogrammable, pos:0, group:2, gmask:3
5: LEFT SCROLL AS AC PAN , default: HorzScrollLeftSet => LEFT SCROLL AS AC PAN
divertable, mse, persistently divertable, reprogrammable, pos:0, group:2, gmask:3
6: RIGHT SCROLL AS AC PAN , default: HorzScrollRightSet => RIGHT SCROLL AS AC PAN
divertable, mse, persistently divertable, reprogrammable, pos:0, group:2, gmask:3
7: unknown:00D7 , default: unknown:00B4 => unknown:00D7
divertable, virtual, pos:0, group:3, gmask:0
Battery: 50%, discharging.

View File

@@ -36,7 +36,7 @@ registers:
<< ( 259.270) [10 02 81F1 000000] '\x10\x02\x81\xf1\x00\x00\x00'
>> ( 259.283) [10 02 8F81 F10300] '\x10\x02\x8f\x81\xf1\x03\x00'
# writing 01 here will trigger an avalance of events, most likely
# writing 01 here will trigger an avalanche of events, most likely
# raw input from the mouse; disable by writing 00
<< ( 261.300) [10 02 81F3 000000] '\x10\x02\x81\xf3\x00\x00\x00'
>> ( 261.315) [10 02 81F3 000000] '\x10\x02\x81\xf3\x00\x00\x00'

View File

@@ -0,0 +1,82 @@
Unifying Receiver
Device path : /dev/hidraw1
USB id : 046d:c534
Serial : 0
Firmware : 29.00.B0015
Has 2 paired device(s) out of a maximum of 6.
Notifications: (none)
1: Wireless Keyboard MK270
Codename : MK270
Kind : keyboard
Wireless PID : 4023
Protocol : HID++ 2.0
Polling rate : 20 ms (50Hz)
Serial number: 00000000
Firmware: RQK 49.00.B0029
Supports 18 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: BATTERY STATUS {1000}
5: REPROG CONTROLS {1B00}
6: WIRELESS DEVICE STATUS {1D4B}
7: FN INVERSION {40A0}
8: ENCRYPTION {4100}
9: KEYBOARD LAYOUT {4520}
10: unknown:1810 {1810} internal, hidden
11: unknown:1830 {1830} internal, hidden
12: unknown:1890 {1890} internal, hidden
13: unknown:18A0 {18A0} internal, hidden
14: unknown:18B0 {18B0} internal, hidden
15: unknown:1DF3 {1DF3} internal, hidden
16: unknown:1E00 {1E00} hidden
17: unknown:1868 {1868} internal, hidden
Has 11 reprogrammable keys:
0: MY HOME => HomePage is FN, FN sensitive, reprogrammable
1: Mail => Email is FN, FN sensitive, reprogrammable
2: SEARCH => Search is FN, FN sensitive, reprogrammable
3: Calculator => Calculator is FN, FN sensitive, reprogrammable
4: MEDIA PLAYER => Music is FN, FN sensitive, reprogrammable
5: Previous => Previous is FN, FN sensitive
6: Play/Pause => Play/Pause is FN, FN sensitive
7: Next => Next is FN, FN sensitive
8: Mute => Mute is FN, FN sensitive
9: Volume Down => Volume Down is FN, FN sensitive
10: Volume Up => Volume Up is FN, FN sensitive
Battery: 30%, discharging.
2: Wireless Mouse
Codename :
Kind : mouse
Wireless PID : 4022
Protocol : HID++ 2.0
Polling rate : 8 ms (125Hz)
Serial number: 00000000
Firmware: RQM 38.00.B0044
Supports 18 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: BATTERY STATUS {1000}
5: REPROG CONTROLS {1B00}
6: WIRELESS DEVICE STATUS {1D4B}
7: VERTICAL SCROLLING {2100}
8: MOUSE POINTER {2200}
9: unknown:1810 {1810} internal, hidden
10: unknown:1830 {1830} internal, hidden
11: unknown:1850 {1850} internal, hidden
12: unknown:1890 {1890} internal, hidden
13: unknown:18B0 {18B0} internal, hidden
14: unknown:1DF3 {1DF3} internal, hidden
15: unknown:1868 {1868} internal, hidden
16: unknown:1869 {1869} internal, hidden
17: unknown:1E00 {1E00} hidden
Has 3 reprogrammable keys:
0: LEFT CLICK => LeftClick mse, reprogrammable
1: RIGHT CLICK => RightClick mse, reprogrammable
2: MIDDLE BUTTON => MiddleMouseButton mse, reprogrammable
Battery: 30%, discharging.

78
docs/devices/mk220.txt Normal file
View File

@@ -0,0 +1,78 @@
Unifying Receiver
Device path : /dev/hidraw1
USB id : 046d:c52e
Serial : 758596BF
Firmware : 23.01.B0006
Has 2 paired device(s) out of a maximum of 2.
Notifications: wireless, software present (0x000900)
1: Wireless Mouse M150
Codename : M150
Kind : mouse
Wireless PID : 400C
Protocol : HID++ 2.0
Polling rate : 8 ms (125Hz)
Serial number: 93850883
Firmware: RQM 29.00.B0010
The power switch is located on the base.
Supports 16 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: unknown:1850 {1850} hidden
5: unknown:1860 {1860} hidden
6: BATTERY STATUS {1000}
7: WIRELESS DEVICE STATUS {1D4B}
8: unknown:1DF3 {1DF3} hidden
9: REPROG CONTROLS {1B00}
10: unknown:1DF0 {1DF0} hidden
11: unknown:1E00 {1E00} hidden
12: unknown:1E80 {1E80} hidden
13: unknown:1F03 {1F03} hidden
14: VERTICAL SCROLLING {2100}
15: MOUSE POINTER {2200}
Has 3 reprogrammable keys:
0: LEFT CLICK => LeftClick mse, reprogrammable
1: RIGHT CLICK => RightClick mse, reprogrammable
2: MIDDLE BUTTON => MiddleMouseButton mse, reprogrammable
Battery: 90%, discharging.
2: Wireless Keyboard K220
Codename : K220
Kind : keyboard
Wireless PID : 4005
Protocol : HID++ 2.0
Polling rate : 20 ms (50Hz)
Serial number: 5BB1D72E
Firmware: RQK 37.00.B0011
Supports 14 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: FEATURE INFO {0002}
3: DEVICE FW VERSION {0003}
4: DEVICE NAME {0005}
5: BATTERY STATUS {1000}
6: unknown:1820 {1820} hidden
7: REPROG CONTROLS {1B00}
8: REPROG CONTROLS V2 {1B01}
9: WIRELESS DEVICE STATUS {1D4B}
10: unknown:1DF0 {1DF0} hidden
11: unknown:1DF3 {1DF3} hidden
12: ENCRYPTION {4100}
13: KEYBOARD LAYOUT {4520}
Has 12 reprogrammable keys:
0: FN F1 => Do Nothing One is FN, reprogrammable
1: FN F2 => Do Nothing One is FN, reprogrammable
2: FN F3 => Do Nothing One is FN, reprogrammable
3: FN F4 => Do Nothing One is FN, reprogrammable
4: FN F5 => Do Nothing One is FN, reprogrammable
5: FN F6 => Do Nothing One is FN, reprogrammable
6: FN F7 => Do Nothing One is FN, reprogrammable
7: FN F8 => Do Nothing One is FN, reprogrammable
8: FN F9 => Do Nothing One is FN, reprogrammable
9: Mute => Mute is FN
10: Volume Down => Volume Down is FN
11: Volume Up => Volume Up is FN
Battery: 90%, discharging.

129
docs/devices/mk240-nano.md Normal file
View File

@@ -0,0 +1,129 @@
# Logitech MK240 NANO Device Information
## `solaar show all` Dump
```
Unifying Receiver (NOTE: NOT claimed to be supporting Unifying from the package box, the only advanced feature that may be related is 128-bit AES encryption) (M/N: C-U0010)
Device path : /dev/hidraw0
USB id : 046d:c534
Serial : 0
Firmware : 29.01.B0016
Has 2 paired device(s) out of a maximum of 6.
Notifications: wireless, software present (0x000900)
1: Wireless Keyboard MK270 (NOTE: The Product is actually "MK240 NANO Wireless Keyboard and Mouse Combo" with the "K240"(M/N: Y-R0036) keyboard model)
Codename : MK270
Kind : keyboard
Wireless PID : 4023
Protocol : HID++ 2.0
Polling rate : 20 ms (50Hz)
Serial number: 4BBBBA4A
Firmware: RQK 49.00.B0029
Supports 18 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: BATTERY STATUS {1000}
5: REPROG CONTROLS {1B00}
6: WIRELESS DEVICE STATUS {1D4B}
7: FN INVERSION {40A0}
8: ENCRYPTION {4100}
9: KEYBOARD LAYOUT {4520}
10: unknown:1810 {1810} internal, hidden
11: unknown:1830 {1830} internal, hidden
12: unknown:1890 {1890} internal, hidden
13: unknown:18A0 {18A0} internal, hidden
14: unknown:18B0 {18B0} internal, hidden
15: unknown:1DF3 {1DF3} internal, hidden
16: unknown:1E00 {1E00} hidden
17: unknown:1868 {1868} internal, hidden
Has 11 reprogrammable keys:
0: MY HOME => HomePage is FN, FN sensitive, reprogrammable
1: Mail => Email is FN, FN sensitive, reprogrammable
2: SEARCH => Search is FN, FN sensitive, reprogrammable
3: Calculator => Calculator is FN, FN sensitive, reprogrammable
4: MEDIA PLAYER => Music is FN, FN sensitive, reprogrammable
5: Previous => Previous is FN, FN sensitive
6: Play/Pause => Play/Pause is FN, FN sensitive
7: Next => Next is FN, FN sensitive
8: Mute => Mute is FN, FN sensitive
9: Volume Down => Volume Down is FN, FN sensitive
10: Volume Up => Volume Up is FN, FN sensitive
Battery: 30%, discharging. (NOTE: Capacity readings appears to be faked, or in extremely low sensitivity)
2: Wireless Mouse M150 (NOTE: The Product is actually "MK240 NANO Wireless Keyboard and Mouse Combo" with the "M212"(M/N: M-R0041) mouse model)
Codename : M150
Kind : mouse
Wireless PID : 4022
Protocol : HID++ 2.0
Polling rate : 8 ms (125Hz)
Serial number: 00000000
Firmware: RQM 38.00.B0044
Supports 18 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: BATTERY STATUS {1000}
5: REPROG CONTROLS {1B00}
6: WIRELESS DEVICE STATUS {1D4B}
7: VERTICAL SCROLLING {2100}
8: MOUSE POINTER {2200}
9: unknown:1810 {1810} internal, hidden
10: unknown:1830 {1830} internal, hidden
11: unknown:1850 {1850} internal, hidden
12: unknown:1890 {1890} internal, hidden
13: unknown:18B0 {18B0} internal, hidden
14: unknown:1DF3 {1DF3} internal, hidden
15: unknown:1868 {1868} internal, hidden
16: unknown:1869 {1869} internal, hidden
17: unknown:1E00 {1E00} hidden
Has 3 reprogrammable keys:
0: LEFT CLICK => LeftClick mse, reprogrammable
1: RIGHT CLICK => RightClick mse, reprogrammable
2: MIDDLE BUTTON => MiddleMouseButton mse, reprogrammable
Battery: 30%, discharging. (NOTE: Capacity readings appears to be faked, or in extremely low sensitivity, in the Logitech SetPoint utility battery level is displayed as "HIGH")
```
## Connect Utility Report
```
Re-Connect Software Version : 2.00.3
Dj Api Version : 2, 50, 25
接收器(Receiver)
Name : 無線接收器(Wireless Receiver)
ModelId : 0x46dc534
Serial Number :
Handle : 0xff000001
Wireless Status : 0x3
Firmware version : 029.001.00016
Bootloader version :
Dfu Status : 0x1
Is Dfu Cancellable : Yes
Max Device Capacity : 6
滑鼠(Mouse)
Name :
ModelId : 0x0
Serial Number : 4022-00-00-00-00
Handle : 0x2000003
Wireless Status : 0x0
Firmware version : 038.000.00044
Bootloader version :
Dfu Status : 0x1
Is Dfu Cancellable : No
Battery Status : 0x2
Parent Handle : 0xff000001
鍵盤(Keyboard)
Name :
ModelId : 0x0
Serial Number : 4023-4B-BB-BA-4A
Handle : 0x1000002
Wireless Status : 0x0
Firmware version : 049.000.00029
Bootloader version :
Dfu Status : 0x1
Is Dfu Cancellable : No
Battery Status : 0x2
Parent Handle : 0xff000001
```

86
docs/devices/mk270.txt Normal file
View File

@@ -0,0 +1,86 @@
Unifying Receiver
Device path : /dev/hidraw9
USB id : 046d:c534
Serial : 00000000
Firmware : 29.00.B0015
Has 2 paired device(s) out of a maximum of 6.
Notifications: (none)
1: Wireless Keyboard
Codename : MK270
Kind : keyboard
Wireless PID : 4023
Protocol : HID++ 2.0
Polling rate : 20 ms (50Hz)
Serial number: 00000000
Firmware: RQK 49.00.B0029
Supports 18 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: BATTERY STATUS {1000}
5: REPROG CONTROLS {1B00}
6: WIRELESS DEVICE STATUS {1D4B}
7: FN INVERSION {40A0}
8: ENCRYPTION {4100}
9: KEYBOARD LAYOUT {4520}
10: unknown:1810 {1810} internal, hidden
11: unknown:1830 {1830} internal, hidden
12: unknown:1890 {1890} internal, hidden
13: unknown:18A0 {18A0} internal, hidden
14: unknown:18B0 {18B0} internal, hidden
15: unknown:1DF3 {1DF3} internal, hidden
16: unknown:1E00 {1E00} hidden
17: unknown:1868 {1868} internal, hidden
Has 11 reprogrammable keys:
0: MY HOME => HomePage FN sensitive, is FN, reprogrammable
1: Mail => Email FN sensitive, is FN, reprogrammable
2: SEARCH => Search FN sensitive, is FN, reprogrammable
3: Calculator => Calculator FN sensitive, is FN, reprogrammable
4: MEDIA PLAYER => Music FN sensitive, is FN, reprogrammable
5: Previous => Previous FN sensitive, is FN
6: Play/Pause => Play/Pause FN sensitive, is FN
7: Next => Next FN sensitive, is FN
8: Mute => Mute FN sensitive, is FN
9: Volume Down => Volume Down FN sensitive, is FN
10: Volume Up => Volume Up FN sensitive, is FN
Battery: 30%, discharging.
2: Wireless Mouse
Codename : M185
Kind : mouse
Wireless PID : 4038
Protocol : HID++ 2.0
Polling rate : 8 ms (125Hz)
Serial number: 00000000
Firmware: RQM 54.00.B0004
Supports 22 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: BATTERY STATUS {1000}
5: unknown:1830 {1830} internal, hidden
6: unknown:1850 {1850} internal, hidden
7: unknown:1860 {1860} internal, hidden
8: unknown:1890 {1890} internal, hidden
9: unknown:18A0 {18A0} internal, hidden
10: unknown:18C0 {18C0} internal, hidden
11: WIRELESS DEVICE STATUS {1D4B}
12: unknown:1DF3 {1DF3} internal, hidden
13: REPROG CONTROLS {1B00}
14: unknown:1DF0 {1DF0} hidden
15: unknown:1E00 {1E00} hidden
16: unknown:1E80 {1E80} internal, hidden
17: unknown:1E90 {1E90} internal, hidden
18: unknown:1F03 {1F03} internal, hidden
19: VERTICAL SCROLLING {2100}
20: MOUSE POINTER {2200}
21: unknown:18B0 {18B0} internal, hidden
Has 3 reprogrammable keys:
0: LEFT CLICK => LeftClick mse, reprogrammable
1: RIGHT CLICK => RightClick mse, reprogrammable
2: MIDDLE BUTTON => MiddleMouseButton mse, reprogrammable
Battery: 70%, discharging.

61
docs/devices/mk520.txt Normal file
View File

@@ -0,0 +1,61 @@
Unifying Receiver
Device path : /dev/hidraw3
USB id : 046d:c52b
Serial : AF9C98A0
Firmware : 12.03.B0025
Bootloader : 02.15
Other : AA.AA
Has 2 paired device(s) out of a maximum of 6.
Notifications: wireless, software present (0x000900)
Device activity counters: 1=22, 2=167
1: Wireless Mouse M310/M310t
Codename : M310/M310t
Kind : mouse
Wireless PID : 4031
Protocol : HID++ 2.0
Polling rate : 8 ms (125Hz)
Serial number: 03E8C0B4
Firmware: RQM 40.01.B0017
The power switch is located on the base.
Supports 21 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: BATTERY STATUS {1000}
5: unknown:1830 {1830} internal, hidden
6: unknown:1850 {1850} internal, hidden
7: unknown:1860 {1860} internal, hidden
8: unknown:1890 {1890} internal, hidden
9: unknown:18A0 {18A0} internal, hidden
10: unknown:18C0 {18C0} internal, hidden
11: WIRELESS DEVICE STATUS {1D4B}
12: unknown:1DF3 {1DF3} internal, hidden
13: REPROG CONTROLS {1B00}
14: unknown:1DF0 {1DF0} hidden
15: unknown:1E00 {1E00} hidden
16: unknown:1E80 {1E80} internal, hidden
17: unknown:1E90 {1E90} internal, hidden
18: unknown:1F03 {1F03} internal, hidden
19: VERTICAL SCROLLING {2100}
20: MOUSE POINTER {2200}
Has 3 reprogrammable keys:
0: LEFT CLICK => LeftClick mse, reprogrammable
1: RIGHT CLICK => RightClick mse, reprogrammable
2: MIDDLE BUTTON => MiddleMouseButton mse, reprogrammable
Battery: 70%, discharging.
2: Wireless Keyboard K520
Codename : K520
Kind : keyboard
Wireless PID : 2011
Protocol : HID++ 1.0
Polling rate : 20 ms (50Hz)
Serial number: ACDE97EF
Firmware: 26.00.B0012
Other: 00.07
The power switch is located on the top case.
Notifications: (none).
Battery: full, discharging.

View File

@@ -0,0 +1,84 @@
Unifying Receiver
Device path : /dev/hidraw7
USB id : 046d:c52b
Serial : BB7D9447
Firmware : 24.01.B0023
Bootloader : 01.08
Other : AA.AD
Has 2 paired device(s) out of a maximum of 6.
Notifications: wireless, software present (0x000900)
Device activity counters: 2=156
1: Wireless Mouse MX Master 2S
Codename : MX Master 2S
Kind : mouse
Wireless PID : 4069
Protocol : HID++ 4.5
Polling rate : 8 ms (125Hz)
Serial number: EAB71F76
Bootloader: BOT 56.01.B0006
Firmware: MPM 12.01.B0006
Firmware: MPM 12.01.B0006
Other:
The power switch is located on the base.
Supports 32 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: WIRELESS DEVICE STATUS {1D4B}
5: RESET {0020}
6: unknown:0021 {0021}
7: BATTERY STATUS {1000}
8: unknown:1806 {1806} internal, hidden
9: CHANGE HOST {1814}
10: REPROG CONTROLS V4 {1B04}
11: ADJUSTABLE DPI {2201}
12: VERTICAL SCROLLING {2100}
Roller type: 3G
Ratchet per turn: 24
Scroll lines: 0
13: SMART SHIFT {2110}
14: HIRES WHEEL {2121}
Multiplier: 8
Has invert
Normal wheel motion
Has ratchet switch
Normal wheel mode
High resolution mode
HID notification
15: GESTURE 2 {6501}
16: unknown:00C2 {00C2}
17: unknown:1813 {1813} internal, hidden
18: unknown:1830 {1830} internal, hidden
19: unknown:1890 {1890} internal, hidden
20: unknown:1891 {1891} internal, hidden
21: unknown:18A1 {18A1} internal, hidden
22: unknown:18C0 {18C0} internal, hidden
23: unknown:1DF3 {1DF3} internal, hidden
24: unknown:1E00 {1E00} hidden
25: unknown:1EB0 {1EB0} internal, hidden
26: unknown:1803 {1803} internal, hidden
27: unknown:1861 {1861} internal, hidden
28: unknown:9001 {9001} internal, hidden
29: unknown:9200 {9200} internal, hidden
30: unknown:9202 {9202} internal, hidden
31: unknown:1805 {1805} internal, hidden
Has 8 reprogrammable keys:
0: LEFT CLICK , default: LeftClick => LEFT CLICK
mse, pos:0, group:1, gmask:1
1: RIGHT CLICK , default: RightClick => RIGHT CLICK
mse, pos:0, group:1, gmask:1
2: MIDDLE BUTTON , default: MiddleMouseButton => MIDDLE BUTTON
mse, reprogrammable, divertable, pos:0, group:3, gmask:7
3: BACK AS BUTTON 4 , default: BackEx => BACK AS BUTTON 4
mse, reprogrammable, divertable, pos:0, group:2, gmask:3
4: FORWARD AS BUTTON 5 , default: BrowserForwardEx => FORWARD AS BUTTON 5
mse, reprogrammable, divertable, pos:0, group:2, gmask:3
5: unknown:00C3 , default: unknown:00A9 => unknown:00C3
mse, reprogrammable, divertable, pos:0, group:3, gmask:7
6: unknown:00C4 , default: unknown:009D => unknown:00C4
mse, reprogrammable, divertable, pos:0, group:3, gmask:7
7: unknown:00D7 , default: unknown:00B4 => unknown:00D7
divertable, virtual, pos:0, group:4, gmask:0
Battery: 50%, discharging.

View File

@@ -0,0 +1,83 @@
Unifying Receiver
Device path : /dev/hidraw0
USB id : 046d:c52b
Serial : 32A29DF4
Firmware : 12.09.B0030
Bootloader : 04.16
Other : AA.AA
Has 1 paired device(s) out of a maximum of 6.
Notifications: wireless (0x000100)
Device activity counters: 1=18
1: Wireless Mouse MX Master 3
Codename : MX Master 3
Kind : mouse
Wireless PID : 4082
Protocol : HID++ 4.5
Polling rate : 8 ms (125Hz)
Serial number: B0C9EC6E
Bootloader: BOT 95.00.B0013
Firmware: MPM 19.00.B0013
Other:
The power switch is located on the base.
Supports 34 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: WIRELESS DEVICE STATUS {1D4B}
5: RESET {0020}
6: unknown:0021 {0021}
7: BATTERY STATUS {1000}
8: REPROG CONTROLS V4 {1B04}
9: CHANGE HOST {1814}
10: unknown:2250 {2250}
11: ADJUSTABLE DPI {2201}
12: SMART SHIFT {2110}
13: HIRES WHEEL {2121}
Multiplier: 15
Has invert
Normal wheel motion
Has ratchet switch
Normal wheel mode
Low resolution mode
HID notification
14: unknown:2150 {2150}
15: unknown:2251 {2251}
16: unknown:00C2 {00C2}
17: unknown:1802 {1802} internal, hidden
18: unknown:1803 {1803} internal, hidden
19: unknown:1806 {1806} internal, hidden
20: unknown:1813 {1813} internal, hidden
21: unknown:1805 {1805} internal, hidden
22: unknown:1830 {1830} internal, hidden
23: unknown:1890 {1890} internal, hidden
24: unknown:1891 {1891} internal, hidden
25: unknown:18A1 {18A1} internal, hidden
26: unknown:1DF3 {1DF3} internal, hidden
27: unknown:1E00 {1E00} hidden
28: unknown:1EB0 {1EB0} internal, hidden
29: unknown:1861 {1861} internal, hidden
30: unknown:9001 {9001} internal, hidden
31: unknown:9203 {9203} internal, hidden
32: unknown:9205 {9205} internal, hidden
33: unknown:9300 {9300} internal, hidden
Has 8 reprogrammable keys:
0: LEFT CLICK , default: LeftClick => LEFT CLICK
mse, pos:0, group:1, gmask:1
1: RIGHT CLICK , default: RightClick => RIGHT CLICK
mse, pos:0, group:1, gmask:1
2: MIDDLE BUTTON , default: MiddleMouseButton => MIDDLE BUTTON
divertable, mse, reprogrammable, pos:0, group:3, gmask:7
3: BACK AS BUTTON 4 , default: BackEx => BACK AS BUTTON 4
divertable, mse, reprogrammable, pos:0, group:2, gmask:3
4: FORWARD AS BUTTON 5 , default: BrowserForwardEx => FORWARD AS BUTTON 5
divertable, mse, reprogrammable, pos:0, group:2, gmask:3
5: unknown:00C3 , default: unknown:00A9 => unknown:00C3
divertable, mse, reprogrammable, pos:0, group:3, gmask:7
6: unknown:00C4 , default: unknown:009D => unknown:00C4
divertable, mse, reprogrammable, pos:0, group:3, gmask:7
7: unknown:00D7 , default: unknown:00B4 => unknown:00D7
divertable, virtual, pos:0, group:4, gmask:0
Battery: 100%, discharging.

View File

@@ -0,0 +1,54 @@
Unifying Receiver
Device path : /dev/hidraw3
USB id : 046d:c52b
Serial : 0E039B8F
Firmware : 24.01.B0023
Bootloader : 01.08
Other : AA.AC
Has 1 paired device(s) out of a maximum of 6.
Notifications: wireless, software present (0x000900)
Device activity counters: 1=134
1: Wireless Mouse MX Master
Codename : MX Master
Kind : mouse
Wireless PID : 4041
Protocol : HID++ 4.5
Polling rate : 8 ms (125Hz)
Serial number: A975E230
Bootloader: BOT 18.00.B0012
Firmware: MPM 11.00.B0012
Firmware: MPM 11.00.B0012
Other:
The power switch is located on the base.
Supports 29 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: DEVICE FW VERSION {0003}
3: DEVICE NAME {0005}
4: WIRELESS DEVICE STATUS {1D4B}
5: RESET {0020}
6: BATTERY STATUS {1000}
7: CHANGE HOST {1814}
8: REPROG CONTROLS V4 {1B04}
9: ADJUSTABLE DPI {2201}
10: VERTICAL SCROLLING {2100}
11: SMART SHIFT {2110}
12: HIRES WHEEL {2121}
13: GESTURE 2 {6501}
14: DFUCONTROL 2 {00C1}
15: unknown:1813 {1813} internal, hidden
16: unknown:1830 {1830} internal, hidden
17: unknown:1890 {1890} internal, hidden
18: unknown:18A1 {18A1} internal, hidden
19: unknown:18C0 {18C0} internal, hidden
20: unknown:1DF3 {1DF3} internal, hidden
21: unknown:1E00 {1E00} hidden
22: unknown:1EB0 {1EB0} internal, hidden
23: unknown:1803 {1803} internal, hidden
24: unknown:1861 {1861} internal, hidden
25: unknown:9000 {9000} internal, hidden
26: unknown:9200 {9200} internal, hidden
27: unknown:9240 {9240} internal, hidden
28: unknown:1805 {1805} internal, hidden
Battery: 50%, discharging.

109
docs/devices/t400.txt Normal file
View File

@@ -0,0 +1,109 @@
Receiver
LZ2388S-DJ
M/N:C-U0007
(ltunify)
Serial number: E6B794F8
Firmware version: 012.001.00019
Bootloader version: BL.002.014
Mouse
(ltunify)
HID++ version: 2.0
Device index 1
Mouse
Name: T400
Wireless Product ID: 4026
Serial number: 131A3093
Device was unavailable, version information not available.
Total number of HID++ 2.0 features: 27
0: [0000] IRoot
1: [0001] IFeatureSet
2: [0002] unknown
3: [0003] IFirmwareInfo
4: [0005] GetDeviceNameType
5: [00C0] DFUControl
6: [1000] batteryLevelStatus
7: [1802] HI unknown
8: [1810] HI unknown
9: [1830] HI unknown
10: [1850] HI unknown
11: [1860] HI unknown
12: [1890] HI unknown
13: [18A0] HI unknown
14: [18E3] HI unknown
15: [1B00] SpecialKeysMSEButtons
16: [1D4B] WirelessDeviceStatus
17: [1DF3] HI unknown
18: [1E00] H unknown
19: [1E80] HI unknown
20: [1F03] HI unknown
21: [1F04] HI unknown
22: [2100] VerticalScrolling
23: [2101] H unknown
24: [2120] HiResScrolling
25: [2200] MousePointer
26: [6110] H TouchmouseRawPoints
27: [1B03] ReprogControlsV3
(O = obsolete feature; H = SW hidden feature;
I = reserved for internal use)
(solaar)
Unifying Receiver
Device path : /dev/hidraw2
USB id : 046d:c52b
Serial : E6B794F8
Firmware : 12.01.B0019
Bootloader : 02.14
Has 1 paired device(s) out of a maximum of 6.
Notifications: (none)
Device activity counters: 1=134
1: Zone Touch Mouse T400
Codename : T400
Kind : mouse
Wireless PID : 4026
Protocol : HID++ 2.0
Polling rate : 8 ms (125Hz)
Serial number: 131A3093
Firmware: RQM 39.00.B0029
Bootloader: BL 03.00
Hardware: 72
Other:
The power switch is located on the base.
Supports 28 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: FEATURE INFO {0002}
3: DEVICE FW VERSION {0003}
4: DEVICE NAME {0005}
5: DFUCONTROL {00C0}
6: BATTERY STATUS {1000}
7: unknown:1802 {1802} internal, hidden
8: unknown:1810 {1810} internal, hidden
9: unknown:1830 {1830} internal, hidden
10: unknown:1850 {1850} internal, hidden
11: unknown:1860 {1860} internal, hidden
12: unknown:1890 {1890} internal, hidden
13: unknown:18A0 {18A0} internal, hidden
14: unknown:18E3 {18E3} internal, hidden
15: REPROG CONTROLS {1B00}
16: WIRELESS DEVICE STATUS {1D4B}
17: unknown:1DF3 {1DF3} internal, hidden
18: unknown:1E00 {1E00} hidden
19: unknown:1E80 {1E80} internal, hidden
20: unknown:1F03 {1F03} internal, hidden
21: unknown:1F04 {1F04} internal, hidden
22: VERTICAL SCROLLING {2100}
23: unknown:2101 {2101} hidden
24: HI RES SCROLLING {2120}
25: MOUSE POINTER {2200}
26: TOUCHMOUSE RAW POINTS {6110} hidden
27: REPROG CONTROLS V3 {1B03}
Has 7 reprogrammable keys:
0: LEFT CLICK => LeftClick mse, reprogrammable
1: RIGHT CLICK => RightClick mse, reprogrammable
2: MIDDLE BUTTON => MiddleMouseButton mse, reprogrammable
3: METRO START SCREEN => MetroStartScreen mse, reprogrammable
4: ZOOMIN => Do Nothing mse, reprogrammable
5: ZOOMOUT => Do Nothing mse, reprogrammable
6: BACK HSCROLL => TouchBackForwardHorzScroll
Battery: 100%, discharging.

94
docs/devices/t650.txt Normal file
View File

@@ -0,0 +1,94 @@
Receiver
LZ2458D-DJ
M/N:C-U0008
(ltunify)
Serial number: 28E69A3E
Firmware version: 024.000.00018
Bootloader version: BL.000.006
Touchpad
(ltunify)
HID++ version: 2.0
Device index 1
Touchpad
Name: T650
Wireless Product ID: 4101
Serial number: 22205A4D
Device was unavailable, version information not available.
Total number of HID++ 2.0 features: 22
0: [0000] IRoot
1: [0001] IFeatureSet
2: [0002] unknown
3: [0003] IFirmwareInfo
4: [0005] GetDeviceNameType
5: [1000] batteryLevelStatus
6: [1D4B] WirelessDeviceStatus
7: [1DF3] HI unknown
8: [1B00] SpecialKeysMSEButtons
9: [1F03] HI unknown
10: [2100] VerticalScrolling
11: [2120] HiResScrolling
12: [2200] MousePointer
13: [00C0] DFUControl
14: [1E80] HI unknown
15: [6100] TouchpadRawXy
16: [1860] HI unknown
17: [1E00] H unknown
18: [1B01] ReprogControlsV2
19: [1890] HI unknown
20: [18E5] HI unknown
21: [18A0] HI unknown
22: [1830] HI unknown
(O = obsolete feature; H = SW hidden feature;
I = reserved for internal use)
(solaar)
Unifying Receiver
Device path : /dev/hidraw2
USB id : 046d:c52b
Serial : 28E69A3E
Firmware : 24.00.B0018
Bootloader : 00.06
Has 1 paired device(s) out of a maximum of 6.
Notifications: (none)
Device activity counters: 1=221
1: Wireless Rechargeable Touchpad T650
Codename : T650
Kind : touchpad
Wireless PID : 4101
Protocol : HID++ 2.0
Polling rate : 8 ms (125Hz)
Serial number: 22205A4D
Firmware: RQM 41.01.B0037
Bootloader: BL 03.00
Hardware: 72
Other:
The power switch is located on the base.
Supports 23 HID++ 2.0 features:
0: ROOT {0000}
1: FEATURE SET {0001}
2: FEATURE INFO {0002}
3: DEVICE FW VERSION {0003}
4: DEVICE NAME {0005}
5: BATTERY STATUS {1000}
6: WIRELESS DEVICE STATUS {1D4B}
7: unknown:1DF3 {1DF3} internal, hidden
8: REPROG CONTROLS {1B00}
9: unknown:1F03 {1F03} internal, hidden
10: VERTICAL SCROLLING {2100}
11: HI RES SCROLLING {2120}
12: MOUSE POINTER {2200}
13: DFUCONTROL {00C0}
14: unknown:1E80 {1E80} internal, hidden
15: TOUCHPAD RAW XY {6100}
16: unknown:1860 {1860} internal, hidden
17: unknown:1E00 {1E00} hidden
18: REPROG CONTROLS V2 {1B01}
19: unknown:1890 {1890} internal, hidden
20: unknown:18E5 {18E5} internal, hidden
21: unknown:18A0 {18A0} internal, hidden
22: unknown:1830 {1830} internal, hidden
Has 2 reprogrammable keys:
0: LEFT CLICK => LeftClick mse, reprogrammable
1: RIGHT CLICK => RightClick mse, reprogrammable
Battery: 50%, discharging.

112
docs/features.md Normal file
View File

@@ -0,0 +1,112 @@
---
title: List of HID++ 2.0 features
layout: page
---
# Feature status
See functions in hidpp20.py and settings_templates.py
Feature | ID | Status | Notes
---------------------------------------|----------|:------------------:|------
`ROOT` | `0x0000` | :heavy_check_mark: | System
`FEATURE_SET` | `0x0001` | :heavy_check_mark: | System
`FEATURE_INFO` | `0x0002` | :heavy_check_mark: | System
`DEVICE_FW_VERSION` | `0x0003` | :heavy_check_mark: | `get_firmware()`
`DEVICE_UNIT_ID` | `0x0004` | :x: |
`DEVICE_NAME` | `0x0005` | :heavy_check_mark: | `get_kind()`, `get_name()`
`DEVICE_GROUPS` | `0x0006` | :x: |
`DEVICE_FRIENDLY_NAME` | `0x0007` | :x: |
`KEEP_ALIVE` | `0x0008` | :x: |
`RESET` | `0x0020` | :x: | aka "Config Change"
`CRYPTO_ID` | `0x0021` | :x: |
`TARGET_SOFTWARE` | `0x0030` | :x: |
`WIRELESS_SIGNAL_STRENGTH` | `0x0080` | :x: |
`DFUCONTROL_LEGACY` | `0x00C0` | :x: |
`DFUCONTROL_UNSIGNED` | `0x00C1` | :x: |
`DFUCONTROL_SIGNED` | `0x00C2` | :x: |
`DFU` | `0x00D0` | :x: |
`BATTERY_STATUS` | `0x1000` | :heavy_check_mark: | `get_battery()`
`BATTERY_VOLTAGE` | `0x1001` | :x: |
`CHARGING_CONTROL` | `0x1010` | :x: |
`LED_CONTROL` | `0x1300` | :x: |
`GENERIC_TEST` | `0x1800` | :x: |
`DEVICE_RESET` | `0x1802` | :x: |
`OOBSTATE` | `0x1805` | :x: |
`CONFIG_DEVICE_PROPS` | `0x1806` | :x: |
`CHANGE_HOST` | `0x1814` | :x: |
`HOSTS_INFO` | `0x1815` | :x: |
`BACKLIGHT` | `0x1981` | :x: |
`BACKLIGHT2` | `0x1982` | :x: |
`BACKLIGHT3` | `0x1983` | :x: |
`PRESENTER_CONTROL` | `0x1A00` | :x: |
`SENSOR_3D` | `0x1A01` | :x: |
`REPROG_CONTROLS` | `0x1B00` | :heavy_plus_sign: | Partially, only listing. `get_keys()`
`REPROG_CONTROLS_V2` | `0x1B01` | :x: |
`REPROG_CONTROLS_V2_2` | `0x1B02` | :x: |
`REPROG_CONTROLS_V3` | `0x1B03` | :x: |
`REPROG_CONTROLS_V4` | `0x1B04` | :heavy_plus_sign: | Partially, only listing. `get_keys()`
`REPORT_HID_USAGE` | `0x1BC0` | :x: |
`PERSISTENT_REMAPPABLE_ACTION` | `0x1C00` | :x: |
`WIRELESS_DEVICE_STATUS` | `0x1D4B` | :x: |
`REMAINING_PAIRING` | `0x1DF0` | :x: |
`FIRMWARE_PROPERTIES` | `0x1F1F` | :x: |
`ADC_MEASUREMENT` | `0x1F20` | :x: |
`LEFT_RIGHT_SWAP` | `0x2001` | :x: |
`SWAP_BUTTON_CANCEL` | `0x2005` | :x: |
`POINTER_AXIS_ORIENTATION` | `0x2006` | :x: |
`VERTICAL_SCROLLING` | `0x2100` | :heavy_check_mark: | `get_vertical_scrolling_info()`
`SMART_SHIFT` | `0x2110` | :heavy_check_mark: | `_feature_smart_shift()`
`HI_RES_SCROLLING` | `0x2120` | :heavy_check_mark: | `get_hi_res_scrolling_info()`, `_feature_hi_res_scroll()`
`HIRES_WHEEL` | `0x2121` | :heavy_check_mark: | `get_hires_wheel()`, `_feature_hires_smooth_invert()`, `_feature_hires_smooth_resolution()`
`LOWRES_WHEEL` | `0x2130` | :heavy_check_mark: | `get_lowres_wheel_status()`, `_feature_lowres_smooth_scroll()`
`THUMB_WHEEL` | `0x2150` | :x: |
`MOUSE_POINTER` | `0x2200` | :heavy_check_mark: | `get_mouse_pointer_info()`
`ADJUSTABLE_DPI` | `0x2201` | :heavy_check_mark: | `_feature_adjustable_dpi()`
`POINTER_SPEED` | `0x2205` | :heavy_check_mark: | `get_pointer_speed_info()`, `_feature_pointer_speed()`
`ANGLE_SNAPPING` | `0x2230` | :x: |
`SURFACE_TUNING` | `0x2240` | :x: |
`HYBRID_TRACKING` | `0x2400` | :x: |
`FN_INVERSION` | `0x40A0` | :heavy_check_mark: | `_feature_fn_swap()`
`NEW_FN_INVERSION` | `0x40A2` | :heavy_check_mark: | `_feature_new_fn_swap()`
`K375S_FN_INVERSION` | `0x40A3` | :heavy_check_mark: | `_feature_k375s_fn_swap()`
`ENCRYPTION` | `0x4100` | :x: |
`LOCK_KEY_STATE` | `0x4220` | :x: |
`SOLAR_DASHBOARD` | `0x4301` | :x: |
`KEYBOARD_LAYOUT` | `0x4520` | :x: |
`KEYBOARD_DISABLE` | `0x4521` | :x: |
`KEYBOARD_DISABLE_BY_USAGE` | `0x4522` | :x: |
`DUALPLATFORM` | `0x4530` | :x: |
`MULTIPLATFORM` | `0x4531` | :x: |
`KEYBOARD_LAYOUT_2` | `0x4540` | :x: |
`CROWN` | `0x4600` | :x: |
`TOUCHPAD_FW_ITEMS` | `0x6010` | :x: |
`TOUCHPAD_SW_ITEMS` | `0x6011` | :x: |
`TOUCHPAD_WIN8_FW_ITEMS` | `0x6012` | :x: |
`TAP_ENABLE` | `0x6020` | :x: |
`TAP_ENABLE_EXTENDED` | `0x6021` | :x: |
`CURSOR_BALLISTIC` | `0x6030` | :x: |
`TOUCHPAD_RESOLUTION` | `0x6040` | :x: |
`TOUCHPAD_RAW_XY` | `0x6100` | :x: |
`TOUCHMOUSE_RAW_POINTS` | `0x6110` | :x: |
`TOUCHMOUSE_6120` | `0x6120` | :x: |
`GESTURE` | `0x6500` | :x: |
`GESTURE_2` | `0x6501` | :x: |
`GKEY` | `0x8010` | :x: |
`MKEYS` | `0x8020` | :x: |
`MR` | `0x8030` | :x: |
`BRIGHTNESS_CONTROL` | `0x8040` | :x: |
`REPORT_RATE` | `0x8060` | :x: |
`COLOR_LED_EFFECTS` | `0x8070` | :x: |
`RGB_EFFECTS` | `0X8071` | :x: |
`PER_KEY_LIGHTING` | `0x8080` | :x: |
`PER_KEY_LIGHTING_V2` | `0x8081` | :x: |
`MODE_STATUS` | `0x8090` | :x: |
`ONBOARD_PROFILES` | `0x8100` | :x: |
`MOUSE_BUTTON_SPY` | `0x8110` | :x: |
`LATENCY_MONITORING` | `0x8111` | :x: |
`GAMING_ATTACHMENTS` | `0x8120` | :x: |
`FORCE_FEEDBACK` | `0x8123` | :x: |
`SIDETONE` | `0x8300` | :x: |
`EQUALIZER` | `0x8310` | :x: |
`HEADSET_OUT` | `0x8320` | :x: |

View File

@@ -5,7 +5,7 @@ First, make sure you have installed the `gettext` package.
Here are the steps to add/update a translation (you should run all scripts from
the source root):
1. Get an up-to-date copy of the source files. Preferrably, make a clone on
1. Get an up-to-date copy of the source files. Preferably, make a clone on
GitHub and clone it locally on your machine; this way you can later make a
pull request to the main project.
@@ -22,9 +22,37 @@ the source root):
4. Run `./tools/po-compile.sh`. It will bring up-to-date all the compiled
language files, necessary at runtime.
5. Start Solaar (`./bin/solaar`). By default it will pick up the system languge
5. Start Solaar (`./bin/solaar`). By default it will pick up the system language
from your environment; to start it in another language, run
`LANGUAGE=<language> ./bin/solaar`.
You can edit the translation iteratively, just repeat from step 3.
If the upstream changes, do a `git pull` and then repeat from step 2.
Before opening a pull request, please run `./tools/po-update.sh` again. It will
format and sort the translation file, and ensure a minimal diff when updating
a translation.
# Supported languages
Currently Solaar has been translated in the following languages:
- Français: [Papoteur][papoteur], [David Geiger][david-geiger],
[Damien Lallement][damsweb]
- Italiano: [Michele Olivo][micheleolivo]
- Polski: [Adrian Piotrowicz][nexces]
- Portuguese-BR: [Drovetto][drovetto], [Josenivaldo Benito Jr.][jrbenito]
- Română: Daniel Pavel
- Russian: [Dimitriy Ryazantcev][DJm00n]
- Svensk: [Daniel Zippert][zipperten], Emelie Snecker
[papoteur]: http://github.com/papoteur
[david-geiger]: http://github.com/david-geiger
[damsweb]: http://github.com/damsweb
[DJm00n]: https://github.com/DJm00n
[nexces]: http://github.com/nexces
[zipperten]: http://github.com/zipperten
[micheleolivo]: http://github.com/micheleolivo
[drovetto]: https://github.com/drovetto
[jrbenito]: https://github.com/jrbenito/

136
docs/index.md Normal file
View File

@@ -0,0 +1,136 @@
---
title: Solaar
layout: default
---
**Solaar** is a Linux manager for Logitech's devices that connect via a USB
[Unifying][unifying], Lightspeed, or Nano receiver.
Solaar does not work with Logitech peripherals that
use Bluetooth or peripherals from other companies.
Solaar can be used as a GUI application or via its command-line interface.
Both interfaces are able to list the devices paired to a receiver and
show information about each device, including battery status for devices that support this feature.
Solaar's GUI normally uses an icon in the system tray and starts with its main window hidden.
If Solaar is invoked with the `--window=show` option (the default) Solaar starts with its main window visible.
If Solaar is invoked with the `--window=hide` option Solaar starts with its main window hidden.
If solaar is invoked with the `--window=only` option Solaar does not set up an icon in the
system tray and also starts with its main window showing.
For more information on Solaar's command-line interface use the help option,
as in `solaar --help`.
Solaar is able to pair and unpair devices with
receivers as supported by the receiver. Solaar can also control
some of the changeable features of devices, such as smooth scrolling or
function key behavior.
For more information on how to use Solaar see
[docs/usage.md](https://pwr-solaar.github.io/Solaar/usage).
For more information on the capabilities of Solaar see
[docs/capabilities.md](https://pwr-solaar.github.io/Solaar/capabilities).
Solaar does not process normal input from the devices. Solaar is thus unable
to fix problems that arise from incorrect handling of mouse movements or keycodes
by Linux drivers or other software.
Solaar has progressed past version 1.0. Problems with earlier versions should
not be reported as bugs. Instead upgrade to a recent version or manually install
the current version from [GitHub](https://github.com/pwr-Solaar/Solaar).
Some of the capabilities of Solaar have been developed by observing the behavior of
Logitech receivers and devices and generalizing from these observations.
If your Logitech receiver or device behaves in a strange way this may be caused by
an incorrect behavior generalization.
Please report such experiences by creating an issue in
[the Solaar repository](https://github.com/pwr-Solaar/Solaar/issues).
[unifying]: https://en.wikipedia.org/wiki/Logitech_Unifying_receiver
## Supported Devices
**Solaar** will detect all devices paired with your Unifying, Lightspeed, or Nano
receiver, and at the very least display some basic information about them.
Solaar can pair and unpair a Logitech device showing the Unifying logo (Solaar's version of the [logo][logo])
with any Unifying receiver and can pair and unpair devices with Lightspeed receivers.
Solaar can pair some Logitech
devices with Logitech Nano receivers but not all Logitech devices can be
paired with Nano receivers. Logitech devices without a Unifying logo
generally cannot be paired with Unifying receivers.
For some devices, extra settings (usually not available through the standard
Linux system configuration) are supported. For a list of supported devices
and their features, see [docs/devices.md](https://pwr-solaar.github.io/Solaar/devices).
[logo]: https://pwr-solaar.github.io/Solaar/assets/solaar.svg
## Pre-built packages
Pre-built packages are available for a few Linux distros.
Solaar has progressed beyond version 1.0 but some distros or repositories
version 0.9.2, which is very old. Installing the current version will
provide significant improvements. If a recent version of Solaar is not
available from the standard repositories for your distribution you can try
one of these packages.
* Arch solaar package in the [community repository][arch]
* Debian 7 (Wheezy) or higher: packages in this [repository](https://pwr-solaar.github.io/Solaar/debian)
* Ubuntu/Kubuntu 16.04+: use the solaar package from [universe repository][universe repository]
* Ubuntu/Kubuntu stable packages: use the [Solaar stable ppa][ppa2], courtesy of [gogo][ppa4]
* Ubuntu/Kubuntu git build packages: use the [Solaar git ppa][ppa1], courtesy of [gogo][ppa4]
* a [Fedora package][fedora], courtesy of Eric Smith
* a [Gentoo package][gentoo], courtesy of Carlos Silva and Tim Harder
* a [Mageia package][mageia], courtesy of David Geiger
* an [OpenSUSE rpm][opensuse], courtesy of Mathias Homann
Solaar uses a standard system tray implementation; solaar-gnome3 is no longer required for gnome or unity integration.
[ppa4]: https://launchpad.net/~trebelnik-stefina
[ppa2]: https://launchpad.net/~solaar-unifying/+archive/ubuntu/stable
[ppa1]: https://launchpad.net/~solaar-unifying/+archive/ubuntu/ppa
[ppa]: http://launchpad.net/~daniel.pavel/+archive/solaar
[arch]: https://www.archlinux.org/packages/community/any/solaar/
[fedora]: https://apps.fedoraproject.org/packages/solaar
[gentoo]: https://packages.gentoo.org/packages/app-misc/solaar
[mageia]: http://mageia.madb.org/package/show/release/cauldron/application/0/name/solaar
[opensuse]: http://software.opensuse.org/package/Solaar
[universe repository]: http://packages.ubuntu.com/search?keywords=solaar&searchon=names&suite=all&section=all
## Manual installation
See [docs/installation.md](https://pwr-solaar.github.io/Solaar/installation) for the step-by-step procedure for manual installation.
## Known Issues
- KDE/Kubuntu: if some icons appear broken in the application, make sure you've
properly configured the Gtk theme and icon theme in KDE's control panel.
- Running the command-line application while the GUI
application is also running *may* occasionally cause either of them to become
confused about the state of the devices.
## License
This software is distributed under the terms of the
[GNU Public License, v2](COPYING).
## Thanks
This project began as a third-hand clone of [Noah K. Tilton](https://github.com/noah)'s
logitech-solar-k750 project on GitHub (no longer available). It was developed
further thanks to the diggings in Logitech's HID++ protocol done by many other
people:
- [Julien Danjou](http://julien.danjou.info/blog/2012/logitech-k750-linux-support),
who also provided some internal
[Logitech documentation](http://julien.danjou.info/blog/2012/logitech-unifying-upower)
- [Lars-Dominik Braun](http://6xq.net/git/lars/lshidpp.git)
- [Alexander Hofbauer](http://derhofbauer.at/blog/blog/2012/08/28/logitech-performance-mx)
- [Clach04](http://bitbucket.org/clach04/logitech-unifying-receiver-tools)
- [Peter Wu](https://lekensteyn.nl/logitech-unifying.html)
- [Nestor Lopez Casado](http://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28)
provided some more Logitech specifications for the HID++ protocol
Also thanks to Douglas Wagner, Julien Gascard and Peter Wu for helping with
application testing and supporting new devices.

View File

@@ -1,53 +1,106 @@
---
title: Manual Installation
layout: page
---
# Manual installation
### Requirements
You should have a reasonably new kernel (3.2+), with the `logitech-djreceiver`
driver enabled and loaded; also, the `udev` package must be installed and the
daemon running. If you have a modern Linux distribution (2011+), you're most
likely good to go.
## Downloading
The command-line application (`bin/solaar-cli`) requires Python 2.7.3 or 3.2+
(either version should work), and the `python-pyudev`/`python3-pyudev` package.
Clone solaar from GitHub via `git clone https://github.com/pwr-Solaar/Solaar.git`
The GUI application (`bin/solaar`) also requires Gtk3, and its GObject
Introspection bindings. The Debian/Ubuntu package names are
`python-gi`/`python3-gi` and `gir1.2-gtk-3.0`; if you're using another
## Requirements for running Solaar
Installing Solaar from a repository should have set up all these requirements
so in this situation you should be able to skip this section.
Solaar needs a reasonably new kernel (5.0+ should work fine), with kernel modules `hid-logitech-dj`
and `hid-logitech-hidpp` loaded. Also, the `udev` package must be installed
and its daemon running. If you have a recent Linux distribution, you are
most likely good to go.
Solaar requires Python 3.2+
and the `python3-pyudev` package.
To run the GUI, solaar also requires Gtk3, and its GObject
introspection bindings.
The Debian/Ubuntu packages that need to be installed are
`python3-gi` and `gir1.2-gtk-3.0`;
in Fedora you need `gtk3` and `python3-gobject`;
if you're using another
distribution the required packages are most likely named something similar.
If the desktop notifications bindings are also installed (`gir1.2-notify-0.7`),
If the desktop notifications bindings are also installed
(`gir1.2-notify-0.7` for Debian/Ubuntu),
you will also get desktop notifications when devices come online/go offline.
For gnome-shell/Unity support, you also need to have `gir1.2-appindicator3-0.1`
installed.
For GNOME Shell/Budgie Desktop/KDE/XFCE support, you also need to have
`gir1.2-ayatanaappindicator3-0.1` installed in Debian/Ubuntu. Although it is
recommended to install and use `gir1.2-ayatanaappindicator3-0.1` if it is
available, you can also use `gir1.2-appindicator3-0.1` if necessary (e.g.,
for Unity in Ubuntu).
### Installation
### Installing Solaar's udev Rule
Normally USB devices are not accessible for r/w by regular users, so you will
need to do a one-time udev rule installation to allow access to the Logitech
Unifying Receiver.
Solaar needs to write to the receiver's HID device.
To be able to do this without running as root requires udev rule
that gives seated users write access to the HID devices for Logitech receivers.
You can run the `rules.d/install.sh` script from Solaar to do this installation
automatically (make sure to run it as your regular desktop user, it will switch
to root when necessary), or you can do all the required steps by hand, as the
root user:
You can install this rule by copying, as root,
`rules.d/42-logitech-unify-permissions.rules` from Solaar to
`/etc/udev/rules.d`.
The udev daemon will automatically pick up this file using inotify.
1. Copy `rules.d/99-logitech-unifying-receiver.rules` from Solaar to
`/etc/udev/rules.d/`. The `udev` daemon will automatically pick up this file
using inotify.
For this rule to set up the correct permissions for your receiver
you will then need to either physically remove the receiver and
re-insert it or reboot your computer.
By default, the rule allows all members of the `plugdev` group to have
read/write access to the Unifying Receiver device. (standard Debian/Ubuntu
group for pluggable devices). It may need changes, specific to your
particular system's configuration. If in doubt, replacing `GROUP="plugdev"`
with `GROUP="<your username>"` should just work.
2. Physically remove the Unifying Receiver and re-insert it.
## Running from the Download Directories
This is necessary because if the receiver is already plugged-in, it already
has a `/dev/hidrawX` device node, but with the old (`root:root`) permissions.
Plugging it again will re-create the device node with the right permissions.
If Solaar's udev rule is installed,
you can just go to the solaar directory and run `bin/solaar` for the GUI
or `bin/solaar <command> <arguments>` for the CLI.
3. Make sure your desktop users are part of the `plugdev` group, by running
`gpasswd <desktop username> plugdev`. If these users were not assigned to the
group before, they must re-login for the changes to take effect.
Otherwise you will need to run Solaar as root via
`sudo bin/solaar` for the GUI
or `sudo bin/solaar <command> <arguments>` for the CLI.
## Installing Solaar
Python programs are usually installed using [pip][pip].
The pip instructions for solaar are in `setup.py`, the standard place to put such instructions.
To install solaar for yourself only run `pip install --user .` from the solaar directory.
This tells pip to install into your `.local` directory, but does not install Solaar's udev rule.
(See above for installing the udev rule.)
You can then run solaar as `sudo ~/.local/bin/solaar` (or just `~/.local/bin/solaar`
if the udev rule has been installed).
Installing python programs to system directories using pip is generally frowned on both
because this runs arbitrary code as root and because this can override existing python libraries
that other users or even the system depend on. If you want to install solaar to /usr/local run
`sudo bash -c 'umask 022 ; pip install .'` in the solaar directory.
(The umask is needed so that the created files and directories can be read and executed by everyone.)
This will also install the udev rule and the Solaar autostart desktop file.
Then solaar can be run as /usr/local/bin/solaar.
[pip]: https://en.wikipedia.org/wiki/Pip_(package_manager)
## Running Solaar at Startup
Solaar is run automatically at user login via the desktop file
`/etc/xdg/autostart/solaar.desktop`.
If you install Solaar yourself you may need to create or modify this file.
## Using PyPI
As an alternative to downloading and installing you can install the most recent release
(but not the current github version) of Solaar from PyPI.
Just run `pip install --user solaar` or `sudo pip install solaar`.
The `--user` flag will not install the Solaar udev rule or the Solaar autostart file.

88
docs/usage.md Normal file
View File

@@ -0,0 +1,88 @@
---
title: Solaar Usage
layout: page
---
# Solaar usage
Under normal usage Solaar creates an icon in your system tray. This icon is
usually a battery icon showing the approximate battery level for your device
with the lowest known battery level. If there is no battery information to
show the icon is one of the Solaar icons.
Solaar also has a main window. To show the main window if it is not visible
click on the icon in the system tray to bring up the Solaar menu and then
click on a receiver or device in the menu.
Here is an example of the Solaar icon (in the system tray) and the Solaar menu:
![Solaar-menu](Solaar-menu.png)
Clicking on "Quit" in the Solaar menu terminates Solaar.
Clicking on "About Solaar" pops up a window with information on Solaar.
## Solaar options
There are several options that affect how Solaar behaves:
* `--help` shows a help message and then quits
* `---window=show` starts Solaar with the main window showing
* `---window=hide` starts Solaar with the main window not showing
* `---window=only` starts Solaar with no system tray icon and the main window showing
* `--battery-icons=symbolic` uses symbolic icons for battery levels
## Solaar main window
The Solaar main window shows your Logitech receivers and devices that Solaar
knows about and can be used to pair new devices, unpair paired devices, and
view and change some settings of the selected receiver or device.
To select a receiver or device click on it in the left side of the window.
Closing the main window does not terminate Solaar (unless Solaar is not using the system tray).
Clicking on "Quit Solaar" terminates Solaar.
Clicking on "About Solaar" pops up a window with information on Solaar.
Clicking on the lightbulb (or similar icon) displays detailed information
about the selected receiver or device (mostly only useful for debugging).
When a receiver is selected in the main window you can pair a new device by
clicking on the "Pair new device" button.
Then turn on the device and it should pair with the receiver if that is possible.
![Solaar-main-window-receiver](Solaar-main-window-receiver.png)
Some receivers can only pair with certain kinds of devices. Some receivers
pair a new device by replacing an existing paired device of the same kind.
Some receivers can only pair a limited number of times.
A receiver with the Unifying logo should be able to pair with any device
with the Unifying logo. If there are no open pairing slots, however, you may
not be able to pair a new device. In this case to pair a new device you
first need to unpair a device.
When a device is selected you can unpair the device if your receiver supports
unpairing. To unpair the device, just click on the "Unpair" button and
confirm in the window that pops up.
When a device is selected you can see the approximate battery level of the
device, if that is reported by the device, and the status of the link
between the device and its receiver.
You can also see some settings of the device and change
some of these. Changing settings is performed by clicking on buttons or
moving sliders.
![Solaar-main-window-keyboard](Solaar-main-window-keyboard.png)
![Solaar-main-window-mouse](Solaar-main-window-mouse.png)
If the selected device is off or otherwise disconnected its settings cannot be changed
but it still can be unpaired if its receiver allows unpairing.
![Solaar-main-window-offline](Solaar-main-window-offline.png)
## Solaar command line interface
Solaar also has a command line interface that can do most of what can be
done using the Solaar main window. For more information on the Solaar
command line interface, run `solaar --help` to see the Solaar commands and
then `solaar <command> --help` to see the arguments to any of the commands.

View File

@@ -10,3 +10,5 @@ Nano receiver, Advanced/Unifying ready:
Nano receiver:
046d:c51a interface: 1 driver: hid-generic
046d:c526 interface: 1 driver: hid-generic
046d:c52e interface: 1 driver: hid-generic
046d:c531 interface: 1 driver: hid-generic

View File

@@ -9,8 +9,8 @@
# The latest version can be obtained from
# http://www.linux-usb.org/usb.ids
#
# Version: 2013.05.24
# Date: 2013-05-24 20:34:03
# Version: 2019.11.05
# Date: 2019-11-05 20:34:06
#
# Vendors, devices and interfaces. Please keep sorted.
@@ -24,6 +24,7 @@
0082 Acer Aspire 5672 Webcam
0200 WingMan Extreme Joystick
0203 M2452 Keyboard
0242 Chillstream for Xbox 360
0301 M4848 Mouse
0401 HP PageScan
0402 NEC PageScan
@@ -40,16 +41,25 @@
080f Webcam C120
0810 QuickCam Pro
0819 Webcam C210
081a Webcam C260
081b Webcam C310
081d HD Webcam C510
0820 QuickCam VC
0821 HD Webcam C910
0823 HD Webcam B910
0825 Webcam C270
0826 HD Webcam C525
0828 HD Webcam B990
082b Webcam C170
082c HD Webcam C615
082d HD Pro Webcam C920
0830 QuickClip
0836 B525 HD Webcam
0837 BCC950 ConferenceCam
0840 QuickCam Express
0843 Webcam C930e
0850 QuickCam Web
085c C922 Pro Stream Webcam
0870 QuickCam Express
0890 QuickCam Traveler
0892 OrbiCam
@@ -96,7 +106,7 @@
08d7 QuickCam Communicate STX
08d8 QuickCam for Notebook Deluxe
08d9 QuickCam IM/Connect
08da QuickCam Messanger
08da QuickCam Messenger
08dd QuickCam for Notebooks
08e0 QuickCam Express
08e1 Labtec Webcam
@@ -147,14 +157,25 @@
0a0b ClearChat Pro USB
0a0c Clear Chat Comfort USB Headset
0a13 Z-5 Speakers
0a14 USB Headset
0a15 G35 Headset
0a17 G330 Headset
0a1f G930
0a29 H600 [Wireless Headset]
0a37 USB Headset H540
0a38 Headset H340
0a44 Headset H390
0a45 960 Headset
0a4d G430 Surround Sound Gaming Headset
0a5b G933 Wireless Headset Dongle
0a66 [G533 Wireless Headset Dongle]
0b02 C-UV35 [Bluetooth Mini-Receiver] (HID proxy mode)
8801 Video Camera
b014 Bluetooth Mouse M336/M337/M535
b305 BT Mini-Receiver
bfe4 Premium Optical Wheel Mouse
c000 N43 [Pilot Mouse]
c001 N48/M-BB48 [FirstMouse Plus]
c001 N48/M-BB48/M-UK96A [FirstMouse Plus]
c002 M-BA47 [MouseMan Plus]
c003 MouseMan
c004 WingMan Gaming Mouse
@@ -212,20 +233,32 @@
c061 RX1500 Laser Mouse
c062 M-UAS144 [LS1 Laser Mouse]
c063 DELL Laser Mouse
c064 M110 corded optical mouse (M-B0001)
c066 G9x Laser Mouse
c068 G500 Laser Mouse
c069 M500 Laser Mouse
c069 M-U0007 [Corded Mouse M500]
c06a USB Optical Mouse
c06b G700 Wireless Gaming Mouse
c06c Optical Mouse
c077 M105 Optical Mouse
c07c M-R0017 [G700s Rechargeable Gaming Mouse]
c07d G502 Mouse
c07e G402 Gaming Mouse
c080 G303 Gaming Mouse
c083 G403 Prodigy Gaming Mouse
c084 G203 Gaming Mouse
c101 UltraX Media Remote
c110 Harmony 785/885 Remote
c110 Harmony 785/880/885 Remote
c111 Harmony 525 Remote
c112 Harmony 890 Remote
c11f Harmony 900/1100 Remote
c121 Harmony One Remote
c122 Harmony 700 Remote
c124 Harmony 300 Remote
c122 Harmony 650/700 Remote
c124 Harmony 300/350 Remote
c125 Harmony 200 Remote
c126 Harmony Link
c129 Harmony Hub
c12b Harmony Touch/Ultimate Remote
c201 WingMan Extreme Joystick with Throttle
c202 WingMan Formula
c207 WingMan Extreme Digital 3D
@@ -236,14 +269,14 @@
c20c WingMan Precision
c20d WingMan Attack 2
c20e WingMan Formula GP
c211 iTouch Cordless Reciever
c211 iTouch Cordless Receiver
c212 WingMan Extreme Digital 3D
c213 J-UH16 (Freedom 2.4 Cordless Joystick)
c214 ATK3 (Attack III Joystick)
c215 Extreme 3D Pro
c216 Dual Action Gamepad
c218 Logitech RumblePad 2 USB
c219 Cordless RumblePad 2
c216 F310 Gamepad [DirectInput Mode]
c218 F510 Gamepad [DirectInput Mode]
c219 F710 Gamepad [DirectInput Mode]
c21a Precision Gamepad
c21c G13 Advanced Gameboard
c21d F310 Gamepad [XInput Mode]
@@ -255,12 +288,20 @@
c225 G11/G15 Keyboard / G keys
c226 G15 Refresh Keyboard
c227 G15 Refresh Keyboard
c228 G19 Gaming Keyboard
c229 G19 Gaming Keyboard Macro Interface
c22a Gaming Keyboard G110
c22b Gaming Keyboard G110 G-keys
c22d G510 Gaming Keyboard
c22e G510 Gaming Keyboard onboard audio
c231 G13 Virtual Mouse
c245 G400 Optical Mouse
c246 Gaming Mouse G300
c248 G105 Gaming Keyboard
c24a G600 Gaming Mouse
c24c G400s Optical Mouse
c24d G710 Gaming Keyboard
c24e G500s Laser Gaming Mouse
c281 WingMan Force
c283 WingMan Force 3D
c285 WingMan Strike Force 3D
@@ -276,13 +317,14 @@
c29c Speed Force Wireless Wheel for Wii
c2a0 Wingman Force Feedback Mouse
c2a1 WingMan Force Feedback Mouse
c2ab G13 Joystick
c301 iTouch Keyboard
c302 iTouch Pro Keyboard
c303 iTouch Keyboard
c305 Internet Keyboard
c307 Internet Keyboard
c308 Internet Navigator Keyboard
c309 Internet Keyboard
c309 Y-BF37 [Internet Navigator Keyboard]
c30a iTouch Composite
c30b NetPlay Keyboard
c30c Internet Keys (X)
@@ -298,8 +340,14 @@
c318 Illuminated Keyboard
c31a Comfort Wave 450
c31b Compact Keyboard K300
c31c Keyboard K120 for Business
c31c Keyboard K120
c31d Media Keyboard K200
c31f Comfort Keyboard K290
c326 Washable Keyboard K310
c328 Corded Keyboard K280e
c332 G502 Proteus Spectrum Optical Mouse
c335 G910 Orion Spectrum Mechanical Keyboard
c33a G413 Gaming Keyboard
c401 TrackMan Marble Wheel
c402 Marble Mouse (2-button)
c403 Turbo TrackMan Marble FX
@@ -332,12 +380,24 @@
c526 Nano Receiver
c529 Logitech Keyboard + Mice
c52b Unifying Receiver
c52d R700 Remote Presenter receiver
c52e MK260 Wireless Combo Receiver
c52f Unifying Receiver
c531 C-U0007 [Unifying Receiver]
c532 Unifying Receiver
c534 Unifying Receiver
c603 3Dconnexion Spacemouse Plus XT
c605 3Dconnexion CADman
c606 3Dconnexion Spacemouse Classic
c621 3Dconnexion Spaceball 5000
c623 3Dconnexion Space Traveller 3D Mouse
c625 3Dconnexion Space Pilot 3D Mouse
c626 3Dconnexion Space Navigator 3D Mouse
c627 3Dconnexion Space Explorer 3D Mouse
c628 3Dconnexion Space Navigator for Notebooks
c629 3Dconnexion SpacePilot Pro 3D Mouse
c62b 3Dconnexion Space Mouse Pro
c640 NuLOOQ navigator
c702 Cordless Presenter
c703 Elite Keyboard Y-RP20 + Mouse MX900 (Bluetooth)
c704 diNovo Wireless Desktop
@@ -360,5 +420,10 @@
c720 Bluetooth wireless hub
ca03 MOMO Racing
ca04 Formula Vibration Feedback Wheel
ca84 Cordless Controller for Xbox
ca88 Thunderpad for Xbox
ca8a Precision Vibration Feedback Wheel for Xbox
caa3 DriveFX Racing Wheel
cab1 Cordless Keyboard for Wii HID Receiver
d001 QuickCam Pro
f301 Controller

View File

@@ -1,12 +0,0 @@
title: Solaar
tagline: Linux devices manager for the Logitech Unifying Receiver.
owner: pwr
owner_url: https://github.com/pwr
repository: https://github.com/pwr/Solaar
version: 0.9.1
tar_download: https://github.com/pwr/Solaar/archive/0.9.1.tar.gz
ga_id: UA-36908718-1
pygments: true
safe: true
lsi: false

View File

@@ -1,14 +0,0 @@
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" style="width: 48px; height:48px; margin-bottom: -10px;">
<defs>
<linearGradient id="gradient_blue">
<stop style="stop-color:#009099;stop-opacity:1" offset="0" />
<stop style="stop-color:#00a899;stop-opacity:0.9" offset="1" />
</linearGradient>
<linearGradient x1="5" y1="50" x2="95" y2="50" id="gradient_rect" xlink:href="#gradient_blue" gradientUnits="userSpaceOnUse" />
<linearGradient x1="37" y1="50" x2="63" y2="50" id="gradient_dot" xlink:href="#gradient_blue" gradientUnits="userSpaceOnUse" />
</defs>
<g transform="scale(0.48)">
<path d="M 21.5,5.5 C 12.636,5.5 5.5,12.636 5.5,21.5 L 5.5,78.5 C 5.5,87.364 12.636,94.5 21.5,94.5 L 78.5,94.5 C 87.364,94.5 94.5,87.364 94.5,78.5 L 94.5,21.5 C 94.5,12.636 87.364,5.5 78.5,5.5 L 21.5,5.5 z M 37.6875,16.6875 46.71875,32.3125 C 47.784179,32.115965 48.877705,32 50,32 51.122295,32 52.215821,32.115965 53.28125,32.3125 L 62.3125,16.6875 72.6875,22.6875 63.65625,38.3125 C 65.078123,39.972287 66.191785,41.898777 66.9375,44 L 85,44 85,56 66.9375,56 C 66.191785,58.101223 65.078123,60.027713 63.65625,61.6875 L 72.6875,77.3125 62.3125,83.3125 53.28125,67.6875 C 52.215821,67.884035 51.122295,68 50,68 48.877705,68 47.784179,67.884035 46.71875,67.6875 L 37.6875,83.3125 27.3125,77.3125 36.34375,61.6875 C 34.921877,60.027713 33.808215,58.101223 33.0625,56 L 15,56 15,44 33.0625,44 C 33.808215,41.898777 34.921877,39.972287 36.34375,38.3125 L 27.3125,22.6875 37.6875,16.6875 z" style="fill:url(#gradient_rect);fill-opacity:1;fill-rule:nonzero;stroke:#16161d;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" />
<path d="M 62,50 A 12,12 0 1 1 38,50 12,12 0 1 1 62,50 z" style="fill:url(#gradient_dot);fill-opacity:1;fill-rule:nonzero;stroke:#16161d;stroke-opacity:1" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1,56 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<meta name="description" content="{{ site.tagline }}" />
<link rel="stylesheet" type="text/css" media="screen" href="style/stylesheet.css">
<link rel="icon" type="image/png" href="images/favicon.png" />
<title>{{ page.title }}</title>
</head>
<body>
<div id="header_wrap" class="outer">
<header class="inner">
<a id="forkme_banner" href="{{ site.repository }}">View on GitHub</a>
<h1 id="project_title">{% include solaar.svg %} {{ site.title }}</h1>
<h2 id="project_tagline">{{ site.tagline }}</h2>
<section id="downloads">
<a class="tar_download_link" href="{{ site.tar_download }}">Solaar {{ site.version }}</a>
<p style="color: #fff">
Latest version:<br/>
<span style="float: right; font-weight: bolder;">{{ site.version }}</span>
</p>
</section>
</header>
</div>
<div id="main_content_wrap" class="outer">
<section id="main_content" class="inner">
{{ content }}
</section>
</div>
<div id="footer_wrap" class="outer">
<footer class="inner">
<p class="copyright"><a href="{{ site.repository }}">{{ site.title }}</a> maintained by <a href="{{ site.owner_url }}">{{ site.owner }}</a></p>
<p><a href="https://github.com/jsncostello/slate">Slate</a> theme by <a href="https://github.com/jsncostello">Jason Costello</a></p>
</footer>
</div>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("{{ site.ga_id }}");
pageTracker._trackPageview();
} catch(err) {}
</script>
</body>
</html>

View File

@@ -1,47 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<meta name="description" content="{{ site.tagline }}" />
<link rel="stylesheet" type="text/css" media="screen" href="style/stylesheet.css">
<link rel="icon" type="image/png" href="images/favicon.png" />
<title>{{ page.title }}</title>
</head>
<body>
<div id="header_wrap" class="outer">
<header class="inner">
<h1 id="project_title">
<a href="index.html">{% include solaar.svg %} {{ site.title }}</a>
</h1>
</header>
</div>
<div id="main_content_wrap" class="outer">
<section id="main_content" class="inner">
{{ content }}
</section>
</div>
<div id="footer_wrap" class="outer">
<footer class="inner">
<p class="copyright"><a href="{{ site.repository }}">{{ site.title }}</a> maintained by <a href="{{ site.owner_url }}">{{ site.owner }}</a></p>
<p><a href="https://github.com/jsncostello/slate">Slate</a> theme by <a href="https://github.com/jsncostello">Jason Costello</a></p>
</footer>
</div>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("{{ site.ga_id }}");
pageTracker._trackPageview();
} catch(err) {}
</script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 943 B

After

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,70 +0,0 @@
.highlight .hll { background-color: #ffffcc }
.highlight { background: #f0f3f3; }
.highlight .c { color: #0099FF; font-style: italic } /* Comment */
.highlight .err { color: #AA0000; background-color: #FFAAAA } /* Error */
.highlight .k { color: #006699; font-weight: bold } /* Keyword */
.highlight .o { color: #555555 } /* Operator */
.highlight .cm { color: #0099FF; font-style: italic } /* Comment.Multiline */
.highlight .cp { color: #009999 } /* Comment.Preproc */
.highlight .c1 { color: #0099FF; font-style: italic } /* Comment.Single */
.highlight .cs { color: #0099FF; font-weight: bold; font-style: italic } /* Comment.Special */
.highlight .gd { background-color: #FFCCCC; border: 1px solid #CC0000 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #FF0000 } /* Generic.Error */
.highlight .gh { color: #003300; font-weight: bold } /* Generic.Heading */
.highlight .gi { background-color: #CCFFCC; border: 1px solid #00CC00 } /* Generic.Inserted */
.highlight .go { color: #AAAAAA } /* Generic.Output */
.highlight .gp { color: #000099; font-weight: bold } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #003300; font-weight: bold } /* Generic.Subheading */
.highlight .gt { color: #99CC66 } /* Generic.Traceback */
.highlight .kc { color: #006699; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #006699; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #006699; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #006699 } /* Keyword.Pseudo */
.highlight .kr { color: #006699; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #007788; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #FF6600 } /* Literal.Number */
.highlight .s { color: #CC3300 } /* Literal.String */
.highlight .na { color: #330099 } /* Name.Attribute */
.highlight .nb { color: #336666 } /* Name.Builtin */
.highlight .nc { color: #00AA88; font-weight: bold } /* Name.Class */
.highlight .no { color: #336600 } /* Name.Constant */
.highlight .nd { color: #9999FF } /* Name.Decorator */
.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */
.highlight .ne { color: #CC0000; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #CC00FF } /* Name.Function */
.highlight .nl { color: #9999FF } /* Name.Label */
.highlight .nn { color: #00CCFF; font-weight: bold } /* Name.Namespace */
.highlight .nt { color: #330099; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #003333 } /* Name.Variable */
.highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mf { color: #FF6600 } /* Literal.Number.Float */
.highlight .mh { color: #FF6600 } /* Literal.Number.Hex */
.highlight .mi { color: #FF6600 } /* Literal.Number.Integer */
.highlight .mo { color: #FF6600 } /* Literal.Number.Oct */
.highlight .sb { color: #CC3300 } /* Literal.String.Backtick */
.highlight .sc { color: #CC3300 } /* Literal.String.Char */
.highlight .sd { color: #CC3300; font-style: italic } /* Literal.String.Doc */
.highlight .s2 { color: #CC3300 } /* Literal.String.Double */
.highlight .se { color: #CC3300; font-weight: bold } /* Literal.String.Escape */
.highlight .sh { color: #CC3300 } /* Literal.String.Heredoc */
.highlight .si { color: #AA0000 } /* Literal.String.Interpol */
.highlight .sx { color: #CC3300 } /* Literal.String.Other */
.highlight .sr { color: #33AAAA } /* Literal.String.Regex */
.highlight .s1 { color: #CC3300 } /* Literal.String.Single */
.highlight .ss { color: #FFCC33 } /* Literal.String.Symbol */
.highlight .bp { color: #336666 } /* Name.Builtin.Pseudo */
.highlight .vc { color: #003333 } /* Name.Variable.Class */
.highlight .vg { color: #003333 } /* Name.Variable.Global */
.highlight .vi { color: #003333 } /* Name.Variable.Instance */
.highlight .il { color: #FF6600 } /* Literal.Number.Integer.Long */
.type-csharp .highlight .k { color: #0000FF }
.type-csharp .highlight .kt { color: #0000FF }
.type-csharp .highlight .nf { color: #000000; font-weight: normal }
.type-csharp .highlight .nc { color: #2B91AF }
.type-csharp .highlight .nn { color: #000000 }
.type-csharp .highlight .s { color: #A31515 }
.type-csharp .highlight .sc { color: #A31515 }

View File

@@ -1,442 +0,0 @@
/*******************************************************************************
Slate Theme for Github Pages
by Jason Costello, @jsncostello
*******************************************************************************/
@import url(pygment_trac.css);
/*******************************************************************************
MeyerWeb Reset
*******************************************************************************/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
ol, ul {
list-style: none;
}
blockquote, q {
}
table {
border-collapse: collapse;
border-spacing: 0;
}
a:focus {
outline: none;
}
/*******************************************************************************
Theme Styles
*******************************************************************************/
body {
box-sizing: border-box;
color:#373737;
background: #212121;
font-size: 18px;
font-family: 'Myriad Pro', Calibri, Helvetica, Arial, sans-serif;
line-height: 1.4;
-webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4, h5, h6 {
margin: 20px 0;
font-weight: 700;
color:#222222;
font-family: 'Lucida Grande', 'Calibri', Helvetica, Arial, sans-serif;
letter-spacing: -1px;
}
h1 {
font-size: 36px;
font-weight: 700;
}
h2 {
padding-bottom: 0px;
font-size: 32px;
background: url('../images/bg_hr.png') repeat-x bottom;
}
h3 {
font-size: 24px;
}
h4 {
font-size: 21px;
}
h5 {
font-size: 18px;
}
h6 {
font-size: 16px;
}
p {
margin: 10px 0 15px 0;
}
footer p {
color: #f2f2f2;
}
a {
text-decoration: none;
color: #007edf;
text-shadow: none;
transition: color 0.5s ease;
transition: text-shadow 0.5s ease;
-webkit-transition: color 0.5s ease;
-webkit-transition: text-shadow 0.5s ease;
-moz-transition: color 0.5s ease;
-moz-transition: text-shadow 0.5s ease;
-o-transition: color 0.5s ease;
-o-transition: text-shadow 0.5s ease;
-ms-transition: color 0.5s ease;
-ms-transition: text-shadow 0.5s ease;
}
#main_content a:hover {
color: #0069ba;
text-shadow: #0090ff 0px 0px 2px;
}
footer a:hover {
color: #43adff;
text-shadow: #0090ff 0px 0px 2px;
}
em {
font-style: italic;
}
strong {
font-weight: bold;
}
img {
position: relative;
margin: 0 auto;
max-width: 739px;
padding: 5px;
margin: 10px 0 10px 0;
border: 1px solid #ebebeb;
box-shadow: 0 0 5px #ebebeb;
-webkit-box-shadow: 0 0 5px #ebebeb;
-moz-box-shadow: 0 0 5px #ebebeb;
-o-box-shadow: 0 0 5px #ebebeb;
-ms-box-shadow: 0 0 5px #ebebeb;
}
img.logo {
max-width: 48px;
width: 48px;
height: 48px;
margin: 0;
padding: 0;
border: 0;
bottom: -10px;
box-shadow: none;
-webkit-box-shadow: 0;
-moz-box-shadow: 0;
-o-box-shadow: 0;
-ms-box-shadow: 0;
}
pre, code {
width: 100%;
color: #222;
background-color: #fff;
font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace;
font-size: 14px;
border-radius: 2px;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
}
pre {
width: 100%;
padding: 10px;
box-shadow: 0 0 10px rgba(0,0,0,.1);
overflow: auto;
}
code {
padding: 3px;
margin: 0 3px;
box-shadow: 0 0 10px rgba(0,0,0,.1);
}
pre code {
display: block;
box-shadow: none;
}
blockquote {
color: #666;
margin-bottom: 20px;
padding: 0 0 0 20px;
border-left: 3px solid #bbb;
}
ul, ol, dl {
margin-bottom: 15px
}
ul li {
list-style: inside;
padding-left: 20px;
}
ol li {
list-style: decimal inside;
padding-left: 20px;
}
dl dt {
font-weight: bold;
}
dl dd {
padding-left: 20px;
font-style: italic;
}
dl p {
padding-left: 20px;
font-style: italic;
}
hr {
height: 1px;
margin-bottom: 5px;
border: none;
background: url('../images/bg_hr.png') repeat-x center;
}
table {
border: 1px solid #373737;
margin-bottom: 20px;
text-align: left;
width: 100%;
}
th {
font-family: 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif;
padding: 10px;
background: #373737;
color: #fff;
}
td {
padding: 10px;
border: 1px solid #373737;
}
form {
background: #f2f2f2;
padding: 20px;
}
img {
width: 100%;
max-width: 100%;
}
/*******************************************************************************
Full-Width Styles
*******************************************************************************/
.outer {
width: 100%;
}
.inner {
position: relative;
max-width: 940px;
padding: 20px 10px;
margin: 0 auto;
}
.copyright {
float: right;
}
#forkme_banner {
display: block;
position: absolute;
top:0;
right: 10px;
z-index: 10;
padding: 10px 50px 10px 10px;
color: #fff;
background: url('../images/blacktocat.png') #0090ff no-repeat 95% 50%;
font-weight: 700;
box-shadow: 0 0 10px rgba(0,0,0,.5);
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
#header_wrap {
background: #212121;
}
#header_wrap .inner {
padding 50px 10px 30px 10px;
}
#project_title {
margin: 0;
color: #fff;
font-size: 42px;
font-weight: 700;
text-shadow: #111 0px 0px 10px;
}
#project_tagline {
color: #fff;
font-size: 24px;
font-weight: 300;
background: none;
text-shadow: #111 0px 0px 10px;
}
#downloads {
position: absolute;
width: 240px;
z-index: 10;
bottom: 0px;
right: 0;
height: 80px;
background: url('../images/icon_download.png') no-repeat 15% 70%;
}
.zip_download_link {
display: block;
float: right;
width: 90px;
height:70px;
text-indent: -5000px;
overflow: hidden;
background: url(../images/sprite_download.png) no-repeat bottom left;
}
.tar_download_link {
display: block;
float: right;
width: 90px;
height:70px;
text-indent: -5000px;
overflow: hidden;
background: url(../images/sprite_download.png) no-repeat bottom right;
margin-left: 10px;
}
.zip_download_link:hover {
background: url(../images/sprite_download.png) no-repeat top left;
}
.tar_download_link:hover {
background: url(../images/sprite_download.png) no-repeat top right;
}
#main_content_wrap {
background: #f2f2f2;
border-top: 1px solid #111;
border-bottom: 1px solid #111;
}
#main_content {
padding-top: 40px;
}
#footer_wrap {
background: #212121;
}
/*******************************************************************************
Small Device Styles
*******************************************************************************/
@media screen and (max-width: 480px) {
body {
font-size:14px;
}
#downloads {
display: none;
}
.inner {
min-width: 320px;
max-width: 480px;
}
#project_title {
font-size: 32px;
}
h1 {
font-size: 28px;
}
h2 {
font-size: 24px;
}
h3 {
font-size: 21px;
}
h4 {
font-size: 18px;
}
h5 {
font-size: 14px;
}
h6 {
font-size: 12px;
}
code, pre {
min-width: 320px;
max-width: 480px;
font-size: 11px;
}
}

View File

@@ -90,7 +90,7 @@ def _print(marker, data, scroll=False):
sys.stdout.write('\n')
# flush stdout manually...
# because trying to open stdin/out unbuffered programatically
# because trying to open stdin/out unbuffered programmatically
# works much too differently in Python 2/3
sys.stdout.flush()

View File

@@ -30,8 +30,10 @@ from __future__ import absolute_import, division, print_function, unicode_litera
import os as _os
import errno as _errno
from time import sleep
from select import select as _select
from pyudev import Context as _Context, Monitor as _Monitor, Device as _Device
from pyudev import DeviceNotFoundError
native_implementation = 'udev'
@@ -76,7 +78,13 @@ def exit():
return True
def _match(action, device, vendor_id=None, product_id=None, interface_number=None, hid_driver=None):
# The filter is used to determine whether this is a device of interest to Solaar
def _match(action, device, filter):
vendor_id=filter.get('vendor_id')
product_id=filter.get('product_id')
interface_number=filter.get('usb_interface')
hid_driver=filter.get('hid_driver')
usb_device = device.find_parent('usb', 'usb_device')
# print ("* parent", action, device, "usb:", usb_device)
if not usb_device:
@@ -84,6 +92,8 @@ def _match(action, device, vendor_id=None, product_id=None, interface_number=Non
vid = usb_device.get('ID_VENDOR_ID')
pid = usb_device.get('ID_MODEL_ID')
if vid is None or pid is None:
return # there are reports that sometimes the usb_device isn't set up right so be defensive
if not ((vendor_id is None or vendor_id == int(vid, 16)) and
(product_id is None or product_id == int(pid, 16))):
return
@@ -162,7 +172,7 @@ def monitor_glib(callback, *device_filters):
# print ("***", action, device)
if action == 'add':
for filter in filters:
d_info = _match(action, device, *filter)
d_info = _match(action, device, filter)
if d_info:
GLib.idle_add(cb, action, d_info)
break
@@ -187,7 +197,7 @@ def monitor_glib(callback, *device_filters):
m.start()
def enumerate(vendor_id=None, product_id=None, interface_number=None, hid_driver=None):
def enumerate(usb_id):
"""Enumerate the HID Devices.
List all the HID devices attached to the system, optionally filtering by
@@ -195,8 +205,9 @@ def enumerate(vendor_id=None, product_id=None, interface_number=None, hid_driver
:returns: a list of matching ``DeviceInfo`` tuples.
"""
for dev in _Context().list_devices(subsystem='hidraw'):
dev_info = _match('add', dev, vendor_id, product_id, interface_number, hid_driver)
dev_info = _match('add', dev, usb_id)
if dev_info:
yield dev_info
@@ -259,7 +270,17 @@ def write(device_handle, data):
assert device_handle
assert data
assert isinstance(data, bytes), (repr(data), type(data))
bytes_written = _os.write(device_handle, data)
retrycount = 0
bytes_written = 0
while(retrycount < 3):
try:
bytes_written = _os.write(device_handle, data)
retrycount += 1
except IOError as e:
if e.errno == _errno.EPIPE:
sleep(0.1)
else:
break
if bytes_written != len(data):
raise IOError(_errno.EIO, 'written %d bytes out of expected %d' % (bytes_written, len(data)))
@@ -338,27 +359,31 @@ def get_indexed_string(device_handle, index):
:param device_handle: a device handle returned by open() or open_path().
:param index: the index of the string to get.
:returns: the value corresponding to index, or None if no value found
:rtype: bytes or NoneType
"""
if index not in _DEVICE_STRINGS:
return None
try:
key = _DEVICE_STRINGS[index]
except KeyError:
return None
assert device_handle
stat = _os.fstat(device_handle)
dev = _Device.from_device_number(_Context(), 'char', stat.st_rdev)
if dev:
hid_dev = dev.find_parent('hid')
if hid_dev:
assert 'HID_ID' in hid_dev
bus, _ignore, _ignore = hid_dev['HID_ID'].split(':')
try:
dev = _Device.from_device_number(_Context(), 'char', stat.st_rdev)
except (DeviceNotFoundError, ValueError):
return None
if bus == '0003': # USB
usb_dev = dev.find_parent('usb', 'usb_device')
assert usb_dev
key = _DEVICE_STRINGS[index]
attrs = usb_dev.attributes
if key in attrs:
return attrs[key]
hid_dev = dev.find_parent('hid')
if hid_dev:
assert 'HID_ID' in hid_dev
bus, _ignore, _ignore = hid_dev['HID_ID'].split(':')
elif bus == '0005': # BLUETOOTH
# TODO
pass
if bus == '0003': # USB
usb_dev = dev.find_parent('usb', 'usb_device')
assert usb_dev
return usb_dev.attributes.get(key)
elif bus == '0005': # BLUETOOTH
# TODO
pass

View File

@@ -44,6 +44,15 @@ _LONG_MESSAGE_SIZE = 20
_MEDIUM_MESSAGE_SIZE = 15
_MAX_READ_SIZE = 32
# mapping from report_id to message length
report_lengths = {
0x10: _SHORT_MESSAGE_SIZE,
0x11: _LONG_MESSAGE_SIZE,
0x20: _MEDIUM_MESSAGE_SIZE,
0x21: _MAX_READ_SIZE
}
"""Default timeout on read (in seconds)."""
DEFAULT_TIMEOUT = 4
# the receiver itself should reply very fast, within 500ms
@@ -83,7 +92,7 @@ from .base_usb import ALL as _RECEIVER_USB_IDS
def receivers():
"""List all the Linux devices exposed by the UR attached to the machine."""
for receiver_usb_id in _RECEIVER_USB_IDS:
for d in _hid.enumerate(*receiver_usb_id):
for d in _hid.enumerate(receiver_usb_id):
yield d
@@ -189,6 +198,18 @@ def read(handle, timeout=DEFAULT_TIMEOUT):
return reply[1:]
# sanity checks on message report id and size
def check_message(data) :
assert isinstance(data, bytes), (repr(data), type(data))
report_id = ord(data[:1])
if report_id in report_lengths: # is this an HID++ or DJ message?
if report_lengths.get(report_id) == len(data):
return True
else:
_log.warn("unexpected message size: report_id %02X message %s" % (report_id, _strhex(data)))
return False
def _read(handle, timeout):
"""Read an incoming packet from the receiver.
@@ -207,18 +228,8 @@ def _read(handle, timeout):
close(handle)
raise NoReceiver(reason=reason)
if data:
assert isinstance(data, bytes), (repr(data), type(data))
if data and check_message(data): # ignore messages that fail check
report_id = ord(data[:1])
assert ((report_id & 0xF0 == 0) or
(report_id == 0x10 and len(data) == _SHORT_MESSAGE_SIZE) or
(report_id == 0x11 and len(data) == _LONG_MESSAGE_SIZE) or
(report_id == 0x20 and len(data) == _MEDIUM_MESSAGE_SIZE)), \
"unexpected message size: report_id %02X message %s" % (report_id, _strhex(data))
if report_id & 0xF0 == 0x00:
if _log.isEnabledFor(_DEBUG):
_log.debug("(%s) => r[%02X %s] ignoring unknown report", handle, report_id, _strhex(data[1:]))
return
devnumber = ord(data[1:2])
if _log.isEnabledFor(_DEBUG):
@@ -246,18 +257,12 @@ def _skip_incoming(handle, ihandle, notifications_hook):
raise NoReceiver(reason=reason)
if data:
assert isinstance(data, bytes), (repr(data), type(data))
report_id = ord(data[:1])
if _log.isEnabledFor(_DEBUG):
assert ((report_id & 0xF0 == 0) or
(report_id == 0x10 and len(data) == _SHORT_MESSAGE_SIZE) or
(report_id == 0x11 and len(data) == _LONG_MESSAGE_SIZE) or
(report_id == 0x20 and len(data) == _MEDIUM_MESSAGE_SIZE)), \
"unexpected message size: report_id %02X message %s" % (report_id, _strhex(data))
if notifications_hook and report_id & 0xF0:
n = make_notification(ord(data[1:2]), data[2:])
if n:
notifications_hook(n)
if check_message(data): # only process messages that pass check
report_id = ord(data[:1])
if notifications_hook:
n = make_notification(ord(data[1:2]), data[2:])
if n:
notifications_hook(n)
else:
# nothing in the input buffer, we're done
return
@@ -266,11 +271,17 @@ def _skip_incoming(handle, ihandle, notifications_hook):
def make_notification(devnumber, data):
"""Guess if this is a notification (and not just a request reply), and
return a Notification tuple if it is."""
sub_id = ord(data[:1])
if sub_id & 0x80 == 0x80:
# this is either a HID++1.0 register r/w, or an error reply
return
# regular keyboard key press reports and mouse movement reports are not notifications
# it would be better to check for report_id 0x20 but that information is not sent here
if len(data) == _MEDIUM_MESSAGE_SIZE-2 and (sub_id==0x02 or sub_id==0x01):
return
address = ord(data[1:2])
if (
# standard HID++ 1.0 notification, SubId may be 0x40 - 0x7F
@@ -291,6 +302,7 @@ from collections import namedtuple
_HIDPP_Notification = namedtuple('_HIDPP_Notification', ('devnumber', 'sub_id', 'address', 'data'))
_HIDPP_Notification.__str__ = lambda self: 'Notification(%d,%02X,%02X,%s)' % (self.devnumber, self.sub_id, self.address, _strhex(self.data))
_HIDPP_Notification.__unicode__ = _HIDPP_Notification.__str__
DJ_NOTIFICATION_LENGTH = _MEDIUM_MESSAGE_SIZE - 4 # to allow easy distinguishing of DJ notifications
del namedtuple
#
@@ -306,7 +318,7 @@ def request(handle, devnumber, request_id, *params):
:param devnumber: attached device number.
:param request_id: a 16-bit integer.
:param params: parameters for the feature call, 3 to 16 bytes.
:returns: the reply data, or ``None`` if some error occured.
:returns: the reply data, or ``None`` if some error occurred.
"""
# import inspect as _inspect
@@ -386,7 +398,7 @@ def request(handle, devnumber, request_id, *params):
if reply_data[2:3] == params[:1]:
return reply_data[2:]
else:
# hm, not mathing my request, and certainly not a notification
# hm, not matching my request, and certainly not a notification
continue
else:
return reply_data[2:]

View File

@@ -23,35 +23,102 @@
from __future__ import absolute_import, division, print_function, unicode_literals
_UNIFYING_DRIVER = 'logitech-djreceiver'
_GENERIC_DRIVER = ('hid-generic', 'generic-usb')
_DRIVER = ('hid-generic', 'generic-usb', 'logitech-djreceiver')
# max_devices is only used for receivers that do not support reading from _R.receiver_info offset 0x03, default to 1
# may_unpair is only used for receivers that do not support reading from _R.receiver_info offset 0x03, default to False
## should this last be changed so that may_unpair is used for all receivers? writing to _R.receiver_pairing doesn't seem right
# re_pairs determines whether a receiver pairs by replacing existing pairings, default to False
## currently only one receiver is so marked - should there be more?
# each tuple contains (vendor_id, product_id, usb interface number, hid driver)
_unifying_receiver = lambda product_id: {
'vendor_id':0x046d,
'product_id':product_id,
'usb_interface':2,
'hid_driver':_DRIVER,
'name':'Unifying Receiver'
}
_nano_receiver = lambda product_id: {
'vendor_id':0x046d,
'product_id':product_id,
'usb_interface':1,
'hid_driver':_DRIVER,
'name':'Nano Receiver',
'may_unpair': False,
're_pairs': True
}
_nano_receiver_max2 = lambda product_id: {
'vendor_id':0x046d,
'product_id':product_id,
'usb_interface':1,
'hid_driver':_DRIVER,
'name':'Nano Receiver',
'max_devices': 2,
'may_unpair': False,
're_pairs': True
}
_nano_receiver_maxn = lambda product_id, max: {
'vendor_id':0x046d,
'product_id':product_id,
'usb_interface':1,
'hid_driver':_DRIVER,
'name':'Nano Receiver',
'max_devices': max,
'may_unpair': False,
're_pairs': True
}
_lenovo_receiver = lambda product_id: {
'vendor_id':0x17ef,
'product_id':product_id,
'usb_interface':1,
'hid_driver':_DRIVER,
'name':'Nano Receiver'
}
_lightspeed_receiver = lambda product_id: {
'vendor_id':0x046d,
'product_id':product_id,
'usb_interface':2,
'hid_driver':_DRIVER,
'name':'Lightspeed Receiver'
}
# standard Unifying receivers (marked with the orange Unifying logo)
UNIFYING_RECEIVER = (0x046d, 0xc52b, 2, _UNIFYING_DRIVER)
UNIFYING_RECEIVER_2 = (0x046d, 0xc532, 2, _UNIFYING_DRIVER)
UNIFYING_RECEIVER_C52B = _unifying_receiver(0xc52b)
UNIFYING_RECEIVER_C532 = _unifying_receiver(0xc532)
# Nano receviers that support the Unifying protocol
NANO_RECEIVER_ADVANCED = (0x046d, 0xc52f, 1, _GENERIC_DRIVER)
NANO_RECEIVER_ADVANCED = _nano_receiver(0xc52f)
# Nano receivers that don't support the Unifying protocol
NANO_RECEIVER_C517 = (0x046d, 0xc517, 1, _GENERIC_DRIVER)
NANO_RECEIVER_C518 = (0x046d, 0xc518, 1, _GENERIC_DRIVER)
NANO_RECEIVER_C51A = (0x046d, 0xc51a, 1, _GENERIC_DRIVER)
NANO_RECEIVER_C51B = (0x046d, 0xc51b, 1, _GENERIC_DRIVER)
NANO_RECEIVER_C521 = (0x046d, 0xc521, 1, _GENERIC_DRIVER)
NANO_RECEIVER_C525 = (0x046d, 0xc525, 1, _GENERIC_DRIVER)
NANO_RECEIVER_C526 = (0x046d, 0xc526, 1, _GENERIC_DRIVER)
NANO_RECEIVER_C517 = _nano_receiver_maxn(0xc517,6)
NANO_RECEIVER_C518 = _nano_receiver(0xc518)
NANO_RECEIVER_C51A = _nano_receiver(0xc51a)
NANO_RECEIVER_C51B = _nano_receiver(0xc51b)
NANO_RECEIVER_C521 = _nano_receiver(0xc521)
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_max2(0xc534)
NANO_RECEIVER_C537 = _nano_receiver(0xc537)
NANO_RECEIVER_6042 = _lenovo_receiver(0x6042)
# Lightspeed receivers
LIGHTSPEED_RECEIVER_C539 = _lightspeed_receiver(0xc539)
LIGHTSPEED_RECEIVER_C53a = _lightspeed_receiver(0xc53a)
LIGHTSPEED_RECEIVER_C53f = _lightspeed_receiver(0xc53f)
del _DRIVER, _unifying_receiver, _nano_receiver, _lenovo_receiver, _lightspeed_receiver
ALL = (
UNIFYING_RECEIVER,
UNIFYING_RECEIVER_2,
UNIFYING_RECEIVER_C52B,
UNIFYING_RECEIVER_C532,
NANO_RECEIVER_ADVANCED,
NANO_RECEIVER_C517,
NANO_RECEIVER_C518,
@@ -60,4 +127,20 @@ ALL = (
NANO_RECEIVER_C521,
NANO_RECEIVER_C525,
NANO_RECEIVER_C526,
NANO_RECEIVER_C52e,
NANO_RECEIVER_C531,
NANO_RECEIVER_C534,
NANO_RECEIVER_C537,
NANO_RECEIVER_6042,
LIGHTSPEED_RECEIVER_C539,
LIGHTSPEED_RECEIVER_C53a,
LIGHTSPEED_RECEIVER_C53f,
)
def product_information(usb_id):
if isinstance(usb_id,str):
usb_id = int(usb_id,16)
for r in ALL:
if usb_id == r.get('product_id'):
return r
return { }

View File

@@ -115,6 +115,11 @@ class NamedInts(object):
# assert len(values) == len(self._indexed), "(%d) %r\n=> (%d) %r" % (len(values), values, len(self._indexed), self._indexed)
self._fallback = None
@classmethod
def list(cls, items, name_generator=lambda x: str(x)):
values = {name_generator(x): x for x in items}
return NamedInts(**values)
@classmethod
def range(cls, from_value, to_value, name_generator=lambda x: str(x), step=1):
values = {name_generator(x): x for x in range(from_value, to_value + 1, step)}
@@ -267,11 +272,21 @@ FirmwareInfo = namedtuple('FirmwareInfo', [
'version',
'extras'])
"""Reprogrammable keys informations."""
"""Reprogrammable keys information."""
ReprogrammableKeyInfo = namedtuple('ReprogrammableKeyInfo', [
'index',
'key',
'task',
'flags'])
ReprogrammableKeyInfoV4 = namedtuple('ReprogrammableKeyInfoV4', [
'index',
'key',
'task',
'flags',
'pos',
'group',
'group_mask',
'remapped'])
del namedtuple

View File

@@ -20,31 +20,30 @@
from __future__ import absolute_import, division, print_function, unicode_literals
from . import hidpp10 as _hidpp10
from .common import NamedInts as _NamedInts
from .hidpp10 import REGISTERS as _R, DEVICE_KIND as _DK
from .settings_templates import RegisterSettings as _RS, FeatureSettings as _FS
_R = _hidpp10.REGISTERS
#
#
#
from collections import namedtuple
_DeviceDescriptor = namedtuple('_DeviceDescriptor',
('name', 'kind', 'wpid', 'codename', 'protocol', 'registers', 'settings'))
('name', 'kind', 'wpid', 'codename', 'protocol', 'registers', 'settings', 'persister'))
del namedtuple
DEVICES = {}
def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None, settings=None):
def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None, settings=None, persister=None):
assert name
if kind is None:
kind = (_hidpp10.DEVICE_KIND.mouse if 'Mouse' in name
else _hidpp10.DEVICE_KIND.keyboard if 'Keyboard' in name
else _hidpp10.DEVICE_KIND.touchpad if 'Touchpad' in name
else _hidpp10.DEVICE_KIND.trackball if 'Trackball' in name
kind = (_DK.mouse if 'Mouse' in name
else _DK.keyboard if 'Keyboard' in name
else _DK.numpad if 'Number Pad' in name
else _DK.touchpad if 'Touchpad' in name
else _DK.trackball if 'Trackball' in name
else None)
assert kind is not None, 'descriptor for %s does not have kind set' % name
@@ -64,16 +63,16 @@ def _D(name, codename=None, kind=None, wpid=None, protocol=None, registers=None,
if wpid:
for w in wpid if isinstance(wpid, tuple) else (wpid, ):
if protocol > 1.0:
assert w[0:1] == '4', name + ' has protocol ' + protocol + ', wpid ' + w
assert w[0:1] == '4', '%s has protocol %0.1f, wpid %s' % (name, protocol, w)
else:
if w[0:1] == '1':
assert kind == _hidpp10.DEVICE_KIND.mouse, name + ' has protocol ' + protocol + ', wpid ' + w
assert kind == _DK.mouse, '%s has protocol %0.1f, wpid %s' % (name, protocol, w)
elif w[0:1] == '2':
assert kind == _hidpp10.DEVICE_KIND.keyboard, name + ' has protocol ' + protocol + ', wpid ' + w
assert kind in (_DK.keyboard, _DK.numpad), '%s has protocol %0.1f, wpid %s' % (name, protocol, w)
device_descriptor = _DeviceDescriptor(name=name, kind=kind,
wpid=wpid, codename=codename, protocol=protocol,
registers=registers, settings=settings)
registers=registers, settings=settings, persister=persister)
assert codename not in DEVICES, 'duplicate codename in device descriptors: %s' % (DEVICES[codename], )
DEVICES[codename] = device_descriptor
@@ -144,21 +143,58 @@ _PERFORMANCE_MX_DPIS = _NamedInts.range(0x81, 0x8F, lambda x: str((x - 0x80) * 1
# Keyboards
_D('Wireless Keyboard K230', protocol=2.0, wpid='400D')
_D('Wireless Keyboard K270')
_D('Wireless Keyboard K270(unifying)', protocol=2.0, wpid='4003')
_D('Wireless Keyboard MK270', protocol=2.0, wpid='4023',
settings=[
_FS.fn_swap()
],
)
_D('Wireless Keyboard K270', protocol=1.0,
registers=(_R.battery_status, ),
)
_D('Wireless Keyboard MK300', protocol=1.0, wpid='8521',
registers=(_R.battery_status, ),
)
_D('Wireless Keyboard MK320', protocol=1.0, wpid='200F',
registers=(_R.battery_status, ),
)
_D('Wireless Keyboard MK330')
_D('Wireless Keyboard K340')
_D('Wireless Keyboard K350', wpid='200A')
_D('Wireless Compact Keyboard K340', protocol=1.0, wpid='2007',
registers=(_R.battery_status, ),
)
_D('Wireless Wave Keyboard K350', protocol=1.0, wpid='200A',
registers=(_R.battery_status, ),
)
_D('Wireless Keyboard K360', protocol=2.0, wpid='4004',
settings=[
_FS.fn_swap()
],
)
_D('Wireless Keyboard K375s', protocol=2.0, wpid='4061',
settings=[
_FS.k375s_fn_swap()
],
)
_D('Wireless Touch Keyboard K400', protocol=2.0, wpid=('400E', '4024'),
settings=[
_FS.fn_swap()
],
)
_D('Wireless Keyboard MK520')
_D('Wireless Touch Keyboard K400 Plus', codename='K400 Plus', protocol=2.0, wpid='404D',
settings=[
_FS.new_fn_swap()
],
)
_D('Wireless Keyboard K520', protocol=1.0, wpid='2011',
registers=(_R.battery_status, ),
settings=[
_RS.fn_swap(),
],
)
_D('Number Pad N545', protocol=1.0, wpid='2006',
registers=(_R.battery_status, ),
)
_D('Wireless Keyboard MK550')
_D('Wireless Keyboard MK700', protocol=1.0, wpid='2008',
registers=(_R.battery_status, ),
@@ -171,6 +207,11 @@ _D('Wireless Solar Keyboard K750', protocol=2.0, wpid='4002',
_FS.fn_swap()
],
)
_D('Wireless Multi-Device Keyboard K780', protocol=4.5, wpid='405B',
settings=[
_FS.new_fn_swap()
],
)
_D('Wireless Illuminated Keyboard K800', protocol=1.0, wpid='2010',
registers=(_R.battery_status, _R.three_leds, ),
settings=[
@@ -178,25 +219,61 @@ _D('Wireless Illuminated Keyboard K800', protocol=1.0, wpid='2010',
_RS.hand_detection(),
],
)
_D('Wireless Illuminated Keyboard K800 new', codename='K800 new', protocol=4.5, wpid='406E',
settings=[
_FS.fn_swap()
],
)
_D('Illuminated Living-Room Keyboard K830', protocol=2.0, wpid='4032',
settings=[
_FS.new_fn_swap()
],
)
_D('Craft Advanced Keyboard', codename='Craft', protocol=4.5, wpid='4066')
_D('Wireless Keyboard S510', codename='S510', protocol=1.0, wpid='3622',
registers=(_R.battery_status, ),
)
# Mice
_D('Wireless Mouse M175')
_D('Wireless Mouse M185')
_D('Wireless Mouse M150', protocol=2.0, wpid='4022')
_D('Wireless Mouse M175', protocol=2.0, wpid='4008')
_D('Wireless Mouse M185 new', codename='M185n', protocol=4.5, wpid='4054',
settings=[
_FS.lowres_smooth_scroll(),
_FS.pointer_speed(),
])
# Apparently Logitech uses wpid 4055 for three different mice
# That's not so strange, as M185 is used on both Unifying-ready and non-Unifying-ready mice
_D('Wireless Mouse M185/M235/M310', codename='M185/M235/M310', protocol=4.5, wpid='4055',
settings=[
_FS.lowres_smooth_scroll(),
_FS.pointer_speed(),
])
_D('Wireless Mouse M185', protocol=2.0, wpid='4038')
_D('Wireless Mouse M187', protocol=2.0, wpid='4019')
_D('Wireless Mouse M215', protocol=1.0, wpid='1020')
_D('Wireless Mouse M235')
_D('Wireless Mouse M305', protocol=1.0, wpid='101F',
registers=(_R.battery_status, ),
settings=[
_RS.side_scroll(),
],
)
_D('Wireless Mouse M310')
_D('Wireless Mouse M310', protocol=1.0, wpid='1024',
registers=(_R.battery_status, ),
)
_D('Wireless Mouse M315')
_D('Wireless Mouse M317')
_D('Wireless Mouse M325')
_D('Wireless Mouse M325', protocol=2.0, wpid='400A',
settings=[
_FS.hi_res_scroll(),
])
_D('Wireless Mouse M345', protocol=2.0, wpid='4017')
_D('Wireless Mouse M350', protocol=1.0, wpid='101C',
registers=(_R.battery_charge, ),
)
_D('Wireless Mouse Pebble M350', codename='Pebble', protocol=2.0, wpid='4080')
_D('Wireless Mouse M505', codename='M505/B605', protocol=1.0, wpid='101D',
registers=(_R.battery_charge, ),
settings=[
@@ -211,19 +288,35 @@ _D('Wireless Mouse M510', protocol=1.0, wpid='1025',
_RS.side_scroll(),
],
)
_D('Wireless Mouse M510', codename='M510v2', protocol=2.0, wpid='4051',
settings=[
_FS.lowres_smooth_scroll(),
])
_D('Couch Mouse M515', protocol=2.0, wpid='4007')
_D('Wireless Mouse M525', protocol=2.0, wpid='4013')
_D('Multi Device Silent Mouse M585/M590', codename='M585/M590', protocol=4.5, wpid='406B',
settings=[
_FS.lowres_smooth_scroll(),
_FS.pointer_speed(),
],
)
_D('Touch Mouse M600', protocol=2.0, wpid='401A')
_D('Marathon Mouse M705', protocol=1.0, wpid='101B',
_D('Marathon Mouse M705 (M-R0009)', codename='M705 (M-R0009)', protocol=1.0, wpid='101B',
registers=(_R.battery_charge, ),
settings=[
_RS.smooth_scroll(),
_RS.side_scroll(),
],
)
_D('Marathon Mouse M705 (M-R0073)', codename='M705 (M-R0073)', protocol=4.5, wpid='406D',
settings=[
_FS.hires_smooth_invert(),
_FS.hires_smooth_resolution(),
_FS.pointer_speed(),
])
_D('Zone Touch Mouse T400')
_D('Touch Mouse T620', protocol=2.0)
_D('Logitech Cube', kind=_hidpp10.DEVICE_KIND.mouse, protocol=2.0)
_D('Logitech Cube', kind=_DK.mouse, protocol=2.0)
_D('Anywhere Mouse MX', codename='Anywhere MX', protocol=1.0, wpid='1017',
registers=(_R.battery_charge, ),
settings=[
@@ -231,6 +324,12 @@ _D('Anywhere Mouse MX', codename='Anywhere MX', protocol=1.0, wpid='1017',
_RS.side_scroll(),
],
)
_D('Anywhere Mouse MX 2', codename='Anywhere MX 2', protocol=4.5, wpid='404A',
settings=[
_FS.hires_smooth_invert(),
_FS.hires_smooth_resolution(),
],
)
_D('Performance Mouse MX', codename='Performance MX', protocol=1.0, wpid='101A',
registers=(_R.battery_status, _R.three_leds, ),
settings=[
@@ -240,6 +339,44 @@ _D('Performance Mouse MX', codename='Performance MX', protocol=1.0, wpid='101A',
],
)
_D('Wireless Mouse MX Master', codename='MX Master', protocol=4.5, wpid='4041',
settings=[
_FS.hires_smooth_invert(),
_FS.hires_smooth_resolution(),
],
)
_D('Wireless Mouse MX Master 2S', codename='MX Master 2S', protocol=4.5,wpid='4069',
settings=[
_FS.hires_smooth_invert(),
_FS.hires_smooth_resolution(),
],
)
_D('G7 Cordless Laser Mouse', codename='G7', protocol=1.0, wpid='1002',
registers=(_R.battery_status, ),
)
_D('G700 Gaming Mouse', codename='G700', protocol=1.0, wpid='1023',
registers=(_R.battery_status, _R.three_leds, ),
settings=[
_RS.smooth_scroll(),
_RS.side_scroll(),
],
)
_D('G700s Gaming Mouse', codename='G700s', protocol=1.0, wpid='102A',
registers=(_R.battery_status, _R.three_leds, ),
settings=[
_RS.smooth_scroll(),
_RS.side_scroll(),
],
)
_D('LX5 Cordless Mouse', codename='LX5', protocol=1.0, wpid='5612',
registers=(_R.battery_status, ),
)
_D('Wireless Mouse M30', codename='M30', protocol=1.0, wpid='6822',
registers=(_R.battery_status, ),
)
# Trackballs
_D('Wireless Trackball M570')
@@ -254,7 +391,7 @@ _D('Wireless Touchpad', codename='Wireless Touch', protocol=2.0, wpid='4011')
# A wpid is necessary to properly identify them.
#
_D('VX Nano Cordless Laser Mouse', codename='VX Nano', protocol=1.0, wpid='100F',
_D('VX Nano Cordless Laser Mouse', codename='VX Nano', protocol=1.0, wpid=('100B', '100F'),
registers=(_R.battery_charge, ),
settings=[
_RS.smooth_scroll(),
@@ -271,3 +408,41 @@ _D('V550 Nano Cordless Laser Mouse', codename='V550 Nano', protocol=1.0, wpid='1
_RS.side_scroll(),
],
)
# Mini receiver mice
_D('MX610 Laser Cordless Mouse', codename='MX610', protocol=1.0, wpid='1001',
registers=(_R.battery_status, ),
)
_D('MX620 Laser Cordless Mouse', codename='MX620', protocol=1.0, wpid=('100A', '1016'),
registers=(_R.battery_charge, ),
)
_D('MX610 Left-Handled Mouse', codename='MX610L', protocol=1.0, wpid='1004',
registers=(_R.battery_status, ),
)
_D('V400 Laser Cordless Mouse', codename='V400', protocol=1.0, wpid='1003',
registers=(_R.battery_status, ),
)
_D('V450 Laser Cordless Mouse', codename='V450', protocol=1.0, wpid='1005',
registers=(_R.battery_status, ),
)
_D('VX Revolution', codename='VX Revolution', kind=_DK.mouse, protocol=1.0, wpid=('1006', '100D'),
registers=(_R.battery_charge, ),
)
_D('MX Air', codename='MX Air', protocol=1.0, kind=_DK.mouse, wpid=('1007', '100E'),
registers=(_R.battery_charge, ),
)
_D('MX Revolution', codename='MX Revolution', protocol=1.0, kind=_DK.mouse, wpid=('1008', '100C'),
registers=(_R.battery_charge, ),
)
_D('MX 1100 Cordless Laser Mouse', codename='MX 1100', protocol=1.0, kind=_DK.mouse, wpid='1014',
registers=(_R.battery_charge, ),
settings=[
_RS.smooth_scroll(),
_RS.side_scroll(),
],
)
# Some exotics...
_D('Fujitsu Sonic Mouse', codename='Sonic', protocol=1.0, wpid='1029')

View File

@@ -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,
@@ -38,6 +39,8 @@ from . import special_keys
#
#
# <FeaturesSupported.xml sed '/LD_FID_/{s/.*LD_FID_/\t/;s/"[ \t]*Id="/=/;s/" \/>/,/p}' | sort -t= -k2
# additional features names taken from https://github.com/cvuchener/hidpp and https://github.com/Logitech/cpg-docs/tree/master/hidpp20
"""Possible features available on a Logitech device.
A particular device might not support all these features, and may support other
@@ -47,30 +50,109 @@ FEATURE = _NamedInts(
ROOT=0x0000,
FEATURE_SET=0x0001,
FEATURE_INFO=0x0002,
# Common
DEVICE_FW_VERSION=0x0003,
DEVICE_UNIT_ID=0x0004,
DEVICE_NAME=0x0005,
DEVICE_GROUPS=0x0006,
DFUCONTROL=0x00C0,
DEVICE_FRIENDLY_NAME=0x0007,
KEEP_ALIVE=0x0008,
RESET=0x0020, # "Config Change"
CRYPTO_ID=0x0021,
TARGET_SOFTWARE=0x0030,
WIRELESS_SIGNAL_STRENGTH=0x0080,
DFUCONTROL_LEGACY=0x00C0,
DFUCONTROL_UNSIGNED=0x00C1,
DFUCONTROL_SIGNED=0x00C2,
DFU=0x00D0,
BATTERY_STATUS=0x1000,
BATTERY_VOLTAGE=0x1001,
CHARGING_CONTROL=0x1010,
LED_CONTROL=0x1300,
GENERIC_TEST=0x1800,
DEVICE_RESET=0x1802,
OOBSTATE=0x1805,
CONFIG_DEVICE_PROPS=0x1806,
CHANGE_HOST=0x1814,
HOSTS_INFO=0x1815,
BACKLIGHT=0x1981,
BACKLIGHT2=0x1982,
BACKLIGHT3=0x1983,
PRESENTER_CONTROL=0x1A00,
SENSOR_3D=0x1A01,
REPROG_CONTROLS=0x1B00,
REPROG_CONTROLS_V2=0x1B01,
REPROG_CONTROLS_V2_2=0x1B02, # LogiOptions 2.10.73 features.xml
REPROG_CONTROLS_V3=0x1B03,
REPROG_CONTROLS_V4=0x1B04,
REPORT_HID_USAGE=0x1BC0,
PERSISTENT_REMAPPABLE_ACTION=0x1C00,
WIRELESS_DEVICE_STATUS=0x1D4B,
REMAINING_PAIRING=0x1DF0,
FIRMWARE_PROPERTIES=0x1F1F,
ADC_MEASUREMENT=0x1F20,
# Mouse
LEFT_RIGHT_SWAP=0x2001,
SWAP_BUTTON_CANCEL=0x2005,
POINTER_AXIS_ORIENTATION=0x2006,
VERTICAL_SCROLLING=0x2100,
SMART_SHIFT=0x2110,
HI_RES_SCROLLING=0x2120,
HIRES_WHEEL=0x2121,
LOWRES_WHEEL=0x2130,
THUMB_WHEEL=0x2150,
MOUSE_POINTER=0x2200,
ADJUSTABLE_DPI=0x2201,
POINTER_SPEED=0x2205,
ANGLE_SNAPPING=0x2230,
SURFACE_TUNING=0x2240,
HYBRID_TRACKING=0x2400,
# Keyboard
FN_INVERSION=0x40A0,
NEW_FN_INVERSION=0x40A2,
K375S_FN_INVERSION=0x40A3,
ENCRYPTION=0x4100,
LOCK_KEY_STATE=0x4220,
SOLAR_DASHBOARD=0x4301,
KEYBOARD_LAYOUT=0x4520,
KEYBOARD_DISABLE=0x4521,
KEYBOARD_DISABLE_BY_USAGE=0x4522,
DUALPLATFORM=0x4530,
MULTIPLATFORM=0x4531,
KEYBOARD_LAYOUT_2=0x4540,
CROWN=0x4600,
# Touchpad
TOUCHPAD_FW_ITEMS=0x6010,
TOUCHPAD_SW_ITEMS=0x6011,
TOUCHPAD_WIN8_FW_ITEMS=0x6012,
TAP_ENABLE=0x6020,
TAP_ENABLE_EXTENDED=0x6021,
CURSOR_BALLISTIC=0x6030,
TOUCHPAD_RESOLUTION=0x6040,
TOUCHPAD_RAW_XY=0x6100,
TOUCHMOUSE_RAW_POINTS=0x6110,
TOUCHMOUSE_6120=0x6120,
GESTURE=0x6500,
GESTURE_2=0x6501,
# Gaming Devices
GKEY=0x8010,
MKEYS=0x8020,
MR=0x8030,
BRIGHTNESS_CONTROL=0x8040,
REPORT_RATE=0x8060,
COLOR_LED_EFFECTS=0x8070,
RGB_EFFECTS=0X8071,
PER_KEY_LIGHTING=0x8080,
PER_KEY_LIGHTING_V2=0x8081,
MODE_STATUS=0x8090,
ONBOARD_PROFILES=0x8100,
MOUSE_BUTTON_SPY=0x8110,
LATENCY_MONITORING=0x8111,
GAMING_ATTACHMENTS=0x8120,
FORCE_FEEDBACK=0x8123,
SIDETONE=0x8300,
EQUALIZER=0x8310,
HEADSET_OUT=0x8320,
)
FEATURE._fallback = lambda x: 'unknown:%04X' % x
@@ -106,6 +188,22 @@ BATTERY_STATUS = _NamedInts(
invalid_battery=0x05,
thermal_error=0x06)
CHARGE_STATUS = _NamedInts(
charging=0x00,
full=0x01,
not_charging=0x02,
error=0x07)
CHARGE_LEVEL = _NamedInts(
average=0x00,
full=0x01,
critical=0x02)
CHARGE_TYPE = _NamedInts(
standard=0x00,
fast=0x01,
slow=0x02)
ERROR = _NamedInts(
unknown=0x01,
invalid_argument=0x02,
@@ -210,9 +308,10 @@ class FeaturesArray(object):
indices = index.indices(len(self.features))
return [self.__getitem__(i) for i in range(*indices)]
def __contains__(self, value):
def __contains__(self, featureId):
"""Tests whether the list contains given Feature ID"""
if self._check():
ivalue = int(value)
ivalue = int(featureId)
may_have = False
for f in self.features:
@@ -220,8 +319,6 @@ class FeaturesArray(object):
may_have = True
elif ivalue == int(f):
return True
elif ivalue < int(f):
break
if may_have:
reply = self.device.request(0x0000, _pack('!H', ivalue))
@@ -231,17 +328,16 @@ class FeaturesArray(object):
self.features[index] = FEATURE[ivalue]
return True
def index(self, value):
def index(self, featureId):
"""Gets the Feature Index for a given Feature ID"""
if self._check():
may_have = False
ivalue = int(value)
ivalue = int(featureId)
for index, f in enumerate(self.features):
if f is None:
may_have = True
elif ivalue == int(f):
return index
elif ivalue < int(f):
raise ValueError("%r not in list" % value)
if may_have:
reply = self.device.request(0x0000, _pack('!H', ivalue))
@@ -250,7 +346,7 @@ class FeaturesArray(object):
self.features[index] = FEATURE[ivalue]
return index
raise ValueError("%r not in list" % value)
raise ValueError("%r not in list" % featureId)
def __iter__(self):
if self._check():
@@ -270,11 +366,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):
@@ -282,13 +379,34 @@ 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:
try:
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
except Exception:
remapped = key
remap_key = key
remap_flag = 0
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]
@@ -402,14 +520,49 @@ def get_battery(device):
battery = feature_request(device, FEATURE.BATTERY_STATUS)
if battery:
discharge, dischargeNext, status = _unpack('!BBB', battery[:3])
discharge = None if discharge == 0 else discharge
if _log.isEnabledFor(_DEBUG):
_log.debug("device %d battery %d%% charged, next level %d%% charge, status %d = %s",
device.number, discharge, dischargeNext, status, BATTERY_STATUS[status])
return discharge, BATTERY_STATUS[status]
def get_voltage(device):
battery_voltage = feature_request(device, FEATURE.BATTERY_VOLTAGE)
if battery_voltage:
voltage, flags = _unpack('>HB', battery_voltage[:3])
charging = False
charge_sts = ERROR.unknown
charge_lvl = CHARGE_LEVEL.average
charge_type = CHARGE_TYPE.standard
if flags & (1 << 7):
charging = True
charge_sts = CHARGE_STATUS[flags & 0x03]
if charge_sts is None:
charge_sts = ERROR.unknown
elif charge_sts == CHARGE_STATUS.full:
charge_lvl = CHARGE_LEVEL.full
if (flags & (1 << 3)):
charge_type = CHARGE_TYPE.fast
elif (flags & (1 << 4)):
charge_type = CHARGE_TYPE.slow
elif (flags & (1 << 5)):
charge_lvl = CHARGE_LEVEL.critical
if _log.isEnabledFor(_DEBUG):
_log.debug("device %d, battery voltage %d mV, charging = %d, charge status %d = %s, charge level %s, charge type %s",
device.number, voltage, charging, (flags & 0x03), charge_sts, charge_lvl, charge_type)
return voltage, charging, charge_sts, charge_lvl, charge_type
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]))
@@ -427,3 +580,67 @@ def get_mouse_pointer_info(device):
'suggest_os_ballistics': suggest_os_ballistics,
'suggest_vertical_orientation': suggest_vertical_orientation
}
def get_vertical_scrolling_info(device):
vertical_scrolling_info = feature_request(device, FEATURE.VERTICAL_SCROLLING)
if vertical_scrolling_info:
roller, ratchet, lines = _unpack('!BBB', vertical_scrolling_info[:3])
roller_type = ('reserved', 'standard', 'reserved', '3G', 'micro', 'normal touch pad', 'inverted touch pad', 'reserved')[roller]
return {
'roller': roller_type,
'ratchet': ratchet,
'lines': lines
}
def get_hi_res_scrolling_info(device):
hi_res_scrolling_info = feature_request(device, FEATURE.HI_RES_SCROLLING)
if hi_res_scrolling_info:
mode, resolution = _unpack('!BB', hi_res_scrolling_info[:2])
return mode, resolution
def get_pointer_speed_info(device):
pointer_speed_info = feature_request(device, FEATURE.POINTER_SPEED)
if pointer_speed_info:
pointer_speed_hi, pointer_speed_lo = _unpack('!BB', pointer_speed_info[:2])
#if pointer_speed_lo > 0:
# pointer_speed_lo = pointer_speed_lo
return pointer_speed_hi+pointer_speed_lo/256
def get_lowres_wheel_status(device):
lowres_wheel_status = feature_request(device, FEATURE.LOWRES_WHEEL)
if lowres_wheel_status:
wheel_flag = _unpack('!B', lowres_wheel_status[:1])[0]
wheel_reporting = ('HID', 'HID++')[wheel_flag & 0x01]
return wheel_reporting
def get_hires_wheel(device):
caps = feature_request(device, FEATURE.HIRES_WHEEL, 0x00)
mode = feature_request(device, FEATURE.HIRES_WHEEL, 0x10)
ratchet = feature_request(device, FEATURE.HIRES_WHEEL, 0x030)
if caps and mode and ratchet:
# Parse caps
multi, flags = _unpack('!BB', caps[:2])
has_invert = (flags & 0x08) != 0
has_ratchet = (flags & 0x04) != 0
# Parse mode
wheel_mode, reserved = _unpack('!BB', mode[:2])
target = (wheel_mode & 0x01) != 0
res = (wheel_mode & 0x02) != 0
inv = (wheel_mode & 0x04) != 0
# Parse Ratchet switch
ratchet_mode, reserved = _unpack('!BB', ratchet[:2])
ratchet = (ratchet_mode & 0x01) != 0
return multi, has_invert, has_ratchet, inv, res, target, ratchet

View File

@@ -27,8 +27,10 @@ import gettext as _gettext
try:
unicode
_ = lambda x: _gettext.gettext(x).decode('UTF-8')
ngettext = lambda *x: _gettext.ngettext(*x).decode('UTF-8')
except:
_ = _gettext.gettext
ngettext = _gettext.ngettext
# A few common strings, not always accessible as such in the code.
@@ -38,7 +40,7 @@ _DUMMY = (
_("empty"), _("critical"), _("low"), _("good"), _("full"),
# battery charging statuses
_("discharging"), _("recharging"), _("almost full"), _("full"),
_("discharging"), _("recharging"), _("almost full"), _("charged"),
_("slow recharge"), _("invalid battery"), _("thermal error"),
# pairing errors

View File

@@ -150,7 +150,7 @@ class EventsListener(_threading.Thread):
# replace the handle with a threaded one
self.receiver.handle = _ThreadedHandle(self, self.receiver.path, self.receiver.handle)
# get the right low-level handle for this thead
# get the right low-level handle for this thread
ihandle = int(self.receiver.handle)
if _log.isEnabledFor(_INFO):
_log.info("started with %s (%d)", self.receiver, ihandle)
@@ -222,7 +222,8 @@ class EventsListener(_threading.Thread):
if self._active: # and _threading.current_thread() == self:
# if _log.isEnabledFor(_DEBUG):
# _log.debug("queueing unhandled %s", n)
self._queued_notifications.put(n)
if not self._queued_notifications.full():
self._queued_notifications.put(n)
def __bool__(self):
return bool(self._active and self.receiver)

View File

@@ -18,7 +18,7 @@
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# Handles incoming events from the receiver/devices, updating the related
# status object as appropiate.
# status object as appropriate.
from __future__ import absolute_import, division, print_function, unicode_literals
@@ -32,6 +32,7 @@ from .common import strhex as _strhex, unpack as _unpack
from . import hidpp10 as _hidpp10
from . import hidpp20 as _hidpp20
from .status import KEYS as _K, ALERT as _ALERT
from .base import DJ_NOTIFICATION_LENGTH as _DJ_NOTIFICATION_LENGTH
_R = _hidpp10.REGISTERS
_F = _hidpp20.FEATURE
@@ -64,7 +65,7 @@ def _process_receiver_notification(receiver, status, n):
# pairing lock notification
if n.sub_id == 0x4A:
status.lock_open = bool(n.address & 0x01)
reason = _("pairing lock is ") + (_("open") if status.lock_open else _("closed"))
reason = (_("pairing lock is open") if status.lock_open else _("pairing lock is closed"))
if _log.isEnabledFor(_INFO):
_log.info("%s: %s", receiver, reason)
@@ -92,9 +93,12 @@ def _process_device_notification(device, status, n):
# HID++ 1.0 requests, should never get here
assert n.sub_id & 0x80 == 0
# 0x40 to 0x7F appear to be HID++ 1.0 notifications
# 0x40 to 0x7F appear to be HID++ 1.0 or DJ notifications
if n.sub_id >= 0x40:
return _process_hidpp10_notification(device, status, n)
if len(n.data) == _DJ_NOTIFICATION_LENGTH :
return _process_dj_notification(device, status, n)
else:
return _process_hidpp10_notification(device, status, n)
# At this point, we need to know the device's protocol, otherwise it's
# possible to not know how to handle it.
@@ -115,6 +119,30 @@ def _process_device_notification(device, status, n):
return _process_feature_notification(device, status, n, feature)
def _process_dj_notification(device, status, n) :
if _log.isEnabledFor(_DEBUG):
_log.debug("%s (%s) DJ notification %s", device, device.protocol, n)
if n.sub_id == 0x40:
# do all DJ paired notifications also show up as HID++ 1.0 notifications?
if _log.isEnabledFor(_INFO):
_log.info("%s: ignoring DJ unpaired: %s", device, n)
return True
if n.sub_id == 0x41:
# do all DJ paired notifications also show up as HID++ 1.0 notifications?
if _log.isEnabledFor(_INFO):
_log.info("%s: ignoring DJ paired: %s", device, n)
return True
if n.sub_id == 0x42:
if _log.isEnabledFor(_INFO):
_log.info("%s: ignoring DJ connection: %s", device, n)
return True
_log.warn("%s: unrecognized DJ %s", device, n)
def _process_hidpp10_custom_notification(device, status, n):
if _log.isEnabledFor(_DEBUG):
_log.debug("%s (%s) custom notification %s", device, device.protocol, n)
@@ -127,7 +155,7 @@ def _process_hidpp10_custom_notification(device, status, n):
status.set_battery_info(charge, status_text)
return True
if n.sub_id == _R.illumination:
if n.sub_id == _R.keyboard_illumination:
# message layout: 10 ix 17("address") <??> <?> <??> <light level 1=off..5=max>
# TODO anything we can do with this?
if _log.isEnabledFor(_INFO):
@@ -147,15 +175,24 @@ def _process_hidpp10_notification(device, status, n):
device.status = None
if device.number in device.receiver:
del device.receiver[device.number]
status.changed(active=False, alert=_ALERT.ALL, reason='unpaired')
status.changed(active=False, alert=_ALERT.ALL, reason=_("unpaired"))
else:
_log.warn("%s: disconnection with unknown type %02X: %s", device, n.address, n)
return True
# wireless link notification
if n.sub_id == 0x41:
protocol_name = ('unifying (eQuad DJ)' if n.address == 0x04
else 'eQuad' if n.address == 0x03
protocol_name = ('Bluetooth' if n.address == 0x01
else '27 MHz' if n.address == 0x02
else 'QUAD or eQUAD' if n.address == 0x03
else 'eQUAD step 4 DJ' if n.address == 0x04
else 'DFU Lite' if n.address == 0x05
else 'eQUAD step 4 Lite' if n.address == 0x06
else 'eQUAD step 4 Gaming' if n.address == 0x07
else 'eQUAD step 4 for gamepads' if n.address == 0x08
else 'eQUAD nano Lite' if n.address == 0x0A
else 'Lightspeed 1' if n.address == 0x0C
else 'Lightspeed 1_1' if n.address == 0x0D
else None)
if protocol_name:
if _log.isEnabledFor(_DEBUG):
@@ -163,14 +200,14 @@ def _process_hidpp10_notification(device, status, n):
assert wpid == device.wpid, "%s wpid mismatch, got %s" % (device, wpid)
flags = ord(n.data[:1]) & 0xF0
link_encrypyed = bool(flags & 0x20)
link_encrypted = bool(flags & 0x20)
link_established = not (flags & 0x40)
if _log.isEnabledFor(_DEBUG):
sw_present = bool(flags & 0x10)
has_payload = bool(flags & 0x80)
_log.debug("%s: %s connection notification: software=%s, encrypted=%s, link=%s, payload=%s",
device, protocol_name, sw_present, link_encrypyed, link_established, has_payload)
status[_K.LINK_ENCRYPTED] = link_encrypyed
device, protocol_name, sw_present, link_encrypted, link_established, has_payload)
status[_K.LINK_ENCRYPTED] = link_encrypted
status.changed(active=link_established)
else:
_log.warn("%s: connection notification with unknown protocol %02X: %s", device.number, n.address, n)
@@ -189,7 +226,7 @@ def _process_hidpp10_notification(device, status, n):
if n.address == 0x01:
if _log.isEnabledFor(_DEBUG):
_log.debug("%s: device powered on", device)
reason = str(status) or _("powered on")
reason = status.to_string() or _("powered on")
status.changed(active=True, alert=_ALERT.NOTIFICATION, reason=reason)
else:
_log.warn("%s: unknown %s", device, n)
@@ -201,9 +238,11 @@ def _process_hidpp10_notification(device, status, n):
def _process_feature_notification(device, status, n, feature):
if feature == _F.BATTERY_STATUS:
if n.address == 0x00:
discharge = ord(n.data[:1])
battery_status = ord(n.data[1:2])
status.set_battery_info(discharge, _hidpp20.BATTERY_STATUS[battery_status])
discharge_level = ord(n.data[:1])
discharge_level = None if discharge_level == 0 else discharge_level
discharge_next_level = ord(n.data[1:2])
battery_status = ord(n.data[2:3])
status.set_battery_info(discharge_level, _hidpp20.BATTERY_STATUS[battery_status])
else:
_log.warn("%s: unknown BATTERY %s", device, n)
return True
@@ -255,7 +294,7 @@ def _process_feature_notification(device, status, n, feature):
reports_period = 2 # seconds
device.feature_request(_F.SOLAR_DASHBOARD, 0x00, reports_count, reports_period)
else:
_log.warn("%s: unknown SOLAR CHAGE %s", device, n)
_log.warn("%s: unknown SOLAR CHARGE %s", device, n)
else:
_log.warn("%s: SOLAR CHARGE not GOOD? %s", device, n)
return True
@@ -274,4 +313,22 @@ def _process_feature_notification(device, status, n, feature):
_log.warn("%s: unknown TOUCH MOUSE %s", device, n)
return True
if feature == _F.HIRES_WHEEL:
if (n.address == 0x00):
if _log.isEnabledFor(_INFO):
flags, delta_v = _unpack('>bh', n.data[:3])
high_res = (flags & 0x10) != 0
periods = flags & 0x0f
_log.info("%s: WHEEL: res: %d periods: %d delta V:%-3d", device, high_res, periods, delta_v)
return True
elif (n.address == 0x10):
if _log.isEnabledFor(_INFO):
flags = ord(n.data[:1])
ratchet = flags & 0x01
_log.info("%s: WHEEL: ratchet: %d", device, ratchet)
return True
else:
_log.warn("%s: unknown WHEEL %s", device, n)
return True
_log.warn("%s: unrecognized %s for feature %s (index %02X)", device, n, feature, n.sub_id)

View File

@@ -33,6 +33,7 @@ from . import hidpp20 as _hidpp20
from .common import strhex as _strhex
from .descriptors import DEVICES as _DESCRIPTORS
from .settings_templates import check_feature_settings as _check_feature_settings
from .base_usb import product_information as _product_information
_R = _hidpp10.REGISTERS
@@ -55,7 +56,7 @@ class PairedDevice(object):
self.wpid = None
self.descriptor = None
# mose, keyboard, etc (see _hidpp10.DEVICE_KIND)
# mouse, keyboard, etc (see _hidpp10.DEVICE_KIND)
self._kind = None
# Unifying peripherals report a codename.
self._codename = None
@@ -80,7 +81,7 @@ class PairedDevice(object):
# _log.debug("new PairedDevice(%s, %s, %s)", receiver, number, link_notification)
if link_notification is not None:
self.online = bool(ord(link_notification.data[0:1]) & 0x40)
self.online = not bool(ord(link_notification.data[0:1]) & 0x40)
self.wpid = _strhex(link_notification.data[2:3] + link_notification.data[1:2])
# assert link_notification.address == (0x04 if unifying else 0x03)
kind = ord(link_notification.data[0:1]) & 0x0F
@@ -311,7 +312,7 @@ class PairedDevice(object):
__bool__ = __nonzero__ = lambda self: self.wpid is not None and self.number in self.receiver
def __str__(self):
return '<PairedDevice(%d,%s,%s)>' % (self.number, self.wpid, self.codename or '?')
return '<PairedDevice(%d,%s,%s,%s)>' % (self.number, self.wpid, self.codename or '?', self.serial)
__unicode__ = __repr__ = __str__
#
@@ -333,27 +334,30 @@ class Receiver(object):
self.path = device_info.path
# USB product id, used for some Nano receivers
self.product_id = device_info.product_id
product_info = _product_information(self.product_id)
if not product_info:
raise Exception("Unknown receiver type", self.product_id)
# 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 serial_reply :
self.serial = _strhex(serial_reply[1:5])
self.max_devices = ord(serial_reply[6:7])
# TODO _properly_ figure out which receivers do and which don't support unpairing
# This code supposes that receivers that don't unpair support a pairing request for device index 0
self.may_unpair = self.write_register(_R.receiver_pairing) is None
else: # handle receivers that don't have a serial number specially (i.e., c534)
self.serial = None
self.max_devices = product_info.get('max_devices',1)
self.may_unpair = product_info.get('may_unpair',False)
if self.max_devices == 6:
self.name = 'Unifying Receiver'
elif self.max_devices < 6:
self.name = 'Nano Receiver'
else:
raise Exception("unknown receiver type", self.max_devices)
self.name = product_info.get('name','')
self.re_pairs = product_info.get('re_pairs',False)
self._str = '<%s(%s,%s%s)>' % (self.name.replace(' ', ''), self.path, '' if isinstance(self.handle, int) else 'T', self.handle)
# TODO _properly_ figure out which receivers do and which don't support unpairing
self.may_unpair = self.write_register(_R.receiver_pairing) is None
self._firmware = None
self._devices = {}
self._remaining_pairings = None
def close(self):
handle, self.handle = self.handle, None
@@ -369,6 +373,15 @@ class Receiver(object):
self._firmware = _hidpp10.get_firmware(self)
return self._firmware
# how many pairings remain (None for unknown, -1 for unlimited)
def remaining_pairings(self,cache=True):
if self._remaining_pairings is None or not cache:
ps = self.read_register(_R.receiver_connection)
if ps is not None:
ps = ord(ps[2:3])
self._remaining_pairings = ps-5 if ps >= 5 else -1
return self._remaining_pairings
def enable_notifications(self, enable=True):
"""Enable or disable device (dis)connection notifications on this
receiver."""
@@ -465,6 +478,9 @@ class Receiver(object):
return self.register_new_device(key)
def __delitem__(self, key):
self._unpair_device(key, False)
def _unpair_device(self, key, force=False):
key = int(key)
if self._devices.get(key) is None:
@@ -476,18 +492,25 @@ class Receiver(object):
del self._devices[key]
return
action = 0x03
reply = self.write_register(_R.receiver_pairing, action, key)
if reply:
# invalidate the device
if self.re_pairs and not force:
# invalidate the device, but these receivers don't unpair per se
dev.online = False
dev.wpid = None
if key in self._devices:
del self._devices[key]
_log.warn("%s unpaired device %s", self, dev)
_log.warn("%s removed device %s", self, dev)
else:
_log.error("%s failed to unpair device %s", self, dev)
raise IndexError(key)
reply = self.write_register(_R.receiver_pairing, 0x03, key)
if reply:
# invalidate the device
dev.online = False
dev.wpid = None
if key in self._devices:
del self._devices[key]
_log.warn("%s unpaired device %s", self, dev)
else:
_log.error("%s failed to unpair device %s", self, dev)
raise IndexError(key)
def __len__(self):
return len([d for d in self._devices.values() if d is not None])

View File

@@ -24,24 +24,25 @@ _log = getLogger(__name__)
del getLogger
from copy import copy as _copy
import math
from .common import (
NamedInt as _NamedInt,
NamedInts as _NamedInts,
bytes2int as _bytes2int,
int2bytes as _int2bytes,
)
#
#
#
KIND = _NamedInts(toggle=0x01, choice=0x02, range=0x12)
KIND = _NamedInts(toggle=0x01, choice=0x02, range=0x04)
class Setting(object):
"""A setting descriptor.
Needs to be instantiated for each specific device."""
__slots__ = ('name', 'label', 'description', 'kind', 'persister', 'device_kind',
__slots__ = ('name', 'label', 'description', 'kind', 'device_kind',
'_rw', '_validator', '_device', '_value')
def __init__(self, name, rw, validator, kind=None, label=None, description=None, device_kind=None):
@@ -56,11 +57,11 @@ class Setting(object):
assert kind is None or kind & validator.kind != 0
self.kind = kind or validator.kind
self.persister = None
def __call__(self, device):
assert not hasattr(self, '_value')
assert self.device_kind is None or self.device_kind == device.kind
# combined keyboards and touchpads (e.g., K400) break this assertion so don't use it
# assert self.device_kind is None or device.kind in self.device_kind
p = device.protocol
if p == 1.0:
# HID++ 1.0 devices do not support features
@@ -81,30 +82,41 @@ class Setting(object):
return self._validator.choices if self._validator.kind & KIND.choice else None
@property
def range(self):
assert hasattr(self, '_value')
assert hasattr(self, '_device')
if self._validator.kind == KIND.range:
return (self._validator.min_value, self._validator.max_value)
def read(self, cached=True):
assert hasattr(self, '_value')
assert hasattr(self, '_device')
if self._value is None and self.persister:
if _log.isEnabledFor(_DEBUG):
_log.debug("%s: settings read %r from %s", self.name, self._value, self._device)
if self._value is None and self._device.persister:
# We haven't read a value from the device yet,
# maybe we have something in the configuration.
self._value = self.persister.get(self.name)
self._value = self._device.persister.get(self.name)
if cached and self._value is not None:
if self.persister and self.name not in self.persister:
if self._device.persister and self.name not in self._device.persister:
# If this is a new device (or a new setting for an old device),
# make sure to save its current value for the next time.
self.persister[self.name] = self._value
self._device.persister[self.name] = self._value
return self._value
if self._device.online:
reply = self._rw.read(self._device)
if reply:
self._value = self._validator.validate_read(reply)
if self.persister and self.name not in self.persister:
if self._device.persister and self.name not in self._device.persister:
# Don't update the persister if it already has a value,
# otherwise the first read might overwrite the value we wanted.
self.persister[self.name] = self._value
self._device.persister[self.name] = self._value
return self._value
def write(self, value):
@@ -113,15 +125,15 @@ class Setting(object):
assert value is not None
if _log.isEnabledFor(_DEBUG):
_log.debug("%s: write %r to %s", self.name, value, self._device)
_log.debug("%s: settings write %r to %s", self.name, value, self._device)
if self._device.online:
# Remember the value we're trying to set, even if the write fails.
# This way even if the device is offline or some other error occurs,
# the last value we've tried to write is remembered in the configuration.
self._value = value
if self.persister:
self.persister[self.name] = value
if self._device.persister:
self._device.persister[self.name] = value
current_value = None
if self._validator.needs_current_value:
@@ -131,7 +143,7 @@ class Setting(object):
data_bytes = self._validator.prepare_write(value, current_value)
if data_bytes is not None:
if _log.isEnabledFor(_DEBUG):
_log.debug("%s: prepare write(%s) => %r", self.name, value, data_bytes)
_log.debug("%s: settings prepare write(%s) => %r", self.name, value, data_bytes)
reply = self._rw.write(self._device, data_bytes)
if not reply:
@@ -291,17 +303,17 @@ class BooleanValidator(object):
if current_value is not None and to_write == ord(current_value[:1]):
return None
else:
to_write = list(to_write)
to_write = bytearray(to_write)
count = len(self.mask)
for i in range(0, count):
b = ord(to_write[i])
b = ord(to_write[i:i+1])
m = ord(self.mask[i : i + 1])
assert b & m == b
# b &= m
if current_value is not None and self.needs_current_value:
b |= ord(current_value[i : i + 1]) & (0xFF ^ m)
to_write[i] = chr(b)
to_write = b''.join(to_write)
to_write[i] = b
to_write = bytes(to_write)
if current_value is not None and to_write == current_value[:len(to_write)]:
return None
@@ -317,7 +329,11 @@ class ChoicesValidator(object):
kind = KIND.choice
def __init__(self, choices):
"""Translates between NamedInts and a byte sequence.
:param choices: a list of NamedInts
:param bytes_count: the size of the derived byte sequence. If None, it
will be calculated from the choices."""
def __init__(self, choices, bytes_count=None):
assert choices is not None
assert isinstance(choices, _NamedInts)
assert len(choices) > 2
@@ -326,6 +342,9 @@ class ChoicesValidator(object):
max_bits = max(x.bit_length() for x in choices)
self._bytes_count = (max_bits // 8) + (1 if max_bits % 8 else 0)
if bytes_count:
assert self._bytes_count <= bytes_count
self._bytes_count = bytes_count
assert self._bytes_count < 8
def validate_read(self, reply_bytes):
@@ -340,6 +359,8 @@ class ChoicesValidator(object):
else:
if isinstance(new_value, int):
choice = self.choices[new_value]
elif int(new_value) in self.choices:
choice = self.choices[int(new_value)]
elif new_value in self.choices:
choice = self.choices[new_value]
else:
@@ -349,3 +370,36 @@ class ChoicesValidator(object):
raise ValueError("invalid choice %r" % new_value)
assert isinstance(choice, _NamedInt)
return choice.bytes(self._bytes_count)
class RangeValidator(object):
__slots__ = ('min_value', 'max_value', 'flag', '_bytes_count', 'needs_current_value')
kind = KIND.range
"""Translates between integers and a byte sequence.
:param min_value: minimum accepted value (inclusive)
:param max_value: maximum accepted value (inclusive)
:param bytes_count: the size of the derived byte sequence. If None, it
will be calculated from the range."""
def __init__(self, min_value, max_value, bytes_count=None):
assert max_value > min_value
self.min_value = min_value
self.max_value = max_value
self.needs_current_value = False
self._bytes_count = math.ceil(math.log(max_value + 1, 256))
if bytes_count:
assert self._bytes_count <= bytes_count
self._bytes_count = bytes_count
assert self._bytes_count < 8
def validate_read(self, reply_bytes):
reply_value = _bytes2int(reply_bytes[:self._bytes_count])
assert reply_value >= self.min_value, "%s: failed to validate read value %02X" % (self.__class__.__name__, reply_value)
assert reply_value <= self.max_value, "%s: failed to validate read value %02X" % (self.__class__.__name__, reply_value)
return reply_value
def prepare_write(self, new_value, current_value=None):
if new_value < self.min_value or new_value > self.max_value:
raise ValueError("invalid choice %r" % new_value)
return _int2bytes(new_value, self._bytes_count)

View File

@@ -19,10 +19,19 @@
from __future__ import absolute_import, division, print_function, unicode_literals
from logging import getLogger, DEBUG as _DEBUG
_log = getLogger(__name__)
del getLogger
from .i18n import _
from . import hidpp10 as _hidpp10
from . import hidpp20 as _hidpp20
from .common import (
bytes2int as _bytes2int,
int2bytes as _int2bytes,
NamedInts as _NamedInts,
unpack as _unpack,
)
from .settings import (
KIND as _KIND,
Setting as _Setting,
@@ -30,6 +39,7 @@ from .settings import (
FeatureRW as _FeatureRW,
BooleanValidator as _BooleanV,
ChoicesValidator as _ChoicesV,
RangeValidator as _RangeV,
)
_DK = _hidpp10.DEVICE_KIND
@@ -70,16 +80,61 @@ def feature_toggle(name, feature,
rw = _FeatureRW(feature, read_function_id, write_function_id)
return _Setting(name, rw, validator, label=label, description=description, device_kind=device_kind)
def feature_choices(name, feature, choices,
read_function_id, write_function_id,
bytes_count=None,
label=None, description=None, device_kind=None):
assert choices
validator = _ChoicesV(choices, bytes_count=bytes_count)
rw = _FeatureRW(feature, read_function_id, write_function_id)
return _Setting(name, rw, validator, kind=_KIND.choice, label=label, description=description, device_kind=device_kind)
def feature_choices_dynamic(name, feature, choices_callback,
read_function_id, write_function_id,
bytes_count=None,
label=None, description=None, device_kind=None):
# Proxy that obtains choices dynamically from a device
def instantiate(device):
# Obtain choices for this feature
choices = choices_callback(device)
setting = feature_choices(name, feature, choices,
read_function_id, write_function_id,
bytes_count=bytes_count,
label=label, description=description, device_kind=device_kind)
return setting(device)
return instantiate
def feature_range(name, feature, min_value, max_value,
read_function_id=_FeatureRW.default_read_fnid,
write_function_id=_FeatureRW.default_write_fnid,
rw=None,
bytes_count=None,
label=None, description=None, device_kind=None):
validator = _RangeV(min_value, max_value, bytes_count=bytes_count)
if rw is None:
rw = _FeatureRW(feature, read_function_id, write_function_id)
return _Setting(name, rw, validator, kind=_KIND.range, label=label, description=description, device_kind=device_kind)
#
# common strings for settings
#
_SMOOTH_SCROLL = ('smooth-scroll', _("Smooth Scrolling"),
_("High-sensitivity mode for vertical scroll with the wheel."))
_LOW_RES_SCROLL = ('lowres-smooth-scroll', _("HID++ Scrolling"),
_("HID++ mode for vertical scroll with the wheel."))
_HI_RES_SCROLL = ('hi-res-scroll', _("High Resolution Scrolling"),
_("High-sensitivity mode for vertical scroll with the wheel."))
_HIRES_INV = ('hires-smooth-invert', _("High Resolution Wheel Invert"),
_("High-sensitivity wheel invert mode for vertical scroll."))
_HIRES_RES = ('hires-smooth-resolution', _("Wheel Resolution"),
_("High-sensitivity mode for vertical scroll with the wheel."))
_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.")
@@ -88,7 +143,12 @@ _FN_SWAP = ('fn-swap', _("Swap Fx function"),
"and you must hold the FN key to activate their special function."))
_HAND_DETECTION = ('hand-detection', _("Hand Detection"),
_("Turn on illumination when the hands hover over the keyboard."))
_BACKLIGHT = ('backlight', _("Backlight"),
_("Turn illumination on or off on keyboard."))
_SMART_SHIFT = ('smart-shift', _("Smart Shift"),
_("Automatically switch the mouse wheel between ratchet and freespin mode.\n"
"The mouse wheel is always free at 0, and always locked at 50"))
#
#
#
@@ -97,35 +157,155 @@ def _register_hand_detection(register=_R.keyboard_hand_detection,
true_value=b'\x00\x00\x00', false_value=b'\x00\x00\x30', mask=b'\x00\x00\xFF'):
return register_toggle(_HAND_DETECTION[0], register, true_value=true_value, false_value=false_value,
label=_HAND_DETECTION[1], description=_HAND_DETECTION[2],
device_kind=_DK.keyboard)
device_kind=(_DK.keyboard,))
def _register_fn_swap(register=_R.keyboard_fn_swap, true_value=b'\x00\x01', mask=b'\x00\x01'):
return register_toggle(_FN_SWAP[0], register, true_value=true_value, mask=mask,
label=_FN_SWAP[1], description=_FN_SWAP[2],
device_kind=_DK.keyboard)
device_kind=(_DK.keyboard,))
def _register_smooth_scroll(register=_R.mouse_button_flags, true_value=0x40, mask=0x40):
return register_toggle(_SMOOTH_SCROLL[0], register, true_value=true_value, mask=mask,
label=_SMOOTH_SCROLL[1], description=_SMOOTH_SCROLL[2],
device_kind=_DK.mouse)
device_kind=(_DK.mouse, _DK.trackball))
def _register_side_scroll(register=_R.mouse_button_flags, true_value=0x02, mask=0x02):
return register_toggle(_SIDE_SCROLL[0], register, true_value=true_value, mask=mask,
label=_SIDE_SCROLL[1], description=_SIDE_SCROLL[2],
device_kind=_DK.mouse)
device_kind=(_DK.mouse, _DK.trackball))
def _register_dpi(register=_R.mouse_dpi, choices=None):
return register_choices(_DPI[0], register, choices,
label=_DPI[1], description=_DPI[2],
device_kind=_DK.mouse)
device_kind=(_DK.mouse, _DK.trackball))
def _feature_fn_swap():
return feature_toggle(_FN_SWAP[0], _F.FN_INVERSION,
label=_FN_SWAP[1], description=_FN_SWAP[2],
device_kind=_DK.keyboard)
device_kind=(_DK.keyboard,))
def _feature_new_fn_swap():
return feature_toggle(_FN_SWAP[0], _F.NEW_FN_INVERSION,
label=_FN_SWAP[1], description=_FN_SWAP[2],
device_kind=(_DK.keyboard,))
def _feature_k375s_fn_swap():
return feature_toggle(_FN_SWAP[0], _F.K375S_FN_INVERSION,
label=_FN_SWAP[1], description=_FN_SWAP[2],
device_kind=(_DK.keyboard,))
# FIXME: This will enable all supported backlight settings, we should allow the users to select which settings they want to enable.
def _feature_backlight2():
return feature_toggle(_BACKLIGHT[0], _F.BACKLIGHT2,
label=_BACKLIGHT[1], description=_BACKLIGHT[2],
device_kind=(_DK.keyboard,))
def _feature_hi_res_scroll():
return feature_toggle(_HI_RES_SCROLL[0], _F.HI_RES_SCROLLING,
label=_HI_RES_SCROLL[1], description=_HI_RES_SCROLL[2],
device_kind=(_DK.mouse, _DK.trackball))
def _feature_lowres_smooth_scroll():
return feature_toggle(_LOW_RES_SCROLL[0], _F.LOWRES_WHEEL,
label=_LOW_RES_SCROLL[1], description=_LOW_RES_SCROLL[2],
device_kind=(_DK.mouse, _DK.trackball))
def _feature_hires_smooth_invert():
return feature_toggle(_HIRES_INV[0], _F.HIRES_WHEEL,
read_function_id=0x10,
write_function_id=0x20,
true_value=0x04, mask=0x04,
label=_HIRES_INV[1], description=_HIRES_INV[2],
device_kind=(_DK.mouse, _DK.trackball))
def _feature_hires_smooth_resolution():
return feature_toggle(_HIRES_RES[0], _F.HIRES_WHEEL,
read_function_id=0x10,
write_function_id=0x20,
true_value=0x02, mask=0x02,
label=_HIRES_RES[1], description=_HIRES_RES[2],
device_kind=(_DK.mouse, _DK.trackball))
def _feature_smart_shift():
_MIN_SMART_SHIFT_VALUE = 0
_MAX_SMART_SHIFT_VALUE = 50
class _SmartShiftRW(_FeatureRW):
def __init__(self, feature):
super(_SmartShiftRW, self).__init__(feature)
def read(self, device):
value = super(_SmartShiftRW, self).read(device)
if _bytes2int(value[0:1]) == 1:
# Mode = Freespin, map to minimum
return _int2bytes(_MIN_SMART_SHIFT_VALUE, count=1)
else:
# Mode = smart shift, map to the value, capped at maximum
threshold = min(_bytes2int(value[1:2]), _MAX_SMART_SHIFT_VALUE)
return _int2bytes(threshold, count=1)
def write(self, device, data_bytes):
threshold = _bytes2int(data_bytes)
# Freespin at minimum
mode = 1 if threshold == _MIN_SMART_SHIFT_VALUE else 2
# Ratchet at maximum
if threshold == _MAX_SMART_SHIFT_VALUE:
threshold = 255
data = _int2bytes(mode, count=1) + _int2bytes(threshold, count=1) * 2
return super(_SmartShiftRW, self).write(device, data)
return feature_range(_SMART_SHIFT[0], _F.SMART_SHIFT,
_MIN_SMART_SHIFT_VALUE, _MAX_SMART_SHIFT_VALUE,
bytes_count=1,
rw=_SmartShiftRW(_F.SMART_SHIFT),
label=_SMART_SHIFT[1], description=_SMART_SHIFT[2],
device_kind=(_DK.mouse, _DK.trackball))
def _feature_adjustable_dpi_choices(device):
# [1] getSensorDpiList(sensorIdx)
reply = device.feature_request(_F.ADJUSTABLE_DPI, 0x10)
# Should not happen, but might happen when the user unplugs device while the
# query is being executed. TODO retry logic?
assert reply, 'Oops, DPI list cannot be retrieved!'
dpi_list = []
step = None
for val in _unpack('!7H', reply[1:1+14]):
if val == 0:
break
if val >> 13 == 0b111:
assert step is None and len(dpi_list) == 1, \
'Invalid DPI list item: %r' % val
step = val & 0x1fff
else:
dpi_list.append(val)
if step:
assert len(dpi_list) == 2, 'Invalid DPI list range: %r' % dpi_list
dpi_list = range(dpi_list[0], dpi_list[1] + 1, step)
return _NamedInts.list(dpi_list)
def _feature_adjustable_dpi():
"""Pointer Speed feature"""
# Assume sensorIdx 0 (there is only one sensor)
# [2] getSensorDpi(sensorIdx) -> sensorIdx, dpiMSB, dpiLSB
# [3] setSensorDpi(sensorIdx, dpi)
return feature_choices_dynamic(_DPI[0], _F.ADJUSTABLE_DPI,
_feature_adjustable_dpi_choices,
read_function_id=0x20,
write_function_id=0x30,
bytes_count=3,
label=_DPI[1], description=_DPI[2],
device_kind=(_DK.mouse, _DK.trackball))
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, _DK.trackball))
#
#
#
@@ -133,29 +313,56 @@ def _feature_fn_swap():
from collections import namedtuple
_SETTINGS_LIST = namedtuple('_SETTINGS_LIST', [
'fn_swap',
'new_fn_swap',
'k375s_fn_swap',
'smooth_scroll',
'hi_res_scroll',
'lowres_smooth_scroll',
'hires_smooth_invert',
'hires_smooth_resolution',
'side_scroll',
'dpi',
'pointer_speed',
'hand_detection',
'backlight',
'typing_illumination',
'smart_shift',
])
del namedtuple
RegisterSettings = _SETTINGS_LIST(
fn_swap=_register_fn_swap,
new_fn_swap=None,
k375s_fn_swap=None,
smooth_scroll=_register_smooth_scroll,
hi_res_scroll=None,
lowres_smooth_scroll=None,
hires_smooth_invert=None,
hires_smooth_resolution=None,
side_scroll=_register_side_scroll,
dpi=_register_dpi,
pointer_speed=None,
hand_detection=_register_hand_detection,
backlight=None,
typing_illumination=None,
smart_shift=None,
)
FeatureSettings = _SETTINGS_LIST(
fn_swap=_feature_fn_swap,
new_fn_swap=_feature_new_fn_swap,
k375s_fn_swap=_feature_k375s_fn_swap,
smooth_scroll=None,
hi_res_scroll=_feature_hi_res_scroll,
lowres_smooth_scroll=_feature_lowres_smooth_scroll,
hires_smooth_invert=_feature_hires_smooth_invert,
hires_smooth_resolution=_feature_hires_smooth_resolution,
side_scroll=None,
dpi=None,
dpi=_feature_adjustable_dpi,
pointer_speed=_feature_pointer_speed,
hand_detection=None,
backlight=_feature_backlight2,
typing_illumination=None,
smart_shift=_feature_smart_shift,
)
del _SETTINGS_LIST
@@ -166,10 +373,44 @@ del _SETTINGS_LIST
def check_feature_settings(device, already_known):
"""Try to auto-detect device settings by the HID++ 2.0 features they have."""
if device.features is None:
if device.features is None or not device.online:
return
if device.protocol and device.protocol < 2.0:
return
if not any(s.name == _FN_SWAP[0] for s in already_known) and _F.FN_INVERSION in device.features:
fn_swap = FeatureSettings.fn_swap()
already_known.append(fn_swap(device))
def check_feature(name, featureId, field_name=None):
"""
:param name: user-visible setting name.
:param featureId: the numeric Feature ID for this setting.
:param field_name: override the FeatureSettings name if it is
different from the user-visible setting name. Useful if there
are multiple features for the same setting.
"""
if not featureId in device.features:
return
if any(s.name == name for s in already_known):
return
if not field_name:
# Convert user-visible settings name for FeatureSettings
field_name = name.replace('-', '_')
feature = getattr(FeatureSettings, field_name)()
try:
detected = feature(device)
if _log.isEnabledFor(_DEBUG):
_log.debug("check_feature[%s] detected %s", featureId, detected)
already_known.append(detected)
except Exception as reason:
_log.error("check_feature[%s] inconsistent feature %s", featureId, reason)
check_feature(_HI_RES_SCROLL[0], _F.HI_RES_SCROLLING)
check_feature(_LOW_RES_SCROLL[0], _F.LOWRES_WHEEL)
check_feature(_HIRES_INV[0], _F.HIRES_WHEEL, "hires_smooth_invert")
check_feature(_HIRES_RES[0], _F.HIRES_WHEEL, "hires_smooth_resolution")
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.K375S_FN_INVERSION, 'k375s_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)
check_feature(_BACKLIGHT[0], _F.BACKLIGHT2)

View File

@@ -322,10 +322,24 @@ TASK = _NamedInts(
MetroForwRightHorz=0x0083,
Win8_Back=0x0084, # also known as MetroCharms
Win8_Forward=0x0085, # also known as AppSwitchBar
Win8Charm_Appswitch_GifAnimation=0x0086,
Win8BackHorzLeft=0x008B, # also known as Back
Win8ForwardHorzRight=0x008C, # also known as BrowserForward
MetroSearch2=0x0087,
MetroShare2=0x0088,
MetroSettings2=0x008A,
MetroDevices2=0x0089,
Win8MetroWin7Forward=0x008D, # also known as MetroStartScreen
Win8ShowDesktopWin7Back=0x008E, # also known as ShowDesktop
MetroApplicationSwitch=0x0090, # also known as MetroStartScreen
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,

View File

@@ -26,7 +26,7 @@ _log = getLogger(__name__)
del getLogger
from .i18n import _
from .i18n import _, ngettext
from .common import NamedInts as _NamedInts, NamedInt as _NamedInt
from . import hidpp10 as _hidpp10
from . import hidpp20 as _hidpp20
@@ -54,7 +54,7 @@ KEYS = _NamedInts(
_BATTERY_ATTENTION_LEVEL = 5
# If no updates have been receiver from the device for a while, ping the device
# and update it status accordinly.
# and update it status accordingly.
# _STATUS_TIMEOUT = 5 * 60 # seconds
_LONG_SLEEP = 15 * 60 # seconds
@@ -66,10 +66,11 @@ def attach_to(device, changed_callback):
assert device
assert changed_callback
if device.kind is None:
device.status = ReceiverStatus(device, changed_callback)
else:
device.status = DeviceStatus(device, changed_callback)
if not hasattr(device, 'status') or device.status is None:
if device.kind is None:
device.status = ReceiverStatus(device, changed_callback)
else:
device.status = DeviceStatus(device, changed_callback)
#
#
@@ -96,8 +97,7 @@ class ReceiverStatus(dict):
def __str__(self):
count = len(self._receiver)
return (_("No paired devices.") if count == 0 else
_("1 paired device.") if count == 1 else
(str(count) + _(" paired devices.")))
ngettext("%(count)s paired device.", "%(count)s paired devices.", count) % { 'count': count })
__unicode__ = __str__
def changed(self, alert=ALERT.NOTIFICATION, reason=None):
@@ -139,34 +139,32 @@ class DeviceStatus(dict):
# timestamp of when this status object was last updated
self.updated = 0
def __str__(self):
def _item(name, format):
value = self.get(name)
if value is not None:
return format % value
def to_string(self):
def _items():
# TODO properly string approximative battery levels
comma = False
battery_level = self.get(KEYS.BATTERY_LEVEL)
if battery_level is not None:
if isinstance(battery_level, _NamedInt):
yield _("Battery") + ': ' + _(str(battery_level))
yield _("Battery: %(level)s") % { 'level': _(str(battery_level)) }
else:
yield _("Battery") + ': ' + ('%d%%' % battery_level)
yield _("Battery: %(percent)d%%") % { 'percent': battery_level }
battery_status = _item(KEYS.BATTERY_STATUS, ' (%s)')
if battery_status:
yield battery_status
battery_status = self.get(KEYS.BATTERY_STATUS)
if battery_status is not None:
yield ' (%s)' % _(str(battery_status))
light_level = _item(KEYS.LIGHT_LEVEL, _("Lighting") + ': %d ' + _("lux"))
if light_level:
if battery_level:
yield ', '
yield light_level
comma = True
light_level = self.get(KEYS.LIGHT_LEVEL)
if light_level is not None:
if comma: yield ', '
yield _("Lighting: %(level)s lux") % { 'level': light_level }
return ''.join(i for i in _items())
__unicode__ = __str__
def __repr__(self):
return '{' + ', '.join('\'%s\': %r' % (k, v) for k, v in self.items()) + '}'
def __bool__(self):
return bool(self._active)
@@ -178,8 +176,16 @@ class DeviceStatus(dict):
if level is None:
# Some notifications may come with no battery level info, just
# charging state info, so assume the level is unchanged.
level = self.get(KEYS.BATTERY_LEVEL)
# charging state info, so do our best to infer a level (even if it is just the last level)
# It is not always possible to do this well
if status == _hidpp20.BATTERY_STATUS.full:
level = _hidpp10.BATTERY_APPOX.full
elif status in (_hidpp20.BATTERY_STATUS.almost_full, _hidpp20.BATTERY_STATUS.recharging):
level = _hidpp10.BATTERY_APPOX.good
elif status == _hidpp20.BATTERY_STATUS.slow_recharge:
level = _hidpp10.BATTERY_APPOX.low
else:
level = self.get(KEYS.BATTERY_LEVEL)
else:
assert isinstance(level, int)
@@ -187,13 +193,14 @@ class DeviceStatus(dict):
old_level, self[KEYS.BATTERY_LEVEL] = self.get(KEYS.BATTERY_LEVEL), level
old_status, self[KEYS.BATTERY_STATUS] = self.get(KEYS.BATTERY_STATUS), status
charging = status in (_hidpp20.BATTERY_STATUS.recharging, _hidpp20.BATTERY_STATUS.slow_recharge)
charging = status in (_hidpp20.BATTERY_STATUS.recharging, _hidpp20.BATTERY_STATUS.almost_full,
_hidpp20.BATTERY_STATUS.full, _hidpp20.BATTERY_STATUS.slow_recharge)
old_charging, self[KEYS.BATTERY_CHARGING] = self.get(KEYS.BATTERY_CHARGING), charging
changed = old_level != level or old_status != status or old_charging != charging
alert, reason = ALERT.NONE, None
if _hidpp20.BATTERY_OK(status) and level > _BATTERY_ATTENTION_LEVEL:
if _hidpp20.BATTERY_OK(status) and ( level is None or level > _BATTERY_ATTENTION_LEVEL ):
self[KEYS.ERROR] = None
else:
_log.warn("%s: battery %d%%, ALERT %s", self._device, level, status)
@@ -202,9 +209,9 @@ class DeviceStatus(dict):
# only show the notification once
alert = ALERT.NOTIFICATION | ALERT.ATTENTION
if isinstance(level, _NamedInt):
reason = 'battery: %s (%s)' % (level, status)
reason = _("Battery: %(level)s (%(status)s)") % { 'level': _(level), 'status': _(status) }
else:
reason = 'battery: %d%% (%s)' % (level, status)
reason = _("Battery: %(percent)d%% (%(status)s)") % { 'percent': level, 'status': status.name }
if changed or reason:
# update the leds on the device, if any
@@ -225,7 +232,7 @@ class DeviceStatus(dict):
# broadcasting it's battery status anyway, it will just take a little while.
# However, when the device has just been detected, it will not show
# any battery status for a while (broadcasts happen every 90 seconds).
if battery is None and _hidpp20.FEATURE.SOLAR_DASHBOARD in d.features:
if battery is None and d.features and _hidpp20.FEATURE.SOLAR_DASHBOARD in d.features:
d.feature_request(_hidpp20.FEATURE.SOLAR_DASHBOARD, 0x00, 1, 1)
return
@@ -263,7 +270,8 @@ class DeviceStatus(dict):
# Devices lose configuration when they are turned off,
# make sure they're up-to-date.
# _log.debug("%s settings %s", d, d.settings)
if _log.isEnabledFor(_DEBUG):
_log.debug("%s pushing device settings %s", d, d.settings)
for s in d.settings:
s.apply()

View File

@@ -19,5 +19,5 @@
from __future__ import absolute_import, division, print_function, unicode_literals
__version__ = '0.9.2'
__version__ = '1.0.2'
NAME = 'Solaar'

View File

@@ -1,434 +0,0 @@
#!/usr/bin/env python
# -*- python-mode -*-
# -*- coding: UTF-8 -*-
## Copyright (C) 2012-2013 Daniel Pavel
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from __future__ import absolute_import, division, print_function, unicode_literals
import sys
import logging
NAME = 'solaar-cli'
from solaar import __version__
#
#
#
def _fail(text):
if sys.exc_info()[0]:
logging.exception(text)
sys.exit("%s: error: %s" % (NAME, text))
def _require(module, os_package):
try:
__import__(module)
except ImportError:
_fail("missing required package '%s'" % os_package)
#
#
#
def _receiver(dev_path=None):
from logitech_receiver import Receiver
from logitech_receiver.base import receivers
for dev_info in receivers():
if dev_path is not None and dev_path != dev_info.path:
continue
try:
r = Receiver.open(dev_info)
if r:
return r
except Exception as e:
_fail(str(e))
return r
_fail("Logitech receiver not found")
def _find_device(receiver, name, may_be_receiver=False):
if len(name) == 1:
try:
number = int(name)
except:
pass
else:
if number < 1 or number > receiver.max_devices:
_fail("%s (%s) supports device numbers 1 to %d" % (receiver.name, receiver.path, receiver.max_devices))
dev = receiver[number]
if dev is None:
_fail("no paired device with number %s" % number)
return dev
if len(name) < 3:
_fail("need at least 3 characters to match a device")
name = name.lower()
if may_be_receiver and ('receiver'.startswith(name) or name == receiver.serial.lower()):
return receiver
for dev in receiver:
if (name == dev.serial.lower() or
name == dev.codename.lower() or
name == str(dev.kind).lower() or
name in dev.name.lower()):
return dev
_fail("no device found matching '%s'" % name)
def _print_receiver(receiver, verbose=False):
paired_count = receiver.count()
if not verbose:
print ("Unifying Receiver [%s:%s] with %d devices" % (receiver.path, receiver.serial, paired_count))
return
print ("Unifying Receiver")
print (" Device path :", receiver.path)
print (" USB id : 046d:%s" % receiver.product_id)
print (" Serial :", receiver.serial)
for f in receiver.firmware:
print (" %-11s: %s" % (f.kind, f.version))
print (" Has", paired_count, "paired device(s) out of a maximum of", receiver.max_devices, ".")
from logitech_receiver import hidpp10
notification_flags = hidpp10.get_notification_flags(receiver)
if notification_flags is not None:
if notification_flags:
notification_names = hidpp10.NOTIFICATION_FLAG.flag_names(notification_flags)
print (" Notifications: 0x%06X = %s" % (notification_flags, ', '.join(notification_names)))
else:
print (" Notifications: (none)")
activity = receiver.read_register(hidpp10.REGISTERS.devices_activity)
if activity:
activity = [(d, ord(activity[d - 1:d])) for d in range(1, receiver.max_devices)]
activity_text = ', '.join(('%d=%d' % (d, a)) for d, a in activity if a > 0)
print (" Device activity counters:", activity_text or '(empty)')
def _print_device(dev, verbose=False):
assert dev
state = '' if dev.ping() else 'offline'
if not verbose:
print ("%d: %s [%s:%s]" % (dev.number, dev.name, dev.codename, dev.serial), state)
return
print ("%d: %s" % (dev.number, dev.name))
print (" Codename :", dev.codename)
print (" Kind :", dev.kind)
print (" Wireless PID :", dev.wpid)
if dev.protocol:
print (" Protocol : HID++ %1.1f" % dev.protocol)
else:
print (" Protocol : unknown (device is offline)")
print (" Polling rate :", dev.polling_rate, "ms")
print (" Serial number:", dev.serial)
for fw in dev.firmware:
print (" %11s:" % fw.kind, (fw.name + ' ' + fw.version).strip())
if dev.power_switch_location:
print (" The power switch is located on the %s." % dev.power_switch_location)
from logitech_receiver import hidpp10, hidpp20, special_keys
if dev.online:
notification_flags = hidpp10.get_notification_flags(dev)
if notification_flags is not None:
if notification_flags:
notification_names = hidpp10.NOTIFICATION_FLAG.flag_names(notification_flags)
print (" Notifications: 0x%06X = %s." % (notification_flags, ', '.join(notification_names)))
else:
print (" Notifications: (none).")
if dev.online:
if dev.features:
print (" Supports %d HID++ 2.0 features:" % len(dev.features))
for index, feature in enumerate(dev.features):
feature = dev.features[index]
flags = dev.request(0x0000, feature.bytes(2))
flags = 0 if flags is None else ord(flags[1:2])
flags = hidpp20.FEATURE_FLAG.flag_names(flags)
print (" %2d: %-22s {%04X} %s" % (index, feature, feature, ', '.join(flags)))
if dev.online:
if dev.keys:
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)))
if dev.online:
battery = hidpp20.get_battery(dev)
if battery is None:
battery = hidpp10.get_battery(dev)
if battery is not None:
from logitech_receiver.common import NamedInt as _NamedInt
level, status = battery
if isinstance(level, _NamedInt):
text = str(level)
else:
text = '%d%%' % level
print (" Battery: %s, %s," % (text, status))
else:
print (" Battery status unavailable.")
else:
print (" Battery status is unknown (device is offline).")
#
#
#
def show_devices(receiver, args):
if args.device == 'all':
_print_receiver(receiver, args.verbose)
for dev in receiver:
if args.verbose:
print ("")
_print_device(dev, args.verbose)
else:
dev = _find_device(receiver, args.device, True)
if dev is receiver:
_print_receiver(receiver, args.verbose)
else:
_print_device(dev, args.verbose)
def pair_device(receiver, args):
# get all current devices
known_devices = [dev.number for dev in receiver]
from logitech_receiver import base, hidpp10, status, notifications
receiver.status = status.ReceiverStatus(receiver, lambda *args, **kwargs: None)
# check if it's necessary to set the notification flags
old_notification_flags = hidpp10.get_notification_flags(receiver) or 0
if not (old_notification_flags & hidpp10.NOTIFICATION_FLAG.wireless):
hidpp10.set_notification_flags(receiver, old_notification_flags | hidpp10.NOTIFICATION_FLAG.wireless)
class HandleWithNotificationHook(int):
def notifications_hook(self, n):
assert n
if n.devnumber == 0xFF:
notifications.process(receiver, n)
elif n.sub_id == 0x41 and n.address == 0x04:
if n.devnumber not in known_devices:
receiver.status.new_device = receiver[n.devnumber]
timeout = 20 # seconds
receiver.handle = HandleWithNotificationHook(receiver.handle)
receiver.set_lock(False, timeout=timeout)
print ("Pairing: turn your new device on (timing out in", timeout, "seconds).")
# the lock-open notification may come slightly later, wait for it a bit
from time import time as timestamp
pairing_start = timestamp()
patience = 5 # seconds
while receiver.status.lock_open or timestamp() - pairing_start < patience:
n = base.read(receiver.handle)
if n:
n = base.make_notification(*n)
if n:
receiver.handle.notifications_hook(n)
if not (old_notification_flags & hidpp10.NOTIFICATION_FLAG.wireless):
# only clear the flags if they weren't set before, otherwise a
# concurrently running Solaar app might stop working properly
hidpp10.set_notification_flags(receiver, old_notification_flags)
if receiver.status.new_device:
dev = receiver.status.new_device
print ("Paired device %d: %s [%s:%s:%s]" % (dev.number, dev.name, dev.wpid, dev.codename, dev.serial))
else:
error = receiver.status[status.KEYS.ERROR] or 'no device detected?'
_fail(error)
def unpair_device(receiver, args):
dev = _find_device(receiver, args.device)
# query these now, it's last chance to get them
number, name, codename, serial = dev.number, dev.name, dev.codename, dev.serial
try:
del receiver[number]
print ("Unpaired %d: %s [%s:%s]" % (number, name, codename, serial))
except Exception as e:
_fail("failed to unpair device %s: %s" % (dev.name, e))
def config_device(receiver, args):
dev = _find_device(receiver, args.device)
# if dev is receiver:
# _fail("no settings for the receiver")
if not dev.settings:
_fail("no settings for %s" % dev.name)
if not args.setting:
print ("[%s:%s]" % (dev.serial, dev.kind))
print ("#", dev.name)
for s in dev.settings:
print ("")
print ("# %s" % s.label)
if s.choices:
print ("# possible values: one of [", ', '.join(str(v) for v in s.choices), "], or higher/lower/highest/max/lowest/min")
else:
print ("# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0")
value = s.read()
if value is None:
print ("# %s = ? (failed to read from device)" % s.name)
else:
print (s.name, "=", value)
return
setting = None
for s in dev.settings:
if args.setting.lower() == s.name.lower():
setting = s
break
if setting is None:
_fail("no setting '%s' for %s" % (args.setting, dev.name))
if args.value is None:
result = setting.read()
if result is None:
_fail("failed to read '%s'" % setting.name)
print ("%s = %s" % (setting.name, setting.read()))
return
from logitech_receiver import settings as _settings
if setting.kind == _settings.KIND.toggle:
value = args.value
try:
value = bool(int(value))
except:
if value.lower() in ('1', 'true', 'yes', 'on', 't', 'y'):
value = True
elif value.lower() in ('0', 'false', 'no', 'off', 'f', 'n'):
value = False
else:
_fail("don't know how to interpret '%s' as boolean" % value)
elif setting.choices:
value = args.value.lower()
if value in ('higher', 'lower'):
old_value = setting.read()
if old_value is None:
_fail("could not read current value of '%s'" % setting.name)
if value == 'lower':
lower_values = setting.choices[:old_value]
value = lower_values[-1] if lower_values else setting.choices[:][0]
elif value == 'higher':
higher_values = setting.choices[old_value + 1:]
value = higher_values[0] if higher_values else setting.choices[:][-1]
elif value in ('highest', 'max'):
value = setting.choices[:][-1]
elif value in ('lowest', 'min'):
value = setting.choices[:][0]
elif value not in setting.choices:
_fail("possible values for '%s' are: [%s]" % (setting.name, ', '.join(str(v) for v in setting.choices)))
value = setting.choices[value]
else:
raise NotImplemented
result = setting.write(value)
if result is None:
_fail("failed to set '%s' = '%s' [%r]" % (setting.name, value, value))
print ("%s = %s" % (setting.name, result))
#
#
#
def _parse_arguments():
from argparse import ArgumentParser
arg_parser = ArgumentParser(prog=NAME.lower())
arg_parser.add_argument('-d', '--debug', action='count', default=0,
help='print logging messages, for debugging purposes (may be repeated for extra verbosity)')
arg_parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__)
arg_parser.add_argument('-D', '--hidraw', action='store', dest='hidraw_path', metavar='PATH',
help='unifying receiver to use; the first detected receiver if unspecified. Example: /dev/hidraw2')
subparsers = arg_parser.add_subparsers(title='commands')
sp = subparsers.add_parser('show', help='show information about paired devices')
sp.add_argument('device', nargs='?', default='all',
help='device to show information about; may be a device number (1..6), a device serial, '
'at least 3 characters of a device\'s name, "receiver", or "all" (the default)')
sp.add_argument('-v', '--verbose', action='store_true',
help='print all available information about the inspected device(s)')
sp.set_defaults(cmd=show_devices)
sp = subparsers.add_parser('config', help='read/write device-specific settings',
epilog='Please note that configuration only works on active devices.')
sp.add_argument('device',
help='device to configure; may be a device number (1..6), a device serial, '
'or at least 3 characters of a device\'s name')
sp.add_argument('setting', nargs='?',
help='device-specific setting; leave empty to list available settings')
sp.add_argument('value', nargs='?',
help='new value for the setting')
sp.set_defaults(cmd=config_device)
sp = subparsers.add_parser('pair', help='pair a new device',
epilog='The Logitech Unifying Receiver supports up to 6 paired devices at the same time.')
sp.set_defaults(cmd=pair_device)
sp = subparsers.add_parser('unpair', help='unpair a device')
sp.add_argument('device',
help='device to unpair; may be a device number (1..6), a device serial, '
'or at least 3 characters of a device\'s name.')
sp.set_defaults(cmd=unpair_device)
args = arg_parser.parse_args()
# Python 3 has an undocumented 'feature' that breaks parsing empty args
# http://bugs.python.org/issue16308
if not 'cmd' in args:
arg_parser.print_usage(sys.stderr)
sys.stderr.write('%s: error: too few arguments\n' % NAME.lower())
sys.exit(2)
if args.debug > 0:
log_level = logging.WARNING - 10 * args.debug
log_format='%(asctime)s,%(msecs)03d %(levelname)8s %(name)s: %(message)s'
logging.basicConfig(level=max(log_level, logging.DEBUG), format=log_format, datefmt='%H:%M:%S')
else:
logging.root.addHandler(logging.NullHandler())
logging.root.setLevel(logging.ERROR)
return args
def main():
_require('pyudev', 'python-pyudev')
args = _parse_arguments()
receiver = _receiver(args.hidraw_path)
args.cmd(receiver, args)
if __name__ == '__main__':
main()

168
lib/solaar/cli/__init__.py Normal file
View File

@@ -0,0 +1,168 @@
# -*- python-mode -*-
# -*- coding: UTF-8 -*-
## Copyright (C) 2012-2013 Daniel Pavel
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from __future__ import absolute_import, division, print_function, unicode_literals
import argparse as _argparse
import sys as _sys
from logging import getLogger, DEBUG as _DEBUG
_log = getLogger(__name__)
del getLogger
from solaar import NAME
#
#
#
def _create_parser():
parser = _argparse.ArgumentParser(prog=NAME.lower(), add_help=False,
epilog='For details on individual actions, run `%s <action> --help`.' % NAME.lower())
subparsers = parser.add_subparsers(title='actions',
help='optional action to perform')
sp = subparsers.add_parser('show', help='show information about devices')
sp.add_argument('device', nargs='?', default='all',
help='device to show information about; may be a device number (1..6), a serial, '
'a substring of a device\'s name, or "all" (the default)')
sp.set_defaults(action='show')
sp = subparsers.add_parser('probe', help='probe a receiver (debugging use only)')
sp.add_argument('receiver', nargs='?',
help='select a certain receiver when more than one is present')
sp.set_defaults(action='probe')
sp = subparsers.add_parser('config', help='read/write device-specific settings',
epilog='Please note that configuration only works on active devices.')
sp.add_argument('device',
help='device to configure; may be a device number (1..6), a device serial, '
'or at least 3 characters of a device\'s name')
sp.add_argument('setting', nargs='?',
help='device-specific setting; leave empty to list available settings')
sp.add_argument('value', nargs='?',
help='new value for the setting')
sp.set_defaults(action='config')
sp = subparsers.add_parser('pair', help='pair a new device',
epilog='The Logitech Unifying Receiver supports up to 6 paired devices at the same time.')
sp.add_argument('receiver', nargs='?',
help='select a certain receiver when more than one is present')
sp.set_defaults(action='pair')
sp = subparsers.add_parser('unpair', help='unpair a device')
sp.add_argument('device',
help='device to unpair; may be a device number (1..6), a serial, '
'or a substring of a device\'s name.')
sp.set_defaults(action='unpair')
return parser, subparsers.choices
_cli_parser, actions = _create_parser()
print_help = _cli_parser.print_help
def _receivers(dev_path=None):
from logitech_receiver import Receiver
from logitech_receiver.base import receivers
for dev_info in receivers():
if dev_path is not None and dev_path != dev_info.path:
continue
try:
r = Receiver.open(dev_info)
if _log.isEnabledFor(_DEBUG):
_log.debug("[%s] => %s", dev_info.path, r)
if r:
yield r
except Exception as e:
_log.exception('opening ' + str(dev_info))
_sys.exit("%s: error: %s" % (NAME, str(e)))
def _find_receiver(receivers, name):
assert receivers
assert name
for r in receivers:
if name in r.name.lower() or (r.serial is not None and name == r.serial.lower()):
return r
def _find_device(receivers, name):
assert receivers
assert name
number = None
if len(name) == 1:
try:
number = int(name)
except:
pass
else:
assert not (number < 0)
if number > 6: number = None
for r in receivers:
if number and number <= r.max_devices:
dev = r[number]
if dev:
return dev
for dev in r:
if (name == dev.serial.lower() or
name == dev.codename.lower() or
name == str(dev.kind).lower() or
name in dev.name.lower()):
return dev
raise Exception("no device found matching '%s'" % name)
def run(cli_args=None, hidraw_path=None):
if cli_args:
action = cli_args[0]
args = _cli_parser.parse_args(cli_args)
else:
args = _cli_parser.parse_args()
# Python 3 has an undocumented 'feature' that breaks parsing empty args
# http://bugs.python.org/issue16308
if not 'cmd' in args:
_cli_parser.print_usage(_sys.stderr)
_sys.stderr.write('%s: error: too few arguments\n' % NAME.lower())
_sys.exit(2)
action = args.action
assert action in actions
try:
c = list(_receivers(hidraw_path))
if not c:
raise Exception('Logitech receiver not found')
from importlib import import_module
m = import_module('.' + action, package=__name__)
m.run(c, args, _find_receiver, _find_device)
except AssertionError as e:
from traceback import extract_tb
tb_last = extract_tb(_sys.exc_info()[2])[-1]
_sys.exit('%s: assertion failed: %s line %d' % (NAME.lower(), tb_last[0], tb_last[1]))
except Exception as e:
_sys.exit('%s: error: %s' % (NAME.lower(), e))

127
lib/solaar/cli/config.py Normal file
View File

@@ -0,0 +1,127 @@
# -*- python-mode -*-
# -*- coding: UTF-8 -*-
## Copyright (C) 2012-2013 Daniel Pavel
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from __future__ import absolute_import, division, print_function, unicode_literals
from solaar import configuration as _configuration
from logitech_receiver import settings as _settings
def _print_setting(s, verbose=True):
print ('#', s.label)
if verbose:
if s.description:
print ('#', s.description.replace('\n', ' '))
if s.kind == _settings.KIND.toggle:
print ('# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0')
elif s.choices:
print ('# possible values: one of [', ', '.join(str(v) for v in s.choices), '], or higher/lower/highest/max/lowest/min')
else:
# wtf?
pass
value = s.read(cached=False)
if value is None:
print (s.name, '= ? (failed to read from device)')
else:
print (s.name, '= %r' % value)
def run(receivers, args, find_receiver, find_device):
assert receivers
assert args.device
device_name = args.device.lower()
dev = find_device(receivers, device_name)
if not dev.ping():
raise Exception('%s is offline' % dev.name)
if not dev.settings:
raise Exception('no settings for %s' % dev.name)
_configuration.attach_to(dev)
if not args.setting:
print (dev.name, '(%s) [%s:%s]' % (dev.codename, dev.wpid, dev.serial))
for s in dev.settings:
print ('')
_print_setting(s)
return
setting_name = args.setting.lower()
setting = None
for s in dev.settings:
if setting_name == s.name.lower():
setting = s
break
if setting is None:
raise Exception("no setting '%s' for %s" % (args.setting, dev.name))
if args.value is None:
_print_setting(setting)
return
if setting.kind == _settings.KIND.toggle:
value = args.value
try:
value = bool(int(value))
except:
if value.lower() in ('true', 'yes', 'on', 't', 'y'):
value = True
elif value.lower() in ('false', 'no', 'off', 'f', 'n'):
value = False
else:
raise Exception("don't know how to interpret '%s' as boolean" % value)
elif setting.choices:
value = args.value.lower()
if value in ('higher', 'lower'):
old_value = setting.read()
if old_value is None:
raise Exception("could not read current value of '%s'" % setting.name)
if value == 'lower':
lower_values = setting.choices[:old_value]
value = lower_values[-1] if lower_values else setting.choices[:][0]
elif value == 'higher':
higher_values = setting.choices[old_value + 1:]
value = higher_values[0] if higher_values else setting.choices[:][-1]
elif value in ('highest', 'max'):
value = setting.choices[:][-1]
elif value in ('lowest', 'min'):
value = setting.choices[:][0]
elif value not in setting.choices:
raise Exception("possible values for '%s' are: [%s]" % (setting.name, ', '.join(str(v) for v in setting.choices)))
value = setting.choices[value]
elif setting.kind == _settings.KIND.range:
try:
value = int(args.value)
except ValueError:
raise Exception("can't interpret '%s' as integer" % args.value)
else:
raise NotImplemented
result = setting.write(value)
if result is None:
raise Exception("failed to set '%s' = '%s' [%r]" % (setting.name, str(value), value))
_print_setting(setting, False)

98
lib/solaar/cli/pair.py Normal file
View File

@@ -0,0 +1,98 @@
# -*- python-mode -*-
# -*- coding: UTF-8 -*-
## Copyright (C) 2012-2013 Daniel Pavel
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from __future__ import absolute_import, division, print_function, unicode_literals
from time import time as _timestamp
from logitech_receiver import (
base as _base,
hidpp10 as _hidpp10,
status as _status,
notifications as _notifications,
)
def run(receivers, args, find_receiver, _ignore):
assert receivers
if args.receiver:
receiver_name = args.receiver.lower()
receiver = find_receiver(receiver_name)
if not receiver:
raise Exception("no receiver found matching '%s'" % receiver_name)
else:
receiver = receivers[0]
assert receiver
receiver.status = _status.ReceiverStatus(receiver, lambda *args, **kwargs: None)
# check if it's necessary to set the notification flags
old_notification_flags = _hidpp10.get_notification_flags(receiver) or 0
if not (old_notification_flags & _hidpp10.NOTIFICATION_FLAG.wireless):
_hidpp10.set_notification_flags(receiver, old_notification_flags | _hidpp10.NOTIFICATION_FLAG.wireless)
# get all current devices
known_devices = [dev.number for dev in receiver]
class _HandleWithNotificationHook(int):
def notifications_hook(self, n):
assert n
if n.devnumber == 0xFF:
_notifications.process(receiver, n)
elif n.sub_id == 0x41: # allow for other protocols! (was and n.address == 0x04)
if n.devnumber not in known_devices:
receiver.status.new_device = receiver[n.devnumber]
elif receiver.re_pairs:
del receiver[n.devnumber] # get rid of information on device re-paired away
receiver.status.new_device = receiver[n.devnumber]
timeout = 20 # seconds
receiver.handle = _HandleWithNotificationHook(receiver.handle)
receiver.set_lock(False, timeout=timeout)
print ('Pairing: turn your new device on (timing out in', timeout, 'seconds).')
# the lock-open notification may come slightly later, wait for it a bit
pairing_start = _timestamp()
patience = 5 # seconds
while receiver.status.lock_open or _timestamp() - pairing_start < patience:
n = _base.read(receiver.handle)
if n:
n = _base.make_notification(*n)
if n:
receiver.handle.notifications_hook(n)
if not (old_notification_flags & _hidpp10.NOTIFICATION_FLAG.wireless):
# only clear the flags if they weren't set before, otherwise a
# concurrently running Solaar app might stop working properly
_hidpp10.set_notification_flags(receiver, old_notification_flags)
if receiver.status.new_device:
dev = receiver.status.new_device
print ('Paired device %d: %s (%s) [%s:%s]' % (dev.number, dev.name, dev.codename, dev.wpid, dev.serial))
else:
error = receiver.status.get(_status.KEYS.ERROR)
if error :
raise Exception("pairing failed: %s" % error)
else :
print ('Paired a device') # this is better than an error

70
lib/solaar/cli/probe.py Normal file
View File

@@ -0,0 +1,70 @@
# -*- python-mode -*-
# -*- coding: UTF-8 -*-
## Copyright (C) 2020
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from __future__ import absolute_import, division, print_function, unicode_literals
from time import time as _timestamp
from logitech_receiver.common import strhex as _strhex
from logitech_receiver import (
base as _base,
hidpp10 as _hidpp10,
status as _status,
notifications as _notifications,
)
_R = _hidpp10.REGISTERS
from solaar.cli.show import _print_receiver
def run(receivers, args, find_receiver, _ignore):
assert receivers
if args.receiver:
receiver_name = args.receiver.lower()
receiver = find_receiver(receiver_name)
if not receiver:
raise Exception("no receiver found matching '%s'" % receiver_name)
else:
receiver = receivers[0]
assert receiver
_print_receiver(receiver)
print (' Register Dump')
register = receiver.read_register(_R.notifications)
print(" Notification Register %#04x: %s" % (_R.notifications%0x100,'0x'+_strhex(register) if register else "None"))
register = receiver.read_register(_R.receiver_connection)
print(" Connection State %#04x: %s" % (_R.receiver_connection%0x100,'0x'+_strhex(register) if register else "None"))
register = receiver.read_register(_R.devices_activity)
print(" Device Activity %#04x: %s" % (_R.devices_activity%0x100,'0x'+_strhex(register) if register else "None"))
for device in range(0,6):
for sub_reg in [ 0x0, 0x10, 0x20, 0x30 ] :
register = receiver.read_register(_R.receiver_info, sub_reg + device)
print(" Pairing Register %#04x %#04x: %s" % (_R.receiver_info%0x100,sub_reg + device,'0x'+_strhex(register) if register else "None"))
register = receiver.read_register(_R.receiver_info, 0x40 + device)
print(" Pairing Name %#04x %#02x: %s" % (_R.receiver_info%0x100,0x40 + device,register[2:2+ord(register[1:2])] if register else "None"))
for sub_reg in range(0,5):
register = receiver.read_register(_R.firmware, sub_reg)
print(" Firmware %#04x %#04x: %s" % (_R.firmware%0x100,sub_reg,'0x'+_strhex(register) if register else "None"))

224
lib/solaar/cli/show.py Normal file
View File

@@ -0,0 +1,224 @@
# -*- python-mode -*-
# -*- coding: UTF-8 -*-
## Copyright (C) 2012-2013 Daniel Pavel
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from __future__ import absolute_import, division, print_function, unicode_literals
from logitech_receiver import (
hidpp10 as _hidpp10,
hidpp20 as _hidpp20,
special_keys as _special_keys,
)
def _print_receiver(receiver):
paired_count = receiver.count()
print (receiver.name)
print (' Device path :', receiver.path)
print (' USB id : 046d:%s' % receiver.product_id)
print (' Serial :', receiver.serial)
if receiver.firmware:
for f in receiver.firmware:
print (' %-11s: %s' % (f.kind, f.version))
print (' Has', paired_count, 'paired device(s) out of a maximum of %d.' % receiver.max_devices)
if receiver.remaining_pairings() and receiver.remaining_pairings() >= 0 :
print (' Has %d successful pairing(s) remaining.' % receiver.remaining_pairings() )
notification_flags = _hidpp10.get_notification_flags(receiver)
if notification_flags is not None:
if notification_flags:
notification_names = _hidpp10.NOTIFICATION_FLAG.flag_names(notification_flags)
print (' Notifications: %s (0x%06X)' % (', '.join(notification_names), notification_flags))
else:
print (' Notifications: (none)')
activity = receiver.read_register(_hidpp10.REGISTERS.devices_activity)
if activity:
activity = [(d, ord(activity[d - 1:d])) for d in range(1, receiver.max_devices)]
activity_text = ', '.join(('%d=%d' % (d, a)) for d, a in activity if a > 0)
print (' Device activity counters:', activity_text or '(empty)')
def _print_device(dev):
assert dev
# check if the device is online
dev.ping()
print (' %d: %s' % (dev.number, dev.name))
print (' Codename :', dev.codename)
print (' Kind :', dev.kind)
print (' Wireless PID :', dev.wpid)
if dev.protocol:
print (' Protocol : HID++ %1.1f' % dev.protocol)
else:
print (' Protocol : unknown (device is offline)')
if dev.polling_rate:
print (' Polling rate :', dev.polling_rate, 'ms (%dHz)' % (1000 // dev.polling_rate))
print (' Serial number:', dev.serial)
if dev.firmware:
for fw in dev.firmware:
print (' %11s:' % fw.kind, (fw.name + ' ' + fw.version).strip())
if dev.power_switch_location:
print (' The power switch is located on the %s.' % dev.power_switch_location)
if dev.online:
notification_flags = _hidpp10.get_notification_flags(dev)
if notification_flags is not None:
if notification_flags:
notification_names = _hidpp10.NOTIFICATION_FLAG.flag_names(notification_flags)
print (' Notifications: %s (0x%06X).' % (', '.join(notification_names), notification_flags))
else:
print (' Notifications: (none).')
if dev.online and dev.features:
print (' Supports %d HID++ 2.0 features:' % len(dev.features))
for index, feature in enumerate(dev.features):
feature = dev.features[index]
flags = dev.request(0x0000, feature.bytes(2))
flags = 0 if flags is None else ord(flags[1:2])
flags = _hidpp20.FEATURE_FLAG.flag_names(flags)
print (' %2d: %-22s {%04X} %s' % (index, feature, feature, ', '.join(flags)))
if feature == _hidpp20.FEATURE.HIRES_WHEEL:
wheel = _hidpp20.get_hires_wheel(dev)
if wheel:
multi, has_invert, has_switch, inv, res, target, ratchet = wheel
print(" Multiplier: %s" % multi)
if has_invert:
print(" Has invert")
if inv:
print(" Inverse wheel motion")
else:
print(" Normal wheel motion")
if has_switch:
print(" Has ratchet switch")
if ratchet:
print(" Normal wheel mode")
else:
print(" Free wheel mode")
if res:
print(" High resolution mode")
else:
print(" Low resolution mode")
if target:
print(" HID++ notification")
else:
print(" HID notification")
if feature == _hidpp20.FEATURE.MOUSE_POINTER:
mouse_pointer = _hidpp20.get_mouse_pointer_info(dev)
if mouse_pointer:
print(" DPI: %s" % mouse_pointer['dpi'])
print(" Acceleration: %s" % mouse_pointer['acceleration'])
if mouse_pointer['suggest_os_ballistics']:
print(" Use OS ballistics")
else:
print(" Override OS ballistics")
if mouse_pointer['suggest_vertical_orientation']:
print(" Provide vertical tuning, trackball")
else:
print(" No vertical tuning, standard mice")
if feature == _hidpp20.FEATURE.VERTICAL_SCROLLING:
vertical_scrolling_info = _hidpp20.get_vertical_scrolling_info(dev)
if vertical_scrolling_info:
print(" Roller type: %s" % vertical_scrolling_info['roller'])
print(" Ratchet per turn: %s" % vertical_scrolling_info['ratchet'])
print(" Scroll lines: %s" % vertical_scrolling_info['lines'])
if feature == _hidpp20.FEATURE.HI_RES_SCROLLING:
scrolling_mode, scrolling_resolution = _hidpp20.get_hi_res_scrolling_info(dev)
if scrolling_mode:
print(" Hi-res scrolling enabled")
else:
print(" Hi-res scrolling disabled")
if scrolling_resolution:
print(" Hi-res scrolling multiplier: %s" % scrolling_resolution)
if feature == _hidpp20.FEATURE.POINTER_SPEED:
pointer_speed = _hidpp20.get_pointer_speed_info(dev)
if pointer_speed:
print(" Pointer Speed: %s" % pointer_speed)
if feature == _hidpp20.FEATURE.LOWRES_WHEEL:
wheel_status = _hidpp20.get_lowres_wheel_status(dev)
if wheel_status:
print(" Wheel Reports: %s" % wheel_status)
if dev.online and dev.keys:
print (' Has %d reprogrammable keys:' % len(dev.keys))
for k in dev.keys:
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)))
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:
battery = _hidpp10.get_battery(dev)
if battery is not None:
from logitech_receiver.common import NamedInt as _NamedInt
level, status = battery
if level is not None:
if isinstance(level, _NamedInt):
text = str(level)
else:
text = '%d%%' % level
else:
text = 'N/A'
print (' Battery: %s, %s.' % (text, status))
else:
battery_voltage = _hidpp20.get_voltage(dev)
if battery_voltage :
(voltage, charging, charge_sts, charge_lvl, charge_type) = battery_voltage
print (' Battery: %smV, %s.' % (voltage, 'Charging' if charging else 'Discharging'))
else:
print (' Battery status unavailable.')
else:
print (' Battery: unknown (device is offline).')
def run(receivers, args, find_receiver, find_device):
assert receivers
assert args.device
device_name = args.device.lower()
if device_name == 'all':
for r in receivers:
_print_receiver(r)
count = r.count()
if count:
for dev in r:
print ('')
_print_device(dev)
count -= 1
if not count:
break
print ('')
return
dev = find_receiver(receivers, device_name)
if dev:
_print_receiver(dev)
return
dev = find_device(receivers, device_name)
assert dev
_print_device(dev)

39
lib/solaar/cli/unpair.py Normal file
View File

@@ -0,0 +1,39 @@
# -*- python-mode -*-
# -*- coding: UTF-8 -*-
## Copyright (C) 2012-2013 Daniel Pavel
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from __future__ import absolute_import, division, print_function, unicode_literals
def run(receivers, args, find_receiver, find_device):
assert receivers
assert args.device
device_name = args.device.lower()
dev = find_device(receivers, device_name)
if not dev.receiver.may_unpair:
print('Receiver for %s [%s:%s] does not unpair, but attempting anyway' % (dev.name,dev.wpid,dev.serial))
try:
# query these now, it's last chance to get them
number, codename, wpid, serial = dev.number, dev.codename, dev.wpid, dev.serial
dev.receiver._unpair_device(number, True) # force an unpair
print ('Unpaired %d: %s (%s) [%s:%s]' % (number, dev.name, codename, wpid, serial))
except Exception as e:
raise Exception('failed to unpair device %s: %s' % (dev.name, e))

View File

@@ -127,7 +127,4 @@ def attach_to(device):
_load()
persister = _device_entry(device)
for s in device.settings:
if s.persister is None:
s.persister = persister
assert s.persister == persister
device.persister = persister

71
lib/solaar/gtk.py Normal file → Executable file
View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- python-mode -*-
# -*- coding: UTF-8 -*-
@@ -20,30 +20,57 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import importlib
from solaar import __version__, NAME
import solaar.i18n as _i18n
import solaar.cli as _cli
#
#
#
def _require(module, os_package):
def _require(module, os_package, gi=None, gi_package=None, gi_version=None):
try:
__import__(module)
except ImportError:
if gi is not None:
gi.require_version(gi_package,gi_version)
return importlib.import_module(module)
except (ImportError, ValueError):
import sys
sys.exit("%s: missing required package '%s'" % (NAME, os_package))
sys.exit("%s: missing required system package %s" % (NAME, os_package))
prefer_symbolic_battery_icons = False
def _parse_arguments():
import argparse
arg_parser = argparse.ArgumentParser(prog=NAME.lower())
arg_parser.add_argument('-d', '--debug', action='count', default=0,
help="print logging messages, for debugging purposes (may be repeated for extra verbosity)")
help='print logging messages, for debugging purposes (may be repeated for extra verbosity)')
arg_parser.add_argument('-D', '--hidraw', action='store', dest='hidraw_path', metavar='PATH',
help='unifying receiver to use; the first detected receiver if unspecified. Example: /dev/hidraw2')
arg_parser.add_argument('--restart-on-wake-up', action='store_true',
help='restart Solaar on sleep wake-up (experimental)')
arg_parser.add_argument('-w', '--window', choices=('show','hide','only'), help='start with window showing / hidden / only (no tray icon)')
arg_parser.add_argument('-b', '--battery-icons', choices=('regular','symbolic'), help='prefer regular / symbolic icons')
arg_parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__)
arg_parser.add_argument('--help-actions', action='store_true',
help='print help for the optional actions')
arg_parser.add_argument('action', nargs=argparse.REMAINDER, choices=_cli.actions,
help='optional actions to perform')
args = arg_parser.parse_args()
if args.help_actions:
_cli.print_help()
return
if args.window is None:
args.window = 'show' # default behaviour is to show main window
global prefer_symbolic_battery_icons
prefer_symbolic_battery_icons = True if args.battery_icons == 'symbolic' else False
import logging
if args.debug > 0:
log_level = logging.WARNING - 10 * args.debug
@@ -53,34 +80,42 @@ def _parse_arguments():
logging.root.addHandler(logging.NullHandler())
logging.root.setLevel(logging.ERROR)
if logging.root.isEnabledFor(logging.INFO):
logging.info("language %s (%s), translations path %s", _i18n.language, _i18n.encoding, _i18n.path)
if not args.action:
if logging.root.isEnabledFor(logging.INFO):
logging.info("language %s (%s), translations path %s", _i18n.language, _i18n.encoding, _i18n.path)
return args
def main():
_require('pyudev', 'python-pyudev')
_require('gi.repository', 'python-gi')
_require('gi.repository.Gtk', 'gir1.2-gtk-3.0')
_parse_arguments()
_require('pyudev', 'python3-pyudev')
# handle ^C in console
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)
args = _parse_arguments()
if not args: return
if args.action:
# if any argument, run comandline and exit
return _cli.run(args.action, args.hidraw_path)
gi = _require('gi', 'python3-gi or python3-gobject')
_require('gi.repository.Gtk', 'gir1.2-gtk-3.0', gi, 'Gtk', '3.0')
try:
import solaar.ui as ui
ui.init()
import solaar.listener as listener
listener.setup_scanner(ui.status_changed, ui.error_dialog)
listener.start_all()
import solaar.upower as _upower
if args.restart_on_wake_up:
_upower.watch(listener.start_all, listener.stop_all)
else:
_upower.watch(lambda: listener.ping_all(True))
# main UI event loop
ui.run_loop()
listener.stop_all()
ui.run_loop(listener.start_all, listener.stop_all, args.window!='only', args.window!='hide')
except Exception as e:
import sys
sys.exit('%s: error: %s' % (NAME.lower(), e))

View File

@@ -60,5 +60,7 @@ _gettext.install(_LOCALE_DOMAIN)
try:
unicode
_ = lambda x: _gettext.gettext(x).decode('UTF-8')
ngettext = lambda *x: _gettext.ngettext(*x).decode('UTF-8')
except:
_ = _gettext.gettext
ngettext = _gettext.ngettext

View File

@@ -18,6 +18,7 @@
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from __future__ import absolute_import, division, print_function, unicode_literals
import time
from logging import getLogger, INFO as _INFO
_log = getLogger(__name__)
@@ -181,10 +182,33 @@ class ReceiverListener(_listener.EventsListener):
return
# a device notification
assert n.devnumber > 0 and n.devnumber <= self.receiver.max_devices
assert 0 < n.devnumber <= self.receiver.max_devices
already_known = n.devnumber in self.receiver
if not already_known and n.sub_id == 0x41:
dev = self.receiver.register_new_device(n.devnumber, n)
# FIXME: hacky fix for kernel/hardware race condition
# If the device was just turned on or woken up from sleep, it may not
# be ready to receive commands. The "payload" bit of the wireless
# status notification seems to tell us this. If this is the case, we
# must wait a short amount of time to avoid causing a broken pipe
# error.
device_ready = not bool(ord(n.data[0:1]) & 0x80) or n.sub_id != 0x41
if not device_ready:
time.sleep(0.01)
if n.sub_id == 0x40 and not already_known:
return # disconnecting something that is not known - nothing to do
if n.sub_id == 0x41:
if not already_known:
dev = self.receiver.register_new_device(n.devnumber, n)
elif self.receiver.status.lock_open and self.receiver.re_pairs and not ord(n.data[0:1]) & 0x40:
dev = self.receiver[n.devnumber]
del self.receiver[n.devnumber] # get rid of information on device re-paired away
self._status_changed(dev) # signal that this device has changed
dev = self.receiver.register_new_device(n.devnumber, n)
self.receiver.status.new_device = self.receiver[n.devnumber]
else:
dev = self.receiver[n.devnumber]
else:
dev = self.receiver[n.devnumber]
@@ -192,7 +216,8 @@ class ReceiverListener(_listener.EventsListener):
_log.warn("%s: received %s for invalid device %d: %r", self.receiver, n, n.devnumber, dev)
return
if not already_known:
# Apply settings every time the device connects
if n.sub_id == 0x41:
if _log.isEnabledFor(_INFO):
_log.info("%s triggered new device %s (%s)", n, dev, dev.kind)
# If there are saved configs, bring the device's settings up-to-date.
@@ -211,8 +236,7 @@ class ReceiverListener(_listener.EventsListener):
if _log.isEnabledFor(_INFO):
_log.info("%s: pairing detected new device", self.receiver)
self.receiver.status.new_device = dev
elif dev:
if dev.online is None:
elif dev.online is None:
dev.ping()
def __str__(self):
@@ -267,10 +291,21 @@ def stop_all():
for l in listeners:
l.join()
# stop/start all receiver threads on suspend/resume events, if possible
from . import upower
upower.watch(start_all, stop_all)
# ping all devices to find out whether they are connected
# after a resume, the device may have been off
# so mark its saved status to ensure that the status is pushed to the device when it comes back
def ping_all(resuming = False):
for l in _all_listeners.values():
count = l.receiver.count()
if count:
for dev in l.receiver:
if resuming:
dev.status._active = False
dev.ping()
l._status_changed(dev)
count -= 1
if not count:
break
from logitech_receiver import base as _base
@@ -308,4 +343,13 @@ def _process_receiver_event(action, device_info):
_start(device_info)
except OSError:
# permission error, ignore this path for now
_error_callback('permissions', device_info.path)
# If receiver has extended ACL but not writable then it is for another seat.
# (It would be easier to use pylibacl but adding the pylibacl dependencies
# for this special case is not good.)
try:
import subprocess, re
output = subprocess.check_output(['/usr/bin/getfacl', '-p', device_info.path])
if not re.search(b'user:.+:',output) :
_error_callback('permissions', device_info.path)
except:
_error_callback('permissions', device_info.path)

70
lib/solaar/tasks.py Normal file
View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
# -*- python-mode -*-
# -*- coding: UTF-8 -*-
## Copyright (C) 2012-2013 Daniel Pavel
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from __future__ import absolute_import, division, print_function, unicode_literals
from logging import getLogger, DEBUG as _DEBUG
_log = getLogger(__name__)
del getLogger
from threading import Thread as _Thread
try:
from Queue import Queue as _Queue
except ImportError:
from queue import Queue as _Queue
#
#
#
class TaskRunner(_Thread):
def __init__(self, name):
super(TaskRunner, self).__init__(name=name)
self.daemon = True
self.queue = _Queue(16)
self.alive = False
def __call__(self, function, *args, **kwargs):
task = (function, args, kwargs)
self.queue.put(task)
def stop(self):
self.alive = False
self.queue.put(None)
def run(self):
self.alive = True
if _log.isEnabledFor(_DEBUG):
_log.debug("started")
while self.alive:
task = self.queue.get()
if task:
function, args, kwargs = task
assert function
try:
function(*args, **kwargs)
except:
_log.exception("calling %s", function)
if _log.isEnabledFor(_DEBUG):
_log.debug("stopped")

View File

@@ -20,7 +20,7 @@
from __future__ import absolute_import, division, print_function, unicode_literals
from logging import getLogger, DEBUG as _DEBUG, INFO as _INFO
from logging import getLogger, DEBUG as _DEBUG
_log = getLogger(__name__)
del getLogger
@@ -37,27 +37,6 @@ assert Gtk.get_major_version() > 2, 'Solaar requires Gtk 3 python bindings'
GLib.threads_init()
def _init_application():
APP_ID = 'io.github.pwr.solaar'
app = Gtk.Application.new(APP_ID, 0)
# not sure this is necessary...
# app.set_property('register-session', True)
registered = app.register(None)
dbus_path = app.get_dbus_object_path() if hasattr(app, 'get_dbus_object_path') else APP_ID
if _log.isEnabledFor(_INFO):
_log.info("application %s, registered %s", dbus_path, registered)
# assert registered, "failed to register unique application %s" % app
# if there is already a running instance, bail out
if app.get_is_remote():
# pop up the window in the other instance
app.activate()
raise Exception("already running")
return app
application = _init_application()
#
#
#
@@ -72,7 +51,7 @@ def _error_dialog(reason, object):
_("If you've just installed Solaar, try removing the receiver and plugging it back in.")
elif reason == 'unpair':
title = _("Unpairing failed")
text = _("Failed to unpair %s from %s.") % (object.name, object.receiver.name) + \
text = _("Failed to unpair %{device} from %{receiver}.").format(device=object.name, receiver=object.receiver.name) + \
'\n\n' + \
_("The receiver returned an error, with no further details.")
else:
@@ -92,40 +71,13 @@ def error_dialog(reason, object):
GLib.idle_add(_error_dialog, reason, object)
#
# A separate thread is used to read/write from the device
# so as not to block the main (GUI) thread.
#
#
try:
from Queue import Queue
except ImportError:
from queue import Queue
_task_queue = Queue(16)
del Queue
from threading import Thread, current_thread as _current_thread
def _process_async_queue():
t = _current_thread()
t.alive = True
while t.alive:
function, args, kwargs = _task_queue.get()
if function:
function(*args, **kwargs)
if _log.isEnabledFor(_DEBUG):
_log.debug("stopped")
_queue_processor = Thread(name='AsyncUI', target=_process_async_queue)
_queue_processor.daemon = True
_queue_processor.alive = False
_queue_processor.start()
del Thread
def async(function, *args, **kwargs):
task = (function, args, kwargs)
_task_queue.put(task)
_task_runner = None
def ui_async(function, *args, **kwargs):
if _task_runner:
_task_runner(function, *args, **kwargs)
#
#
@@ -133,36 +85,75 @@ def async(function, *args, **kwargs):
from . import notify, tray, window
def init():
def _startup(app, startup_hook, use_tray, show_window):
if _log.isEnabledFor(_DEBUG):
_log.debug("startup registered=%s, remote=%s", app.get_is_registered(), app.get_is_remote())
from solaar.tasks import TaskRunner as _TaskRunner
global _task_runner
_task_runner = _TaskRunner('AsyncUI')
_task_runner.start()
notify.init()
tray.init(lambda _ignore: window.destroy())
window.init()
if use_tray:
tray.init(lambda _ignore: window.destroy())
window.init(show_window, use_tray)
def run_loop():
def _activate(app):
assert app == application
if app.get_windows():
window.popup()
else:
app.add_window(window._window)
startup_hook()
def _shutdown(app):
# stop the async UI processor
_queue_processor.alive = False
async(None)
tray.destroy()
notify.uninit()
def _activate(app):
if _log.isEnabledFor(_DEBUG):
_log.debug("activate")
if app.get_windows():
window.popup()
else:
app.add_window(window._window)
def _command_line(app, command_line):
if _log.isEnabledFor(_DEBUG):
_log.debug("command_line %s", command_line.get_arguments())
return 0
def _shutdown(app, shutdown_hook):
if _log.isEnabledFor(_DEBUG):
_log.debug("shutdown")
shutdown_hook()
# stop the async UI processor
global _task_runner
_task_runner.stop()
_task_runner = None
tray.destroy()
notify.uninit()
def run_loop(startup_hook, shutdown_hook, use_tray, show_window, args=None):
assert use_tray or show_window, 'need either tray or visible window'
# from gi.repository.Gio import ApplicationFlags as _ApplicationFlags
APP_ID = 'io.github.pwr.solaar'
application = Gtk.Application.new(APP_ID, 0) # _ApplicationFlags.HANDLES_COMMAND_LINE)
application.connect('startup', lambda app, startup_hook:_startup(app,startup_hook,use_tray,show_window), startup_hook)
application.connect('command-line', _command_line)
application.connect('activate', _activate)
application.connect('shutdown', _shutdown)
application.run(None)
application.connect('shutdown', _shutdown, shutdown_hook)
application.run(args)
#
#
#
from logitech_receiver.status import ALERT
def _status_changed(device, alert, reason):
assert device is not None
if _log.isEnabledFor(_DEBUG):
@@ -172,10 +163,10 @@ def _status_changed(device, alert, reason):
if alert & ALERT.ATTENTION:
tray.attention(reason)
need_popup = alert & (ALERT.SHOW_WINDOW | ALERT.ATTENTION)
need_popup = alert & ALERT.SHOW_WINDOW
window.update(device, need_popup)
if alert & ALERT.NOTIFICATION:
if alert & (ALERT.NOTIFICATION | ALERT.ATTENTION):
notify.show(device, reason)

Some files were not shown because too many files have changed in this diff Show More