OLD | NEW |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "remoting/host/win/rdp_client_window.h" | 5 #include "remoting/host/win/rdp_client_window.h" |
6 | 6 |
7 #include <wtsdefs.h> | 7 #include <wtsdefs.h> |
8 | 8 |
9 #include <list> | 9 #include <list> |
10 | 10 |
| 11 #include "base/bind.h" |
11 #include "base/lazy_instance.h" | 12 #include "base/lazy_instance.h" |
| 13 #include "base/location.h" |
12 #include "base/logging.h" | 14 #include "base/logging.h" |
13 #include "base/macros.h" | 15 #include "base/macros.h" |
14 #include "base/strings/string16.h" | 16 #include "base/strings/string16.h" |
15 #include "base/strings/utf_string_conversions.h" | 17 #include "base/strings/utf_string_conversions.h" |
16 #include "base/threading/thread_local.h" | 18 #include "base/threading/thread_local.h" |
17 #include "base/win/scoped_bstr.h" | 19 #include "base/win/scoped_bstr.h" |
18 | 20 |
19 namespace remoting { | 21 namespace remoting { |
20 | 22 |
21 namespace { | 23 namespace { |
22 | 24 |
23 // RDP session disconnect reason codes that should not be interpreted as errors. | 25 // RDP session disconnect reason codes that should not be interpreted as errors. |
24 constexpr long kDisconnectReasonNoInfo = 0; | 26 constexpr long kDisconnectReasonNoInfo = 0; |
25 constexpr long kDisconnectReasonLocalNotError = 1; | 27 constexpr long kDisconnectReasonLocalNotError = 1; |
26 constexpr long kDisconnectReasonRemoteByUser = 2; | 28 constexpr long kDisconnectReasonRemoteByUser = 2; |
27 constexpr long kDisconnectReasonByServer = 3; | 29 constexpr long kDisconnectReasonByServer = 3; |
28 | 30 |
29 // Maximum length of a window class name including the terminating nullptr. | 31 // Maximum length of a window class name including the terminating nullptr. |
30 constexpr int kMaxWindowClassLength = 256; | 32 constexpr int kMaxWindowClassLength = 256; |
31 | 33 |
32 // Each member of the array returned by GetKeyboardState() contains status data | 34 // Each member of the array returned by GetKeyboardState() contains status data |
33 // for a virtual key. If the high-order bit is 1, the key is down; otherwise, it | 35 // for a virtual key. If the high-order bit is 1, the key is down; otherwise, it |
34 // is up. | 36 // is up. |
35 constexpr BYTE kKeyPressedFlag = 0x80; | 37 constexpr BYTE kKeyPressedFlag = 0x80; |
36 | 38 |
37 constexpr int kKeyboardStateLength = 256; | 39 constexpr int kKeyboardStateLength = 256; |
38 | 40 |
| 41 constexpr base::TimeDelta kReapplyResolutionPeriod = |
| 42 base::TimeDelta::FromMilliseconds(250); |
| 43 |
| 44 // We want to try to reapply resolution changes for ~5 seconds (20 * 250ms). |
| 45 constexpr int kMaxResolutionReapplyAttempts = 20; |
| 46 |
39 // The RDP control creates 'IHWindowClass' window to handle keyboard input. | 47 // The RDP control creates 'IHWindowClass' window to handle keyboard input. |
40 constexpr wchar_t kRdpInputWindowClass[] = L"IHWindowClass"; | 48 constexpr wchar_t kRdpInputWindowClass[] = L"IHWindowClass"; |
41 | 49 |
42 enum RdpAudioMode { | 50 enum RdpAudioMode { |
43 // Redirect sounds to the client. This is the default value. | 51 // Redirect sounds to the client. This is the default value. |
44 kRdpAudioModeRedirect = 0, | 52 kRdpAudioModeRedirect = 0, |
45 | 53 |
46 // Play sounds at the remote computer. Equivalent to |kRdpAudioModeNone| if | 54 // Play sounds at the remote computer. Equivalent to |kRdpAudioModeNone| if |
47 // the remote computer is running a server SKU. | 55 // the remote computer is running a server SKU. |
48 kRdpAudioModePlayOnServer = 1, | 56 kRdpAudioModePlayOnServer = 1, |
(...skipping 159 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
208 SendMessage(input_window, WM_KEYUP, VK_MENU, MAKELPARAM(1, alt | up)); | 216 SendMessage(input_window, WM_KEYUP, VK_MENU, MAKELPARAM(1, alt | up)); |
209 | 217 |
210 // Release 'Ctrl'. | 218 // Release 'Ctrl'. |
211 keyboard_state[VK_CONTROL] &= ~kKeyPressedFlag; | 219 keyboard_state[VK_CONTROL] &= ~kKeyPressedFlag; |
212 keyboard_state[VK_LCONTROL] &= ~kKeyPressedFlag; | 220 keyboard_state[VK_LCONTROL] &= ~kKeyPressedFlag; |
213 CHECK(SetKeyboardState(keyboard_state)); | 221 CHECK(SetKeyboardState(keyboard_state)); |
214 SendMessage(input_window, WM_KEYUP, VK_CONTROL, MAKELPARAM(1, control | up)); | 222 SendMessage(input_window, WM_KEYUP, VK_CONTROL, MAKELPARAM(1, control | up)); |
215 } | 223 } |
216 | 224 |
217 void RdpClientWindow::ChangeResolution(const ScreenResolution& resolution) { | 225 void RdpClientWindow::ChangeResolution(const ScreenResolution& resolution) { |
| 226 // Stop any pending resolution changes. |
| 227 apply_resolution_timer_.Stop(); |
218 screen_resolution_ = resolution; | 228 screen_resolution_ = resolution; |
219 UpdateDesktopResolution(); | 229 HRESULT result = UpdateDesktopResolution(); |
| 230 if (FAILED(result)) { |
| 231 LOG(WARNING) << "UpdateSessionDisplaySettings() failed: 0x" << std::hex |
| 232 << result; |
| 233 } |
220 } | 234 } |
221 | 235 |
222 void RdpClientWindow::OnClose() { | 236 void RdpClientWindow::OnClose() { |
223 if (!client_.get()) { | 237 if (!client_.get()) { |
224 NotifyDisconnected(); | 238 NotifyDisconnected(); |
225 return; | 239 return; |
226 } | 240 } |
227 | 241 |
228 // Request a graceful shutdown. | 242 // Request a graceful shutdown. |
229 mstsc::ControlCloseStatus close_status; | 243 mstsc::ControlCloseStatus close_status; |
230 HRESULT result = client_->RequestClose(&close_status); | 244 HRESULT result = client_->RequestClose(&close_status); |
231 if (FAILED(result)) { | 245 if (FAILED(result)) { |
232 LOG(ERROR) << "Failed to request a graceful shutdown of an RDP connection" | 246 LOG(ERROR) << "Failed to request a graceful shutdown of an RDP connection" |
233 << ", result=0x" << std::hex << result << std::dec; | 247 << ", result=0x" << std::hex << result; |
234 NotifyDisconnected(); | 248 NotifyDisconnected(); |
235 return; | 249 return; |
236 } | 250 } |
237 | 251 |
238 if (close_status != mstsc::controlCloseWaitForEvents) { | 252 if (close_status != mstsc::controlCloseWaitForEvents) { |
239 NotifyDisconnected(); | 253 NotifyDisconnected(); |
240 return; | 254 return; |
241 } | 255 } |
242 | 256 |
243 // Expect IMsTscAxEvents::OnConfirmClose() or IMsTscAxEvents::OnDisconnect() | 257 // Expect IMsTscAxEvents::OnConfirmClose() or IMsTscAxEvents::OnDisconnect() |
(...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
383 if (FAILED(result)) | 397 if (FAILED(result)) |
384 return LogOnCreateError(result); | 398 return LogOnCreateError(result); |
385 | 399 |
386 return 0; | 400 return 0; |
387 } | 401 } |
388 | 402 |
389 void RdpClientWindow::OnDestroy() { | 403 void RdpClientWindow::OnDestroy() { |
390 client_.Release(); | 404 client_.Release(); |
391 client_9_.Release(); | 405 client_9_.Release(); |
392 client_settings_.Release(); | 406 client_settings_.Release(); |
| 407 apply_resolution_timer_.Stop(); |
393 } | 408 } |
394 | 409 |
395 HRESULT RdpClientWindow::OnAuthenticationWarningDisplayed() { | 410 HRESULT RdpClientWindow::OnAuthenticationWarningDisplayed() { |
396 LOG(WARNING) << "RDP: authentication warning is about to be shown."; | 411 LOG(WARNING) << "RDP: authentication warning is about to be shown."; |
397 | 412 |
398 // Hook window activation to cancel any modal UI shown by the RDP control. | 413 // Hook window activation to cancel any modal UI shown by the RDP control. |
399 // This does not affect creation of other instances of the RDP control on this | 414 // This does not affect creation of other instances of the RDP control on this |
400 // thread because the RDP control's window is hidden and is not activated. | 415 // thread because the RDP control's window is hidden and is not activated. |
401 window_activate_hook_ = WindowHook::Create(); | 416 window_activate_hook_ = WindowHook::Create(); |
402 return S_OK; | 417 return S_OK; |
(...skipping 11 matching lines...) Expand all Loading... |
414 | 429 |
415 NotifyConnected(); | 430 NotifyConnected(); |
416 return S_OK; | 431 return S_OK; |
417 } | 432 } |
418 | 433 |
419 HRESULT RdpClientWindow::OnLoginComplete() { | 434 HRESULT RdpClientWindow::OnLoginComplete() { |
420 VLOG(1) << "RDP: user successfully logged in."; | 435 VLOG(1) << "RDP: user successfully logged in."; |
421 | 436 |
422 user_logged_in_ = true; | 437 user_logged_in_ = true; |
423 | 438 |
424 // Apply pending screen size changes to the desktop. | 439 // Set up a timer to periodically apply pending screen size changes to the |
425 UpdateDesktopResolution(); | 440 // desktop. Attempting to set the resolution now seems to fail consistently, |
| 441 // but succeeds after a brief timeout. |
| 442 if (client_9_) { |
| 443 apply_resolution_attempts_ = 0; |
| 444 apply_resolution_timer_.Start( |
| 445 FROM_HERE, kReapplyResolutionPeriod, |
| 446 base::Bind(&RdpClientWindow::ReapplyDesktopResolution, this)); |
| 447 } |
426 | 448 |
427 return S_OK; | 449 return S_OK; |
428 } | 450 } |
429 | 451 |
430 HRESULT RdpClientWindow::OnDisconnected(long reason) { | 452 HRESULT RdpClientWindow::OnDisconnected(long reason) { |
431 if (reason == kDisconnectReasonNoInfo || | 453 if (reason == kDisconnectReasonNoInfo || |
432 reason == kDisconnectReasonLocalNotError || | 454 reason == kDisconnectReasonLocalNotError || |
433 reason == kDisconnectReasonRemoteByUser || | 455 reason == kDisconnectReasonRemoteByUser || |
434 reason == kDisconnectReasonByServer) { | 456 reason == kDisconnectReasonByServer) { |
435 VLOG(1) << "RDP: disconnected from " << server_endpoint_.ToString() | 457 VLOG(1) << "RDP: disconnected from " << server_endpoint_.ToString() |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
473 | 495 |
474 HRESULT RdpClientWindow::OnConfirmClose(VARIANT_BOOL* allow_close) { | 496 HRESULT RdpClientWindow::OnConfirmClose(VARIANT_BOOL* allow_close) { |
475 *allow_close = VARIANT_TRUE; | 497 *allow_close = VARIANT_TRUE; |
476 | 498 |
477 NotifyDisconnected(); | 499 NotifyDisconnected(); |
478 return S_OK; | 500 return S_OK; |
479 } | 501 } |
480 | 502 |
481 int RdpClientWindow::LogOnCreateError(HRESULT error) { | 503 int RdpClientWindow::LogOnCreateError(HRESULT error) { |
482 LOG(ERROR) << "RDP: failed to initiate a connection to " | 504 LOG(ERROR) << "RDP: failed to initiate a connection to " |
483 << server_endpoint_.ToString() << ": error=" | 505 << server_endpoint_.ToString() << ": error=" << std::hex << error; |
484 << std::hex << error << std::dec; | |
485 client_.Release(); | 506 client_.Release(); |
486 client_9_.Release(); | 507 client_9_.Release(); |
487 client_settings_.Release(); | 508 client_settings_.Release(); |
488 return -1; | 509 return -1; |
489 } | 510 } |
490 | 511 |
491 void RdpClientWindow::NotifyConnected() { | 512 void RdpClientWindow::NotifyConnected() { |
492 if (event_handler_) { | 513 if (event_handler_) { |
493 event_handler_->OnConnected(); | 514 event_handler_->OnConnected(); |
494 } | 515 } |
495 } | 516 } |
496 | 517 |
497 void RdpClientWindow::NotifyDisconnected() { | 518 void RdpClientWindow::NotifyDisconnected() { |
498 if (event_handler_) { | 519 if (event_handler_) { |
499 EventHandler* event_handler = event_handler_; | 520 EventHandler* event_handler = event_handler_; |
500 event_handler_ = nullptr; | 521 event_handler_ = nullptr; |
501 event_handler->OnDisconnected(); | 522 event_handler->OnDisconnected(); |
502 } | 523 } |
503 } | 524 } |
504 | 525 |
505 void RdpClientWindow::UpdateDesktopResolution() { | 526 HRESULT RdpClientWindow::UpdateDesktopResolution() { |
506 if (!client_9_ || !user_logged_in_) { | 527 if (!client_9_ || !user_logged_in_) { |
507 return; | 528 return S_FALSE; |
508 } | 529 } |
509 | 530 |
510 // UpdateSessionDisplaySettings() is poorly documented in MSDN and has a few | 531 // UpdateSessionDisplaySettings() is poorly documented in MSDN and has a few |
511 // quirks that should be noted. | 532 // quirks that should be noted. |
512 // 1.) This method will only work when the user is logged into their session. | 533 // 1.) This method will only work when the user is logged into their session. |
513 // 2.) The method may return E_UNEXPECTED until some amount of time (seconds) | 534 // 2.) The method may return E_UNEXPECTED until some amount of time (seconds) |
514 // have elapsed after logging in to the user's session. | 535 // have elapsed after logging in to the user's session. |
515 // TODO(joedow): Investigate adding retry logic for applying the settings. | 536 return client_9_->UpdateSessionDisplaySettings( |
516 HRESULT result = client_9_->UpdateSessionDisplaySettings( | |
517 screen_resolution_.dimensions().width(), | 537 screen_resolution_.dimensions().width(), |
518 screen_resolution_.dimensions().height(), | 538 screen_resolution_.dimensions().height(), |
519 screen_resolution_.dimensions().width(), | 539 screen_resolution_.dimensions().width(), |
520 screen_resolution_.dimensions().height(), | 540 screen_resolution_.dimensions().height(), |
521 /*ulOrientation=*/0, | 541 /*ulOrientation=*/0, |
522 screen_resolution_.dpi().x(), | 542 screen_resolution_.dpi().x(), |
523 screen_resolution_.dpi().y()); | 543 screen_resolution_.dpi().y()); |
524 if (FAILED(result)) { | 544 } |
525 LOG(ERROR) << "UpdateSessionDisplaySettings() failed: 0x" << std::hex | 545 |
526 << result << std::dec; | 546 void RdpClientWindow::ReapplyDesktopResolution() { |
| 547 DCHECK_LT(apply_resolution_attempts_, kMaxResolutionReapplyAttempts); |
| 548 |
| 549 HRESULT result = UpdateDesktopResolution(); |
| 550 apply_resolution_attempts_++; |
| 551 |
| 552 if (SUCCEEDED(result)) { |
| 553 // Successfully applied the new resolution so stop the retry timer. |
| 554 apply_resolution_timer_.Stop(); |
| 555 } else if (apply_resolution_attempts_ == kMaxResolutionReapplyAttempts) { |
| 556 // Only log an error on the last attempt to reduce log spam since a few |
| 557 // errors can be expected and don't signal an actual failure. |
| 558 LOG(WARNING) << "All UpdateSessionDisplaySettings() retries failed: 0x" |
| 559 << std::hex << result; |
| 560 apply_resolution_timer_.Stop(); |
527 } | 561 } |
528 } | 562 } |
529 | 563 |
530 scoped_refptr<RdpClientWindow::WindowHook> | 564 scoped_refptr<RdpClientWindow::WindowHook> |
531 RdpClientWindow::WindowHook::Create() { | 565 RdpClientWindow::WindowHook::Create() { |
532 scoped_refptr<WindowHook> window_hook = g_window_hook.Pointer()->Get(); | 566 scoped_refptr<WindowHook> window_hook = g_window_hook.Pointer()->Get(); |
533 | 567 |
534 if (!window_hook.get()) { | 568 if (!window_hook.get()) { |
535 window_hook = new WindowHook(); | 569 window_hook = new WindowHook(); |
536 } | 570 } |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
569 int code, WPARAM wparam, LPARAM lparam) { | 603 int code, WPARAM wparam, LPARAM lparam) { |
570 // Get the hook handle. | 604 // Get the hook handle. |
571 HHOOK hook = g_window_hook.Pointer()->Get()->hook_; | 605 HHOOK hook = g_window_hook.Pointer()->Get()->hook_; |
572 | 606 |
573 if (code != HCBT_ACTIVATE) { | 607 if (code != HCBT_ACTIVATE) { |
574 return CallNextHookEx(hook, code, wparam, lparam); | 608 return CallNextHookEx(hook, code, wparam, lparam); |
575 } | 609 } |
576 | 610 |
577 // Close the window once all pending window messages are processed. | 611 // Close the window once all pending window messages are processed. |
578 HWND window = reinterpret_cast<HWND>(wparam); | 612 HWND window = reinterpret_cast<HWND>(wparam); |
579 LOG(WARNING) << "RDP: closing a window: " << std::hex << window << std::dec; | 613 LOG(WARNING) << "RDP: closing a window: " << std::hex << window; |
580 ::PostMessage(window, WM_CLOSE, 0, 0); | 614 ::PostMessage(window, WM_CLOSE, 0, 0); |
581 return 0; | 615 return 0; |
582 } | 616 } |
583 | 617 |
584 } // namespace remoting | 618 } // namespace remoting |
OLD | NEW |