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"
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.
26 const int DISCONNECT_HOTKEY_ID
= 1000;
28 // Maximum length of "Your desktop is shared with ..." message in UTF-16
30 const size_t kMaxSharingWithTextLength
= 100;
32 const wchar_t kShellTrayWindowName
[] = L
"Shell_TrayWnd";
33 const int kWindowBorderRadius
= 14;
35 } // namespace anonymous
39 class DisconnectWindowWin
: public DisconnectWindow
{
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
;
50 static BOOL CALLBACK
DialogProc(HWND hwnd
, UINT message
, WPARAM wparam
,
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
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_
;
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};
82 int result
= GetWindowText(control
, text
, arraysize(text
));
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
);
92 DisconnectWindowWin::DisconnectWindowWin(const UiStrings
* ui_strings
)
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() {
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
)) {
119 void DisconnectWindowWin::Hide() {
120 // Clear the |disconnect_callback_| so it won't be invoked by EndDialog().
121 disconnect_callback_
.Reset();
125 BOOL CALLBACK
DisconnectWindowWin::DialogProc(HWND hwnd
, UINT message
,
126 WPARAM wparam
, LPARAM lparam
) {
127 LONG_PTR self
= NULL
;
128 if (message
== WM_INITDIALOG
) {
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();
137 self
= GetWindowLongPtr(hwnd
, DWLP_USER
);
141 return reinterpret_cast<DisconnectWindowWin
*>(self
)->OnDialogMessage(
142 hwnd
, message
, wparam
, lparam
);
147 BOOL
DisconnectWindowWin::OnDialogMessage(HWND hwnd
, UINT message
,
148 WPARAM wparam
, LPARAM lparam
) {
150 // Ignore close messages.
154 // Handle the Disconnect button.
156 switch (LOWORD(wparam
)) {
163 // Ensure we don't try to use the HWND anymore.
167 // Ensure that the disconnect callback is invoked even if somehow our
168 // window gets destroyed.
173 // Ensure the dialog stays visible if the work area dimensions change.
174 case WM_SETTINGCHANGE
:
175 if (wparam
== SPI_SETWORKAREA
)
179 // Ensure the dialog stays visible if the display dimensions change.
180 case WM_DISPLAYCHANGE
:
184 // Handle the disconnect hot-key.
189 // Let the window be draggable by its client area by responding
190 // that the entire window is the title bar.
192 SetWindowLong(hwnd
, DWL_MSGRESULT
, HTCAPTION
);
197 HDC hdc
= BeginPaint(hwnd_
, &ps
);
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
);
213 bool DisconnectWindowWin::BeginDialog(const std::string
& username
) {
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
)
223 HGLOBAL dialog_template
= LoadResource(module
, dialog_resource
);
224 if (!dialog_template
)
227 DLGTEMPLATE
* dialog_pointer
=
228 reinterpret_cast<DLGTEMPLATE
*>(LockResource(dialog_template
));
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));
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
)) {
257 if (!SetStrings(UTF8ToUTF16(username
)))
261 ShowWindow(hwnd_
, SW_SHOW
);
262 return IsWindowVisible(hwnd_
) != FALSE
;
265 void DisconnectWindowWin::EndDialog() {
267 UnregisterHotKey(hwnd_
, DISCONNECT_HOTKEY_ID
);
272 DestroyWindow(hwnd_
);
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
)};
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
-
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()))
304 // Localize the disconnect button text and measure length of the old and new
306 HWND disconnect_button
= GetDlgItem(hwnd_
, IDC_DISCONNECT
);
307 if (!disconnect_button
)
309 int button_old_required_width
= GetControlTextWidth(disconnect_button
);
310 if (!SetWindowText(disconnect_button
,
311 ui_strings_
->disconnect_button_text
.c_str())) {
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
,
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
)
329 int label_old_required_width
= GetControlTextWidth(sharing_with_label
);
330 if (!SetWindowText(sharing_with_label
, text
.c_str()))
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.
339 if (!GetClientRect(sharing_with_label
, &label_rect
))
341 if (!SetWindowPos(sharing_with_label
, NULL
, 0, 0,
342 label_rect
.right
+ label_width_delta
, label_rect
.bottom
,
343 SWP_NOMOVE
| SWP_NOZORDER
)) {
347 // Reposition the disconnect button as well.
349 if (!GetWindowRect(disconnect_button
, &button_rect
))
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
)
358 if (!SetWindowPos(disconnect_button
, NULL
,
359 button_rect
.left
+ label_width_delta
, button_rect
.top
,
360 button_width
+ button_width_delta
, button_height
,
365 // Resize the whole window to fit the resized controls.
367 if (!GetWindowRect(hwnd_
, &window_rect
))
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
)) {
377 // Make the corners of the disconnect window rounded.
378 HRGN rgn
= CreateRoundRectRgn(0, 0, width
, height
, kWindowBorderRadius
,
379 kWindowBorderRadius
);
382 if (!SetWindowRgn(hwnd_
, rgn
, TRUE
))
388 scoped_ptr
<DisconnectWindow
> DisconnectWindow::Create(
389 const UiStrings
* ui_strings
) {
390 return scoped_ptr
<DisconnectWindow
>(new DisconnectWindowWin(ui_strings
));
393 } // namespace remoting