disconnect_window_win.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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_util.h" 10#include "base/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/ui_strings.h" 18#include "remoting/host/win/core_resource.h" 19 20namespace remoting { 21 22namespace { 23 24const int DISCONNECT_HOTKEY_ID = 1000; 25 26// Maximum length of "Your desktop is shared with ..." message in UTF-16 27// characters. 28const size_t kMaxSharingWithTextLength = 100; 29 30const wchar_t kShellTrayWindowName[] = L"Shell_TrayWnd"; 31const int kWindowBorderRadius = 14; 32 33class DisconnectWindowWin : public HostWindow { 34 public: 35 explicit DisconnectWindowWin(const UiStrings& ui_strings); 36 virtual ~DisconnectWindowWin(); 37 38 // HostWindow overrides. 39 virtual void Start( 40 const base::WeakPtr<ClientSessionControl>& client_session_control) 41 OVERRIDE; 42 43 protected: 44 static INT_PTR CALLBACK DialogProc(HWND hwnd, UINT message, WPARAM wparam, 45 LPARAM lparam); 46 47 BOOL OnDialogMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); 48 49 // Creates the dialog window and registers the disconnect hot key. 50 bool BeginDialog(); 51 52 // Closes the dialog, unregisters the hot key and invokes the disconnect 53 // callback, if set. 54 void EndDialog(); 55 56 // Trys to position the dialog window above the taskbar. 57 void SetDialogPosition(); 58 59 // Applies localization string and resizes the dialog. 60 bool SetStrings(); 61 62 // Used to disconnect the client session. 63 base::WeakPtr<ClientSessionControl> client_session_control_; 64 65 // Localized UI strings. 66 UiStrings ui_strings_; 67 68 // Specifies the remote user name. 69 std::string username_; 70 71 HWND hwnd_; 72 bool has_hotkey_; 73 base::win::ScopedGDIObject<HPEN> border_pen_; 74 75 DISALLOW_COPY_AND_ASSIGN(DisconnectWindowWin); 76}; 77 78int GetControlTextWidth(HWND control) { 79 RECT rect = {0, 0, 0, 0}; 80 WCHAR text[256]; 81 int result = GetWindowText(control, text, arraysize(text)); 82 if (result) { 83 base::win::ScopedGetDC dc(control); 84 base::win::ScopedSelectObject font( 85 dc, (HFONT)SendMessage(control, WM_GETFONT, 0, 0)); 86 DrawText(dc, text, -1, &rect, DT_CALCRECT | DT_SINGLELINE); 87 } 88 return rect.right; 89} 90 91DisconnectWindowWin::DisconnectWindowWin(const UiStrings& ui_strings) 92 : ui_strings_(ui_strings), 93 hwnd_(NULL), 94 has_hotkey_(false), 95 border_pen_(CreatePen(PS_SOLID, 5, 96 RGB(0.13 * 255, 0.69 * 255, 0.11 * 255))) { 97} 98 99DisconnectWindowWin::~DisconnectWindowWin() { 100 EndDialog(); 101} 102 103void DisconnectWindowWin::Start( 104 const base::WeakPtr<ClientSessionControl>& client_session_control) { 105 DCHECK(CalledOnValidThread()); 106 DCHECK(!client_session_control_); 107 DCHECK(client_session_control); 108 109 client_session_control_ = client_session_control; 110 111 std::string client_jid = client_session_control_->client_jid(); 112 username_ = client_jid.substr(0, client_jid.find('/')); 113 if (!BeginDialog()) 114 EndDialog(); 115} 116 117INT_PTR CALLBACK DisconnectWindowWin::DialogProc(HWND hwnd, 118 UINT message, 119 WPARAM wparam, 120 LPARAM lparam) { 121 LONG_PTR self = NULL; 122 if (message == WM_INITDIALOG) { 123 self = lparam; 124 125 // Store |this| to the window's user data. 126 SetLastError(ERROR_SUCCESS); 127 LONG_PTR result = SetWindowLongPtr(hwnd, DWLP_USER, self); 128 if (result == 0 && GetLastError() != ERROR_SUCCESS) 129 reinterpret_cast<DisconnectWindowWin*>(self)->EndDialog(); 130 } else { 131 self = GetWindowLongPtr(hwnd, DWLP_USER); 132 } 133 134 if (self) { 135 return reinterpret_cast<DisconnectWindowWin*>(self)->OnDialogMessage( 136 hwnd, message, wparam, lparam); 137 } 138 return FALSE; 139} 140 141BOOL DisconnectWindowWin::OnDialogMessage(HWND hwnd, 142 UINT message, 143 WPARAM wparam, 144 LPARAM lparam) { 145 DCHECK(CalledOnValidThread()); 146 147 switch (message) { 148 // Ignore close messages. 149 case WM_CLOSE: 150 return TRUE; 151 152 // Handle the Disconnect button. 153 case WM_COMMAND: 154 switch (LOWORD(wparam)) { 155 case IDC_DISCONNECT: 156 EndDialog(); 157 return TRUE; 158 } 159 return FALSE; 160 161 // Ensure we don't try to use the HWND anymore. 162 case WM_DESTROY: 163 hwnd_ = NULL; 164 165 // Ensure that the disconnect callback is invoked even if somehow our 166 // window gets destroyed. 167 EndDialog(); 168 169 return TRUE; 170 171 // Ensure the dialog stays visible if the work area dimensions change. 172 case WM_SETTINGCHANGE: 173 if (wparam == SPI_SETWORKAREA) 174 SetDialogPosition(); 175 return TRUE; 176 177 // Ensure the dialog stays visible if the display dimensions change. 178 case WM_DISPLAYCHANGE: 179 SetDialogPosition(); 180 return TRUE; 181 182 // Handle the disconnect hot-key. 183 case WM_HOTKEY: 184 EndDialog(); 185 return TRUE; 186 187 // Let the window be draggable by its client area by responding 188 // that the entire window is the title bar. 189 case WM_NCHITTEST: 190 SetWindowLongPtr(hwnd, DWLP_MSGRESULT, HTCAPTION); 191 return TRUE; 192 193 case WM_PAINT: { 194 PAINTSTRUCT ps; 195 HDC hdc = BeginPaint(hwnd_, &ps); 196 RECT rect; 197 GetClientRect(hwnd_, &rect); 198 { 199 base::win::ScopedSelectObject border(hdc, border_pen_); 200 base::win::ScopedSelectObject brush(hdc, GetStockObject(NULL_BRUSH)); 201 RoundRect(hdc, rect.left, rect.top, rect.right - 1, rect.bottom - 1, 202 kWindowBorderRadius, kWindowBorderRadius); 203 } 204 EndPaint(hwnd_, &ps); 205 return TRUE; 206 } 207 } 208 return FALSE; 209} 210 211bool DisconnectWindowWin::BeginDialog() { 212 DCHECK(CalledOnValidThread()); 213 DCHECK(!hwnd_); 214 215 // Load the dialog resource so that we can modify the RTL flags if necessary. 216 HMODULE module = base::GetModuleFromAddress(&DialogProc); 217 HRSRC dialog_resource = 218 FindResource(module, MAKEINTRESOURCE(IDD_DISCONNECT), RT_DIALOG); 219 if (!dialog_resource) 220 return false; 221 222 HGLOBAL dialog_template = LoadResource(module, dialog_resource); 223 if (!dialog_template) 224 return false; 225 226 DLGTEMPLATE* dialog_pointer = 227 reinterpret_cast<DLGTEMPLATE*>(LockResource(dialog_template)); 228 if (!dialog_pointer) 229 return false; 230 231 // The actual resource type is DLGTEMPLATEEX, but this is not defined in any 232 // standard headers, so we treat it as a generic pointer and manipulate the 233 // correct offsets explicitly. 234 scoped_ptr<unsigned char[]> rtl_dialog_template; 235 if (ui_strings_.direction == UiStrings::RTL) { 236 unsigned long dialog_template_size = 237 SizeofResource(module, dialog_resource); 238 rtl_dialog_template.reset(new unsigned char[dialog_template_size]); 239 memcpy(rtl_dialog_template.get(), dialog_pointer, dialog_template_size); 240 DWORD* rtl_dwords = reinterpret_cast<DWORD*>(rtl_dialog_template.get()); 241 rtl_dwords[2] |= (WS_EX_LAYOUTRTL | WS_EX_RTLREADING); 242 dialog_pointer = reinterpret_cast<DLGTEMPLATE*>(rtl_dwords); 243 } 244 245 hwnd_ = CreateDialogIndirectParam(module, dialog_pointer, NULL, 246 DialogProc, reinterpret_cast<LPARAM>(this)); 247 if (!hwnd_) 248 return false; 249 250 // Set up handler for Ctrl-Alt-Esc shortcut. 251 if (!has_hotkey_ && RegisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID, 252 MOD_ALT | MOD_CONTROL, VK_ESCAPE)) { 253 has_hotkey_ = true; 254 } 255 256 if (!SetStrings()) 257 return false; 258 259 SetDialogPosition(); 260 ShowWindow(hwnd_, SW_SHOW); 261 return IsWindowVisible(hwnd_) != FALSE; 262} 263 264void DisconnectWindowWin::EndDialog() { 265 DCHECK(CalledOnValidThread()); 266 267 if (has_hotkey_) { 268 UnregisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID); 269 has_hotkey_ = false; 270 } 271 272 if (hwnd_) { 273 DestroyWindow(hwnd_); 274 hwnd_ = NULL; 275 } 276 277 if (client_session_control_) 278 client_session_control_->DisconnectSession(); 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 if (!SetWindowText(hwnd_, ui_strings_.product_name.c_str())) 305 return false; 306 307 // Localize the disconnect button text and measure length of the old and new 308 // labels. 309 HWND disconnect_button = GetDlgItem(hwnd_, IDC_DISCONNECT); 310 if (!disconnect_button) 311 return false; 312 int button_old_required_width = GetControlTextWidth(disconnect_button); 313 if (!SetWindowText(disconnect_button, 314 ui_strings_.disconnect_button_text.c_str())) { 315 return false; 316 } 317 int button_new_required_width = GetControlTextWidth(disconnect_button); 318 int button_width_delta = 319 button_new_required_width - button_old_required_width; 320 321 // Format and truncate "Your desktop is shared with ..." message. 322 string16 text = ReplaceStringPlaceholders(ui_strings_.disconnect_message, 323 UTF8ToUTF16(username_), NULL); 324 if (text.length() > kMaxSharingWithTextLength) 325 text.erase(kMaxSharingWithTextLength); 326 327 // Set localized and truncated "Your desktop is shared with ..." message and 328 // measure length of the old and new text. 329 HWND sharing_with_label = GetDlgItem(hwnd_, IDC_DISCONNECT_SHARINGWITH); 330 if (!sharing_with_label) 331 return false; 332 int label_old_required_width = GetControlTextWidth(sharing_with_label); 333 if (!SetWindowText(sharing_with_label, text.c_str())) 334 return false; 335 int label_new_required_width = GetControlTextWidth(sharing_with_label); 336 int label_width_delta = label_new_required_width - label_old_required_width; 337 338 // Reposition the controls such that the label lies to the left of the 339 // disconnect button (assuming LTR layout). The dialog template determines 340 // the controls' spacing; update their size to fit the localized content. 341 RECT label_rect; 342 if (!GetClientRect(sharing_with_label, &label_rect)) 343 return false; 344 if (!SetWindowPos(sharing_with_label, NULL, 0, 0, 345 label_rect.right + label_width_delta, label_rect.bottom, 346 SWP_NOMOVE | SWP_NOZORDER)) { 347 return false; 348 } 349 350 // Reposition the disconnect button as well. 351 RECT button_rect; 352 if (!GetWindowRect(disconnect_button, &button_rect)) 353 return false; 354 int button_width = button_rect.right - button_rect.left; 355 int button_height = button_rect.bottom - button_rect.top; 356 SetLastError(ERROR_SUCCESS); 357 int result = MapWindowPoints(HWND_DESKTOP, hwnd_, 358 reinterpret_cast<LPPOINT>(&button_rect), 2); 359 if (!result && GetLastError() != ERROR_SUCCESS) 360 return false; 361 if (!SetWindowPos(disconnect_button, NULL, 362 button_rect.left + label_width_delta, button_rect.top, 363 button_width + button_width_delta, button_height, 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 = (window_rect.right - window_rect.left) + 373 button_width_delta + label_width_delta; 374 int height = window_rect.bottom - window_rect.top; 375 if (!SetWindowPos(hwnd_, NULL, 0, 0, width, height, 376 SWP_NOMOVE | SWP_NOZORDER)) { 377 return false; 378 } 379 380 // Make the corners of the disconnect window rounded. 381 HRGN rgn = CreateRoundRectRgn(0, 0, width, height, kWindowBorderRadius, 382 kWindowBorderRadius); 383 if (!rgn) 384 return false; 385 if (!SetWindowRgn(hwnd_, rgn, TRUE)) 386 return false; 387 388 return true; 389} 390 391} // namespace 392 393// static 394scoped_ptr<HostWindow> HostWindow::CreateDisconnectWindow( 395 const UiStrings& ui_strings) { 396 return scoped_ptr<HostWindow>(new DisconnectWindowWin(ui_strings)); 397} 398 399} // namespace remoting 400