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.
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/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"
24 const int DISCONNECT_HOTKEY_ID
= 1000;
26 // Maximum length of "Your desktop is shared with ..." message in UTF-16
28 const size_t kMaxSharingWithTextLength
= 100;
30 const wchar_t kShellTrayWindowName
[] = L
"Shell_TrayWnd";
31 const int kWindowBorderRadius
= 14;
33 class DisconnectWindowWin
: public HostWindow
{
35 explicit DisconnectWindowWin(const UiStrings
& ui_strings
);
36 virtual ~DisconnectWindowWin();
38 // HostWindow overrides.
40 const base::WeakPtr
<ClientSessionControl
>& client_session_control
)
44 static INT_PTR CALLBACK
DialogProc(HWND hwnd
, UINT message
, WPARAM wparam
,
47 BOOL
OnDialogMessage(HWND hwnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
);
49 // Creates the dialog window and registers the disconnect hot key.
52 // Closes the dialog, unregisters the hot key and invokes the disconnect
56 // Trys to position the dialog window above the taskbar.
57 void SetDialogPosition();
59 // Applies localization string and resizes the dialog.
62 // Used to disconnect the client session.
63 base::WeakPtr
<ClientSessionControl
> client_session_control_
;
65 // Localized UI strings.
66 UiStrings ui_strings_
;
68 // Specifies the remote user name.
69 std::string username_
;
73 base::win::ScopedGDIObject
<HPEN
> border_pen_
;
75 DISALLOW_COPY_AND_ASSIGN(DisconnectWindowWin
);
78 int GetControlTextWidth(HWND control
) {
79 RECT rect
= {0, 0, 0, 0};
81 int result
= GetWindowText(control
, text
, arraysize(text
));
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
);
91 DisconnectWindowWin::DisconnectWindowWin(const UiStrings
& ui_strings
)
92 : ui_strings_(ui_strings
),
95 border_pen_(CreatePen(PS_SOLID
, 5,
96 RGB(0.13 * 255, 0.69 * 255, 0.11 * 255))) {
99 DisconnectWindowWin::~DisconnectWindowWin() {
103 void DisconnectWindowWin::Start(
104 const base::WeakPtr
<ClientSessionControl
>& client_session_control
) {
105 DCHECK(CalledOnValidThread());
106 DCHECK(!client_session_control_
);
107 DCHECK(client_session_control
);
109 client_session_control_
= client_session_control
;
111 std::string client_jid
= client_session_control_
->client_jid();
112 username_
= client_jid
.substr(0, client_jid
.find('/'));
117 INT_PTR CALLBACK
DisconnectWindowWin::DialogProc(HWND hwnd
,
121 LONG_PTR self
= NULL
;
122 if (message
== WM_INITDIALOG
) {
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();
131 self
= GetWindowLongPtr(hwnd
, DWLP_USER
);
135 return reinterpret_cast<DisconnectWindowWin
*>(self
)->OnDialogMessage(
136 hwnd
, message
, wparam
, lparam
);
141 BOOL
DisconnectWindowWin::OnDialogMessage(HWND hwnd
,
145 DCHECK(CalledOnValidThread());
148 // Ignore close messages.
152 // Handle the Disconnect button.
154 switch (LOWORD(wparam
)) {
161 // Ensure we don't try to use the HWND anymore.
165 // Ensure that the disconnect callback is invoked even if somehow our
166 // window gets destroyed.
171 // Ensure the dialog stays visible if the work area dimensions change.
172 case WM_SETTINGCHANGE
:
173 if (wparam
== SPI_SETWORKAREA
)
177 // Ensure the dialog stays visible if the display dimensions change.
178 case WM_DISPLAYCHANGE
:
182 // Handle the disconnect hot-key.
187 // Let the window be draggable by its client area by responding
188 // that the entire window is the title bar.
190 SetWindowLongPtr(hwnd
, DWLP_MSGRESULT
, HTCAPTION
);
195 HDC hdc
= BeginPaint(hwnd_
, &ps
);
197 GetClientRect(hwnd_
, &rect
);
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
);
204 EndPaint(hwnd_
, &ps
);
211 bool DisconnectWindowWin::BeginDialog() {
212 DCHECK(CalledOnValidThread());
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
)
222 HGLOBAL dialog_template
= LoadResource(module
, dialog_resource
);
223 if (!dialog_template
)
226 DLGTEMPLATE
* dialog_pointer
=
227 reinterpret_cast<DLGTEMPLATE
*>(LockResource(dialog_template
));
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
);
245 hwnd_
= CreateDialogIndirectParam(module
, dialog_pointer
, NULL
,
246 DialogProc
, reinterpret_cast<LPARAM
>(this));
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
)) {
260 ShowWindow(hwnd_
, SW_SHOW
);
261 return IsWindowVisible(hwnd_
) != FALSE
;
264 void DisconnectWindowWin::EndDialog() {
265 DCHECK(CalledOnValidThread());
268 UnregisterHotKey(hwnd_
, DISCONNECT_HOTKEY_ID
);
273 DestroyWindow(hwnd_
);
277 if (client_session_control_
)
278 client_session_control_
->DisconnectSession();
281 void DisconnectWindowWin::SetDialogPosition() {
282 DCHECK(CalledOnValidThread());
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
)};
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
-
297 SetWindowPos(hwnd_
, NULL
, left
, top
, 0, 0, SWP_NOSIZE
| SWP_NOZORDER
);
301 bool DisconnectWindowWin::SetStrings() {
302 DCHECK(CalledOnValidThread());
304 if (!SetWindowText(hwnd_
, ui_strings_
.product_name
.c_str()))
307 // Localize the disconnect button text and measure length of the old and new
309 HWND disconnect_button
= GetDlgItem(hwnd_
, IDC_DISCONNECT
);
310 if (!disconnect_button
)
312 int button_old_required_width
= GetControlTextWidth(disconnect_button
);
313 if (!SetWindowText(disconnect_button
,
314 ui_strings_
.disconnect_button_text
.c_str())) {
317 int button_new_required_width
= GetControlTextWidth(disconnect_button
);
318 int button_width_delta
=
319 button_new_required_width
- button_old_required_width
;
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
);
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
)
332 int label_old_required_width
= GetControlTextWidth(sharing_with_label
);
333 if (!SetWindowText(sharing_with_label
, text
.c_str()))
335 int label_new_required_width
= GetControlTextWidth(sharing_with_label
);
336 int label_width_delta
= label_new_required_width
- label_old_required_width
;
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.
342 if (!GetClientRect(sharing_with_label
, &label_rect
))
344 if (!SetWindowPos(sharing_with_label
, NULL
, 0, 0,
345 label_rect
.right
+ label_width_delta
, label_rect
.bottom
,
346 SWP_NOMOVE
| SWP_NOZORDER
)) {
350 // Reposition the disconnect button as well.
352 if (!GetWindowRect(disconnect_button
, &button_rect
))
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
)
361 if (!SetWindowPos(disconnect_button
, NULL
,
362 button_rect
.left
+ label_width_delta
, button_rect
.top
,
363 button_width
+ button_width_delta
, button_height
,
368 // Resize the whole window to fit the resized controls.
370 if (!GetWindowRect(hwnd_
, &window_rect
))
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
)) {
380 // Make the corners of the disconnect window rounded.
381 HRGN rgn
= CreateRoundRectRgn(0, 0, width
, height
, kWindowBorderRadius
,
382 kWindowBorderRadius
);
385 if (!SetWindowRgn(hwnd_
, rgn
, TRUE
))
394 scoped_ptr
<HostWindow
> HostWindow::CreateDisconnectWindow(
395 const UiStrings
& ui_strings
) {
396 return scoped_ptr
<HostWindow
>(new DisconnectWindowWin(ui_strings
));
399 } // namespace remoting