~jocave/checkbox/hybrid-amd-gpu-mods

« back to all changes in this revision

Viewing changes to providers/plainbox-provider-checkbox/bin/key_test

  • Committer: Tarmac
  • Author(s): Brendan Donegan
  • Date: 2013-06-03 11:12:58 UTC
  • mfrom: (2154.2.1 bug1185759)
  • Revision ID: tarmac-20130603111258-1b3m5ydvkf1accts
"[r=zkrynicki][bug=1185759][author=brendan-donegan] automatic merge by tarmac"

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python3
2
 
#
3
 
# This file is part of Checkbox.
4
 
#
5
 
# Copyright 2012 Canonical Ltd.
6
 
#
7
 
# Checkbox is free software: you can redistribute it and/or modify
8
 
# it under the terms of the GNU General Public License version 3,
9
 
# as published by the Free Software Foundation.
10
 
 
11
 
#
12
 
# Checkbox is distributed in the hope that it will be useful,
13
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 
# GNU General Public License for more details.
16
 
#
17
 
# You should have received a copy of the GNU General Public License
18
 
# along with Checkbox.  If not, see <http://www.gnu.org/licenses/>.
19
 
 
20
 
 
21
 
import os
22
 
import sys
23
 
 
24
 
import fcntl
25
 
import gettext
26
 
import struct
27
 
import termios
28
 
 
29
 
from gettext import gettext as _
30
 
from gi.repository import GObject
31
 
from optparse import OptionParser
32
 
 
33
 
 
34
 
EXIT_WITH_FAILURE = 1
35
 
EXIT_WITH_SUCCESS = 0
36
 
EXIT_TIMEOUT = 30
37
 
 
38
 
# Keyboard options from /usr/include/linux/kd.h
39
 
K_RAW = 0x00
40
 
K_XLATE = 0x01
41
 
K_MEDIUMRAW = 0x02
42
 
K_UNICODE = 0x03
43
 
K_OFF = 0x04
44
 
KDGKBMODE = 0x4B44
45
 
KDSKBMODE = 0x4B45
46
 
 
47
 
 
48
 
def ioctl_p_int(fd, request, value=0):
49
 
    s = struct.pack("i", value)
50
 
    s2 = fcntl.ioctl(fd, request, s)
51
 
    (ret,) = struct.unpack("i", s2)  # This always returns a tuple.
52
 
    return ret
53
 
 
54
 
 
55
 
class Key:
56
 
 
57
 
    def __init__(self, codes, name=None):
58
 
        self.codes = codes
59
 
        self.name = name
60
 
        self.tested = False
61
 
        self.required = True
62
 
 
63
 
    @property
64
 
    def status(self):
65
 
        if not self.required:
66
 
            return _("Not required")
67
 
        if not self.tested:
68
 
            return _("Untested")
69
 
        return _("Tested")
70
 
 
71
 
 
72
 
class Reporter(object):
73
 
 
74
 
    exit_code = EXIT_WITH_FAILURE
75
 
 
76
 
    def __init__(self, main_loop, keys, scancodes=False):
77
 
        self.main_loop = main_loop
78
 
        self.keys = keys
79
 
        self.scancodes = scancodes
80
 
 
81
 
        self.fileno = os.open("/dev/console", os.O_RDONLY)
82
 
        GObject.io_add_watch(self.fileno, GObject.IO_IN, self.on_key)
83
 
 
84
 
        # Set terminal attributes
85
 
        self.saved_attributes = termios.tcgetattr(self.fileno)
86
 
        attributes = termios.tcgetattr(self.fileno)
87
 
        attributes[3] &= ~(termios.ICANON | termios.ECHO)
88
 
        attributes[6][termios.VMIN] = 1
89
 
        attributes[6][termios.VTIME] = 0
90
 
        termios.tcsetattr(self.fileno, termios.TCSANOW, attributes)
91
 
 
92
 
        # Set keyboard mode
93
 
        self.saved_mode = ioctl_p_int(self.fileno, KDGKBMODE)
94
 
        mode = K_RAW if scancodes else K_MEDIUMRAW
95
 
        fcntl.ioctl(self.fileno, KDSKBMODE, mode)
96
 
 
97
 
    def _parse_codes(self, raw_bytes):
98
 
        """Parse the given string of bytes to scancodes or keycodes."""
99
 
        if self.scancodes:
