1 // Copyright (c) 2006-2008 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.
5 #include "build/build_config.h"
10 #include "base/event_recorder.h"
11 #include "base/file_util.h"
12 #include "base/logging.h"
15 // For perfect playback of events, you'd like a very accurate timer
16 // so that events are played back at exactly the same time that
17 // they were recorded. However, windows has a clock which is only
18 // granular to ~15ms. We see more consistent event playback when
19 // using a higher resolution timer. To do this, we use the
20 // timeGetTime API instead of the default GetTickCount() API.
24 EventRecorder
* EventRecorder::current_
= NULL
;
26 LRESULT CALLBACK
StaticRecordWndProc(int nCode
, WPARAM wParam
,
28 CHECK(EventRecorder::current());
29 return EventRecorder::current()->RecordWndProc(nCode
, wParam
, lParam
);
32 LRESULT CALLBACK
StaticPlaybackWndProc(int nCode
, WPARAM wParam
,
34 CHECK(EventRecorder::current());
35 return EventRecorder::current()->PlaybackWndProc(nCode
, wParam
, lParam
);
38 EventRecorder::~EventRecorder() {
39 // Try to assert early if the caller deletes the recorder
40 // while it is still in use.
41 DCHECK(!journal_hook_
);
42 DCHECK(!is_recording_
&& !is_playing_
);
45 bool EventRecorder::StartRecording(const FilePath
& filename
) {
46 if (journal_hook_
!= NULL
)
48 if (is_recording_
|| is_playing_
)
51 // Open the recording file.
52 DCHECK(file_
== NULL
);
53 file_
= file_util::OpenFile(filename
, "wb+");
55 DLOG(ERROR
) << "EventRecorder could not open log file";
59 // Set the faster clock, if possible.
62 // Set the recording hook. JOURNALRECORD can only be used as a global hook.
63 journal_hook_
= ::SetWindowsHookEx(WH_JOURNALRECORD
, StaticRecordWndProc
,
64 GetModuleHandle(NULL
), 0);
66 DLOG(ERROR
) << "EventRecorder Record Hook failed";
67 file_util::CloseFile(file_
);
75 void EventRecorder::StopRecording() {
77 DCHECK(journal_hook_
!= NULL
);
79 if (!::UnhookWindowsHookEx(journal_hook_
)) {
80 DLOG(ERROR
) << "EventRecorder Unhook failed";
81 // Nothing else we can really do here.
87 DCHECK(file_
!= NULL
);
88 file_util::CloseFile(file_
);
92 is_recording_
= false;
96 bool EventRecorder::StartPlayback(const FilePath
& filename
) {
97 if (journal_hook_
!= NULL
)
99 if (is_recording_
|| is_playing_
)
102 // Open the recording file.
103 DCHECK(file_
== NULL
);
104 file_
= file_util::OpenFile(filename
, "rb");
106 DLOG(ERROR
) << "EventRecorder Playback could not open log file";
109 // Read the first event from the record.
110 if (fread(&playback_msg_
, sizeof(EVENTMSG
), 1, file_
) != 1) {
111 DLOG(ERROR
) << "EventRecorder Playback has no records!";
112 file_util::CloseFile(file_
);
116 // Set the faster clock, if possible.
117 ::timeBeginPeriod(1);
119 // Playback time is tricky. When playing back, we read a series of events,
120 // each with timeouts. Simply subtracting the delta between two timers will
121 // lead to fast playback (about 2x speed). The API has two events, one
122 // which advances to the next event (HC_SKIP), and another that requests the
123 // event (HC_GETNEXT). The same event will be requested multiple times.
124 // Each time the event is requested, we must calculate the new delay.
125 // To do this, we track the start time of the playback, and constantly
126 // re-compute the delay. I mention this only because I saw two examples
127 // of how to use this code on the net, and both were broken :-)
128 playback_start_time_
= timeGetTime();
129 playback_first_msg_time_
= playback_msg_
.time
;
131 // Set the hook. JOURNALPLAYBACK can only be used as a global hook.
132 journal_hook_
= ::SetWindowsHookEx(WH_JOURNALPLAYBACK
, StaticPlaybackWndProc
,
133 GetModuleHandle(NULL
), 0);
134 if (!journal_hook_
) {
135 DLOG(ERROR
) << "EventRecorder Playback Hook failed";
144 void EventRecorder::StopPlayback() {
146 DCHECK(journal_hook_
!= NULL
);
148 if (!::UnhookWindowsHookEx(journal_hook_
)) {
149 DLOG(ERROR
) << "EventRecorder Unhook failed";
150 // Nothing else we can really do here.
153 DCHECK(file_
!= NULL
);
154 file_util::CloseFile(file_
);
159 journal_hook_
= NULL
;
164 // Windows callback hook for the recorder.
165 LRESULT
EventRecorder::RecordWndProc(int nCode
, WPARAM wParam
, LPARAM lParam
) {
166 static bool recording_enabled
= true;
167 EVENTMSG
* msg_ptr
= NULL
;
169 // The API says we have to do this.
170 // See http://msdn2.microsoft.com/en-us/library/ms644983(VS.85).aspx
172 return ::CallNextHookEx(journal_hook_
, nCode
, wParam
, lParam
);
174 // Check for the break key being pressed and stop recording.
175 if (::GetKeyState(VK_CANCEL
) & 0x8000) {
177 return ::CallNextHookEx(journal_hook_
, nCode
, wParam
, lParam
);
180 // The Journal Recorder must stop recording events when system modal
181 // dialogs are present. (see msdn link above)
184 recording_enabled
= false;
187 recording_enabled
= true;
191 if (nCode
== HC_ACTION
&& recording_enabled
) {
192 // Aha - we have an event to record.
193 msg_ptr
= reinterpret_cast<EVENTMSG
*>(lParam
);
194 msg_ptr
->time
= timeGetTime();
195 fwrite(msg_ptr
, sizeof(EVENTMSG
), 1, file_
);
199 return CallNextHookEx(journal_hook_
, nCode
, wParam
, lParam
);
202 // Windows callback for the playback mode.
203 LRESULT
EventRecorder::PlaybackWndProc(int nCode
, WPARAM wParam
,
205 static bool playback_enabled
= true;
209 // A system modal dialog box is being displayed. Stop playing back
212 playback_enabled
= false;
215 // A system modal dialog box is destroyed. We can start playing back
218 playback_enabled
= true;
221 // Prepare to copy the next mouse or keyboard event to playback.
223 if (!playback_enabled
)
226 // Read the next event from the record.
227 if (fread(&playback_msg_
, sizeof(EVENTMSG
), 1, file_
) != 1)
228 this->StopPlayback();
231 // Copy the mouse or keyboard event to the EVENTMSG structure in lParam.
233 if (!playback_enabled
)
236 memcpy(reinterpret_cast<void*>(lParam
), &playback_msg_
,
237 sizeof(playback_msg_
));
239 // The return value is the amount of time (in milliseconds) to wait
240 // before playing back the next message in the playback queue. Each
241 // time this is called, we recalculate the delay relative to our current
243 delay
= (playback_msg_
.time
- playback_first_msg_time_
) -
244 (timeGetTime() - playback_start_time_
);
249 // An application has called PeekMessage with wRemoveMsg set to PM_NOREMOVE
250 // indicating that the message is not removed from the message queue after
251 // PeekMessage processing.
256 return CallNextHookEx(journal_hook_
, nCode
, wParam
, lParam
);