Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/cpp/iedriver/DocumentHost.cpp
2867 views
1
// Licensed to the Software Freedom Conservancy (SFC) under one
2
// or more contributor license agreements. See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership. The SFC licenses this file
5
// to you under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
17
#include "DocumentHost.h"
18
19
#include <IEPMapi.h>
20
#include <UIAutomation.h>
21
22
#include "errorcodes.h"
23
#include "logging.h"
24
25
#include "BrowserCookie.h"
26
#include "BrowserFactory.h"
27
#include "CookieManager.h"
28
#include "HookProcessor.h"
29
#include "messages.h"
30
#include "RegistryUtilities.h"
31
#include "Script.h"
32
#include "StringUtilities.h"
33
34
namespace webdriver {
35
36
DocumentHost::DocumentHost(HWND hwnd, HWND executor_handle) {
37
LOG(TRACE) << "Entering DocumentHost::DocumentHost";
38
39
// NOTE: COM should be initialized on this thread, so we
40
// could use CoCreateGuid() and StringFromGUID2() instead.
41
UUID guid;
42
RPC_WSTR guid_string = NULL;
43
RPC_STATUS status = ::UuidCreate(&guid);
44
if (status != RPC_S_OK) {
45
// If we encounter an error, not bloody much we can do about it.
46
// Just log it and continue.
47
LOG(WARN) << "UuidCreate returned a status other then RPC_S_OK: " << status;
48
}
49
status = ::UuidToString(&guid, &guid_string);
50
if (status != RPC_S_OK) {
51
// If we encounter an error, not bloody much we can do about it.
52
// Just log it and continue.
53
LOG(WARN) << "UuidToString returned a status other then RPC_S_OK: " << status;
54
}
55
56
// RPC_WSTR is currently typedef'd in RpcDce.h (pulled in by rpc.h)
57
// as unsigned short*. It needs to be typedef'd as wchar_t*
58
wchar_t* cast_guid_string = reinterpret_cast<wchar_t*>(guid_string);
59
this->browser_id_ = StringUtilities::ToString(cast_guid_string);
60
61
::RpcStringFree(&guid_string);
62
this->window_handle_ = hwnd;
63
this->executor_handle_ = executor_handle;
64
this->script_executor_handle_ = NULL;
65
this->is_closing_ = false;
66
this->wait_required_ = false;
67
this->is_awaiting_new_process_ = false;
68
this->focused_frame_window_ = NULL;
69
this->cookie_manager_ = new CookieManager();
70
if (this->window_handle_ != NULL) {
71
this->cookie_manager_->Initialize(this->window_handle_);
72
}
73
}
74
75
DocumentHost::~DocumentHost(void) {
76
delete this->cookie_manager_;
77
}
78
79
std::string DocumentHost::GetCurrentUrl() {
80
LOG(TRACE) << "Entering DocumentHost::GetCurrentUrl";
81
82
CComPtr<IHTMLDocument2> doc;
83
this->GetDocument(&doc);
84
if (!doc) {
85
LOG(WARN) << "Unable to get document object, DocumentHost::GetDocument returned NULL. "
86
<< "Attempting to get URL from IWebBrowser2 object";
87
return this->GetBrowserUrl();
88
}
89
90
CComBSTR url;
91
HRESULT hr = doc->get_URL(&url);
92
if (FAILED(hr)) {
93
LOGHR(WARN, hr) << "Unable to get current URL, call to IHTMLDocument2::get_URL failed";
94
return "";
95
}
96
97
std::wstring converted_url(url, ::SysStringLen(url));
98
std::string current_url = StringUtilities::ToString(converted_url);
99
return current_url;
100
}
101
102
std::string DocumentHost::GetPageSource() {
103
LOG(TRACE) << "Entering DocumentHost::GetPageSource";
104
105
CComPtr<IHTMLDocument2> doc;
106
this->GetDocument(&doc);
107
if (!doc) {
108
LOG(WARN) << "Unable to get document object, DocumentHost::GetDocument did not return a valid IHTMLDocument2 pointer";
109
return "";
110
}
111
112
CComPtr<IHTMLDocument3> doc3;
113
HRESULT hr = doc->QueryInterface<IHTMLDocument3>(&doc3);
114
if (FAILED(hr) || !doc3) {
115
LOG(WARN) << "Unable to get document object, QueryInterface to IHTMLDocument3 failed";
116
return "";
117
}
118
119
CComPtr<IHTMLElement> document_element;
120
hr = doc3->get_documentElement(&document_element);
121
if (FAILED(hr) || !document_element) {
122
LOGHR(WARN, hr) << "Unable to get document element from page, call to IHTMLDocument3::get_documentElement failed";
123
return "";
124
}
125
126
CComBSTR html;
127
hr = document_element->get_outerHTML(&html);
128
if (FAILED(hr)) {
129
LOGHR(WARN, hr) << "Have document element but cannot read source, call to IHTMLElement::get_outerHTML failed";
130
return "";
131
}
132
133
std::wstring converted_html = html;
134
std::string page_source = StringUtilities::ToString(converted_html);
135
return page_source;
136
}
137
138
void DocumentHost::Restore(void) {
139
if (this->IsFullScreen()) {
140
this->SetFullScreen(false);
141
}
142
HWND window_handle = this->GetTopLevelWindowHandle();
143
if (::IsZoomed(window_handle) || ::IsIconic(window_handle)) {
144
::ShowWindow(window_handle, SW_RESTORE);
145
}
146
}
147
148
int DocumentHost::SetFocusedFrameByElement(IHTMLElement* frame_element) {
149
LOG(TRACE) << "Entering DocumentHost::SetFocusedFrameByElement";
150
151
HRESULT hr = S_OK;
152
if (!frame_element) {
153
this->focused_frame_window_ = NULL;
154
return WD_SUCCESS;
155
}
156
157
CComPtr<IHTMLWindow2> interim_result;
158
CComPtr<IHTMLObjectElement4> object_element;
159
hr = frame_element->QueryInterface<IHTMLObjectElement4>(&object_element);
160
if (SUCCEEDED(hr) && object_element) {
161
CComPtr<IDispatch> object_disp;
162
object_element->get_contentDocument(&object_disp);
163
if (!object_disp) {
164
LOG(WARN) << "Cannot get IDispatch interface from IHTMLObjectElement4 element";
165
return ENOSUCHFRAME;
166
}
167
168
CComPtr<IHTMLDocument2> object_doc;
169
object_disp->QueryInterface<IHTMLDocument2>(&object_doc);
170
if (!object_doc) {
171
LOG(WARN) << "Cannot get IHTMLDocument2 document from IDispatch reference";
172
return ENOSUCHFRAME;
173
}
174
175
hr = object_doc->get_parentWindow(&interim_result);
176
if (FAILED(hr)) {
177
LOGHR(WARN, hr) << "Cannot get parentWindow from IHTMLDocument2, call to IHTMLDocument2::get_parentWindow failed";
178
return ENOSUCHFRAME;
179
}
180
} else {
181
CComPtr<IHTMLFrameBase2> frame_base;
182
frame_element->QueryInterface<IHTMLFrameBase2>(&frame_base);
183
if (!frame_base) {
184
LOG(WARN) << "IHTMLElement is not a FRAME or IFRAME element";
185
return ENOSUCHFRAME;
186
}
187
188
hr = frame_base->get_contentWindow(&interim_result);
189
if (FAILED(hr)) {
190
LOGHR(WARN, hr) << "Cannot get contentWindow from IHTMLFrameBase2, call to IHTMLFrameBase2::get_contentWindow failed";
191
return ENOSUCHFRAME;
192
}
193
}
194
195
this->focused_frame_window_ = interim_result;
196
return WD_SUCCESS;
197
}
198
199
int DocumentHost::SetFocusedFrameByName(const std::string& frame_name) {
200
LOG(TRACE) << "Entering DocumentHost::SetFocusedFrameByName";
201
CComVariant frame_identifier = StringUtilities::ToWString(frame_name).c_str();
202
return this->SetFocusedFrameByIdentifier(frame_identifier);
203
}
204
205
int DocumentHost::SetFocusedFrameByIndex(const int frame_index) {
206
LOG(TRACE) << "Entering DocumentHost::SetFocusedFrameByIndex";
207
CComVariant frame_identifier;
208
frame_identifier.vt = VT_I4;
209
frame_identifier.lVal = frame_index;
210
return this->SetFocusedFrameByIdentifier(frame_identifier);
211
}
212
213
void DocumentHost::SetFocusedFrameToParent() {
214
LOG(TRACE) << "Entering DocumentHost::SetFocusedFrameToParent";
215
// Three possible outcomes.
216
// Outcome 1: Already at top-level browsing context. No-op.
217
if (this->focused_frame_window_ != NULL) {
218
CComPtr<IHTMLWindow2> parent_window;
219
HRESULT hr = this->focused_frame_window_->get_parent(&parent_window);
220
if (FAILED(hr)) {
221
LOGHR(WARN, hr) << "IHTMLWindow2::get_parent call failed.";
222
}
223
CComPtr<IHTMLWindow2> top_window;
224
hr = this->focused_frame_window_->get_top(&top_window);
225
if (FAILED(hr)) {
226
LOGHR(WARN, hr) << "IHTMLWindow2::get_top call failed.";
227
}
228
if (top_window.IsEqualObject(parent_window)) {
229
// Outcome 2: Focus is on a frame one level deep, making the
230
// parent the top-level browsing context. Set focused frame
231
// pointer to NULL.
232
this->focused_frame_window_ = NULL;
233
} else {
234
// Outcome 3: Focus is on a frame more than one level deep.
235
// Set focused frame pointer to parent frame.
236
this->focused_frame_window_ = parent_window;
237
}
238
}
239
}
240
241
int DocumentHost::SetFocusedFrameByIdentifier(VARIANT frame_identifier) {
242
LOG(TRACE) << "Entering DocumentHost::SetFocusedFrameByIdentifier";
243
244
CComPtr<IHTMLDocument2> doc;
245
this->GetDocument(&doc);
246
247
CComPtr<IHTMLFramesCollection2> frames;
248
HRESULT hr = doc->get_frames(&frames);
249
250
if (!frames) {
251
LOG(WARN) << "No frames in document are set, IHTMLDocument2::get_frames returned NULL";
252
return ENOSUCHFRAME;
253
}
254
255
long length = 0;
256
frames->get_length(&length);
257
if (!length) {
258
LOG(WARN) << "No frames in document are found IHTMLFramesCollection2::get_length returned 0";
259
return ENOSUCHFRAME;
260
}
261
262
// Find the frame
263
CComVariant frame_holder;
264
hr = frames->item(&frame_identifier, &frame_holder);
265
266
if (FAILED(hr)) {
267
LOGHR(WARN, hr) << "Error retrieving frame holder, call to IHTMLFramesCollection2::item failed";
268
return ENOSUCHFRAME;
269
}
270
271
CComPtr<IHTMLWindow2> interim_result;
272
frame_holder.pdispVal->QueryInterface<IHTMLWindow2>(&interim_result);
273
if (!interim_result) {
274
LOG(WARN) << "Error retrieving frame, IDispatch cannot be cast to IHTMLWindow2";
275
return ENOSUCHFRAME;
276
}
277
278
this->focused_frame_window_ = interim_result;
279
return WD_SUCCESS;
280
}
281
282
void DocumentHost::PostQuitMessage() {
283
LOG(TRACE) << "Entering DocumentHost::PostQuitMessage";
284
285
LPSTR message_payload = new CHAR[this->browser_id_.size() + 1];
286
strcpy_s(message_payload, this->browser_id_.size() + 1, this->browser_id_.c_str());
287
::PostMessage(this->executor_handle(),
288
WD_BROWSER_QUIT,
289
NULL,
290
reinterpret_cast<LPARAM>(message_payload));
291
}
292
293
HWND DocumentHost::FindContentWindowHandle(HWND top_level_window_handle) {
294
LOG(TRACE) << "Entering DocumentHost::FindContentWindowHandle";
295
296
ProcessWindowInfo process_window_info;
297
process_window_info.pBrowser = NULL;
298
process_window_info.hwndBrowser = NULL;
299
DWORD process_id;
300
::GetWindowThreadProcessId(top_level_window_handle, &process_id);
301
process_window_info.dwProcessId = process_id;
302
303
::EnumChildWindows(top_level_window_handle,
304
&BrowserFactory::FindChildWindowForProcess,
305
reinterpret_cast<LPARAM>(&process_window_info));
306
return process_window_info.hwndBrowser;
307
}
308
309
int DocumentHost::GetDocumentMode(IHTMLDocument2* doc) {
310
LOG(TRACE) << "Entering DocumentHost::GetDocumentMode";
311
CComPtr<IHTMLDocument6> mode_doc;
312
doc->QueryInterface<IHTMLDocument6>(&mode_doc);
313
if (!mode_doc) {
314
LOG(DEBUG) << "QueryInterface for IHTMLDocument6 fails, so document mode must be 7 or less.";
315
return 5;
316
}
317
CComVariant mode;
318
HRESULT hr = mode_doc->get_documentMode(&mode);
319
if (FAILED(hr)) {
320
LOGHR(WARN, hr) << "get_documentMode failed.";
321
return 5;
322
}
323
int document_mode = static_cast<int>(mode.fltVal);
324
return document_mode;
325
}
326
327
bool DocumentHost::IsStandardsMode(IHTMLDocument2* doc) {
328
LOG(TRACE) << "Entering DocumentHost::IsStandardsMode";
329
CComPtr<IHTMLDocument5> compatibility_mode_doc;
330
doc->QueryInterface<IHTMLDocument5>(&compatibility_mode_doc);
331
if (!compatibility_mode_doc) {
332
LOG(WARN) << "Unable to cast document to IHTMLDocument5. IE6 or greater is required.";
333
return false;
334
}
335
336
CComBSTR compatibility_mode;
337
HRESULT hr = compatibility_mode_doc->get_compatMode(&compatibility_mode);
338
if (FAILED(hr)) {
339
LOGHR(WARN, hr) << "Failed calling get_compatMode.";
340
return false;
341
}
342
// Compatibility mode should be "BackCompat" for quirks mode, and
343
// "CSS1Compat" for standards mode. Check for "BackCompat" because
344
// that's less likely to change.
345
return compatibility_mode != L"BackCompat";
346
}
347
348
bool DocumentHost::GetDocumentDimensions(IHTMLDocument2* doc, LocationInfo* info) {
349
LOG(TRACE) << "Entering DocumentHost::GetDocumentDimensions";
350
CComVariant document_height;
351
CComVariant document_width;
352
353
// In non-standards-compliant mode, the BODY element represents the canvas.
354
// In standards-compliant mode, the HTML element represents the canvas.
355
CComPtr<IHTMLElement> canvas_element;
356
if (!IsStandardsMode(doc)) {
357
doc->get_body(&canvas_element);
358
if (!canvas_element) {
359
LOG(WARN) << "Unable to get canvas element from document in compatibility mode";
360
return false;
361
}
362
} else {
363
CComPtr<IHTMLDocument3> document_element_doc;
364
doc->QueryInterface<IHTMLDocument3>(&document_element_doc);
365
if (!document_element_doc) {
366
LOG(WARN) << "Unable to get IHTMLDocument3 handle from document.";
367
return false;
368
}
369
370
// The root node should be the HTML element.
371
document_element_doc->get_documentElement(&canvas_element);
372
if (!canvas_element) {
373
LOG(WARN) << "Could not retrieve document element.";
374
return false;
375
}
376
377
CComPtr<IHTMLHtmlElement> html_element;
378
canvas_element->QueryInterface<IHTMLHtmlElement>(&html_element);
379
if (!html_element) {
380
LOG(WARN) << "Document element is not the HTML element.";
381
return false;
382
}
383
}
384
385
canvas_element->getAttribute(CComBSTR("scrollHeight"), 0, &document_height);
386
canvas_element->getAttribute(CComBSTR("scrollWidth"), 0, &document_width);
387
info->height = document_height.lVal;
388
info->width = document_width.lVal;
389
return true;
390
}
391
392
bool DocumentHost::IsCrossZoneUrl(std::string url) {
393
LOG(TRACE) << "Entering Browser::IsCrossZoneUrl";
394
std::wstring target_url = StringUtilities::ToWString(url);
395
CComPtr<IUri> parsed_url;
396
HRESULT hr = ::CreateUri(target_url.c_str(),
397
Uri_CREATE_IE_SETTINGS,
398
0,
399
&parsed_url);
400
if (FAILED(hr)) {
401
// If we can't parse the URL, assume that it's invalid, and
402
// therefore won't cross a Protected Mode boundary.
403
return false;
404
}
405
bool is_protected_mode_browser = this->IsProtectedMode();
406
bool is_protected_mode_url = is_protected_mode_browser;
407
if (url.find("about:blank") != 0) {
408
// If the URL starts with "about:blank", it won't cross the Protected
409
// Mode boundary, so skip checking if it's a Protected Mode URL.
410
is_protected_mode_url = ::IEIsProtectedModeURL(target_url.c_str()) == S_OK;
411
}
412
bool is_cross_zone = is_protected_mode_browser != is_protected_mode_url;
413
if (is_cross_zone) {
414
LOG(DEBUG) << "Navigation across Protected Mode zone detected. URL: "
415
<< url
416
<< ", is URL Protected Mode: "
417
<< (is_protected_mode_url ? "true" : "false")
418
<< ", is IE in Protected Mode: "
419
<< (is_protected_mode_browser ? "true" : "false");
420
}
421
return is_cross_zone;
422
}
423
424
bool DocumentHost::IsProtectedMode() {
425
LOG(TRACE) << "Entering DocumentHost::IsProtectedMode";
426
HWND window_handle = this->GetBrowserWindowHandle();
427
HookSettings hook_settings;
428
hook_settings.hook_procedure_name = "ProtectedModeWndProc";
429
hook_settings.hook_procedure_type = WH_CALLWNDPROC;
430
hook_settings.window_handle = window_handle;
431
hook_settings.communication_type = OneWay;
432
433
HookProcessor hook;
434
if (!hook.CanSetWindowsHook(window_handle)) {
435
LOG(WARN) << "Cannot check Protected Mode because driver and browser are "
436
<< "not the same bit-ness.";
437
return false;
438
}
439
hook.Initialize(hook_settings);
440
HookProcessor::ResetFlag();
441
::SendMessage(window_handle, WD_IS_BROWSER_PROTECTED_MODE, NULL, NULL);
442
bool is_protected_mode = HookProcessor::GetFlagValue();
443
return is_protected_mode;
444
}
445
446
bool DocumentHost::SetFocusToBrowser() {
447
LOG(TRACE) << "Entering DocumentHost::SetFocusToBrowser";
448
449
HWND top_level_window_handle = this->GetTopLevelWindowHandle();
450
HWND foreground_window = ::GetAncestor(::GetForegroundWindow(), GA_ROOT);
451
if (foreground_window != top_level_window_handle) {
452
LOG(TRACE) << "Top-level IE window is " << top_level_window_handle
453
<< " foreground window is " << foreground_window;
454
CComPtr<IUIAutomation> ui_automation;
455
HRESULT hr = ::CoCreateInstance(CLSID_CUIAutomation,
456
NULL,
457
CLSCTX_INPROC_SERVER,
458
IID_IUIAutomation,
459
reinterpret_cast<void**>(&ui_automation));
460
if (SUCCEEDED(hr)) {
461
LOG(TRACE) << "Using UI Automation to set window focus";
462
CComPtr<IUIAutomationElement> parent_window;
463
hr = ui_automation->ElementFromHandle(top_level_window_handle,
464
&parent_window);
465
if (SUCCEEDED(hr)) {
466
CComPtr<IUIAutomationWindowPattern> window_pattern;
467
hr = parent_window->GetCurrentPatternAs(UIA_WindowPatternId,
468
IID_PPV_ARGS(&window_pattern));
469
if (SUCCEEDED(hr) && window_pattern != nullptr) {
470
BOOL is_topmost;
471
hr = window_pattern->get_CurrentIsTopmost(&is_topmost);
472
WindowVisualState visual_state;
473
hr = window_pattern->get_CurrentWindowVisualState(&visual_state);
474
if (visual_state == WindowVisualState::WindowVisualState_Maximized ||
475
visual_state == WindowVisualState::WindowVisualState_Normal) {
476
parent_window->SetFocus();
477
window_pattern->SetWindowVisualState(visual_state);
478
}
479
}
480
}
481
}
482
}
483
484
foreground_window = ::GetAncestor(::GetForegroundWindow(), GA_ROOT);
485
if (foreground_window != top_level_window_handle) {
486
HWND content_window_handle = this->GetContentWindowHandle();
487
LOG(TRACE) << "Top-level IE window is " << top_level_window_handle
488
<< " foreground window is " << foreground_window;
489
LOG(TRACE) << "Window still not in foreground; "
490
<< "attempting to use SetForegroundWindow API";
491
UINT_PTR lock_timeout = 0;
492
DWORD process_id = 0;
493
DWORD thread_id = ::GetWindowThreadProcessId(top_level_window_handle,
494
&process_id);
495
DWORD current_thread_id = ::GetCurrentThreadId();
496
DWORD current_process_id = ::GetCurrentProcessId();
497
if (current_thread_id != thread_id) {
498
::AttachThreadInput(current_thread_id, thread_id, TRUE);
499
::SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT,
500
0,
501
&lock_timeout,
502
0);
503
::SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT,
504
0,
505
0,
506
SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);
507
HookSettings hook_settings;
508
hook_settings.hook_procedure_name = "AllowSetForegroundProc";
509
hook_settings.hook_procedure_type = WH_CALLWNDPROC;
510
hook_settings.window_handle = content_window_handle;
511
hook_settings.communication_type = OneWay;
512
513
HookProcessor hook;
514
if (!hook.CanSetWindowsHook(content_window_handle)) {
515
LOG(WARN) << "Setting window focus may fail because driver and browser "
516
<< "are not the same bit-ness.";
517
return false;
518
}
519
hook.Initialize(hook_settings);
520
::SendMessage(content_window_handle,
521
WD_ALLOW_SET_FOREGROUND,
522
NULL,
523
NULL);
524
hook.Dispose();
525
}
526
::SetForegroundWindow(top_level_window_handle);
527
::Sleep(100);
528
if (current_thread_id != thread_id) {
529
::SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT,
530
0,
531
reinterpret_cast<void*>(lock_timeout),
532
SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE);
533
::AttachThreadInput(current_thread_id, thread_id, FALSE);
534
}
535
}
536
foreground_window = ::GetAncestor(::GetForegroundWindow(), GA_ROOT);
537
return foreground_window == top_level_window_handle;
538
}
539
540
541
} // namespace webdriver
542
543
#ifdef __cplusplus
544
extern "C" {
545
#endif
546
547
LRESULT CALLBACK ProtectedModeWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
548
CWPSTRUCT* call_window_proc_struct = reinterpret_cast<CWPSTRUCT*>(lParam);
549
if (WD_IS_BROWSER_PROTECTED_MODE == call_window_proc_struct->message) {
550
BOOL is_protected_mode = FALSE;
551
HRESULT hr = ::IEIsProtectedModeProcess(&is_protected_mode);
552
webdriver::HookProcessor::SetFlagValue(is_protected_mode == TRUE);
553
}
554
return ::CallNextHookEx(NULL, nCode, wParam, lParam);
555
}
556
557
LRESULT CALLBACK AllowSetForegroundProc(int nCode, WPARAM wParam, LPARAM lParam) {
558
if ((nCode == HC_ACTION) && (wParam == PM_REMOVE)) {
559
MSG* msg = reinterpret_cast<MSG*>(lParam);
560
if (msg->message == WD_ALLOW_SET_FOREGROUND) {
561
::AllowSetForegroundWindow(ASFW_ANY);
562
}
563
}
564
565
return CallNextHookEx(NULL, nCode, wParam, lParam);
566
}
567
568
#ifdef __cplusplus
569
}
570
#endif
571
572