// 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/memory.h" #include "base/strings/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/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; // Margin between dialog controls (in dialog units). const int kWindowTextMargin = 8; class DisconnectWindowWin : public HostWindow { public: DisconnectWindowWin(); ~DisconnectWindowWin() override; // HostWindow overrides. 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(); // Returns |control| rectangle in the dialog coordinates. bool GetControlRect(HWND control, RECT* rect); // 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_; // Specifies the remote user name. std::string username_; HWND hwnd_; bool has_hotkey_; base::win::ScopedGDIObject border_pen_; DISALLOW_COPY_AND_ASSIGN(DisconnectWindowWin); }; // Returns the text for the given dialog control window. bool GetControlText(HWND control, base::string16* text) { // GetWindowText truncates the text if it is longer than can fit into // the buffer. WCHAR buffer[256]; int result = GetWindowText(control, buffer, arraysize(buffer)); if (!result) return false; text->assign(buffer); return true; } // Returns width |text| rendered in |control| window. bool GetControlTextWidth(HWND control, const base::string16& text, LONG* width) { RECT rect = {0, 0, 0, 0}; base::win::ScopedGetDC dc(control); base::win::ScopedSelectObject font( dc, (HFONT)SendMessage(control, WM_GETFONT, 0, 0)); if (!DrawText(dc, text.c_str(), -1, &rect, DT_CALCRECT | DT_SINGLELINE)) return false; *width = rect.right; return true; } DisconnectWindowWin::DisconnectWindowWin() : hwnd_(nullptr), 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 = 0; 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_ = nullptr; // 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_); HMODULE module = base::GetModuleFromAddress(&DialogProc); hwnd_ = CreateDialogParam(module, MAKEINTRESOURCE(IDD_DISCONNECT), nullptr, 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_ = nullptr; } if (client_session_control_) client_session_control_->DisconnectSession(); } // Returns |control| rectangle in the dialog coordinates. bool DisconnectWindowWin::GetControlRect(HWND control, RECT* rect) { if (!GetWindowRect(control, rect)) return false; SetLastError(ERROR_SUCCESS); int result = MapWindowPoints(HWND_DESKTOP, hwnd_, reinterpret_cast(rect), 2); if (!result && GetLastError() != ERROR_SUCCESS) return false; return true; } 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, nullptr); 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_, nullptr, left, top, 0, 0, SWP_NOSIZE | SWP_NOZORDER); } } bool DisconnectWindowWin::SetStrings() { DCHECK(CalledOnValidThread()); // Localize the disconnect button text and measure length of the old and new // labels. HWND hwnd_button = GetDlgItem(hwnd_, IDC_DISCONNECT); HWND hwnd_message = GetDlgItem(hwnd_, IDC_DISCONNECT_SHARINGWITH); if (!hwnd_button || !hwnd_message) return false; base::string16 button_text; base::string16 message_text; if (!GetControlText(hwnd_button, &button_text) || !GetControlText(hwnd_message, &message_text)) { return false; } // Format and truncate "Your desktop is shared with ..." message. message_text = ReplaceStringPlaceholders(message_text, base::UTF8ToUTF16(username_), nullptr); if (message_text.length() > kMaxSharingWithTextLength) message_text.erase(kMaxSharingWithTextLength); if (!SetWindowText(hwnd_message, message_text.c_str())) return false; // Calculate the margin between controls in pixels. RECT rect = {0}; rect.right = kWindowTextMargin; if (!MapDialogRect(hwnd_, &rect)) return false; int margin = rect.right; // Resize |hwnd_message| so that the text is not clipped. RECT message_rect; if (!GetControlRect(hwnd_message, &message_rect)) return false; LONG control_width; if (!GetControlTextWidth(hwnd_message, message_text, &control_width)) return false; message_rect.right = message_rect.left + control_width + margin; if (!SetWindowPos(hwnd_message, nullptr, message_rect.left, message_rect.top, message_rect.right - message_rect.left, message_rect.bottom - message_rect.top, SWP_NOZORDER)) { return false; } // Reposition and resize |hwnd_button| as well. RECT button_rect; if (!GetControlRect(hwnd_button, &button_rect)) return false; if (!GetControlTextWidth(hwnd_button, button_text, &control_width)) return false; button_rect.left = message_rect.right; button_rect.right = button_rect.left + control_width + margin * 2; if (!SetWindowPos(hwnd_button, nullptr, button_rect.left, button_rect.top, button_rect.right - button_rect.left, button_rect.bottom - button_rect.top, 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 = button_rect.right + margin; int height = window_rect.bottom - window_rect.top; if (!SetWindowPos(hwnd_, nullptr, 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() { return make_scoped_ptr(new DisconnectWindowWin()); } } // namespace remoting