Merge lp:~cr3/checkbox/xinput_resource into lp:checkbox
- xinput_resource
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Zygmunt Krynicki |
Approved revision: | 1648 |
Merged at revision: | 1693 |
Proposed branch: | lp:~cr3/checkbox/xinput_resource |
Merge into: | lp:checkbox |
Diff against target: |
798 lines (+725/-6) 7 files modified
checkbox/parsers/modinfo.py (+7/-6) checkbox/parsers/tests/fixtures/xinput_quantal.txt (+143/-0) checkbox/parsers/tests/fixtures/xinput_toshiba.txt (+166/-0) checkbox/parsers/tests/xinput.py (+135/-0) checkbox/parsers/xinput.py (+198/-0) debian/changelog (+2/-0) scripts/xinput_resource (+74/-0) |
To merge this branch: | bzr merge lp:~cr3/checkbox/xinput_resource |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Zygmunt Krynicki (community) | Approve | ||
Marc Tardif (community) | Needs Resubmitting | ||
Review via email: mp+124326@code.launchpad.net |
Commit message
Added xinput_resource script and xinput parser by cr3.
Description of the change
This is just the xinput_resource script with unit tests using fixtures for xinput values from a toshiba laptop and a quantal all-in-one system.
Zygmunt Krynicki (zyga) wrote : | # |
Zygmunt Krynicki (zyga) wrote : | # |
Oh, and generally, some more documentation is always better, especially on stuff that lands in checkbox.*
Marc Tardif (cr3) wrote : | # |
* Zygmunt Krynicki <email address hidden> [2012-09-14 08:21 -0000]:
> 40 - raise Excepion("Key not found: %s" % field)
> 41 + raise Exception("Key not found: %s" % field)
>
> Just bike shedding, sorry, I'd use KeyError here
Another way would be to just let the exception occur by removing
the conditional statement and just having this:
return self._modinfo[
However, even though self._modinfo is a dictionary, the exception
is about a missing key expected in the modinfo output. In other
words, the exception is not intended to convey a Python thing
but a modinfo thing. So, I might suggest a ModinfoError instead
of just a generic Exception. What do you think about that for
parser errors in general?
> 609 +<<<<<<< TREE
> 610 * patch/0.14.2: Fixed patch to rmtree instead of rmdir scripts directory.
> 611 * [FEATURE] debian/
> 612 preseed properties in environment_info plugin.
> 613 +=======
> 614 + * [FEATURE] scripts/
> 615 + resource script to test multitouch devices.
> 616 +>>>>>>> MERGE-SOURCE
>
> There's a typical conflict in our changelogs but I guess we cannot
> avoid that with our current model
Agreed, everyone is annoyed by that :)
> 394 +class XinputResult:
> 395 +
> 396 + def __init__(self):
> 397 + self.devices = {}
> 398 +
> 399 + def addXinputDevice
> 400 + self.devices[
> 401 +
> 402 + def addXinputDevice
> 403 + self.devices[
> 404 + self.devices[
>
> This should be in checkbox/
> interface that describes it. Otherwise if I want to use your parser
> I need to reverse engineer the interface that I'm expected to pass to
> XInputParser.run().
You're right. Ideally, we could define an abstract base class or an
interface in the checkbox.
are not supported natively in Python. How about something like this:
class XinputResult:
def addXinputDevice
"""Method to add an xinput device to this result."""
def addXinputDevice
"""Method to add a class under an xinput device."""
> 487 +DEVICE_RE = re.compile(
> 488 + r""".+?
> 489 + % ascii_uppercase)
> 490 +ATTRIBUTE_RE = re.compile(
> 491 + r"""(?P<
> 492 + % ascii_letters)
> 493 +CLASS_VALUE_RE = re.compile(
> 494 + r"""\d+\. Type: (?P<class>.+)""")
> 495 +LIST_VALUE_RE = re.compile(
> 496 + r"""((?:[^ "]|"[^"]*")+)""")
>
> I would really document those, as in how the data looks like and what
> we parse out.
Agreed, would you have an example for documenting regular expressions that
I would gladly follow?
Thanks for the review!
Zygmunt Krynicki (zyga) wrote : | # |
W dniu 14.09.2012 16:01, Marc Tardif pisze:
> * Zygmunt Krynicki <email address hidden> [2012-09-14 08:21 -0000]:
>> 40 - raise Excepion("Key not found: %s" % field)
>> 41 + raise Exception("Key not found: %s" % field)
>>
>> Just bike shedding, sorry, I'd use KeyError here
>
> Another way would be to just let the exception occur by removing
> the conditional statement and just having this:
>
> return self._modinfo[
>
> However, even though self._modinfo is a dictionary, the exception
> is about a missing key expected in the modinfo output. In other
> words, the exception is not intended to convey a Python thing
> but a modinfo thing. So, I might suggest a ModinfoError instead
> of just a generic Exception. What do you think about that for
> parser errors in general?
Hmm, I see.
I rarely use raw Exceptions, typically I wrap the logic so that you have
a function, feed it some data and if the data makes the function go boom
but there is no need for a dedicated exception subclass I use
ValueError. I don't claim it's superior much but one thing it allows you
to do to though is to catch ValueError[s] and not all Exception[s].
>> 394 +class XinputResult:
>> 395 +
>> 396 + def __init__(self):
>> 397 + self.devices = {}
>> 398 +
>> 399 + def addXinputDevice
>> 400 + self.devices[
>> 401 +
>> 402 + def addXinputDevice
>> 403 + self.devices[
>> 404 + self.devices[
>>
>> This should be in checkbox/
>> interface that describes it. Otherwise if I want to use your parser
>> I need to reverse engineer the interface that I'm expected to pass to
>> XInputParser.run().
>
> You're right. Ideally, we could define an abstract base class or an
> interface in the checkbox.
> are not supported natively in Python. How about something like this:
Actually I grew fond of purely-A ABCs as interfaces.
from abc import ABCMeta, abstractmethod
class IXInputResult:
metaclass = ABCMeta
@abstractmethod
def addXinputDevice
"""
docs
"""
The static part of you could even require isinstance(result,
IXInputResult) but in general it's a simple way to ensure we understand
the responsibilities of various classes without creating fat abstract
base classes.
(ps, any reason why you named it X{lowercase i}input and not XInput?
>> 487 +DEVICE_RE = re.compile(
>> 488 + r""".+?
>> 489 + % ascii_uppercase)
>> 490 +ATTRIBUTE_RE = re.compile(
>> 491 + r"""(?P<
>> 492 + % ascii_letters)
>> 493 +CLASS_VALUE_RE = re.compile(
>> 494 + r"""\d+\. Type: (?P<class>.+)""")
>> 495 +LIST_VALUE_RE = re.compile(
>> 496 + r"""((?:[^ "]|"[^"]*")+)""")
>>
>> I would really document those, as in how the data looks like and what
>> we parse out.
>
> Agreed, would you have an example for documenting regular expressions that
> I would gladly follow?
Not really, frankly I needed to look-up a few of the syntax pieces here
so maybe just explain that in a ...
Marc Tardif (cr3) wrote : | # |
* Zygmunt Krynicki <email address hidden> [2012-09-14 14:13 -0000]:
[snip]
> Actually I grew fond of purely-A ABCs as interfaces.
>
> from abc import ABCMeta, abstractmethod
>
> class IXInputResult:
> metaclass = ABCMeta
>
> @abstractmethod
> def addXinputDevice
> """
> docs
> """
>
> The static part of you could even require isinstance(result,
> IXInputResult) but in general it's a simple way to ensure we understand
> the responsibilities of various classes without creating fat abstract
> base classes.
Following discussions on IRC, I just declared a base class with empty
functions and comments.
> (ps, any reason why you named it X{lowercase i}input and not XInput?
Same reason as the DmidecodeParser class; "dmidecode" and "xinput" are
command names that I consider as one word.
> Not really, frankly I needed to look-up a few of the syntax pieces here
> so maybe just explain that in a few # comments what the thing being
> parsed is. More usefulness than form or convention.
Thanks for the advice, that inspired me to also add a unit test for each
regular expression. I hope you like all the comments!
Marc Tardif (cr3) wrote : | # |
I need to learn how to set the review to "resubmit" by email...
Zygmunt Krynicki (zyga) wrote : | # |
Awesome!
I'll merge it in a sec. Would you mind waiting a few more hours for the initial tarmac test setup?
Zygmunt Krynicki (zyga) wrote : | # |
Attempt to merge into lp:checkbox failed due to conflicts:
text conflict in debian/changelog
Zygmunt Krynicki (zyga) wrote : | # |
There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.
Preview Diff
1 | === modified file 'checkbox/parsers/modinfo.py' |
2 | --- checkbox/parsers/modinfo.py 2012-08-30 16:03:05 +0000 |
3 | +++ checkbox/parsers/modinfo.py 2012-09-21 13:35:25 +0000 |
4 | @@ -17,6 +17,7 @@ |
5 | # along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
6 | # |
7 | |
8 | + |
9 | class ModinfoParser: |
10 | """ |
11 | Parser for modinfo information. |
12 | @@ -57,25 +58,25 @@ |
13 | for line in stream.splitlines(): |
14 | # At this point, stream should be the stdout from the modinfo |
15 | # command, in a list. |
16 | - key, data = line.split(':',1) |
17 | + key, data = line.split(':', 1) |
18 | data = data.strip() |
19 | - # First, we need to handle alias, parm, firmware, and depends |
20 | + # First, we need to handle alias, parm, firmware, and depends |
21 | # because there can be multiple lines of output for these. |
22 | - if key in ('alias', 'depend', 'firmware', 'parm'): |
23 | + if key in ('alias', 'depend', 'firmware', 'parm',): |
24 | self._modinfo[key].append(data) |
25 | # Now handle unknown keys |
26 | elif key not in self._modinfo.keys(): |
27 | self._modinfo[key] = ("WARNING: Unknown Key %s providing " |
28 | - "data: %s") % (key,data) |
29 | + "data: %s") % (key, data) |
30 | # And finally known keys |
31 | else: |
32 | self._modinfo[key] = data |
33 | - |
34 | + |
35 | def get_all(self): |
36 | return self._modinfo |
37 | |
38 | def get_field(self, field): |
39 | if field not in self._modinfo.keys(): |
40 | - raise Excepion("Key not found: %s" % field) |
41 | + raise Exception("Key not found: %s" % field) |
42 | else: |
43 | return self._modinfo[field] |
44 | |
45 | === added directory 'checkbox/parsers/tests/fixtures' |
46 | === added file 'checkbox/parsers/tests/fixtures/xinput_quantal.txt' |
47 | --- checkbox/parsers/tests/fixtures/xinput_quantal.txt 1970-01-01 00:00:00 +0000 |
48 | +++ checkbox/parsers/tests/fixtures/xinput_quantal.txt 2012-09-21 13:35:25 +0000 |
49 | @@ -0,0 +1,143 @@ |
50 | +⎡ Virtual core pointer id=2 [master pointer (3)] |
51 | +Reporting 4 classes: |
52 | +Class originated from: 10. Type: XIButtonClass |
53 | +Buttons supported: 10 |
54 | +Button labels: "Button Unknown" "Button Unknown" "Button Unknown" |
55 | +"Button Wheel Up" "Button Wheel Down" "Button Horiz Wheel Left" "Button |
56 | +Horiz Wheel Right" None None None |
57 | +Button state: |
58 | +Class originated from: 10. Type: XIValuatorClass |
59 | +Detail for Valuator 0: |
60 | +Label: Abs MT Position X |
61 | +Range: 0.000000 - 1919.000000 |
62 | +Resolution: 0 units/m |
63 | +Mode: absolute |
64 | +Current value: 1664.000000 |
65 | +Class originated from: 10. Type: XIValuatorClass |
66 | +Detail for Valuator 1: |
67 | +Label: Abs MT Position Y |
68 | +Range: 0.000000 - 1079.000000 |
69 | +Resolution: 0 units/m |
70 | +Mode: absolute |
71 | +Current value: 932.000000 |
72 | +Class originated from: 0. Type: XITouchClass |
73 | +Touch mode: direct |
74 | +Max number of touches: 17 |
75 | + |
76 | +⎜ ↳ Virtual core XTEST pointer id=4 [slave pointer (2)] |
77 | +Reporting 3 classes: |
78 | +Class originated from: 4. Type: XIButtonClass |
79 | +Buttons supported: 10 |
80 | +Button labels: "Button Left" "Button Middle" "Button Right" "Button |
81 | +Wheel Up" "Button Wheel Down" "Button Horiz Wheel Left" "Button Horiz |
82 | +Wheel Right" None None None |
83 | +Button state: |
84 | +Class originated from: 4. Type: XIValuatorClass |
85 | +Detail for Valuator 0: |
86 | +Label: Rel X |
87 | +Range: -1.000000 - -1.000000 |
88 | +Resolution: 0 units/m |
89 | +Mode: relative |
90 | +Class originated from: 4. Type: XIValuatorClass |
91 | +Detail for Valuator 1: |
92 | +Label: Rel Y |
93 | +Range: -1.000000 - -1.000000 |
94 | +Resolution: 0 units/m |
95 | +Mode: relative |
96 | + |
97 | +⎜ ↳ Quanta OpticalTouchScreen id=10 [slave pointer (2)] |
98 | +Reporting 4 classes: |
99 | +Class originated from: 10. Type: XIButtonClass |
100 | +Buttons supported: 5 |
101 | +Button labels: "Button Unknown" "Button Unknown" "Button Unknown" |
102 | +"Button Wheel Up" "Button Wheel Down" |
103 | +Button state: |
104 | +Class originated from: 10. Type: XIValuatorClass |
105 | +Detail for Valuator 0: |
106 | +Label: Abs MT Position X |
107 | +Range: 0.000000 - 1919.000000 |
108 | +Resolution: 0 units/m |
109 | +Mode: absolute |
110 | +Current value: 1664.000000 |
111 | +Class originated from: 10. Type: XIValuatorClass |
112 | +Detail for Valuator 1: |
113 | +Label: Abs MT Position Y |
114 | +Range: 0.000000 - 1079.000000 |
115 | +Resolution: 0 units/m |
116 | +Mode: absolute |
117 | +Current value: 932.000000 |
118 | +Class originated from: 0. Type: XITouchClass |
119 | +Touch mode: direct |
120 | +Max number of touches: 9 |
121 | + |
122 | +⎜ ↳ MCE IR Keyboard/Mouse (nuvoton-cir) id=12 [slave pointer (2)] |
123 | +Reporting 4 classes: |
124 | +Class originated from: 12. Type: XIButtonClass |
125 | +Buttons supported: 5 |
126 | +Button labels: "Button Left" "Button Unknown" "Button Right" "Button |
127 | +Wheel Up" "Button Wheel Down" |
128 | +Button state: |
129 | +Class originated from: 12. Type: XIKeyClass |
130 | +Keycodes supported: 248 |
131 | +Class originated from: 12. Type: XIValuatorClass |
132 | +Detail for Valuator 0: |
133 | +Label: Rel X |
134 | +Range: -1.000000 - -1.000000 |
135 | +Resolution: 1 units/m |
136 | +Mode: relative |
137 | +Class originated from: 12. Type: XIValuatorClass |
138 | +Detail for Valuator 1: |
139 | +Label: Rel Y |
140 | +Range: -1.000000 - -1.000000 |
141 | +Resolution: 1 units/m |
142 | +Mode: relative |
143 | + |
144 | +⎣ Virtual core keyboard id=3 [master keyboard (2)] |
145 | +Reporting 1 classes: |
146 | +Class originated from: 14. Type: XIKeyClass |
147 | +Keycodes supported: 248 |
148 | + |
149 | +↳ Virtual core XTEST keyboard id=5 [slave keyboard (3)] |
150 | +Reporting 1 classes: |
151 | +Class originated from: 5. Type: XIKeyClass |
152 | +Keycodes supported: 248 |
153 | + |
154 | +↳ Power Button id=6 [slave keyboard (3)] |
155 | +Reporting 1 classes: |
156 | +Class originated from: 6. Type: XIKeyClass |
157 | +Keycodes supported: 248 |
158 | + |
159 | +↳ Power Button id=7 [slave keyboard (3)] |
160 | +Reporting 1 classes: |
161 | +Class originated from: 7. Type: XIKeyClass |
162 | +Keycodes supported: 248 |
163 | + |
164 | +↳ Laptop_Integrated_Webcam_2M id=8 [slave keyboard (3)] |
165 | +Reporting 1 classes: |
166 | +Class originated from: 8. Type: XIKeyClass |
167 | +Keycodes supported: 248 |
168 | + |
169 | +↳ HID 413c:8161 id=9 [slave keyboard (3)] |
170 | +Reporting 1 classes: |
171 | +Class originated from: 9. Type: XIKeyClass |
172 | +Keycodes supported: 248 |
173 | + |
174 | +↳ Nuvoton w836x7hg Infrared Remote Transceiver id=11 [slave keyboard (3)] |
175 | +Reporting 1 classes: |
176 | +Class originated from: 11. Type: XIKeyClass |
177 | +Keycodes supported: 248 |
178 | + |
179 | +↳ Dell AIO WMI hotkeys id=13 [slave keyboard (3)] |
180 | +Reporting 1 classes: |
181 | +Class originated from: 13. Type: XIKeyClass |
182 | +Keycodes supported: 248 |
183 | + |
184 | +↳ Chicony USB Keyboard id=14 [slave keyboard (3)] |
185 | +Reporting 1 classes: |
186 | +Class originated from: 14. Type: XIKeyClass |
187 | +Keycodes supported: 248 |
188 | + |
189 | +↳ Chicony USB Keyboard id=15 [slave keyboard (3)] |
190 | +Reporting 1 classes: |
191 | +Class originated from: 15. Type: XIKeyClass |
192 | +Keycodes supported: 248 |
193 | |
194 | === added file 'checkbox/parsers/tests/fixtures/xinput_toshiba.txt' |
195 | --- checkbox/parsers/tests/fixtures/xinput_toshiba.txt 1970-01-01 00:00:00 +0000 |
196 | +++ checkbox/parsers/tests/fixtures/xinput_toshiba.txt 2012-09-21 13:35:25 +0000 |
197 | @@ -0,0 +1,166 @@ |
198 | +⎡ Virtual core pointer id=2 [master pointer (3)] |
199 | + Reporting 8 classes: |
200 | + Class originated from: 12. Type: XIButtonClass |
201 | + Buttons supported: 12 |
202 | + Button labels: "Button Left" "Button Middle" "Button Right" "Button Wheel Up" "Button Wheel Down" "Button Horiz Wheel Left" "Button Horiz Wheel Right" None None None None None |
203 | + Button state: |
204 | + Class originated from: 12. Type: XIValuatorClass |
205 | + Detail for Valuator 0: |
206 | + Label: Rel X |
207 | + Range: 0.000000 - 2000.000000 |
208 | + Resolution: 0 units/m |
209 | + Mode: relative |
210 | + Class originated from: 12. Type: XIValuatorClass |
211 | + Detail for Valuator 1: |
212 | + Label: Rel Y |
213 | + Range: 0.000000 - 1400.000000 |
214 | + Resolution: 0 units/m |
215 | + Mode: relative |
216 | + Class originated from: 12. Type: XIValuatorClass |
217 | + Detail for Valuator 2: |
218 | + Label: Rel Horiz Scroll |
219 | + Range: 0.000000 - -1.000000 |
220 | + Resolution: 0 units/m |
221 | + Mode: relative |
222 | + Class originated from: 12. Type: XIValuatorClass |
223 | + Detail for Valuator 3: |
224 | + Label: Rel Vert Scroll |
225 | + Range: 0.000000 - -1.000000 |
226 | + Resolution: 0 units/m |
227 | + Mode: relative |
228 | + Class originated from: 12. Type: XIScrollClass |
229 | + Scroll info for Valuator 2 |
230 | + type: 2 (horizontal) |
231 | + increment: 48.000000 |
232 | + flags: 0x0 |
233 | + Class originated from: 12. Type: XIScrollClass |
234 | + Scroll info for Valuator 3 |
235 | + type: 1 (vertical) |
236 | + increment: 48.000000 |
237 | + flags: 0x0 |
238 | + Class originated from: 0. Type: XITouchClass |
239 | + Touch mode: dependent |
240 | + Max number of touches: 2 |
241 | + |
242 | +⎜ ↳ Virtual core XTEST pointer id=4 [slave pointer (2)] |
243 | + Reporting 3 classes: |
244 | + Class originated from: 4. Type: XIButtonClass |
245 | + Buttons supported: 10 |
246 | + Button labels: "Button Left" "Button Middle" "Button Right" "Button Wheel Up" "Button Wheel Down" "Button Horiz Wheel Left" "Button Horiz Wheel Right" None None None |
247 | + Button state: |
248 | + Class originated from: 4. Type: XIValuatorClass |
249 | + Detail for Valuator 0: |
250 | + Label: Rel X |
251 | + Range: -1.000000 - -1.000000 |
252 | + Resolution: 0 units/m |
253 | + Mode: relative |
254 | + Class originated from: 4. Type: XIValuatorClass |
255 | + Detail for Valuator 1: |
256 | + Label: Rel Y |
257 | + Range: -1.000000 - -1.000000 |
258 | + Resolution: 0 units/m |
259 | + Mode: relative |
260 | + |
261 | +⎜ ↳ DualPoint Stick id=11 [slave pointer (2)] |
262 | + Reporting 3 classes: |
263 | + Class originated from: 11. Type: XIButtonClass |
264 | + Buttons supported: 7 |
265 | + Button labels: "Button Left" "Button Middle" "Button Right" "Button Wheel Up" "Button Wheel Down" "Button Horiz Wheel Left" "Button Horiz Wheel Right" |
266 | + Button state: |
267 | + Class originated from: 11. Type: XIValuatorClass |
268 | + Detail for Valuator 0: |
269 | + Label: Rel X |
270 | + Range: -1.000000 - -1.000000 |
271 | + Resolution: 1 units/m |
272 | + Mode: relative |
273 | + Class originated from: 11. Type: XIValuatorClass |
274 | + Detail for Valuator 1: |
275 | + Label: Rel Y |
276 | + Range: -1.000000 - -1.000000 |
277 | + Resolution: 1 units/m |
278 | + Mode: relative |
279 | + |
280 | +⎜ ↳ AlpsPS/2 ALPS DualPoint TouchPad id=12 [slave pointer (2)] |
281 | + Reporting 8 classes: |
282 | + Class originated from: 12. Type: XIButtonClass |
283 | + Buttons supported: 12 |
284 | + Button labels: "Button Left" "Button Middle" "Button Right" "Button Wheel Up" "Button Wheel Down" "Button Horiz Wheel Left" "Button Horiz Wheel Right" None None None None None |
285 | + Button state: |
286 | + Class originated from: 12. Type: XIValuatorClass |
287 | + Detail for Valuator 0: |
288 | + Label: Rel X |
289 | + Range: 0.000000 - 2000.000000 |
290 | + Resolution: 0 units/m |
291 | + Mode: relative |
292 | + Class originated from: 12. Type: XIValuatorClass |
293 | + Detail for Valuator 1: |
294 | + Label: Rel Y |
295 | + Range: 0.000000 - 1400.000000 |
296 | + Resolution: 0 units/m |
297 | + Mode: relative |
298 | + Class originated from: 12. Type: XIValuatorClass |
299 | + Detail for Valuator 2: |
300 | + Label: Rel Horiz Scroll |
301 | + Range: 0.000000 - -1.000000 |
302 | + Resolution: 0 units/m |
303 | + Mode: relative |
304 | + Class originated from: 12. Type: XIValuatorClass |
305 | + Detail for Valuator 3: |
306 | + Label: Rel Vert Scroll |
307 | + Range: 0.000000 - -1.000000 |
308 | + Resolution: 0 units/m |
309 | + Mode: relative |
310 | + Class originated from: 12. Type: XIScrollClass |
311 | + Scroll info for Valuator 2 |
312 | + type: 2 (horizontal) |
313 | + increment: 48.000000 |
314 | + flags: 0x0 |
315 | + Class originated from: 12. Type: XIScrollClass |
316 | + Scroll info for Valuator 3 |
317 | + type: 1 (vertical) |
318 | + increment: 48.000000 |
319 | + flags: 0x0 |
320 | + Class originated from: 0. Type: XITouchClass |
321 | + Touch mode: dependent |
322 | + Max number of touches: 2 |
323 | + |
324 | +⎣ Virtual core keyboard id=3 [master keyboard (2)] |
325 | + Reporting 1 classes: |
326 | + Class originated from: 10. Type: XIKeyClass |
327 | + Keycodes supported: 248 |
328 | + |
329 | + ↳ Virtual core XTEST keyboard id=5 [slave keyboard (3)] |
330 | + Reporting 1 classes: |
331 | + Class originated from: 5. Type: XIKeyClass |
332 | + Keycodes supported: 248 |
333 | + |
334 | + ↳ Power Button id=6 [slave keyboard (3)] |
335 | + Reporting 1 classes: |
336 | + Class originated from: 6. Type: XIKeyClass |
337 | + Keycodes supported: 248 |
338 | + |
339 | + ↳ Video Bus id=7 [slave keyboard (3)] |
340 | + Reporting 1 classes: |
341 | + Class originated from: 7. Type: XIKeyClass |
342 | + Keycodes supported: 248 |
343 | + |
344 | + ↳ Power Button id=8 [slave keyboard (3)] |
345 | + Reporting 1 classes: |
346 | + Class originated from: 8. Type: XIKeyClass |
347 | + Keycodes supported: 248 |
348 | + |
349 | + ↳ CNF9055 id=9 [slave keyboard (3)] |
350 | + Reporting 1 classes: |
351 | + Class originated from: 9. Type: XIKeyClass |
352 | + Keycodes supported: 248 |
353 | + |
354 | + ↳ AT Translated Set 2 keyboard id=10 [slave keyboard (3)] |
355 | + Reporting 1 classes: |
356 | + Class originated from: 10. Type: XIKeyClass |
357 | + Keycodes supported: 248 |
358 | + |
359 | + ↳ Toshiba input device id=13 [slave keyboard (3)] |
360 | + Reporting 1 classes: |
361 | + Class originated from: 13. Type: XIKeyClass |
362 | + Keycodes supported: 248 |
363 | + |
364 | |
365 | === added file 'checkbox/parsers/tests/xinput.py' |
366 | --- checkbox/parsers/tests/xinput.py 1970-01-01 00:00:00 +0000 |
367 | +++ checkbox/parsers/tests/xinput.py 2012-09-21 13:35:25 +0000 |
368 | @@ -0,0 +1,135 @@ |
369 | +# -*- coding: utf-8 -*- |
370 | +# |
371 | +# This file is part of Checkbox. |
372 | +# |
373 | +# Copyright 2012 Canonical Ltd. |
374 | +# |
375 | +# Checkbox is free software: you can redistribute it and/or modify |
376 | +# it under the terms of the GNU General Public License as published by |
377 | +# the Free Software Foundation, either version 3 of the License, or |
378 | +# (at your option) any later version. |
379 | +# |
380 | +# Checkbox is distributed in the hope that it will be useful, |
381 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
382 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
383 | +# GNU General Public License for more details. |
384 | +# |
385 | +# You should have received a copy of the GNU General Public License |
386 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
387 | +# |
388 | +import os |
389 | + |
390 | +from unittest import TestCase |
391 | + |
392 | +from checkbox.parsers.xinput import ( |
393 | + DEVICE_RE, |
394 | + ATTRIBUTE_RE, |
395 | + CLASS_VALUE_RE, |
396 | + LIST_VALUE_RE, |
397 | + IXinputResult, |
398 | + XinputParser, |
399 | + ) |
400 | + |
401 | + |
402 | +class TestDeviceRe(TestCase): |
403 | + |
404 | + def test_string(self): |
405 | + match = DEVICE_RE.match( |
406 | + """⎡ Virtual core pointer """ |
407 | + """id=2 [master pointer (3)]""") |
408 | + self.assertTrue(match) |
409 | + self.assertEquals(match.group("name"), "Virtual core pointer") |
410 | + self.assertEquals(match.group("id"), "2") |
411 | + |
412 | + |
413 | +class TestAttributeRe(TestCase): |
414 | + |
415 | + def test_string(self): |
416 | + match = ATTRIBUTE_RE.match("""Buttons supported: 12""") |
417 | + self.assertTrue(match) |
418 | + self.assertEquals(match.group("key"), "Buttons supported") |
419 | + self.assertEquals(match.group("value"), "12") |
420 | + |
421 | + |
422 | +class TestClassValueRe(TestCase): |
423 | + |
424 | + def test_string(self): |
425 | + match = CLASS_VALUE_RE.match("""12. Type: XIButtonClass""") |
426 | + self.assertTrue(match) |
427 | + self.assertEquals(match.group("class"), "XIButtonClass") |
428 | + |
429 | + |
430 | +class TestListValueRe(TestCase): |
431 | + |
432 | + def test_string(self): |
433 | + elements = LIST_VALUE_RE.split( |
434 | + """"Button Horiz Wheel Right" None None""")[1::2] |
435 | + self.assertTrue(elements) |
436 | + self.assertEquals(len(elements), 3) |
437 | + self.assertEquals(elements[0], '"Button Horiz Wheel Right"') |
438 | + self.assertEquals(elements[1], "None") |
439 | + self.assertEquals(elements[2], "None") |
440 | + |
441 | + |
442 | +class XinputResult(IXinputResult): |
443 | + |
444 | + def __init__(self): |
445 | + self.devices = {} |
446 | + |
447 | + def addXinputDevice(self, device): |
448 | + self.devices[device["id"]] = device |
449 | + |
450 | + def addXinputDeviceClass(self, device, device_class): |
451 | + self.devices[device["id"]].setdefault("classes", []) |
452 | + self.devices[device["id"]]["classes"].append(device_class) |
453 | + |
454 | + |
455 | +class TestXinputParser(TestCase): |
456 | + |
457 | + def getFixture(self, name): |
458 | + return os.path.join(os.path.dirname(__file__), "fixtures", name) |
459 | + |
460 | + def getParser(self, name): |
461 | + fixture = self.getFixture(name) |
462 | + stream = open(fixture) |
463 | + return XinputParser(stream) |
464 | + |
465 | + def getResult(self, name): |
466 | + parser = self.getParser(name) |
467 | + result = XinputResult() |
468 | + parser.run(result) |
469 | + return result |
470 | + |
471 | + def test_number_of_devices_with_spaces(self): |
472 | + """The toshiba xinput with spaces contains 12 devices.""" |
473 | + result = self.getResult("xinput_toshiba.txt") |
474 | + self.assertEquals(len(result.devices), 12) |
475 | + |
476 | + def test_number_of_devices_without_spaces(self): |
477 | + """The quantal xinput without spaces contains 14 devices.""" |
478 | + result = self.getResult("xinput_quantal.txt") |
479 | + self.assertEquals(len(result.devices), 14) |
480 | + |
481 | + def test_multitouch_touchpad_device(self): |
482 | + """The toshiba xinput contains a multitouch touchpad device.""" |
483 | + result = self.getResult("xinput_toshiba.txt") |
484 | + devices = [device for device in result.devices.values() |
485 | + if device["name"] == "AlpsPS/2 ALPS DualPoint TouchPad"] |
486 | + self.assertEquals(len(devices), 1) |
487 | + |
488 | + classes = [cls for cls in devices[0]["classes"] |
489 | + if cls["class"] == "XITouchClass"] |
490 | + self.assertEquals(len(classes), 1) |
491 | + self.assertEquals(classes[0]["touch_mode"], "dependent") |
492 | + |
493 | + def test_multitouch_touchscreen_device(self): |
494 | + """The quantal xinput contains a multitouch touchscreen device.""" |
495 | + result = self.getResult("xinput_quantal.txt") |
496 | + devices = [device for device in result.devices.values() |
497 | + if device["name"] == "Quanta OpticalTouchScreen"] |
498 | + self.assertEquals(len(devices), 1) |
499 | + |
500 | + classes = [cls for cls in devices[0]["classes"] |
501 | + if cls["class"] == "XITouchClass"] |
502 | + self.assertEquals(len(classes), 1) |
503 | + self.assertEquals(classes[0]["touch_mode"], "direct") |
504 | |
505 | === added file 'checkbox/parsers/xinput.py' |
506 | --- checkbox/parsers/xinput.py 1970-01-01 00:00:00 +0000 |
507 | +++ checkbox/parsers/xinput.py 2012-09-21 13:35:25 +0000 |
508 | @@ -0,0 +1,198 @@ |
509 | +# -*- coding: utf-8 -*- |
510 | +# |
511 | +# This file is part of Checkbox. |
512 | +# |
513 | +# Copyright 2012 Canonical Ltd. |
514 | +# |
515 | +# Checkbox is free software: you can redistribute it and/or modify |
516 | +# it under the terms of the GNU General Public License as published by |
517 | +# the Free Software Foundation, either version 3 of the License, or |
518 | +# (at your option) any later version. |
519 | +# |
520 | +# Checkbox is distributed in the hope that it will be useful, |
521 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
522 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
523 | +# GNU General Public License for more details. |
524 | +# |
525 | +# You should have received a copy of the GNU General Public License |
526 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
527 | +# |
528 | +import re |
529 | + |
530 | +from string import ( |
531 | + ascii_letters, |
532 | + ascii_uppercase, |
533 | + ) |
534 | + |
535 | + |
536 | +# Device string to match: |
537 | +# ⎡ Virtual core pointer id=2 [master pointer (3)] |
538 | +DEVICE_RE = re.compile( |
539 | + r""".+?(?P<name>[%s].+?) *\sid=(?P<id>\d+)""" |
540 | + % ascii_uppercase) |
541 | + |
542 | +# Attribute string to match: |
543 | +# Buttons supported: 12 |
544 | +ATTRIBUTE_RE = re.compile( |
545 | + r"""(?P<key>[%s].+?): (?P<value>.+)""" |
546 | + % ascii_letters) |
547 | + |
548 | +# Class string to match: |
549 | +# 12. Type: XIButtonClass |
550 | +CLASS_VALUE_RE = re.compile( |
551 | + r"""\d+\. Type: (?P<class>.+)""") |
552 | + |
553 | +# List string to split: |
554 | +# "Button Horiz Wheel Right" None None |
555 | +LIST_VALUE_RE = re.compile( |
556 | + r"""((?:[^ "]|"[^"]*")+)""") |
557 | + |
558 | + |
559 | +class IXinputResult: |
560 | + """ |
561 | + Base class for a result passed to the XinputParser run method. |
562 | + """ |
563 | + |
564 | + def addXinputDevice(self, device): |
565 | + """Method to add an xinput device to this result.""" |
566 | + |
567 | + def addXinputDeviceClass(self, device, device_class): |
568 | + """Method to add a class under an xinput device.""" |
569 | + |
570 | + |
571 | +class XinputParser: |
572 | + """ |
573 | + Parser for the xinput command. |
574 | + """ |
575 | + |
576 | + _key_map = { |
577 | + "Buttons supported": "buttons_supported", |
578 | + "Button labels": "button_labels", |
579 | + "Button state": "button_state", |
580 | + "Class originated from": "class", |
581 | + "Keycodes supported": "keycodes_supported", |
582 | + "Touch mode": "touch_mode", |
583 | + "Max number of touches": "max_touch", |
584 | + } |
585 | + |
586 | + def __init__(self, stream): |
587 | + """ |
588 | + Construct a parser with the given stream. |
589 | + |
590 | + The stream is expected to contain the output of the command: |
591 | + xinput --list --long |
592 | + """ |
593 | + self.stream = stream |
594 | + |
595 | + def _parseKey(self, key): |
596 | + """ |
597 | + Parse the given key into a sanitized string. |
598 | + |
599 | + Returns a string in lower case without any spaces, or None if |
600 | + the key is not recognized. |
601 | + """ |
602 | + if " " in key: |
603 | + return self._key_map.get(key) |
604 | + else: |
605 | + return key.lower() |
606 | + |
607 | + def _parseValue(self, value): |
608 | + """ |
609 | + Parse the given value into a sanitized object. |
610 | + |
611 | + Returns a string with leading and trailing spaces stripped, |
612 | + or a list of the value contains double quotes, or None if the |
613 | + value is empty. |
614 | + """ |
615 | + if value is not None: |
616 | + value = value.strip() |
617 | + if not value: |
618 | + return None |
619 | + |
620 | + match = CLASS_VALUE_RE.match(value) |
621 | + if match: |
622 | + return match.group("class") |
623 | + |
624 | + if '"' in value: |
625 | + return list(self._parseList(value)) |
626 | + |
627 | + return value |
628 | + |
629 | + def _parseList(self, string): |
630 | + """ |
631 | + Parse the given string into a list. |
632 | + |
633 | + The string can contain double quoted elements that are stripped |
634 | + of the quotes, or the string "None" that is replaced by None, |
635 | + or just space separated strings. |
636 | + """ |
637 | + for element in LIST_VALUE_RE.split(string)[1::2]: |
638 | + if element.startswith('"') and element.endswith('"'): |
639 | + yield element.strip('"') |
640 | + elif element == "None": |
641 | + yield None |
642 | + |
643 | + def run(self, result): |
644 | + """ |
645 | + Run the parser on the stream and add to the given result. |
646 | + |
647 | + The result is a derived instance of the IXinputResult base class |
648 | + to which results are added incrementally as the stream is parsed. |
649 | + """ |
650 | + output = self.stream.read() |
651 | + for record in re.split(r"\n{2,}", output): |
652 | + record = record.strip() |
653 | + |
654 | + # Skip empty records |
655 | + if not record: |
656 | + continue |
657 | + |
658 | + lines = record.split("\n") |
659 | + |
660 | + # Parse device |
661 | + line = lines.pop(0) |
662 | + match = DEVICE_RE.match(line) |
663 | + if not match: |
664 | + continue |
665 | + |
666 | + device = { |
667 | + "id": int(match.group("id")), |
668 | + "name": match.group("name"), |
669 | + } |
670 | + result.addXinputDevice(device) |
671 | + |
672 | + # Parse device classes |
673 | + device_class = {} |
674 | + prefix = "" |
675 | + |
676 | + for line in lines: |
677 | + line = line.strip() |
678 | + |
679 | + # Skip lines with an unsupported attribute |
680 | + match = ATTRIBUTE_RE.match(line) |
681 | + if not match: |
682 | + if line.startswith("Scroll"): |
683 | + prefix = "scroll_" |
684 | + elif line.startswith("Detail"): |
685 | + prefix = "detail_" |
686 | + continue |
687 | + |
688 | + # Skip lines with an unsupported key |
689 | + key = self._parseKey(match.group("key")) |
690 | + if not key: |
691 | + continue |
692 | + |
693 | + value = self._parseValue(match.group("value")) |
694 | + |
695 | + # Special case for the class |
696 | + if key == "class" and device_class: |
697 | + result.addXinputDeviceClass(device, device_class) |
698 | + device_class = {} |
699 | + prefix = "" |
700 | + |
701 | + device_class[prefix + key] = value |
702 | + |
703 | + if device_class: |
704 | + result.addXinputDeviceClass(device, device_class) |
705 | + |
706 | + return result |
707 | |
708 | === modified file 'debian/changelog' |
709 | --- debian/changelog 2012-09-21 02:16:21 +0000 |
710 | +++ debian/changelog 2012-09-21 13:35:25 +0000 |
711 | @@ -63,6 +63,8 @@ |
712 | [Marc Tardif] |
713 | * scripts/touchpad_scroll_resource: Added support for systems without |
714 | a touchpad (LP #1045066) |
715 | + * [FEATURE] scripts/xinput_resource, checkbox/parsers/xinput.py: Xinput |
716 | + resource script to test multitouch devices. |
717 | * patch/0.14.2: Fixed patch to rmtree instead of rmdir scripts directory. |
718 | * [FEATURE] debian/checkbox.templates, debian/checkbox.config: Added support to |
719 | preseed properties in environment_info plugin. |
720 | |
721 | === added file 'scripts/xinput_resource' |
722 | --- scripts/xinput_resource 1970-01-01 00:00:00 +0000 |
723 | +++ scripts/xinput_resource 2012-09-21 13:35:25 +0000 |
724 | @@ -0,0 +1,74 @@ |
725 | +#!/usr/bin/python3 |
726 | +# |
727 | +# This file is part of Checkbox. |
728 | +# |
729 | +# Copyright 2012 Canonical Ltd. |
730 | +# |
731 | +# Checkbox is free software: you can redistribute it and/or modify |
732 | +# it under the terms of the GNU General Public License as published by |
733 | +# the Free Software Foundation, either version 3 of the License, or |
734 | +# (at your option) any later version. |
735 | +# |
736 | +# Checkbox is distributed in the hope that it will be useful, |
737 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
738 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
739 | +# GNU General Public License for more details. |
740 | +# |
741 | +# You should have received a copy of the GNU General Public License |
742 | +# along with Checkbox. If not, see <http://www.gnu.org/licenses/>. |
743 | +# |
744 | +import os |
745 | +import sys |
746 | + |
747 | +from argparse import ArgumentParser |
748 | + |
749 | +from checkbox.lib.template import Template |
750 | +from checkbox.parsers.xinput import ( |
751 | + IXinputResult, |
752 | + XinputParser, |
753 | + ) |
754 | + |
755 | + |
756 | +# Command to retrieve xinput information. |
757 | +COMMAND = "xinput --list --long" |
758 | + |
759 | + |
760 | +class XinputResult(IXinputResult): |
761 | + |
762 | + def __init__(self): |
763 | + self.elements = [] |
764 | + |
765 | + def addXinputDevice(self, device): |
766 | + device["type"] = "device" |
767 | + self.elements.append(device) |
768 | + |
769 | + def addXinputDeviceClass(self, device, device_class): |
770 | + device_class["type"] = "class" |
771 | + device_class.update(device) |
772 | + self.elements.append(device_class) |
773 | + |
774 | + |
775 | +def main(): |
776 | + parser = ArgumentParser() |
777 | + parser.add_argument("filename", nargs='?', |
778 | + help="Optional filename containing xinput data") |
779 | + args = parser.parse_args() |
780 | + |
781 | + if args.filename: |
782 | + stream = open(args.filename) |
783 | + else: |
784 | + stream = os.popen(COMMAND) |
785 | + |
786 | + xinput = XinputParser(stream) |
787 | + |
788 | + result = XinputResult() |
789 | + xinput.run(result) |
790 | + |
791 | + template = Template() |
792 | + template.dump_file(result.elements, sys.stdout) |
793 | + |
794 | + return 0 |
795 | + |
796 | + |
797 | +if __name__ == "__main__": |
798 | + sys.exit(main()) |
40 - raise Excepion("Key not found: %s" % field)
41 + raise Exception("Key not found: %s" % field)
Just bike shedding, sorry, I'd use KeyError here
609 +<<<<<<< TREE checkbox. templates, debian/ checkbox. config: Added support to xinput_ resource, checkbox/ parsers/ xinput. py: Xinput
610 * patch/0.14.2: Fixed patch to rmtree instead of rmdir scripts directory.
611 * [FEATURE] debian/
612 preseed properties in environment_info plugin.
613 +=======
614 + * [FEATURE] scripts/
615 + resource script to test multitouch devices.
616 +>>>>>>> MERGE-SOURCE
There's a typical conflict in our changelogs but I guess we cannot avoid that with our current model
394 +class XinputResult: (self, device): device[ "id"]] = device Class(self, device, device_class): device[ "id"]]. setdefault( "classes" , []) device[ "id"]][ "classes" ].append( device_ class)
395 +
396 + def __init__(self):
397 + self.devices = {}
398 +
399 + def addXinputDevice
400 + self.devices[
401 +
402 + def addXinputDevice
403 + self.devices[
404 + self.devices[
This should be in checkbox/ parsers/ xinput. py or there should be an interface that describes it. Otherwise if I want to use your parser I need to reverse engineer the interface that I'm expected to pass to XInputParser.run().
487 +DEVICE_RE = re.compile( (?P<name> [%s].+? ) *\sid=( ?P<id>\ d+)""" key>[%s] .+?): (?P<value>.+)"""
488 + r""".+?
489 + % ascii_uppercase)
490 +ATTRIBUTE_RE = re.compile(
491 + r"""(?P<
492 + % ascii_letters)
493 +CLASS_VALUE_RE = re.compile(
494 + r"""\d+\. Type: (?P<class>.+)""")
495 +LIST_VALUE_RE = re.compile(
496 + r"""((?:[^ "]|"[^"]*")+)""")
I would really document those, as in how the data looks like and what we parse out.
Otherwise okay, neat job!