- add sources.
[platform/framework/web/crosswalk.git] / src / base / event_recorder_win.cc
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.
4
5 #include <stddef.h>
6 #include <windows.h>
7 #include <mmsystem.h>
8
9 #include "base/event_recorder.h"
10 #include "base/file_util.h"
11 #include "base/logging.h"
12
13 // A note about time.
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.
20
21 namespace base {
22
23 EventRecorder* EventRecorder::current_ = NULL;
24
25 LRESULT CALLBACK StaticRecordWndProc(int nCode, WPARAM wParam,
26                                      LPARAM lParam) {
27   DCHECK(EventRecorder::current());
28   return EventRecorder::current()->RecordWndProc(nCode, wParam, lParam);
29 }
30
31 LRESULT CALLBACK StaticPlaybackWndProc(int nCode, WPARAM wParam,
32                                        LPARAM lParam) {
33   DCHECK(EventRecorder::current());
34   return EventRecorder::current()->PlaybackWndProc(nCode, wParam, lParam);
35 }
36
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_);
42 }
43
44 bool EventRecorder::StartRecording(const FilePath& filename) {
45   if (journal_hook_ != NULL)
46     return false;
47   if (is_recording_ || is_playing_)
48     return false;
49
50   // Open the recording file.
51   DCHECK(!file_);
52   file_ = file_util::OpenFile(filename, "wb+");
53   if (!file_) {
54     DLOG(ERROR) << "EventRecorder could not open log file";
55     return false;
56   }
57
58   // Set the faster clock, if possible.
59   ::timeBeginPeriod(1);
60
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);
64   if (!journal_hook_) {
65     DLOG(ERROR) << "EventRecorder Record Hook failed";
66     file_util::CloseFile(file_);
67     return false;
68   }
69
70   is_recording_ = true;
71   return true;
72 }
73
74 void EventRecorder::StopRecording() {
75   if (is_recording_) {
76     DCHECK(journal_hook_ != NULL);
77
78     if (!::UnhookWindowsHookEx(journal_hook_)) {
79       DLOG(ERROR) << "EventRecorder Unhook failed";
80       // Nothing else we can really do here.
81       return;
82     }
83
84     ::timeEndPeriod(1);
85
86     DCHECK(file_ != NULL);
87     file_util::CloseFile(file_);
88     file_ = NULL;
89
90     journal_hook_ = NULL;
91     is_recording_ = false;
92   }
93 }
94
95 bool EventRecorder::StartPlayback(const FilePath& filename) {
96   if (journal_hook_ != NULL)
97     return false;
98   if (is_recording_ || is_playing_)
99     return false;
100
101   // Open the recording file.
102   DCHECK(!file_);
103   file_ = file_util::OpenFile(filename, "rb");
104   if (!file_) {
105     DLOG(ERROR) << "EventRecorder Playback could not open log file";
106     return false;
107   }
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_);
112     return false;
113   }
114
115   // Set the faster clock, if possible.
116   ::timeBeginPeriod(1);
117
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;
129
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";
135     return false;
136   }
137
138   is_playing_ = true;
139
140   return true;
141 }
142
143 void EventRecorder::StopPlayback() {
144   if (is_playing_) {
145     DCHECK(journal_hook_ != NULL);
146
147     if (!::UnhookWindowsHookEx(journal_hook_)) {
148       DLOG(ERROR) << "EventRecorder Unhook failed";
149       // Nothing else we can really do here.
150     }
151
152     DCHECK(file_ != NULL);
153     file_util::CloseFile(file_);
154     file_ = NULL;
155
156     ::timeEndPeriod(1);
157
158     journal_hook_ = NULL;
159     is_playing_ = false;
160   }
161 }
162
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;
167
168   // The API says we have to do this.
169   // See http://msdn2.microsoft.com/en-us/library/ms644983(VS.85).aspx
170   if (nCode < 0)
171     return ::CallNextHookEx(journal_hook_, nCode, wParam, lParam);
172
173   // Check for the break key being pressed and stop recording.
174   if (::GetKeyState(VK_CANCEL) & 0x8000) {
175     StopRecording();
176     return ::CallNextHookEx(journal_hook_, nCode, wParam, lParam);
177   }
178
179   // The Journal Recorder must stop recording events when system modal
180   // dialogs are present. (see msdn link above)
181   switch (nCode) {
182     case HC_SYSMODALON:
183       recording_enabled = false;
184       break;
185     case HC_SYSMODALOFF:
186       recording_enabled = true;
187       break;
188   }
189
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_);
195     fflush(file_);
196   }
197
198   return CallNextHookEx(journal_hook_, nCode, wParam, lParam);
199 }
200
201 // Windows callback for the playback mode.
202 LRESULT EventRecorder::PlaybackWndProc(int nCode, WPARAM wParam,
203                                        LPARAM lParam) {
204   static bool playback_enabled = true;
205   int delay = 0;
206
207   switch (nCode) {
208     // A system modal dialog box is being displayed.  Stop playing back
209     // messages.
210     case HC_SYSMODALON:
211       playback_enabled = false;
212       break;
213
214     // A system modal dialog box is destroyed.  We can start playing back
215     // messages again.
216     case HC_SYSMODALOFF:
217       playback_enabled = true;
218       break;
219
220     // Prepare to copy the next mouse or keyboard event to playback.
221     case HC_SKIP:
222       if (!playback_enabled)
223         break;
224
225       // Read the next event from the record.
226       if (fread(&playback_msg_, sizeof(EVENTMSG), 1, file_) != 1)
227         this->StopPlayback();
228       break;
229
230     // Copy the mouse or keyboard event to the EVENTMSG structure in lParam.
231     case HC_GETNEXT:
232       if (!playback_enabled)
233         break;
234
235       memcpy(reinterpret_cast<void*>(lParam), &playback_msg_,
236              sizeof(playback_msg_));
237
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
241       // wall clock.
242       delay = (playback_msg_.time - playback_first_msg_time_) -
243               (timeGetTime() - playback_start_time_);
244       if (delay < 0)
245         delay = 0;
246       return delay;
247
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.
251     case HC_NOREMOVE:
252       break;
253   }
254
255   return CallNextHookEx(journal_hook_, nCode, wParam, lParam);
256 }
257
258 }  // namespace base