disconnect_window_win.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "remoting/host/disconnect_window.h" 6 7#include <windows.h> 8 9#include "base/compiler_specific.h" 10#include "base/logging.h" 11#include "base/string_util.h" 12#include "base/utf_string_conversions.h" 13#include "base/win/scoped_gdi_object.h" 14#include "base/win/scoped_hdc.h" 15#include "base/win/scoped_select_object.h" 16#include "remoting/host/chromoting_host.h" 17#include "remoting/host/host_ui_resource.h" 18#include "remoting/host/ui_strings.h" 19 20// TODO(garykac): Lots of duplicated code in this file and 21// continue_window_win.cc. If we need to expand this then we should 22// create a class with the shared code. 23 24// HMODULE from DllMain/WinMain. This is needed to find our dialog resource. 25// This is defined in: 26// Plugin: host_plugin.cc 27// SimpleHost: simple_host_process.cc 28extern HMODULE g_hModule; 29 30namespace { 31 32const int DISCONNECT_HOTKEY_ID = 1000; 33const int kWindowBorderRadius = 14; 34const wchar_t kShellTrayWindowName[] = L"Shell_TrayWnd"; 35 36} // namespace anonymous 37 38namespace remoting { 39 40class DisconnectWindowWin : public DisconnectWindow { 41 public: 42 DisconnectWindowWin(); 43 virtual ~DisconnectWindowWin(); 44 45 virtual void Show(ChromotingHost* host, 46 const DisconnectCallback& disconnect_callback, 47 const std::string& username) OVERRIDE; 48 virtual void Hide() OVERRIDE; 49 50private: 51 static BOOL CALLBACK DialogProc(HWND hwmd, UINT msg, WPARAM wParam, 52 LPARAM lParam); 53 54 BOOL OnDialogMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); 55 56 void ShutdownHost(); 57 void EndDialog(); 58 void SetStrings(const UiStrings& strings, const std::string& username); 59 void SetDialogPosition(); 60 61 DisconnectCallback disconnect_callback_; 62 HWND hwnd_; 63 bool has_hotkey_; 64 base::win::ScopedGDIObject<HPEN> border_pen_; 65 66 DISALLOW_COPY_AND_ASSIGN(DisconnectWindowWin); 67}; 68 69DisconnectWindowWin::DisconnectWindowWin() 70 : hwnd_(NULL), 71 has_hotkey_(false), 72 border_pen_(CreatePen(PS_SOLID, 5, 73 RGB(0.13 * 255, 0.69 * 255, 0.11 * 255))) { 74} 75 76DisconnectWindowWin::~DisconnectWindowWin() { 77 EndDialog(); 78} 79 80BOOL CALLBACK DisconnectWindowWin::DialogProc(HWND hwnd, UINT msg, 81 WPARAM wParam, LPARAM lParam) { 82 DisconnectWindowWin* win = NULL; 83 if (msg == WM_INITDIALOG) { 84 win = reinterpret_cast<DisconnectWindowWin*>(lParam); 85 CHECK(win); 86 SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)win); 87 } else { 88 LONG_PTR lp = GetWindowLongPtr(hwnd, DWLP_USER); 89 win = reinterpret_cast<DisconnectWindowWin*>(lp); 90 } 91 if (win == NULL) 92 return FALSE; 93 return win->OnDialogMessage(hwnd, msg, wParam, lParam); 94} 95 96BOOL DisconnectWindowWin::OnDialogMessage(HWND hwnd, UINT msg, 97 WPARAM wParam, LPARAM lParam) { 98 switch (msg) { 99 // Ignore close messages. 100 case WM_CLOSE: 101 return TRUE; 102 103 // Handle the Disconnect button. 104 case WM_COMMAND: 105 switch (LOWORD(wParam)) { 106 case IDC_DISCONNECT: 107 EndDialog(); 108 ShutdownHost(); 109 return TRUE; 110 } 111 return FALSE; 112 113 // Ensure we don't try to use the HWND anymore. 114 case WM_DESTROY: 115 hwnd_ = NULL; 116 return TRUE; 117 118 // Ensure the dialog stays visible if the work area dimensions change. 119 case WM_SETTINGCHANGE: 120 if (wParam == SPI_SETWORKAREA) 121 SetDialogPosition(); 122 return TRUE; 123 124 // Ensure the dialog stays visible if the display dimensions change. 125 case WM_DISPLAYCHANGE: 126 SetDialogPosition(); 127 return TRUE; 128 129 // Handle the disconnect hot-key. 130 case WM_HOTKEY: 131 EndDialog(); 132 ShutdownHost(); 133 return TRUE; 134 135 // Let the window be draggable by its client area by responding 136 // that the entire window is the title bar. 137 case WM_NCHITTEST: 138 SetWindowLong(hwnd, DWL_MSGRESULT, HTCAPTION); 139 return TRUE; 140 141 case WM_PAINT: { 142 PAINTSTRUCT ps; 143 HDC hdc = BeginPaint(hwnd_, &ps); 144 RECT rect; 145 GetClientRect(hwnd_, &rect); 146 { 147 base::win::ScopedSelectObject border(hdc, border_pen_); 148 base::win::ScopedSelectObject brush(hdc, GetStockObject(NULL_BRUSH)); 149 RoundRect(hdc, rect.left, rect.top, rect.right - 1, rect.bottom - 1, 150 kWindowBorderRadius, kWindowBorderRadius); 151 } 152 EndPaint(hwnd_, &ps); 153 return TRUE; 154 } 155 } 156 return FALSE; 157} 158 159void DisconnectWindowWin::Show(ChromotingHost* host, 160 const DisconnectCallback& disconnect_callback, 161 const std::string& username) { 162 CHECK(!hwnd_); 163 disconnect_callback_ = disconnect_callback; 164 165 // Load the dialog resource so that we can modify the RTL flags if necessary. 166 // This is taken from chrome/default_plugin/install_dialog.cc 167 HRSRC dialog_resource = 168 FindResource(g_hModule, MAKEINTRESOURCE(IDD_DISCONNECT), RT_DIALOG); 169 CHECK(dialog_resource); 170 HGLOBAL dialog_template = LoadResource(g_hModule, dialog_resource); 171 CHECK(dialog_template); 172 DLGTEMPLATE* dialog_pointer = 173 reinterpret_cast<DLGTEMPLATE*>(LockResource(dialog_template)); 174 CHECK(dialog_pointer); 175 176 // The actual resource type is DLGTEMPLATEEX, but this is not defined in any 177 // standard headers, so we treat it as a generic pointer and manipulate the 178 // correct offsets explicitly. 179 scoped_ptr<unsigned char> rtl_dialog_template; 180 if (host->ui_strings().direction == UiStrings::RTL) { 181 unsigned long dialog_template_size = 182 SizeofResource(g_hModule, dialog_resource); 183 rtl_dialog_template.reset(new unsigned char[dialog_template_size]); 184 memcpy(rtl_dialog_template.get(), dialog_pointer, dialog_template_size); 185 DWORD* rtl_dwords = reinterpret_cast<DWORD*>(rtl_dialog_template.get()); 186 rtl_dwords[2] |= (WS_EX_LAYOUTRTL | WS_EX_RTLREADING); 187 dialog_pointer = reinterpret_cast<DLGTEMPLATE*>(rtl_dwords); 188 } 189 190 hwnd_ = CreateDialogIndirectParam(g_hModule, dialog_pointer, NULL, 191 (DLGPROC)DialogProc, (LPARAM)this); 192 CHECK(hwnd_); 193 194 // Set up handler for Ctrl-Alt-Esc shortcut. 195 if (!has_hotkey_ && RegisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID, 196 MOD_ALT | MOD_CONTROL, VK_ESCAPE)) { 197 has_hotkey_ = true; 198 } 199 200 SetStrings(host->ui_strings(), username); 201 SetDialogPosition(); 202 ShowWindow(hwnd_, SW_SHOW); 203} 204 205void DisconnectWindowWin::ShutdownHost() { 206 CHECK(!disconnect_callback_.is_null()); 207 disconnect_callback_.Run(); 208} 209 210static int GetControlTextWidth(HWND control) { 211 RECT rect = {0, 0, 0, 0}; 212 WCHAR text[256]; 213 int result = GetWindowText(control, text, arraysize(text)); 214 if (result) { 215 base::win::ScopedGetDC dc(control); 216 base::win::ScopedSelectObject font( 217 dc, (HFONT)SendMessage(control, WM_GETFONT, 0, 0)); 218 DrawText(dc, text, -1, &rect, DT_CALCRECT|DT_SINGLELINE); 219 } 220 return rect.right; 221} 222 223void DisconnectWindowWin::SetStrings(const UiStrings& strings, 224 const std::string& username) { 225 SetWindowText(hwnd_, strings.product_name.c_str()); 226 227 HWND hwndButton = GetDlgItem(hwnd_, IDC_DISCONNECT); 228 CHECK(hwndButton); 229 const WCHAR* label = 230 has_hotkey_ ? strings.disconnect_button_text_plus_shortcut.c_str() 231 : strings.disconnect_button_text.c_str(); 232 int button_old_required_width = GetControlTextWidth(hwndButton); 233 SetWindowText(hwndButton, label); 234 int button_new_required_width = GetControlTextWidth(hwndButton); 235 236 HWND hwndSharingWith = GetDlgItem(hwnd_, IDC_DISCONNECT_SHARINGWITH); 237 CHECK(hwndSharingWith); 238 string16 text = ReplaceStringPlaceholders( 239 strings.disconnect_message, UTF8ToUTF16(username), NULL); 240 int label_old_required_width = GetControlTextWidth(hwndSharingWith); 241 SetWindowText(hwndSharingWith, text.c_str()); 242 int label_new_required_width = GetControlTextWidth(hwndSharingWith); 243 244 int label_width_delta = label_new_required_width - label_old_required_width; 245 int button_width_delta = 246 button_new_required_width - button_old_required_width; 247 248 // Reposition the controls such that the label lies to the left of the 249 // disconnect button (assuming LTR layout). The dialog template determines 250 // the controls' spacing; update their size to fit the localized content. 251 RECT label_rect; 252 GetClientRect(hwndSharingWith, &label_rect); 253 SetWindowPos(hwndSharingWith, NULL, 0, 0, 254 label_rect.right + label_width_delta, label_rect.bottom, 255 SWP_NOMOVE|SWP_NOZORDER); 256 257 RECT button_rect; 258 GetWindowRect(hwndButton, &button_rect); 259 int button_width = button_rect.right - button_rect.left; 260 int button_height = button_rect.bottom - button_rect.top; 261 MapWindowPoints(NULL, hwnd_, reinterpret_cast<LPPOINT>(&button_rect), 2); 262 SetWindowPos(hwndButton, NULL, 263 button_rect.left + label_width_delta, button_rect.top, 264 button_width + button_width_delta, button_height, SWP_NOZORDER); 265 266 RECT window_rect; 267 GetWindowRect(hwnd_, &window_rect); 268 int width = (window_rect.right - window_rect.left) + 269 button_width_delta + label_width_delta; 270 int height = window_rect.bottom - window_rect.top; 271 SetWindowPos(hwnd_, NULL, 0, 0, width, height, SWP_NOMOVE|SWP_NOZORDER); 272 HRGN rgn = CreateRoundRectRgn(0, 0, width, height, kWindowBorderRadius, 273 kWindowBorderRadius); 274 SetWindowRgn(hwnd_, rgn, TRUE); 275} 276 277void DisconnectWindowWin::SetDialogPosition() { 278 // Try to center the window above the task-bar. If that fails, use the 279 // primary monitor. If that fails (very unlikely), use the default position. 280 HWND taskbar = FindWindow(kShellTrayWindowName, NULL); 281 HMONITOR monitor = MonitorFromWindow(taskbar, MONITOR_DEFAULTTOPRIMARY); 282 MONITORINFO monitor_info = {sizeof(monitor_info)}; 283 RECT window_rect; 284 if (GetMonitorInfo(monitor, &monitor_info) && 285 GetWindowRect(hwnd_, &window_rect)) { 286 int window_width = window_rect.right - window_rect.left; 287 int window_height = window_rect.bottom - window_rect.top; 288 int top = monitor_info.rcWork.bottom - window_height; 289 int left = (monitor_info.rcWork.right + monitor_info.rcWork.left - 290 window_width) / 2; 291 SetWindowPos(hwnd_, NULL, left, top, 0, 0, SWP_NOSIZE | SWP_NOZORDER); 292 } 293} 294 295void DisconnectWindowWin::Hide() { 296 EndDialog(); 297} 298 299void DisconnectWindowWin::EndDialog() { 300 if (has_hotkey_) { 301 UnregisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID); 302 has_hotkey_ = false; 303 } 304 305 if (hwnd_) { 306 ::DestroyWindow(hwnd_); 307 hwnd_ = NULL; 308 } 309} 310 311scoped_ptr<DisconnectWindow> DisconnectWindow::Create() { 312 return scoped_ptr<DisconnectWindow>(new DisconnectWindowWin()); 313} 314 315} // namespace remoting 316