1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/ipc/chromium/src/base/event_recorder.cc Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,259 @@ 1.4 +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. 1.5 +// Use of this source code is governed by a BSD-style license that can be 1.6 +// found in the LICENSE file. 1.7 + 1.8 +#include "build/build_config.h" 1.9 + 1.10 +#include <windows.h> 1.11 +#include <mmsystem.h> 1.12 + 1.13 +#include "base/event_recorder.h" 1.14 +#include "base/file_util.h" 1.15 +#include "base/logging.h" 1.16 + 1.17 +// A note about time. 1.18 +// For perfect playback of events, you'd like a very accurate timer 1.19 +// so that events are played back at exactly the same time that 1.20 +// they were recorded. However, windows has a clock which is only 1.21 +// granular to ~15ms. We see more consistent event playback when 1.22 +// using a higher resolution timer. To do this, we use the 1.23 +// timeGetTime API instead of the default GetTickCount() API. 1.24 + 1.25 +namespace base { 1.26 + 1.27 +EventRecorder* EventRecorder::current_ = NULL; 1.28 + 1.29 +LRESULT CALLBACK StaticRecordWndProc(int nCode, WPARAM wParam, 1.30 + LPARAM lParam) { 1.31 + CHECK(EventRecorder::current()); 1.32 + return EventRecorder::current()->RecordWndProc(nCode, wParam, lParam); 1.33 +} 1.34 + 1.35 +LRESULT CALLBACK StaticPlaybackWndProc(int nCode, WPARAM wParam, 1.36 + LPARAM lParam) { 1.37 + CHECK(EventRecorder::current()); 1.38 + return EventRecorder::current()->PlaybackWndProc(nCode, wParam, lParam); 1.39 +} 1.40 + 1.41 +EventRecorder::~EventRecorder() { 1.42 + // Try to assert early if the caller deletes the recorder 1.43 + // while it is still in use. 1.44 + DCHECK(!journal_hook_); 1.45 + DCHECK(!is_recording_ && !is_playing_); 1.46 +} 1.47 + 1.48 +bool EventRecorder::StartRecording(const FilePath& filename) { 1.49 + if (journal_hook_ != NULL) 1.50 + return false; 1.51 + if (is_recording_ || is_playing_) 1.52 + return false; 1.53 + 1.54 + // Open the recording file. 1.55 + DCHECK(file_ == NULL); 1.56 + file_ = file_util::OpenFile(filename, "wb+"); 1.57 + if (!file_) { 1.58 + DLOG(ERROR) << "EventRecorder could not open log file"; 1.59 + return false; 1.60 + } 1.61 + 1.62 + // Set the faster clock, if possible. 1.63 + ::timeBeginPeriod(1); 1.64 + 1.65 + // Set the recording hook. JOURNALRECORD can only be used as a global hook. 1.66 + journal_hook_ = ::SetWindowsHookEx(WH_JOURNALRECORD, StaticRecordWndProc, 1.67 + GetModuleHandle(NULL), 0); 1.68 + if (!journal_hook_) { 1.69 + DLOG(ERROR) << "EventRecorder Record Hook failed"; 1.70 + file_util::CloseFile(file_); 1.71 + return false; 1.72 + } 1.73 + 1.74 + is_recording_ = true; 1.75 + return true; 1.76 +} 1.77 + 1.78 +void EventRecorder::StopRecording() { 1.79 + if (is_recording_) { 1.80 + DCHECK(journal_hook_ != NULL); 1.81 + 1.82 + if (!::UnhookWindowsHookEx(journal_hook_)) { 1.83 + DLOG(ERROR) << "EventRecorder Unhook failed"; 1.84 + // Nothing else we can really do here. 1.85 + return; 1.86 + } 1.87 + 1.88 + ::timeEndPeriod(1); 1.89 + 1.90 + DCHECK(file_ != NULL); 1.91 + file_util::CloseFile(file_); 1.92 + file_ = NULL; 1.93 + 1.94 + journal_hook_ = NULL; 1.95 + is_recording_ = false; 1.96 + } 1.97 +} 1.98 + 1.99 +bool EventRecorder::StartPlayback(const FilePath& filename) { 1.100 + if (journal_hook_ != NULL) 1.101 + return false; 1.102 + if (is_recording_ || is_playing_) 1.103 + return false; 1.104 + 1.105 + // Open the recording file. 1.106 + DCHECK(file_ == NULL); 1.107 + file_ = file_util::OpenFile(filename, "rb"); 1.108 + if (!file_) { 1.109 + DLOG(ERROR) << "EventRecorder Playback could not open log file"; 1.110 + return false; 1.111 + } 1.112 + // Read the first event from the record. 1.113 + if (fread(&playback_msg_, sizeof(EVENTMSG), 1, file_) != 1) { 1.114 + DLOG(ERROR) << "EventRecorder Playback has no records!"; 1.115 + file_util::CloseFile(file_); 1.116 + return false; 1.117 + } 1.118 + 1.119 + // Set the faster clock, if possible. 1.120 + ::timeBeginPeriod(1); 1.121 + 1.122 + // Playback time is tricky. When playing back, we read a series of events, 1.123 + // each with timeouts. Simply subtracting the delta between two timers will 1.124 + // lead to fast playback (about 2x speed). The API has two events, one 1.125 + // which advances to the next event (HC_SKIP), and another that requests the 1.126 + // event (HC_GETNEXT). The same event will be requested multiple times. 1.127 + // Each time the event is requested, we must calculate the new delay. 1.128 + // To do this, we track the start time of the playback, and constantly 1.129 + // re-compute the delay. I mention this only because I saw two examples 1.130 + // of how to use this code on the net, and both were broken :-) 1.131 + playback_start_time_ = timeGetTime(); 1.132 + playback_first_msg_time_ = playback_msg_.time; 1.133 + 1.134 + // Set the hook. JOURNALPLAYBACK can only be used as a global hook. 1.135 + journal_hook_ = ::SetWindowsHookEx(WH_JOURNALPLAYBACK, StaticPlaybackWndProc, 1.136 + GetModuleHandle(NULL), 0); 1.137 + if (!journal_hook_) { 1.138 + DLOG(ERROR) << "EventRecorder Playback Hook failed"; 1.139 + return false; 1.140 + } 1.141 + 1.142 + is_playing_ = true; 1.143 + 1.144 + return true; 1.145 +} 1.146 + 1.147 +void EventRecorder::StopPlayback() { 1.148 + if (is_playing_) { 1.149 + DCHECK(journal_hook_ != NULL); 1.150 + 1.151 + if (!::UnhookWindowsHookEx(journal_hook_)) { 1.152 + DLOG(ERROR) << "EventRecorder Unhook failed"; 1.153 + // Nothing else we can really do here. 1.154 + } 1.155 + 1.156 + DCHECK(file_ != NULL); 1.157 + file_util::CloseFile(file_); 1.158 + file_ = NULL; 1.159 + 1.160 + ::timeEndPeriod(1); 1.161 + 1.162 + journal_hook_ = NULL; 1.163 + is_playing_ = false; 1.164 + } 1.165 +} 1.166 + 1.167 +// Windows callback hook for the recorder. 1.168 +LRESULT EventRecorder::RecordWndProc(int nCode, WPARAM wParam, LPARAM lParam) { 1.169 + static bool recording_enabled = true; 1.170 + EVENTMSG* msg_ptr = NULL; 1.171 + 1.172 + // The API says we have to do this. 1.173 + // See http://msdn2.microsoft.com/en-us/library/ms644983(VS.85).aspx 1.174 + if (nCode < 0) 1.175 + return ::CallNextHookEx(journal_hook_, nCode, wParam, lParam); 1.176 + 1.177 + // Check for the break key being pressed and stop recording. 1.178 + if (::GetKeyState(VK_CANCEL) & 0x8000) { 1.179 + StopRecording(); 1.180 + return ::CallNextHookEx(journal_hook_, nCode, wParam, lParam); 1.181 + } 1.182 + 1.183 + // The Journal Recorder must stop recording events when system modal 1.184 + // dialogs are present. (see msdn link above) 1.185 + switch(nCode) { 1.186 + case HC_SYSMODALON: 1.187 + recording_enabled = false; 1.188 + break; 1.189 + case HC_SYSMODALOFF: 1.190 + recording_enabled = true; 1.191 + break; 1.192 + } 1.193 + 1.194 + if (nCode == HC_ACTION && recording_enabled) { 1.195 + // Aha - we have an event to record. 1.196 + msg_ptr = reinterpret_cast<EVENTMSG*>(lParam); 1.197 + msg_ptr->time = timeGetTime(); 1.198 + fwrite(msg_ptr, sizeof(EVENTMSG), 1, file_); 1.199 + fflush(file_); 1.200 + } 1.201 + 1.202 + return CallNextHookEx(journal_hook_, nCode, wParam, lParam); 1.203 +} 1.204 + 1.205 +// Windows callback for the playback mode. 1.206 +LRESULT EventRecorder::PlaybackWndProc(int nCode, WPARAM wParam, 1.207 + LPARAM lParam) { 1.208 + static bool playback_enabled = true; 1.209 + int delay = 0; 1.210 + 1.211 + switch(nCode) { 1.212 + // A system modal dialog box is being displayed. Stop playing back 1.213 + // messages. 1.214 + case HC_SYSMODALON: 1.215 + playback_enabled = false; 1.216 + break; 1.217 + 1.218 + // A system modal dialog box is destroyed. We can start playing back 1.219 + // messages again. 1.220 + case HC_SYSMODALOFF: 1.221 + playback_enabled = true; 1.222 + break; 1.223 + 1.224 + // Prepare to copy the next mouse or keyboard event to playback. 1.225 + case HC_SKIP: 1.226 + if (!playback_enabled) 1.227 + break; 1.228 + 1.229 + // Read the next event from the record. 1.230 + if (fread(&playback_msg_, sizeof(EVENTMSG), 1, file_) != 1) 1.231 + this->StopPlayback(); 1.232 + break; 1.233 + 1.234 + // Copy the mouse or keyboard event to the EVENTMSG structure in lParam. 1.235 + case HC_GETNEXT: 1.236 + if (!playback_enabled) 1.237 + break; 1.238 + 1.239 + memcpy(reinterpret_cast<void*>(lParam), &playback_msg_, 1.240 + sizeof(playback_msg_)); 1.241 + 1.242 + // The return value is the amount of time (in milliseconds) to wait 1.243 + // before playing back the next message in the playback queue. Each 1.244 + // time this is called, we recalculate the delay relative to our current 1.245 + // wall clock. 1.246 + delay = (playback_msg_.time - playback_first_msg_time_) - 1.247 + (timeGetTime() - playback_start_time_); 1.248 + if (delay < 0) 1.249 + delay = 0; 1.250 + return delay; 1.251 + 1.252 + // An application has called PeekMessage with wRemoveMsg set to PM_NOREMOVE 1.253 + // indicating that the message is not removed from the message queue after 1.254 + // PeekMessage processing. 1.255 + case HC_NOREMOVE: 1.256 + break; 1.257 + } 1.258 + 1.259 + return CallNextHookEx(journal_hook_, nCode, wParam, lParam); 1.260 +} 1.261 + 1.262 +} // namespace base