Only sync parent directory once after a leveldb file rename.
[chromium-blink-merge.git] / remoting / host / disconnect_window_win.cc
blob6d0634686b224c0458b4733525f5c1ed4bb9b884
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_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"
20 namespace remoting {
22 namespace {
24 const int DISCONNECT_HOTKEY_ID = 1000;
26 // Maximum length of "Your desktop is shared with ..." message in UTF-16
27 // characters.
28 const size_t kMaxSharingWithTextLength = 100;
30 const wchar_t kShellTrayWindowName[] = L"Shell_TrayWnd";
31 const int kWindowBorderRadius = 14;
33 class DisconnectWindowWin : public HostWindow {
34 public:
35 explicit DisconnectWindowWin(const UiStrings& ui_strings);
36 virtual ~DisconnectWindowWin();
38 // HostWindow overrides.
39 virtual void Start(
40 const base::WeakPtr<ClientSessionControl>& client_session_control)
41 OVERRIDE;
43 protected:
44 static INT_PTR CALLBACK DialogProc(HWND hwnd, UINT message, WPARAM wparam,
45 LPARAM lparam);
47 BOOL OnDialogMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
49 // Creates the dialog window and registers the disconnect hot key.
50 bool BeginDialog();
52 // Closes the dialog, unregisters the hot key and invokes the disconnect
53 // callback, if set.
54 void EndDialog();
56 // Trys to position the dialog window above the taskbar.
57 void SetDialogPosition();
59 // Applies localization string and resizes the dialog.
60 bool SetStrings();
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_;
71 HWND hwnd_;
72 bool has_hotkey_;
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};
80 WCHAR text[256];
81 int result = GetWindowText(control, text, arraysize(text));
82 if (result) {
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);
88 return rect.right;
91 DisconnectWindowWin::DisconnectWindowWin(const UiStrings& ui_strings)
92 : ui_strings_(ui_strings),
93 hwnd_(NULL),
94 has_hotkey_(false),
95 border_pen_(CreatePen(PS_SOLID, 5,
96 RGB(0.13 * 255, 0.69 * 255, 0.11 * 255))) {
99 DisconnectWindowWin::~DisconnectWindowWin() {
100 EndDialog();
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('/'));
113 if (!BeginDialog())
114 EndDialog();
117 INT_PTR CALLBACK DisconnectWindowWin::DialogProc(HWND hwnd,
118 UINT message,
119 WPARAM wparam,
120 LPARAM lparam) {
121 LONG_PTR self = NULL;
122 if (message == WM_INITDIALOG) {
123 self = lparam;
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();
130 } else {
131 self = GetWindowLongPtr(hwnd, DWLP_USER);
134 if (self) {
135 return reinterpret_cast<DisconnectWindowWin*>(self)->OnDialogMessage(
136 hwnd, message, wparam, lparam);
138 return FALSE;
141 BOOL DisconnectWindowWin::OnDialogMessage(HWND hwnd,
142 UINT message,
143 WPARAM wparam,
144 LPARAM lparam) {
145 DCHECK(CalledOnValidThread());
147 switch (message) {
148 // Ignore close messages.
149 case WM_CLOSE:
150 return TRUE;
152 // Handle the Disconnect button.
153 case WM_COMMAND:
154 switch (LOWORD(wparam)) {
155 case IDC_DISCONNECT:
156 EndDialog();
157 return TRUE;
159 return FALSE;
161 // Ensure we don't try to use the HWND anymore.
162 case WM_DESTROY:
163 hwnd_ = NULL;
165 // Ensure that the disconnect callback is invoked even if somehow our
166 // window gets destroyed.
167 EndDialog();
169 return TRUE;
171 // Ensure the dialog stays visible if the work area dimensions change.
172 case WM_SETTINGCHANGE:
173 if (wparam == SPI_SETWORKAREA)
174 SetDialogPosition();
175 return TRUE;
177 // Ensure the dialog stays visible if the display dimensions change.
178 case WM_DISPLAYCHANGE:
179 SetDialogPosition();
180 return TRUE;
182 // Handle the disconnect hot-key.
183 case WM_HOTKEY:
184 EndDialog();
185 return TRUE;
187 // Let the window be draggable by its client area by responding
188 // that the entire window is the title bar.
189 case WM_NCHITTEST:
190 SetWindowLongPtr(hwnd, DWLP_MSGRESULT, HTCAPTION);
191 return TRUE;
193 case WM_PAINT: {
194 PAINTSTRUCT ps;
195 HDC hdc = BeginPaint(hwnd_, &ps);
196 RECT rect;
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);
205 return TRUE;
208 return FALSE;
211 bool DisconnectWindowWin::BeginDialog() {
212 DCHECK(CalledOnValidThread());
213 DCHECK(!hwnd_);
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)
220 return false;
222 HGLOBAL dialog_template = LoadResource(module, dialog_resource);
223 if (!dialog_template)
224 return false;
226 DLGTEMPLATE* dialog_pointer =
227 reinterpret_cast<DLGTEMPLATE*>(LockResource(dialog_template));
228 if (!dialog_pointer)
229 return false;
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));
247 if (!hwnd_)
248 return false;
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)) {
253 has_hotkey_ = true;
256 if (!SetStrings())
257 return false;
259 SetDialogPosition();
260 ShowWindow(hwnd_, SW_SHOW);
261 return IsWindowVisible(hwnd_) != FALSE;
264 void DisconnectWindowWin::EndDialog() {
265 DCHECK(CalledOnValidThread());
267 if (has_hotkey_) {
268 UnregisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID);
269 has_hotkey_ = false;
272 if (hwnd_) {
273 DestroyWindow(hwnd_);
274 hwnd_ = NULL;
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)};
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);
301 bool DisconnectWindowWin::SetStrings() {
302 DCHECK(CalledOnValidThread());
304 if (!SetWindowText(hwnd_, ui_strings_.product_name.c_str()))
305 return false;
307 // Localize the disconnect button text and measure length of the old and new
308 // labels.
309 HWND disconnect_button = GetDlgItem(hwnd_, IDC_DISCONNECT);
310 if (!disconnect_button)
311 return false;
312 int button_old_required_width = GetControlTextWidth(disconnect_button);
313 if (!SetWindowText(disconnect_button,
314 ui_strings_.disconnect_button_text.c_str())) {
315 return false;
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)
331 return false;
332 int label_old_required_width = GetControlTextWidth(sharing_with_label);
333 if (!SetWindowText(sharing_with_label, text.c_str()))
334 return false;
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.
341 RECT label_rect;
342 if (!GetClientRect(sharing_with_label, &label_rect))
343 return false;
344 if (!SetWindowPos(sharing_with_label, NULL, 0, 0,
345 label_rect.right + label_width_delta, label_rect.bottom,
346 SWP_NOMOVE | SWP_NOZORDER)) {
347 return false;
350 // Reposition the disconnect button as well.
351 RECT button_rect;
352 if (!GetWindowRect(disconnect_button, &button_rect))
353 return false;
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)
360 return false;
361 if (!SetWindowPos(disconnect_button, NULL,
362 button_rect.left + label_width_delta, button_rect.top,
363 button_width + button_width_delta, button_height,
364 SWP_NOZORDER)) {
365 return false;
368 // Resize the whole window to fit the resized controls.
369 RECT window_rect;
370 if (!GetWindowRect(hwnd_, &window_rect))
371 return false;
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)) {
377 return false;
380 // Make the corners of the disconnect window rounded.
381 HRGN rgn = CreateRoundRectRgn(0, 0, width, height, kWindowBorderRadius,
382 kWindowBorderRadius);
383 if (!rgn)
384 return false;
385 if (!SetWindowRgn(hwnd_, rgn, TRUE))
386 return false;
388 return true;
391 } // namespace
393 // static
394 scoped_ptr<HostWindow> HostWindow::CreateDisconnectWindow(
395 const UiStrings& ui_strings) {
396 return scoped_ptr<HostWindow>(new DisconnectWindowWin(ui_strings));
399 } // namespace remoting