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, gobject, time, os, commands, logging 11import common 12from autotest_lib.client.common_lib import error 13from autotest_lib.client.virt import virt_utils, ppm_utils, virt_step_editor 14from autotest_lib.client.virt import kvm_monitor 15pygtk.require('2.0') 16 17 18class StepMaker(virt_step_editor.StepMakerWindow): 19 """ 20 Application used to create a step file. It will grab your input to the 21 virtual machine and record it on a 'step file', that can be played 22 making it possible to do unattended installs. 23 """ 24 # Constructor 25 def __init__(self, vm, steps_filename, tempdir, params): 26 virt_step_editor.StepMakerWindow.__init__(self) 27 28 self.vm = vm 29 self.steps_filename = steps_filename 30 self.steps_data_dir = ppm_utils.get_data_dir(steps_filename) 31 self.tempdir = tempdir 32 self.screendump_filename = os.path.join(tempdir, "scrdump.ppm") 33 self.params = params 34 35 if not os.path.exists(self.steps_data_dir): 36 os.makedirs(self.steps_data_dir) 37 38 self.steps_file = open(self.steps_filename, "w") 39 self.vars_file = open(os.path.join(self.steps_data_dir, "vars"), "w") 40 41 self.step_num = 1 42 self.run_time = 0 43 self.update_delay = 1000 44 self.prev_x = 0 45 self.prev_y = 0 46 self.vars = {} 47 self.timer_id = None 48 49 self.time_when_done_clicked = time.time() 50 self.time_when_actions_completed = time.time() 51 52 self.steps_file.write("# Generated by Step Maker\n") 53 self.steps_file.write("# Generated on %s\n" % time.asctime()) 54 self.steps_file.write("# uname -a: %s\n" % 55 commands.getoutput("uname -a")) 56 self.steps_file.flush() 57 58 self.vars_file.write("# This file lists the vars used during recording" 59 " with Step Maker\n") 60 self.vars_file.flush() 61 62 # Done/Break HBox 63 hbox = gtk.HBox(spacing=10) 64 self.user_vbox.pack_start(hbox) 65 hbox.show() 66 67 self.button_break = gtk.Button("Break") 68 self.button_break.connect("clicked", self.event_break_clicked) 69 hbox.pack_start(self.button_break) 70 self.button_break.show() 71 72 self.button_done = gtk.Button("Done") 73 self.button_done.connect("clicked", self.event_done_clicked) 74 hbox.pack_start(self.button_done) 75 self.button_done.show() 76 77 # Set window title 78 self.window.set_title("Step Maker") 79 80 # Connect "capture" button 81 self.button_capture.connect("clicked", self.event_capture_clicked) 82 83 # Switch to run mode 84 self.switch_to_run_mode() 85 86 87 def destroy(self, widget): 88 self.vm.monitor.cmd("cont") 89 self.steps_file.close() 90 self.vars_file.close() 91 virt_step_editor.StepMakerWindow.destroy(self, widget) 92 93 94 # Utilities 95 def redirect_timer(self, delay=0, func=None): 96 if self.timer_id != None: 97 gobject.source_remove(self.timer_id) 98 self.timer_id = None 99 if func != None: 100 self.timer_id = gobject.timeout_add(delay, func, 101 priority=gobject.PRIORITY_LOW) 102 103 104 def switch_to_run_mode(self): 105 # Set all widgets to their default states 106 self.clear_state(clear_screendump=False) 107 # Enable/disable some widgets 108 self.button_break.set_sensitive(True) 109 self.button_done.set_sensitive(False) 110 self.data_vbox.set_sensitive(False) 111 # Give focus to the Break button 112 self.button_break.grab_focus() 113 # Start the screendump timer 114 self.redirect_timer(100, self.update) 115 # Resume the VM 116 self.vm.monitor.cmd("cont") 117 118 119 def switch_to_step_mode(self): 120 # Set all widgets to their default states 121 self.clear_state(clear_screendump=False) 122 # Enable/disable some widgets 123 self.button_break.set_sensitive(False) 124 self.button_done.set_sensitive(True) 125 self.data_vbox.set_sensitive(True) 126 # Give focus to the keystrokes entry widget 127 self.entry_keys.grab_focus() 128 # Start the screendump timer 129 self.redirect_timer() 130 # Stop the VM 131 self.vm.monitor.cmd("stop") 132 133 134 # Events in step mode 135 def update(self): 136 self.redirect_timer() 137 138 if os.path.exists(self.screendump_filename): 139 os.unlink(self.screendump_filename) 140 141 try: 142 self.vm.monitor.screendump(self.screendump_filename, debug=False) 143 except kvm_monitor.MonitorError, e: 144 logging.warning(e) 145 else: 146 self.set_image_from_file(self.screendump_filename) 147 148 self.redirect_timer(self.update_delay, self.update) 149 return True 150 151 152 def event_break_clicked(self, widget): 153 if not self.vm.is_alive(): 154 self.message("The VM doesn't seem to be alive.", "Error") 155 return 156 # Switch to step mode 157 self.switch_to_step_mode() 158 # Compute time elapsed since last click on "Done" and add it 159 # to self.run_time 160 self.run_time += time.time() - self.time_when_done_clicked 161 # Set recording time widget 162 self.entry_time.set_text("%.2f" % self.run_time) 163 # Update screendump ID 164 self.update_screendump_id(self.steps_data_dir) 165 # By default, check the barrier checkbox 166 self.check_barrier.set_active(True) 167 # Set default sleep and barrier timeout durations 168 time_delta = time.time() - self.time_when_actions_completed 169 if time_delta < 1.0: time_delta = 1.0 170 self.spin_sleep.set_value(round(time_delta)) 171 self.spin_barrier_timeout.set_value(round(time_delta * 5)) 172 # Set window title 173 self.window.set_title("Step Maker -- step %d at time %.2f" % 174 (self.step_num, self.run_time)) 175 176 177 def event_done_clicked(self, widget): 178 # Get step lines and screendump 179 lines = self.get_step_lines(self.steps_data_dir) 180 if lines == None: 181 return 182 183 # Get var values from user and write them to vars file 184 vars = {} 185 for line in lines.splitlines(): 186 words = line.split() 187 if words and words[0] == "var": 188 varname = words[1] 189 if varname in self.vars.keys(): 190 val = self.vars[varname] 191 elif varname in vars.keys(): 192 val = vars[varname] 193 elif varname in self.params.keys(): 194 val = self.params[varname] 195 vars[varname] = val 196 else: 197 val = self.inputdialog("$%s =" % varname, "Variable") 198 if val == None: 199 return 200 vars[varname] = val 201 for varname in vars.keys(): 202 self.vars_file.write("%s=%s\n" % (varname, vars[varname])) 203 self.vars.update(vars) 204 205 # Write step lines to file 206 self.steps_file.write("# " + "-" * 32 + "\n") 207 self.steps_file.write(lines) 208 209 # Flush buffers of both files 210 self.steps_file.flush() 211 self.vars_file.flush() 212 213 # Remember the current time 214 self.time_when_done_clicked = time.time() 215 216 # Switch to run mode 217 self.switch_to_run_mode() 218 219 # Send commands to VM 220 for line in lines.splitlines(): 221 words = line.split() 222 if not words: 223 continue 224 elif words[0] == "key": 225 self.vm.send_key(words[1]) 226 elif words[0] == "var": 227 val = self.vars.get(words[1]) 228 if not val: 229 continue 230 self.vm.send_string(val) 231 elif words[0] == "mousemove": 232 self.vm.monitor.mouse_move(-8000, -8000) 233 time.sleep(0.5) 234 self.vm.monitor.mouse_move(words[1], words[2]) 235 time.sleep(0.5) 236 elif words[0] == "mouseclick": 237 self.vm.monitor.mouse_button(words[1]) 238 time.sleep(0.1) 239 self.vm.monitor.mouse_button(0) 240 241 # Remember the current time 242 self.time_when_actions_completed = time.time() 243 244 # Move on to next step 245 self.step_num += 1 246 247 def event_capture_clicked(self, widget): 248 self.message("Mouse actions disabled (for now).", "Sorry") 249 return 250 251 self.image_width_backup = self.image_width 252 self.image_height_backup = self.image_height 253 self.image_data_backup = self.image_data 254 255 gtk.gdk.pointer_grab(self.event_box.window, False, 256 gtk.gdk.BUTTON_PRESS_MASK | 257 gtk.gdk.BUTTON_RELEASE_MASK) 258 # Create empty cursor 259 pix = gtk.gdk.Pixmap(self.event_box.window, 1, 1, 1) 260 color = gtk.gdk.Color() 261 cursor = gtk.gdk.Cursor(pix, pix, color, color, 0, 0) 262 self.event_box.window.set_cursor(cursor) 263 gtk.gdk.display_get_default().warp_pointer(gtk.gdk.screen_get_default(), 264 self.prev_x, self.prev_y) 265 self.redirect_event_box_input( 266 self.event_capture_button_press, 267 self.event_capture_button_release, 268 self.event_capture_scroll) 269 self.redirect_timer(10, self.update_capture) 270 self.vm.monitor.cmd("cont") 271 272 # Events in mouse capture mode 273 274 def update_capture(self): 275 self.redirect_timer() 276 277 (screen, x, y, flags) = gtk.gdk.display_get_default().get_pointer() 278 self.mouse_click_coords[0] = int(x * self.spin_sensitivity.get_value()) 279 self.mouse_click_coords[1] = int(y * self.spin_sensitivity.get_value()) 280 281 delay = self.spin_latency.get_value() / 1000 282 if (x, y) != (self.prev_x, self.prev_y): 283 self.vm.monitor.mouse_move(-8000, -8000) 284 time.sleep(delay) 285 self.vm.monitor.mouse_move(self.mouse_click_coords[0], 286 self.mouse_click_coords[1]) 287 time.sleep(delay) 288 289 self.prev_x = x 290 self.prev_y = y 291 292 if os.path.exists(self.screendump_filename): 293 os.unlink(self.screendump_filename) 294 295 try: 296 self.vm.monitor.screendump(self.screendump_filename, debug=False) 297 except kvm_monitor.MonitorError, e: 298 logging.warning(e) 299 else: 300 self.set_image_from_file(self.screendump_filename) 301 302 self.redirect_timer(int(self.spin_latency.get_value()), 303 self.update_capture) 304 return True 305 306 def event_capture_button_press(self, widget,event): 307 pass 308 309 def event_capture_button_release(self, widget,event): 310 gtk.gdk.pointer_ungrab() 311 self.event_box.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.CROSSHAIR)) 312 self.redirect_event_box_input( 313 self.event_button_press, 314 self.event_button_release, 315 None, 316 None, 317 self.event_expose) 318 self.redirect_timer() 319 self.vm.monitor.cmd("stop") 320 self.mouse_click_captured = True 321 self.mouse_click_button = event.button 322 self.set_image(self.image_width_backup, self.image_height_backup, 323 self.image_data_backup) 324 self.check_mousemove.set_sensitive(True) 325 self.check_mouseclick.set_sensitive(True) 326 self.check_mousemove.set_active(True) 327 self.check_mouseclick.set_active(True) 328 self.update_mouse_click_info() 329 330 def event_capture_scroll(self, widget, event): 331 if event.direction == gtk.gdk.SCROLL_UP: 332 direction = 1 333 else: 334 direction = -1 335 self.spin_sensitivity.set_value(self.spin_sensitivity.get_value() + 336 direction) 337 pass 338 339 340def run_stepmaker(test, params, env): 341 vm = env.get_vm(params.get("main_vm")) 342 if not vm: 343 raise error.TestError("VM object not found in environment") 344 if not vm.is_alive(): 345 raise error.TestError("VM seems to be dead; Step Maker requires a" 346 " living VM") 347 348 steps_filename = params.get("steps") 349 if not steps_filename: 350 raise error.TestError("Steps filename not specified") 351 steps_filename = virt_utils.get_path(test.bindir, steps_filename) 352 if os.path.exists(steps_filename): 353 raise error.TestError("Steps file %s already exists" % steps_filename) 354 355 StepMaker(vm, steps_filename, test.debugdir, params) 356 gtk.main() 357