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/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"
18
19 namespace remoting {
20
21 namespace {
22
23 const int DISCONNECT_HOTKEY_ID = 1000;
24
25 // Maximum length of "Your desktop is shared with ..." message in UTF-16
26 // characters.
27 const size_t kMaxSharingWithTextLength = 100;
28
29 const wchar_t kShellTrayWindowName[] = L"Shell_TrayWnd";
30 const int kWindowBorderRadius = 14;
31
32 // Margin between dialog controls (in dialog units).
33 const int kWindowTextMargin = 8;
34
35 class DisconnectWindowWin : public HostWindow {
36 public:
37 DisconnectWindowWin();
38 virtual ~DisconnectWindowWin();
39
40 // HostWindow overrides.
41 virtual void Start(
42 const base::WeakPtr<ClientSessionControl>& client_session_control)
43 OVERRIDE;
44
45 protected:
46 static INT_PTR CALLBACK DialogProc(HWND hwnd, UINT message, WPARAM wparam,
47 LPARAM lparam);
48
49 BOOL OnDialogMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
50
51 // Creates the dialog window and registers the disconnect hot key.
52 bool BeginDialog();
53
54 // Closes the dialog, unregisters the hot key and invokes the disconnect
55 // callback, if set.
56 void EndDialog();
57
58 // Returns |control| rectangle in the dialog coordinates.
59 bool GetControlRect(HWND control, RECT* rect);
60
61 // Trys to position the dialog window above the taskbar.
62 void SetDialogPosition();
63
64 // Applies localization string and resizes the dialog.
65 bool SetStrings();
66
67 // Used to disconnect the client session.
68 base::WeakPtr<ClientSessionControl> client_session_control_;
69
70 // Specifies the remote user name.
71 std::string username_;
72
73 HWND hwnd_;
74 bool has_hotkey_;
75 base::win::ScopedGDIObject<HPEN> border_pen_;
76
77 DISALLOW_COPY_AND_ASSIGN(DisconnectWindowWin);
78 };
79
80 // Returns the text for the given dialog control window.
GetControlText(HWND control,base::string16 * text)81 bool GetControlText(HWND control, base::string16* text) {
82 // GetWindowText truncates the text if it is longer than can fit into
83 // the buffer.
84 WCHAR buffer[256];
85 int result = GetWindowText(control, buffer, arraysize(buffer));
86 if (!result)
87 return false;
88
89 text->assign(buffer);
90 return true;
91 }
92
93 // Returns width |text| rendered in |control| window.
GetControlTextWidth(HWND control,const base::string16 & text,LONG * width)94 bool GetControlTextWidth(HWND control,
95 const base::string16& text,
96 LONG* width) {
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))
102 return false;
103
104 *width = rect.right;
105 return true;
106 }
107
DisconnectWindowWin()108 DisconnectWindowWin::DisconnectWindowWin()
109 : hwnd_(NULL),
110 has_hotkey_(false),
111 border_pen_(CreatePen(PS_SOLID, 5,
112 RGB(0.13 * 255, 0.69 * 255, 0.11 * 255))) {
113 }
114
~DisconnectWindowWin()115 DisconnectWindowWin::~DisconnectWindowWin() {
116 EndDialog();
117 }
118
Start(const base::WeakPtr<ClientSessionControl> & client_session_control)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);
124
125 client_session_control_ = client_session_control;
126
127 std::string client_jid = client_session_control_->client_jid();
128 username_ = client_jid.substr(0, client_jid.find('/'));
129 if (!BeginDialog())
130 EndDialog();
131 }
132
DialogProc(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)133 INT_PTR CALLBACK DisconnectWindowWin::DialogProc(HWND hwnd,
134 UINT message,
135 WPARAM wparam,
136 LPARAM lparam) {
137 LONG_PTR self = NULL;
138 if (message == WM_INITDIALOG) {
139 self = lparam;
140
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();
146 } else {
147 self = GetWindowLongPtr(hwnd, DWLP_USER);
148 }
149
150 if (self) {
151 return reinterpret_cast<DisconnectWindowWin*>(self)->OnDialogMessage(
152 hwnd, message, wparam, lparam);
153 }
154 return FALSE;
155 }
156
OnDialogMessage(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)157 BOOL DisconnectWindowWin::OnDialogMessage(HWND hwnd,
158 UINT message,
159 WPARAM wparam,
160 LPARAM lparam) {
161 DCHECK(CalledOnValidThread());
162
163 switch (message) {
164 // Ignore close messages.
165 case WM_CLOSE:
166 return TRUE;
167
168 // Handle the Disconnect button.
169 case WM_COMMAND:
170 switch (LOWORD(wparam)) {
171 case IDC_DISCONNECT:
172 EndDialog();
173 return TRUE;
174 }
175 return FALSE;
176
177 // Ensure we don't try to use the HWND anymore.
178 case WM_DESTROY:
179 hwnd_ = NULL;
180
181 // Ensure that the disconnect callback is invoked even if somehow our
182 // window gets destroyed.
183 EndDialog();
184
185 return TRUE;
186
187 // Ensure the dialog stays visible if the work area dimensions change.
188 case WM_SETTINGCHANGE:
189 if (wparam == SPI_SETWORKAREA)
190 SetDialogPosition();
191 return TRUE;
192
193 // Ensure the dialog stays visible if the display dimensions change.
194 case WM_DISPLAYCHANGE:
195 SetDialogPosition();
196 return TRUE;
197
198 // Handle the disconnect hot-key.
199 case WM_HOTKEY:
200 EndDialog();
201 return TRUE;
202
203 // Let the window be draggable by its client area by responding
204 // that the entire window is the title bar.
205 case WM_NCHITTEST:
206 SetWindowLongPtr(hwnd, DWLP_MSGRESULT, HTCAPTION);
207 return TRUE;
208
209 case WM_PAINT: {
210 PAINTSTRUCT ps;
211 HDC hdc = BeginPaint(hwnd_, &ps);
212 RECT rect;
213 GetClientRect(hwnd_, &rect);
214 {
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);
219 }
220 EndPaint(hwnd_, &ps);
221 return TRUE;
222 }
223 }
224 return FALSE;
225 }
226
BeginDialog()227 bool DisconnectWindowWin::BeginDialog() {
228 DCHECK(CalledOnValidThread());
229 DCHECK(!hwnd_);
230
231 HMODULE module = base::GetModuleFromAddress(&DialogProc);
232 hwnd_ = CreateDialogParam(module, MAKEINTRESOURCE(IDD_DISCONNECT), NULL,
233 DialogProc, reinterpret_cast<LPARAM>(this));
234 if (!hwnd_)
235 return false;
236
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)) {
240 has_hotkey_ = true;
241 }
242
243 if (!SetStrings())
244 return false;
245
246 SetDialogPosition();
247 ShowWindow(hwnd_, SW_SHOW);
248 return IsWindowVisible(hwnd_) != FALSE;
249 }
250
EndDialog()251 void DisconnectWindowWin::EndDialog() {
252 DCHECK(CalledOnValidThread());
253
254 if (has_hotkey_) {
255 UnregisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID);
256 has_hotkey_ = false;
257 }
258
259 if (hwnd_) {
260 DestroyWindow(hwnd_);
261 hwnd_ = NULL;
262 }
263
264 if (client_session_control_)
265 client_session_control_->DisconnectSession();
266 }
267
268 // Returns |control| rectangle in the dialog coordinates.
GetControlRect(HWND control,RECT * rect)269 bool DisconnectWindowWin::GetControlRect(HWND control, RECT* rect) {
270 if (!GetWindowRect(control, rect))
271 return false;
272 SetLastError(ERROR_SUCCESS);
273 int result = MapWindowPoints(HWND_DESKTOP, hwnd_,
274 reinterpret_cast<LPPOINT>(rect), 2);
275 if (!result && GetLastError() != ERROR_SUCCESS)
276 return false;
277
278 return true;
279 }
280
SetDialogPosition()281 void 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
SetStrings()301 bool DisconnectWindowWin::SetStrings() {
302 DCHECK(CalledOnValidThread());
303
304 // Localize the disconnect button text and measure length of the old and new
305 // labels.
306 HWND hwnd_button = GetDlgItem(hwnd_, IDC_DISCONNECT);
307 HWND hwnd_message = GetDlgItem(hwnd_, IDC_DISCONNECT_SHARINGWITH);
308 if (!hwnd_button || !hwnd_message)
309 return false;
310
311 base::string16 button_text;
312 base::string16 message_text;
313 if (!GetControlText(hwnd_button, &button_text) ||
314 !GetControlText(hwnd_message, &message_text)) {
315 return false;
316 }
317
318 // Format and truncate "Your desktop is shared with ..." message.
319 message_text = ReplaceStringPlaceholders(message_text,
320 base::UTF8ToUTF16(username_),
321 NULL);
322 if (message_text.length() > kMaxSharingWithTextLength)
323 message_text.erase(kMaxSharingWithTextLength);
324
325 if (!SetWindowText(hwnd_message, message_text.c_str()))
326 return false;
327
328 // Calculate the margin between controls in pixels.
329 RECT rect = {0};
330 rect.right = kWindowTextMargin;
331 if (!MapDialogRect(hwnd_, &rect))
332 return false;
333 int margin = rect.right;
334
335 // Resize |hwnd_message| so that the text is not clipped.
336 RECT message_rect;
337 if (!GetControlRect(hwnd_message, &message_rect))
338 return false;
339
340 LONG control_width;
341 if (!GetControlTextWidth(hwnd_message, message_text, &control_width))
342 return false;
343 message_rect.right = message_rect.left + control_width + margin;
344
345 if (!SetWindowPos(hwnd_message, NULL,
346 message_rect.left, message_rect.top,
347 message_rect.right - message_rect.left,
348 message_rect.bottom - message_rect.top,
349 SWP_NOZORDER)) {
350 return false;
351 }
352
353 // Reposition and resize |hwnd_button| as well.
354 RECT button_rect;
355 if (!GetControlRect(hwnd_button, &button_rect))
356 return false;
357
358 if (!GetControlTextWidth(hwnd_button, button_text, &control_width))
359 return false;
360
361 button_rect.left = message_rect.right;
362 button_rect.right = button_rect.left + control_width + margin * 2;
363 if (!SetWindowPos(hwnd_button, NULL,
364 button_rect.left, button_rect.top,
365 button_rect.right - button_rect.left,
366 button_rect.bottom - button_rect.top,
367 SWP_NOZORDER)) {
368 return false;
369 }
370
371 // Resize the whole window to fit the resized controls.
372 RECT window_rect;
373 if (!GetWindowRect(hwnd_, &window_rect))
374 return false;
375 int width = button_rect.right + margin;
376 int height = window_rect.bottom - window_rect.top;
377 if (!SetWindowPos(hwnd_, NULL, 0, 0, width, height,
378 SWP_NOMOVE | SWP_NOZORDER)) {
379 return false;
380 }
381
382 // Make the corners of the disconnect window rounded.
383 HRGN rgn = CreateRoundRectRgn(0, 0, width, height, kWindowBorderRadius,
384 kWindowBorderRadius);
385 if (!rgn)
386 return false;
387 if (!SetWindowRgn(hwnd_, rgn, TRUE))
388 return false;
389
390 return true;
391 }
392
393 } // namespace
394
395 // static
CreateDisconnectWindow()396 scoped_ptr<HostWindow> HostWindow::CreateDisconnectWindow() {
397 return scoped_ptr<HostWindow>(new DisconnectWindowWin());
398 }
399
400 } // namespace remoting
401