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