Compare commits
450 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a108b3ab7 | ||
|
|
fac6cd6840 | ||
|
|
edf610d235 | ||
|
|
dac9c1bd8e | ||
|
|
352375aa9a | ||
|
|
44722560d2 | ||
|
|
101e24c7bf | ||
|
|
b7e8e646b5 | ||
|
|
97fa7de28a | ||
|
|
302720b0db | ||
|
|
813c238704 | ||
|
|
354c914bd2 | ||
|
|
574aee0cc7 | ||
|
|
a3cdcf4cd1 | ||
|
|
592ced0a53 | ||
|
|
3a2593798e | ||
|
|
67b7d46844 | ||
|
|
5411e43b77 | ||
|
|
66dc0bd285 | ||
|
|
f641554ed5 | ||
|
|
beec4c2130 | ||
|
|
f5bf488ffc | ||
|
|
1deaa09142 | ||
|
|
88a0dfff6f | ||
|
|
f28da6e7c4 | ||
|
|
5290980ac8 | ||
|
|
4d23d3abac | ||
|
|
0c4e757cf9 | ||
|
|
d487a0d889 | ||
|
|
65824a4c08 | ||
|
|
58a1c7ffb0 | ||
|
|
1f896d2b9e | ||
|
|
377c954933 | ||
|
|
4744ce47b2 | ||
|
|
b01b74f2a8 | ||
|
|
6282ae05e8 | ||
|
|
b5e1f47c50 | ||
|
|
8ffaeb112f | ||
|
|
2189d4a35b | ||
|
|
706ee2990f | ||
|
|
06875fb9ab | ||
|
|
f6b0cc59ed | ||
|
|
e3aacdbf05 | ||
|
|
45dc81b4c4 | ||
|
|
0324fd9a1e | ||
|
|
ca9f9b3447 | ||
|
|
624247d378 | ||
|
|
5f8dbdeb6d | ||
|
|
2bdb844557 | ||
|
|
422f336d70 | ||
|
|
563ef0d8ef | ||
|
|
3a85c4f64b | ||
|
|
4056f5b9d3 | ||
|
|
442a7d18a5 | ||
|
|
79be4b20d7 | ||
|
|
b2f3b56c31 | ||
|
|
072f932206 | ||
|
|
d5a3a4fe7e | ||
|
|
a49b7938e0 | ||
|
|
1eef88eb44 | ||
|
|
eda1399330 | ||
|
|
8dbc4b5707 | ||
|
|
687a28d2c2 | ||
|
|
f733390bd1 | ||
|
|
ac6fa9643f | ||
|
|
f28053a09a | ||
|
|
49145ae272 | ||
|
|
af26870e83 | ||
|
|
007163a563 | ||
|
|
a9acdbe47a | ||
|
|
955df30c7f | ||
|
|
23c0397764 | ||
|
|
789f5f05c3 | ||
|
|
ea2c22c015 | ||
|
|
1c09b9c45d | ||
|
|
dd9626f7a4 | ||
|
|
35909afb0d | ||
|
|
8e67bbbc11 | ||
|
|
a5813e4e23 | ||
|
|
d439d43be6 | ||
|
|
11465f2b2a | ||
|
|
079f2dacd4 | ||
|
|
7c6bd4202d | ||
|
|
1740a9a8c4 | ||
|
|
0cadc3247e | ||
|
|
e23de2ee9f | ||
|
|
8fb52a3f37 | ||
|
|
3edac85b90 | ||
|
|
64c76e51ef | ||
|
|
02cac598a0 | ||
|
|
02a47b4003 | ||
|
|
4afe8c893b | ||
|
|
5afa094175 | ||
|
|
196705ba1f | ||
|
|
4c46a999f7 | ||
|
|
7bd31c0d95 | ||
|
|
dc2a1adf26 | ||
|
|
e89b50cdf2 | ||
|
|
d08e0a9574 | ||
|
|
22d37c4c1c | ||
|
|
546ccb7ac2 | ||
|
|
1ab03d4c9f | ||
|
|
a6fcb75aa8 | ||
|
|
907c5ab075 | ||
|
|
515f994ab8 | ||
|
|
25905c5d77 | ||
|
|
babf0f4ded | ||
|
|
41664b8e21 | ||
|
|
5c080a7831 | ||
|
|
5aa4fa0239 | ||
|
|
4f4e610635 | ||
|
|
39791be440 | ||
|
|
2814350f9f | ||
|
|
4a28b48275 | ||
|
|
97474a21de | ||
|
|
719106bf0f | ||
|
|
0a735a32ed | ||
|
|
bd26b9340d | ||
|
|
f45ae5ffad | ||
|
|
0520dde990 | ||
|
|
e53781ec6e | ||
|
|
1bc189e2fe | ||
|
|
91bcfa28ea | ||
|
|
64c1de32da | ||
|
|
56762b5494 | ||
|
|
77e21a0b63 | ||
|
|
d6fa5269c5 | ||
|
|
0198d58dc7 | ||
|
|
c30f0a79bd | ||
|
|
89ee83b906 | ||
|
|
0bef7b84f8 | ||
|
|
c3e90bc73e | ||
|
|
814b7f30ba | ||
|
|
8039c035a7 | ||
|
|
430b70711b | ||
|
|
82915eea4b | ||
|
|
995544396c | ||
|
|
4678f52293 | ||
|
|
26c5a5f9ba | ||
|
|
c6ccc0638d | ||
|
|
6ba8c6c3fc | ||
|
|
9087f4187c | ||
|
|
673c81c096 | ||
|
|
43bfa80753 | ||
|
|
3884ce14d7 | ||
|
|
4fafd4f6be | ||
|
|
0bec0e02b1 | ||
|
|
59c0367283 | ||
|
|
63f523dac6 | ||
|
|
4822975acb | ||
|
|
c389c9c2b7 | ||
|
|
76e70799aa | ||
|
|
2a94cc9233 | ||
|
|
ae9e862f19 | ||
|
|
12f4e187d6 | ||
|
|
51dd2a5d28 | ||
|
|
6981555804 | ||
|
|
3583759d3a | ||
|
|
c92a889b68 | ||
|
|
b9fb005c9c | ||
|
|
a06ea6de11 | ||
|
|
a868b477e1 | ||
|
|
c799e038de | ||
|
|
8468be2126 | ||
|
|
1ff9c3a40b | ||
|
|
9b201f6b0d | ||
|
|
afda6f9f66 | ||
|
|
5cc0b8854d | ||
|
|
f88951c56d | ||
|
|
2aab7f71fd | ||
|
|
6376980213 | ||
|
|
1bb08d011d | ||
|
|
966c55e69d | ||
|
|
6b14004a6f | ||
|
|
7a4f7bbb84 | ||
|
|
155e2f8c40 | ||
|
|
6a1968beff | ||
|
|
f4557233f1 | ||
|
|
a597b81f5a | ||
|
|
a587ae65d1 | ||
|
|
6522b5fef0 | ||
|
|
7f78f0c580 | ||
|
|
127a8053d8 | ||
|
|
6c0acee645 | ||
|
|
60c52eb13f | ||
|
|
8bac8d68f4 | ||
|
|
087a5d2c42 | ||
|
|
481511b6c2 | ||
|
|
21d98294bd | ||
|
|
ebbb54758b | ||
|
|
c07c115ee3 | ||
|
|
8c02320795 | ||
|
|
932164458a | ||
|
|
de79bf1b5c | ||
|
|
5944103aef | ||
|
|
ab4226e066 | ||
|
|
a66121db0d | ||
|
|
eacfdea37f | ||
|
|
68b1f4e413 | ||
|
|
947035a7b7 | ||
|
|
e54134cda9 | ||
|
|
1609c3e98a | ||
|
|
ed9aa76547 | ||
|
|
b8529030e6 | ||
|
|
1412a07899 | ||
|
|
f79ad67ea7 | ||
|
|
dd8018a4d5 | ||
|
|
350784eb70 | ||
|
|
f0fc63e5b7 | ||
|
|
51375d4510 | ||
|
|
2c0ea34694 | ||
|
|
867edf8516 | ||
|
|
dcd1a88250 | ||
|
|
8f95dd323d | ||
|
|
336b778398 | ||
|
|
572c7f5e59 | ||
|
|
6b12b7935f | ||
|
|
ade2fbacec | ||
|
|
a59368f3e7 | ||
|
|
d021d87656 | ||
|
|
5d0d353c74 | ||
|
|
e00d680fc9 | ||
|
|
59b7285fdf | ||
|
|
988bdc2c8d | ||
|
|
1a67b33860 | ||
|
|
d43bffc9d9 | ||
|
|
f8079f4f9d | ||
|
|
47e7f997d5 | ||
|
|
8c0cf9fe9b | ||
|
|
fa8bd83ba3 | ||
|
|
697fb999b4 | ||
|
|
aeb943fc9e | ||
|
|
434af009cb | ||
|
|
cc010cb653 | ||
|
|
e450e959a6 | ||
|
|
10f3421021 | ||
|
|
8eba60d617 | ||
|
|
e3d658ea10 | ||
|
|
ef1c051e10 | ||
|
|
bc566718b4 | ||
|
|
22719f21fc | ||
|
|
c51daa922c | ||
|
|
632d8804be | ||
|
|
a4b7194490 | ||
|
|
ea80c6d639 | ||
|
|
a01e4b2efe | ||
|
|
801bdfa224 | ||
|
|
410d100dc6 | ||
|
|
7078750a3f | ||
|
|
360c92f6b0 | ||
|
|
2377fef3a8 | ||
|
|
684afa871e | ||
|
|
dd2f884504 | ||
|
|
9dd05bb727 | ||
|
|
e96a0fbfbb | ||
|
|
f9618dd0ab | ||
|
|
db0656967b | ||
|
|
53ec751cf1 | ||
|
|
45cc161cc7 | ||
|
|
1ccd2d1856 | ||
|
|
d9d37edc2f | ||
|
|
ae405871fb | ||
|
|
f4700f07ce | ||
|
|
d2d19a615d | ||
|
|
c36cc506b6 | ||
|
|
7180693f89 | ||
|
|
aa08356823 | ||
|
|
197ff7365d | ||
|
|
a0c6a6ce59 | ||
|
|
934ecb821d | ||
|
|
f0cf949336 | ||
|
|
ecd245e8dc | ||
|
|
f65f000d38 | ||
|
|
3b60f205f7 | ||
|
|
d6c93cfcfe | ||
|
|
cbd71f9d24 | ||
|
|
45d5119a5a | ||
|
|
773cb7f45f | ||
|
|
d74e3a9b92 | ||
|
|
fe1fba7ac6 | ||
|
|
4e20ef61c8 | ||
|
|
a750b32d14 | ||
|
|
69006c48c6 | ||
|
|
b12d03dfe3 | ||
|
|
ddbf93fb24 | ||
|
|
65c55b789c | ||
|
|
d83530bee3 | ||
|
|
e6c02f5c52 | ||
|
|
122d76d061 | ||
|
|
fb1442dbcb | ||
|
|
0b8141f092 | ||
|
|
137c32262b | ||
|
|
7c726738b1 | ||
|
|
2570f584b9 | ||
|
|
4c1f568fab | ||
|
|
27bd71253e | ||
|
|
9a9a82016f | ||
|
|
aa03cef61f | ||
|
|
c14e30b6f5 | ||
|
|
b5d43cdc3c | ||
|
|
1edd8a577b | ||
|
|
358e0958bb | ||
|
|
fa5fba796b | ||
|
|
ce215228f3 | ||
|
|
28e43395ab | ||
|
|
e09231c05a | ||
|
|
f29de0f3dd | ||
|
|
ba540338e2 | ||
|
|
2442fee341 | ||
|
|
bbadd3e755 | ||
|
|
2fdce2f938 | ||
|
|
f1fad2d16b | ||
|
|
8a5700b44e | ||
|
|
e046455a05 | ||
|
|
ddbc27486c | ||
|
|
2041007b38 | ||
|
|
7b7e17819e | ||
|
|
f855cd60e1 | ||
|
|
883ed9549d | ||
|
|
d1858f747b | ||
|
|
aa7d1b6410 | ||
|
|
bc3a98ddb2 | ||
|
|
5b01f375e0 | ||
|
|
dd2755909d | ||
|
|
b052ab9ef0 | ||
|
|
9c768d60a1 | ||
|
|
d1d0ab85ff | ||
|
|
e1eee2e078 | ||
|
|
5c38f90cd6 | ||
|
|
45d49b9de8 | ||
|
|
4ccb8ab26d | ||
|
|
ab162583e4 | ||
|
|
e25b47aa1c | ||
|
|
63a98819a6 | ||
|
|
27d3d80d30 | ||
|
|
478381a994 | ||
|
|
51a15a960f | ||
|
|
92967eed23 | ||
|
|
57338303e8 | ||
|
|
e735ce10f0 | ||
|
|
d75b6d2f2b | ||
|
|
bd1470dad7 | ||
|
|
15f97681ef | ||
|
|
7075ce910a | ||
|
|
5ba816dd38 | ||
|
|
a515cc3860 | ||
|
|
73344cbf26 | ||
|
|
cf27328d13 | ||
|
|
869f1e4791 | ||
|
|
e2d65a690e | ||
|
|
3141f06472 | ||
|
|
b88cf5a07b | ||
|
|
a344ad9867 | ||
|
|
76e327d2fa | ||
|
|
0ae0ef195a | ||
|
|
b1f74a1280 | ||
|
|
30431fbfb8 | ||
|
|
fb4c055fbf | ||
|
|
3ea51fe1a2 | ||
|
|
afd36764c2 | ||
|
|
b694809394 | ||
|
|
66296265ba | ||
|
|
952495ce04 | ||
|
|
29eb377267 | ||
|
|
47345e1dcb | ||
|
|
36fc6de760 | ||
|
|
6c0aca8c63 | ||
|
|
bd93689109 | ||
|
|
4e5d4853d6 | ||
|
|
bb2361a986 | ||
|
|
7c8d4815f0 | ||
|
|
0f05589cd6 | ||
|
|
21db60a14e | ||
|
|
091c28ca8b | ||
|
|
9af353dd32 | ||
|
|
981ff00ca3 | ||
|
|
ec0197df3d | ||
|
|
2111107ff4 | ||
|
|
2b08c5e0c9 | ||
|
|
cc8a6c4ab5 | ||
|
|
437e17cf37 | ||
|
|
22191d7d0c | ||
|
|
ba890c2d2e | ||
|
|
9a6cdd9cee | ||
|
|
32324f3e66 | ||
|
|
ffe297b239 | ||
|
|
7fe5983f7c | ||
|
|
d5f14bb5f8 | ||
|
|
f3049ed451 | ||
|
|
8485494ea7 | ||
|
|
7a75813a84 | ||
|
|
35830e3d50 | ||
|
|
46fd4dac56 | ||
|
|
ebd8635f84 | ||
|
|
efbf43b472 | ||
|
|
8a6d8e2ed1 | ||
|
|
bff99799ab | ||
|
|
1434b2a50d | ||
|
|
c3fdbfb643 | ||
|
|
a9ebac264f | ||
|
|
60a591ab6b | ||
|
|
ab3a6b50d4 | ||
|
|
b193b39701 | ||
|
|
806d67a72f | ||
|
|
9c465cd998 | ||
|
|
024a71b618 | ||
|
|
f144816256 | ||
|
|
f0eeea8625 | ||
|
|
72819b6130 | ||
|
|
23e4f32e11 | ||
|
|
243d1548dc | ||
|
|
c3bc56f306 | ||
|
|
288c607069 | ||
|
|
55c307d0e5 | ||
|
|
69febfcea5 | ||
|
|
9934755566 | ||
|
|
a7561eaa9b | ||
|
|
689661169a | ||
|
|
eac40abc3e | ||
|
|
372e4e0949 | ||
|
|
e42f7f1393 | ||
|
|
3860d4a56b | ||
|
|
3b75b69970 | ||
|
|
a4f0eab855 | ||
|
|
f54ea27d97 | ||
|
|
db48fe1b40 | ||
|
|
d0a0e902a7 | ||
|
|
95a6412d5c | ||
|
|
5faecbf4f3 | ||
|
|
5af1719384 | ||
|
|
6a66370ffe | ||
|
|
c0874220d2 | ||
|
|
9c67b1b494 | ||
|
|
22656d5b82 | ||
|
|
d55963caba | ||
|
|
413cebe40f | ||
|
|
140ffae01a | ||
|
|
68251777fb | ||
|
|
188b20a4f1 | ||
|
|
5fba1db11c | ||
|
|
21b1a722fc | ||
|
|
715c50685b | ||
|
|
dd0ba17351 | ||
|
|
576dd4cdd4 | ||
|
|
a2a0380401 | ||
|
|
f1a85f45bd | ||
|
|
200cf48df4 | ||
|
|
14b5993dc3 | ||
|
|
585ba93588 | ||
|
|
e30a692a6e |
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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.
|
||||
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal 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
@@ -5,6 +5,7 @@ __pycache__/
|
||||
*.mo
|
||||
|
||||
/lib/Solaar.egg-info/
|
||||
/lib/solaar.egg-info/
|
||||
/build/
|
||||
/sdist/
|
||||
/dist/
|
||||
|
||||
12
COPYRIGHT
@@ -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/>.
|
||||
|
||||
86
ChangeLog
@@ -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:
|
||||
|
||||
88
README.md
@@ -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.
|
||||
15
RELEASE.md
Normal 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`
|
||||
17
bin/solaar
@@ -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:
|
||||
|
||||
@@ -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
@@ -0,0 +1,6 @@
|
||||
# ignore documentation-specific files
|
||||
|
||||
.jekyll-metadata
|
||||
Gemfile
|
||||
Gemfile.lock
|
||||
_site/
|
||||
4
docs/README.md
Normal 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/)
|
||||
BIN
docs/Solaar-main-window-keyboard.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
docs/Solaar-main-window-mouse.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
docs/Solaar-main-window-offline.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
docs/Solaar-main-window-receiver.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
docs/Solaar-menu.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
10
docs/_config.yml
Normal 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
|
||||
53
docs/_layouts/default.html
Normal 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
@@ -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
|
After Width: | Height: | Size: 1.7 KiB |
100
docs/assets/solaar.svg
Normal 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
@@ -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
|
||||
@@ -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`
|
||||
|
||||
286
docs/devices.md
@@ -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
|
||||
|
||||
48
docs/devices/M510_HIDppV4.5.txt
Normal 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.
|
||||
51
docs/devices/anywhere-mx-2.txt
Normal 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.
|
||||
88
docs/devices/anywhere-mx.txt
Normal 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
@@ -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.
|
||||
|
||||
43
docs/devices/k270-unifying.txt
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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'
|
||||
|
||||
82
docs/devices/mk220-new.txt
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
84
docs/devices/mx-master-2s.txt
Normal 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.
|
||||
83
docs/devices/mx-master-3.txt
Normal 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.
|
||||
|
||||
54
docs/devices/mx-master.txt
Normal 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
@@ -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
@@ -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
@@ -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: |
|
||||
|
||||
32
docs/i18n.md
@@ -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
@@ -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§ion=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.
|
||||
@@ -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
@@ -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:
|
||||
|
||||

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

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

|
||||
|
||||

|
||||
|
||||
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 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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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 |
@@ -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>
|
||||
@@ -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>
|
||||
|
Before Width: | Height: | Size: 943 B After Width: | Height: | Size: 96 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 432 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 230 B |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 14 KiB |
@@ -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 }
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:]
|
||||
|
||||
@@ -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 { }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -19,5 +19,5 @@
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
__version__ = '0.9.2'
|
||||
__version__ = '1.0.2'
|
||||
NAME = 'Solaar'
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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))
|
||||
@@ -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
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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")
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||