1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
9 #include "base/event_recorder.h"
10 #include "base/file_util.h"
11 #include "base/logging.h"
14 // For perfect playback of events, you'd like a very accurate timer
15 // so that events are played back at exactly the same time that
16 // they were recorded. However, windows has a clock which is only
17 // granular to ~15ms. We see more consistent event playback when
18 // using a higher resolution timer. To do this, we use the
19 // timeGetTime API instead of the default GetTickCount() API.
23 EventRecorder* EventRecorder::current_ = NULL;
25 LRESULT CALLBACK StaticRecordWndProc(int nCode, WPARAM wParam,
27 DCHECK(EventRecorder::current());
28 return EventRecorder::current()->RecordWndProc(nCode, wParam, lParam);
31 LRESULT CALLBACK StaticPlaybackWndProc(int nCode, WPARAM wParam,
33 DCHECK(EventRecorder::current());
34 return EventRecorder::current()->PlaybackWndProc(nCode, wParam, lParam);
37 EventRecorder::~EventRecorder() {
38 // Try to assert early if the caller deletes the recorder
39 // while it is still in use.
40 DCHECK(!journal_hook_);
41 DCHECK(!is_recording_ && !is_playing_);
44 bool EventRecorder::StartRecording(const FilePath& filename) {
45 if (journal_hook_ != NULL)
47 if (is_recording_ || is_playing_)
50 // Open the recording file.
52 file_ = file_util::OpenFile(filename, "wb+");
54 DLOG(ERROR) << "EventRecorder could not open log file";
58 // Set the faster clock, if possible.
61 // Set the recording hook. JOURNALRECORD can only be used as a global hook.
62 journal_hook_ = ::SetWindowsHookEx(WH_JOURNALRECORD, StaticRecordWndProc,
63 GetModuleHandle(NULL), 0);
65 DLOG(ERROR) << "EventRecorder Record Hook failed";
66 file_util::CloseFile(file_);
74 void EventRecorder::StopRecording() {
76 DCHECK(journal_hook_ != NULL);
78 if (!::UnhookWindowsHookEx(journal_hook_)) {
79 DLOG(ERROR) << "EventRecorder Unhook failed";
80 // Nothing else we can really do here.
86 DCHECK(file_ != NULL);
87 file_util::CloseFile(file_);
91 is_recording_ = false;
95 bool EventRecorder::StartPlayback(const FilePath& filename) {
96 if (journal_hook_ != NULL)
98 if (is_recording_ || is_playing_)
101 // Open the recording file.
103 file_ = file_util::OpenFile(filename, "rb");
105 DLOG(ERROR) << "EventRecorder Playback could not open log file";
108 // Read the first event from the record.
109 if (fread(&playback_msg_, sizeof(EVENTMSG), 1, file_) != 1) {
110 DLOG(ERROR) << "EventRecorder Playback has no records!";
111 file_util::CloseFile(file_);
115 // Set the faster clock, if possible.
116 ::timeBeginPeriod(1);
118 // Playback time is tricky. When playing back, we read a series of events,
119 // each with timeouts. Simply subtracting the delta between two timers will
120 // lead to fast playback (about 2x speed). The API has two events, one
121 // which advances to the next event (HC_SKIP), and another that requests the
122 // event (HC_GETNEXT). The same event will be requested multiple times.
123 // Each time the event is requested, we must calculate the new delay.
124 // To do this, we track the start time of the playback, and constantly
125 // re-compute the delay. I mention this only because I saw two examples
126 // of how to use this code on the net, and both were broken :-)
127 playback_start_time_ = timeGetTime();
128 playback_first_msg_time_ = playback_msg_.time;
130 // Set the hook. JOURNALPLAYBACK can only be used as a global hook.
131 journal_hook_ = ::SetWindowsHookEx(WH_JOURNALPLAYBACK, StaticPlaybackWndProc,
132 GetModuleHandle(NULL), 0);
133 if (!journal_hook_) {
134 DLOG(ERROR) << "EventRecorder Playback Hook failed";
143 void EventRecorder::StopPlayback() {
145 DCHECK(journal_hook_ != NULL);
147 if (!::UnhookWindowsHookEx(journal_hook_)) {
148 DLOG(ERROR) << "EventRecorder Unhook failed";
149 // Nothing else we can really do here.
152 DCHECK(file_ != NULL);
153 file_util::CloseFile(file_);
158 journal_hook_ = NULL;
163 // Windows callback hook for the recorder.
164 LRESULT EventRecorder::RecordWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
165 static bool recording_enabled = true;
166 EVENTMSG* msg_ptr = NULL;
168 // The API says we have to do this.
169 // See http://msdn2.microsoft.com/en-us/library/ms644983(VS.85).aspx
171 return ::CallNextHookEx(journal_hook_, nCode, wParam, lParam);
173 // Check for the break key being pressed and stop recording.
174 if (::GetKeyState(VK_CANCEL) & 0x8000) {
176 return ::CallNextHookEx(journal_hook_, nCode, wParam, lParam);
179 // The Journal Recorder must stop recording events when system modal
180 // dialogs are present. (see msdn link above)
183 recording_enabled = false;
186 recording_enabled = true;
190 if (nCode == HC_ACTION && recording_enabled) {
191 // Aha - we have an event to record.
192 msg_ptr = reinterpret_cast<EVENTMSG*>(lParam);
193 msg_ptr->time = timeGetTime();
194 fwrite(msg_ptr, sizeof(EVENTMSG), 1, file_);
198 return CallNextHookEx(journal_hook_, nCode, wParam, lParam);
201 // Windows callback for the playback mode.
202 LRESULT EventRecorder::PlaybackWndProc(int nCode, WPARAM wParam,
204 static bool playback_enabled = true;
208 // A system modal dialog box is being displayed. Stop playing back
211 playback_enabled = false;
214 // A system modal dialog box is destroyed. We can start playing back
217 playback_enabled = true;
220 // Prepare to copy the next mouse or keyboard event to playback.
222 if (!playback_enabled)
225 // Read the next event from the record.
226 if (fread(&playback_msg_, sizeof(EVENTMSG), 1, file_) != 1)
227 this->StopPlayback();
230 // Copy the mouse or keyboard event to the EVENTMSG structure in lParam.
232 if (!playback_enabled)
235 memcpy(reinterpret_cast<void*>(lParam), &playback_msg_,
236 sizeof(playback_msg_));
238 // The return value is the amount of time (in milliseconds) to wait
239 // before playing back the next message in the playback queue. Each
240 // time this is called, we recalculate the delay relative to our current
242 delay = (playback_msg_.time - playback_first_msg_time_) -
243 (timeGetTime() - playback_start_time_);
248 // An application has called PeekMessage with wRemoveMsg set to PM_NOREMOVE
249 // indicating that the message is not removed from the message queue after
250 // PeekMessage processing.
255 return CallNextHookEx(journal_hook_, nCode, wParam, lParam);