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