Revert "Merged all Chromoting Host code into remoting_core.dll (Windows)."
[chromium-blink-merge.git] / remoting / host / disconnect_window_win.cc
blobff41c675a161dd6d62cc48c5d109fe8dd366a8e6
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.
5 #include "remoting/host/disconnect_window.h"
7 #include <windows.h>
9 #include "base/compiler_specific.h"
10 #include "base/logging.h"
11 #include "base/process_util.h"
12 #include "base/string_util.h"
13 #include "base/utf_string_conversions.h"
14 #include "base/win/scoped_gdi_object.h"
15 #include "base/win/scoped_hdc.h"
16 #include "base/win/scoped_select_object.h"
17 #include "remoting/host/host_ui_resource.h"
18 #include "remoting/host/ui_strings.h"
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.
24 namespace {
26 const int DISCONNECT_HOTKEY_ID = 1000;
28 // Maximum length of "Your desktop is shared with ..." message in UTF-16
29 // characters.
30 const size_t kMaxSharingWithTextLength = 100;
32 const wchar_t kShellTrayWindowName[] = L"Shell_TrayWnd";
33 const int kWindowBorderRadius = 14;
35 } // namespace anonymous
37 namespace remoting {
39 class DisconnectWindowWin : public DisconnectWindow {
40 public:
41 explicit DisconnectWindowWin(const UiStrings* ui_strings);
42 virtual ~DisconnectWindowWin();
44 // DisconnectWindow interface.
45 virtual bool Show(const base::Closure& disconnect_callback,
46 const std::string& username) OVERRIDE;
47 virtual void Hide() OVERRIDE;
49 private:
50 static BOOL CALLBACK DialogProc(HWND hwnd, UINT message, WPARAM wparam,
51 LPARAM lparam);
53 BOOL OnDialogMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
55 // Creates the dialog window and registers the disconnect hot key.
56 bool BeginDialog(const std::string& username);
58 // Closes the dialog, unregisters the hot key and invokes the disconnect
59 // callback, if set.
60 void EndDialog();
62 // Trys to position the dialog window above the taskbar.
63 void SetDialogPosition();
65 // Applies localization string and resizes the dialog.
66 bool SetStrings(const string16& username);
68 base::Closure disconnect_callback_;
69 HWND hwnd_;
70 bool has_hotkey_;
71 base::win::ScopedGDIObject<HPEN> border_pen_;
73 // Points to the localized strings.
74 const UiStrings* ui_strings_;
76 DISALLOW_COPY_AND_ASSIGN(DisconnectWindowWin);
79 static int GetControlTextWidth(HWND control) {
80 RECT rect = {0, 0, 0, 0};
81 WCHAR text[256];
82 int result = GetWindowText(control, text, arraysize(text));
83 if (result) {
84 base::win::ScopedGetDC dc(control);
85 base::win::ScopedSelectObject font(
86 dc, (HFONT)SendMessage(control, WM_GETFONT, 0, 0));
87 DrawText(dc, text, -1, &rect, DT_CALCRECT | DT_SINGLELINE);
89 return rect.right;
92 DisconnectWindowWin::DisconnectWindowWin(const UiStrings* 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 ui_strings_(ui_strings) {
100 DisconnectWindowWin::~DisconnectWindowWin() {
101 Hide();
104 bool DisconnectWindowWin::Show(const base::Closure& disconnect_callback,
105 const std::string& username) {
106 DCHECK(disconnect_callback_.is_null());
107 DCHECK(!disconnect_callback.is_null());
109 disconnect_callback_ = disconnect_callback;
111 if (BeginDialog(username)) {
112 return true;
113 } else {
114 Hide();
115 return false;
119 void DisconnectWindowWin::Hide() {
120 // Clear the |disconnect_callback_| so it won't be invoked by EndDialog().
121 disconnect_callback_.Reset();
122 EndDialog();
125 BOOL CALLBACK DisconnectWindowWin::DialogProc(HWND hwnd, UINT message,
126 WPARAM wparam, LPARAM lparam) {
127 LONG_PTR self = NULL;
128 if (message == WM_INITDIALOG) {
129 self = lparam;
131 // Store |this| to the window's user data.
132 SetLastError(ERROR_SUCCESS);
133 LONG_PTR result = SetWindowLongPtr(hwnd, DWLP_USER, self);
134 if (result == 0 && GetLastError() != ERROR_SUCCESS)
135 reinterpret_cast<DisconnectWindowWin*>(self)->EndDialog();
136 } else {
137 self = GetWindowLongPtr(hwnd, DWLP_USER);
140 if (self) {
141 return reinterpret_cast<DisconnectWindowWin*>(self)->OnDialogMessage(
142 hwnd, message, wparam, lparam);
144 return FALSE;
147 BOOL DisconnectWindowWin::OnDialogMessage(HWND hwnd, UINT message,
148 WPARAM wparam, LPARAM lparam) {
149 switch (message) {
150 // Ignore close messages.
151 case WM_CLOSE:
152 return TRUE;
154 // Handle the Disconnect button.
155 case WM_COMMAND:
156 switch (LOWORD(wparam)) {
157 case IDC_DISCONNECT:
158 EndDialog();
159 return TRUE;
161 return FALSE;
163 // Ensure we don't try to use the HWND anymore.
164 case WM_DESTROY:
165 hwnd_ = NULL;
167 // Ensure that the disconnect callback is invoked even if somehow our
168 // window gets destroyed.
169 EndDialog();
171 return TRUE;
173 // Ensure the dialog stays visible if the work area dimensions change.
174 case WM_SETTINGCHANGE:
175 if (wparam == SPI_SETWORKAREA)
176 SetDialogPosition();
177 return TRUE;
179 // Ensure the dialog stays visible if the display dimensions change.
180 case WM_DISPLAYCHANGE:
181 SetDialogPosition();
182 return TRUE;
184 // Handle the disconnect hot-key.
185 case WM_HOTKEY:
186 EndDialog();
187 return TRUE;
189 // Let the window be draggable by its client area by responding
190 // that the entire window is the title bar.
191 case WM_NCHITTEST:
192 SetWindowLong(hwnd, DWL_MSGRESULT, HTCAPTION);
193 return TRUE;
195 case WM_PAINT: {
196 PAINTSTRUCT ps;
197 HDC hdc = BeginPaint(hwnd_, &ps);
198 RECT rect;
199 GetClientRect(hwnd_, &rect);
201 base::win::ScopedSelectObject border(hdc, border_pen_);
202 base::win::ScopedSelectObject brush(hdc, GetStockObject(NULL_BRUSH));
203 RoundRect(hdc, rect.left, rect.top, rect.right - 1, rect.bottom - 1,
204 kWindowBorderRadius, kWindowBorderRadius);
206 EndPaint(hwnd_, &ps);
207 return TRUE;
210 return FALSE;
213 bool DisconnectWindowWin::BeginDialog(const std::string& username) {
214 DCHECK(!hwnd_);
216 // Load the dialog resource so that we can modify the RTL flags if necessary.
217 HMODULE module = base::GetModuleFromAddress(&DialogProc);
218 HRSRC dialog_resource =
219 FindResource(module, MAKEINTRESOURCE(IDD_DISCONNECT), RT_DIALOG);
220 if (!dialog_resource)
221 return false;
223 HGLOBAL dialog_template = LoadResource(module, dialog_resource);
224 if (!dialog_template)
225 return false;
227 DLGTEMPLATE* dialog_pointer =
228 reinterpret_cast<DLGTEMPLATE*>(LockResource(dialog_template));
229 if (!dialog_pointer)
230 return false;
232 // The actual resource type is DLGTEMPLATEEX, but this is not defined in any
233 // standard headers, so we treat it as a generic pointer and manipulate the
234 // correct offsets explicitly.
235 scoped_array<unsigned char> rtl_dialog_template;
236 if (ui_strings_->direction == UiStrings::RTL) {
237 unsigned long dialog_template_size =
238 SizeofResource(module, dialog_resource);
239 rtl_dialog_template.reset(new unsigned char[dialog_template_size]);
240 memcpy(rtl_dialog_template.get(), dialog_pointer, dialog_template_size);
241 DWORD* rtl_dwords = reinterpret_cast<DWORD*>(rtl_dialog_template.get());
242 rtl_dwords[2] |= (WS_EX_LAYOUTRTL | WS_EX_RTLREADING);
243 dialog_pointer = reinterpret_cast<DLGTEMPLATE*>(rtl_dwords);
246 hwnd_ = CreateDialogIndirectParam(module, dialog_pointer, NULL,
247 DialogProc, reinterpret_cast<LPARAM>(this));
248 if (!hwnd_)
249 return false;
251 // Set up handler for Ctrl-Alt-Esc shortcut.
252 if (!has_hotkey_ && RegisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID,
253 MOD_ALT | MOD_CONTROL, VK_ESCAPE)) {
254 has_hotkey_ = true;
257 if (!SetStrings(UTF8ToUTF16(username)))
258 return false;
260 SetDialogPosition();
261 ShowWindow(hwnd_, SW_SHOW);
262 return IsWindowVisible(hwnd_) != FALSE;
265 void DisconnectWindowWin::EndDialog() {
266 if (has_hotkey_) {
267 UnregisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID);
268 has_hotkey_ = false;
271 if (hwnd_) {
272 DestroyWindow(hwnd_);
273 hwnd_ = NULL;
276 if (!disconnect_callback_.is_null()) {
277 disconnect_callback_.Run();
278 disconnect_callback_.Reset();
282 void DisconnectWindowWin::SetDialogPosition() {
283 // Try to center the window above the task-bar. If that fails, use the
284 // primary monitor. If that fails (very unlikely), use the default position.
285 HWND taskbar = FindWindow(kShellTrayWindowName, NULL);
286 HMONITOR monitor = MonitorFromWindow(taskbar, MONITOR_DEFAULTTOPRIMARY);
287 MONITORINFO monitor_info = {sizeof(monitor_info)};
288 RECT window_rect;
289 if (GetMonitorInfo(monitor, &monitor_info) &&
290 GetWindowRect(hwnd_, &window_rect)) {
291 int window_width = window_rect.right - window_rect.left;
292 int window_height = window_rect.bottom - window_rect.top;
293 int top = monitor_info.rcWork.bottom - window_height;
294 int left = (monitor_info.rcWork.right + monitor_info.rcWork.left -
295 window_width) / 2;
296 SetWindowPos(hwnd_, NULL, left, top, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
300 bool DisconnectWindowWin::SetStrings(const string16& username) {
301 if (!SetWindowText(hwnd_, ui_strings_->product_name.c_str()))
302 return false;
304 // Localize the disconnect button text and measure length of the old and new
305 // labels.
306 HWND disconnect_button = GetDlgItem(hwnd_, IDC_DISCONNECT);
307 if (!disconnect_button)
308 return false;
309 int button_old_required_width = GetControlTextWidth(disconnect_button);
310 if (!SetWindowText(disconnect_button,
311 ui_strings_->disconnect_button_text.c_str())) {
312 return false;
314 int button_new_required_width = GetControlTextWidth(disconnect_button);
315 int button_width_delta =
316 button_new_required_width - button_old_required_width;
318 // Format and truncate "Your desktop is shared with ..." message.
319 string16 text = ReplaceStringPlaceholders(ui_strings_->disconnect_message,
320 username, NULL);
321 if (text.length() > kMaxSharingWithTextLength)
322 text.erase(kMaxSharingWithTextLength);
324 // Set localized and truncated "Your desktop is shared with ..." message and
325 // measure length of the old and new text.
326 HWND sharing_with_label = GetDlgItem(hwnd_, IDC_DISCONNECT_SHARINGWITH);
327 if (!sharing_with_label)
328 return false;
329 int label_old_required_width = GetControlTextWidth(sharing_with_label);
330 if (!SetWindowText(sharing_with_label, text.c_str()))
331 return false;
332 int label_new_required_width = GetControlTextWidth(sharing_with_label);
333 int label_width_delta = label_new_required_width - label_old_required_width;
335 // Reposition the controls such that the label lies to the left of the
336 // disconnect button (assuming LTR layout). The dialog template determines
337 // the controls' spacing; update their size to fit the localized content.
338 RECT label_rect;
339 if (!GetClientRect(sharing_with_label, &label_rect))
340 return false;
341 if (!SetWindowPos(sharing_with_label, NULL, 0, 0,
342 label_rect.right + label_width_delta, label_rect.bottom,
343 SWP_NOMOVE | SWP_NOZORDER)) {
344 return false;
347 // Reposition the disconnect button as well.
348 RECT button_rect;
349 if (!GetWindowRect(disconnect_button, &button_rect))
350 return false;
351 int button_width = button_rect.right - button_rect.left;
352 int button_height = button_rect.bottom - button_rect.top;
353 SetLastError(ERROR_SUCCESS);
354 int result = MapWindowPoints(HWND_DESKTOP, hwnd_,
355 reinterpret_cast<LPPOINT>(&button_rect), 2);
356 if (!result && GetLastError() != ERROR_SUCCESS)
357 return false;
358 if (!SetWindowPos(disconnect_button, NULL,
359 button_rect.left + label_width_delta, button_rect.top,
360 button_width + button_width_delta, button_height,
361 SWP_NOZORDER)) {
362 return false;
365 // Resize the whole window to fit the resized controls.
366 RECT window_rect;
367 if (!GetWindowRect(hwnd_, &window_rect))
368 return false;
369 int width = (window_rect.right - window_rect.left) +
370 button_width_delta + label_width_delta;
371 int height = window_rect.bottom - window_rect.top;
372 if (!SetWindowPos(hwnd_, NULL, 0, 0, width, height,
373 SWP_NOMOVE | SWP_NOZORDER)) {
374 return false;
377 // Make the corners of the disconnect window rounded.
378 HRGN rgn = CreateRoundRectRgn(0, 0, width, height, kWindowBorderRadius,
379 kWindowBorderRadius);
380 if (!rgn)
381 return false;
382 if (!SetWindowRgn(hwnd_, rgn, TRUE))
383 return false;
385 return true;
388 scoped_ptr<DisconnectWindow> DisconnectWindow::Create(
389 const UiStrings* ui_strings) {
390 return scoped_ptr<DisconnectWindow>(new DisconnectWindowWin(ui_strings));
393 } // namespace remoting