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