@@ -59,25 +59,25 @@ b7 09 cd 09 e2 09 e9 09 ea 81 02 95 01 81 01 c0 05 01 09 02 a1 01 09 01 a1 00 85
59
59
13 15 00 26 ff 00 09 02 81 00 09 02 91 00 c0
60
60
N: Bluetooth HID Hub - RPi
61
61
I: 5 1d6b 0246
62
- # ReportID: 4 / Button: 0 0 0 0 0 0 0 0 | X: 0 | Y: 0 | Wheel: 0 | 0xcff00: 0 | Vendor Usage 1: 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0
62
+ # ReportID: 4 / Button: 0 0 0 0 0 0 0 0 | X: 0 | Y: 0 | Wheel: 0 | 0xcff00: 0 | Vendor Usage 1: 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0
63
63
E: 000000.000000 16 04 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
64
- # ReportID: 4 / Button: 1 0 0 0 0 0 0 0 | X: 0 | Y: 0 | Wheel: 0 | 0xcff00: 0 | Vendor Usage 1: 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0
64
+ # ReportID: 4 / Button: 1 0 0 0 0 0 0 0 | X: 0 | Y: 0 | Wheel: 0 | 0xcff00: 0 | Vendor Usage 1: 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0
65
65
E: 000000.000698 16 04 01 00 00 00 00 00 00 01 01 00 00 00 00 00 00
66
- # ReportID: 4 / Button: 0 0 0 0 0 0 0 0 | X: 0 | Y: 0 | Wheel: 0 | 0xcff00: 0 | Vendor Usage 1: 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0
66
+ # ReportID: 4 / Button: 0 0 0 0 0 0 0 0 | X: 0 | Y: 0 | Wheel: 0 | 0xcff00: 0 | Vendor Usage 1: 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0
67
67
E: 000000.001094 16 04 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
68
- # ReportID: 4 / Button: 1 0 0 0 0 0 0 0 | X: 0 | Y: 0 | Wheel: 0 | 0xcff00: 0 | Vendor Usage 1: 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0
68
+ # ReportID: 4 / Button: 1 0 0 0 0 0 0 0 | X: 0 | Y: 0 | Wheel: 0 | 0xcff00: 0 | Vendor Usage 1: 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0
69
69
E: 000000.001470 16 04 01 00 00 00 00 00 00 01 01 00 00 00 00 00 00
70
- # ReportID: 4 / Button: 0 0 0 0 0 0 0 0 | X: 0 | Y: 0 | Wheel: 0 | 0xcff00: 0 | Vendor Usage 1: 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0
70
+ # ReportID: 4 / Button: 0 0 0 0 0 0 0 0 | X: 0 | Y: 0 | Wheel: 0 | 0xcff00: 0 | Vendor Usage 1: 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0
71
71
E: 000000.001878 16 04 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
72
- # ReportID: 4 / Button: 0 0 0 0 0 0 0 0 | X: 0 | Y: 0 | Wheel: 0 | 0xcff00: 0 | Vendor Usage 1: 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0
72
+ # ReportID: 4 / Button: 0 0 0 0 0 0 0 0 | X: 0 | Y: 0 | Wheel: 0 | 0xcff00: 0 | Vendor Usage 1: 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0
73
73
E: 000000.044655 16 04 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
74
74
```
75
75
76
76
One difference is that the ReportID has been changed from 1 to 4 on each event.
77
77
This is needed for the RPi to be able to handle multiple devices. This is not the cause.
78
78
79
79
If we save the above output to a file, then we can repeat the event with `` hid-replay my-recording.hid `` .
80
- This allows use to change parts and test them out.
80
+ This allows us to change parts and test them out.
81
81
82
82
Through trial and error we can figure out that the double-click only works when the vendor ID (middle value under `` I: `` ) is set to `` 0b33 `` .
83
83
This is the ID for Contour and it suggests that there is vendor-specific code in the kernel to make this button work. Not helpful...
@@ -99,54 +99,101 @@ Hmm, it seems like the double-click actually sends 2 clicks of a different butto
99
99
What if we change the `` 80 `` to `` 01 `` , so we actually send 2 left-click events?
100
100
Running that through `` hid-replay `` , it works!
101
101
102
- However, if you just change that on the RPi, we later find it still does not work.
103
- Through more trial and error, comparing working/non-working events we can also figure out that the 2 left-click events
104
- only get interpreted as a double-click if there's a gap between the events of atleast around 25ms (the first number after
105
- `` E: `` is a timestamp of the event).
102
+ ## Writing a filter
106
103
107
- ## Implementing the fix
104
+ To implement the fix we need to create a new filter function.
105
+ While ssh'd into the RPi, create the new file: `` bthidhub/filters/contour.py `` :
108
106
109
- To implement the fix we need to create a new filter class in the bthidhub repo.
110
- While ssh'd into the RPi, I've created `` bthidhub/contour_message_filter.py `` :
107
+ ```
108
+ """Contour Rollermouse"""
111
109
110
+ def message_filter(msg: bytes) -> bytes:
111
+ if len(msg) >= 10 and msg[9] == 0x80:
112
+ # Convert vendor specific double click (button 0x80), to normal double click (button 1).
113
+ msg = msg[:9] + (b"\x01" if msg[1] else b"\x00") + msg[10:]
114
+ return msg
112
115
```
113
- import time
114
- from typing import Optional
115
116
116
- from hid_message_filter import HIDMessageFilter
117
+ The docstring on the first line can be used to customise the name in the UI.
118
+ If omitted, the name will be based on the filename.
119
+ Each module in the filters directory must define a function named `` message_filter `` .
120
+ This function will be automatically imported and available as a filter.
121
+
122
+ The above filter function changes the byte sequence for the double click button, so it
123
+ looks like regular single clicks.
124
+
125
+ Restart the service:
126
+ `` sudo systemctl restart remapper ``
127
+
128
+ After a few seconds, refresh the web page and you should see the new filter in the dropdown
129
+ options. Select the new filter and it will immediately be applied to all events from that
130
+ device.
131
+
132
+ If you add `` print() `` calls for debugging, you can view the logs with:
133
+ `` journalctl -xeu remapper ``
134
+
135
+ ### Using clases to store state
136
+
137
+ With the above code, it seems that the double click is only getting recognised as a single
138
+ click. Through more trial and error, comparing working/non-working events we can also
139
+ figure out that the 2 left-click events only get interpreted as a double-click if there's
140
+ a gap between the events of atleast around 25ms (the first number after `` E: `` is a
141
+ timestamp of the event).
142
+
143
+ To emulate this, we can create a class to store a boolean state and then sleep for 25ms in
144
+ the middle of the double click events. A complete filter for this might look like:
145
+
146
+ ```
147
+ """Contour Rollermouse"""
148
+
149
+ import time
117
150
118
- class ContourMessageFilter(HIDMessageFilter) :
151
+ class ContourMessageFilter:
119
152
delay = False
120
153
121
- def filter_message_to_host (self, msg: bytes) -> Optional[ bytes] :
154
+ def filter_message (self, msg: bytes) -> bytes:
122
155
if len(msg) >= 10 and msg[9] == 0x80:
123
156
# Convert vendor specific double click (button 0x80), to normal double click (button 1).
124
- msg = msg[:9] + (b' \x01' if msg[1] else b' \x00' ) + msg[10:]
157
+ msg = msg[:9] + (b" \x01" if msg[1] else b" \x00" ) + msg[10:]
125
158
if msg[1]:
126
159
# Ensure a small delay between click events otherwise it won't register as a double click.
127
160
if self.delay:
128
161
time.sleep(0.025)
129
162
self.delay = not self.delay
130
- return b'\xa1' + (msg[0] + 3).to_bytes(1, 'big') + msg[1:]
163
+ return msg
164
+
165
+ message_filter = ContourMessageFilter().filter_message
131
166
```
132
167
133
- The above code converts that pesky `` 80 `` to a `` 01 `` and forces a 25ms gap between the 2 click events.
168
+ Note the last line which assigns the bound method to the `` message_filter `` name, so it
169
+ can still be imported the same way as our first example.
170
+
171
+ ### Suppressing events
172
+
173
+ If you want to block some messages from being sent entirely, instead of returning the
174
+ message, simply `` return None `` .
175
+
176
+ You will need to change the return type annotation in this case to `` -> bytes | None: `` .
177
+
178
+ ### Compiling for maximum performance
179
+
180
+ Once you have your filters working correctly, you can compile the code to ensure
181
+ maximum performance. Simply run:
134
182
135
- We also need to patch this into `` hid_devices.py `` by updating these lines:
136
183
```
137
- from contour_message_filter import ContourMessageFilter
138
- FILTERS = [
139
- {"id":"Default", "name":"Default"},
140
- {"id":"Mouse", "name":"Mouse"},
141
- {"id":"A1314", "name":"A1314"},
142
- {"id":"Contour", "name":"Contour"}
143
- ]
144
- FILTER_INSTANCES = {
145
- "Default" : HIDMessageFilter(), "Mouse":MouseMessageFilter(), "A1314":A1314MessageFilter(),
146
- "Contour": ContourMessageFilter(),
147
- }
184
+ cd $HOME/bthidhub/
185
+ mypyc
148
186
```
149
187
150
- Then we can select the "Contour" option in the web interface in order to enable the filter.
188
+ This may take upto 20 mins to complete.
189
+
190
+ ## Notes
191
+
192
+ The same techniques could be used to remap events when you just want to change
193
+ the behaviour of a device.
194
+
195
+ [ This post] ( http://who-t.blogspot.com/2018/12/understanding-hid-report-descriptors.html )
196
+ is super helpful in getting your head around HID descriptors.
151
197
152
- The same techniques can be used to remap events when you just want to change the behaviour of a device.
198
+ If you have a report descriptor and don't want to use hid-tools to decode, an online parser is:
199
+ https://eleccelerator.com/usbdescreqparser/
0 commit comments