100
 
            return self._parse_scancodes(raw_bytes)
101
 
        else:
102
 
            return self._parse_keycodes(raw_bytes)
103
 
 
104
 
    def _parse_scancodes(self, raw_bytes):
105
 
        """Parse the bytes in raw_bytes into a scancode."""
106
 
        index = 0
107
 
        length = len(raw_bytes)
108
 
        while index < length:
109
 
            if (index + 1 < length and raw_bytes[index] == 0xE0):
110
 
                code = ((raw_bytes[index] << 8) | raw_bytes[index + 1])
111
 
                index += 2
112
 
            else:
113
 
                code = raw_bytes[0]
114
 
                index += 1
115
 
 
116
 
            yield code
117
 
 
118
 
    def _parse_keycodes(self, raw_bytes):
119
 
        """Parse the bytes in raw_bytes into a keycode."""
120
 
        index = 0
121
 
        length = len(raw_bytes)
122
 
        while index < length:
123
 
            if (index + 2 < length and (raw_bytes[index] & 0x7f) == 0
124
 
                    and (raw_bytes[index + 1] & 0x80) != 0
125
 
                    and (raw_bytes[index + 2] & 0x80) != 0):
126
 
                code = (((raw_bytes[index + 1] & 0x7f) << 7) |
127
 
                        (raw_bytes[2] & 0x7f))
128
 
                index += 3
129
 
            else:
130
 
                code = (raw_bytes[0] & 0x7f)
131
 
                index += 1
132
 
 
133
 
            yield code
134
 
 
135
 
    @property
136
 
    def required_keys_tested(self):
137
 
        """Returns True if all keys marked as required have been tested"""
138
 
        return all([key.tested for key in self.keys if key.required])
139
 
 
140
 
    def show_text(self, string):
141
 
        pass
142
 
 
143
 
    def quit(self, exit_code=EXIT_WITH_FAILURE):
144
 
        self.exit_code = exit_code
145
 
 
146
 
        termios.tcsetattr(self.fileno, termios.TCSANOW, self.saved_attributes)
147
 
        fcntl.ioctl(self.fileno, KDSKBMODE, self.saved_mode)
148
 
 
149
 
        # FIXME: Having a reference to the mainloop is suboptimal.
150
 
        self.main_loop.quit()
151
 
 
152
 
    def found_key(self, key):
153
 
        key.tested = True
154
 
 
155
 
    def toggle_key(self, key):
156
 
        key.required = not key.required
157
 
        key.tested = False
158
 
 
159
 
    def on_key(self, source, cb_condition):
160
 
        raw_bytes = os.read(source, 18)
161
 
        for code in self._parse_codes(raw_bytes):
162
 
            if code == 1:
163
 
                # Check for ESC key pressed
164
 
                self.show_text(_("Test cancelled"))
165
 
                self.quit()
166
 
            elif 1 < code < 10 and type(self) == CLIReporter:
167
 
                # Check for number to skip
168
 
                self.toggle_key(self.keys[code - 2])
169
 
            else:
170
 
                # Check for other key pressed
171
 
                for key in self.keys:
172
 
                    if code in key.codes:
173
 
                        self.found_key(key)
174
 
                        break
175
 
 
176
 
        return True
177
 
 
178
 
 
179
 
class CLIReporter(Reporter):
180
 
 
181
 
    def __init__(self, *args, **kwargs):
182
 
        super(CLIReporter, self).__init__(*args, **kwargs)
183
 
 
184
 
        self.show_text(_("Please press each key on your keyboard."))
185
 
        self.show_text(_("I will exit automatically once all keys "
186
 
                         "have been pressed."))
187
 
        self.show_text(_("If your keyboard lacks one or more keys, "
188
 
                         "press its number to skip testing that key."))
189
 
        self.show_text(_("You can also close me by pressing ESC or Ctrl+C."))
190
 
 
191
 
        self.show_keys()
192
 
 
193
 
    def show_text(self, string):
194
 
        sys.stdout.write(string + "\n")
195
 
        sys.stdout.flush()
196
 
 
197
 
    def show_keys(self):
198
 
        self.show_text("---")
199
 
        for index, key in enumerate(self.keys):
200
 
            self.show_text(
201
 
                "%(number)d - %(key)s - %(status)s" %
202
 
                {"number": index + 1, "key": key.name, "status": key.status})
