1#!/usr/bin/python 2""" 3Step file creator/editor. 4 5@copyright: Red Hat Inc 2009 6@author: mgoldish@redhat.com (Michael Goldish) 7@version: "20090401" 8""" 9 10import pygtk, gtk, os, glob, shutil, sys, logging 11import common, ppm_utils 12pygtk.require('2.0') 13 14 15# General utilities 16 17def corner_and_size_clipped(startpoint, endpoint, limits): 18 c0 = startpoint[:] 19 c1 = endpoint[:] 20 if c0[0] < 0: 21 c0[0] = 0 22 if c0[1] < 0: 23 c0[1] = 0 24 if c1[0] < 0: 25 c1[0] = 0 26 if c1[1] < 0: 27 c1[1] = 0 28 if c0[0] > limits[0] - 1: 29 c0[0] = limits[0] - 1 30 if c0[1] > limits[1] - 1: 31 c0[1] = limits[1] - 1 32 if c1[0] > limits[0] - 1: 33 c1[0] = limits[0] - 1 34 if c1[1] > limits[1] - 1: 35 c1[1] = limits[1] - 1 36 return ([min(c0[0], c1[0]), 37 min(c0[1], c1[1])], 38 [abs(c1[0] - c0[0]) + 1, 39 abs(c1[1] - c0[1]) + 1]) 40 41 42def key_event_to_qemu_string(event): 43 keymap = gtk.gdk.keymap_get_default() 44 keyvals = keymap.get_entries_for_keycode(event.hardware_keycode) 45 keyval = keyvals[0][0] 46 keyname = gtk.gdk.keyval_name(keyval) 47 48 dict = { "Return": "ret", 49 "Tab": "tab", 50 "space": "spc", 51 "Left": "left", 52 "Right": "right", 53 "Up": "up", 54 "Down": "down", 55 "F1": "f1", 56 "F2": "f2", 57 "F3": "f3", 58 "F4": "f4", 59 "F5": "f5", 60 "F6": "f6", 61 "F7": "f7", 62 "F8": "f8", 63 "F9": "f9", 64 "F10": "f10", 65 "F11": "f11", 66 "F12": "f12", 67 "Escape": "esc", 68 "minus": "minus", 69 "equal": "equal", 70 "BackSpace": "backspace", 71 "comma": "comma", 72 "period": "dot", 73 "slash": "slash", 74 "Insert": "insert", 75 "Delete": "delete", 76 "Home": "home", 77 "End": "end", 78 "Page_Up": "pgup", 79 "Page_Down": "pgdn", 80 "Menu": "menu", 81 "semicolon": "0x27", 82 "backslash": "0x2b", 83 "apostrophe": "0x28", 84 "grave": "0x29", 85 "less": "0x2b", 86 "bracketleft": "0x1a", 87 "bracketright": "0x1b", 88 "Super_L": "0xdc", 89 "Super_R": "0xdb", 90 } 91 92 if ord('a') <= keyval <= ord('z') or ord('0') <= keyval <= ord('9'): 93 str = keyname 94 elif keyname in dict.keys(): 95 str = dict[keyname] 96 else: 97 return "" 98 99 if event.state & gtk.gdk.CONTROL_MASK: 100 str = "ctrl-" + str 101 if event.state & gtk.gdk.MOD1_MASK: 102 str = "alt-" + str 103 if event.state & gtk.gdk.SHIFT_MASK: 104 str = "shift-" + str 105 106 return str 107 108 109class StepMakerWindow: 110 111 # Constructor 112 113 def __init__(self): 114 # Window 115 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) 116 self.window.set_title("Step Maker Window") 117 self.window.connect("delete-event", self.delete_event) 118 self.window.connect("destroy", self.destroy) 119 self.window.set_default_size(600, 800) 120 121 # Main box (inside a frame which is inside a VBox) 122 self.menu_vbox = gtk.VBox() 123 self.window.add(self.menu_vbox) 124 self.menu_vbox.show() 125 126 frame = gtk.Frame() 127 frame.set_border_width(10) 128 frame.set_shadow_type(gtk.SHADOW_NONE) 129 self.menu_vbox.pack_end(frame) 130 frame.show() 131 132 self.main_vbox = gtk.VBox(spacing=10) 133 frame.add(self.main_vbox) 134 self.main_vbox.show() 135 136 # EventBox 137 self.scrolledwindow = gtk.ScrolledWindow() 138 self.scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, 139 gtk.POLICY_AUTOMATIC) 140 self.scrolledwindow.set_shadow_type(gtk.SHADOW_NONE) 141 self.main_vbox.pack_start(self.scrolledwindow) 142 self.scrolledwindow.show() 143 144 table = gtk.Table(1, 1) 145 self.scrolledwindow.add_with_viewport(table) 146 table.show() 147 table.realize() 148 149 self.event_box = gtk.EventBox() 150 table.attach(self.event_box, 0, 1, 0, 1, gtk.EXPAND, gtk.EXPAND) 151 self.event_box.show() 152 self.event_box.realize() 153 154 # Image 155 self.image = gtk.Image() 156 self.event_box.add(self.image) 157 self.image.show() 158 159 # Data VBox 160 self.data_vbox = gtk.VBox(spacing=10) 161 self.main_vbox.pack_start(self.data_vbox, expand=False) 162 self.data_vbox.show() 163 164 # User VBox 165 self.user_vbox = gtk.VBox(spacing=10) 166 self.main_vbox.pack_start(self.user_vbox, expand=False) 167 self.user_vbox.show() 168 169 # Screendump ID HBox 170 box = gtk.HBox(spacing=10) 171 self.data_vbox.pack_start(box) 172 box.show() 173 174 label = gtk.Label("Screendump ID:") 175 box.pack_start(label, False) 176 label.show() 177 178 self.entry_screendump = gtk.Entry() 179 self.entry_screendump.set_editable(False) 180 box.pack_start(self.entry_screendump) 181 self.entry_screendump.show() 182 183 label = gtk.Label("Time:") 184 box.pack_start(label, False) 185 label.show() 186 187 self.entry_time = gtk.Entry() 188 self.entry_time.set_editable(False) 189 self.entry_time.set_width_chars(10) 190 box.pack_start(self.entry_time, False) 191 self.entry_time.show() 192 193 # Comment HBox 194 box = gtk.HBox(spacing=10) 195 self.data_vbox.pack_start(box) 196 box.show() 197 198 label = gtk.Label("Comment:") 199 box.pack_start(label, False) 200 label.show() 201 202 self.entry_comment = gtk.Entry() 203 box.pack_start(self.entry_comment) 204 self.entry_comment.show() 205 206 # Sleep HBox 207 box = gtk.HBox(spacing=10) 208 self.data_vbox.pack_start(box) 209 box.show() 210 211 self.check_sleep = gtk.CheckButton("Sleep:") 212 self.check_sleep.connect("toggled", self.event_check_sleep_toggled) 213 box.pack_start(self.check_sleep, False) 214 self.check_sleep.show() 215 216 self.spin_sleep = gtk.SpinButton(gtk.Adjustment(0, 0, 50000, 1, 10, 0), 217 climb_rate=0.0) 218 box.pack_start(self.spin_sleep, False) 219 self.spin_sleep.show() 220 221 # Barrier HBox 222 box = gtk.HBox(spacing=10) 223 self.data_vbox.pack_start(box) 224 box.show() 225 226 self.check_barrier = gtk.CheckButton("Barrier:") 227 self.check_barrier.connect("toggled", self.event_check_barrier_toggled) 228 box.pack_start(self.check_barrier, False) 229 self.check_barrier.show() 230 231 vbox = gtk.VBox() 232 box.pack_start(vbox) 233 vbox.show() 234 235 self.label_barrier_region = gtk.Label("Region:") 236 self.label_barrier_region.set_alignment(0, 0.5) 237 vbox.pack_start(self.label_barrier_region) 238 self.label_barrier_region.show() 239 240 self.label_barrier_md5sum = gtk.Label("MD5:") 241 self.label_barrier_md5sum.set_alignment(0, 0.5) 242 vbox.pack_start(self.label_barrier_md5sum) 243 self.label_barrier_md5sum.show() 244 245 self.label_barrier_timeout = gtk.Label("Timeout:") 246 box.pack_start(self.label_barrier_timeout, False) 247 self.label_barrier_timeout.show() 248 249 self.spin_barrier_timeout = gtk.SpinButton(gtk.Adjustment(0, 0, 50000, 250 1, 10, 0), 251 climb_rate=0.0) 252 box.pack_start(self.spin_barrier_timeout, False) 253 self.spin_barrier_timeout.show() 254 255 self.check_barrier_optional = gtk.CheckButton("Optional") 256 box.pack_start(self.check_barrier_optional, False) 257 self.check_barrier_optional.show() 258 259 # Keystrokes HBox 260 box = gtk.HBox(spacing=10) 261 self.data_vbox.pack_start(box) 262 box.show() 263 264 label = gtk.Label("Keystrokes:") 265 box.pack_start(label, False) 266 label.show() 267 268 frame = gtk.Frame() 269 frame.set_shadow_type(gtk.SHADOW_IN) 270 box.pack_start(frame) 271 frame.show() 272 273 self.text_buffer = gtk.TextBuffer() 274 self.entry_keys = gtk.TextView(self.text_buffer) 275 self.entry_keys.set_wrap_mode(gtk.WRAP_WORD) 276 self.entry_keys.connect("key-press-event", self.event_key_press) 277 frame.add(self.entry_keys) 278 self.entry_keys.show() 279 280 self.check_manual = gtk.CheckButton("Manual") 281 self.check_manual.connect("toggled", self.event_manual_toggled) 282 box.pack_start(self.check_manual, False) 283 self.check_manual.show() 284 285 button = gtk.Button("Clear") 286 button.connect("clicked", self.event_clear_clicked) 287 box.pack_start(button, False) 288 button.show() 289 290 # Mouse click HBox 291 box = gtk.HBox(spacing=10) 292 self.data_vbox.pack_start(box) 293 box.show() 294 295 label = gtk.Label("Mouse action:") 296 box.pack_start(label, False) 297 label.show() 298 299 self.button_capture = gtk.Button("Capture") 300 box.pack_start(self.button_capture, False) 301 self.button_capture.show() 302 303 self.check_mousemove = gtk.CheckButton("Move: ...") 304 box.pack_start(self.check_mousemove, False) 305 self.check_mousemove.show() 306 307 self.check_mouseclick = gtk.CheckButton("Click: ...") 308 box.pack_start(self.check_mouseclick, False) 309 self.check_mouseclick.show() 310 311 self.spin_sensitivity = gtk.SpinButton(gtk.Adjustment(1, 1, 100, 1, 10, 312 0), 313 climb_rate=0.0) 314 box.pack_end(self.spin_sensitivity, False) 315 self.spin_sensitivity.show() 316 317 label = gtk.Label("Sensitivity:") 318 box.pack_end(label, False) 319 label.show() 320 321 self.spin_latency = gtk.SpinButton(gtk.Adjustment(10, 1, 500, 1, 10, 0), 322 climb_rate=0.0) 323 box.pack_end(self.spin_latency, False) 324 self.spin_latency.show() 325 326 label = gtk.Label("Latency:") 327 box.pack_end(label, False) 328 label.show() 329 330 self.handler_event_box_press = None 331 self.handler_event_box_release = None 332 self.handler_event_box_scroll = None 333 self.handler_event_box_motion = None 334 self.handler_event_box_expose = None 335 336 self.window.realize() 337 self.window.show() 338 339 self.clear_state() 340 341 # Utilities 342 343 def message(self, text, title): 344 dlg = gtk.MessageDialog(self.window, 345 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 346 gtk.MESSAGE_INFO, 347 gtk.BUTTONS_CLOSE, 348 title) 349 dlg.set_title(title) 350 dlg.format_secondary_text(text) 351 response = dlg.run() 352 dlg.destroy() 353 354 355 def question_yes_no(self, text, title): 356 dlg = gtk.MessageDialog(self.window, 357 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 358 gtk.MESSAGE_QUESTION, 359 gtk.BUTTONS_YES_NO, 360 title) 361 dlg.set_title(title) 362 dlg.format_secondary_text(text) 363 response = dlg.run() 364 dlg.destroy() 365 if response == gtk.RESPONSE_YES: 366 return True 367 return False 368 369 370 def inputdialog(self, text, title, default_response=""): 371 # Define a little helper function 372 def inputdialog_entry_activated(entry): 373 dlg.response(gtk.RESPONSE_OK) 374 375 # Create the dialog 376 dlg = gtk.MessageDialog(self.window, 377 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 378 gtk.MESSAGE_QUESTION, 379 gtk.BUTTONS_OK_CANCEL, 380 title) 381 dlg.set_title(title) 382 dlg.format_secondary_text(text) 383 384 # Create an entry widget 385 entry = gtk.Entry() 386 entry.set_text(default_response) 387 entry.connect("activate", inputdialog_entry_activated) 388 dlg.vbox.pack_start(entry) 389 entry.show() 390 391 # Run the dialog 392 response = dlg.run() 393 dlg.destroy() 394 if response == gtk.RESPONSE_OK: 395 return entry.get_text() 396 return None 397 398 399 def filedialog(self, title=None, default_filename=None): 400 chooser = gtk.FileChooserDialog(title=title, parent=self.window, 401 action=gtk.FILE_CHOOSER_ACTION_OPEN, 402 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, 403 gtk.RESPONSE_OK)) 404 chooser.resize(700, 500) 405 if default_filename: 406 chooser.set_filename(os.path.abspath(default_filename)) 407 filename = None 408 response = chooser.run() 409 if response == gtk.RESPONSE_OK: 410 filename = chooser.get_filename() 411 chooser.destroy() 412 return filename 413 414 415 def redirect_event_box_input(self, press=None, release=None, scroll=None, 416 motion=None, expose=None): 417 if self.handler_event_box_press != None: \ 418 self.event_box.disconnect(self.handler_event_box_press) 419 if self.handler_event_box_release != None: \ 420 self.event_box.disconnect(self.handler_event_box_release) 421 if self.handler_event_box_scroll != None: \ 422 self.event_box.disconnect(self.handler_event_box_scroll) 423 if self.handler_event_box_motion != None: \ 424 self.event_box.disconnect(self.handler_event_box_motion) 425 if self.handler_event_box_expose != None: \ 426 self.event_box.disconnect(self.handler_event_box_expose) 427 self.handler_event_box_press = None 428 self.handler_event_box_release = None 429 self.handler_event_box_scroll = None 430 self.handler_event_box_motion = None 431 self.handler_event_box_expose = None 432 if press != None: self.handler_event_box_press = \ 433 self.event_box.connect("button-press-event", press) 434 if release != None: self.handler_event_box_release = \ 435 self.event_box.connect("button-release-event", release) 436 if scroll != None: self.handler_event_box_scroll = \ 437 self.event_box.connect("scroll-event", scroll) 438 if motion != None: self.handler_event_box_motion = \ 439 self.event_box.connect("motion-notify-event", motion) 440 if expose != None: self.handler_event_box_expose = \ 441 self.event_box.connect_after("expose-event", expose) 442 443 444 def get_keys(self): 445 return self.text_buffer.get_text( 446 self.text_buffer.get_start_iter(), 447 self.text_buffer.get_end_iter()) 448 449 450 def add_key(self, key): 451 text = self.get_keys() 452 if len(text) > 0 and text[-1] != ' ': 453 text += " " 454 text += key 455 self.text_buffer.set_text(text) 456 457 458 def clear_keys(self): 459 self.text_buffer.set_text("") 460 461 462 def update_barrier_info(self): 463 if self.barrier_selected: 464 self.label_barrier_region.set_text("Selected region: Corner: " + \ 465 str(tuple(self.barrier_corner)) + \ 466 " Size: " + \ 467 str(tuple(self.barrier_size))) 468 else: 469 self.label_barrier_region.set_text("No region selected.") 470 self.label_barrier_md5sum.set_text("MD5: " + self.barrier_md5sum) 471 472 473 def update_mouse_click_info(self): 474 if self.mouse_click_captured: 475 self.check_mousemove.set_label("Move: " + \ 476 str(tuple(self.mouse_click_coords))) 477 self.check_mouseclick.set_label("Click: button %d" % 478 self.mouse_click_button) 479 else: 480 self.check_mousemove.set_label("Move: ...") 481 self.check_mouseclick.set_label("Click: ...") 482 483 484 def clear_state(self, clear_screendump=True): 485 # Recording time 486 self.entry_time.set_text("unknown") 487 if clear_screendump: 488 # Screendump 489 self.clear_image() 490 # Screendump ID 491 self.entry_screendump.set_text("") 492 # Comment 493 self.entry_comment.set_text("") 494 # Sleep 495 self.check_sleep.set_active(True) 496 self.check_sleep.set_active(False) 497 self.spin_sleep.set_value(10) 498 # Barrier 499 self.clear_barrier_state() 500 # Keystrokes 501 self.check_manual.set_active(False) 502 self.clear_keys() 503 # Mouse actions 504 self.check_mousemove.set_sensitive(False) 505 self.check_mouseclick.set_sensitive(False) 506 self.check_mousemove.set_active(False) 507 self.check_mouseclick.set_active(False) 508 self.mouse_click_captured = False 509 self.mouse_click_coords = [0, 0] 510 self.mouse_click_button = 0 511 self.update_mouse_click_info() 512 513 514 def clear_barrier_state(self): 515 self.check_barrier.set_active(True) 516 self.check_barrier.set_active(False) 517 self.check_barrier_optional.set_active(False) 518 self.spin_barrier_timeout.set_value(10) 519 self.barrier_selection_started = False 520 self.barrier_selected = False 521 self.barrier_corner0 = [0, 0] 522 self.barrier_corner1 = [0, 0] 523 self.barrier_corner = [0, 0] 524 self.barrier_size = [0, 0] 525 self.barrier_md5sum = "" 526 self.update_barrier_info() 527 528 529 def set_image(self, w, h, data): 530 (self.image_width, self.image_height, self.image_data) = (w, h, data) 531 self.image.set_from_pixbuf(gtk.gdk.pixbuf_new_from_data( 532 data, gtk.gdk.COLORSPACE_RGB, False, 8, 533 w, h, w*3)) 534 hscrollbar = self.scrolledwindow.get_hscrollbar() 535 hscrollbar.set_range(0, w) 536 vscrollbar = self.scrolledwindow.get_vscrollbar() 537 vscrollbar.set_range(0, h) 538 539 540 def set_image_from_file(self, filename): 541 if not ppm_utils.image_verify_ppm_file(filename): 542 logging.warning("set_image_from_file: Warning: received invalid" 543 "screendump file") 544 return self.clear_image() 545 (w, h, data) = ppm_utils.image_read_from_ppm_file(filename) 546 self.set_image(w, h, data) 547 548 549 def clear_image(self): 550 self.image.clear() 551 self.image_width = 0 552 self.image_height = 0 553 self.image_data = "" 554 555 556 def update_screendump_id(self, data_dir): 557 if not self.image_data: 558 return 559 # Find a proper ID for the screendump 560 scrdump_md5sum = ppm_utils.image_md5sum(self.image_width, 561 self.image_height, 562 self.image_data) 563 scrdump_id = ppm_utils.find_id_for_screendump(scrdump_md5sum, data_dir) 564 if not scrdump_id: 565 # Not found; generate one 566 scrdump_id = ppm_utils.generate_id_for_screendump(scrdump_md5sum, 567 data_dir) 568 self.entry_screendump.set_text(scrdump_id) 569 570 571 def get_step_lines(self, data_dir=None): 572 if self.check_barrier.get_active() and not self.barrier_selected: 573 self.message("No barrier region selected.", "Error") 574 return 575 576 str = "step" 577 578 # Add step recording time 579 if self.entry_time.get_text(): 580 str += " " + self.entry_time.get_text() 581 582 str += "\n" 583 584 # Add screendump line 585 if self.image_data: 586 str += "screendump %s\n" % self.entry_screendump.get_text() 587 588 # Add comment 589 if self.entry_comment.get_text(): 590 str += "# %s\n" % self.entry_comment.get_text() 591 592 # Add sleep line 593 if self.check_sleep.get_active(): 594 str += "sleep %d\n" % self.spin_sleep.get_value() 595 596 # Add barrier_2 line 597 if self.check_barrier.get_active(): 598 str += "barrier_2 %d %d %d %d %s %d" % ( 599 self.barrier_size[0], self.barrier_size[1], 600 self.barrier_corner[0], self.barrier_corner[1], 601 self.barrier_md5sum, self.spin_barrier_timeout.get_value()) 602 if self.check_barrier_optional.get_active(): 603 str += " optional" 604 str += "\n" 605 606 # Add "Sending keys" comment 607 keys_to_send = self.get_keys().split() 608 if keys_to_send: 609 str += "# Sending keys: %s\n" % self.get_keys() 610 611 # Add key and var lines 612 for key in keys_to_send: 613 if key.startswith("$"): 614 varname = key[1:] 615 str += "var %s\n" % varname 616 else: 617 str += "key %s\n" % key 618 619 # Add mousemove line 620 if self.check_mousemove.get_active(): 621 str += "mousemove %d %d\n" % (self.mouse_click_coords[0], 622 self.mouse_click_coords[1]) 623 624 # Add mouseclick line 625 if self.check_mouseclick.get_active(): 626 dict = { 1 : 1, 627 2 : 2, 628 3 : 4 } 629 str += "mouseclick %d\n" % dict[self.mouse_click_button] 630 631 # Write screendump and cropped screendump image files 632 if data_dir and self.image_data: 633 # Create the data dir if it doesn't exist 634 if not os.path.exists(data_dir): 635 os.makedirs(data_dir) 636 # Get the full screendump filename 637 scrdump_filename = os.path.join(data_dir, 638 self.entry_screendump.get_text()) 639 # Write screendump file if it doesn't exist 640 if not os.path.exists(scrdump_filename): 641 try: 642 ppm_utils.image_write_to_ppm_file(scrdump_filename, 643 self.image_width, 644 self.image_height, 645 self.image_data) 646 except IOError: 647 self.message("Could not write screendump file.", "Error") 648 649 #if self.check_barrier.get_active(): 650 # # Crop image to get the cropped screendump 651 # (cw, ch, cdata) = ppm_utils.image_crop( 652 # self.image_width, self.image_height, self.image_data, 653 # self.barrier_corner[0], self.barrier_corner[1], 654 # self.barrier_size[0], self.barrier_size[1]) 655 # cropped_scrdump_md5sum = ppm_utils.image_md5sum(cw, ch, cdata) 656 # cropped_scrdump_filename = \ 657 # ppm_utils.get_cropped_screendump_filename(scrdump_filename, 658 # cropped_scrdump_md5sum) 659 # # Write cropped screendump file 660 # try: 661 # ppm_utils.image_write_to_ppm_file(cropped_scrdump_filename, 662 # cw, ch, cdata) 663 # except IOError: 664 # self.message("Could not write cropped screendump file.", 665 # "Error") 666 667 return str 668 669 def set_state_from_step_lines(self, str, data_dir, warn=True): 670 self.clear_state() 671 672 for line in str.splitlines(): 673 words = line.split() 674 if not words: 675 continue 676 677 if line.startswith("#") \ 678 and not self.entry_comment.get_text() \ 679 and not line.startswith("# Sending keys:") \ 680 and not line.startswith("# ----"): 681 self.entry_comment.set_text(line.strip("#").strip()) 682 683 elif words[0] == "step": 684 if len(words) >= 2: 685 self.entry_time.set_text(words[1]) 686 687 elif words[0] == "screendump": 688 self.entry_screendump.set_text(words[1]) 689 self.set_image_from_file(os.path.join(data_dir, words[1])) 690 691 elif words[0] == "sleep": 692 self.spin_sleep.set_value(int(words[1])) 693 self.check_sleep.set_active(True) 694 695 elif words[0] == "key": 696 self.add_key(words[1]) 697 698 elif words[0] == "var": 699 self.add_key("$%s" % words[1]) 700 701 elif words[0] == "mousemove": 702 self.mouse_click_captured = True 703 self.mouse_click_coords = [int(words[1]), int(words[2])] 704 self.update_mouse_click_info() 705 706 elif words[0] == "mouseclick": 707 self.mouse_click_captured = True 708 self.mouse_click_button = int(words[1]) 709 self.update_mouse_click_info() 710 711 elif words[0] == "barrier_2": 712 # Get region corner and size from step lines 713 self.barrier_corner = [int(words[3]), int(words[4])] 714 self.barrier_size = [int(words[1]), int(words[2])] 715 # Get corner0 and corner1 from step lines 716 self.barrier_corner0 = self.barrier_corner 717 self.barrier_corner1 = [self.barrier_corner[0] + 718 self.barrier_size[0] - 1, 719 self.barrier_corner[1] + 720 self.barrier_size[1] - 1] 721 # Get the md5sum 722 self.barrier_md5sum = words[5] 723 # Pretend the user selected the region with the mouse 724 self.barrier_selection_started = True 725 self.barrier_selected = True 726 # Update label widgets according to region information 727 self.update_barrier_info() 728 # Check the barrier checkbutton 729 self.check_barrier.set_active(True) 730 # Set timeout value 731 self.spin_barrier_timeout.set_value(int(words[6])) 732 # Set 'optional' checkbutton state 733 self.check_barrier_optional.set_active(words[-1] == "optional") 734 # Update the image widget 735 self.event_box.queue_draw() 736 737 if warn: 738 # See if the computed md5sum matches the one recorded in 739 # the file 740 computed_md5sum = ppm_utils.get_region_md5sum( 741 self.image_width, self.image_height, 742 self.image_data, self.barrier_corner[0], 743 self.barrier_corner[1], self.barrier_size[0], 744 self.barrier_size[1]) 745 if computed_md5sum != self.barrier_md5sum: 746 self.message("Computed MD5 sum (%s) differs from MD5" 747 " sum recorded in steps file (%s)" % 748 (computed_md5sum, self.barrier_md5sum), 749 "Warning") 750 751 # Events 752 753 def delete_event(self, widget, event): 754 pass 755 756 def destroy(self, widget): 757 gtk.main_quit() 758 759 def event_check_barrier_toggled(self, widget): 760 if self.check_barrier.get_active(): 761 self.redirect_event_box_input( 762 self.event_button_press, 763 self.event_button_release, 764 None, 765 None, 766 self.event_expose) 767 self.event_box.queue_draw() 768 self.event_box.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.CROSSHAIR)) 769 self.label_barrier_region.set_sensitive(True) 770 self.label_barrier_md5sum.set_sensitive(True) 771 self.label_barrier_timeout.set_sensitive(True) 772 self.spin_barrier_timeout.set_sensitive(True) 773 self.check_barrier_optional.set_sensitive(True) 774 else: 775 self.redirect_event_box_input() 776 self.event_box.queue_draw() 777 self.event_box.window.set_cursor(None) 778 self.label_barrier_region.set_sensitive(False) 779 self.label_barrier_md5sum.set_sensitive(False) 780 self.label_barrier_timeout.set_sensitive(False) 781 self.spin_barrier_timeout.set_sensitive(False) 782 self.check_barrier_optional.set_sensitive(False) 783 784 def event_check_sleep_toggled(self, widget): 785 if self.check_sleep.get_active(): 786 self.spin_sleep.set_sensitive(True) 787 else: 788 self.spin_sleep.set_sensitive(False) 789 790 def event_manual_toggled(self, widget): 791 self.entry_keys.grab_focus() 792 793 def event_clear_clicked(self, widget): 794 self.clear_keys() 795 self.entry_keys.grab_focus() 796 797 def event_expose(self, widget, event): 798 if not self.barrier_selection_started: 799 return 800 (corner, size) = corner_and_size_clipped(self.barrier_corner0, 801 self.barrier_corner1, 802 self.event_box.size_request()) 803 gc = self.event_box.window.new_gc(line_style=gtk.gdk.LINE_DOUBLE_DASH, 804 line_width=1) 805 gc.set_foreground(gc.get_colormap().alloc_color("red")) 806 gc.set_background(gc.get_colormap().alloc_color("dark red")) 807 gc.set_dashes(0, (4, 4)) 808 self.event_box.window.draw_rectangle( 809 gc, False, 810 corner[0], corner[1], 811 size[0]-1, size[1]-1) 812 813 def event_drag_motion(self, widget, event): 814 old_corner1 = self.barrier_corner1 815 self.barrier_corner1 = [int(event.x), int(event.y)] 816 (corner, size) = corner_and_size_clipped(self.barrier_corner0, 817 self.barrier_corner1, 818 self.event_box.size_request()) 819 (old_corner, old_size) = corner_and_size_clipped(self.barrier_corner0, 820 old_corner1, 821 self.event_box.size_request()) 822 corner0 = [min(corner[0], old_corner[0]), min(corner[1], old_corner[1])] 823 corner1 = [max(corner[0] + size[0], old_corner[0] + old_size[0]), 824 max(corner[1] + size[1], old_corner[1] + old_size[1])] 825 size = [corner1[0] - corner0[0] + 1, 826 corner1[1] - corner0[1] + 1] 827 self.event_box.queue_draw_area(corner0[0], corner0[1], size[0], size[1]) 828 829 def event_button_press(self, widget, event): 830 (corner, size) = corner_and_size_clipped(self.barrier_corner0, 831 self.barrier_corner1, 832 self.event_box.size_request()) 833 self.event_box.queue_draw_area(corner[0], corner[1], size[0], size[1]) 834 self.barrier_corner0 = [int(event.x), int(event.y)] 835 self.barrier_corner1 = [int(event.x), int(event.y)] 836 self.redirect_event_box_input( 837 self.event_button_press, 838 self.event_button_release, 839 None, 840 self.event_drag_motion, 841 self.event_expose) 842 self.barrier_selection_started = True 843 844 def event_button_release(self, widget, event): 845 self.redirect_event_box_input( 846 self.event_button_press, 847 self.event_button_release, 848 None, 849 None, 850 self.event_expose) 851 (self.barrier_corner, self.barrier_size) = \ 852 corner_and_size_clipped(self.barrier_corner0, self.barrier_corner1, 853 self.event_box.size_request()) 854 self.barrier_md5sum = ppm_utils.get_region_md5sum( 855 self.image_width, self.image_height, self.image_data, 856 self.barrier_corner[0], self.barrier_corner[1], 857 self.barrier_size[0], self.barrier_size[1]) 858 self.barrier_selected = True 859 self.update_barrier_info() 860 861 def event_key_press(self, widget, event): 862 if self.check_manual.get_active(): 863 return False 864 str = key_event_to_qemu_string(event) 865 self.add_key(str) 866 return True 867 868 869class StepEditor(StepMakerWindow): 870 ui = '''<ui> 871 <menubar name="MenuBar"> 872 <menu action="File"> 873 <menuitem action="Open"/> 874 <separator/> 875 <menuitem action="Quit"/> 876 </menu> 877 <menu action="Edit"> 878 <menuitem action="CopyStep"/> 879 <menuitem action="DeleteStep"/> 880 </menu> 881 <menu action="Insert"> 882 <menuitem action="InsertNewBefore"/> 883 <menuitem action="InsertNewAfter"/> 884 <separator/> 885 <menuitem action="InsertStepsBefore"/> 886 <menuitem action="InsertStepsAfter"/> 887 </menu> 888 <menu action="Tools"> 889 <menuitem action="CleanUp"/> 890 </menu> 891 </menubar> 892</ui>''' 893 894 # Constructor 895 896 def __init__(self, filename=None): 897 StepMakerWindow.__init__(self) 898 899 self.steps_filename = None 900 self.steps = [] 901 902 # Create a UIManager instance 903 uimanager = gtk.UIManager() 904 905 # Add the accelerator group to the toplevel window 906 accelgroup = uimanager.get_accel_group() 907 self.window.add_accel_group(accelgroup) 908 909 # Create an ActionGroup 910 actiongroup = gtk.ActionGroup('StepEditor') 911 912 # Create actions 913 actiongroup.add_actions([ 914 ('Quit', gtk.STOCK_QUIT, '_Quit', None, 'Quit the Program', 915 self.quit), 916 ('Open', gtk.STOCK_OPEN, '_Open', None, 'Open steps file', 917 self.open_steps_file), 918 ('CopyStep', gtk.STOCK_COPY, '_Copy current step...', "", 919 'Copy current step to user specified position', self.copy_step), 920 ('DeleteStep', gtk.STOCK_DELETE, '_Delete current step', "", 921 'Delete current step', self.event_remove_clicked), 922 ('InsertNewBefore', gtk.STOCK_ADD, '_New step before current', "", 923 'Insert new step before current step', self.insert_before), 924 ('InsertNewAfter', gtk.STOCK_ADD, 'N_ew step after current', "", 925 'Insert new step after current step', self.insert_after), 926 ('InsertStepsBefore', gtk.STOCK_ADD, '_Steps before current...', 927 "", 'Insert steps (from file) before current step', 928 self.insert_steps_before), 929 ('InsertStepsAfter', gtk.STOCK_ADD, 'Steps _after current...', "", 930 'Insert steps (from file) after current step', 931 self.insert_steps_after), 932 ('CleanUp', gtk.STOCK_DELETE, '_Clean up data directory', "", 933 'Move unused PPM files to a backup directory', self.cleanup), 934 ('File', None, '_File'), 935 ('Edit', None, '_Edit'), 936 ('Insert', None, '_Insert'), 937 ('Tools', None, '_Tools') 938 ]) 939 940 def create_shortcut(name, callback, keyname): 941 # Create an action 942 action = gtk.Action(name, None, None, None) 943 # Connect a callback to the action 944 action.connect("activate", callback) 945 actiongroup.add_action_with_accel(action, keyname) 946 # Have the action use accelgroup 947 action.set_accel_group(accelgroup) 948 # Connect the accelerator to the action 949 action.connect_accelerator() 950 951 create_shortcut("Next", self.event_next_clicked, "Page_Down") 952 create_shortcut("Previous", self.event_prev_clicked, "Page_Up") 953 954 # Add the actiongroup to the uimanager 955 uimanager.insert_action_group(actiongroup, 0) 956 957 # Add a UI description 958 uimanager.add_ui_from_string(self.ui) 959 960 # Create a MenuBar 961 menubar = uimanager.get_widget('/MenuBar') 962 self.menu_vbox.pack_start(menubar, False) 963 964 # Remember the Edit menu bar for future reference 965 self.menu_edit = uimanager.get_widget('/MenuBar/Edit') 966 self.menu_edit.set_sensitive(False) 967 968 # Remember the Insert menu bar for future reference 969 self.menu_insert = uimanager.get_widget('/MenuBar/Insert') 970 self.menu_insert.set_sensitive(False) 971 972 # Remember the Tools menu bar for future reference 973 self.menu_tools = uimanager.get_widget('/MenuBar/Tools') 974 self.menu_tools.set_sensitive(False) 975 976 # Next/Previous HBox 977 hbox = gtk.HBox(spacing=10) 978 self.user_vbox.pack_start(hbox) 979 hbox.show() 980 981 self.button_first = gtk.Button(stock=gtk.STOCK_GOTO_FIRST) 982 self.button_first.connect("clicked", self.event_first_clicked) 983 hbox.pack_start(self.button_first) 984 self.button_first.show() 985 986 #self.button_prev = gtk.Button("<< Previous") 987 self.button_prev = gtk.Button(stock=gtk.STOCK_GO_BACK) 988 self.button_prev.connect("clicked", self.event_prev_clicked) 989 hbox.pack_start(self.button_prev) 990 self.button_prev.show() 991 992 self.label_step = gtk.Label("Step:") 993 hbox.pack_start(self.label_step, False) 994 self.label_step.show() 995 996 self.entry_step_num = gtk.Entry() 997 self.entry_step_num.connect("activate", self.event_entry_step_activated) 998 self.entry_step_num.set_width_chars(3) 999 hbox.pack_start(self.entry_step_num, False) 1000 self.entry_step_num.show() 1001 1002 #self.button_next = gtk.Button("Next >>") 1003 self.button_next = gtk.Button(stock=gtk.STOCK_GO_FORWARD) 1004 self.button_next.connect("clicked", self.event_next_clicked) 1005 hbox.pack_start(self.button_next) 1006 self.button_next.show() 1007 1008 self.button_last = gtk.Button(stock=gtk.STOCK_GOTO_LAST) 1009 self.button_last.connect("clicked", self.event_last_clicked) 1010 hbox.pack_start(self.button_last) 1011 self.button_last.show() 1012 1013 # Save HBox 1014 hbox = gtk.HBox(spacing=10) 1015 self.user_vbox.pack_start(hbox) 1016 hbox.show() 1017 1018 self.button_save = gtk.Button("_Save current step") 1019 self.button_save.connect("clicked", self.event_save_clicked) 1020 hbox.pack_start(self.button_save) 1021 self.button_save.show() 1022 1023 self.button_remove = gtk.Button("_Delete current step") 1024 self.button_remove.connect("clicked", self.event_remove_clicked) 1025 hbox.pack_start(self.button_remove) 1026 self.button_remove.show() 1027 1028 self.button_replace = gtk.Button("_Replace screendump") 1029 self.button_replace.connect("clicked", self.event_replace_clicked) 1030 hbox.pack_start(self.button_replace) 1031 self.button_replace.show() 1032 1033 # Disable unused widgets 1034 self.button_capture.set_sensitive(False) 1035 self.spin_latency.set_sensitive(False) 1036 self.spin_sensitivity.set_sensitive(False) 1037 1038 # Disable main vbox because no steps file is loaded 1039 self.main_vbox.set_sensitive(False) 1040 1041 # Set title 1042 self.window.set_title("Step Editor") 1043 1044 # Events 1045 1046 def delete_event(self, widget, event): 1047 # Make sure the step is saved (if the user wants it to be) 1048 self.verify_save() 1049 1050 def event_first_clicked(self, widget): 1051 if not self.steps: 1052 return 1053 # Make sure the step is saved (if the user wants it to be) 1054 self.verify_save() 1055 # Go to first step 1056 self.set_step(0) 1057 1058 def event_last_clicked(self, widget): 1059 if not self.steps: 1060 return 1061 # Make sure the step is saved (if the user wants it to be) 1062 self.verify_save() 1063 # Go to last step 1064 self.set_step(len(self.steps) - 1) 1065 1066 def event_prev_clicked(self, widget): 1067 if not self.steps: 1068 return 1069 # Make sure the step is saved (if the user wants it to be) 1070 self.verify_save() 1071 # Go to previous step 1072 index = self.current_step_index - 1 1073 if self.steps: 1074 index = index % len(self.steps) 1075 self.set_step(index) 1076 1077 def event_next_clicked(self, widget): 1078 if not self.steps: 1079 return 1080 # Make sure the step is saved (if the user wants it to be) 1081 self.verify_save() 1082 # Go to next step 1083 index = self.current_step_index + 1 1084 if self.steps: 1085 index = index % len(self.steps) 1086 self.set_step(index) 1087 1088 def event_entry_step_activated(self, widget): 1089 if not self.steps: 1090 return 1091 step_index = self.entry_step_num.get_text() 1092 if not step_index.isdigit(): 1093 return 1094 step_index = int(step_index) - 1 1095 if step_index == self.current_step_index: 1096 return 1097 self.verify_save() 1098 self.set_step(step_index) 1099 1100 def event_save_clicked(self, widget): 1101 if not self.steps: 1102 return 1103 self.save_step() 1104 1105 def event_remove_clicked(self, widget): 1106 if not self.steps: 1107 return 1108 if not self.question_yes_no("This will modify the steps file." 1109 " Are you sure?", "Remove step?"): 1110 return 1111 # Remove step 1112 del self.steps[self.current_step_index] 1113 # Write changes to file 1114 self.write_steps_file(self.steps_filename) 1115 # Move to previous step 1116 self.set_step(self.current_step_index) 1117 1118 def event_replace_clicked(self, widget): 1119 if not self.steps: 1120 return 1121 # Let the user choose a screendump file 1122 current_filename = os.path.join(self.steps_data_dir, 1123 self.entry_screendump.get_text()) 1124 filename = self.filedialog("Choose PPM image file", 1125 default_filename=current_filename) 1126 if not filename: 1127 return 1128 if not ppm_utils.image_verify_ppm_file(filename): 1129 self.message("Not a valid PPM image file.", "Error") 1130 return 1131 self.clear_image() 1132 self.clear_barrier_state() 1133 self.set_image_from_file(filename) 1134 self.update_screendump_id(self.steps_data_dir) 1135 1136 # Menu actions 1137 1138 def open_steps_file(self, action): 1139 # Make sure the step is saved (if the user wants it to be) 1140 self.verify_save() 1141 # Let the user choose a steps file 1142 current_filename = self.steps_filename 1143 filename = self.filedialog("Open steps file", 1144 default_filename=current_filename) 1145 if not filename: 1146 return 1147 self.set_steps_file(filename) 1148 1149 def quit(self, action): 1150 # Make sure the step is saved (if the user wants it to be) 1151 self.verify_save() 1152 # Quit 1153 gtk.main_quit() 1154 1155 def copy_step(self, action): 1156 if not self.steps: 1157 return 1158 self.verify_save() 1159 self.set_step(self.current_step_index) 1160 # Get the desired position 1161 step_index = self.inputdialog("Copy step to position:", 1162 "Copy step", 1163 str(self.current_step_index + 2)) 1164 if not step_index: 1165 return 1166 step_index = int(step_index) - 1 1167 # Get the lines of the current step 1168 step = self.steps[self.current_step_index] 1169 # Insert new step at position step_index 1170 self.steps.insert(step_index, step) 1171 # Go to new step 1172 self.set_step(step_index) 1173 # Write changes to disk 1174 self.write_steps_file(self.steps_filename) 1175 1176 def insert_before(self, action): 1177 if not self.steps_filename: 1178 return 1179 if not self.question_yes_no("This will modify the steps file." 1180 " Are you sure?", "Insert new step?"): 1181 return 1182 self.verify_save() 1183 step_index = self.current_step_index 1184 # Get the lines of a blank step 1185 self.clear_state() 1186 step = self.get_step_lines() 1187 # Insert new step at position step_index 1188 self.steps.insert(step_index, step) 1189 # Go to new step 1190 self.set_step(step_index) 1191 # Write changes to disk 1192 self.write_steps_file(self.steps_filename) 1193 1194 def insert_after(self, action): 1195 if not self.steps_filename: 1196 return 1197 if not self.question_yes_no("This will modify the steps file." 1198 " Are you sure?", "Insert new step?"): 1199 return 1200 self.verify_save() 1201 step_index = self.current_step_index + 1 1202 # Get the lines of a blank step 1203 self.clear_state() 1204 step = self.get_step_lines() 1205 # Insert new step at position step_index 1206 self.steps.insert(step_index, step) 1207 # Go to new step 1208 self.set_step(step_index) 1209 # Write changes to disk 1210 self.write_steps_file(self.steps_filename) 1211 1212 def insert_steps(self, filename, index): 1213 # Read the steps file 1214 (steps, header) = self.read_steps_file(filename) 1215 1216 data_dir = ppm_utils.get_data_dir(filename) 1217 for step in steps: 1218 self.set_state_from_step_lines(step, data_dir, warn=False) 1219 step = self.get_step_lines(self.steps_data_dir) 1220 1221 # Insert steps into self.steps 1222 self.steps[index:index] = steps 1223 # Write changes to disk 1224 self.write_steps_file(self.steps_filename) 1225 1226 def insert_steps_before(self, action): 1227 if not self.steps_filename: 1228 return 1229 # Let the user choose a steps file 1230 current_filename = self.steps_filename 1231 filename = self.filedialog("Choose steps file", 1232 default_filename=current_filename) 1233 if not filename: 1234 return 1235 self.verify_save() 1236 1237 step_index = self.current_step_index 1238 # Insert steps at position step_index 1239 self.insert_steps(filename, step_index) 1240 # Go to new steps 1241 self.set_step(step_index) 1242 1243 def insert_steps_after(self, action): 1244 if not self.steps_filename: 1245 return 1246 # Let the user choose a steps file 1247 current_filename = self.steps_filename 1248 filename = self.filedialog("Choose steps file", 1249 default_filename=current_filename) 1250 if not filename: 1251 return 1252 self.verify_save() 1253 1254 step_index = self.current_step_index + 1 1255 # Insert new steps at position step_index 1256 self.insert_steps(filename, step_index) 1257 # Go to new steps 1258 self.set_step(step_index) 1259 1260 def cleanup(self, action): 1261 if not self.steps_filename: 1262 return 1263 if not self.question_yes_no("All unused PPM files will be moved to a" 1264 " backup directory. Are you sure?", 1265 "Clean up data directory?"): 1266 return 1267 # Remember the current step index 1268 current_step_index = self.current_step_index 1269 # Get the backup dir 1270 backup_dir = os.path.join(self.steps_data_dir, "backup") 1271 # Create it if it doesn't exist 1272 if not os.path.exists(backup_dir): 1273 os.makedirs(backup_dir) 1274 # Move all files to the backup dir 1275 for filename in glob.glob(os.path.join(self.steps_data_dir, 1276 "*.[Pp][Pp][Mm]")): 1277 shutil.move(filename, backup_dir) 1278 # Get the used files back 1279 for step in self.steps: 1280 self.set_state_from_step_lines(step, backup_dir, warn=False) 1281 self.get_step_lines(self.steps_data_dir) 1282 # Remove the used files from the backup dir 1283 used_files = os.listdir(self.steps_data_dir) 1284 for filename in os.listdir(backup_dir): 1285 if filename in used_files: 1286 os.unlink(os.path.join(backup_dir, filename)) 1287 # Restore step index 1288 self.set_step(current_step_index) 1289 # Inform the user 1290 self.message("All unused PPM files may be found at %s." % 1291 os.path.abspath(backup_dir), 1292 "Clean up data directory") 1293 1294 # Methods 1295 1296 def read_steps_file(self, filename): 1297 steps = [] 1298 header = "" 1299 1300 file = open(filename, "r") 1301 for line in file.readlines(): 1302 words = line.split() 1303 if not words: 1304 continue 1305 if line.startswith("# ----"): 1306 continue 1307 if words[0] == "step": 1308 steps.append("") 1309 if steps: 1310 steps[-1] += line 1311 else: 1312 header += line 1313 file.close() 1314 1315 return (steps, header) 1316 1317 def set_steps_file(self, filename): 1318 try: 1319 (self.steps, self.header) = self.read_steps_file(filename) 1320 except (TypeError, IOError): 1321 self.message("Cannot read file %s." % filename, "Error") 1322 return 1323 1324 self.steps_filename = filename 1325 self.steps_data_dir = ppm_utils.get_data_dir(filename) 1326 # Go to step 0 1327 self.set_step(0) 1328 1329 def set_step(self, index): 1330 # Limit index to legal boundaries 1331 if index < 0: 1332 index = 0 1333 if index > len(self.steps) - 1: 1334 index = len(self.steps) - 1 1335 1336 # Enable the menus 1337 self.menu_edit.set_sensitive(True) 1338 self.menu_insert.set_sensitive(True) 1339 self.menu_tools.set_sensitive(True) 1340 1341 # If no steps exist... 1342 if self.steps == []: 1343 self.current_step_index = index 1344 self.current_step = None 1345 # Set window title 1346 self.window.set_title("Step Editor -- %s" % 1347 os.path.basename(self.steps_filename)) 1348 # Set step entry widget text 1349 self.entry_step_num.set_text("") 1350 # Clear the state of all widgets 1351 self.clear_state() 1352 # Disable the main vbox 1353 self.main_vbox.set_sensitive(False) 1354 return 1355 1356 self.current_step_index = index 1357 self.current_step = self.steps[index] 1358 # Set window title 1359 self.window.set_title("Step Editor -- %s -- step %d" % 1360 (os.path.basename(self.steps_filename), 1361 index + 1)) 1362 # Set step entry widget text 1363 self.entry_step_num.set_text(str(self.current_step_index + 1)) 1364 # Load the state from the step lines 1365 self.set_state_from_step_lines(self.current_step, self.steps_data_dir) 1366 # Enable the main vbox 1367 self.main_vbox.set_sensitive(True) 1368 # Make sure the step lines in self.current_step are identical to the 1369 # output of self.get_step_lines 1370 self.current_step = self.get_step_lines() 1371 1372 def verify_save(self): 1373 if not self.steps: 1374 return 1375 # See if the user changed anything 1376 if self.get_step_lines() != self.current_step: 1377 if self.question_yes_no("Step contents have been modified." 1378 " Save step?", "Save changes?"): 1379 self.save_step() 1380 1381 def save_step(self): 1382 lines = self.get_step_lines(self.steps_data_dir) 1383 if lines != None: 1384 self.steps[self.current_step_index] = lines 1385 self.current_step = lines 1386 self.write_steps_file(self.steps_filename) 1387 1388 def write_steps_file(self, filename): 1389 file = open(filename, "w") 1390 file.write(self.header) 1391 for step in self.steps: 1392 file.write("# " + "-" * 32 + "\n") 1393 file.write(step) 1394 file.close() 1395 1396 1397if __name__ == "__main__": 1398 se = StepEditor() 1399 if len(sys.argv) > 1: 1400 se.set_steps_file(sys.argv[1]) 1401 gtk.main() 1402