disconnect_window_win.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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 "remoting/host/disconnect_window.h"
6
7#include <windows.h>
8
9#include "base/compiler_specific.h"
10#include "base/logging.h"
11#include "base/string_util.h"
12#include "base/utf_string_conversions.h"
13#include "base/win/scoped_gdi_object.h"
14#include "base/win/scoped_hdc.h"
15#include "base/win/scoped_select_object.h"
16#include "remoting/host/chromoting_host.h"
17#include "remoting/host/host_ui_resource.h"
18#include "remoting/host/ui_strings.h"
19
20// TODO(garykac): Lots of duplicated code in this file and
21// continue_window_win.cc. If we need to expand this then we should
22// create a class with the shared code.
23
24// HMODULE from DllMain/WinMain. This is needed to find our dialog resource.
25// This is defined in:
26//   Plugin: host_plugin.cc
27//   SimpleHost: simple_host_process.cc
28extern HMODULE g_hModule;
29
30namespace {
31
32const int DISCONNECT_HOTKEY_ID = 1000;
33const int kWindowBorderRadius = 14;
34const wchar_t kShellTrayWindowName[] = L"Shell_TrayWnd";
35
36} // namespace anonymous
37
38namespace remoting {
39
40class DisconnectWindowWin : public DisconnectWindow {
41 public:
42  DisconnectWindowWin();
43  virtual ~DisconnectWindowWin();
44
45  virtual void Show(ChromotingHost* host,
46                    const DisconnectCallback& disconnect_callback,
47                    const std::string& username) OVERRIDE;
48  virtual void Hide() OVERRIDE;
49
50private:
51  static BOOL CALLBACK DialogProc(HWND hwmd, UINT msg, WPARAM wParam,
52                                  LPARAM lParam);
53
54  BOOL OnDialogMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
55
56  void ShutdownHost();
57  void EndDialog();
58  void SetStrings(const UiStrings& strings, const std::string& username);
59  void SetDialogPosition();
60
61  DisconnectCallback disconnect_callback_;
62  HWND hwnd_;
63  bool has_hotkey_;
64  base::win::ScopedGDIObject<HPEN> border_pen_;
65
66  DISALLOW_COPY_AND_ASSIGN(DisconnectWindowWin);
67};
68
69DisconnectWindowWin::DisconnectWindowWin()
70    : hwnd_(NULL),
71      has_hotkey_(false),
72      border_pen_(CreatePen(PS_SOLID, 5,
73                            RGB(0.13 * 255, 0.69 * 255, 0.11 * 255))) {
74}
75
76DisconnectWindowWin::~DisconnectWindowWin() {
77  EndDialog();
78}
79
80BOOL CALLBACK DisconnectWindowWin::DialogProc(HWND hwnd, UINT msg,
81                                              WPARAM wParam, LPARAM lParam) {
82  DisconnectWindowWin* win = NULL;
83  if (msg == WM_INITDIALOG) {
84    win = reinterpret_cast<DisconnectWindowWin*>(lParam);
85    CHECK(win);
86    SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)win);
87  } else {
88    LONG_PTR lp = GetWindowLongPtr(hwnd, DWLP_USER);
89    win = reinterpret_cast<DisconnectWindowWin*>(lp);
90  }
91  if (win == NULL)
92    return FALSE;
93  return win->OnDialogMessage(hwnd, msg, wParam, lParam);
94}
95
96BOOL DisconnectWindowWin::OnDialogMessage(HWND hwnd, UINT msg,
97                                          WPARAM wParam, LPARAM lParam) {
98  switch (msg) {
99    // Ignore close messages.
100    case WM_CLOSE:
101      return TRUE;
102
103    // Handle the Disconnect button.
104    case WM_COMMAND:
105      switch (LOWORD(wParam)) {
106        case IDC_DISCONNECT:
107          EndDialog();
108          ShutdownHost();
109          return TRUE;
110      }
111      return FALSE;
112
113    // Ensure we don't try to use the HWND anymore.
114    case WM_DESTROY:
115      hwnd_ = NULL;
116      return TRUE;
117
118    // Ensure the dialog stays visible if the work area dimensions change.
119    case WM_SETTINGCHANGE:
120      if (wParam == SPI_SETWORKAREA)
121        SetDialogPosition();
122      return TRUE;
123
124    // Ensure the dialog stays visible if the display dimensions change.
125    case WM_DISPLAYCHANGE:
126      SetDialogPosition();
127      return TRUE;
128
129    // Handle the disconnect hot-key.
130    case WM_HOTKEY:
131      EndDialog();
132      ShutdownHost();
133      return TRUE;
134
135    // Let the window be draggable by its client area by responding
136    // that the entire window is the title bar.
137    case WM_NCHITTEST:
138      SetWindowLong(hwnd, DWL_MSGRESULT, HTCAPTION);
139      return TRUE;
140
141    case WM_PAINT: {
142      PAINTSTRUCT ps;
143      HDC hdc = BeginPaint(hwnd_, &ps);
144      RECT rect;
145      GetClientRect(hwnd_, &rect);
146      {
147        base::win::ScopedSelectObject border(hdc, border_pen_);
148        base::win::ScopedSelectObject brush(hdc, GetStockObject(NULL_BRUSH));
149        RoundRect(hdc, rect.left, rect.top, rect.right - 1, rect.bottom - 1,
150                  kWindowBorderRadius, kWindowBorderRadius);
151      }
152      EndPaint(hwnd_, &ps);
153      return TRUE;
154    }
155  }
156  return FALSE;
157}
158
159void DisconnectWindowWin::Show(ChromotingHost* host,
160                               const DisconnectCallback& disconnect_callback,
161                               const std::string& username) {
162  CHECK(!hwnd_);
163  disconnect_callback_ = disconnect_callback;
164
165  // Load the dialog resource so that we can modify the RTL flags if necessary.
166  // This is taken from chrome/default_plugin/install_dialog.cc
167  HRSRC dialog_resource =
168      FindResource(g_hModule, MAKEINTRESOURCE(IDD_DISCONNECT), RT_DIALOG);
169  CHECK(dialog_resource);
170  HGLOBAL dialog_template = LoadResource(g_hModule, dialog_resource);
171  CHECK(dialog_template);
172  DLGTEMPLATE* dialog_pointer =
173      reinterpret_cast<DLGTEMPLATE*>(LockResource(dialog_template));
174  CHECK(dialog_pointer);
175
176  // The actual resource type is DLGTEMPLATEEX, but this is not defined in any
177  // standard headers, so we treat it as a generic pointer and manipulate the
178  // correct offsets explicitly.
179  scoped_ptr<unsigned char> rtl_dialog_template;
180  if (host->ui_strings().direction == UiStrings::RTL) {
181    unsigned long dialog_template_size =
182        SizeofResource(g_hModule, dialog_resource);
183    rtl_dialog_template.reset(new unsigned char[dialog_template_size]);
184    memcpy(rtl_dialog_template.get(), dialog_pointer, dialog_template_size);
185    DWORD* rtl_dwords = reinterpret_cast<DWORD*>(rtl_dialog_template.get());
186    rtl_dwords[2] |= (WS_EX_LAYOUTRTL | WS_EX_RTLREADING);
187    dialog_pointer = reinterpret_cast<DLGTEMPLATE*>(rtl_dwords);
188  }
189
190  hwnd_ = CreateDialogIndirectParam(g_hModule, dialog_pointer, NULL,
191                                    (DLGPROC)DialogProc, (LPARAM)this);
192  CHECK(hwnd_);
193
194  // Set up handler for Ctrl-Alt-Esc shortcut.
195  if (!has_hotkey_ && RegisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID,
196                                     MOD_ALT | MOD_CONTROL, VK_ESCAPE)) {
197    has_hotkey_ = true;
198  }
199
200  SetStrings(host->ui_strings(), username);
201  SetDialogPosition();
202  ShowWindow(hwnd_, SW_SHOW);
203}
204
205void DisconnectWindowWin::ShutdownHost() {
206  CHECK(!disconnect_callback_.is_null());
207  disconnect_callback_.Run();
208}
209
210static int GetControlTextWidth(HWND control) {
211  RECT rect = {0, 0, 0, 0};
212  WCHAR text[256];
213  int result = GetWindowText(control, text, arraysize(text));
214  if (result) {
215    base::win::ScopedGetDC dc(control);
216    base::win::ScopedSelectObject font(
217        dc, (HFONT)SendMessage(control, WM_GETFONT, 0, 0));
218    DrawText(dc, text, -1, &rect, DT_CALCRECT|DT_SINGLELINE);
219  }
220  return rect.right;
221}
222
223void DisconnectWindowWin::SetStrings(const UiStrings& strings,
224                                     const std::string& username) {
225  SetWindowText(hwnd_, strings.product_name.c_str());
226
227  HWND hwndButton = GetDlgItem(hwnd_, IDC_DISCONNECT);
228  CHECK(hwndButton);
229  const WCHAR* label =
230    has_hotkey_ ? strings.disconnect_button_text_plus_shortcut.c_str()
231                : strings.disconnect_button_text.c_str();
232  int button_old_required_width = GetControlTextWidth(hwndButton);
233  SetWindowText(hwndButton, label);
234  int button_new_required_width = GetControlTextWidth(hwndButton);
235
236  HWND hwndSharingWith = GetDlgItem(hwnd_, IDC_DISCONNECT_SHARINGWITH);
237  CHECK(hwndSharingWith);
238  string16 text = ReplaceStringPlaceholders(
239      strings.disconnect_message, UTF8ToUTF16(username), NULL);
240  int label_old_required_width = GetControlTextWidth(hwndSharingWith);
241  SetWindowText(hwndSharingWith, text.c_str());
242  int label_new_required_width = GetControlTextWidth(hwndSharingWith);
243
244  int label_width_delta = label_new_required_width - label_old_required_width;
245  int button_width_delta =
246      button_new_required_width - button_old_required_width;
247
248  // Reposition the controls such that the label lies to the left of the
249  // disconnect button (assuming LTR layout). The dialog template determines
250  // the controls' spacing; update their size to fit the localized content.
251  RECT label_rect;
252  GetClientRect(hwndSharingWith, &label_rect);
253  SetWindowPos(hwndSharingWith, NULL, 0, 0,
254               label_rect.right + label_width_delta, label_rect.bottom,
255               SWP_NOMOVE|SWP_NOZORDER);
256
257  RECT button_rect;
258  GetWindowRect(hwndButton, &button_rect);
259  int button_width = button_rect.right - button_rect.left;
260  int button_height = button_rect.bottom - button_rect.top;
261  MapWindowPoints(NULL, hwnd_, reinterpret_cast<LPPOINT>(&button_rect), 2);
262  SetWindowPos(hwndButton, NULL,
263               button_rect.left + label_width_delta, button_rect.top,
264               button_width + button_width_delta, button_height, SWP_NOZORDER);
265
266  RECT window_rect;
267  GetWindowRect(hwnd_, &window_rect);
268  int width = (window_rect.right - window_rect.left) +
269      button_width_delta + label_width_delta;
270  int height = window_rect.bottom - window_rect.top;
271  SetWindowPos(hwnd_, NULL, 0, 0, width, height, SWP_NOMOVE|SWP_NOZORDER);
272  HRGN rgn = CreateRoundRectRgn(0, 0, width, height, kWindowBorderRadius,
273                                kWindowBorderRadius);
274  SetWindowRgn(hwnd_, rgn, TRUE);
275}
276
277void DisconnectWindowWin::SetDialogPosition() {
278  // Try to center the window above the task-bar. If that fails, use the
279  // primary monitor. If that fails (very unlikely), use the default position.
280  HWND taskbar = FindWindow(kShellTrayWindowName, NULL);
281  HMONITOR monitor = MonitorFromWindow(taskbar, MONITOR_DEFAULTTOPRIMARY);
282  MONITORINFO monitor_info = {sizeof(monitor_info)};
283  RECT window_rect;
284  if (GetMonitorInfo(monitor, &monitor_info) &&
285      GetWindowRect(hwnd_, &window_rect)) {
286    int window_width = window_rect.right - window_rect.left;
287    int window_height = window_rect.bottom - window_rect.top;
288    int top = monitor_info.rcWork.bottom - window_height;
289    int left = (monitor_info.rcWork.right + monitor_info.rcWork.left -
290        window_width) / 2;
291    SetWindowPos(hwnd_, NULL, left, top, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
292  }
293}
294
295void DisconnectWindowWin::Hide() {
296  EndDialog();
297}
298
299void DisconnectWindowWin::EndDialog() {
300  if (has_hotkey_) {
301    UnregisterHotKey(hwnd_, DISCONNECT_HOTKEY_ID);
302    has_hotkey_ = false;
303  }
304
305  if (hwnd_) {
306    ::DestroyWindow(hwnd_);
307    hwnd_ = NULL;
308  }
309}
310
311scoped_ptr<DisconnectWindow> DisconnectWindow::Create() {
312  return scoped_ptr<DisconnectWindow>(new DisconnectWindowWin());
313}
314
315}  // namespace remoting
316