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/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"
23 const int DISCONNECT_HOTKEY_ID
= 1000;
25 // Maximum length of "Your desktop is shared with ..." message in UTF-16
27 const size_t kMaxSharingWithTextLength
= 100;
29 const wchar_t kShellTrayWindowName
[] = L
"Shell_TrayWnd";
30 const int kWindowBorderRadius
= 14;
32 // Margin between dialog controls (in dialog units).
33 const int kWindowTextMargin
= 8;
35 class DisconnectWindowWin
: public HostWindow
{
37 DisconnectWindowWin();
38 ~DisconnectWindowWin() override
;
40 // HostWindow overrides.
42 const base::WeakPtr
<ClientSessionControl
>& client_session_control
)
46 static INT_PTR CALLBACK
DialogProc(HWND hwnd
, UINT message
, WPARAM wparam
,
49 BOOL
OnDialogMessage(HWND hwnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
);
51 // Creates the dialog window and registers the disconnect hot key.
54 // Closes the dialog, unregisters the hot key and invokes the disconnect
58 // Returns |control| rectangle in the dialog coordinates.
59 bool GetControlRect(HWND control
, RECT
* rect
);
61 // Trys to position the dialog window above the taskbar.
62 void SetDialogPosition();
64 // Applies localization string and resizes the dialog.
67 // Used to disconnect the client session.
68 base::WeakPtr
<ClientSessionControl
> client_session_control_
;
70 // Specifies the remote user name.
71 std::string username_
;
75 base::win::ScopedGDIObject
<HPEN
> border_pen_
;
77 DISALLOW_COPY_AND_ASSIGN(DisconnectWindowWin
);
80 // Returns the text for the given dialog control window.
81 bool GetControlText(HWND control
, base::string16
* text
) {
82 // GetWindowText truncates the text if it is longer than can fit into
85 int result
= GetWindowText(control
, buffer
, arraysize(buffer
));
93 // Returns width |text| rendered in |control| window.
94 bool GetControlTextWidth(HWND control
,
95 const base::string16
& text
,
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
))
108 DisconnectWindowWin::DisconnectWindowWin()
111 border_pen_(CreatePen(PS_SOLID
, 5,
112 RGB(0.13 * 255, 0.69 * 255, 0.11 * 255))) {
115 DisconnectWindowWin::~DisconnectWindowWin() {
119 void DisconnectWindowWin::Start(
120 const base::WeakPtr
<ClientSessionControl
>& client_session_control
) {
121 DCHECK(CalledOnValidThread());
122 DCHECK(!client_session_control_
);
123 DCHECK(client_session_control
);
125 client_session_control_
= client_session_control
;
127 std::string client_jid
= client_session_control_
->client_jid();
128 username_
= client_jid
.substr(0, client_jid
.find('/'));
133 INT_PTR CALLBACK
DisconnectWindowWin::DialogProc(HWND hwnd
,
138 if (message
== WM_INITDIALOG
) {
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();
147 self
= GetWindowLongPtr(hwnd
, DWLP_USER
);
151 return reinterpret_cast<DisconnectWindowWin
*>(self
)->OnDialogMessage(
152 hwnd
, message
, wparam
, lparam
);
157 BOOL
DisconnectWindowWin::OnDialogMessage(HWND hwnd
,
161 DCHECK(CalledOnValidThread());
164 // Ignore close messages.
168 // Handle the Disconnect button.
170 switch (LOWORD(wparam
)) {
177 // Ensure we don't try to use the HWND anymore.
181 // Ensure that the disconnect callback is invoked even if somehow our
182 // window gets destroyed.
187 // Ensure the dialog stays visible if the work area dimensions change.
188 case WM_SETTINGCHANGE
:
189 if (wparam
== SPI_SETWORKAREA
)
193 // Ensure the dialog stays visible if the display dimensions change.
194 case WM_DISPLAYCHANGE
:
198 // Handle the disconnect hot-key.
203 // Let the window be draggable by its client area by responding
204 // that the entire window is the title bar.
206 SetWindowLongPtr(hwnd
, DWLP_MSGRESULT
, HTCAPTION
);
211 HDC hdc
= BeginPaint(hwnd_
, &ps
);
213 GetClientRect(hwnd_
, &rect
);
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
);
220 EndPaint(hwnd_
, &ps
);
227 bool DisconnectWindowWin::BeginDialog() {
228 DCHECK(CalledOnValidThread());
231 HMODULE module
= base::GetModuleFromAddress(&DialogProc
);
232 hwnd_
= CreateDialogParam(module
, MAKEINTRESOURCE(IDD_DISCONNECT
), nullptr,
233 DialogProc
, reinterpret_cast<LPARAM
>(this));
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
)) {
247 ShowWindow(hwnd_
, SW_SHOW
);
248 return IsWindowVisible(hwnd_
) != FALSE
;
251 void DisconnectWindowWin::EndDialog() {
252 DCHECK(CalledOnValidThread());
255 UnregisterHotKey(hwnd_
, DISCONNECT_HOTKEY_ID
);
260 DestroyWindow(hwnd_
);
264 if (client_session_control_
)
265 client_session_control_
->DisconnectSession();
268 // Returns |control| rectangle in the dialog coordinates.
269 bool DisconnectWindowWin::GetControlRect(HWND control
, RECT
* rect
) {
270 if (!GetWindowRect(control
, rect
))
272 SetLastError(ERROR_SUCCESS
);
273 int result
= MapWindowPoints(HWND_DESKTOP
, hwnd_
,
274 reinterpret_cast<LPPOINT
>(rect
), 2);
275 if (!result
&& GetLastError() != ERROR_SUCCESS
)
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
, nullptr);
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_
, nullptr, left
, top
, 0, 0, SWP_NOSIZE
| SWP_NOZORDER
);
301 bool DisconnectWindowWin::SetStrings() {
302 DCHECK(CalledOnValidThread());
304 // Localize the disconnect button text and measure length of the old and new
306 HWND hwnd_button
= GetDlgItem(hwnd_
, IDC_DISCONNECT
);
307 HWND hwnd_message
= GetDlgItem(hwnd_
, IDC_DISCONNECT_SHARINGWITH
);
308 if (!hwnd_button
|| !hwnd_message
)
311 base::string16 button_text
;
312 base::string16 message_text
;
313 if (!GetControlText(hwnd_button
, &button_text
) ||
314 !GetControlText(hwnd_message
, &message_text
)) {
318 // Format and truncate "Your desktop is shared with ..." message.
319 message_text
= ReplaceStringPlaceholders(message_text
,
320 base::UTF8ToUTF16(username_
),
322 if (message_text
.length() > kMaxSharingWithTextLength
)
323 message_text
.erase(kMaxSharingWithTextLength
);
325 if (!SetWindowText(hwnd_message
, message_text
.c_str()))
328 // Calculate the margin between controls in pixels.
330 rect
.right
= kWindowTextMargin
;
331 if (!MapDialogRect(hwnd_
, &rect
))
333 int margin
= rect
.right
;
335 // Resize |hwnd_message| so that the text is not clipped.
337 if (!GetControlRect(hwnd_message
, &message_rect
))
341 if (!GetControlTextWidth(hwnd_message
, message_text
, &control_width
))
343 message_rect
.right
= message_rect
.left
+ control_width
+ margin
;
345 if (!SetWindowPos(hwnd_message
, nullptr,
346 message_rect
.left
, message_rect
.top
,
347 message_rect
.right
- message_rect
.left
,
348 message_rect
.bottom
- message_rect
.top
,
353 // Reposition and resize |hwnd_button| as well.
355 if (!GetControlRect(hwnd_button
, &button_rect
))
358 if (!GetControlTextWidth(hwnd_button
, button_text
, &control_width
))
361 button_rect
.left
= message_rect
.right
;
362 button_rect
.right
= button_rect
.left
+ control_width
+ margin
* 2;
363 if (!SetWindowPos(hwnd_button
, nullptr,
364 button_rect
.left
, button_rect
.top
,
365 button_rect
.right
- button_rect
.left
,
366 button_rect
.bottom
- button_rect
.top
,
371 // Resize the whole window to fit the resized controls.
373 if (!GetWindowRect(hwnd_
, &window_rect
))
375 int width
= button_rect
.right
+ margin
;
376 int height
= window_rect
.bottom
- window_rect
.top
;
377 if (!SetWindowPos(hwnd_
, nullptr, 0, 0, width
, height
,
378 SWP_NOMOVE
| SWP_NOZORDER
)) {
382 // Make the corners of the disconnect window rounded.
383 HRGN rgn
= CreateRoundRectRgn(0, 0, width
, height
, kWindowBorderRadius
,
384 kWindowBorderRadius
);
387 if (!SetWindowRgn(hwnd_
, rgn
, TRUE
))
396 scoped_ptr
<HostWindow
> HostWindow::CreateDisconnectWindow() {
397 return make_scoped_ptr(new DisconnectWindowWin());
400 } // namespace remoting