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