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 <windows.h> 6 7 #include "base/compiler_specific.h" 8 #include "base/logging.h" 9 #include "base/process/memory.h" 10 #include "base/strings/string_util.h" 11 #include "base/strings/utf_string_conversions.h" 12 #include "base/win/scoped_gdi_object.h" 13 #include "base/win/scoped_hdc.h" 14 #include "base/win/scoped_select_object.h" 15 #include "remoting/host/client_session_control.h" 16 #include "remoting/host/host_window.h" 17 #include "remoting/host/win/core_resource.h" 18 19 namespace remoting { 20 21 namespace { 22 23 const int DISCONNECT_HOTKEY_ID = 1000; 24 25 // Maximum length of "Your desktop is shared with ..." message in UTF-16 26 // characters. 27 const size_t kMaxSharingWithTextLength = 100; 28 29 const wchar_t kShellTrayWindowName[] = L"Shell_TrayWnd"; 30 const int kWindowBorderRadius = 14; 31 32 // Margin between dialog controls (in dialog units). 33 const int kWindowTextMargin = 8; 34 35 class DisconnectWindowWin : public HostWindow { 36 public: 37 DisconnectWindowWin(); 38 virtual ~DisconnectWindowWin(); 39 40 // HostWindow overrides. 41 virtual void Start( 42 const base::WeakPtr<ClientSessionControl>& client_session_control) 43 OVERRIDE; 44 45 protected: 46 static INT_PTR CALLBACK DialogProc(HWND hwnd, UINT message, WPARAM wparam, 47 LPARAM lparam); 48 49 BOOL OnDialogMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); 50 51 // Creates the dialog window and registers the disconnect hot key. 52 bool BeginDialog(); 53 54 // Closes the dialog, unregisters the hot key and invokes the disconnect 55 // callback, if set. 56 void EndDialog(); 57 58 // Returns |control| rectangle in the dialog coordinates. 59 bool GetControlRect(HWND control, RECT* rect); 60 61 // Trys to position the dialog window above the taskbar. 62 void SetDialogPosition(); 63 64 // Applies localization string and resizes the dialog. 65 bool SetStrings(); 66 67 // Used to disconnect the client session. 68 base::WeakPtr<ClientSessionControl> client_session_control_; 69 70 // Specifies the remote user name. 71 std::string username_; 72 73 HWND hwnd_; 74 bool has_hotkey_; 75 base::win::ScopedGDIObject<HPEN> border_pen_; 76 77 DISALLOW_COPY_AND_ASSIGN(DisconnectWindowWin); 78 }; 79 80 // Returns the text for the given dialog control window. 81 bool GetControlText(HWND control, string16* text) { 82 // GetWindowText truncates the text if it is longer than can fit into 83 // the buffer. 84 WCHAR buffer[256]; 85 int result = GetWindowText(control, buffer, arraysize(buffer)); 86 if (!result) 87 return false; 88 89 text->assign(buffer); 90 return true; 91 } 92 93 // Returns width |text| rendered in |control| window. 94 bool GetControlTextWidth(HWND control, const string16& text, LONG* width) { 95 RECT rect = {0, 0, 0, 0}; 96 base::win::ScopedGetDC dc(control); 97 base::win::ScopedSelectObject font( 98 dc, (HFONT)SendMessage(control, WM_GETFONT, 0, 0)); 99 if (!DrawText(dc, text.c_str(), -1, &rect, DT_CALCRECT | DT_SINGLELINE)) 100 return false; 101 102 *width = rect.right; 103 return true; 104 } 105 106 DisconnectWindowWin::DisconnectWindowWin() 107 : hwnd_(NULL), 108 has_hotkey_(false), 109 border_pen_(CreatePen(PS_SOLID, 5, 110 RGB(0.13 * 255, 0.69 * 255, 0.11 * 255))) { 111 } 112 113 DisconnectWindowWin::~DisconnectWindowWin() { 114 EndDialog(); 115 } 116 117 void DisconnectWindowWin::Start( 118 const base::WeakPtr<ClientSessionControl>& client_session_control) { 119 DCHECK(CalledOnValidThread()); 120 DCHECK(!client_session_control_); 121 DCHECK(client_session_control); 122 123 client_session_control_ = client_session_control; 124 125 std::string client_jid = client_session_control_->client_jid(); 126 username_ = client_jid.substr(0, client_jid.find('/')); 127 if (!BeginDialog()) 128 EndDialog(); 129 } 130 131 INT_PTR CALLBACK DisconnectWindowWin::DialogProc(HWND hwnd, 132 UINT message, 133 WPARAM wparam, 134 LPARAM lparam) { 135 LONG_PTR self = NULL; 136 if (message == WM_INITDIALOG) { 137 self = lparam; 138 139 // Store |this| to the window's user data. 140 SetLastError(ERROR_SUCCESS); 141 LONG_PTR result = SetWindowLongPtr(hwnd, DWLP_USER, self); 142 if (result == 0 && GetLastError() != ERROR_SUCCESS) 143 reinterpret_cast<DisconnectWindowWin*>(self)->EndDialog(); 144 } else { 145 self = GetWindowLongPtr(hwnd, DWLP_USER); 146 } 147 148 if (self) { 149 return reinterpret_cast<DisconnectWindowWin*>(self)->OnDialogMessage( 150 hwnd, message, wparam, lparam); 151 } 152 return FALSE; 153 } 154 155 BOOL DisconnectWindowWin::OnDialogMessage(HWND hwnd, 156 UINT message, 157 WPARAM wparam, 158 LPARAM lparam) { 159 DCHECK(CalledOnValidThread()); 160 161 switch (message) { 162 // Ignore close messages. 163 case WM_CLOSE: 164 return TRUE; 165 166 // Handle the Disconnect button. 167 case WM_COMMAND: 168 switch (LOWORD(wparam)) { 169 case IDC_DISCONNECT: 170 EndDialog(); 171 return TRUE; 172 } 173 return FALSE; 174 175 // Ensure we don't try to use the HWND anymore. 176 case WM_DESTROY: 177 hwnd_ = NULL; 178 179 // Ensure that the disconnect callback is invoked even if somehow our 180 // window gets destroyed. 181 EndDialog(); 182 183 return TRUE; 184 185 // Ensure the dialog stays visible if the work area dimensions change. 186 case WM_SETTINGCHANGE: 187 if (wparam == SPI_SETWORKAREA) 188 SetDialogPosition(); 189 return TRUE; 190 191 // Ensure the dialog stays visible if the display dimensions change. 192 case WM_DISPLAYCHANGE: 193 SetDialogPosition(); 194 return TRUE; 195 196 // Handle the disconnect hot-key. 197 case WM_HOTKEY: 198 EndDialog(); 199 return TRUE; 200 201 // Let the window be draggable by its client area by responding 202 // that the entire window is the title bar. 203 case WM_NCHITTEST: 204 SetWindowLongPtr(hwnd, DWLP_MSGRESULT, HTCAPTION); 205 return TRUE; 206 207 case WM_PAINT: { 208 PAINTSTRUCT ps; 209 HDC hdc = BeginPaint(hwnd_, &ps); 210 RECT rect; 211 GetClientRect(hwnd_, &rect); 212 { 213 base::win::ScopedSelectObject border(hdc, border_pen_); 214 base::win::ScopedSelectObject brush(hdc, GetStockObject(NULL_BRUSH)); 215 RoundRect(hdc, rect.left, rect.top, rect.right - 1, rect.bottom - 1, 216 kWindowBorderRadius, kWindowBorderRadius); 217 } 218 EndPaint(hwnd_, &ps); 219 return TRUE; 220 } 221 } 222 return FALSE; 223 } 224 225 bool DisconnectWindowWin::BeginDialog() { 226 DCHECK(CalledOnValidThread()); 227 DCHECK(!hwnd_); 228 229 HMODULE module = base::GetModuleFromAddress(&DialogProc); 230 hwnd_ = CreateDialogParam(module, MAKEINTRESOURCE(IDD_DISCONNECT), NULL, 231 DialogProc, reinterpret_cast<LPARAM>(this)); 232 if (!hwnd_) 233 return false; 234 235 // Set up handler for Ctrl-Alt-Esc shortcut. 236 if (!has_hotkey_ && RegisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID, 237 MOD_ALT | MOD_CONTROL, VK_ESCAPE)) { 238 has_hotkey_ = true; 239 } 240 241 if (!SetStrings()) 242 return false; 243 244 SetDialogPosition(); 245 ShowWindow(hwnd_, SW_SHOW); 246 return IsWindowVisible(hwnd_) != FALSE; 247 } 248 249 void DisconnectWindowWin::EndDialog() { 250 DCHECK(CalledOnValidThread()); 251 252 if (has_hotkey_) { 253 UnregisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID); 254 has_hotkey_ = false; 255 } 256 257 if (hwnd_) { 258 DestroyWindow(hwnd_); 259 hwnd_ = NULL; 260 } 261 262 if (client_session_control_) 263 client_session_control_->DisconnectSession(); 264 } 265 266 // Returns |control| rectangle in the dialog coordinates. 267 bool DisconnectWindowWin::GetControlRect(HWND control, RECT* rect) { 268 if (!GetWindowRect(control, rect)) 269 return false; 270 SetLastError(ERROR_SUCCESS); 271 int result = MapWindowPoints(HWND_DESKTOP, hwnd_, 272 reinterpret_cast<LPPOINT>(rect), 2); 273 if (!result && GetLastError() != ERROR_SUCCESS) 274 return false; 275 276 return true; 277 } 278 279 void DisconnectWindowWin::SetDialogPosition() { 280 DCHECK(CalledOnValidThread()); 281 282 // Try to center the window above the task-bar. If that fails, use the 283 // primary monitor. If that fails (very unlikely), use the default position. 284 HWND taskbar = FindWindow(kShellTrayWindowName, NULL); 285 HMONITOR monitor = MonitorFromWindow(taskbar, MONITOR_DEFAULTTOPRIMARY); 286 MONITORINFO monitor_info = {sizeof(monitor_info)}; 287 RECT window_rect; 288 if (GetMonitorInfo(monitor, &monitor_info) && 289 GetWindowRect(hwnd_, &window_rect)) { 290 int window_width = window_rect.right - window_rect.left; 291 int window_height = window_rect.bottom - window_rect.top; 292 int top = monitor_info.rcWork.bottom - window_height; 293 int left = (monitor_info.rcWork.right + monitor_info.rcWork.left - 294 window_width) / 2; 295 SetWindowPos(hwnd_, NULL, left, top, 0, 0, SWP_NOSIZE | SWP_NOZORDER); 296 } 297 } 298 299 bool DisconnectWindowWin::SetStrings() { 300 DCHECK(CalledOnValidThread()); 301 302 // Localize the disconnect button text and measure length of the old and new 303 // labels. 304 HWND hwnd_button = GetDlgItem(hwnd_, IDC_DISCONNECT); 305 HWND hwnd_message = GetDlgItem(hwnd_, IDC_DISCONNECT_SHARINGWITH); 306 if (!hwnd_button || !hwnd_message) 307 return false; 308 309 string16 button_text; 310 string16 message_text; 311 if (!GetControlText(hwnd_button, &button_text) || 312 !GetControlText(hwnd_message, &message_text)) { 313 return false; 314 } 315 316 // Format and truncate "Your desktop is shared with ..." message. 317 message_text = ReplaceStringPlaceholders(message_text, UTF8ToUTF16(username_), 318 NULL); 319 if (message_text.length() > kMaxSharingWithTextLength) 320 message_text.erase(kMaxSharingWithTextLength); 321 322 if (!SetWindowText(hwnd_message, message_text.c_str())) 323 return false; 324 325 // Calculate the margin between controls in pixels. 326 RECT rect = {0}; 327 rect.right = kWindowTextMargin; 328 if (!MapDialogRect(hwnd_, &rect)) 329 return false; 330 int margin = rect.right; 331 332 // Resize |hwnd_message| so that the text is not clipped. 333 RECT message_rect; 334 if (!GetControlRect(hwnd_message, &message_rect)) 335 return false; 336 337 LONG control_width; 338 if (!GetControlTextWidth(hwnd_message, message_text, &control_width)) 339 return false; 340 message_rect.right = message_rect.left + control_width + margin; 341 342 if (!SetWindowPos(hwnd_message, NULL, 343 message_rect.left, message_rect.top, 344 message_rect.right - message_rect.left, 345 message_rect.bottom - message_rect.top, 346 SWP_NOZORDER)) { 347 return false; 348 } 349 350 // Reposition and resize |hwnd_button| as well. 351 RECT button_rect; 352 if (!GetControlRect(hwnd_button, &button_rect)) 353 return false; 354 355 if (!GetControlTextWidth(hwnd_button, button_text, &control_width)) 356 return false; 357 358 button_rect.left = message_rect.right; 359 button_rect.right = button_rect.left + control_width + margin * 2; 360 if (!SetWindowPos(hwnd_button, NULL, 361 button_rect.left, button_rect.top, 362 button_rect.right - button_rect.left, 363 button_rect.bottom - button_rect.top, 364 SWP_NOZORDER)) { 365 return false; 366 } 367 368 // Resize the whole window to fit the resized controls. 369 RECT window_rect; 370 if (!GetWindowRect(hwnd_, &window_rect)) 371 return false; 372 int width = button_rect.right + margin; 373 int height = window_rect.bottom - window_rect.top; 374 if (!SetWindowPos(hwnd_, NULL, 0, 0, width, height, 375 SWP_NOMOVE | SWP_NOZORDER)) { 376 return false; 377 } 378 379 // Make the corners of the disconnect window rounded. 380 HRGN rgn = CreateRoundRectRgn(0, 0, width, height, kWindowBorderRadius, 381 kWindowBorderRadius); 382 if (!rgn) 383 return false; 384 if (!SetWindowRgn(hwnd_, rgn, TRUE)) 385 return false; 386 387 return true; 388 } 389 390 } // namespace 391 392 // static 393 scoped_ptr<HostWindow> HostWindow::CreateDisconnectWindow() { 394 return scoped_ptr<HostWindow>(new DisconnectWindowWin()); 395 } 396 397 } // namespace remoting 398