Fix mouse warp with 2x displays
[chromium-blink-merge.git] / remoting / host / disconnect_window_win.cc
blobc0a7ecc1857e15f264c9227252232520db916867
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 <windows.h>
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"
19 namespace remoting {
21 namespace {
23 const int DISCONNECT_HOTKEY_ID = 1000;
25 // Maximum length of "Your desktop is shared with ..." message in UTF-16
26 // characters.
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 {
36 public:
37 DisconnectWindowWin();
38 ~DisconnectWindowWin() override;
40 // HostWindow overrides.
41 void Start(
42 const base::WeakPtr<ClientSessionControl>& client_session_control)
43 override;
45 protected:
46 static INT_PTR CALLBACK DialogProc(HWND hwnd, UINT message, WPARAM wparam,
47 LPARAM lparam);
49 BOOL OnDialogMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
51 // Creates the dialog window and registers the disconnect hot key.
52 bool BeginDialog();
54 // Closes the dialog, unregisters the hot key and invokes the disconnect
55 // callback, if set.
56 void EndDialog();
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.
65 bool SetStrings();
67 // Used to disconnect the client session.
68 base::WeakPtr<ClientSessionControl> client_session_control_;
70 // Specifies the remote user name.
71 std::string username_;
73 HWND hwnd_;
74 bool has_hotkey_;
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
83 // the buffer.
84 WCHAR buffer[256];
85 int result = GetWindowText(control, buffer, arraysize(buffer));
86 if (!result)
87 return false;
89 text->assign(buffer);
90 return true;
93 // Returns width |text| rendered in |control| window.
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;
104 *width = rect.right;
105 return true;
108 DisconnectWindowWin::DisconnectWindowWin()
109 : hwnd_(nullptr),
110 has_hotkey_(false),
111 border_pen_(CreatePen(PS_SOLID, 5,
112 RGB(0.13 * 255, 0.69 * 255, 0.11 * 255))) {
115 DisconnectWindowWin::~DisconnectWindowWin() {
116 EndDialog();
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('/'));
129 if (!BeginDialog())
130 EndDialog();
133 INT_PTR CALLBACK DisconnectWindowWin::DialogProc(HWND hwnd,
134 UINT message,
135 WPARAM wparam,
136 LPARAM lparam) {
137 LONG_PTR self = 0;
138 if (message == WM_INITDIALOG) {
139 self = lparam;
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);
150 if (self) {
151 return reinterpret_cast<DisconnectWindowWin*>(self)->OnDialogMessage(
152 hwnd, message, wparam, lparam);
154 return FALSE;
157 BOOL DisconnectWindowWin::OnDialogMessage(HWND hwnd,
158 UINT message,
159 WPARAM wparam,
160 LPARAM lparam) {
161 DCHECK(CalledOnValidThread());
163 switch (message) {
164 // Ignore close messages.
165 case WM_CLOSE:
166 return TRUE;
168 // Handle the Disconnect button.
169 case WM_COMMAND:
170 switch (LOWORD(wparam)) {
171 case IDC_DISCONNECT:
172 EndDialog();
173 return TRUE;
175 return FALSE;
177 // Ensure we don't try to use the HWND anymore.
178 case WM_DESTROY:
179 hwnd_ = nullptr;
181 // Ensure that the disconnect callback is invoked even if somehow our
182 // window gets destroyed.
183 EndDialog();
185 return TRUE;
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;
193 // Ensure the dialog stays visible if the display dimensions change.
194 case WM_DISPLAYCHANGE:
195 SetDialogPosition();
196 return TRUE;
198 // Handle the disconnect hot-key.
199 case WM_HOTKEY:
200 EndDialog();
201 return TRUE;
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;
209 case WM_PAINT: {
210 PAINTSTRUCT ps;
211 HDC hdc = BeginPaint(hwnd_, &ps);
212 RECT rect;
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);
221 return TRUE;
224 return FALSE;
227 bool DisconnectWindowWin::BeginDialog() {
228 DCHECK(CalledOnValidThread());
229 DCHECK(!hwnd_);
231 HMODULE module = base::GetModuleFromAddress(&DialogProc);
232 hwnd_ = CreateDialogParam(module, MAKEINTRESOURCE(IDD_DISCONNECT), nullptr,
233 DialogProc, reinterpret_cast<LPARAM>(this));
234 if (!hwnd_)
235 return false;
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;
243 if (!SetStrings())
244 return false;
246 SetDialogPosition();
247 ShowWindow(hwnd_, SW_SHOW);
248 return IsWindowVisible(hwnd_) != FALSE;
251 void DisconnectWindowWin::EndDialog() {
252 DCHECK(CalledOnValidThread());
254 if (has_hotkey_) {
255 UnregisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID);
256 has_hotkey_ = false;
259 if (hwnd_) {
260 DestroyWindow(hwnd_);
261 hwnd_ = nullptr;
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))
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;
278 return true;
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)};
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_, 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
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;
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;
318 // Format and truncate "Your desktop is shared with ..." message.
319 message_text = base::ReplaceStringPlaceholders(message_text,
320 base::UTF8ToUTF16(username_),
321 nullptr);
322 if (message_text.length() > kMaxSharingWithTextLength)
323 message_text.erase(kMaxSharingWithTextLength);
325 if (!SetWindowText(hwnd_message, message_text.c_str()))
326 return false;
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;
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;
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;
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,
349 SWP_NOZORDER)) {
350 return false;
353 // Reposition and resize |hwnd_button| as well.
354 RECT button_rect;
355 if (!GetControlRect(hwnd_button, &button_rect))
356 return false;
358 if (!GetControlTextWidth(hwnd_button, button_text, &control_width))
359 return false;
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,
367 SWP_NOZORDER)) {
368 return false;
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_, nullptr, 0, 0, width, height,
378 SWP_NOMOVE | SWP_NOZORDER)) {
379 return false;
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;
390 return true;
393 } // namespace
395 // static
396 scoped_ptr<HostWindow> HostWindow::CreateDisconnectWindow() {
397 return make_scoped_ptr(new DisconnectWindowWin());
400 } // namespace remoting