// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include "base/compiler_specific.h" #include "base/logging.h" #include "base/process_util.h" #include "base/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/win/scoped_gdi_object.h" #include "base/win/scoped_hdc.h" #include "base/win/scoped_select_object.h" #include "remoting/host/client_session_control.h" #include "remoting/host/host_window.h" #include "remoting/host/ui_strings.h" #include "remoting/host/win/core_resource.h" namespace remoting { namespace { const int DISCONNECT_HOTKEY_ID = 1000; // Maximum length of "Your desktop is shared with ..." message in UTF-16 // characters. const size_t kMaxSharingWithTextLength = 100; const wchar_t kShellTrayWindowName[] = L"Shell_TrayWnd"; const int kWindowBorderRadius = 14; class DisconnectWindowWin : public HostWindow { public: explicit DisconnectWindowWin(const UiStrings& ui_strings); virtual ~DisconnectWindowWin(); // HostWindow overrides. virtual void Start( const base::WeakPtr& client_session_control) OVERRIDE; protected: static INT_PTR CALLBACK DialogProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); BOOL OnDialogMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); // Creates the dialog window and registers the disconnect hot key. bool BeginDialog(); // Closes the dialog, unregisters the hot key and invokes the disconnect // callback, if set. void EndDialog(); // Trys to position the dialog window above the taskbar. void SetDialogPosition(); // Applies localization string and resizes the dialog. bool SetStrings(); // Used to disconnect the client session. base::WeakPtr client_session_control_; // Localized UI strings. UiStrings ui_strings_; // Specifies the remote user name. std::string username_; HWND hwnd_; bool has_hotkey_; base::win::ScopedGDIObject border_pen_; DISALLOW_COPY_AND_ASSIGN(DisconnectWindowWin); }; int GetControlTextWidth(HWND control) { RECT rect = {0, 0, 0, 0}; WCHAR text[256]; int result = GetWindowText(control, text, arraysize(text)); if (result) { base::win::ScopedGetDC dc(control); base::win::ScopedSelectObject font( dc, (HFONT)SendMessage(control, WM_GETFONT, 0, 0)); DrawText(dc, text, -1, &rect, DT_CALCRECT | DT_SINGLELINE); } return rect.right; } DisconnectWindowWin::DisconnectWindowWin(const UiStrings& ui_strings) : ui_strings_(ui_strings), hwnd_(NULL), has_hotkey_(false), border_pen_(CreatePen(PS_SOLID, 5, RGB(0.13 * 255, 0.69 * 255, 0.11 * 255))) { } DisconnectWindowWin::~DisconnectWindowWin() { EndDialog(); } void DisconnectWindowWin::Start( const base::WeakPtr& client_session_control) { DCHECK(CalledOnValidThread()); DCHECK(!client_session_control_); DCHECK(client_session_control); client_session_control_ = client_session_control; std::string client_jid = client_session_control_->client_jid(); username_ = client_jid.substr(0, client_jid.find('/')); if (!BeginDialog()) EndDialog(); } INT_PTR CALLBACK DisconnectWindowWin::DialogProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { LONG_PTR self = NULL; if (message == WM_INITDIALOG) { self = lparam; // Store |this| to the window's user data. SetLastError(ERROR_SUCCESS); LONG_PTR result = SetWindowLongPtr(hwnd, DWLP_USER, self); if (result == 0 && GetLastError() != ERROR_SUCCESS) reinterpret_cast(self)->EndDialog(); } else { self = GetWindowLongPtr(hwnd, DWLP_USER); } if (self) { return reinterpret_cast(self)->OnDialogMessage( hwnd, message, wparam, lparam); } return FALSE; } BOOL DisconnectWindowWin::OnDialogMessage(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { DCHECK(CalledOnValidThread()); switch (message) { // Ignore close messages. case WM_CLOSE: return TRUE; // Handle the Disconnect button. case WM_COMMAND: switch (LOWORD(wparam)) { case IDC_DISCONNECT: EndDialog(); return TRUE; } return FALSE; // Ensure we don't try to use the HWND anymore. case WM_DESTROY: hwnd_ = NULL; // Ensure that the disconnect callback is invoked even if somehow our // window gets destroyed. EndDialog(); return TRUE; // Ensure the dialog stays visible if the work area dimensions change. case WM_SETTINGCHANGE: if (wparam == SPI_SETWORKAREA) SetDialogPosition(); return TRUE; // Ensure the dialog stays visible if the display dimensions change. case WM_DISPLAYCHANGE: SetDialogPosition(); return TRUE; // Handle the disconnect hot-key. case WM_HOTKEY: EndDialog(); return TRUE; // Let the window be draggable by its client area by responding // that the entire window is the title bar. case WM_NCHITTEST: SetWindowLongPtr(hwnd, DWLP_MSGRESULT, HTCAPTION); return TRUE; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd_, &ps); RECT rect; GetClientRect(hwnd_, &rect); { base::win::ScopedSelectObject border(hdc, border_pen_); base::win::ScopedSelectObject brush(hdc, GetStockObject(NULL_BRUSH)); RoundRect(hdc, rect.left, rect.top, rect.right - 1, rect.bottom - 1, kWindowBorderRadius, kWindowBorderRadius); } EndPaint(hwnd_, &ps); return TRUE; } } return FALSE; } bool DisconnectWindowWin::BeginDialog() { DCHECK(CalledOnValidThread()); DCHECK(!hwnd_); // Load the dialog resource so that we can modify the RTL flags if necessary. HMODULE module = base::GetModuleFromAddress(&DialogProc); HRSRC dialog_resource = FindResource(module, MAKEINTRESOURCE(IDD_DISCONNECT), RT_DIALOG); if (!dialog_resource) return false; HGLOBAL dialog_template = LoadResource(module, dialog_resource); if (!dialog_template) return false; DLGTEMPLATE* dialog_pointer = reinterpret_cast(LockResource(dialog_template)); if (!dialog_pointer) return false; // The actual resource type is DLGTEMPLATEEX, but this is not defined in any // standard headers, so we treat it as a generic pointer and manipulate the // correct offsets explicitly. scoped_ptr rtl_dialog_template; if (ui_strings_.direction == UiStrings::RTL) { unsigned long dialog_template_size = SizeofResource(module, dialog_resource); rtl_dialog_template.reset(new unsigned char[dialog_template_size]); memcpy(rtl_dialog_template.get(), dialog_pointer, dialog_template_size); DWORD* rtl_dwords = reinterpret_cast(rtl_dialog_template.get()); rtl_dwords[2] |= (WS_EX_LAYOUTRTL | WS_EX_RTLREADING); dialog_pointer = reinterpret_cast(rtl_dwords); } hwnd_ = CreateDialogIndirectParam(module, dialog_pointer, NULL, DialogProc, reinterpret_cast(this)); if (!hwnd_) return false; // Set up handler for Ctrl-Alt-Esc shortcut. if (!has_hotkey_ && RegisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID, MOD_ALT | MOD_CONTROL, VK_ESCAPE)) { has_hotkey_ = true; } if (!SetStrings()) return false; SetDialogPosition(); ShowWindow(hwnd_, SW_SHOW); return IsWindowVisible(hwnd_) != FALSE; } void DisconnectWindowWin::EndDialog() { DCHECK(CalledOnValidThread()); if (has_hotkey_) { UnregisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID); has_hotkey_ = false; } if (hwnd_) { DestroyWindow(hwnd_); hwnd_ = NULL; } if (client_session_control_) client_session_control_->DisconnectSession(); } void DisconnectWindowWin::SetDialogPosition() { DCHECK(CalledOnValidThread()); // Try to center the window above the task-bar. If that fails, use the // primary monitor. If that fails (very unlikely), use the default position. HWND taskbar = FindWindow(kShellTrayWindowName, NULL); HMONITOR monitor = MonitorFromWindow(taskbar, MONITOR_DEFAULTTOPRIMARY); MONITORINFO monitor_info = {sizeof(monitor_info)}; RECT window_rect; if (GetMonitorInfo(monitor, &monitor_info) && GetWindowRect(hwnd_, &window_rect)) { int window_width = window_rect.right - window_rect.left; int window_height = window_rect.bottom - window_rect.top; int top = monitor_info.rcWork.bottom - window_height; int left = (monitor_info.rcWork.right + monitor_info.rcWork.left - window_width) / 2; SetWindowPos(hwnd_, NULL, left, top, 0, 0, SWP_NOSIZE | SWP_NOZORDER); } } bool DisconnectWindowWin::SetStrings() { DCHECK(CalledOnValidThread()); if (!SetWindowText(hwnd_, ui_strings_.product_name.c_str())) return false; // Localize the disconnect button text and measure length of the old and new // labels. HWND disconnect_button = GetDlgItem(hwnd_, IDC_DISCONNECT); if (!disconnect_button) return false; int button_old_required_width = GetControlTextWidth(disconnect_button); if (!SetWindowText(disconnect_button, ui_strings_.disconnect_button_text.c_str())) { return false; } int button_new_required_width = GetControlTextWidth(disconnect_button); int button_width_delta = button_new_required_width - button_old_required_width; // Format and truncate "Your desktop is shared with ..." message. string16 text = ReplaceStringPlaceholders(ui_strings_.disconnect_message, UTF8ToUTF16(username_), NULL); if (text.length() > kMaxSharingWithTextLength) text.erase(kMaxSharingWithTextLength); // Set localized and truncated "Your desktop is shared with ..." message and // measure length of the old and new text. HWND sharing_with_label = GetDlgItem(hwnd_, IDC_DISCONNECT_SHARINGWITH); if (!sharing_with_label) return false; int label_old_required_width = GetControlTextWidth(sharing_with_label); if (!SetWindowText(sharing_with_label, text.c_str())) return false; int label_new_required_width = GetControlTextWidth(sharing_with_label); int label_width_delta = label_new_required_width - label_old_required_width; // Reposition the controls such that the label lies to the left of the // disconnect button (assuming LTR layout). The dialog template determines // the controls' spacing; update their size to fit the localized content. RECT label_rect; if (!GetClientRect(sharing_with_label, &label_rect)) return false; if (!SetWindowPos(sharing_with_label, NULL, 0, 0, label_rect.right + label_width_delta, label_rect.bottom, SWP_NOMOVE | SWP_NOZORDER)) { return false; } // Reposition the disconnect button as well. RECT button_rect; if (!GetWindowRect(disconnect_button, &button_rect)) return false; int button_width = button_rect.right - button_rect.left; int button_height = button_rect.bottom - button_rect.top; SetLastError(ERROR_SUCCESS); int result = MapWindowPoints(HWND_DESKTOP, hwnd_, reinterpret_cast(&button_rect), 2); if (!result && GetLastError() != ERROR_SUCCESS) return false; if (!SetWindowPos(disconnect_button, NULL, button_rect.left + label_width_delta, button_rect.top, button_width + button_width_delta, button_height, SWP_NOZORDER)) { return false; } // Resize the whole window to fit the resized controls. RECT window_rect; if (!GetWindowRect(hwnd_, &window_rect)) return false; int width = (window_rect.right - window_rect.left) + button_width_delta + label_width_delta; int height = window_rect.bottom - window_rect.top; if (!SetWindowPos(hwnd_, NULL, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER)) { return false; } // Make the corners of the disconnect window rounded. HRGN rgn = CreateRoundRectRgn(0, 0, width, height, kWindowBorderRadius, kWindowBorderRadius); if (!rgn) return false; if (!SetWindowRgn(hwnd_, rgn, TRUE)) return false; return true; } } // namespace // static scoped_ptr HostWindow::CreateDisconnectWindow( const UiStrings& ui_strings) { return scoped_ptr(new DisconnectWindowWin(ui_strings)); } } // namespace remoting