203
 
 
204
 
    def found_key(self, key):
205
 
        super(CLIReporter, self).found_key(key)
206
 
        self.show_text(
207
 
            _("%(key_name)s key has been pressed" % {'key_name': key.name}))
208
 
 
209
 
        self.show_keys()
210
 
        if self.required_keys_tested:
211
 
            self.show_text(_("All required keys have been tested!"))
212
 
            self.quit(EXIT_WITH_SUCCESS)
213
 
 
214
 
    def toggle_key(self, key):
215
 
        super(CLIReporter, self).toggle_key(key)
216
 
        self.show_keys()
217
 
 
218
 
 
219
 
class GtkReporter(Reporter):
220
 
 
221
 
    def __init__(self, *args, **kwargs):
222
 
        super(GtkReporter, self).__init__(*args, **kwargs)
223
 
 
224
 
        from gi.repository import Gdk, Gtk
225
 
 
226
 
        # Initialize GTK constants
227
 
        self.ICON_SIZE = Gtk.IconSize.BUTTON
228
 
        self.ICON_TESTED = Gtk.STOCK_YES
229
 
        self.ICON_UNTESTED = Gtk.STOCK_INDEX
230
 
        self.ICON_NOT_REQUIRED = Gtk.STOCK_REMOVE
231
 
 
232
 
        self.button_factory = Gtk.Button
233
 
        self.hbox_factory = Gtk.HBox
234
 
        self.image_factory = Gtk.Image
235
 
        self.label_factory = Gtk.Label
236
 
        self.vbox_factory = Gtk.VBox
237
 
 
238
 
        # Create GTK window.
239
 
        window = Gtk.Window()
240
 
        window.set_type_hint(Gdk.WindowType.TOPLEVEL)
241
 
        window.set_size_request(100, 100)
242
 
        window.set_resizable(False)
243
 
        window.set_title(_("Key test"))
244
 
        window.connect("delete_event", lambda w, e: self.quit())
245
 
        window.connect(
246
 
            "key-release-event",
247
 
            lambda w, k: k.keyval == Gdk.KEY_Escape and self.quit())
248
 
        window.show()
249
 
 
250
 
        # Add common widgets to the window.
251
 
        vbox = self._add_vbox(window)
252
 
        self.label = self._add_label(vbox)
253
 
        button_hbox = self._add_hbox(vbox)
254
 
        validation_hbox = self._add_hbox(vbox)
255
 
        skip_hbox = self._add_hbox(vbox)
256
 
        exit_button = self._add_button(vbox, _("_Exit"), True)
257
 
        exit_button.connect("clicked", lambda w: self.quit())
258
 
 
259
 
        # Add widgets for each key.
260
 
        self.icons = {}
261
 
        for key in self.keys:
262
 
            stock = getattr(Gtk, "STOCK_MEDIA_%s" % key.name.upper(), None)
263
 
            if stock:
264
 
                self._add_image(button_hbox, stock)
265
 
            else:
266
 
                self._add_label(button_hbox, key.name)
267
 
            self.icons[key] = self._add_image(validation_hbox, Gtk.STOCK_INDEX)
268
 
            button = self._add_button(skip_hbox, _("Skip"))
269
 
            button.connect("clicked", self.on_skip, key)
270
 
 
271
 
        self.show_text(_("Please press each key on your keyboard."))
272
 
        self.show_text(_("If a key is not present in your keyboard, "
273
 
                         "press the 'Skip' button below it to remove it "
274
 
                         "from the test."))
275
 
 
276
 
    def _add_button(self, context, label, use_underline=False):
277
 
        button = self.button_factory(label=label, use_underline=use_underline)
278
 
        context.add(button)
279
 
        button.show()
280
 
        return button
281
 
 
282
 
    def _add_hbox(self, context, spacing=4):
283
 
        hbox = self.hbox_factory()
284
 
        context.add(hbox)
285
 
        hbox.set_spacing(4)
286
 
        hbox.show()
287
 
        return hbox
288
 
 
289
 
    def _add_image(self, context, stock):
290
 
        image = self.image_factory(stock=stock, icon_size=self.ICON_SIZE)
291
 
        context.add(image)
292
 
        image.show()
293
 
        return image
294
 
 
295
 
    def _add_label(self, context, text=None):
296
 
        label = self.label_factory()
297
 
        context.add(label)
