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