blob: f47c843494321d81a6442d98fa7084661a28bbd8 [file] [log] [blame]
Hung-Te Lin6bb48552012-02-09 14:37:43 +08001#!/usr/bin/python -u
Hung-Te Linf2f78f72012-02-08 19:27:11 +08002# -*- coding: utf-8 -*-
3#
4# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
8
9# DESCRIPTION :
10#
11# This library provides convenience routines to launch factory tests.
12# This includes support for drawing the test widget in a window at the
13# proper location, grabbing control of the mouse, and making the mouse
14# cursor disappear.
Hung-Te Lin6bb48552012-02-09 14:37:43 +080015#
16# This UI is intended to be used by the factory autotest suite to
17# provide factory operators feedback on test status and control over
18# execution order.
19#
20# In short, the UI is composed of a 'console' panel on the bottom of
21# the screen which displays the autotest log, and there is also a
22# 'test list' panel on the right hand side of the screen. The
23# majority of the screen is dedicated to tests, which are executed in
24# seperate processes, but instructed to display their own UIs in this
25# dedicated area whenever possible. Tests in the test list are
26# executed in order by default, but can be activated on demand via
27# associated keyboard shortcuts. As tests are run, their status is
28# color-indicated to the operator -- greyed out means untested, yellow
29# means active, green passed and red failed.
Hung-Te Linf2f78f72012-02-08 19:27:11 +080030
Hung-Te Lin6bb48552012-02-09 14:37:43 +080031import logging
32import os
33import re
34import subprocess
35import sys
Hung-Te Linf2f78f72012-02-08 19:27:11 +080036from itertools import izip, product
37
Hung-Te Lin6bb48552012-02-09 14:37:43 +080038# GTK and X modules
39import gobject
40import gtk
41import pango
42
43# Factory and autotest modules
Hung-Te Linf2f78f72012-02-08 19:27:11 +080044import factory_common
45from autotest_lib.client.cros import factory
46from autotest_lib.client.cros.factory import TestState
47from autotest_lib.client.cros.factory.event import Event, EventClient
48
Hung-Te Lin6bb48552012-02-09 14:37:43 +080049
Hung-Te Linf2f78f72012-02-08 19:27:11 +080050# For compatibility with tests before TestState existed
51ACTIVE = TestState.ACTIVE
52PASSED = TestState.PASSED
53FAILED = TestState.FAILED
54UNTESTED = TestState.UNTESTED
55
Hung-Te Lin6bb48552012-02-09 14:37:43 +080056# Color definition
Hung-Te Linf2f78f72012-02-08 19:27:11 +080057BLACK = gtk.gdk.Color()
58RED = gtk.gdk.Color(0xFFFF, 0, 0)
59GREEN = gtk.gdk.Color(0, 0xFFFF, 0)
60BLUE = gtk.gdk.Color(0, 0, 0xFFFF)
61WHITE = gtk.gdk.Color(0xFFFF, 0xFFFF, 0xFFFF)
Hung-Te Linf2f78f72012-02-08 19:27:11 +080062LIGHT_GREEN = gtk.gdk.color_parse('light green')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080063SEP_COLOR = gtk.gdk.color_parse('grey50')
64
65RGBA_GREEN_OVERLAY = (0, 0.5, 0, 0.6)
66RGBA_YELLOW_OVERLAY = (0.6, 0.6, 0, 0.6)
67
68LABEL_COLORS = {
69 TestState.ACTIVE: gtk.gdk.color_parse('light goldenrod'),
70 TestState.PASSED: gtk.gdk.color_parse('pale green'),
71 TestState.FAILED: gtk.gdk.color_parse('tomato'),
72 TestState.UNTESTED: gtk.gdk.color_parse('dark slate grey')}
73
74LABEL_FONT = pango.FontDescription('courier new condensed 16')
Hung-Te Linbf545582012-02-15 17:08:07 +080075LABEL_LARGE_FONT = pango.FontDescription('courier new condensed 24')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080076
77FAIL_TIMEOUT = 30
78
79USER_PASS_FAIL_SELECT_STR = (
80 'hit TAB to fail and ENTER to pass\n' +
81 '錯誤請按 TAB,成功請按 ENTER')
82
Hung-Te Lin6bb48552012-02-09 14:37:43 +080083_LABEL_EN_SIZE = (170, 35)
84_LABEL_ZH_SIZE = (70, 35)
85_LABEL_EN_FONT = pango.FontDescription('courier new extra-condensed 16')
86_LABEL_ZH_FONT = pango.FontDescription('normal 12')
87_LABEL_T_SIZE = (40, 35)
88_LABEL_T_FONT = pango.FontDescription('arial ultra-condensed 10')
89_LABEL_UNTESTED_FG = gtk.gdk.color_parse('grey40')
90_LABEL_TROUGH_COLOR = gtk.gdk.color_parse('grey20')
91_LABEL_STATUS_SIZE = (140, 30)
92_LABEL_STATUS_FONT = pango.FontDescription(
93 'courier new bold extra-condensed 16')
94_OTHER_LABEL_FONT = pango.FontDescription('courier new condensed 20')
95
96_ST_LABEL_EN_SIZE = (250, 35)
97_ST_LABEL_ZH_SIZE = (150, 35)
98
99_NO_ACTIVE_TEST_DELAY_MS = 500
100
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800101# ---------------------------------------------------------------------------
102# Client Library
103
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800104
Hung-Te Lin4b0b61f2012-03-05 14:20:06 +0800105# TODO(hungte) Replace gtk_lock by gtk.gdk.lock when it's availble (need pygtk
106# 2.2x, and we're now pinned by 2.1x)
107class _GtkLock(object):
108 __enter__ = gtk.gdk.threads_enter
109 def __exit__(*ignored):
110 gtk.gdk.threads_leave()
111
112
113gtk_lock = _GtkLock()
114
115
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800116def make_label(message, font=LABEL_FONT, fg=LIGHT_GREEN,
117 size=None, alignment=None):
118 l = gtk.Label(message)
119 l.modify_font(font)
120 l.modify_fg(gtk.STATE_NORMAL, fg)
121 if size:
122 l.set_size_request(*size)
123 if alignment:
124 l.set_alignment(*alignment)
125 return l
126
127
128def make_hsep(width=1):
129 frame = gtk.EventBox()
130 frame.set_size_request(-1, width)
131 frame.modify_bg(gtk.STATE_NORMAL, SEP_COLOR)
132 return frame
133
134
135def make_vsep(width=1):
136 frame = gtk.EventBox()
137 frame.set_size_request(width, -1)
138 frame.modify_bg(gtk.STATE_NORMAL, SEP_COLOR)
139 return frame
140
141
Hung-Te Linaf8ec542012-03-14 20:01:40 +0800142def make_countdown_widget(prompt=None, value=None, fg=LIGHT_GREEN):
143 if prompt is None:
144 prompt = 'time remaining / 剩餘時間: '
145 if value is None:
146 value = '%s' % FAIL_TIMEOUT
147 title = make_label(prompt, fg=fg, alignment=(1, 0.5))
148 countdown = make_label(value, fg=fg, alignment=(0, 0.5))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800149 hbox = gtk.HBox()
150 hbox.pack_start(title)
151 hbox.pack_start(countdown)
152 eb = gtk.EventBox()
153 eb.modify_bg(gtk.STATE_NORMAL, BLACK)
154 eb.add(hbox)
155 return eb, countdown
156
157
158def hide_cursor(gdk_window):
159 pixmap = gtk.gdk.Pixmap(None, 1, 1, 1)
160 color = gtk.gdk.Color()
161 cursor = gtk.gdk.Cursor(pixmap, pixmap, color, color, 0, 0)
162 gdk_window.set_cursor(cursor)
163
164
165def calc_scale(wanted_x, wanted_y):
166 (widget_size_x, widget_size_y) = factory.get_shared_data('test_widget_size')
167 scale_x = (0.9 * widget_size_x) / wanted_x
168 scale_y = (0.9 * widget_size_y) / wanted_y
169 scale = scale_y if scale_y < scale_x else scale_x
170 scale = 1 if scale > 1 else scale
171 factory.log('scale: %s' % scale)
172 return scale
173
174
175def trim(text, length):
176 if len(text) > length:
177 text = text[:length-3] + '...'
178 return text
179
180
Hung-Te Lin6dc799c2012-03-13 22:10:47 +0800181class InputError(ValueError):
182 """Execption for input window callbacks to change status text message."""
183 pass
184
185
Hung-Te Linbf545582012-02-15 17:08:07 +0800186def make_input_window(prompt=None,
187 init_value=None,
188 msg_invalid=None,
189 font=None,
190 on_validate=None,
191 on_keypress=None,
192 on_complete=None):
193 """
194 Creates a widget to prompt user for a valid string.
195
196 @param prompt: A string to be displayed. None for default message.
197 @param init_value: Initial value to be set.
198 @param msg_invalid: Status string to display when input is invalid. None for
199 default message.
200 @param font: Font specification (string or pango.FontDescription) for label
201 and entry. None for default large font.
202 @param on_validate: A callback function to validate if the input from user
Hung-Te Lin6dc799c2012-03-13 22:10:47 +0800203 is valid. None for allowing any non-empty input. Any ValueError or
204 ui.InputError raised during execution in on_validate will be displayed
205 in bottom status.
Hung-Te Linbf545582012-02-15 17:08:07 +0800206 @param on_keypress: A callback function when each keystroke is hit.
207 @param on_complete: A callback function when a valid string is passed.
208 None to stop (gtk.main_quit).
209 @return: A widget with prompt, input entry, and status label.
Chun-ta Lin2e7f44c2012-03-03 07:29:36 +0800210 In addition, a method called get_entry() is added to the widget to
211 provide controls on the entry.
Hung-Te Linbf545582012-02-15 17:08:07 +0800212 """
213 DEFAULT_MSG_INVALID = "Invalid input / 輸入不正確"
214 DEFAULT_PROMPT = "Enter Data / 輸入資料:"
215
216 def enter_callback(entry):
217 text = entry.get_text()
Hung-Te Lin6dc799c2012-03-13 22:10:47 +0800218 try:
219 if (on_validate and (not on_validate(text))) or (not text.strip()):
220 raise ValueError(msg_invalid)
Hung-Te Linbf545582012-02-15 17:08:07 +0800221 on_complete(text) if on_complete else gtk.main_quit()
Hung-Te Lin6dc799c2012-03-13 22:10:47 +0800222 except ValueError as e:
223 gtk.gdk.beep()
224 status_label.set_text('ERROR: %s' % e.message)
Hung-Te Linbf545582012-02-15 17:08:07 +0800225 return True
226
227 def key_press_callback(entry, key):
228 status_label.set_text('')
229 if on_keypress:
230 return on_keypress(entry, key)
231 return False
232
233 # Populate default parameters
234 if msg_invalid is None:
235 msg_invalid = DEFAULT_MSG_INVALID
236
237 if prompt is None:
238 prompt = DEFAULT_PROMPT
239
240 if font is None:
241 font = LABEL_LARGE_FONT
242 elif not isinstance(font, pango.FontDescription):
243 font = pango.FontDescription(font)
244
245 widget = gtk.VBox()
246 label = make_label(prompt, font=font)
247 status_label = make_label('', font=font)
248 entry = gtk.Entry()
249 entry.modify_font(font)
250 entry.connect("activate", enter_callback)
251 entry.connect("key_press_event", key_press_callback)
252 if init_value:
253 entry.set_text(init_value)
254 widget.modify_bg(gtk.STATE_NORMAL, BLACK)
255 status_label.modify_fg(gtk.STATE_NORMAL, RED)
256 widget.add(label)
257 widget.pack_start(entry)
258 widget.pack_start(status_label)
Chun-ta Lin2e7f44c2012-03-03 07:29:36 +0800259
260 # Method for getting the entry.
261 widget.get_entry = lambda : entry
Hung-Te Linbf545582012-02-15 17:08:07 +0800262 return widget
263
264
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800265def make_summary_box(tests, state_map, rows=15):
266 '''
267 Creates a widget display status of a set of test.
268
269 @param tests: A list of FactoryTest nodes whose status (and children's
270 status) should be displayed.
271 @param state_map: The state map as provide by the state instance.
272 @param rows: The number of rows to display.
273 @return: A tuple (widget, label_map), where widget is the widget, and
274 label_map is a map from each test to the corresponding label.
275 '''
276 LABEL_EN_SIZE = (170, 35)
277 LABEL_EN_SIZE_2 = (450, 25)
278 LABEL_EN_FONT = pango.FontDescription('courier new extra-condensed 16')
279
280 all_tests = sum([list(t.walk(in_order=True)) for t in tests], [])
281 columns = len(all_tests) / rows + (len(all_tests) % rows != 0)
282
283 info_box = gtk.HBox()
284 info_box.set_spacing(20)
285 for status in (TestState.ACTIVE, TestState.PASSED,
286 TestState.FAILED, TestState.UNTESTED):
287 label = make_label(status,
288 size=LABEL_EN_SIZE,
289 font=LABEL_EN_FONT,
290 alignment=(0.5, 0.5),
291 fg=LABEL_COLORS[status])
292 info_box.pack_start(label, False, False)
293
294 vbox = gtk.VBox()
295 vbox.set_spacing(20)
296 vbox.pack_start(info_box, False, False)
297
298 label_map = {}
299
300 if all_tests:
301 status_table = gtk.Table(rows, columns, True)
302 for (j, i), t in izip(product(xrange(columns), xrange(rows)),
303 all_tests):
304 msg_en = ' ' * (t.depth() - 1) + t.label_en
305 msg_en = trim(msg_en, 12)
306 if t.label_zh:
307 msg = '{0:<12} ({1})'.format(msg_en, t.label_zh)
308 else:
309 msg = msg_en
310 status = state_map[t].status
311 status_label = make_label(msg,
312 size=LABEL_EN_SIZE_2,
313 font=LABEL_EN_FONT,
314 alignment=(0.0, 0.5),
315 fg=LABEL_COLORS[status])
316 label_map[t] = status_label
317 status_table.attach(status_label, j, j+1, i, i+1)
318 vbox.pack_start(status_table, False, False)
319
320 return vbox, label_map
321
322
323def run_test_widget(dummy_job, test_widget,
324 invisible_cursor=True,
325 window_registration_callback=None,
326 cleanup_callback=None):
327 test_widget_size = factory.get_shared_data('test_widget_size')
328
329 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
330 window.modify_bg(gtk.STATE_NORMAL, BLACK)
331 window.set_size_request(*test_widget_size)
332
333 def show_window():
334 window.show()
335 window.window.raise_() # pylint: disable=E1101
336 gtk.gdk.pointer_grab(window.window, confine_to=window.window)
337 if invisible_cursor:
338 hide_cursor(window.window)
339
340 test_path = factory.get_current_test_path()
341
342 def handle_event(event):
343 if (event.type == Event.Type.STATE_CHANGE and
344 test_path and event.path == test_path and
345 event.state.visible):
346 show_window()
347
348 event_client = EventClient(
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800349 callback=handle_event, event_loop=EventClient.EVENT_LOOP_GOBJECT_IO)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800350
351 align = gtk.Alignment(xalign=0.5, yalign=0.5)
352 align.add(test_widget)
353
354 window.add(align)
355 for c in window.get_children():
356 # Show all children, but not the window itself yet.
357 c.show_all()
358
359 if window_registration_callback is not None:
360 window_registration_callback(window)
361
362 # Show the window if it is the visible test, or if the test_path is not
363 # available (e.g., run directly from the command line).
364 if (not test_path) or (
365 TestState.from_dict_or_object(
366 factory.get_state_instance().get_test_state(test_path)).visible):
367 show_window()
368 else:
369 window.hide()
370
371 gtk.main()
372
373 gtk.gdk.pointer_ungrab()
374
375 if cleanup_callback is not None:
376 cleanup_callback()
377
378 del event_client
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800379
380
381# ---------------------------------------------------------------------------
382# Server Implementation
383
384
385class Console(object):
386 '''Display a progress log. Implemented by launching an borderless
387 xterm at a strategic location, and running tail against the log.'''
388
389 def __init__(self, allocation):
390 xterm_coords = '145x13+%d+%d' % (allocation.x, allocation.y)
391 logging.info('xterm_coords = %s', xterm_coords)
392 xterm_opts = ('-bg black -fg lightgray -bw 0 -g %s' % xterm_coords)
393 xterm_cmd = (('urxvt %s -e bash -c ' % xterm_opts).split() +
394 ['tail -f "%s"' % factory.CONSOLE_LOG_PATH])
395 logging.info('xterm_cmd = %s', xterm_cmd)
396 self._proc = subprocess.Popen(xterm_cmd)
397
398 def __del__(self):
399 logging.info('console_proc __del__')
400 self._proc.kill()
401
402
403class TestLabelBox(gtk.EventBox): # pylint: disable=R0904
404
405 def __init__(self, test, show_shortcut=False):
406 gtk.EventBox.__init__(self)
407 self.modify_bg(gtk.STATE_NORMAL, LABEL_COLORS[TestState.UNTESTED])
408
409 label_en = make_label(test.label_en, size=_LABEL_EN_SIZE,
410 font=_LABEL_EN_FONT, alignment=(0.5, 0.5),
411 fg=_LABEL_UNTESTED_FG)
412 label_zh = make_label(test.label_zh, size=_LABEL_ZH_SIZE,
413 font=_LABEL_ZH_FONT, alignment=(0.5, 0.5),
414 fg=_LABEL_UNTESTED_FG)
415 label_t = make_label('C-' + test.kbd_shortcut.upper(),
416 size=_LABEL_T_SIZE, font=_LABEL_T_FONT,
417 alignment=(0.5, 0.5), fg=BLACK)
418
419 # build a better label_en with shortcuts
420 index_hotkey = test.label_en.upper().find(test.kbd_shortcut.upper())
421 if show_shortcut and index_hotkey >= 0:
422 attrs = label_en.get_attributes() or pango.AttrList()
423 attrs.insert(pango.AttrUnderline(
424 pango.UNDERLINE_LOW, index_hotkey, index_hotkey + 1))
425 attrs.insert(pango.AttrWeight(
426 pango.WEIGHT_BOLD, index_hotkey, index_hotkey + 1))
427 label_en.set_attributes(attrs)
428
429 hbox = gtk.HBox()
430 hbox.pack_start(label_en, False, False)
431 hbox.pack_start(label_zh, False, False)
432 hbox.pack_start(label_t, False, False)
433 self.add(hbox)
434 self.label_list = [label_en, label_zh]
435
436 def update(self, state):
437 label_fg = (_LABEL_UNTESTED_FG if state.status == TestState.UNTESTED
438 else BLACK)
439 for label in self.label_list:
440 label.modify_fg(gtk.STATE_NORMAL, label_fg)
441 self.modify_bg(gtk.STATE_NORMAL, LABEL_COLORS[state.status])
442 self.queue_draw()
443
444
445class UiState(object):
446
447 def __init__(self, test_widget_box):
448 self._test_widget_box = test_widget_box
449 self._label_box_map = {}
450 self._transition_count = 0
451
452 self._active_test_label_map = None
453
454 def _remove_state_widget(self):
455 """Remove any existing state widgets."""
456 for child in self._test_widget_box.get_children():
457 self._test_widget_box.remove(child)
458 self._active_test_label_map = None
459
460 def update_test_label(self, test, state):
461 label_box = self._label_box_map.get(test)
462 if label_box:
463 label_box.update(state)
464
465 def update_test_state(self, test_list, state_map):
466 active_tests = [
467 t for t in test_list.walk()
468 if t.is_leaf() and state_map[t].status == TestState.ACTIVE]
469 has_active_ui = any(t.has_ui for t in active_tests)
470
471 if not active_tests:
472 # Display the "no active tests" widget if there are still no
473 # active tests after _NO_ACTIVE_TEST_DELAY_MS.
474 def run(transition_count):
475 if transition_count != self._transition_count:
476 # Something has happened
477 return False
478
479 self._transition_count += 1
480 self._remove_state_widget()
481
482 self._test_widget_box.set(0.5, 0.5, 0.0, 0.0)
483 self._test_widget_box.set_padding(0, 0, 0, 0)
484 label_box = gtk.EventBox()
485 label_box.modify_bg(gtk.STATE_NORMAL, BLACK)
486 label = make_label('no active test', font=_OTHER_LABEL_FONT,
487 alignment=(0.5, 0.5))
488 label_box.add(label)
489 self._test_widget_box.add(label_box)
490 self._test_widget_box.show_all()
491
492 gobject.timeout_add(_NO_ACTIVE_TEST_DELAY_MS, run,
493 self._transition_count)
494 return
495
496 self._transition_count += 1
497
498 if has_active_ui:
499 # Remove the widget (if any) since there is an active test
500 # with a UI.
501 self._remove_state_widget()
502 return
503
504 if (self._active_test_label_map is not None and
505 all(t in self._active_test_label_map for t in active_tests)):
506 # All active tests are already present in the summary, so just
507 # update their states.
508 for test, label in self._active_test_label_map.iteritems():
509 label.modify_fg(
510 gtk.STATE_NORMAL,
511 LABEL_COLORS[state_map[test].status])
512 return
513
514 self._remove_state_widget()
515 # No active UI; draw summary of current test states
516 self._test_widget_box.set(0.5, 0.0, 0.0, 0.0)
517 self._test_widget_box.set_padding(40, 0, 0, 0)
518 vbox, self._active_test_label_map = make_summary_box(
519 [t for t in test_list.subtests
520 if state_map[t].status == TestState.ACTIVE],
521 state_map)
522 self._test_widget_box.add(vbox)
523 self._test_widget_box.show_all()
524
525 def set_label_box(self, test, label_box):
526 self._label_box_map[test] = label_box
527
528
529def main(test_list_path):
530 '''Starts the main UI.
531
532 This is launched by the autotest/cros/factory/client.
533 When operators press keyboard shortcuts, the shortcut
534 value is sent as an event to the control program.'''
535
536 test_list = None
537 ui_state = None
538 event_client = None
539
540 # Delay loading Xlib because Xlib is currently not available in image build
541 # process host-depends list, and it's only required by the main UI, not all
542 # the tests using UI library (in other words, it'll be slower and break the
543 # build system if Xlib is globally imported).
544 try:
545 from Xlib import X
546 from Xlib.display import Display
547 disp = Display()
548 except:
549 logging.error('Failed loading X modules')
550 raise
551
552 def handle_key_release_event(_, event):
553 logging.info('base ui key event (%s)', event.keyval)
554 return True
555
556 def handle_event(event):
557 if event.type == Event.Type.STATE_CHANGE:
558 test = test_list.lookup_path(event.path)
559 state_map = test_list.get_state_map()
560 ui_state.update_test_label(test, state_map[test])
561 ui_state.update_test_state(test_list, state_map)
562
563 def grab_shortcut_keys(kbd_shortcuts):
564 root = disp.screen().root
565 keycode_map = {}
566
567 def handle_xevent( # pylint: disable=W0102
568 dummy_src, dummy_cond, xhandle=root.display,
569 keycode_map=keycode_map):
570 for dummy_i in range(0, xhandle.pending_events()):
571 xevent = xhandle.next_event()
572 if xevent.type == X.KeyPress:
573 keycode = xevent.detail
Jon Salz28a60912012-03-13 13:29:13 +0800574 if keycode in keycode_map:
575 event_client.post_event(Event('kbd_shortcut',
576 key=keycode_map[keycode]))
577 else:
578 logging.warning('Unbound keycode %s' % keycode)
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800579 return True
580
581 # We want to receive KeyPress events
582 root.change_attributes(event_mask = X.KeyPressMask)
583
584 for mod, shortcut in ([(X.ControlMask, k) for k in kbd_shortcuts] +
585 [(X.Mod1Mask, 'Tab')]): # Mod1 = Alt
586 keysym = gtk.gdk.keyval_from_name(shortcut)
587 keycode = disp.keysym_to_keycode(keysym)
588 keycode_map[keycode] = shortcut
589 root.grab_key(keycode, mod, 1,
590 X.GrabModeAsync, X.GrabModeAsync)
591
592 # This flushes the XGrabKey calls to the server.
593 for dummy_x in range(0, root.display.pending_events()):
594 root.display.next_event()
595 gobject.io_add_watch(root.display, gobject.IO_IN, handle_xevent)
596
597
598 test_list = factory.read_test_list(test_list_path)
599
600 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
601 window.connect('destroy', lambda _: gtk.main_quit())
602 window.modify_bg(gtk.STATE_NORMAL, BLACK)
603
604 event_client = EventClient(
605 callback=handle_event,
606 event_loop=EventClient.EVENT_LOOP_GOBJECT_IO)
607
608 screen = window.get_screen()
609 if (screen is None):
610 logging.info('ERROR: communication with the X server is not working, ' +
611 'could not find a working screen. UI exiting.')
612 sys.exit(1)
613
614 screen_size_str = os.environ.get('CROS_SCREEN_SIZE')
615 if screen_size_str:
616 match = re.match(r'^(\d+)x(\d+)$', screen_size_str)
617 assert match, 'CROS_SCREEN_SIZE should be {width}x{height}'
618 screen_size = (int(match.group(1)), int(match.group(2)))
619 else:
620 screen_size = (screen.get_width(), screen.get_height())
621 window.set_size_request(*screen_size)
622
623 label_trough = gtk.VBox()
624 label_trough.set_spacing(0)
625
626 rhs_box = gtk.EventBox()
627 rhs_box.modify_bg(gtk.STATE_NORMAL, _LABEL_TROUGH_COLOR)
628 rhs_box.add(label_trough)
629
630 console_box = gtk.EventBox()
631 console_box.set_size_request(-1, 180)
632 console_box.modify_bg(gtk.STATE_NORMAL, BLACK)
633
634 test_widget_box = gtk.Alignment()
635 test_widget_box.set_size_request(-1, -1)
636
637 lhs_box = gtk.VBox()
638 lhs_box.pack_end(console_box, False, False)
639 lhs_box.pack_start(test_widget_box)
640 lhs_box.pack_start(make_hsep(3), False, False)
641
642 base_box = gtk.HBox()
643 base_box.pack_end(rhs_box, False, False)
644 base_box.pack_end(make_vsep(3), False, False)
645 base_box.pack_start(lhs_box)
646
647 window.connect('key-release-event', handle_key_release_event)
648 window.add_events(gtk.gdk.KEY_RELEASE_MASK)
649
650 ui_state = UiState(test_widget_box)
651
652 for test in test_list.subtests:
653 label_box = TestLabelBox(test, True)
654 ui_state.set_label_box(test, label_box)
655 label_trough.pack_start(label_box, False, False)
656 label_trough.pack_start(make_hsep(), False, False)
657
658 window.add(base_box)
659 window.show_all()
660
661 state_map = test_list.get_state_map()
662 for test, state in test_list.get_state_map().iteritems():
663 ui_state.update_test_label(test, state)
664 ui_state.update_test_state(test_list, state_map)
665
666 grab_shortcut_keys(test_list.kbd_shortcut_map.keys())
667
668 hide_cursor(window.window)
669
670 test_widget_allocation = test_widget_box.get_allocation()
671 test_widget_size = (test_widget_allocation.width,
672 test_widget_allocation.height)
673 factory.set_shared_data('test_widget_size', test_widget_size)
674
675 dummy_console = Console(console_box.get_allocation())
676
677 event_client.post_event(Event(Event.Type.UI_READY))
678
679 logging.info('cros/factory/ui setup done, starting gtk.main()...')
680 gtk.main()
681 logging.info('cros/factory/ui gtk.main() finished, exiting.')
682
683
684if __name__ == '__main__':
685 if len(sys.argv) != 2:
686 print 'usage: %s <test list path>' % sys.argv[0]
687 sys.exit(1)
688
689 factory.init_logging("cros/factory/ui", verbose=True)
690 main(sys.argv[1])