Merge lp:~cr3/checkbox/xinput_resource into lp:checkbox

Proposed by Marc Tardif
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
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.

To post a comment you must log in.
Revision history for this message
Zygmunt Krynicki (zyga) wrote :

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
610 * patch/0.14.2: Fixed patch to rmtree instead of rmdir scripts directory.
611 * [FEATURE] debian/checkbox.templates, debian/checkbox.config: Added support to
612 preseed properties in environment_info plugin.
613 +=======
614 + * [FEATURE] scripts/xinput_resource, checkbox/parsers/xinput.py: Xinput
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:
395 +
396 + def __init__(self):
397 + self.devices = {}
398 +
399 + def addXinputDevice(self, device):
400 + self.devices[device["id"]] = device
401 +
402 + def addXinputDeviceClass(self, device, device_class):
403 + self.devices[device["id"]].setdefault("classes", [])
404 + self.devices[device["id"]]["classes"].append(device_class)

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(
488 + r""".+?(?P<name>[%s].+?) *\sid=(?P<id>\d+)"""
489 + % ascii_uppercase)
490 +ATTRIBUTE_RE = re.compile(
491 + r"""(?P<key>[%s].+?): (?P<value>.+)"""
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!

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

Oh, and generally, some more documentation is always better, especially on stuff that lands in checkbox.*

Revision history for this message
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[field]

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/checkbox.templates, debian/checkbox.config: Added support to
> 612 preseed properties in environment_info plugin.
> 613 +=======
> 614 + * [FEATURE] scripts/xinput_resource, checkbox/parsers/xinput.py: Xinput
> 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(self, device):
> 400 + self.devices[device["id"]] = device
> 401 +
> 402 + def addXinputDeviceClass(self, device, device_class):
> 403 + self.devices[device["id"]].setdefault("classes", [])
> 404 + self.devices[device["id"]]["classes"].append(device_class)
>
> 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().

You're right. Ideally, we could define an abstract base class or an
interface in the checkbox.parsers.xinput module. However, these patterns
are not supported natively in Python. How about something like this:

class XinputResult:

    def addXinputDevice(self, device):
        """Method to add an xinput device to this result."""

    def addXinputDeviceClass(self, device, device_class):
        """Method to add a class under an xinput device."""

> 487 +DEVICE_RE = re.compile(
> 488 + r""".+?(?P<name>[%s].+?) *\sid=(?P<id>\d+)"""
> 489 + % ascii_uppercase)
> 490 +ATTRIBUTE_RE = re.compile(
> 491 + r"""(?P<key>[%s].+?): (?P<value>.+)"""
> 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!

Revision history for this message
Zygmunt Krynicki (zyga) wrote :
Download full text (3.3 KiB)

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[field]
>
> 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(self, device):
>> 400 + self.devices[device["id"]] = device
>> 401 +
>> 402 + def addXinputDeviceClass(self, device, device_class):
>> 403 + self.devices[device["id"]].setdefault("classes", [])
>> 404 + self.devices[device["id"]]["classes"].append(device_class)
>>
>> 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().
>
> You're right. Ideally, we could define an abstract base class or an
> interface in the checkbox.parsers.xinput module. However, these patterns
> 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(self, device):
  """
  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""".+?(?P<name>[%s].+?) *\sid=(?P<id>\d+)"""
>> 489 + % ascii_uppercase)
>> 490 +ATTRIBUTE_RE = re.compile(
>> 491 + r"""(?P<key>[%s].+?): (?P<value>.+)"""
>> 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 ...

Read more...

Revision history for this message
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(self, device):
> """
> 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!

Revision history for this message
Marc Tardif (cr3) wrote :

I need to learn how to set the review to "resubmit" by email...

review: Needs Resubmitting
Revision history for this message
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?

review: Approve
Revision history for this message
Zygmunt Krynicki (zyga) wrote :

Attempt to merge into lp:checkbox failed due to conflicts:

text conflict in debian/changelog

Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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())

Subscribers

People subscribed via source and target branches