298
 
        label.set_size_request(0, 0)
299
 
        label.set_line_wrap(True)
300
 
        if text:
301
 
            label.set_text(text)
302
 
        label.show()
303
 
        return label
304
 
 
305
 
    def _add_vbox(self, context):
306
 
        vbox = self.vbox_factory()
307
 
        vbox.set_homogeneous(False)
308
 
        vbox.set_spacing(8)
309
 
        context.add(vbox)
310
 
        vbox.show()
311
 
        return vbox
312
 
 
313
 
    def show_text(self, string):
314
 
        self.label.set_text(self.label.get_text() + "\n" + string)
315
 
 
316
 
    def check_keys(self):
317
 
        if self.required_keys_tested:
318
 
            self.show_text(_("All required keys have been tested!"))
319
 
            self.quit(EXIT_WITH_SUCCESS)
320
 
 
321
 
    def found_key(self, key):
322
 
        super(GtkReporter, self).found_key(key)
323
 
        self.icons[key].set_from_stock(self.ICON_TESTED, size=self.ICON_SIZE)
324
 
 
325
 
        self.check_keys()
326
 
 
327
 
    def on_skip(self, sender, key):
328
 
        self.toggle_key(key)
329
 
        if key.required:
330
 
            stock_icon = self.ICON_UNTESTED
331
 
        else:
332
 
            stock_icon = self.ICON_NOT_REQUIRED
333
 
        self.icons[key].set_from_stock(stock_icon, self.ICON_SIZE)
334
 
 
335
 
        self.check_keys()
336
 
 
337
 
 
338
 
def main(args):
339
 
    gettext.textdomain("checkbox")
340
 
 
341
 
    usage = """\
342
 
Usage: %prog [OPTIONS] CODE...
343
 
 
344
 
Syntax for codes:
345
 
 
346
 
  57435               - Decimal code without name
347
 
  0160133:Super       - Octal code with name
348
 
  0xe05b,0xe0db:Super - Multiple hex codes with name
349
 
 
350
 
Hint to find codes:
351
 
 
352
 
  The showkey command can show keycodes and scancodes.
353
 
"""
354
 
    parser = OptionParser(usage=usage)
355
 
    parser.add_option("-i", "--interface",
356
 
                      default="auto",
357
 
                      help="Interface to use: cli, gtk or auto")
358
 
    parser.add_option("-s", "--scancodes",
359
 
                      default=False,
360
 
                      action="store_true",
361
 
                      help="Test for scancodes instead of keycodes.")
362
 
    (options, args) = parser.parse_args(args)
363
 
 
364
 
    # Get reporter factory from options or environment.
365
 
    if options.interface == "auto":
366
 
        if "DISPLAY" in os.environ:
367
 
            reporter_factory = GtkReporter
368
 
        else:
369
 
            reporter_factory = CLIReporter
370
 
    elif options.interface == "cli":
371
 
        reporter_factory = CLIReporter
372
 
    elif options.interface == "gtk":
373
 
        reporter_factory = GtkReporter
374
 
    else:
375
 
        parser.error("Unsupported interface: %s" % options.interface)
376
 
 
377
 
    if not args:
378
 
        parser.error("Must specify codes to test.")
379
 
 
380
 
    # Get keys from command line arguments.
381
 
    keys = []
382
 
    for codes_name in args:
383
 
        if ":" in codes_name:
384
 
            codes, name = codes_name.split(":", 1)
385
 
        else:
386
 
            codes, name = codes_name, codes_name
387
 
 
388
 
        # Guess the proper base from the string.
389
 
        codes = [int(code, 0) for code in codes.split(",")]
390
 
        key = Key(codes, name)
391
 
        keys.append(key)
392
 
 
393
 
    main_loop = GObject.MainLoop()
394
 
    try:
395
 
        reporter = reporter_factory(main_loop, keys, options.scancodes)
396
 
    except:
397
 
        parser.error("Failed to initialize interface: %s" % options.interface)
398
 
 
399
 
    try:
400
 
        main_loop.run()
401
 
    except KeyboardInterrupt:
402
 
        reporter.show_text(_("Test interrupted"))
403
 
        reporter.quit()
404
 
 
405
 
    return reporter.exit_code
406
 
 
407
 
if __name__ == "__main__":
408
 
    sys.exit(main(sys.argv[1:]))