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