blob: 431c86c22db76fb8a2ed0ce16eeac0483b234beb [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
101
102# ---------------------------------------------------------------------------
103# Client Library
104
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800105
106def make_label(message, font=LABEL_FONT, fg=LIGHT_GREEN,
107 size=None, alignment=None):
108 l = gtk.Label(message)
109 l.modify_font(font)
110 l.modify_fg(gtk.STATE_NORMAL, fg)
111 if size:
112 l.set_size_request(*size)
113 if alignment:
114 l.set_alignment(*alignment)
115 return l
116
117
118def make_hsep(width=1):
119 frame = gtk.EventBox()
120 frame.set_size_request(-1, width)
121 frame.modify_bg(gtk.STATE_NORMAL, SEP_COLOR)
122 return frame
123
124
125def make_vsep(width=1):
126 frame = gtk.EventBox()
127 frame.set_size_request(width, -1)
128 frame.modify_bg(gtk.STATE_NORMAL, SEP_COLOR)
129 return frame
130
131
132def make_countdown_widget():
133 title = make_label('time remaining / 剩餘時間: ', alignment=(1, 0.5))
134 countdown = make_label('%d' % FAIL_TIMEOUT, alignment=(0, 0.5))
135 hbox = gtk.HBox()
136 hbox.pack_start(title)
137 hbox.pack_start(countdown)
138 eb = gtk.EventBox()
139 eb.modify_bg(gtk.STATE_NORMAL, BLACK)
140 eb.add(hbox)
141 return eb, countdown
142
143
144def hide_cursor(gdk_window):
145 pixmap = gtk.gdk.Pixmap(None, 1, 1, 1)
146 color = gtk.gdk.Color()
147 cursor = gtk.gdk.Cursor(pixmap, pixmap, color, color, 0, 0)
148 gdk_window.set_cursor(cursor)
149
150
151def calc_scale(wanted_x, wanted_y):
152 (widget_size_x, widget_size_y) = factory.get_shared_data('test_widget_size')
153 scale_x = (0.9 * widget_size_x) / wanted_x
154 scale_y = (0.9 * widget_size_y) / wanted_y
155 scale = scale_y if scale_y < scale_x else scale_x
156 scale = 1 if scale > 1 else scale
157 factory.log('scale: %s' % scale)
158 return scale
159
160
161def trim(text, length):
162 if len(text) > length:
163 text = text[:length-3] + '...'
164 return text
165
166
Hung-Te Linbf545582012-02-15 17:08:07 +0800167def make_input_window(prompt=None,
168 init_value=None,
169 msg_invalid=None,
170 font=None,
171 on_validate=None,
172 on_keypress=None,
173 on_complete=None):
174 """
175 Creates a widget to prompt user for a valid string.
176
177 @param prompt: A string to be displayed. None for default message.
178 @param init_value: Initial value to be set.
179 @param msg_invalid: Status string to display when input is invalid. None for
180 default message.
181 @param font: Font specification (string or pango.FontDescription) for label
182 and entry. None for default large font.
183 @param on_validate: A callback function to validate if the input from user
184 is valid. None for allowing any non-empty input.
185 @param on_keypress: A callback function when each keystroke is hit.
186 @param on_complete: A callback function when a valid string is passed.
187 None to stop (gtk.main_quit).
188 @return: A widget with prompt, input entry, and status label.
189 """
190 DEFAULT_MSG_INVALID = "Invalid input / 輸入不正確"
191 DEFAULT_PROMPT = "Enter Data / 輸入資料:"
192
193 def enter_callback(entry):
194 text = entry.get_text()
195 if (on_validate and (not on_validate(text))) or (not text.strip()):
196 gtk.gdk.beep()
197 status_label.set_text(msg_invalid)
198 else:
199 on_complete(text) if on_complete else gtk.main_quit()
200 return True
201
202 def key_press_callback(entry, key):
203 status_label.set_text('')
204 if on_keypress:
205 return on_keypress(entry, key)
206 return False
207
208 # Populate default parameters
209 if msg_invalid is None:
210 msg_invalid = DEFAULT_MSG_INVALID
211
212 if prompt is None:
213 prompt = DEFAULT_PROMPT
214
215 if font is None:
216 font = LABEL_LARGE_FONT
217 elif not isinstance(font, pango.FontDescription):
218 font = pango.FontDescription(font)
219
220 widget = gtk.VBox()
221 label = make_label(prompt, font=font)
222 status_label = make_label('', font=font)
223 entry = gtk.Entry()
224 entry.modify_font(font)
225 entry.connect("activate", enter_callback)
226 entry.connect("key_press_event", key_press_callback)
227 if init_value:
228 entry.set_text(init_value)
229 widget.modify_bg(gtk.STATE_NORMAL, BLACK)
230 status_label.modify_fg(gtk.STATE_NORMAL, RED)
231 widget.add(label)
232 widget.pack_start(entry)
233 widget.pack_start(status_label)
234 return widget
235
236
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800237def make_summary_box(tests, state_map, rows=15):
238 '''
239 Creates a widget display status of a set of test.
240
241 @param tests: A list of FactoryTest nodes whose status (and children's
242 status) should be displayed.
243 @param state_map: The state map as provide by the state instance.
244 @param rows: The number of rows to display.
245 @return: A tuple (widget, label_map), where widget is the widget, and
246 label_map is a map from each test to the corresponding label.
247 '''
248 LABEL_EN_SIZE = (170, 35)
249 LABEL_EN_SIZE_2 = (450, 25)
250 LABEL_EN_FONT = pango.FontDescription('courier new extra-condensed 16')
251
252 all_tests = sum([list(t.walk(in_order=True)) for t in tests], [])
253 columns = len(all_tests) / rows + (len(all_tests) % rows != 0)
254
255 info_box = gtk.HBox()
256 info_box.set_spacing(20)
257 for status in (TestState.ACTIVE, TestState.PASSED,
258 TestState.FAILED, TestState.UNTESTED):
259 label = make_label(status,
260 size=LABEL_EN_SIZE,
261 font=LABEL_EN_FONT,
262 alignment=(0.5, 0.5),
263 fg=LABEL_COLORS[status])
264 info_box.pack_start(label, False, False)
265
266 vbox = gtk.VBox()
267 vbox.set_spacing(20)
268 vbox.pack_start(info_box, False, False)
269
270 label_map = {}
271
272 if all_tests:
273 status_table = gtk.Table(rows, columns, True)
274 for (j, i), t in izip(product(xrange(columns), xrange(rows)),
275 all_tests):
276 msg_en = ' ' * (t.depth() - 1) + t.label_en
277 msg_en = trim(msg_en, 12)
278 if t.label_zh:
279 msg = '{0:<12} ({1})'.format(msg_en, t.label_zh)
280 else:
281 msg = msg_en
282 status = state_map[t].status
283 status_label = make_label(msg,
284 size=LABEL_EN_SIZE_2,
285 font=LABEL_EN_FONT,
286 alignment=(0.0, 0.5),
287 fg=LABEL_COLORS[status])
288 label_map[t] = status_label
289 status_table.attach(status_label, j, j+1, i, i+1)
290 vbox.pack_start(status_table, False, False)
291
292 return vbox, label_map
293
294
295def run_test_widget(dummy_job, test_widget,
296 invisible_cursor=True,
297 window_registration_callback=None,
298 cleanup_callback=None):
299 test_widget_size = factory.get_shared_data('test_widget_size')
300
301 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
302 window.modify_bg(gtk.STATE_NORMAL, BLACK)
303 window.set_size_request(*test_widget_size)
304
305 def show_window():
306 window.show()
307 window.window.raise_() # pylint: disable=E1101
308 gtk.gdk.pointer_grab(window.window, confine_to=window.window)
309 if invisible_cursor:
310 hide_cursor(window.window)
311
312 test_path = factory.get_current_test_path()
313
314 def handle_event(event):
315 if (event.type == Event.Type.STATE_CHANGE and
316 test_path and event.path == test_path and
317 event.state.visible):
318 show_window()
319
320 event_client = EventClient(
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800321 callback=handle_event, event_loop=EventClient.EVENT_LOOP_GOBJECT_IO)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800322
323 align = gtk.Alignment(xalign=0.5, yalign=0.5)
324 align.add(test_widget)
325
326 window.add(align)
327 for c in window.get_children():
328 # Show all children, but not the window itself yet.
329 c.show_all()
330
331 if window_registration_callback is not None:
332 window_registration_callback(window)
333
334 # Show the window if it is the visible test, or if the test_path is not
335 # available (e.g., run directly from the command line).
336 if (not test_path) or (
337 TestState.from_dict_or_object(
338 factory.get_state_instance().get_test_state(test_path)).visible):
339 show_window()
340 else:
341 window.hide()
342
343 gtk.main()
344
345 gtk.gdk.pointer_ungrab()
346
347 if cleanup_callback is not None:
348 cleanup_callback()
349
350 del event_client
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800351
352
353# ---------------------------------------------------------------------------
354# Server Implementation
355
356
357class Console(object):
358 '''Display a progress log. Implemented by launching an borderless
359 xterm at a strategic location, and running tail against the log.'''
360
361 def __init__(self, allocation):
362 xterm_coords = '145x13+%d+%d' % (allocation.x, allocation.y)
363 logging.info('xterm_coords = %s', xterm_coords)
364 xterm_opts = ('-bg black -fg lightgray -bw 0 -g %s' % xterm_coords)
365 xterm_cmd = (('urxvt %s -e bash -c ' % xterm_opts).split() +
366 ['tail -f "%s"' % factory.CONSOLE_LOG_PATH])
367 logging.info('xterm_cmd = %s', xterm_cmd)
368 self._proc = subprocess.Popen(xterm_cmd)
369
370 def __del__(self):
371 logging.info('console_proc __del__')
372 self._proc.kill()
373
374
375class TestLabelBox(gtk.EventBox): # pylint: disable=R0904
376
377 def __init__(self, test, show_shortcut=False):
378 gtk.EventBox.__init__(self)
379 self.modify_bg(gtk.STATE_NORMAL, LABEL_COLORS[TestState.UNTESTED])
380
381 label_en = make_label(test.label_en, size=_LABEL_EN_SIZE,
382 font=_LABEL_EN_FONT, alignment=(0.5, 0.5),
383 fg=_LABEL_UNTESTED_FG)
384 label_zh = make_label(test.label_zh, size=_LABEL_ZH_SIZE,
385 font=_LABEL_ZH_FONT, alignment=(0.5, 0.5),
386 fg=_LABEL_UNTESTED_FG)
387 label_t = make_label('C-' + test.kbd_shortcut.upper(),
388 size=_LABEL_T_SIZE, font=_LABEL_T_FONT,
389 alignment=(0.5, 0.5), fg=BLACK)
390
391 # build a better label_en with shortcuts
392 index_hotkey = test.label_en.upper().find(test.kbd_shortcut.upper())
393 if show_shortcut and index_hotkey >= 0:
394 attrs = label_en.get_attributes() or pango.AttrList()
395 attrs.insert(pango.AttrUnderline(
396 pango.UNDERLINE_LOW, index_hotkey, index_hotkey + 1))
397 attrs.insert(pango.AttrWeight(
398 pango.WEIGHT_BOLD, index_hotkey, index_hotkey + 1))
399 label_en.set_attributes(attrs)
400
401 hbox = gtk.HBox()
402 hbox.pack_start(label_en, False, False)
403 hbox.pack_start(label_zh, False, False)
404 hbox.pack_start(label_t, False, False)
405 self.add(hbox)
406 self.label_list = [label_en, label_zh]
407
408 def update(self, state):
409 label_fg = (_LABEL_UNTESTED_FG if state.status == TestState.UNTESTED
410 else BLACK)
411 for label in self.label_list:
412 label.modify_fg(gtk.STATE_NORMAL, label_fg)
413 self.modify_bg(gtk.STATE_NORMAL, LABEL_COLORS[state.status])
414 self.queue_draw()
415
416
417class UiState(object):
418
419 def __init__(self, test_widget_box):
420 self._test_widget_box = test_widget_box
421 self._label_box_map = {}
422 self._transition_count = 0
423
424 self._active_test_label_map = None
425
426 def _remove_state_widget(self):
427 """Remove any existing state widgets."""
428 for child in self._test_widget_box.get_children():
429 self._test_widget_box.remove(child)
430 self._active_test_label_map = None
431
432 def update_test_label(self, test, state):
433 label_box = self._label_box_map.get(test)
434 if label_box:
435 label_box.update(state)
436
437 def update_test_state(self, test_list, state_map):
438 active_tests = [
439 t for t in test_list.walk()
440 if t.is_leaf() and state_map[t].status == TestState.ACTIVE]
441 has_active_ui = any(t.has_ui for t in active_tests)
442
443 if not active_tests:
444 # Display the "no active tests" widget if there are still no
445 # active tests after _NO_ACTIVE_TEST_DELAY_MS.
446 def run(transition_count):
447 if transition_count != self._transition_count:
448 # Something has happened
449 return False
450
451 self._transition_count += 1
452 self._remove_state_widget()
453
454 self._test_widget_box.set(0.5, 0.5, 0.0, 0.0)
455 self._test_widget_box.set_padding(0, 0, 0, 0)
456 label_box = gtk.EventBox()
457 label_box.modify_bg(gtk.STATE_NORMAL, BLACK)
458 label = make_label('no active test', font=_OTHER_LABEL_FONT,
459 alignment=(0.5, 0.5))
460 label_box.add(label)
461 self._test_widget_box.add(label_box)
462 self._test_widget_box.show_all()
463
464 gobject.timeout_add(_NO_ACTIVE_TEST_DELAY_MS, run,
465 self._transition_count)
466 return
467
468 self._transition_count += 1
469
470 if has_active_ui:
471 # Remove the widget (if any) since there is an active test
472 # with a UI.
473 self._remove_state_widget()
474 return
475
476 if (self._active_test_label_map is not None and
477 all(t in self._active_test_label_map for t in active_tests)):
478 # All active tests are already present in the summary, so just
479 # update their states.
480 for test, label in self._active_test_label_map.iteritems():
481 label.modify_fg(
482 gtk.STATE_NORMAL,
483 LABEL_COLORS[state_map[test].status])
484 return
485
486 self._remove_state_widget()
487 # No active UI; draw summary of current test states
488 self._test_widget_box.set(0.5, 0.0, 0.0, 0.0)
489 self._test_widget_box.set_padding(40, 0, 0, 0)
490 vbox, self._active_test_label_map = make_summary_box(
491 [t for t in test_list.subtests
492 if state_map[t].status == TestState.ACTIVE],
493 state_map)
494 self._test_widget_box.add(vbox)
495 self._test_widget_box.show_all()
496
497 def set_label_box(self, test, label_box):
498 self._label_box_map[test] = label_box
499
500
501def main(test_list_path):
502 '''Starts the main UI.
503
504 This is launched by the autotest/cros/factory/client.
505 When operators press keyboard shortcuts, the shortcut
506 value is sent as an event to the control program.'''
507
508 test_list = None
509 ui_state = None
510 event_client = None
511
512 # Delay loading Xlib because Xlib is currently not available in image build
513 # process host-depends list, and it's only required by the main UI, not all
514 # the tests using UI library (in other words, it'll be slower and break the
515 # build system if Xlib is globally imported).
516 try:
517 from Xlib import X
518 from Xlib.display import Display
519 disp = Display()
520 except:
521 logging.error('Failed loading X modules')
522 raise
523
524 def handle_key_release_event(_, event):
525 logging.info('base ui key event (%s)', event.keyval)
526 return True
527
528 def handle_event(event):
529 if event.type == Event.Type.STATE_CHANGE:
530 test = test_list.lookup_path(event.path)
531 state_map = test_list.get_state_map()
532 ui_state.update_test_label(test, state_map[test])
533 ui_state.update_test_state(test_list, state_map)
534
535 def grab_shortcut_keys(kbd_shortcuts):
536 root = disp.screen().root
537 keycode_map = {}
538
539 def handle_xevent( # pylint: disable=W0102
540 dummy_src, dummy_cond, xhandle=root.display,
541 keycode_map=keycode_map):
542 for dummy_i in range(0, xhandle.pending_events()):
543 xevent = xhandle.next_event()
544 if xevent.type == X.KeyPress:
545 keycode = xevent.detail
546 event_client.post_event(Event('kbd_shortcut',
547 key=keycode_map[keycode]))
548 return True
549
550 # We want to receive KeyPress events
551 root.change_attributes(event_mask = X.KeyPressMask)
552
553 for mod, shortcut in ([(X.ControlMask, k) for k in kbd_shortcuts] +
554 [(X.Mod1Mask, 'Tab')]): # Mod1 = Alt
555 keysym = gtk.gdk.keyval_from_name(shortcut)
556 keycode = disp.keysym_to_keycode(keysym)
557 keycode_map[keycode] = shortcut
558 root.grab_key(keycode, mod, 1,
559 X.GrabModeAsync, X.GrabModeAsync)
560
561 # This flushes the XGrabKey calls to the server.
562 for dummy_x in range(0, root.display.pending_events()):
563 root.display.next_event()
564 gobject.io_add_watch(root.display, gobject.IO_IN, handle_xevent)
565
566
567 test_list = factory.read_test_list(test_list_path)
568
569 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
570 window.connect('destroy', lambda _: gtk.main_quit())
571 window.modify_bg(gtk.STATE_NORMAL, BLACK)
572
573 event_client = EventClient(
574 callback=handle_event,
575 event_loop=EventClient.EVENT_LOOP_GOBJECT_IO)
576
577 screen = window.get_screen()
578 if (screen is None):
579 logging.info('ERROR: communication with the X server is not working, ' +
580 'could not find a working screen. UI exiting.')
581 sys.exit(1)
582
583 screen_size_str = os.environ.get('CROS_SCREEN_SIZE')
584 if screen_size_str:
585 match = re.match(r'^(\d+)x(\d+)$', screen_size_str)
586 assert match, 'CROS_SCREEN_SIZE should be {width}x{height}'
587 screen_size = (int(match.group(1)), int(match.group(2)))
588 else:
589 screen_size = (screen.get_width(), screen.get_height())
590 window.set_size_request(*screen_size)
591
592 label_trough = gtk.VBox()
593 label_trough.set_spacing(0)
594
595 rhs_box = gtk.EventBox()
596 rhs_box.modify_bg(gtk.STATE_NORMAL, _LABEL_TROUGH_COLOR)
597 rhs_box.add(label_trough)
598
599 console_box = gtk.EventBox()
600 console_box.set_size_request(-1, 180)
601 console_box.modify_bg(gtk.STATE_NORMAL, BLACK)
602
603 test_widget_box = gtk.Alignment()
604 test_widget_box.set_size_request(-1, -1)
605
606 lhs_box = gtk.VBox()
607 lhs_box.pack_end(console_box, False, False)
608 lhs_box.pack_start(test_widget_box)
609 lhs_box.pack_start(make_hsep(3), False, False)
610
611 base_box = gtk.HBox()
612 base_box.pack_end(rhs_box, False, False)
613 base_box.pack_end(make_vsep(3), False, False)
614 base_box.pack_start(lhs_box)
615
616 window.connect('key-release-event', handle_key_release_event)
617 window.add_events(gtk.gdk.KEY_RELEASE_MASK)
618
619 ui_state = UiState(test_widget_box)
620
621 for test in test_list.subtests:
622 label_box = TestLabelBox(test, True)
623 ui_state.set_label_box(test, label_box)
624 label_trough.pack_start(label_box, False, False)
625 label_trough.pack_start(make_hsep(), False, False)
626
627 window.add(base_box)
628 window.show_all()
629
630 state_map = test_list.get_state_map()
631 for test, state in test_list.get_state_map().iteritems():
632 ui_state.update_test_label(test, state)
633 ui_state.update_test_state(test_list, state_map)
634
635 grab_shortcut_keys(test_list.kbd_shortcut_map.keys())
636
637 hide_cursor(window.window)
638
639 test_widget_allocation = test_widget_box.get_allocation()
640 test_widget_size = (test_widget_allocation.width,
641 test_widget_allocation.height)
642 factory.set_shared_data('test_widget_size', test_widget_size)
643
644 dummy_console = Console(console_box.get_allocation())
645
646 event_client.post_event(Event(Event.Type.UI_READY))
647
648 logging.info('cros/factory/ui setup done, starting gtk.main()...')
649 gtk.main()
650 logging.info('cros/factory/ui gtk.main() finished, exiting.')
651
652
653if __name__ == '__main__':
654 if len(sys.argv) != 2:
655 print 'usage: %s <test list path>' % sys.argv[0]
656 sys.exit(1)
657
658 factory.init_logging("cros/factory/ui", verbose=True)
659 main(sys.argv[1])